Implement reactive-rendering for scrollable
This commit is contained in:
parent
908af3fed7
commit
7fbc195b11
1 changed files with 362 additions and 325 deletions
|
|
@ -81,6 +81,7 @@ pub struct Scrollable<
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -108,6 +109,7 @@ where
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
on_scroll: None,
|
on_scroll: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
.validate()
|
.validate()
|
||||||
}
|
}
|
||||||
|
|
@ -531,6 +533,8 @@ where
|
||||||
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
|
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
|
||||||
scrollbars.is_mouse_over(cursor);
|
scrollbars.is_mouse_over(cursor);
|
||||||
|
|
||||||
|
let last_offsets = (state.offset_x, state.offset_y);
|
||||||
|
|
||||||
if let Some(last_scrolled) = state.last_scrolled {
|
if let Some(last_scrolled) = state.last_scrolled {
|
||||||
let clear_transaction = match event {
|
let clear_transaction = match event {
|
||||||
Event::Mouse(
|
Event::Mouse(
|
||||||
|
|
@ -549,309 +553,65 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
|
let mut update = || {
|
||||||
match event {
|
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = notify_scroll(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
return 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);
|
|
||||||
|
|
||||||
let _ = notify_scroll(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
|
|
||||||
match event {
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = notify_scroll(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 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);
|
|
||||||
|
|
||||||
let _ = notify_scroll(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let content_status = if state.last_scrolled.is_some()
|
|
||||||
&& 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) =>
|
|
||||||
{
|
|
||||||
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(),
|
|
||||||
content,
|
|
||||||
cursor,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
shell,
|
|
||||||
&Rectangle {
|
|
||||||
y: bounds.y + translation.y,
|
|
||||||
x: bounds.x + translation.x,
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
event,
|
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
|
||||||
| Event::Touch(
|
|
||||||
touch::Event::FingerLifted { .. }
|
|
||||||
| touch::Event::FingerLost { .. }
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
state.scroll_area_touched_at = None;
|
|
||||||
state.x_scroller_grabbed_at = None;
|
|
||||||
state.y_scroller_grabbed_at = None;
|
|
||||||
|
|
||||||
return content_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let event::Status::Captured = content_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 } => {
|
|
||||||
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 {
|
|
||||||
(y, x)
|
|
||||||
} else {
|
|
||||||
(x, y)
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_vertical = match self.direction {
|
|
||||||
Direction::Vertical(_) => true,
|
|
||||||
Direction::Horizontal(_) => false,
|
|
||||||
Direction::Both { .. } => !is_shift_pressed,
|
|
||||||
};
|
|
||||||
|
|
||||||
let movement = if is_vertical {
|
|
||||||
Vector::new(x, y)
|
|
||||||
} else {
|
|
||||||
Vector::new(y, x)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Configurable speed/friction (?)
|
|
||||||
-movement * 60.0
|
|
||||||
}
|
|
||||||
mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y),
|
|
||||||
};
|
|
||||||
|
|
||||||
state.scroll(
|
|
||||||
self.direction.align(delta),
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
);
|
|
||||||
|
|
||||||
let has_scrolled = notify_scroll(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
let in_transaction = state.last_scrolled.is_some();
|
|
||||||
|
|
||||||
if has_scrolled || in_transaction {
|
|
||||||
event::Status::Captured
|
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Touch(event)
|
|
||||||
if state.scroll_area_touched_at.is_some()
|
|
||||||
|| !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
|
|
||||||
{
|
|
||||||
match event {
|
match event {
|
||||||
touch::Event::FingerPressed { .. } => {
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
let Some(cursor_position) = cursor.position() else {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
return event::Status::Ignored;
|
if let Some(scrollbar) = scrollbars.y {
|
||||||
};
|
|
||||||
|
|
||||||
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()
|
let Some(cursor_position) = cursor.position()
|
||||||
else {
|
else {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
};
|
};
|
||||||
|
|
||||||
let delta = Vector::new(
|
state.scroll_y_to(
|
||||||
scroll_box_touched_at.x - cursor_position.x,
|
scrollbar.scroll_percentage_y(
|
||||||
scroll_box_touched_at.y - cursor_position.y,
|
scroller_grabbed_at,
|
||||||
);
|
cursor_position,
|
||||||
|
),
|
||||||
state.scroll(
|
|
||||||
self.direction.align(delta),
|
|
||||||
bounds,
|
bounds,
|
||||||
content_bounds,
|
content_bounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.scroll_area_touched_at =
|
let _ = notify_scroll(
|
||||||
Some(cursor_position);
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
return 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);
|
||||||
|
|
||||||
// TODO: bubble up touch movements if not consumed.
|
|
||||||
let _ = notify_scroll(
|
let _ = notify_scroll(
|
||||||
state,
|
state,
|
||||||
&self.on_scroll,
|
&self.on_scroll,
|
||||||
|
|
@ -860,25 +620,321 @@ where
|
||||||
shell,
|
shell,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(_)) => {
|
|
||||||
let _ = notify_viewport(
|
|
||||||
state,
|
|
||||||
&self.on_scroll,
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
|
||||||
|
match event {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = notify_scroll(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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);
|
||||||
|
|
||||||
|
let _ = notify_scroll(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_status = if state.last_scrolled.is_some()
|
||||||
|
&& matches!(
|
||||||
|
event,
|
||||||
|
Event::Mouse(mouse::Event::WheelScrolled { .. })
|
||||||
|
) {
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
|
} else {
|
||||||
|
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(),
|
||||||
|
content,
|
||||||
|
cursor,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
&Rectangle {
|
||||||
|
y: bounds.y + translation.y,
|
||||||
|
x: bounds.x + translation.x,
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
event,
|
||||||
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
| Event::Touch(
|
||||||
|
touch::Event::FingerLifted { .. }
|
||||||
|
| touch::Event::FingerLost { .. }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
state.scroll_area_touched_at = None;
|
||||||
|
state.x_scroller_grabbed_at = None;
|
||||||
|
state.y_scroller_grabbed_at = None;
|
||||||
|
|
||||||
|
return content_status;
|
||||||
}
|
}
|
||||||
_ => event::Status::Ignored,
|
|
||||||
|
if let event::Status::Captured = content_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 } => {
|
||||||
|
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
|
||||||
|
{
|
||||||
|
(y, x)
|
||||||
|
} else {
|
||||||
|
(x, y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_vertical = match self.direction {
|
||||||
|
Direction::Vertical(_) => true,
|
||||||
|
Direction::Horizontal(_) => false,
|
||||||
|
Direction::Both { .. } => !is_shift_pressed,
|
||||||
|
};
|
||||||
|
|
||||||
|
let movement = if is_vertical {
|
||||||
|
Vector::new(x, y)
|
||||||
|
} else {
|
||||||
|
Vector::new(y, x)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Configurable speed/friction (?)
|
||||||
|
-movement * 60.0
|
||||||
|
}
|
||||||
|
mouse::ScrollDelta::Pixels { x, y } => {
|
||||||
|
-Vector::new(x, y)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.scroll(
|
||||||
|
self.direction.align(delta),
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_scrolled = notify_scroll(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
let in_transaction = state.last_scrolled.is_some();
|
||||||
|
|
||||||
|
if has_scrolled || in_transaction {
|
||||||
|
event::Status::Captured
|
||||||
|
} else {
|
||||||
|
event::Status::Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
scroll_box_touched_at.x - cursor_position.x,
|
||||||
|
scroll_box_touched_at.y - cursor_position.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.scroll(
|
||||||
|
self.direction.align(delta),
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.scroll_area_touched_at =
|
||||||
|
Some(cursor_position);
|
||||||
|
|
||||||
|
// TODO: bubble up touch movements if not consumed.
|
||||||
|
let _ = notify_scroll(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
event::Status::Captured
|
||||||
|
}
|
||||||
|
Event::Window(window::Event::RedrawRequested(_)) => {
|
||||||
|
let _ = notify_viewport(
|
||||||
|
state,
|
||||||
|
&self.on_scroll,
|
||||||
|
bounds,
|
||||||
|
content_bounds,
|
||||||
|
shell,
|
||||||
|
);
|
||||||
|
|
||||||
|
event::Status::Ignored
|
||||||
|
}
|
||||||
|
_ => 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(
|
fn draw(
|
||||||
|
|
@ -920,27 +976,8 @@ where
|
||||||
_ => mouse::Cursor::Unavailable,
|
_ => mouse::Cursor::Unavailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = if state.y_scroller_grabbed_at.is_some()
|
let style = theme
|
||||||
|| state.x_scroller_grabbed_at.is_some()
|
.style(&self.class, self.last_status.unwrap_or(Status::Active));
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
container::draw_background(renderer, &style.container, layout.bounds());
|
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 {
|
enum Offset {
|
||||||
Absolute(f32),
|
Absolute(f32),
|
||||||
Relative(f32),
|
Relative(f32),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue