Inline helper functions in widget modules

This commit is contained in:
Héctor Ramón Jiménez 2024-03-08 13:34:36 +01:00
parent 7161cb40c7
commit 288025f514
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
4 changed files with 553 additions and 669 deletions

View file

@ -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>,