From 2a7cab9565fdfad78614d226603cc71ccfe79ddd Mon Sep 17 00:00:00 2001 From: lufte Date: Fri, 10 May 2024 18:50:10 -0300 Subject: [PATCH] Implement a scroll_by operation for scrollables scroll_by allows scrolling an absolute offset that is applied to the current scrolling position. --- core/src/element.rs | 3 +- core/src/overlay/element.rs | 3 +- core/src/widget/operation.rs | 7 +++-- core/src/widget/operation/scrollable.rs | 40 +++++++++++++++++++++++++ widget/src/container.rs | 1 + widget/src/scrollable.rs | 39 ++++++++++++++++++++++++ 6 files changed, 89 insertions(+), 4 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 7d918a2ea7..c842a10f03 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -338,9 +338,10 @@ where state: &mut dyn widget::operation::Scrollable, id: Option<&widget::Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: Vector, ) { - self.operation.scrollable(state, id, bounds, translation); + self.operation.scrollable(state, id, bounds, content_bounds, translation); } fn text_input( diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 695b88b3a5..7582810995 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -179,9 +179,10 @@ where state: &mut dyn widget::operation::Scrollable, id: Option<&widget::Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: Vector, ) { - self.operation.scrollable(state, id, bounds, translation); + self.operation.scrollable(state, id, bounds, content_bounds, translation); } fn text_input( diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index b91cf9ac94..d05d697fe1 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -37,6 +37,7 @@ pub trait Operation { _state: &mut dyn Scrollable, _id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { } @@ -127,9 +128,10 @@ where state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: Vector, ) { - self.operation.scrollable(state, id, bounds, translation); + self.operation.scrollable(state, id, bounds, content_bounds, translation); } fn focusable( @@ -170,9 +172,10 @@ where state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: Vector, ) { - self.operation.scrollable(state, id, bounds, translation); + self.operation.scrollable(state, id, bounds, content_bounds, translation); } fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index 121612550e..925d80637e 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -9,6 +9,9 @@ pub trait Scrollable { /// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis. fn scroll_to(&mut self, offset: AbsoluteOffset); + + /// Scroll the widget by the given [`AbsoluteOffset`] along the horizontal & vertical axis. + fn scroll_by(&mut self, offset: AbsoluteOffset, bounds: Rectangle, content_bounds: Rectangle); } /// Produces an [`Operation`] that snaps the widget with the given [`Id`] to @@ -34,6 +37,7 @@ pub fn snap_to(target: Id, offset: RelativeOffset) -> impl Operation { state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { if Some(&self.target) == id { @@ -68,6 +72,7 @@ pub fn scroll_to(target: Id, offset: AbsoluteOffset) -> impl Operation { state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { if Some(&self.target) == id { @@ -79,6 +84,41 @@ pub fn scroll_to(target: Id, offset: AbsoluteOffset) -> impl Operation { ScrollTo { target, offset } } +/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] by +/// the provided [`AbsoluteOffset`]. +pub fn scroll_by(target: Id, offset: AbsoluteOffset) -> impl Operation { + struct ScrollBy { + target: Id, + offset: AbsoluteOffset, + } + + impl Operation for ScrollBy { + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + bounds: Rectangle, + content_bounds: Rectangle, + _translation: Vector, + ) { + if Some(&self.target) == id { + state.scroll_by(self.offset, bounds, content_bounds); + } + } + } + + ScrollBy { target, offset } +} + /// The amount of absolute offset in each direction of a [`Scrollable`]. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct AbsoluteOffset { diff --git a/widget/src/container.rs b/widget/src/container.rs index 8b6638d4d4..767137d656 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -480,6 +480,7 @@ pub fn visible_bounds(id: Id) -> Command> { _state: &mut dyn widget::operation::Scrollable, _id: Option<&widget::Id>, bounds: Rectangle, + _content_bounds: Rectangle, translation: Vector, ) { match self.scrollables.last() { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 6fc00f877d..ec75d9b665 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -309,6 +309,7 @@ where state, self.id.as_ref().map(|id| &id.0), bounds, + content_bounds, translation, ); @@ -970,6 +971,15 @@ pub fn scroll_to( Command::widget(operation::scrollable::scroll_to(id.0, offset)) } +/// Produces a [`Command`] that scrolls the [`Scrollable`] with the given [`Id`] +/// by the provided [`AbsoluteOffset`] along the x & y axis. +pub fn scroll_by( + id: Id, + offset: AbsoluteOffset, +) -> Command { + Command::widget(operation::scrollable::scroll_by(id.0, offset)) +} + /// Returns [`true`] if the viewport actually changed. fn notify_on_scroll( state: &mut State, @@ -1053,6 +1063,10 @@ impl operation::Scrollable for State { fn scroll_to(&mut self, offset: AbsoluteOffset) { State::scroll_to(self, offset); } + + fn scroll_by(&mut self, offset: AbsoluteOffset, bounds: Rectangle, content_bounds: Rectangle) { + State::scroll_by(self, offset, bounds, content_bounds); + } } #[derive(Debug, Clone, Copy)] @@ -1237,6 +1251,31 @@ impl State { self.offset_y = Offset::Absolute(offset.y.max(0.0)); } + /// Scroll by the provided [`AbsoluteOffset`]. + pub fn scroll_by( + &mut self, + offset: AbsoluteOffset, + bounds: Rectangle, + content_bounds: Rectangle, + ) { + self.offset_x = match self.offset_x { + Offset::Absolute(v) => Offset::Absolute((v + offset.x).max(0.0).min( + content_bounds.width - bounds.width + )), + rel => Offset::Absolute( + (rel.absolute(bounds.width, content_bounds.width) + offset.x).max(0.0) + ), + }; + self.offset_y = match self.offset_y { + Offset::Absolute(v) => Offset::Absolute((v + offset.y).max(0.0).min( + content_bounds.height - bounds.height + )), + rel => Offset::Absolute( + (rel.absolute(bounds.height, content_bounds.height) + offset.y).max(0.0) + ), + }; + } + /// Unsnaps the current scroll position, if snapped, given the bounds of the /// [`Scrollable`] and its contents. pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {