From 6ad5e03d71db3dee174ac6512dceccc80e3a70a8 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 17 Apr 2023 13:55:40 -0700 Subject: [PATCH] Add scrollable `Viewport` --- examples/scrollable/src/main.rs | 6 +- native/src/widget/operation/scrollable.rs | 9 --- native/src/widget/scrollable.rs | 94 ++++++++++++++--------- src/widget.rs | 4 +- 4 files changed, 61 insertions(+), 52 deletions(-) diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index be8d0f5263..97344c9494 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -36,7 +36,7 @@ enum Message { ScrollerWidthChanged(u16), ScrollToBeginning, ScrollToEnd, - Scrolled(scrollable::CurrentOffset), + Scrolled(scrollable::Viewport), } impl Application for ScrollableDemo { @@ -104,8 +104,8 @@ impl Application for ScrollableDemo { self.current_scroll_offset, ) } - Message::Scrolled(offset) => { - self.current_scroll_offset = offset.relative; + Message::Scrolled(viewport) => { + self.current_scroll_offset = viewport.relative_offset(); Command::none() } diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs index 62f4c91e7e..f947344ded 100644 --- a/native/src/widget/operation/scrollable.rs +++ b/native/src/widget/operation/scrollable.rs @@ -64,15 +64,6 @@ pub fn scroll_to(target: Id, offset: AbsoluteOffset) -> impl Operation { ScrollTo { target, offset } } -/// The current absolute & relative offset of a [`Scrollable`] -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct CurrentOffset { - /// The [`AbsoluteOffset`] of a [`Scrollable`] - pub absolute: AbsoluteOffset, - /// The [`RelativeOffset`] of a [`Scrollable`] - pub relative: RelativeOffset, -} - /// The amount of absolute offset in each direction of a [`Scrollable`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct AbsoluteOffset { diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index eedddfd007..c66a166be3 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -15,9 +15,7 @@ use crate::{ }; pub use iced_style::scrollable::StyleSheet; -pub use operation::scrollable::{ - AbsoluteOffset, CurrentOffset, RelativeOffset, -}; +pub use operation::scrollable::{AbsoluteOffset, RelativeOffset}; pub mod style { //! The styles of a [`Scrollable`]. @@ -40,7 +38,7 @@ where vertical: Properties, horizontal: Option, content: Element<'a, Message, Renderer>, - on_scroll: Option Message + 'a>>, + on_scroll: Option Message + 'a>>, style: ::Style, } @@ -95,11 +93,8 @@ where /// Sets a function to call when the [`Scrollable`] is scrolled. /// - /// The function takes the [`CurrentOffset`] of the [`Scrollable`] - pub fn on_scroll( - mut self, - f: impl Fn(CurrentOffset) -> Message + 'a, - ) -> Self { + /// The function takes the [`Viewport`] of the [`Scrollable`] + pub fn on_scroll(mut self, f: impl Fn(Viewport) -> Message + 'a) -> Self { self.on_scroll = Some(Box::new(f)); self } @@ -437,7 +432,7 @@ pub fn update( shell: &mut Shell<'_, Message>, vertical: &Properties, horizontal: Option<&Properties>, - on_scroll: &Option Message + '_>>, + on_scroll: &Option Message + '_>>, update_content: impl FnOnce( Event, Layout<'_>, @@ -897,7 +892,7 @@ pub fn draw( fn notify_on_scroll( state: &mut State, - on_scroll: &Option Message + '_>>, + on_scroll: &Option Message + '_>>, bounds: Rectangle, content_bounds: Rectangle, shell: &mut Shell<'_, Message>, @@ -909,39 +904,29 @@ fn notify_on_scroll( return; } - let absolute_x = - state.offset_x.absolute(bounds.width, content_bounds.width); - let relative_x = absolute_x / (content_bounds.width - bounds.width); - - let absolute_y = state - .offset_y - .absolute(bounds.height, content_bounds.height); - let relative_y = absolute_y / (content_bounds.height - bounds.height); - - let absolute = AbsoluteOffset { - x: absolute_x, - y: absolute_y, - }; - let relative = RelativeOffset { - x: relative_x, - y: relative_y, + let viewport = Viewport { + offset_x: state.offset_x, + offset_y: state.offset_y, + bounds, + content_bounds, }; - // Don't publish redundant offsets to shell - if let Some(prev_relative) = state.last_notified { + // Don't publish redundant viewports to shell + if let Some(last_notified) = state.last_notified { + let prev = last_notified.relative_offset(); + let curr = viewport.relative_offset(); + let unchanged = |a: f32, b: f32| { (a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan()) }; - if unchanged(prev_relative.x, relative.x) - && unchanged(prev_relative.y, relative.y) - { + if unchanged(prev.x, curr.x) && unchanged(prev.y, curr.y) { return; } } - shell.publish(on_scroll(CurrentOffset { absolute, relative })); - state.last_notified = Some(relative); + shell.publish(on_scroll(viewport)); + state.last_notified = Some(viewport); } } @@ -954,7 +939,7 @@ pub struct State { offset_x: Offset, x_scroller_grabbed_at: Option, keyboard_modifiers: keyboard::Modifiers, - last_notified: Option, + last_notified: Option, } impl Default for State { @@ -988,18 +973,51 @@ enum Offset { } impl Offset { - fn absolute(self, window: f32, content: f32) -> f32 { + fn absolute(self, viewport: f32, content: f32) -> f32 { match self { Offset::Absolute(absolute) => { - absolute.min((content - window).max(0.0)) + absolute.min((content - viewport).max(0.0)) } Offset::Relative(percentage) => { - ((content - window) * percentage).max(0.0) + ((content - viewport) * percentage).max(0.0) } } } } +/// The current [`Viewport`] of the [`Scrollable`]. +#[derive(Debug, Clone, Copy)] +pub struct Viewport { + offset_x: Offset, + offset_y: Offset, + bounds: Rectangle, + content_bounds: Rectangle, +} + +impl Viewport { + /// Returns the [`AbsoluteOffset`] of the current [`Viewport`]. + pub fn absolute_offset(&self) -> AbsoluteOffset { + let x = self + .offset_x + .absolute(self.bounds.width, self.content_bounds.width); + let y = self + .offset_y + .absolute(self.bounds.height, self.content_bounds.height); + + AbsoluteOffset { x, y } + } + + /// Returns the [`RelativeOffset`] of the current [`Viewport`]. + pub fn relative_offset(&self) -> RelativeOffset { + let AbsoluteOffset { x, y } = self.absolute_offset(); + + let x = x / (self.content_bounds.width - self.bounds.width); + let y = y / (self.content_bounds.height - self.bounds.height); + + RelativeOffset { x, y } + } +} + impl State { /// Creates a new [`State`] with the scrollbar(s) at the beginning. pub fn new() -> Self { diff --git a/src/widget.rs b/src/widget.rs index 87e82f4711..38995a45e8 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -109,8 +109,8 @@ pub mod radio { pub mod scrollable { //! Navigate an endless amount of content with a scrollbar. pub use iced_native::widget::scrollable::{ - snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, - CurrentOffset, Id, Properties, RelativeOffset, StyleSheet, + snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset, Id, + Properties, RelativeOffset, StyleSheet, Viewport, }; /// A widget that can vertically display an infinite amount of content