diff options
-rw-r--r-- | core/src/vector.rs | 11 | ||||
-rw-r--r-- | core/src/widget/operation.rs | 46 | ||||
-rw-r--r-- | core/src/widget/operation/scrollable.rs | 45 | ||||
-rw-r--r-- | widget/src/container.rs | 1 | ||||
-rw-r--r-- | widget/src/scrollable.rs | 90 |
5 files changed, 157 insertions, 36 deletions
diff --git a/core/src/vector.rs b/core/src/vector.rs index 1380c3b3..ff848c4f 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -20,6 +20,17 @@ impl Vector { pub const ZERO: Self = Self::new(0.0, 0.0); } +impl<T> std::ops::Neg for Vector<T> +where + T: std::ops::Neg<Output = T>, +{ + type Output = Self; + + fn neg(self) -> Self::Output { + Self::new(-self.x, -self.y) + } +} + impl<T> std::ops::Add for Vector<T> where T: std::ops::Add<Output = T>, diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 4ee4b4a7..097c3601 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -38,6 +38,7 @@ pub trait Operation<T = ()>: Send { _state: &mut dyn Scrollable, _id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { } @@ -76,9 +77,16 @@ where state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: Vector, ) { - self.as_mut().scrollable(state, id, bounds, translation); + self.as_mut().scrollable( + state, + id, + bounds, + content_bounds, + translation, + ); } fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { @@ -151,9 +159,16 @@ 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>) { @@ -222,9 +237,16 @@ 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( @@ -262,9 +284,16 @@ 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>) { @@ -341,9 +370,16 @@ where state: &mut dyn Scrollable, id: Option<&Id>, bounds: Rectangle, + content_bounds: Rectangle, translation: crate::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 12161255..c2fecf56 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -9,6 +9,14 @@ 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 +42,7 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> { state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { if Some(&self.target) == id { @@ -68,6 +77,7 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { state: &mut dyn Scrollable, id: Option<&Id>, _bounds: Rectangle, + _content_bounds: Rectangle, _translation: Vector, ) { if Some(&self.target) == id { @@ -79,6 +89,41 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { ScrollTo { target, offset } } +/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] by +/// the provided [`AbsoluteOffset`]. +pub fn scroll_by<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { + struct ScrollBy { + target: Id, + offset: AbsoluteOffset, + } + + impl<T> Operation<T> for ScrollBy { + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + 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>, + 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 c3a66360..3b794099 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -459,6 +459,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> { _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 c2089340..f8455392 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -243,6 +243,24 @@ impl Direction { Self::Horizontal(_) => None, } } + + fn align(&self, delta: Vector) -> Vector { + let horizontal_alignment = + self.horizontal().map(|p| p.alignment).unwrap_or_default(); + + let vertical_alignment = + self.vertical().map(|p| p.alignment).unwrap_or_default(); + + let align = |alignment: Anchor, delta: f32| match alignment { + Anchor::Start => delta, + Anchor::End => -delta, + }; + + Vector::new( + align(horizontal_alignment, delta.x), + align(vertical_alignment, delta.y), + ) + } } impl Default for Direction { @@ -430,6 +448,7 @@ where state, self.id.as_ref().map(|id| &id.0), bounds, + content_bounds, translation, ); @@ -729,12 +748,16 @@ where }; // TODO: Configurable speed/friction (?) - movement * 60.0 + -movement * 60.0 } mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), }; - state.scroll(delta, self.direction, bounds, content_bounds); + state.scroll( + self.direction.align(delta), + bounds, + content_bounds, + ); if notify_on_scroll( state, @@ -770,13 +793,12 @@ where }; let delta = Vector::new( - cursor_position.x - scroll_box_touched_at.x, - cursor_position.y - scroll_box_touched_at.y, + scroll_box_touched_at.x - cursor_position.x, + scroll_box_touched_at.y - cursor_position.y, ); state.scroll( - delta, - self.direction, + self.direction.align(delta), bounds, content_bounds, ); @@ -1110,19 +1132,27 @@ impl From<Id> for widget::Id { } /// Produces a [`Task`] that snaps the [`Scrollable`] with the given [`Id`] -/// to the provided `percentage` along the x & y axis. +/// to the provided [`RelativeOffset`]. pub fn snap_to<T>(id: Id, offset: RelativeOffset) -> Task<T> { task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset))) } /// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`] -/// to the provided [`AbsoluteOffset`] along the x & y axis. +/// to the provided [`AbsoluteOffset`]. pub fn scroll_to<T>(id: Id, offset: AbsoluteOffset) -> Task<T> { task::effect(Action::widget(operation::scrollable::scroll_to( id.0, offset, ))) } +/// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`] +/// by the provided [`AbsoluteOffset`]. +pub fn scroll_by<T>(id: Id, offset: AbsoluteOffset) -> Task<T> { + task::effect(Action::widget(operation::scrollable::scroll_by( + id.0, offset, + ))) +} + /// Returns [`true`] if the viewport actually changed. fn notify_on_scroll<Message>( state: &mut State, @@ -1210,6 +1240,15 @@ 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)] @@ -1313,34 +1352,13 @@ impl State { pub fn scroll( &mut self, delta: Vector<f32>, - 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: Anchor, delta: f32| match alignment { - Anchor::Start => delta, - Anchor::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) - - delta.y) + + delta.y) .clamp(0.0, content_bounds.height - bounds.height), ); } @@ -1348,7 +1366,7 @@ impl State { if bounds.width < content_bounds.width { self.offset_x = Offset::Absolute( (self.offset_x.absolute(bounds.width, content_bounds.width) - - delta.x) + + delta.x) .clamp(0.0, content_bounds.width - bounds.width), ); } @@ -1394,6 +1412,16 @@ 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.scroll(Vector::new(offset.x, offset.y), bounds, content_bounds); + } + /// 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) { |