mirror of
https://github.com/danbulant/cushy
synced 2026-07-05 19:20:36 +00:00
Merge branch 'virtual-scroll-list-example'
This commit is contained in:
commit
5adb37d2f1
5 changed files with 191 additions and 79 deletions
|
|
@ -249,6 +249,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
default is `true`, which was the behavior before this flag was added.
|
default is `true`, which was the behavior before this flag was added.
|
||||||
- `Image` now supports `ImageCornerRadius`. Thanks to @danbulant for helping
|
- `Image` now supports `ImageCornerRadius`. Thanks to @danbulant for helping
|
||||||
with this change!
|
with this change!
|
||||||
|
- `Scroll` now exposes its scroll amount, maximum scroll, and more information
|
||||||
|
that allows completely customizing a scroll view's behavior. Thanks to
|
||||||
|
@danbulant for helping with this change!
|
||||||
|
|
||||||
|
|
||||||
[139]: https://github.com/khonsulabs/cushy/issues/139
|
[139]: https://github.com/khonsulabs/cushy/issues/139
|
||||||
|
|
|
||||||
65
examples/virtual-scroll-list.rs
Normal file
65
examples/virtual-scroll-list.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use cushy::styles::{Dimension, DimensionRange, Edges};
|
||||||
|
use cushy::value::{Destination, Dynamic, Source};
|
||||||
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::Run;
|
||||||
|
use figures::units::{Lp, UPx};
|
||||||
|
use figures::{Point, Size};
|
||||||
|
|
||||||
|
fn list() -> impl MakeWidget {
|
||||||
|
let height = Lp::inches(10);
|
||||||
|
let content_size: Dynamic<Size<UPx>> = Dynamic::default();
|
||||||
|
let control_size = Dynamic::default();
|
||||||
|
let current_scroll: Dynamic<Point<UPx>> = Dynamic::default();
|
||||||
|
let max_scroll = Dynamic::default();
|
||||||
|
|
||||||
|
let content = content_size.map_each(|s| format!("Content size: {:?};", s));
|
||||||
|
let control = control_size.map_each(|s| format!("Control size: {:?};", s));
|
||||||
|
let scroll = current_scroll.map_each(|s| format!("Current scroll: {:?};", s));
|
||||||
|
let max = max_scroll.map_each(|s| format!("Max scroll: {:?};", s));
|
||||||
|
|
||||||
|
let content = content
|
||||||
|
.and(control)
|
||||||
|
.and(scroll)
|
||||||
|
.and(max)
|
||||||
|
.into_columns()
|
||||||
|
.and("Hello world!")
|
||||||
|
.into_rows()
|
||||||
|
.pad_by(current_scroll.map_each(|scroll| Edges {
|
||||||
|
top: Dimension::from(scroll.y),
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.size(Size::new(
|
||||||
|
DimensionRange::default(),
|
||||||
|
DimensionRange::from(height),
|
||||||
|
));
|
||||||
|
|
||||||
|
let scroll = content.scroll();
|
||||||
|
|
||||||
|
scroll
|
||||||
|
.content_size()
|
||||||
|
.for_each_cloned(move |s| content_size.set(s))
|
||||||
|
.persist();
|
||||||
|
scroll
|
||||||
|
.control_size()
|
||||||
|
.for_each_cloned(move |s| control_size.set(s))
|
||||||
|
.persist();
|
||||||
|
scroll
|
||||||
|
.scroll
|
||||||
|
.for_each_cloned(move |s| current_scroll.set(s))
|
||||||
|
.persist();
|
||||||
|
scroll
|
||||||
|
.max_scroll()
|
||||||
|
.for_each_cloned(move |s| max_scroll.set(s))
|
||||||
|
.persist();
|
||||||
|
|
||||||
|
scroll.expand()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> cushy::Result {
|
||||||
|
list().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runs() {
|
||||||
|
cushy::example!(list).untested_still_frame();
|
||||||
|
}
|
||||||
|
|
@ -63,7 +63,8 @@ change. Let's revisit the example from the [intro](../intro.md):
|
||||||
Both the [`Input`][input] and the [`Label`][label] widgets have been given
|
Both the [`Input`][input] and the [`Label`][label] widgets have been given
|
||||||
instances of `Dynamic<String>`s, but they are two different dynamics. The text
|
instances of `Dynamic<String>`s, but they are two different dynamics. The text
|
||||||
input field was given the dynamic we want to be edited. We react to the changes
|
input field was given the dynamic we want to be edited. We react to the changes
|
||||||
through the `name.map_each(...)` callback.
|
through the `name.map_each(...)` callback. You can react to multiple `Dynamic`s
|
||||||
|
at once using `(&name, &surname).map_each(...)` callback.
|
||||||
|
|
||||||
## What is a `DynamicReader<T>`?
|
## What is a `DynamicReader<T>`?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -757,6 +757,12 @@ impl Default for Dimension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<UPx> for Dimension {
|
||||||
|
fn from(value: UPx) -> Self {
|
||||||
|
Self::Px(value.into_signed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Px> for Dimension {
|
impl From<Px> for Dimension {
|
||||||
fn from(value: Px) -> Self {
|
fn from(value: Px) -> Self {
|
||||||
Self::Px(value)
|
Self::Px(value)
|
||||||
|
|
@ -1212,7 +1218,7 @@ impl NamedComponent for Cow<'_, ComponentName> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type describing characteristics about the edges of a rectangle.
|
/// A type describing characteristics about the edges of a rectangle.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Edges<T = FlexibleDimension> {
|
pub struct Edges<T = FlexibleDimension> {
|
||||||
/// The left edge
|
/// The left edge
|
||||||
pub left: T,
|
pub left: T,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, Zer
|
||||||
use crate::context::{AsEventContext, EventContext, LayoutContext};
|
use crate::context::{AsEventContext, EventContext, LayoutContext};
|
||||||
use crate::styles::components::{EasingIn, EasingOut, LineHeight};
|
use crate::styles::components::{EasingIn, EasingOut, LineHeight};
|
||||||
use crate::styles::Dimension;
|
use crate::styles::Dimension;
|
||||||
use crate::value::{Destination, Dynamic, IntoValue, Source, Value};
|
use crate::value::{Destination, Dynamic, DynamicReader, IntoValue, Source, Value};
|
||||||
use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
|
use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
|
||||||
use crate::window::DeviceId;
|
use crate::window::DeviceId;
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
@ -22,18 +22,23 @@ use crate::ConstraintLimit;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scroll {
|
pub struct Scroll {
|
||||||
contents: WidgetRef,
|
contents: WidgetRef,
|
||||||
content_size: Size<Px>,
|
content_size: Dynamic<Size<UPx>>,
|
||||||
control_size: Size<Px>,
|
control_size: Dynamic<Size<UPx>>,
|
||||||
scroll: Dynamic<Point<Px>>,
|
/// The current scroll position.
|
||||||
|
///
|
||||||
|
/// When a new value is assigned to this, this widget will scroll its
|
||||||
|
/// contents. If a value is out of bounds of the maximum scroll, it will be
|
||||||
|
/// clamped and this dynamic will be updated with clamped scroll.
|
||||||
|
pub scroll: Dynamic<Point<UPx>>,
|
||||||
enabled: Point<bool>,
|
enabled: Point<bool>,
|
||||||
preserve_max_scroll: Value<bool>,
|
preserve_max_scroll: Value<bool>,
|
||||||
max_scroll: Dynamic<Point<Px>>,
|
max_scroll: Dynamic<Point<UPx>>,
|
||||||
scrollbar_opacity: Dynamic<ZeroToOne>,
|
scrollbar_opacity: Dynamic<ZeroToOne>,
|
||||||
scrollbar_opacity_animation: OpacityAnimationState,
|
scrollbar_opacity_animation: OpacityAnimationState,
|
||||||
horizontal_bar: ScrollbarInfo,
|
horizontal_bar: ScrollbarInfo,
|
||||||
vertical_bar: ScrollbarInfo,
|
vertical_bar: ScrollbarInfo,
|
||||||
bar_width: Px,
|
bar_width: UPx,
|
||||||
line_height: Px,
|
line_height: UPx,
|
||||||
drag: DragInfo,
|
drag: DragInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,8 +55,8 @@ impl Scroll {
|
||||||
Self {
|
Self {
|
||||||
contents: WidgetRef::new(contents),
|
contents: WidgetRef::new(contents),
|
||||||
enabled,
|
enabled,
|
||||||
content_size: Size::default(),
|
content_size: Dynamic::new(Size::default()),
|
||||||
control_size: Size::default(),
|
control_size: Dynamic::new(Size::default()),
|
||||||
scroll: Dynamic::new(Point::default()),
|
scroll: Dynamic::new(Point::default()),
|
||||||
max_scroll: Dynamic::new(Point::default()),
|
max_scroll: Dynamic::new(Point::default()),
|
||||||
scrollbar_opacity: Dynamic::default(),
|
scrollbar_opacity: Dynamic::default(),
|
||||||
|
|
@ -62,8 +67,8 @@ impl Scroll {
|
||||||
},
|
},
|
||||||
horizontal_bar: ScrollbarInfo::default(),
|
horizontal_bar: ScrollbarInfo::default(),
|
||||||
vertical_bar: ScrollbarInfo::default(),
|
vertical_bar: ScrollbarInfo::default(),
|
||||||
bar_width: Px::default(),
|
bar_width: UPx::default(),
|
||||||
line_height: Px::default(),
|
line_height: UPx::default(),
|
||||||
drag: DragInfo::default(),
|
drag: DragInfo::default(),
|
||||||
preserve_max_scroll: Value::Constant(true),
|
preserve_max_scroll: Value::Constant(true),
|
||||||
}
|
}
|
||||||
|
|
@ -98,11 +103,31 @@ impl Scroll {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constrained_scroll(scroll: Point<Px>, max_scroll: Point<Px>) -> Point<Px> {
|
/// Returns a reader for the maximum scroll value.
|
||||||
scroll.max(max_scroll).min(Point::default())
|
///
|
||||||
|
/// This represents the maximum amount that the scroll can be moved by.
|
||||||
|
#[must_use]
|
||||||
|
pub fn max_scroll(&self) -> DynamicReader<Point<UPx>> {
|
||||||
|
self.max_scroll.create_reader()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constrain_scroll(&mut self) -> (Point<Px>, Point<Px>) {
|
/// Returns a reader for the size of the scrollable area.
|
||||||
|
#[must_use]
|
||||||
|
pub fn content_size(&self) -> DynamicReader<Size<UPx>> {
|
||||||
|
self.content_size.create_reader()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reader for the size of this Scroll widget.
|
||||||
|
#[must_use]
|
||||||
|
pub fn control_size(&self) -> DynamicReader<Size<UPx>> {
|
||||||
|
self.control_size.create_reader()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constrained_scroll(scroll: Point<UPx>, max_scroll: Point<UPx>) -> Point<UPx> {
|
||||||
|
scroll.min(max_scroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constrain_scroll(&mut self) -> (Point<UPx>, Point<UPx>) {
|
||||||
let scroll = self.scroll.get();
|
let scroll = self.scroll.get();
|
||||||
let max_scroll = self.max_scroll.get();
|
let max_scroll = self.max_scroll.get();
|
||||||
let clamped = Self::constrained_scroll(scroll, max_scroll);
|
let clamped = Self::constrained_scroll(scroll, max_scroll);
|
||||||
|
|
@ -187,14 +212,15 @@ impl Widget for Scroll {
|
||||||
let managed = self.contents.mounted(&mut context.as_event_context());
|
let managed = self.contents.mounted(&mut context.as_event_context());
|
||||||
context.for_other(&managed).redraw();
|
context.for_other(&managed).redraw();
|
||||||
|
|
||||||
let size = context.gfx.region().size;
|
let size = context.gfx.region().size.into_unsigned();
|
||||||
|
|
||||||
if self.horizontal_bar.amount_hidden > 0 {
|
if self.horizontal_bar.amount_hidden > 0 {
|
||||||
context.gfx.draw_shape(&Shape::filled_rect(
|
context.gfx.draw_shape(&Shape::filled_rect(
|
||||||
Rect::new(
|
Rect::new(
|
||||||
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
|
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
|
||||||
Size::new(self.horizontal_bar.size, self.bar_width),
|
Size::new(self.horizontal_bar.size, self.bar_width),
|
||||||
),
|
)
|
||||||
|
.into_signed(), // See https://github.com/khonsulabs/cushy/issues/186
|
||||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +230,8 @@ impl Widget for Scroll {
|
||||||
Rect::new(
|
Rect::new(
|
||||||
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
|
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
|
||||||
Size::new(self.bar_width, self.vertical_bar.size),
|
Size::new(self.bar_width, self.vertical_bar.size),
|
||||||
),
|
)
|
||||||
|
.into_signed(), // See https://github.com/khonsulabs/cushy/issues/186
|
||||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -217,8 +244,8 @@ impl Widget for Scroll {
|
||||||
) -> Size<UPx> {
|
) -> Size<UPx> {
|
||||||
self.bar_width = context
|
self.bar_width = context
|
||||||
.get(&ScrollBarThickness)
|
.get(&ScrollBarThickness)
|
||||||
.into_px(context.gfx.scale());
|
.into_upx(context.gfx.scale());
|
||||||
self.line_height = context.get(&LineHeight).into_px(context.gfx.scale());
|
self.line_height = context.get(&LineHeight).into_upx(context.gfx.scale());
|
||||||
|
|
||||||
let (mut scroll, current_max_scroll) = self.constrain_scroll();
|
let (mut scroll, current_max_scroll) = self.constrain_scroll();
|
||||||
|
|
||||||
|
|
@ -235,12 +262,9 @@ impl Widget for Scroll {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let managed = self.contents.mounted(&mut context.as_event_context());
|
let managed = self.contents.mounted(&mut context.as_event_context());
|
||||||
let new_content_size = context
|
let new_content_size = context.for_other(&managed).layout(max_extents);
|
||||||
.for_other(&managed)
|
|
||||||
.layout(max_extents)
|
|
||||||
.into_signed();
|
|
||||||
|
|
||||||
let layout_size = Size::new(
|
let new_control_size = Size::new(
|
||||||
if self.enabled.x {
|
if self.enabled.x {
|
||||||
constrain_child(available_space.width, new_content_size.width)
|
constrain_child(available_space.width, new_content_size.width)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -252,22 +276,21 @@ impl Widget for Scroll {
|
||||||
new_content_size.height.into_unsigned()
|
new_content_size.height.into_unsigned()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let control_size = layout_size.into_signed();
|
|
||||||
|
|
||||||
self.horizontal_bar =
|
self.horizontal_bar =
|
||||||
scrollbar_region(scroll.x, new_content_size.width, control_size.width);
|
scrollbar_region(scroll.x, new_content_size.width, new_control_size.width);
|
||||||
let max_scroll_x = if self.enabled.x {
|
let max_scroll_x = if self.enabled.x {
|
||||||
-self.horizontal_bar.amount_hidden
|
self.horizontal_bar.amount_hidden
|
||||||
} else {
|
} else {
|
||||||
Px::ZERO
|
UPx::ZERO
|
||||||
};
|
};
|
||||||
|
|
||||||
self.vertical_bar =
|
self.vertical_bar =
|
||||||
scrollbar_region(scroll.y, new_content_size.height, control_size.height);
|
scrollbar_region(scroll.y, new_content_size.height, new_control_size.height);
|
||||||
let max_scroll_y = if self.enabled.y {
|
let max_scroll_y = if self.enabled.y {
|
||||||
-self.vertical_bar.amount_hidden
|
self.vertical_bar.amount_hidden
|
||||||
} else {
|
} else {
|
||||||
Px::ZERO
|
UPx::ZERO
|
||||||
};
|
};
|
||||||
let new_max_scroll = Point::new(max_scroll_x, max_scroll_y);
|
let new_max_scroll = Point::new(max_scroll_x, max_scroll_y);
|
||||||
if current_max_scroll != new_max_scroll {
|
if current_max_scroll != new_max_scroll {
|
||||||
|
|
@ -275,24 +298,29 @@ impl Widget for Scroll {
|
||||||
scroll = scroll.max(new_max_scroll);
|
scroll = scroll.max(new_max_scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is not tracked on purpose - it's only ever changed in layout
|
||||||
|
let content_size = self.content_size.get();
|
||||||
|
let control_size = self.control_size.get();
|
||||||
|
|
||||||
// Preserve the current scroll if the widget has resized
|
// Preserve the current scroll if the widget has resized
|
||||||
if self.content_size.width != new_content_size.width
|
if content_size != Size::ZERO && content_size != new_content_size {
|
||||||
|| self.control_size.width != control_size.width
|
if (content_size.width != new_content_size.width
|
||||||
{
|
|| control_size.width != new_control_size.width)
|
||||||
self.content_size.width = new_content_size.width;
|
&& scroll.x == current_max_scroll.x
|
||||||
if self.preserve_max_scroll.get() && scroll.x == current_max_scroll.x {
|
&& self.preserve_max_scroll.get()
|
||||||
|
{
|
||||||
scroll.x = max_scroll_x;
|
scroll.x = max_scroll_x;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if self.content_size.height != new_content_size.height
|
if (content_size.height != new_content_size.height
|
||||||
|| self.control_size.height != control_size.height
|
|| control_size.height != new_control_size.height)
|
||||||
{
|
&& scroll.y == current_max_scroll.y
|
||||||
self.content_size.height = new_content_size.height;
|
&& self.preserve_max_scroll.get()
|
||||||
if self.preserve_max_scroll.get() && scroll.y == current_max_scroll.y {
|
{
|
||||||
scroll.y = max_scroll_y;
|
scroll.y = max_scroll_y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current scroll, but prevent immediately triggering
|
// Set the current scroll, but prevent immediately triggering
|
||||||
// invalidate.
|
// invalidate.
|
||||||
{
|
{
|
||||||
|
|
@ -301,17 +329,18 @@ impl Widget for Scroll {
|
||||||
*current_scroll = scroll;
|
*current_scroll = scroll;
|
||||||
}
|
}
|
||||||
context.invalidate_when_changed(&self.scroll);
|
context.invalidate_when_changed(&self.scroll);
|
||||||
self.control_size = control_size;
|
self.control_size.set(new_control_size);
|
||||||
self.content_size = new_content_size;
|
self.content_size.set(new_content_size);
|
||||||
|
|
||||||
let region = Rect::new(
|
let region = Rect::new(
|
||||||
scroll,
|
-scroll.into_signed(),
|
||||||
self.content_size
|
new_content_size
|
||||||
.min(Size::new(Px::MAX, Px::MAX) - scroll.max(Point::default())),
|
.min(Size::new(UPx::MAX, UPx::MAX) - scroll.max(Point::default()))
|
||||||
|
.into_signed(),
|
||||||
);
|
);
|
||||||
context.set_child_layout(&managed, region);
|
context.set_child_layout(&managed, region);
|
||||||
|
|
||||||
layout_size
|
new_control_size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_wheel(
|
fn mouse_wheel(
|
||||||
|
|
@ -327,8 +356,10 @@ impl Widget for Scroll {
|
||||||
};
|
};
|
||||||
let mut scroll = self.scroll.lock();
|
let mut scroll = self.scroll.lock();
|
||||||
let old_scroll = *scroll;
|
let old_scroll = *scroll;
|
||||||
let new_scroll =
|
let new_scroll = Self::constrained_scroll(
|
||||||
Self::constrained_scroll(*scroll + amount.cast::<Px>(), self.max_scroll.get());
|
(scroll.into_signed() - amount.cast::<Px>()).into_unsigned(),
|
||||||
|
self.max_scroll.get(),
|
||||||
|
);
|
||||||
if old_scroll == new_scroll {
|
if old_scroll == new_scroll {
|
||||||
IGNORED
|
IGNORED
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -349,10 +380,12 @@ impl Widget for Scroll {
|
||||||
_button: kludgine::app::winit::event::MouseButton,
|
_button: kludgine::app::winit::event::MouseButton,
|
||||||
context: &mut EventContext<'_>,
|
context: &mut EventContext<'_>,
|
||||||
) -> EventHandling {
|
) -> EventHandling {
|
||||||
let relative_x = (self.control_size.width - location.x).max(Px::ZERO);
|
let control_size = self.control_size.get();
|
||||||
|
|
||||||
|
let relative_x = (control_size.width.into_signed() - location.x).into_unsigned();
|
||||||
let in_vertical_area = self.enabled.y && relative_x <= self.bar_width;
|
let in_vertical_area = self.enabled.y && relative_x <= self.bar_width;
|
||||||
|
|
||||||
let relative_y = (self.control_size.height - location.y).max(Px::ZERO);
|
let relative_y = (control_size.height.into_signed() - location.y).into_unsigned();
|
||||||
let in_horizontal_area = self.enabled.x && relative_y <= self.bar_width;
|
let in_horizontal_area = self.enabled.x && relative_y <= self.bar_width;
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
|
|
@ -362,14 +395,14 @@ impl Widget for Scroll {
|
||||||
return IGNORED;
|
return IGNORED;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drag.start = location;
|
self.drag.start = location.into_signed();
|
||||||
self.drag.start_scroll = self.scroll.get();
|
self.drag.start_scroll = self.scroll.get();
|
||||||
self.drag.horizontal = in_horizontal_area;
|
self.drag.horizontal = in_horizontal_area;
|
||||||
self.drag.in_bar = if in_horizontal_area {
|
self.drag.in_bar = if in_horizontal_area {
|
||||||
let relative = location.x - self.horizontal_bar.offset;
|
let relative = location.x - self.horizontal_bar.offset.into_signed();
|
||||||
relative >= 0 && relative < self.horizontal_bar.size
|
relative >= 0 && relative < self.horizontal_bar.size
|
||||||
} else {
|
} else {
|
||||||
let relative = location.y - self.vertical_bar.offset;
|
let relative = location.y - self.vertical_bar.offset.into_signed();
|
||||||
relative >= 0 && relative < self.vertical_bar.size
|
relative >= 0 && relative < self.vertical_bar.size
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -382,7 +415,7 @@ impl Widget for Scroll {
|
||||||
&self.horizontal_bar,
|
&self.horizontal_bar,
|
||||||
&self.vertical_bar,
|
&self.vertical_bar,
|
||||||
self.max_scroll.get(),
|
self.max_scroll.get(),
|
||||||
self.control_size,
|
control_size,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,7 +438,7 @@ impl Widget for Scroll {
|
||||||
&self.horizontal_bar,
|
&self.horizontal_bar,
|
||||||
&self.vertical_bar,
|
&self.vertical_bar,
|
||||||
self.max_scroll.get(),
|
self.max_scroll.get(),
|
||||||
self.control_size,
|
self.control_size.get(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,7 +453,9 @@ impl Widget for Scroll {
|
||||||
|
|
||||||
if self.drag.mouse_buttons_down == 0 {
|
if self.drag.mouse_buttons_down == 0 {
|
||||||
if location.map_or(false, |location| {
|
if location.map_or(false, |location| {
|
||||||
Rect::from(self.control_size).contains(location)
|
Rect::from(self.control_size.get())
|
||||||
|
.into_signed()
|
||||||
|
.contains(location)
|
||||||
}) {
|
}) {
|
||||||
self.scrollbar_opacity_animation.handle.clear();
|
self.scrollbar_opacity_animation.handle.clear();
|
||||||
self.show_scrollbars(context);
|
self.show_scrollbars(context);
|
||||||
|
|
@ -442,7 +477,7 @@ impl Widget for Scroll {
|
||||||
struct DragInfo {
|
struct DragInfo {
|
||||||
mouse_buttons_down: usize,
|
mouse_buttons_down: usize,
|
||||||
start: Point<Px>,
|
start: Point<Px>,
|
||||||
start_scroll: Point<Px>,
|
start_scroll: Point<UPx>,
|
||||||
horizontal: bool,
|
horizontal: bool,
|
||||||
in_bar: bool,
|
in_bar: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -451,11 +486,11 @@ impl DragInfo {
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
location: Point<Px>,
|
location: Point<Px>,
|
||||||
dynamic_scroll: &Dynamic<Point<Px>>,
|
dynamic_scroll: &Dynamic<Point<UPx>>,
|
||||||
horizontal_bar: &ScrollbarInfo,
|
horizontal_bar: &ScrollbarInfo,
|
||||||
vertical_bar: &ScrollbarInfo,
|
vertical_bar: &ScrollbarInfo,
|
||||||
max_scroll: Point<Px>,
|
max_scroll: Point<UPx>,
|
||||||
control_size: Size<Px>,
|
control_size: Size<UPx>,
|
||||||
) {
|
) {
|
||||||
let mut scroll = dynamic_scroll.get();
|
let mut scroll = dynamic_scroll.get();
|
||||||
if self.horizontal {
|
if self.horizontal {
|
||||||
|
|
@ -484,33 +519,35 @@ impl DragInfo {
|
||||||
&self,
|
&self,
|
||||||
location: Px,
|
location: Px,
|
||||||
start: Px,
|
start: Px,
|
||||||
max_scroll: Px,
|
max_scroll: UPx,
|
||||||
start_scroll: Px,
|
start_scroll: UPx,
|
||||||
bar: &ScrollbarInfo,
|
bar: &ScrollbarInfo,
|
||||||
control_size: Px,
|
control_size: UPx,
|
||||||
) -> Px {
|
) -> UPx {
|
||||||
if self.in_bar {
|
if self.in_bar {
|
||||||
let dy = location - start;
|
let dy = location - start;
|
||||||
if dy == 0 {
|
if dy == 0 {
|
||||||
start_scroll
|
start_scroll
|
||||||
} else {
|
} else {
|
||||||
(start_scroll
|
(start_scroll.into_signed()
|
||||||
- Px::from(
|
+ Px::from(
|
||||||
dy.into_float() / (control_size - bar.size).into_float()
|
dy.into_float() / (control_size - bar.size).into_float()
|
||||||
* bar.amount_hidden.into_float(),
|
* bar.amount_hidden.into_float(),
|
||||||
))
|
))
|
||||||
.clamp(max_scroll, Px::ZERO)
|
.into_unsigned()
|
||||||
|
.min(max_scroll)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
max_scroll
|
max_scroll
|
||||||
* ((location - bar.size / 2).max(Px::ZERO).into_float()
|
* ((location - bar.size.into_signed() / 2)
|
||||||
|
.max(Px::ZERO)
|
||||||
|
.into_float()
|
||||||
/ (control_size - bar.size).into_float())
|
/ (control_size - bar.size).into_float())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx {
|
fn constrain_child(constraint: ConstraintLimit, measured: UPx) -> UPx {
|
||||||
let measured = measured.into_unsigned();
|
|
||||||
match constraint {
|
match constraint {
|
||||||
ConstraintLimit::Fill(size) => size.min(measured),
|
ConstraintLimit::Fill(size) => size.min(measured),
|
||||||
ConstraintLimit::SizeToFit(_) => measured,
|
ConstraintLimit::SizeToFit(_) => measured,
|
||||||
|
|
@ -519,18 +556,18 @@ fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct ScrollbarInfo {
|
struct ScrollbarInfo {
|
||||||
offset: Px,
|
offset: UPx,
|
||||||
amount_hidden: Px,
|
amount_hidden: UPx,
|
||||||
size: Px,
|
size: UPx,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scrollbar_region(scroll: Px, content_size: Px, control_size: Px) -> ScrollbarInfo {
|
fn scrollbar_region(scroll: UPx, content_size: UPx, control_size: UPx) -> ScrollbarInfo {
|
||||||
if content_size > control_size {
|
if content_size > control_size {
|
||||||
let amount_hidden = content_size - control_size;
|
let amount_hidden = content_size - control_size;
|
||||||
let ratio_visible = control_size.into_float() / content_size.into_float();
|
let ratio_visible = control_size.into_float() / content_size.into_float();
|
||||||
let bar_size = control_size * ratio_visible;
|
let bar_size = control_size * ratio_visible;
|
||||||
let remaining_area = control_size - bar_size;
|
let remaining_area = control_size - bar_size;
|
||||||
let amount_scrolled = -scroll.into_float() / amount_hidden.into_float();
|
let amount_scrolled = scroll.into_float() / amount_hidden.into_float();
|
||||||
let bar_offset = remaining_area * amount_scrolled;
|
let bar_offset = remaining_area * amount_scrolled;
|
||||||
ScrollbarInfo {
|
ScrollbarInfo {
|
||||||
offset: bar_offset,
|
offset: bar_offset,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue