From 4f066b516bd7c5a8a3a55f01d09d650e10567839 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 12 Jun 2023 21:04:43 -0700 Subject: [PATCH 1/8] Add scrollable alignment option --- examples/scrollable/src/main.rs | 52 +++++++++++++-- widget/src/scrollable.rs | 109 +++++++++++++++++++++++++++++--- 2 files changed, 146 insertions(+), 15 deletions(-) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 4104871fd1..8c08d99316 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -20,6 +20,7 @@ struct ScrollableDemo { scrollbar_margin: u16, scroller_width: u16, current_scroll_offset: scrollable::RelativeOffset, + alignment: scrollable::Alignment, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] @@ -32,6 +33,7 @@ enum Direction { #[derive(Debug, Clone)] enum Message { SwitchDirection(Direction), + AlignmentChanged(scrollable::Alignment), ScrollbarWidthChanged(u16), ScrollbarMarginChanged(u16), ScrollerWidthChanged(u16), @@ -54,6 +56,7 @@ impl Application for ScrollableDemo { scrollbar_margin: 0, scroller_width: 10, current_scroll_offset: scrollable::RelativeOffset::START, + alignment: scrollable::Alignment::Start, }, Command::none(), ) @@ -74,6 +77,15 @@ impl Application for ScrollableDemo { self.current_scroll_offset, ) } + Message::AlignmentChanged(alignment) => { + self.current_scroll_offset = scrollable::RelativeOffset::START; + self.alignment = alignment; + + scrollable::snap_to( + SCROLLABLE_ID.clone(), + self.current_scroll_offset, + ) + } Message::ScrollbarWidthChanged(width) => { self.scrollbar_width = width; @@ -165,10 +177,33 @@ impl Application for ScrollableDemo { .spacing(10) .width(Length::Fill); - let scroll_controls = - row![scroll_slider_controls, scroll_orientation_controls] - .spacing(20) - .width(Length::Fill); + let scroll_alignment_controls = column(vec![ + text("Scrollable alignment:").into(), + radio( + "Start", + scrollable::Alignment::Start, + Some(self.alignment), + Message::AlignmentChanged, + ) + .into(), + radio( + "End", + scrollable::Alignment::End, + Some(self.alignment), + Message::AlignmentChanged, + ) + .into(), + ]) + .spacing(10) + .width(Length::Fill); + + let scroll_controls = row![ + scroll_slider_controls, + scroll_orientation_controls, + scroll_alignment_controls + ] + .spacing(20) + .width(Length::Fill); let scroll_to_end_button = || { button("Scroll to end") @@ -204,7 +239,8 @@ impl Application for ScrollableDemo { Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width), + .scroller_width(self.scroller_width) + .alignment(self.alignment), )) .id(SCROLLABLE_ID.clone()) .on_scroll(Message::Scrolled), @@ -228,7 +264,8 @@ impl Application for ScrollableDemo { Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width), + .scroller_width(self.scroller_width) + .alignment(self.alignment), )) .style(theme::Scrollable::custom(ScrollbarCustomStyle)) .id(SCROLLABLE_ID.clone()) @@ -269,7 +306,8 @@ impl Application for ScrollableDemo { let properties = Properties::new() .width(self.scrollbar_width) .margin(self.scrollbar_margin) - .scroller_width(self.scroller_width); + .scroller_width(self.scroller_width) + .alignment(self.alignment); scrollable::Direction::Both { horizontal: properties, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 473124ca0c..b611197565 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -143,6 +143,7 @@ pub struct Properties { width: f32, margin: f32, scroller_width: f32, + alignment: Alignment, } impl Default for Properties { @@ -151,6 +152,7 @@ impl Default for Properties { width: 10.0, margin: 0.0, scroller_width: 10.0, + alignment: Alignment::Start, } } } @@ -178,6 +180,31 @@ impl Properties { self.scroller_width = scroller_width.into().0.max(0.0); self } + + /// Sets the alignment of the [`Scrollable`] . + pub fn alignment(mut self, alignment: Alignment) -> Self { + self.alignment = alignment; + self + } +} + +/// Alignment of the scrollable's content relative to it's [`Viewport`] in one direction. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum Alignment { + /// Content is aligned to the start of the [`Viewport`]. + #[default] + Start, + /// Content is aligned to the end of the [`Viewport`] + End, +} + +impl Alignment { + fn aligned(self, offset: f32, viewport: f32, content: f32) -> f32 { + match self { + Alignment::Start => offset, + Alignment::End => ((content - viewport).max(0.0) - offset).max(0.0), + } + } } impl<'a, Message, Renderer> Widget @@ -485,6 +512,15 @@ pub fn update( let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); + let horizontal_alignment = direction + .horizontal() + .map(|p| p.alignment) + .unwrap_or_default(); + let vertical_alignment = direction + .vertical() + .map(|p| p.alignment) + .unwrap_or_default(); + let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); @@ -535,7 +571,11 @@ pub fn update( mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), }; - state.scroll(delta, bounds, content_bounds); + state.scroll( + aligned_delta(delta, vertical_alignment, horizontal_alignment), + bounds, + content_bounds, + ); notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); @@ -566,7 +606,15 @@ pub fn update( cursor_position.y - scroll_box_touched_at.y, ); - state.scroll(delta, bounds, content_bounds); + state.scroll( + aligned_delta( + delta, + vertical_alignment, + horizontal_alignment, + ), + bounds, + content_bounds, + ); state.scroll_area_touched_at = Some(cursor_position); @@ -610,6 +658,7 @@ pub fn update( scrollbar.scroll_percentage_y( scroller_grabbed_at, cursor_position, + vertical_alignment, ), bounds, content_bounds, @@ -643,6 +692,7 @@ pub fn update( scrollbar.scroll_percentage_y( scroller_grabbed_at, cursor_position, + vertical_alignment, ), bounds, content_bounds, @@ -685,6 +735,7 @@ pub fn update( scrollbar.scroll_percentage_x( scroller_grabbed_at, cursor_position, + horizontal_alignment, ), bounds, content_bounds, @@ -718,6 +769,7 @@ pub fn update( scrollbar.scroll_percentage_x( scroller_grabbed_at, cursor_position, + horizontal_alignment, ), bounds, content_bounds, @@ -1166,13 +1218,22 @@ impl State { content_bounds: Rectangle, ) -> Vector { Vector::new( - if direction.horizontal().is_some() { - self.offset_x.absolute(bounds.width, content_bounds.width) + if let Some(horizontal) = direction.horizontal() { + horizontal.alignment.aligned( + self.offset_x.absolute(bounds.width, content_bounds.width), + bounds.width, + content_bounds.width, + ) } else { 0.0 }, - if direction.vertical().is_some() { - self.offset_y.absolute(bounds.height, content_bounds.height) + if let Some(vertical) = direction.vertical() { + vertical.alignment.aligned( + self.offset_y + .absolute(bounds.height, content_bounds.height), + bounds.height, + content_bounds.height, + ) } else { 0.0 }, @@ -1216,6 +1277,7 @@ impl Scrollbars { width, margin, scroller_width, + .. } = *vertical; // Adjust the height of the vertical scrollbar if the horizontal scrollbar @@ -1275,6 +1337,7 @@ impl Scrollbars { width, margin, scroller_width, + .. } = *horizontal; // Need to adjust the width of the horizontal scrollbar if the vertical scrollbar @@ -1387,9 +1450,27 @@ impl Scrollbars { } } +fn aligned_delta( + delta: Vector, + vertical_alignment: Alignment, + horizontal_alignment: Alignment, +) -> Vector { + let align = |alignment: Alignment, delta: f32| match alignment { + Alignment::Start => delta, + Alignment::End => -delta, + }; + + Vector::new( + align(horizontal_alignment, delta.x), + align(vertical_alignment, delta.y), + ) +} + pub(super) mod internals { use crate::core::{Point, Rectangle}; + use super::Alignment; + /// The scrollbar of a [`Scrollable`]. #[derive(Debug, Copy, Clone)] pub struct Scrollbar { @@ -1415,8 +1496,9 @@ pub(super) mod internals { &self, grabbed_at: f32, cursor_position: Point, + alignment: Alignment, ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { // cursor position is unavailable! Set to either end or beginning of scrollbar depending // on where the thumb currently is in the track (self.scroller.bounds.y / self.total_bounds.height).round() @@ -1425,6 +1507,11 @@ pub(super) mod internals { - self.bounds.y - self.scroller.bounds.height * grabbed_at) / (self.bounds.height - self.scroller.bounds.height) + }; + + match alignment { + Alignment::Start => pct, + Alignment::End => 1.0 - pct, } } @@ -1433,14 +1520,20 @@ pub(super) mod internals { &self, grabbed_at: f32, cursor_position: Point, + alignment: Alignment, ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { (self.scroller.bounds.x / self.total_bounds.width).round() } else { (cursor_position.x - self.bounds.x - self.scroller.bounds.width * grabbed_at) / (self.bounds.width - self.scroller.bounds.width) + }; + + match alignment { + Alignment::Start => pct, + Alignment::End => 1.0 - pct, } } } From 905c307f0b78d9957a82a95b0ba537be23fa4035 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 13 Jun 2023 08:22:33 -0700 Subject: [PATCH 2/8] Make viewport bounds public --- widget/src/scrollable.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index b611197565..79e5fe3e3c 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1099,8 +1099,10 @@ impl Offset { pub struct Viewport { offset_x: Offset, offset_y: Offset, - bounds: Rectangle, - content_bounds: Rectangle, + /// The viewport bounds of the [`Scrollable`]. + pub bounds: Rectangle, + /// The content bounds of the [`Scrollable`]. + pub content_bounds: Rectangle, } impl Viewport { From d79cedd8da99345947f5b9fcb1ebc3a145cc4112 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 24 Jun 2023 18:01:14 -0700 Subject: [PATCH 3/8] Add alignment to Viewport --- widget/src/scrollable.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 79e5fe3e3c..3912f44518 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -577,7 +577,15 @@ pub fn update( content_bounds, ); - notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); + notify_on_scroll( + state, + on_scroll, + bounds, + content_bounds, + horizontal_alignment, + vertical_alignment, + shell, + ); return event::Status::Captured; } @@ -623,6 +631,8 @@ pub fn update( on_scroll, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, shell, ); } @@ -669,6 +679,8 @@ pub fn update( on_scroll, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, shell, ); @@ -705,6 +717,8 @@ pub fn update( on_scroll, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, shell, ); } @@ -746,6 +760,8 @@ pub fn update( on_scroll, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, shell, ); } @@ -782,6 +798,8 @@ pub fn update( on_scroll, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, shell, ); @@ -997,6 +1015,8 @@ fn notify_on_scroll( on_scroll: &Option Message + '_>>, bounds: Rectangle, content_bounds: Rectangle, + horizontal_alignment: Alignment, + vertical_alignment: Alignment, shell: &mut Shell<'_, Message>, ) { if let Some(on_scroll) = on_scroll { @@ -1011,6 +1031,8 @@ fn notify_on_scroll( offset_y: state.offset_y, bounds, content_bounds, + horizontal_alignment, + vertical_alignment, }; // Don't publish redundant viewports to shell @@ -1103,6 +1125,10 @@ pub struct Viewport { pub bounds: Rectangle, /// The content bounds of the [`Scrollable`]. pub content_bounds: Rectangle, + /// The horizontal [`Alignment`] of the [`Scrollable`]. + pub horizontal_alignment: Alignment, + /// The vertical [`Alignment`] of the [`Scrollable`]. + pub vertical_alignment: Alignment, } impl Viewport { From 44460f7b8b9e90c85bd94c19dd15d418e641f8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jul 2023 09:26:13 +0200 Subject: [PATCH 4/8] Remove public fields from `Viewport` Let's tackle use cases directly instead! --- widget/src/scrollable.rs | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 3912f44518..b611197565 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -577,15 +577,7 @@ pub fn update( content_bounds, ); - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - horizontal_alignment, - vertical_alignment, - shell, - ); + notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); return event::Status::Captured; } @@ -631,8 +623,6 @@ pub fn update( on_scroll, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, shell, ); } @@ -679,8 +669,6 @@ pub fn update( on_scroll, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, shell, ); @@ -717,8 +705,6 @@ pub fn update( on_scroll, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, shell, ); } @@ -760,8 +746,6 @@ pub fn update( on_scroll, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, shell, ); } @@ -798,8 +782,6 @@ pub fn update( on_scroll, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, shell, ); @@ -1015,8 +997,6 @@ fn notify_on_scroll( on_scroll: &Option Message + '_>>, bounds: Rectangle, content_bounds: Rectangle, - horizontal_alignment: Alignment, - vertical_alignment: Alignment, shell: &mut Shell<'_, Message>, ) { if let Some(on_scroll) = on_scroll { @@ -1031,8 +1011,6 @@ fn notify_on_scroll( offset_y: state.offset_y, bounds, content_bounds, - horizontal_alignment, - vertical_alignment, }; // Don't publish redundant viewports to shell @@ -1121,14 +1099,8 @@ impl Offset { pub struct Viewport { offset_x: Offset, offset_y: Offset, - /// The viewport bounds of the [`Scrollable`]. - pub bounds: Rectangle, - /// The content bounds of the [`Scrollable`]. - pub content_bounds: Rectangle, - /// The horizontal [`Alignment`] of the [`Scrollable`]. - pub horizontal_alignment: Alignment, - /// The vertical [`Alignment`] of the [`Scrollable`]. - pub vertical_alignment: Alignment, + bounds: Rectangle, + content_bounds: Rectangle, } impl Viewport { From 2b2f9c07d8962f8a146794c2fd8f1709f104f4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jul 2023 09:49:14 +0200 Subject: [PATCH 5/8] Increase type-safety of `alignment` in `scrollable` --- widget/src/scrollable.rs | 100 +++++++++++++++------------------------ 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index b611197565..17d55e7ca8 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -295,7 +295,7 @@ where cursor, clipboard, shell, - &self.direction, + self.direction, &self.on_scroll, |event, layout, cursor, clipboard, shell| { self.content.as_widget_mut().on_event( @@ -327,7 +327,7 @@ where theme, layout, cursor, - &self.direction, + self.direction, &self.style, |renderer, layout, cursor, viewport| { self.content.as_widget().draw( @@ -355,7 +355,7 @@ where tree.state.downcast_ref::(), layout, cursor, - &self.direction, + self.direction, |layout, cursor, viewport| { self.content.as_widget().mouse_interaction( &tree.children[0], @@ -386,7 +386,7 @@ where let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); let offset = tree.state.downcast_ref::().offset( - &self.direction, + self.direction, bounds, content_bounds, ); @@ -494,7 +494,7 @@ pub fn update( cursor: mouse::Cursor, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - direction: &Direction, + direction: Direction, on_scroll: &Option Message + '_>>, update_content: impl FnOnce( Event, @@ -512,15 +512,6 @@ pub fn update( let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); - let horizontal_alignment = direction - .horizontal() - .map(|p| p.alignment) - .unwrap_or_default(); - let vertical_alignment = direction - .vertical() - .map(|p| p.alignment) - .unwrap_or_default(); - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); @@ -571,11 +562,7 @@ pub fn update( mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), }; - state.scroll( - aligned_delta(delta, vertical_alignment, horizontal_alignment), - bounds, - content_bounds, - ); + state.scroll(delta, direction, bounds, content_bounds); notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); @@ -606,15 +593,7 @@ pub fn update( cursor_position.y - scroll_box_touched_at.y, ); - state.scroll( - aligned_delta( - delta, - vertical_alignment, - horizontal_alignment, - ), - bounds, - content_bounds, - ); + state.scroll(delta, direction, bounds, content_bounds); state.scroll_area_touched_at = Some(cursor_position); @@ -658,7 +637,6 @@ pub fn update( scrollbar.scroll_percentage_y( scroller_grabbed_at, cursor_position, - vertical_alignment, ), bounds, content_bounds, @@ -692,7 +670,6 @@ pub fn update( scrollbar.scroll_percentage_y( scroller_grabbed_at, cursor_position, - vertical_alignment, ), bounds, content_bounds, @@ -735,7 +712,6 @@ pub fn update( scrollbar.scroll_percentage_x( scroller_grabbed_at, cursor_position, - horizontal_alignment, ), bounds, content_bounds, @@ -769,7 +745,6 @@ pub fn update( scrollbar.scroll_percentage_x( scroller_grabbed_at, cursor_position, - horizontal_alignment, ), bounds, content_bounds, @@ -800,7 +775,7 @@ pub fn mouse_interaction( state: &State, layout: Layout<'_>, cursor: mouse::Cursor, - direction: &Direction, + direction: Direction, content_interaction: impl FnOnce( Layout<'_>, mouse::Cursor, @@ -853,7 +828,7 @@ pub fn draw( theme: &Renderer::Theme, layout: Layout<'_>, cursor: mouse::Cursor, - direction: &Direction, + direction: Direction, style: &::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, mouse::Cursor, &Rectangle), ) where @@ -1138,9 +1113,30 @@ impl State { pub fn scroll( &mut self, delta: Vector, + direction: Direction, bounds: Rectangle, content_bounds: Rectangle, ) { + let horizontal_alignment = direction + .horizontal() + .map(|p| p.alignment) + .unwrap_or_default(); + + let vertical_alignment = direction + .vertical() + .map(|p| p.alignment) + .unwrap_or_default(); + + let align = |alignment: Alignment, delta: f32| match alignment { + Alignment::Start => delta, + Alignment::End => -delta, + }; + + let delta = Vector::new( + align(horizontal_alignment, delta.x), + align(vertical_alignment, delta.y), + ); + if bounds.height < content_bounds.height { self.offset_y = Offset::Absolute( (self.offset_y.absolute(bounds.height, content_bounds.height) @@ -1213,7 +1209,7 @@ impl State { /// the bounds of the [`Scrollable`] and its contents. pub fn offset( &self, - direction: &Direction, + direction: Direction, bounds: Rectangle, content_bounds: Rectangle, ) -> Vector { @@ -1258,7 +1254,7 @@ impl Scrollbars { /// Create y and/or x scrollbar(s) if content is overflowing the [`Scrollable`] bounds. fn new( state: &State, - direction: &Direction, + direction: Direction, bounds: Rectangle, content_bounds: Rectangle, ) -> Self { @@ -1327,6 +1323,7 @@ impl Scrollbars { scroller: internals::Scroller { bounds: scroller_bounds, }, + alignment: vertical.alignment, }) } else { None @@ -1387,6 +1384,7 @@ impl Scrollbars { scroller: internals::Scroller { bounds: scroller_bounds, }, + alignment: horizontal.alignment, }) } else { None @@ -1450,39 +1448,17 @@ impl Scrollbars { } } -fn aligned_delta( - delta: Vector, - vertical_alignment: Alignment, - horizontal_alignment: Alignment, -) -> Vector { - let align = |alignment: Alignment, delta: f32| match alignment { - Alignment::Start => delta, - Alignment::End => -delta, - }; - - Vector::new( - align(horizontal_alignment, delta.x), - align(vertical_alignment, delta.y), - ) -} - pub(super) mod internals { use crate::core::{Point, Rectangle}; use super::Alignment; - /// The scrollbar of a [`Scrollable`]. #[derive(Debug, Copy, Clone)] pub struct Scrollbar { - /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, - /// and the scrollbar margin. pub total_bounds: Rectangle, - - /// The bounds of just the [`Scrollbar`]. pub bounds: Rectangle, - - /// The state of this scrollbar's [`Scroller`]. pub scroller: Scroller, + pub alignment: Alignment, } impl Scrollbar { @@ -1496,7 +1472,6 @@ pub(super) mod internals { &self, grabbed_at: f32, cursor_position: Point, - alignment: Alignment, ) -> f32 { let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { // cursor position is unavailable! Set to either end or beginning of scrollbar depending @@ -1509,7 +1484,7 @@ pub(super) mod internals { / (self.bounds.height - self.scroller.bounds.height) }; - match alignment { + match self.alignment { Alignment::Start => pct, Alignment::End => 1.0 - pct, } @@ -1520,7 +1495,6 @@ pub(super) mod internals { &self, grabbed_at: f32, cursor_position: Point, - alignment: Alignment, ) -> f32 { let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { (self.scroller.bounds.x / self.total_bounds.width).round() @@ -1531,7 +1505,7 @@ pub(super) mod internals { / (self.bounds.width - self.scroller.bounds.width) }; - match alignment { + match self.alignment { Alignment::Start => pct, Alignment::End => 1.0 - pct, } From ca2afb04952f177667c4431a102161be9223340f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jul 2023 10:04:26 +0200 Subject: [PATCH 6/8] Disambiguate `offset` from `translation` in `scrollable` --- widget/src/scrollable.rs | 77 +++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 17d55e7ca8..45fbd72bad 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -198,15 +198,6 @@ pub enum Alignment { End, } -impl Alignment { - fn aligned(self, offset: f32, viewport: f32, content: f32) -> f32 { - match self { - Alignment::Start => offset, - Alignment::End => ((content - viewport).max(0.0) - offset).max(0.0), - } - } -} - impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where @@ -385,13 +376,12 @@ where let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); - let offset = tree.state.downcast_ref::().offset( - self.direction, - bounds, - content_bounds, - ); + let translation = tree + .state + .downcast_ref::() + .translation(self.direction, bounds, content_bounds); - overlay.translate(Vector::new(-offset.x, -offset.y)) + overlay.translate(Vector::new(-translation.x, -translation.y)) }) } } @@ -522,7 +512,7 @@ pub fn update( { mouse::Cursor::Available( cursor_position - + state.offset(direction, bounds, content_bounds), + + state.translation(direction, bounds, content_bounds), ) } _ => mouse::Cursor::Unavailable, @@ -798,13 +788,13 @@ pub fn mouse_interaction( { mouse::Interaction::Idle } else { - let offset = state.offset(direction, bounds, content_bounds); + let translation = state.translation(direction, bounds, content_bounds); let cursor = match cursor_over_scrollable { Some(cursor_position) if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => { - mouse::Cursor::Available(cursor_position + offset) + mouse::Cursor::Available(cursor_position + translation) } _ => mouse::Cursor::Unavailable, }; @@ -813,8 +803,8 @@ pub fn mouse_interaction( content_layout, cursor, &Rectangle { - y: bounds.y + offset.y, - x: bounds.x + offset.x, + y: bounds.y + translation.y, + x: bounds.x + translation.x, ..bounds }, ) @@ -845,13 +835,13 @@ pub fn draw( let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = scrollbars.is_mouse_over(cursor); - let offset = state.offset(direction, bounds, content_bounds); + let translation = state.translation(direction, bounds, content_bounds); let cursor = match cursor_over_scrollable { Some(cursor_position) if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => { - mouse::Cursor::Available(cursor_position + offset) + mouse::Cursor::Available(cursor_position + translation) } _ => mouse::Cursor::Unavailable, }; @@ -860,15 +850,15 @@ pub fn draw( if scrollbars.active() { renderer.with_layer(bounds, |renderer| { renderer.with_translation( - Vector::new(-offset.x, -offset.y), + Vector::new(-translation.x, -translation.y), |renderer| { draw_content( renderer, content_layout, cursor, &Rectangle { - y: bounds.y + offset.y, - x: bounds.x + offset.x, + y: bounds.y + translation.y, + x: bounds.x + translation.x, ..bounds }, ); @@ -959,8 +949,8 @@ pub fn draw( content_layout, cursor, &Rectangle { - x: bounds.x + offset.x, - y: bounds.y + offset.y, + x: bounds.x + translation.x, + y: bounds.y + translation.y, ..bounds }, ); @@ -1067,6 +1057,20 @@ impl Offset { } } } + + fn absolute_from_start( + self, + viewport: f32, + content: f32, + alignment: Alignment, + ) -> f32 { + let offset = self.absolute(viewport, content); + + match alignment { + Alignment::Start => offset, + Alignment::End => ((content - viewport).max(0.0) - offset).max(0.0), + } + } } /// The current [`Viewport`] of the [`Scrollable`]. @@ -1205,9 +1209,9 @@ impl State { ); } - /// Returns the scrolling offset of the [`State`], given a [`Direction`], + /// Returns the scrolling translation of the [`State`], given a [`Direction`], /// the bounds of the [`Scrollable`] and its contents. - pub fn offset( + fn translation( &self, direction: Direction, bounds: Rectangle, @@ -1215,20 +1219,19 @@ impl State { ) -> Vector { Vector::new( if let Some(horizontal) = direction.horizontal() { - horizontal.alignment.aligned( - self.offset_x.absolute(bounds.width, content_bounds.width), + self.offset_x.absolute_from_start( bounds.width, content_bounds.width, + horizontal.alignment, ) } else { 0.0 }, if let Some(vertical) = direction.vertical() { - vertical.alignment.aligned( - self.offset_y - .absolute(bounds.height, content_bounds.height), + self.offset_y.absolute_from_start( bounds.height, content_bounds.height, + vertical.alignment, ) } else { 0.0 @@ -1258,7 +1261,7 @@ impl Scrollbars { bounds: Rectangle, content_bounds: Rectangle, ) -> Self { - let offset = state.offset(direction, bounds, content_bounds); + let translation = state.translation(direction, bounds, content_bounds); let show_scrollbar_x = direction .horizontal() @@ -1305,7 +1308,7 @@ impl Scrollbars { let ratio = bounds.height / content_bounds.height; // min height for easier grabbing with super tall content let scroller_height = (bounds.height * ratio).max(2.0); - let scroller_offset = offset.y * ratio; + let scroller_offset = translation.y * ratio; let scroller_bounds = Rectangle { x: bounds.x + bounds.width @@ -1366,7 +1369,7 @@ impl Scrollbars { let ratio = bounds.width / content_bounds.width; // min width for easier grabbing with extra wide content let scroller_length = (bounds.width * ratio).max(2.0); - let scroller_offset = offset.x * ratio; + let scroller_offset = translation.x * ratio; let scroller_bounds = Rectangle { x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width) From d07bac36ab528ea5c3f4a09bb0ae010ae1b5c6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jul 2023 10:05:46 +0200 Subject: [PATCH 7/8] Rename `absolute_from_start` to `translation` in `scrollable` --- widget/src/scrollable.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 45fbd72bad..9a43a978b1 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1058,7 +1058,7 @@ impl Offset { } } - fn absolute_from_start( + fn translation( self, viewport: f32, content: f32, @@ -1219,7 +1219,7 @@ impl State { ) -> Vector { Vector::new( if let Some(horizontal) = direction.horizontal() { - self.offset_x.absolute_from_start( + self.offset_x.translation( bounds.width, content_bounds.width, horizontal.alignment, @@ -1228,7 +1228,7 @@ impl State { 0.0 }, if let Some(vertical) = direction.vertical() { - self.offset_y.absolute_from_start( + self.offset_y.translation( bounds.height, content_bounds.height, vertical.alignment, From ce23e08d0d921040d1e6fb693149e638d291bd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jul 2023 10:13:15 +0200 Subject: [PATCH 8/8] Remove unnecessary cursor unavailability logic in `scrollable` --- widget/src/scrollable.rs | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 9a43a978b1..88746ac4f2 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1476,20 +1476,14 @@ pub(super) mod internals { grabbed_at: f32, cursor_position: Point, ) -> f32 { - let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - // cursor position is unavailable! Set to either end or beginning of scrollbar depending - // on where the thumb currently is in the track - (self.scroller.bounds.y / self.total_bounds.height).round() - } else { - (cursor_position.y - - self.bounds.y - - self.scroller.bounds.height * grabbed_at) - / (self.bounds.height - self.scroller.bounds.height) - }; + let percentage = (cursor_position.y + - self.bounds.y + - self.scroller.bounds.height * grabbed_at) + / (self.bounds.height - self.scroller.bounds.height); match self.alignment { - Alignment::Start => pct, - Alignment::End => 1.0 - pct, + Alignment::Start => percentage, + Alignment::End => 1.0 - percentage, } } @@ -1499,18 +1493,14 @@ pub(super) mod internals { grabbed_at: f32, cursor_position: Point, ) -> f32 { - let pct = if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - (self.scroller.bounds.x / self.total_bounds.width).round() - } else { - (cursor_position.x - - self.bounds.x - - self.scroller.bounds.width * grabbed_at) - / (self.bounds.width - self.scroller.bounds.width) - }; + let percentage = (cursor_position.x + - self.bounds.x + - self.scroller.bounds.width * grabbed_at) + / (self.bounds.width - self.scroller.bounds.width); match self.alignment { - Alignment::Start => pct, - Alignment::End => 1.0 - pct, + Alignment::Start => percentage, + Alignment::End => 1.0 - percentage, } } }