summaryrefslogtreecommitdiffstats
path: root/widget/src/scrollable.rs
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-03-08 13:34:36 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-03-08 13:34:36 +0100
commit288025f5143f4e3f8bc5af36e86f7afa7f07a4c7 (patch)
treee187f94cc74915f686a4b7cefd0984f4de6eae83 /widget/src/scrollable.rs
parent7161cb40c7a9fbda84ee61060a109b93416b2ea0 (diff)
downloadiced-288025f5143f4e3f8bc5af36e86f7afa7f07a4c7.tar.gz
iced-288025f5143f4e3f8bc5af36e86f7afa7f07a4c7.tar.bz2
iced-288025f5143f4e3f8bc5af36e86f7afa7f07a4c7.zip
Inline helper functions in `widget` modules
Diffstat (limited to 'widget/src/scrollable.rs')
-rw-r--r--widget/src/scrollable.rs807
1 files changed, 375 insertions, 432 deletions
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 6cd2048f..9770ce57 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -269,20 +269,29 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- layout(
- renderer,
- limits,
- self.width,
- self.height,
- &self.direction,
- |renderer, limits| {
- self.content.as_widget().layout(
- &mut tree.children[0],
- renderer,
- limits,
- )
- },
- )
+ layout::contained(limits, self.width, self.height, |limits| {
+ let child_limits = layout::Limits::new(
+ Size::new(limits.min().width, limits.min().height),
+ Size::new(
+ if self.direction.horizontal().is_some() {
+ f32::INFINITY
+ } else {
+ limits.max().width
+ },
+ if self.direction.vertical().is_some() {
+ f32::MAX
+ } else {
+ limits.max().height
+ },
+ ),
+ );
+
+ self.content.as_widget().layout(
+ &mut tree.children[0],
+ renderer,
+ &child_limits,
+ )
+ })
}
fn operate(
@@ -332,28 +341,316 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
- update(
- tree.state.downcast_mut::<State>(),
- event,
- layout,
- cursor,
- clipboard,
- shell,
- self.direction,
- &self.on_scroll,
- |event, layout, cursor, clipboard, shell, viewport| {
- self.content.as_widget_mut().on_event(
- &mut tree.children[0],
- event,
- layout,
- cursor,
- renderer,
- clipboard,
+ let state = tree.state.downcast_mut::<State>();
+ let bounds = layout.bounds();
+ let cursor_over_scrollable = cursor.position_over(bounds);
+
+ let content = layout.children().next().unwrap();
+ let content_bounds = content.bounds();
+
+ let scrollbars =
+ Scrollbars::new(state, self.direction, bounds, content_bounds);
+
+ let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
+ scrollbars.is_mouse_over(cursor);
+
+ let mut event_status = {
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(
+ cursor_position
+ + state.translation(
+ self.direction,
+ bounds,
+ content_bounds,
+ ),
+ )
+ }
+ _ => mouse::Cursor::Unavailable,
+ };
+
+ let translation =
+ state.translation(self.direction, bounds, content_bounds);
+
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ &Rectangle {
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
+ ..bounds
+ },
+ )
+ };
+
+ if let event::Status::Captured = event_status {
+ return event::Status::Captured;
+ }
+
+ if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) =
+ event
+ {
+ state.keyboard_modifiers = modifiers;
+
+ return event::Status::Ignored;
+ }
+
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ if cursor_over_scrollable.is_none() {
+ return event::Status::Ignored;
+ }
+
+ let delta = match delta {
+ mouse::ScrollDelta::Lines { x, y } => {
+ // TODO: Configurable speed/friction (?)
+ let movement = if state.keyboard_modifiers.shift() {
+ Vector::new(y, x)
+ } else {
+ Vector::new(x, y)
+ };
+
+ movement * 60.0
+ }
+ mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
+ };
+
+ state.scroll(delta, self.direction, bounds, content_bounds);
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
shell,
- viewport,
- )
- },
- )
+ );
+
+ event_status = event::Status::Captured;
+ }
+ Event::Touch(event)
+ if state.scroll_area_touched_at.is_some()
+ || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
+ {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
+ state.scroll_area_touched_at = Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ state.scroll_area_touched_at
+ {
+ let Some(cursor_position) = cursor.position()
+ else {
+ return event::Status::Ignored;
+ };
+
+ let delta = Vector::new(
+ cursor_position.x - scroll_box_touched_at.x,
+ cursor_position.y - scroll_box_touched_at.y,
+ );
+
+ state.scroll(
+ delta,
+ self.direction,
+ bounds,
+ content_bounds,
+ );
+
+ state.scroll_area_touched_at =
+ Some(cursor_position);
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
+ }
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ state.scroll_area_touched_at = None;
+ }
+ }
+
+ event_status = event::Status::Captured;
+ }
+ _ => {}
+ }
+
+ if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
+ match event {
+ Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ state.y_scroller_grabbed_at = None;
+
+ event_status = event::Status::Captured;
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if let Some(scrollbar) = scrollbars.y {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
+ state.scroll_y_to(
+ scrollbar.scroll_percentage_y(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+
+ event_status = event::Status::Captured;
+ }
+ }
+ _ => {}
+ }
+ } else if mouse_over_y_scrollbar {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
+ if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
+ scrollbars.grab_y_scroller(cursor_position),
+ scrollbars.y,
+ ) {
+ state.scroll_y_to(
+ scrollbar.scroll_percentage_y(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
+
+ state.y_scroller_grabbed_at = Some(scroller_grabbed_at);
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
+
+ event_status = event::Status::Captured;
+ }
+ _ => {}
+ }
+ }
+
+ if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
+ match event {
+ Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ state.x_scroller_grabbed_at = None;
+
+ event_status = event::Status::Captured;
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
+ if let Some(scrollbar) = scrollbars.x {
+ state.scroll_x_to(
+ scrollbar.scroll_percentage_x(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
+
+ event_status = event::Status::Captured;
+ }
+ _ => {}
+ }
+ } else if mouse_over_x_scrollbar {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
+ if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
+ scrollbars.grab_x_scroller(cursor_position),
+ scrollbars.x,
+ ) {
+ state.scroll_x_to(
+ scrollbar.scroll_percentage_x(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
+
+ state.x_scroller_grabbed_at = Some(scroller_grabbed_at);
+
+ notify_on_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+
+ event_status = event::Status::Captured;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ event_status
}
fn draw(
@@ -551,21 +848,48 @@ where
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(
- tree.state.downcast_ref::<State>(),
- layout,
- cursor,
- self.direction,
- |layout, cursor, viewport| {
- self.content.as_widget().mouse_interaction(
- &tree.children[0],
- layout,
- cursor,
- viewport,
- renderer,
- )
- },
- )
+ let state = tree.state.downcast_ref::<State>();
+ let bounds = layout.bounds();
+ let cursor_over_scrollable = cursor.position_over(bounds);
+
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+
+ let scrollbars =
+ Scrollbars::new(state, self.direction, bounds, content_bounds);
+
+ let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
+ scrollbars.is_mouse_over(cursor);
+
+ if (mouse_over_x_scrollbar || mouse_over_y_scrollbar)
+ || state.scrollers_grabbed()
+ {
+ mouse::Interaction::Idle
+ } else {
+ let translation =
+ state.translation(self.direction, bounds, content_bounds);
+
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(cursor_position + translation)
+ }
+ _ => mouse::Cursor::Unavailable,
+ };
+
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor,
+ &Rectangle {
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
+ ..bounds
+ },
+ renderer,
+ )
+ }
}
fn overlay<'b>(
@@ -651,386 +975,6 @@ pub fn scroll_to<Message: 'static>(
Command::widget(operation::scrollable::scroll_to(id.0, offset))
}
-/// Computes the layout of a [`Scrollable`].
-pub fn layout<Renderer>(
- renderer: &Renderer,
- limits: &layout::Limits,
- width: Length,
- height: Length,
- direction: &Direction,
- layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
-) -> layout::Node {
- layout::contained(limits, width, height, |limits| {
- let child_limits = layout::Limits::new(
- Size::new(limits.min().width, limits.min().height),
- Size::new(
- if direction.horizontal().is_some() {
- f32::INFINITY
- } else {
- limits.max().width
- },
- if direction.vertical().is_some() {
- f32::MAX
- } else {
- limits.max().height
- },
- ),
- );
-
- layout_content(renderer, &child_limits)
- })
-}
-
-/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`]
-/// accordingly.
-pub fn update<Message>(
- state: &mut State,
- event: Event,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- direction: Direction,
- on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
- update_content: impl FnOnce(
- Event,
- Layout<'_>,
- mouse::Cursor,
- &mut dyn Clipboard,
- &mut Shell<'_, Message>,
- &Rectangle,
- ) -> event::Status,
-) -> event::Status {
- let bounds = layout.bounds();
- let cursor_over_scrollable = cursor.position_over(bounds);
-
- let content = layout.children().next().unwrap();
- let content_bounds = content.bounds();
-
- let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);
-
- let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor);
-
- let mut event_status = {
- let cursor = match cursor_over_scrollable {
- Some(cursor_position)
- if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
- {
- mouse::Cursor::Available(
- cursor_position
- + state.translation(direction, bounds, content_bounds),
- )
- }
- _ => mouse::Cursor::Unavailable,
- };
-
- 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 {
- return event::Status::Captured;
- }
-
- if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event
- {
- state.keyboard_modifiers = modifiers;
-
- return event::Status::Ignored;
- }
-
- match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
- if cursor_over_scrollable.is_none() {
- return event::Status::Ignored;
- }
-
- let delta = match delta {
- mouse::ScrollDelta::Lines { x, y } => {
- // TODO: Configurable speed/friction (?)
- let movement = if state.keyboard_modifiers.shift() {
- Vector::new(y, x)
- } else {
- Vector::new(x, y)
- };
-
- movement * 60.0
- }
- mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
- };
-
- state.scroll(delta, direction, bounds, content_bounds);
-
- notify_on_scroll(state, on_scroll, bounds, content_bounds, shell);
-
- event_status = event::Status::Captured;
- }
- Event::Touch(event)
- if state.scroll_area_touched_at.is_some()
- || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
- {
- match event {
- touch::Event::FingerPressed { .. } => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- state.scroll_area_touched_at = Some(cursor_position);
- }
- touch::Event::FingerMoved { .. } => {
- if let Some(scroll_box_touched_at) =
- state.scroll_area_touched_at
- {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- let delta = Vector::new(
- cursor_position.x - scroll_box_touched_at.x,
- cursor_position.y - scroll_box_touched_at.y,
- );
-
- state.scroll(delta, direction, bounds, content_bounds);
-
- state.scroll_area_touched_at = Some(cursor_position);
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
- }
- touch::Event::FingerLifted { .. }
- | touch::Event::FingerLost { .. } => {
- state.scroll_area_touched_at = None;
- }
- }
-
- event_status = event::Status::Captured;
- }
- _ => {}
- }
-
- if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
- match event {
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerLifted { .. })
- | Event::Touch(touch::Event::FingerLost { .. }) => {
- state.y_scroller_grabbed_at = None;
-
- event_status = event::Status::Captured;
- }
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- if let Some(scrollbar) = scrollbars.y {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- state.scroll_y_to(
- scrollbar.scroll_percentage_y(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
-
- event_status = event::Status::Captured;
- }
- }
- _ => {}
- }
- } else if mouse_over_y_scrollbar {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- if let (Some(scroller_grabbed_at), Some(scrollbar)) =
- (scrollbars.grab_y_scroller(cursor_position), scrollbars.y)
- {
- state.scroll_y_to(
- scrollbar.scroll_percentage_y(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
-
- state.y_scroller_grabbed_at = Some(scroller_grabbed_at);
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
-
- event_status = event::Status::Captured;
- }
- _ => {}
- }
- }
-
- if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
- match event {
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerLifted { .. })
- | Event::Touch(touch::Event::FingerLost { .. }) => {
- state.x_scroller_grabbed_at = None;
-
- event_status = event::Status::Captured;
- }
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- if let Some(scrollbar) = scrollbars.x {
- state.scroll_x_to(
- scrollbar.scroll_percentage_x(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
-
- event_status = event::Status::Captured;
- }
- _ => {}
- }
- } else if mouse_over_x_scrollbar {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
-
- if let (Some(scroller_grabbed_at), Some(scrollbar)) =
- (scrollbars.grab_x_scroller(cursor_position), scrollbars.x)
- {
- state.scroll_x_to(
- scrollbar.scroll_percentage_x(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
-
- state.x_scroller_grabbed_at = Some(scroller_grabbed_at);
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
-
- event_status = event::Status::Captured;
- }
- }
- _ => {}
- }
- }
-
- event_status
-}
-
-/// Computes the current [`mouse::Interaction`] of a [`Scrollable`].
-pub fn mouse_interaction(
- state: &State,
- layout: Layout<'_>,
- cursor: mouse::Cursor,
- direction: Direction,
- content_interaction: impl FnOnce(
- Layout<'_>,
- mouse::Cursor,
- &Rectangle,
- ) -> mouse::Interaction,
-) -> mouse::Interaction {
- let bounds = layout.bounds();
- let cursor_over_scrollable = cursor.position_over(bounds);
-
- let content_layout = layout.children().next().unwrap();
- let content_bounds = content_layout.bounds();
-
- let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);
-
- let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor);
-
- if (mouse_over_x_scrollbar || mouse_over_y_scrollbar)
- || state.scrollers_grabbed()
- {
- mouse::Interaction::Idle
- } else {
- let translation = state.translation(direction, bounds, content_bounds);
-
- let cursor = match cursor_over_scrollable {
- Some(cursor_position)
- if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
- {
- mouse::Cursor::Available(cursor_position + translation)
- }
- _ => mouse::Cursor::Unavailable,
- };
-
- content_interaction(
- content_layout,
- cursor,
- &Rectangle {
- y: bounds.y + translation.y,
- x: bounds.x + translation.x,
- ..bounds
- },
- )
- }
-}
-
fn notify_on_scroll<Message>(
state: &mut State,
on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
@@ -1078,9 +1022,8 @@ fn notify_on_scroll<Message>(
}
}
-/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
-pub struct State {
+struct State {
scroll_area_touched_at: Option<Point>,
offset_y: Offset,
y_scroller_grabbed_at: Option<f32>,