Skip to content

Commit

Permalink
Implement animated scroll_to_top on scrollable
Browse files Browse the repository at this point in the history
  • Loading branch information
w4 committed Jan 13, 2024
1 parent 25b2f0c commit 01e07d6
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 9 deletions.
1 change: 1 addition & 0 deletions widget/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ wgpu = ["iced_renderer/wgpu"]
iced_renderer.workspace = true
iced_runtime.workspace = true
iced_style.workspace = true
keyframe = "1.1"

num-traits.workspace = true
thiserror.workspace = true
Expand Down
4 changes: 2 additions & 2 deletions widget/src/mouse_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use std::time::{Duration, Instant};

use crate::core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget,
event, layout, mouse, overlay, renderer, touch,
widget::{tree, Operation, Tree},
window::RedrawRequest, Event,
window::RedrawRequest,
Clipboard, Element, Event, Layout, Length, Rectangle, Shell, Size, Widget,
};

/// Emit messages on mouse events.
Expand Down
3 changes: 2 additions & 1 deletion widget/src/overlay/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ where
impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer>
where
T: ToString + Clone,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
Expand Down Expand Up @@ -177,6 +177,7 @@ where
Renderer: text::Renderer,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
Message: Clone,
{
pub fn new<T>(
menu: Menu<'a, T, Message, Renderer>,
Expand Down
6 changes: 3 additions & 3 deletions widget/src/pick_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
where
T: Clone + ToString + PartialEq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
Expand Down Expand Up @@ -283,7 +283,7 @@ impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
where
T: Clone + ToString + PartialEq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
Expand Down Expand Up @@ -579,7 +579,7 @@ pub fn overlay<'a, T, Message, Renderer>(
) -> Option<overlay::Element<'a, Message, Renderer>>
where
T: Clone + ToString,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
Expand Down
69 changes: 66 additions & 3 deletions widget/src/scrollable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
Expand All @@ -9,12 +8,16 @@ use crate::core::touch;
use crate::core::widget;
use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::{keyboard, window};
use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
Rectangle, Shell, Size, Vector, Widget,
};
use crate::runtime::Command;
use keyframe::{functions::EaseOutQuint, keyframes, AnimationSequence};
use std::time::Instant;

use crate::core::window::RedrawRequest;
pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet};
pub use operation::scrollable::{AbsoluteOffset, RelativeOffset};

Expand All @@ -32,7 +35,9 @@ where
direction: Direction,
content: Element<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
on_animate_finished: Option<Message>,
style: <Renderer::Theme as StyleSheet>::Style,
scroll_to_top: bool,
}

impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer>
Expand All @@ -49,10 +54,24 @@ where
direction: Direction::default(),
content: content.into(),
on_scroll: None,
on_animate_finished: None,
style: Default::default(),
scroll_to_top: false,
}
}

/// Message to send when a `scroll_to_top` finishes.
pub fn on_animate_finished(mut self, msg: Message) -> Self {
self.on_animate_finished = Some(msg);
self
}

/// Scrolls the scrollable to the top.
pub fn scroll_to_top(mut self, v: bool) -> Self {
self.scroll_to_top = v;
self
}

/// Sets the [`Id`] of the [`Scrollable`].
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
Expand Down Expand Up @@ -203,6 +222,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
where
Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
Message: Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
Expand Down Expand Up @@ -305,6 +325,8 @@ where
shell,
self.direction,
&self.on_scroll,
self.on_animate_finished.as_ref(),
self.scroll_to_top,
|event, layout, cursor, clipboard, shell, viewport| {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
Expand Down Expand Up @@ -410,6 +432,7 @@ where
Message: 'a,
Renderer: 'a + crate::core::Renderer,
Renderer::Theme: StyleSheet,
Message: Clone,
{
fn from(
text_input: Scrollable<'a, Message, Renderer>,
Expand Down Expand Up @@ -492,7 +515,7 @@ pub fn layout<Renderer>(

/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`]
/// accordingly.
pub fn update<Message>(
pub fn update<Message: Clone>(
state: &mut State,
event: Event,
layout: Layout<'_>,
Expand All @@ -501,6 +524,8 @@ pub fn update<Message>(
shell: &mut Shell<'_, Message>,
direction: Direction,
on_scroll: &Option<Box<dyn Fn(Viewport) -> Message + '_>>,
on_animate_finished: Option<&Message>,
scroll_to_top: bool,
update_content: impl FnOnce(
Event,
Layout<'_>,
Expand All @@ -516,6 +541,41 @@ pub fn update<Message>(
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();

if scroll_to_top {
let (last_render, keyframes) = state.animate.get_or_insert_with(|| {
let offs = match state.offset_y {
Offset::Absolute(v) => v,
Offset::Relative(_) => panic!(),
};
let keyframes = keyframes![(offs, 0.0, EaseOutQuint), (0.0, 0.5)];
(Instant::now(), keyframes)
});

if let Event::Window(_, window::Event::RedrawRequested(_)) = event {
let _ = keyframes.advance_by(last_render.elapsed().as_secs_f64());
state.offset_y = Offset::Absolute(keyframes.now());
*last_render = Instant::now();
let finished = keyframes.finished();

notify_on_scroll(state, on_scroll, bounds, content_bounds, shell);

if finished {
if let Some(msg) = on_animate_finished {
shell.publish(msg.clone());
}
} else {
shell.request_redraw(RedrawRequest::NextFrame);
}

return event::Status::Captured;
} else {
// ignore any other events while we're animating
return event::Status::Ignored;
}
} else {
state.animate = None;
}

let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds);

let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
Expand Down Expand Up @@ -1034,7 +1094,8 @@ fn notify_on_scroll<Message>(
}

/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct State {
scroll_area_touched_at: Option<Point>,
offset_y: Offset,
Expand All @@ -1043,6 +1104,7 @@ pub struct State {
x_scroller_grabbed_at: Option<f32>,
keyboard_modifiers: keyboard::Modifiers,
last_notified: Option<Viewport>,
animate: Option<(Instant, AnimationSequence<f32>)>,
}

impl Default for State {
Expand All @@ -1055,6 +1117,7 @@ impl Default for State {
x_scroller_grabbed_at: None,
keyboard_modifiers: keyboard::Modifiers::default(),
last_notified: None,
animate: None,
}
}
}
Expand Down

0 comments on commit 01e07d6

Please sign in to comment.