From 288025f5143f4e3f8bc5af36e86f7afa7f07a4c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 13:34:36 +0100 Subject: Inline helper functions in `widget` modules --- widget/src/scrollable.rs | 807 ++++++++++++++++++++++------------------------- 1 file changed, 375 insertions(+), 432 deletions(-) (limited to 'widget/src/scrollable.rs') 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::(), - 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::(); + 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::(), - 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::(); + 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( Command::widget(operation::scrollable::scroll_to(id.0, offset)) } -/// Computes the layout of a [`Scrollable`]. -pub fn layout( - 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( - state: &mut State, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - direction: Direction, - on_scroll: &Option 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( state: &mut State, on_scroll: &Option Message + '_>>, @@ -1078,9 +1022,8 @@ fn notify_on_scroll( } } -/// The local state of a [`Scrollable`]. #[derive(Debug, Clone, Copy)] -pub struct State { +struct State { scroll_area_touched_at: Option, offset_y: Offset, y_scroller_grabbed_at: Option, -- cgit