Implement reactive-rendering for scrollable

This commit is contained in:
Héctor Ramón Jiménez 2024-10-23 21:07:45 +02:00
parent 908af3fed7
commit 7fbc195b11
No known key found for this signature in database
GPG key ID: 4C07CEC81AFA161F

View file

@ -81,6 +81,7 @@ pub struct Scrollable<
content: Element<'a, Message, Theme, Renderer>,
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
class: Theme::Class<'a>,
last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
@ -108,6 +109,7 @@ where
content: content.into(),
on_scroll: None,
class: Theme::default(),
last_status: None,
}
.validate()
}
@ -531,6 +533,8 @@ where
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
scrollbars.is_mouse_over(cursor);
let last_offsets = (state.offset_x, state.offset_y);
if let Some(last_scrolled) = state.last_scrolled {
let clear_transaction = match event {
Event::Mouse(
@ -549,12 +553,14 @@ where
}
}
let mut update = || {
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
match event {
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some(scrollbar) = scrollbars.y {
let Some(cursor_position) = cursor.position() else {
let Some(cursor_position) = cursor.position()
else {
return event::Status::Ignored;
};
@ -603,7 +609,8 @@ where
content_bounds,
);
state.y_scroller_grabbed_at = Some(scroller_grabbed_at);
state.y_scroller_grabbed_at =
Some(scroller_grabbed_at);
let _ = notify_scroll(
state,
@ -674,7 +681,8 @@ where
content_bounds,
);
state.x_scroller_grabbed_at = Some(scroller_grabbed_at);
state.x_scroller_grabbed_at =
Some(scroller_grabbed_at);
let _ = notify_scroll(
state,
@ -692,13 +700,16 @@ where
}
let content_status = if state.last_scrolled.is_some()
&& matches!(event, Event::Mouse(mouse::Event::WheelScrolled { .. }))
{
&& matches!(
event,
Event::Mouse(mouse::Event::WheelScrolled { .. })
) {
event::Status::Ignored
} else {
let cursor = match cursor_over_scrollable {
Some(cursor_position)
if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
if !(mouse_over_x_scrollbar
|| mouse_over_y_scrollbar) =>
{
mouse::Cursor::Available(
cursor_position
@ -750,8 +761,9 @@ where
return event::Status::Captured;
}
if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) =
event
if let Event::Keyboard(keyboard::Event::ModifiersChanged(
modifiers,
)) = event
{
state.keyboard_modifiers = modifiers;
@ -766,11 +778,13 @@ where
let delta = match delta {
mouse::ScrollDelta::Lines { x, y } => {
let is_shift_pressed = state.keyboard_modifiers.shift();
let is_shift_pressed =
state.keyboard_modifiers.shift();
// macOS automatically inverts the axes when Shift is pressed
let (x, y) =
if cfg!(target_os = "macos") && is_shift_pressed {
let (x, y) = if cfg!(target_os = "macos")
&& is_shift_pressed
{
(y, x)
} else {
(x, y)
@ -791,7 +805,9 @@ where
// TODO: Configurable speed/friction (?)
-movement * 60.0
}
mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y),
mouse::ScrollDelta::Pixels { x, y } => {
-Vector::new(x, y)
}
};
state.scroll(
@ -818,15 +834,18 @@ where
}
Event::Touch(event)
if state.scroll_area_touched_at.is_some()
|| !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
|| !mouse_over_y_scrollbar
&& !mouse_over_x_scrollbar =>
{
match event {
touch::Event::FingerPressed { .. } => {
let Some(cursor_position) = cursor.position() else {
let Some(cursor_position) = cursor.position()
else {
return event::Status::Ignored;
};
state.scroll_area_touched_at = Some(cursor_position);
state.scroll_area_touched_at =
Some(cursor_position);
}
touch::Event::FingerMoved { .. } => {
if let Some(scroll_box_touched_at) =
@ -879,6 +898,43 @@ where
}
_ => event::Status::Ignored,
}
};
let event_status = update();
let status = if state.y_scroller_grabbed_at.is_some()
|| state.x_scroller_grabbed_at.is_some()
{
Status::Dragged {
is_horizontal_scrollbar_dragged: state
.x_scroller_grabbed_at
.is_some(),
is_vertical_scrollbar_dragged: state
.y_scroller_grabbed_at
.is_some(),
}
} else if cursor_over_scrollable.is_some() {
Status::Hovered {
is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar,
is_vertical_scrollbar_hovered: mouse_over_y_scrollbar,
}
} else {
Status::Active
};
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.last_status = Some(status);
}
if last_offsets != (state.offset_x, state.offset_y)
|| self
.last_status
.is_some_and(|last_status| last_status != status)
{
shell.request_redraw(window::RedrawRequest::NextFrame);
}
event_status
}
fn draw(
@ -920,27 +976,8 @@ where
_ => mouse::Cursor::Unavailable,
};
let status = if state.y_scroller_grabbed_at.is_some()
|| state.x_scroller_grabbed_at.is_some()
{
Status::Dragged {
is_horizontal_scrollbar_dragged: state
.x_scroller_grabbed_at
.is_some(),
is_vertical_scrollbar_dragged: state
.y_scroller_grabbed_at
.is_some(),
}
} else if cursor_over_scrollable.is_some() {
Status::Hovered {
is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar,
is_vertical_scrollbar_hovered: mouse_over_y_scrollbar,
}
} else {
Status::Active
};
let style = theme.style(&self.class, status);
let style = theme
.style(&self.class, self.last_status.unwrap_or(Status::Active));
container::draw_background(renderer, &style.container, layout.bounds());
@ -1323,7 +1360,7 @@ impl operation::Scrollable for State {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum Offset {
Absolute(f32),
Relative(f32),