summaryrefslogtreecommitdiffstats
path: root/widget/src/scrollable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'widget/src/scrollable.rs')
-rw-r--r--widget/src/scrollable.rs113
1 files changed, 85 insertions, 28 deletions
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 88746ac4..49aed2f0 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -46,7 +46,7 @@ where
id: None,
width: Length::Shrink,
height: Length::Shrink,
- direction: Default::default(),
+ direction: Direction::default(),
content: content.into(),
on_scroll: None,
style: Default::default(),
@@ -117,7 +117,7 @@ impl Direction {
match self {
Self::Horizontal(properties) => Some(properties),
Self::Both { horizontal, .. } => Some(horizontal),
- _ => None,
+ Self::Vertical(_) => None,
}
}
@@ -126,7 +126,7 @@ impl Direction {
match self {
Self::Vertical(properties) => Some(properties),
Self::Both { vertical, .. } => Some(vertical),
- _ => None,
+ Self::Horizontal(_) => None,
}
}
}
@@ -217,7 +217,7 @@ where
}
fn diff(&self, tree: &mut Tree) {
- tree.diff_children(std::slice::from_ref(&self.content))
+ tree.diff_children(std::slice::from_ref(&self.content));
}
fn width(&self) -> Length {
@@ -230,6 +230,7 @@ where
fn layout(
&self,
+ tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
@@ -240,7 +241,11 @@ where
self.height,
&self.direction,
|renderer, limits| {
- self.content.as_widget().layout(renderer, limits)
+ self.content.as_widget().layout(
+ &mut tree.children[0],
+ renderer,
+ limits,
+ )
},
)
}
@@ -254,10 +259,22 @@ where
) {
let state = tree.state.downcast_mut::<State>();
- operation.scrollable(state, self.id.as_ref().map(|id| &id.0));
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let translation =
+ state.translation(self.direction, bounds, content_bounds);
+
+ operation.scrollable(
+ state,
+ self.id.as_ref().map(|id| &id.0),
+ bounds,
+ translation,
+ );
operation.container(
self.id.as_ref().map(|id| &id.0),
+ bounds,
&mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
@@ -278,6 +295,7 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
+ _viewport: &Rectangle,
) -> event::Status {
update(
tree.state.downcast_mut::<State>(),
@@ -288,7 +306,7 @@ where
shell,
self.direction,
&self.on_scroll,
- |event, layout, cursor, clipboard, shell| {
+ |event, layout, cursor, clipboard, shell, viewport| {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
@@ -297,6 +315,7 @@ where
renderer,
clipboard,
shell,
+ viewport,
)
},
)
@@ -329,9 +348,9 @@ where
layout,
cursor,
viewport,
- )
+ );
},
- )
+ );
}
fn mouse_interaction(
@@ -492,6 +511,7 @@ pub fn update<Message>(
mouse::Cursor,
&mut dyn Clipboard,
&mut Shell<'_, Message>,
+ &Rectangle,
) -> event::Status,
) -> event::Status {
let bounds = layout.bounds();
@@ -518,7 +538,20 @@ pub fn update<Message>(
_ => mouse::Cursor::Unavailable,
};
- update_content(event.clone(), content, cursor, clipboard, shell)
+ let translation = state.translation(direction, bounds, content_bounds);
+
+ update_content(
+ event.clone(),
+ content,
+ cursor,
+ clipboard,
+ shell,
+ &Rectangle {
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
+ ..bounds
+ },
+ )
};
if let event::Status::Captured = event_status {
@@ -565,7 +598,7 @@ pub fn update<Message>(
match event {
touch::Event::FingerPressed { .. } => {
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
state.scroll_area_touched_at = Some(cursor_position);
@@ -575,7 +608,7 @@ pub fn update<Message>(
state.scroll_area_touched_at
{
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
let delta = Vector::new(
@@ -620,7 +653,7 @@ pub fn update<Message>(
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some(scrollbar) = scrollbars.y {
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
state.scroll_y_to(
@@ -650,7 +683,7 @@ pub fn update<Message>(
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
@@ -694,7 +727,7 @@ pub fn update<Message>(
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
if let Some(scrollbar) = scrollbars.x {
@@ -725,7 +758,7 @@ pub fn update<Message>(
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored
+ return event::Status::Ignored;
};
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
@@ -1036,7 +1069,7 @@ impl operation::Scrollable for State {
}
fn scroll_to(&mut self, offset: AbsoluteOffset) {
- State::scroll_to(self, offset)
+ State::scroll_to(self, offset);
}
}
@@ -1095,6 +1128,20 @@ impl Viewport {
AbsoluteOffset { x, y }
}
+ /// Returns the [`AbsoluteOffset`] of the current [`Viewport`], but with its
+ /// alignment reversed.
+ ///
+ /// This method can be useful to switch the alignment of a [`Scrollable`]
+ /// while maintaining its scrolling position.
+ pub fn absolute_offset_reversed(&self) -> AbsoluteOffset {
+ let AbsoluteOffset { x, y } = self.absolute_offset();
+
+ AbsoluteOffset {
+ x: (self.content_bounds.width - self.bounds.width).max(0.0) - x,
+ y: (self.content_bounds.height - self.bounds.height).max(0.0) - y,
+ }
+ }
+
/// Returns the [`RelativeOffset`] of the current [`Viewport`].
pub fn relative_offset(&self) -> RelativeOffset {
let AbsoluteOffset { x, y } = self.absolute_offset();
@@ -1104,6 +1151,16 @@ impl Viewport {
RelativeOffset { x, y }
}
+
+ /// Returns the bounds of the current [`Viewport`].
+ pub fn bounds(&self) -> Rectangle {
+ self.bounds
+ }
+
+ /// Returns the content bounds of the current [`Viewport`].
+ pub fn content_bounds(&self) -> Rectangle {
+ self.content_bounds
+ }
}
impl State {
@@ -1146,7 +1203,7 @@ impl State {
(self.offset_y.absolute(bounds.height, content_bounds.height)
- delta.y)
.clamp(0.0, content_bounds.height - bounds.height),
- )
+ );
}
if bounds.width < content_bounds.width {
@@ -1307,15 +1364,15 @@ impl Scrollbars {
let ratio = bounds.height / content_bounds.height;
// min height for easier grabbing with super tall content
- let scroller_height = (bounds.height * ratio).max(2.0);
- let scroller_offset = translation.y * ratio;
+ let scroller_height = (scrollbar_bounds.height * ratio).max(2.0);
+ let scroller_offset =
+ translation.y * ratio * scrollbar_bounds.height / bounds.height;
let scroller_bounds = Rectangle {
x: bounds.x + bounds.width
- total_scrollbar_width / 2.0
- scroller_width / 2.0,
- y: (scrollbar_bounds.y + scroller_offset - x_scrollbar_height)
- .max(0.0),
+ y: (scrollbar_bounds.y + scroller_offset).max(0.0),
width: scroller_width,
height: scroller_height,
};
@@ -1342,8 +1399,8 @@ impl Scrollbars {
// Need to adjust the width of the horizontal scrollbar if the vertical scrollbar
// is present
- let scrollbar_y_width = show_scrollbar_y
- .map_or(0.0, |v| v.width.max(v.scroller_width) + v.margin);
+ let scrollbar_y_width = y_scrollbar
+ .map_or(0.0, |scrollbar| scrollbar.total_bounds.width);
let total_scrollbar_height =
width.max(scroller_width) + 2.0 * margin;
@@ -1368,12 +1425,12 @@ impl Scrollbars {
let ratio = bounds.width / content_bounds.width;
// min width for easier grabbing with extra wide content
- let scroller_length = (bounds.width * ratio).max(2.0);
- let scroller_offset = translation.x * ratio;
+ let scroller_length = (scrollbar_bounds.width * ratio).max(2.0);
+ let scroller_offset =
+ translation.x * ratio * scrollbar_bounds.width / bounds.width;
let scroller_bounds = Rectangle {
- x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width)
- .max(0.0),
+ x: (scrollbar_bounds.x + scroller_offset).max(0.0),
y: bounds.y + bounds.height
- total_scrollbar_height / 2.0
- scroller_width / 2.0,