Implement scroll_by operation for scrollable
`scroll_by` allows scrolling an absolute offset that is applied to the current scrolling position.
This commit is contained in:
parent
44235f0c0b
commit
e102e89c6a
5 changed files with 157 additions and 36 deletions
|
|
@ -20,6 +20,17 @@ impl Vector {
|
||||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
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>
|
impl<T> std::ops::Add for Vector<T>
|
||||||
where
|
where
|
||||||
T: std::ops::Add<Output = T>,
|
T: std::ops::Add<Output = T>,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ pub trait Operation<T = ()>: Send {
|
||||||
_state: &mut dyn Scrollable,
|
_state: &mut dyn Scrollable,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
|
_content_bounds: Rectangle,
|
||||||
_translation: Vector,
|
_translation: Vector,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
@ -76,9 +77,16 @@ where
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
translation: Vector,
|
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>) {
|
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||||
|
|
@ -151,9 +159,16 @@ where
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
translation: Vector,
|
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>) {
|
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||||
|
|
@ -222,9 +237,16 @@ where
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
self.operation.scrollable(state, id, bounds, translation);
|
self.operation.scrollable(
|
||||||
|
state,
|
||||||
|
id,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
translation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focusable(
|
fn focusable(
|
||||||
|
|
@ -262,9 +284,16 @@ where
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
translation: Vector,
|
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>) {
|
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||||
|
|
@ -341,9 +370,16 @@ where
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
translation: crate::Vector,
|
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>) {
|
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,14 @@ pub trait Scrollable {
|
||||||
|
|
||||||
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
|
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
|
||||||
fn scroll_to(&mut self, offset: AbsoluteOffset);
|
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
|
/// 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,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
|
_content_bounds: Rectangle,
|
||||||
_translation: Vector,
|
_translation: Vector,
|
||||||
) {
|
) {
|
||||||
if Some(&self.target) == id {
|
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,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
|
_content_bounds: Rectangle,
|
||||||
_translation: Vector,
|
_translation: Vector,
|
||||||
) {
|
) {
|
||||||
if Some(&self.target) == id {
|
if Some(&self.target) == id {
|
||||||
|
|
@ -79,6 +89,41 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
|
||||||
ScrollTo { target, offset }
|
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`].
|
/// The amount of absolute offset in each direction of a [`Scrollable`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
pub struct AbsoluteOffset {
|
pub struct AbsoluteOffset {
|
||||||
|
|
|
||||||
|
|
@ -459,6 +459,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
|
||||||
_state: &mut dyn widget::operation::Scrollable,
|
_state: &mut dyn widget::operation::Scrollable,
|
||||||
_id: Option<&widget::Id>,
|
_id: Option<&widget::Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
_content_bounds: Rectangle,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
match self.scrollables.last() {
|
match self.scrollables.last() {
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,24 @@ impl Direction {
|
||||||
Self::Horizontal(_) => None,
|
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 {
|
impl Default for Direction {
|
||||||
|
|
@ -430,6 +448,7 @@ where
|
||||||
state,
|
state,
|
||||||
self.id.as_ref().map(|id| &id.0),
|
self.id.as_ref().map(|id| &id.0),
|
||||||
bounds,
|
bounds,
|
||||||
|
content_bounds,
|
||||||
translation,
|
translation,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -729,12 +748,16 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Configurable speed/friction (?)
|
// TODO: Configurable speed/friction (?)
|
||||||
movement * 60.0
|
-movement * 60.0
|
||||||
}
|
}
|
||||||
mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
|
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(
|
if notify_on_scroll(
|
||||||
state,
|
state,
|
||||||
|
|
@ -770,13 +793,12 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let delta = Vector::new(
|
let delta = Vector::new(
|
||||||
cursor_position.x - scroll_box_touched_at.x,
|
scroll_box_touched_at.x - cursor_position.x,
|
||||||
cursor_position.y - scroll_box_touched_at.y,
|
scroll_box_touched_at.y - cursor_position.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.scroll(
|
state.scroll(
|
||||||
delta,
|
self.direction.align(delta),
|
||||||
self.direction,
|
|
||||||
bounds,
|
bounds,
|
||||||
content_bounds,
|
content_bounds,
|
||||||
);
|
);
|
||||||
|
|
@ -1110,19 +1132,27 @@ impl From<Id> for widget::Id {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces a [`Task`] that snaps the [`Scrollable`] with the given [`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> {
|
pub fn snap_to<T>(id: Id, offset: RelativeOffset) -> Task<T> {
|
||||||
task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset)))
|
task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`]
|
/// 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> {
|
pub fn scroll_to<T>(id: Id, offset: AbsoluteOffset) -> Task<T> {
|
||||||
task::effect(Action::widget(operation::scrollable::scroll_to(
|
task::effect(Action::widget(operation::scrollable::scroll_to(
|
||||||
id.0, offset,
|
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.
|
/// Returns [`true`] if the viewport actually changed.
|
||||||
fn notify_on_scroll<Message>(
|
fn notify_on_scroll<Message>(
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
|
|
@ -1210,6 +1240,15 @@ impl operation::Scrollable for State {
|
||||||
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
||||||
State::scroll_to(self, offset);
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -1313,34 +1352,13 @@ impl State {
|
||||||
pub fn scroll(
|
pub fn scroll(
|
||||||
&mut self,
|
&mut self,
|
||||||
delta: Vector<f32>,
|
delta: Vector<f32>,
|
||||||
direction: Direction,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
content_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 {
|
if bounds.height < content_bounds.height {
|
||||||
self.offset_y = Offset::Absolute(
|
self.offset_y = Offset::Absolute(
|
||||||
(self.offset_y.absolute(bounds.height, content_bounds.height)
|
(self.offset_y.absolute(bounds.height, content_bounds.height)
|
||||||
- delta.y)
|
+ delta.y)
|
||||||
.clamp(0.0, content_bounds.height - bounds.height),
|
.clamp(0.0, content_bounds.height - bounds.height),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1348,7 +1366,7 @@ impl State {
|
||||||
if bounds.width < content_bounds.width {
|
if bounds.width < content_bounds.width {
|
||||||
self.offset_x = Offset::Absolute(
|
self.offset_x = Offset::Absolute(
|
||||||
(self.offset_x.absolute(bounds.width, content_bounds.width)
|
(self.offset_x.absolute(bounds.width, content_bounds.width)
|
||||||
- delta.x)
|
+ delta.x)
|
||||||
.clamp(0.0, content_bounds.width - bounds.width),
|
.clamp(0.0, content_bounds.width - bounds.width),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1394,6 +1412,16 @@ impl State {
|
||||||
self.offset_y = Offset::Absolute(offset.y.max(0.0));
|
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
|
/// 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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue