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 6db175a commit f8a73b7
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 7 deletions.
1 change: 1 addition & 0 deletions widget/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ qr_code = ["canvas", "qrcode"]
unicode-segmentation = "1.6"
num-traits = "0.2"
thiserror = "1"
keyframe = "1.1"

[dependencies.iced_runtime]
version = "0.1"
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 + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
Expand Down Expand Up @@ -281,7 +281,7 @@ impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
where
T: Clone + ToString + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Message: 'a + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
Expand Down Expand Up @@ -545,7 +545,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
75 changes: 72 additions & 3 deletions widget/src/scrollable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Navigate an endless amount of content with a scrollbar.

Check failure on line 1 in widget/src/scrollable.rs

View workflow job for this annotation

GitHub Actions / all

Diff in /home/runner/work/iced/iced/widget/src/scrollable.rs
use std::time::Instant;
use keyframe::{keyframes, AnimationSequence, functions::EaseOutQuint};
use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::{keyboard, window};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
Expand All @@ -17,6 +19,7 @@ use crate::runtime::Command;

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

/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
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: Default::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 @@ -301,6 +321,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 @@ -406,6 +428,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 @@ -491,7 +514,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 @@ -500,6 +523,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 @@ -515,6 +540,47 @@ 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();

Check failure on line 556 in widget/src/scrollable.rs

View workflow job for this annotation

GitHub Actions / all

Diff in /home/runner/work/iced/iced/widget/src/scrollable.rs
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 @@ -1033,7 +1099,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 @@ -1042,6 +1109,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 @@ -1054,6 +1122,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 f8a73b7

Please sign in to comment.