From e3898bda50f69449b494109c60139f96265fc285 Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Wed, 16 Oct 2024 22:41:42 +0200 Subject: [PATCH 1/2] Add a simple example about reading and synchronizing values from a Scroll --- examples/virtual-scroll-list.rs | 62 +++++++++++++++++++++++++++ guide/src/about/reactive.md | 3 +- src/styles.rs | 2 +- src/widgets/scroll.rs | 76 +++++++++++++++++++++++---------- 4 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 examples/virtual-scroll-list.rs diff --git a/examples/virtual-scroll-list.rs b/examples/virtual-scroll-list.rs new file mode 100644 index 0000000..8149f01 --- /dev/null +++ b/examples/virtual-scroll-list.rs @@ -0,0 +1,62 @@ +use cushy::styles::{Dimension, DimensionRange, Edges}; +use cushy::value::{Destination, Dynamic, Source}; +use cushy::widget::MakeWidget; +use cushy::Run; +use figures::units::{Lp, Px}; +use figures::{Point, Size}; + +fn list() -> impl MakeWidget { + let height = Lp::inches(10); + let content_size: Dynamic> = Dynamic::default(); + let control_size = Dynamic::default(); + let current_scroll: Dynamic> = 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.get_content_size() + .for_each_cloned(move |s| content_size.set(s)) + .persist(); + scroll.get_control_size() + .for_each_cloned(move |s| control_size.set(s)) + .persist(); + scroll.get_scroll() + .for_each_cloned(move |s| current_scroll.set(s)) + .persist(); + scroll.get_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(); +} diff --git a/guide/src/about/reactive.md b/guide/src/about/reactive.md index 5c8ca39..455ca6c 100644 --- a/guide/src/about/reactive.md +++ b/guide/src/about/reactive.md @@ -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 instances of `Dynamic`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 -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`? diff --git a/src/styles.rs b/src/styles.rs index 975c9fc..3413699 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -1212,7 +1212,7 @@ impl NamedComponent for Cow<'_, ComponentName> { } /// A type describing characteristics about the edges of a rectangle. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Edges { /// The left edge pub left: T, diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index e86ba15..1cdeb0a 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -13,7 +13,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, Zer use crate::context::{AsEventContext, EventContext, LayoutContext}; use crate::styles::components::{EasingIn, EasingOut, LineHeight}; 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::window::DeviceId; use crate::ConstraintLimit; @@ -22,9 +22,9 @@ use crate::ConstraintLimit; #[derive(Debug)] pub struct Scroll { contents: WidgetRef, - content_size: Size, - control_size: Size, - scroll: Dynamic>, + content_size: Dynamic>, + control_size: Dynamic>, + pub scroll: Dynamic>, enabled: Point, preserve_max_scroll: Value, max_scroll: Dynamic>, @@ -50,8 +50,8 @@ impl Scroll { Self { contents: WidgetRef::new(contents), enabled, - content_size: Size::default(), - control_size: Size::default(), + content_size: Dynamic::new(Size::default()), + control_size: Dynamic::new(Size::default()), scroll: Dynamic::new(Point::default()), max_scroll: Dynamic::new(Point::default()), scrollbar_opacity: Dynamic::default(), @@ -98,6 +98,30 @@ impl Scroll { self } + /// Creates a reader for the scroll value. + pub fn get_scroll(&self) -> DynamicReader> { + self.scroll.create_reader() + } + + /// Creates a reader for the maximum scroll value. + /// This represents the maximum amount that the scroll can be moved by. + /// This should usually mean that this value + the scroll visual size is the size of the content. + pub fn get_max_scroll(&self) -> DynamicReader> { + self.max_scroll.create_reader() + } + + /// Creates a reader for the content size. + /// This is the size of the content that is being scrolled. + pub fn get_content_size(&self) -> DynamicReader> { + self.content_size.create_reader() + } + + /// Creates a reader for the control size. + /// This is the size of the visible area of the scroll widget. + pub fn get_control_size(&self) -> DynamicReader> { + self.control_size.create_reader() + } + fn constrained_scroll(scroll: Point, max_scroll: Point) -> Point { scroll.max(max_scroll).min(Point::default()) } @@ -252,10 +276,10 @@ impl Widget for Scroll { new_content_size.height.into_unsigned() }, ); - let control_size = layout_size.into_signed(); + let new_control_size = layout_size.into_signed(); 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 { -self.horizontal_bar.amount_hidden } else { @@ -263,7 +287,7 @@ impl Widget for Scroll { }; 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 { -self.vertical_bar.amount_hidden } else { @@ -275,20 +299,24 @@ impl Widget for 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 - if self.content_size.width != new_content_size.width - || 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; + // content_size.width = new_content_size.width; if self.preserve_max_scroll.get() && scroll.x == current_max_scroll.x { scroll.x = max_scroll_x; } } - if self.content_size.height != new_content_size.height - || self.control_size.height != control_size.height + if content_size.height != new_content_size.height + || control_size.height != new_control_size.height { - self.content_size.height = new_content_size.height; + // content_size.height = new_content_size.height; if self.preserve_max_scroll.get() && scroll.y == current_max_scroll.y { scroll.y = max_scroll_y; } @@ -301,12 +329,12 @@ impl Widget for Scroll { *current_scroll = scroll; } context.invalidate_when_changed(&self.scroll); - self.control_size = control_size; - self.content_size = new_content_size; + self.control_size.set(new_control_size); + self.content_size.set(new_content_size); let region = Rect::new( scroll, - self.content_size + new_content_size .min(Size::new(Px::MAX, Px::MAX) - scroll.max(Point::default())), ); context.set_child_layout(&managed, region); @@ -349,10 +377,12 @@ impl Widget for Scroll { _button: kludgine::app::winit::event::MouseButton, context: &mut EventContext<'_>, ) -> 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 - location.x).max(Px::ZERO); 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 - location.y).max(Px::ZERO); let in_horizontal_area = self.enabled.x && relative_y <= self.bar_width; if matches!( @@ -382,7 +412,7 @@ impl Widget for Scroll { &self.horizontal_bar, &self.vertical_bar, self.max_scroll.get(), - self.control_size, + control_size, ); } @@ -405,7 +435,7 @@ impl Widget for Scroll { &self.horizontal_bar, &self.vertical_bar, self.max_scroll.get(), - self.control_size, + self.control_size.get(), ); } @@ -420,7 +450,7 @@ impl Widget for Scroll { if self.drag.mouse_buttons_down == 0 { if location.map_or(false, |location| { - Rect::from(self.control_size).contains(location) + Rect::from(self.control_size.get()).contains(location) }) { self.scrollbar_opacity_animation.handle.clear(); self.show_scrollbars(context); From 51649a3c0516c3269c5ebd8e2f4856421019be35 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Thu, 17 Oct 2024 09:46:32 -0700 Subject: [PATCH 2/2] Refactored Scroll to be UPx Also addressed comments in #185 --- CHANGELOG.md | 3 + examples/virtual-scroll-list.rs | 37 +++---- src/styles.rs | 6 ++ src/widgets/scroll.rs | 171 +++++++++++++++++--------------- 4 files changed, 118 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a502e6..81bb08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,6 +235,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. - `Image` now supports `ImageCornerRadius`. Thanks to @danbulant for helping 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 diff --git a/examples/virtual-scroll-list.rs b/examples/virtual-scroll-list.rs index 8149f01..ce2c3b0 100644 --- a/examples/virtual-scroll-list.rs +++ b/examples/virtual-scroll-list.rs @@ -2,24 +2,20 @@ use cushy::styles::{Dimension, DimensionRange, Edges}; use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::Run; -use figures::units::{Lp, Px}; +use figures::units::{Lp, UPx}; use figures::{Point, Size}; fn list() -> impl MakeWidget { let height = Lp::inches(10); - let content_size: Dynamic> = Dynamic::default(); + let content_size: Dynamic> = Dynamic::default(); let control_size = Dynamic::default(); - let current_scroll: Dynamic> = Dynamic::default(); + let current_scroll: Dynamic> = 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_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) @@ -29,23 +25,30 @@ fn list() -> impl MakeWidget { .and("Hello world!") .into_rows() .pad_by(current_scroll.map_each(|scroll| Edges { - top: Dimension::from(-scroll.y), + top: Dimension::from(scroll.y), ..Default::default() })) - .size(Size::new(DimensionRange::default(), DimensionRange::from(height))); + .size(Size::new( + DimensionRange::default(), + DimensionRange::from(height), + )); let scroll = content.scroll(); - scroll.get_content_size() + scroll + .content_size() .for_each_cloned(move |s| content_size.set(s)) .persist(); - scroll.get_control_size() + scroll + .control_size() .for_each_cloned(move |s| control_size.set(s)) .persist(); - scroll.get_scroll() + scroll + .scroll .for_each_cloned(move |s| current_scroll.set(s)) .persist(); - scroll.get_max_scroll() + scroll + .max_scroll() .for_each_cloned(move |s| max_scroll.set(s)) .persist(); diff --git a/src/styles.rs b/src/styles.rs index 3413699..0606a41 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -757,6 +757,12 @@ impl Default for Dimension { } } +impl From for Dimension { + fn from(value: UPx) -> Self { + Self::Px(value.into_signed()) + } +} + impl From for Dimension { fn from(value: Px) -> Self { Self::Px(value) diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index 1cdeb0a..a015853 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -22,18 +22,23 @@ use crate::ConstraintLimit; #[derive(Debug)] pub struct Scroll { contents: WidgetRef, - content_size: Dynamic>, - control_size: Dynamic>, - pub scroll: Dynamic>, + content_size: Dynamic>, + control_size: Dynamic>, + /// 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>, enabled: Point, preserve_max_scroll: Value, - max_scroll: Dynamic>, + max_scroll: Dynamic>, scrollbar_opacity: Dynamic, scrollbar_opacity_animation: OpacityAnimationState, horizontal_bar: ScrollbarInfo, vertical_bar: ScrollbarInfo, - bar_width: Px, - line_height: Px, + bar_width: UPx, + line_height: UPx, drag: DragInfo, } @@ -62,8 +67,8 @@ impl Scroll { }, horizontal_bar: ScrollbarInfo::default(), vertical_bar: ScrollbarInfo::default(), - bar_width: Px::default(), - line_height: Px::default(), + bar_width: UPx::default(), + line_height: UPx::default(), drag: DragInfo::default(), preserve_max_scroll: Value::Constant(true), } @@ -98,35 +103,31 @@ impl Scroll { self } - /// Creates a reader for the scroll value. - pub fn get_scroll(&self) -> DynamicReader> { - self.scroll.create_reader() - } - - /// Creates a reader for the maximum scroll value. + /// Returns a reader for the maximum scroll value. + /// /// This represents the maximum amount that the scroll can be moved by. - /// This should usually mean that this value + the scroll visual size is the size of the content. - pub fn get_max_scroll(&self) -> DynamicReader> { + #[must_use] + pub fn max_scroll(&self) -> DynamicReader> { self.max_scroll.create_reader() } - /// Creates a reader for the content size. - /// This is the size of the content that is being scrolled. - pub fn get_content_size(&self) -> DynamicReader> { + /// Returns a reader for the size of the scrollable area. + #[must_use] + pub fn content_size(&self) -> DynamicReader> { self.content_size.create_reader() } - /// Creates a reader for the control size. - /// This is the size of the visible area of the scroll widget. - pub fn get_control_size(&self) -> DynamicReader> { + /// Returns a reader for the size of this Scroll widget. + #[must_use] + pub fn control_size(&self) -> DynamicReader> { self.control_size.create_reader() } - fn constrained_scroll(scroll: Point, max_scroll: Point) -> Point { - scroll.max(max_scroll).min(Point::default()) + fn constrained_scroll(scroll: Point, max_scroll: Point) -> Point { + scroll.min(max_scroll) } - fn constrain_scroll(&mut self) -> (Point, Point) { + fn constrain_scroll(&mut self) -> (Point, Point) { let scroll = self.scroll.get(); let max_scroll = self.max_scroll.get(); let clamped = Self::constrained_scroll(scroll, max_scroll); @@ -211,14 +212,15 @@ impl Widget for Scroll { let managed = self.contents.mounted(&mut context.as_event_context()); 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 { context.gfx.draw_shape(&Shape::filled_rect( Rect::new( Point::new(self.horizontal_bar.offset, size.height - 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()), )); } @@ -228,7 +230,8 @@ impl Widget for Scroll { Rect::new( Point::new(size.width - self.bar_width, self.vertical_bar.offset), 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()), )); } @@ -241,8 +244,8 @@ impl Widget for Scroll { ) -> Size { self.bar_width = context .get(&ScrollBarThickness) - .into_px(context.gfx.scale()); - self.line_height = context.get(&LineHeight).into_px(context.gfx.scale()); + .into_upx(context.gfx.scale()); + self.line_height = context.get(&LineHeight).into_upx(context.gfx.scale()); let (mut scroll, current_max_scroll) = self.constrain_scroll(); @@ -259,12 +262,9 @@ impl Widget for Scroll { }, ); let managed = self.contents.mounted(&mut context.as_event_context()); - let new_content_size = context - .for_other(&managed) - .layout(max_extents) - .into_signed(); + let new_content_size = context.for_other(&managed).layout(max_extents); - let layout_size = Size::new( + let new_control_size = Size::new( if self.enabled.x { constrain_child(available_space.width, new_content_size.width) } else { @@ -276,22 +276,21 @@ impl Widget for Scroll { new_content_size.height.into_unsigned() }, ); - let new_control_size = layout_size.into_signed(); self.horizontal_bar = scrollbar_region(scroll.x, new_content_size.width, new_control_size.width); let max_scroll_x = if self.enabled.x { - -self.horizontal_bar.amount_hidden + self.horizontal_bar.amount_hidden } else { - Px::ZERO + UPx::ZERO }; self.vertical_bar = scrollbar_region(scroll.y, new_content_size.height, new_control_size.height); let max_scroll_y = if self.enabled.y { - -self.vertical_bar.amount_hidden + self.vertical_bar.amount_hidden } else { - Px::ZERO + UPx::ZERO }; let new_max_scroll = Point::new(max_scroll_x, max_scroll_y); if current_max_scroll != new_max_scroll { @@ -304,23 +303,24 @@ impl Widget for Scroll { let control_size = self.control_size.get(); // Preserve the current scroll if the widget has resized - if content_size.width != new_content_size.width - || control_size.width != new_control_size.width - { - // content_size.width = new_content_size.width; - if self.preserve_max_scroll.get() && scroll.x == current_max_scroll.x { + if content_size != Size::ZERO && content_size != new_content_size { + if (content_size.width != new_content_size.width + || control_size.width != new_control_size.width) + && scroll.x == current_max_scroll.x + && self.preserve_max_scroll.get() + { scroll.x = max_scroll_x; } - } - if content_size.height != new_content_size.height - || control_size.height != new_control_size.height - { - // content_size.height = new_content_size.height; - if self.preserve_max_scroll.get() && scroll.y == current_max_scroll.y { + if (content_size.height != new_content_size.height + || control_size.height != new_control_size.height) + && scroll.y == current_max_scroll.y + && self.preserve_max_scroll.get() + { scroll.y = max_scroll_y; } } + // Set the current scroll, but prevent immediately triggering // invalidate. { @@ -333,13 +333,14 @@ impl Widget for Scroll { self.content_size.set(new_content_size); let region = Rect::new( - scroll, + -scroll.into_signed(), 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); - layout_size + new_control_size } fn mouse_wheel( @@ -355,8 +356,10 @@ impl Widget for Scroll { }; let mut scroll = self.scroll.lock(); let old_scroll = *scroll; - let new_scroll = - Self::constrained_scroll(*scroll + amount.cast::(), self.max_scroll.get()); + let new_scroll = Self::constrained_scroll( + (scroll.into_signed() - amount.cast::()).into_unsigned(), + self.max_scroll.get(), + ); if old_scroll == new_scroll { IGNORED } else { @@ -378,11 +381,11 @@ impl Widget for Scroll { context: &mut EventContext<'_>, ) -> EventHandling { let control_size = self.control_size.get(); - - let relative_x = (control_size.width - location.x).max(Px::ZERO); + + 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 relative_y = (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; if matches!( @@ -392,14 +395,14 @@ impl Widget for Scroll { return IGNORED; } - self.drag.start = location; + self.drag.start = location.into_signed(); self.drag.start_scroll = self.scroll.get(); self.drag.horizontal = 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 } 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 }; @@ -450,7 +453,9 @@ impl Widget for Scroll { if self.drag.mouse_buttons_down == 0 { if location.map_or(false, |location| { - Rect::from(self.control_size.get()).contains(location) + Rect::from(self.control_size.get()) + .into_signed() + .contains(location) }) { self.scrollbar_opacity_animation.handle.clear(); self.show_scrollbars(context); @@ -472,7 +477,7 @@ impl Widget for Scroll { struct DragInfo { mouse_buttons_down: usize, start: Point, - start_scroll: Point, + start_scroll: Point, horizontal: bool, in_bar: bool, } @@ -481,11 +486,11 @@ impl DragInfo { fn update( &self, location: Point, - dynamic_scroll: &Dynamic>, + dynamic_scroll: &Dynamic>, horizontal_bar: &ScrollbarInfo, vertical_bar: &ScrollbarInfo, - max_scroll: Point, - control_size: Size, + max_scroll: Point, + control_size: Size, ) { let mut scroll = dynamic_scroll.get(); if self.horizontal { @@ -514,33 +519,35 @@ impl DragInfo { &self, location: Px, start: Px, - max_scroll: Px, - start_scroll: Px, + max_scroll: UPx, + start_scroll: UPx, bar: &ScrollbarInfo, - control_size: Px, - ) -> Px { + control_size: UPx, + ) -> UPx { if self.in_bar { let dy = location - start; if dy == 0 { start_scroll } else { - (start_scroll - - Px::from( + (start_scroll.into_signed() + + Px::from( dy.into_float() / (control_size - bar.size).into_float() * bar.amount_hidden.into_float(), )) - .clamp(max_scroll, Px::ZERO) + .into_unsigned() + .min(max_scroll) } } else { 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()) } } } -fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx { - let measured = measured.into_unsigned(); +fn constrain_child(constraint: ConstraintLimit, measured: UPx) -> UPx { match constraint { ConstraintLimit::Fill(size) => size.min(measured), ConstraintLimit::SizeToFit(_) => measured, @@ -549,18 +556,18 @@ fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx { #[derive(Debug, Default)] struct ScrollbarInfo { - offset: Px, - amount_hidden: Px, - size: Px, + offset: UPx, + amount_hidden: UPx, + 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 { let amount_hidden = content_size - control_size; let ratio_visible = control_size.into_float() / content_size.into_float(); let bar_size = control_size * ratio_visible; 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; ScrollbarInfo { offset: bar_offset,