Add scroll_to operation for absolute scroll

This commit is contained in:
Cory Forsstrom 2023-04-14 13:32:44 -07:00
parent 4b05f42fd6
commit b623f280ed
No known key found for this signature in database
GPG key ID: 1DFE170A4415C9F5
4 changed files with 90 additions and 23 deletions

View file

@ -36,7 +36,7 @@ enum Message {
ScrollerWidthChanged(u16), ScrollerWidthChanged(u16),
ScrollToBeginning, ScrollToBeginning,
ScrollToEnd, ScrollToEnd,
Scrolled(scrollable::RelativeOffset), Scrolled(scrollable::CurrentOffset),
} }
impl Application for ScrollableDemo { impl Application for ScrollableDemo {
@ -105,7 +105,7 @@ impl Application for ScrollableDemo {
) )
} }
Message::Scrolled(offset) => { Message::Scrolled(offset) => {
self.current_scroll_offset = offset; self.current_scroll_offset = offset.relative;
Command::none() Command::none()
} }

View file

@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};
pub trait Scrollable { pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis. /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, offset: RelativeOffset); fn snap_to(&mut self, offset: RelativeOffset);
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
fn scroll_to(&mut self, offset: AbsoluteOffset);
} }
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to /// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@ -34,7 +37,52 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
SnapTo { target, offset } SnapTo { target, offset }
} }
/// The amount of offset in each direction of a [`Scrollable`]. /// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to
/// the provided [`AbsoluteOffset`].
pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
struct ScrollTo {
target: Id,
offset: AbsoluteOffset,
}
impl<T> Operation<T> for ScrollTo {
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
if Some(&self.target) == id {
state.scroll_to(self.offset);
}
}
}
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 {
/// The amount of horizontal offset
pub x: f32,
/// The amount of vertical offset
pub y: f32,
}
/// The amount of relative offset in each direction of a [`Scrollable`].
/// ///
/// A value of `0.0` means start, while `1.0` means end. /// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]

View file

@ -15,7 +15,9 @@ use crate::{
}; };
pub use iced_style::scrollable::StyleSheet; pub use iced_style::scrollable::StyleSheet;
pub use operation::scrollable::RelativeOffset; pub use operation::scrollable::{
AbsoluteOffset, CurrentOffset, RelativeOffset,
};
pub mod style { pub mod style {
//! The styles of a [`Scrollable`]. //! The styles of a [`Scrollable`].
@ -38,7 +40,7 @@ where
vertical: Properties, vertical: Properties,
horizontal: Option<Properties>, horizontal: Option<Properties>,
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>, on_scroll: Option<Box<dyn Fn(CurrentOffset) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style, style: <Renderer::Theme as StyleSheet>::Style,
} }
@ -93,11 +95,10 @@ where
/// Sets a function to call when the [`Scrollable`] is scrolled. /// Sets a function to call when the [`Scrollable`] is scrolled.
/// ///
/// The function takes the new relative x & y offset of the [`Scrollable`] /// The function takes the [`CurrentOffset`] of the [`Scrollable`]
/// (e.g. `0` means beginning, while `1` means end).
pub fn on_scroll( pub fn on_scroll(
mut self, mut self,
f: impl Fn(RelativeOffset) -> Message + 'a, f: impl Fn(CurrentOffset) -> Message + 'a,
) -> Self { ) -> Self {
self.on_scroll = Some(Box::new(f)); self.on_scroll = Some(Box::new(f));
self self
@ -436,7 +437,7 @@ pub fn update<Message>(
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
vertical: &Properties, vertical: &Properties,
horizontal: Option<&Properties>, horizontal: Option<&Properties>,
on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(CurrentOffset) -> Message + '_>>,
update_content: impl FnOnce( update_content: impl FnOnce(
Event, Event,
Layout<'_>, Layout<'_>,
@ -896,7 +897,7 @@ pub fn draw<Renderer>(
fn notify_on_scroll<Message>( fn notify_on_scroll<Message>(
state: &mut State, state: &mut State,
on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, on_scroll: &Option<Box<dyn Fn(CurrentOffset) -> Message + '_>>,
bounds: Rectangle, bounds: Rectangle,
content_bounds: Rectangle, content_bounds: Rectangle,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
@ -908,31 +909,39 @@ fn notify_on_scroll<Message>(
return; return;
} }
let x = state.offset_x.absolute(bounds.width, content_bounds.width) let absolute_x =
/ (content_bounds.width - bounds.width); state.offset_x.absolute(bounds.width, content_bounds.width);
let relative_x = absolute_x / (content_bounds.width - bounds.width);
let y = state let absolute_y = state
.offset_y .offset_y
.absolute(bounds.height, content_bounds.height) .absolute(bounds.height, content_bounds.height);
/ (content_bounds.height - bounds.height); let relative_y = absolute_y / (content_bounds.height - bounds.height);
let new_offset = RelativeOffset { x, y }; let absolute = AbsoluteOffset {
x: absolute_x,
y: absolute_y,
};
let relative = RelativeOffset {
x: relative_x,
y: relative_y,
};
// Don't publish redundant offsets to shell // Don't publish redundant offsets to shell
if let Some(prev_offset) = state.last_notified { if let Some(prev_relative) = state.last_notified {
let unchanged = |a: f32, b: f32| { let unchanged = |a: f32, b: f32| {
(a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan()) (a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
}; };
if unchanged(prev_offset.x, new_offset.x) if unchanged(prev_relative.x, relative.x)
&& unchanged(prev_offset.y, new_offset.y) && unchanged(prev_relative.y, relative.y)
{ {
return; return;
} }
} }
shell.publish(on_scroll(new_offset)); shell.publish(on_scroll(CurrentOffset { absolute, relative }));
state.last_notified = Some(new_offset); state.last_notified = Some(relative);
} }
} }
@ -966,6 +975,10 @@ impl operation::Scrollable for State {
fn snap_to(&mut self, offset: RelativeOffset) { fn snap_to(&mut self, offset: RelativeOffset) {
State::snap_to(self, offset); State::snap_to(self, offset);
} }
fn scroll_to(&mut self, offset: AbsoluteOffset) {
State::scroll_to(self, offset)
}
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -1052,6 +1065,12 @@ impl State {
self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0)); self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0));
} }
/// Scroll to the provided [`AbsoluteOffset`].
pub fn scroll_to(&mut self, offset: AbsoluteOffset) {
self.offset_x = Offset::Absolute(offset.x.max(0.0));
self.offset_y = Offset::Absolute(offset.y.max(0.0));
}
/// Unsnaps the current scroll position, if snapped, given the bounds of the /// Unsnaps the current scroll position, if snapped, given the bounds of the
/// [`Scrollable`] and its contents. /// [`Scrollable`] and its contents.
pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) { pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {

View file

@ -109,8 +109,8 @@ pub mod radio {
pub mod scrollable { pub mod scrollable {
//! Navigate an endless amount of content with a scrollbar. //! Navigate an endless amount of content with a scrollbar.
pub use iced_native::widget::scrollable::{ pub use iced_native::widget::scrollable::{
snap_to, style::Scrollbar, style::Scroller, Id, Properties, snap_to, style::Scrollbar, style::Scroller, AbsoluteOffset,
RelativeOffset, StyleSheet, CurrentOffset, Id, Properties, RelativeOffset, StyleSheet,
}; };
/// A widget that can vertically display an infinite amount of content /// A widget that can vertically display an infinite amount of content