summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/vector.rs11
-rw-r--r--core/src/widget/operation.rs46
-rw-r--r--core/src/widget/operation/scrollable.rs45
-rw-r--r--widget/src/container.rs1
-rw-r--r--widget/src/scrollable.rs90
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) {