cranpose_ui/widgets/
animate_scroll.rs1use std::rc::Rc;
5
6use cranpose_foundation::lazy::LazyListState;
7
8use crate::fling_animation::{SettleAnimation, SpringParams};
9
10pub trait AnimateScroll {
13 fn animate_scroll_by(&self, distance: f32);
15
16 fn animate_scroll_to_item(&self, index: usize, offset: f32);
19}
20
21impl AnimateScroll for LazyListState {
22 fn animate_scroll_by(&self, distance: f32) {
23 animate(*self, distance, None);
24 }
25
26 fn animate_scroll_to_item(&self, index: usize, offset: f32) {
27 animate(
28 *self,
29 distance_to_item(self, index) + offset,
30 Some((index, offset)),
31 );
32 }
33}
34
35fn distance_to_item(state: &LazyListState, index: usize) -> f32 {
39 let shown = state.with_layout_info(|info| {
40 info.visible_items_info
41 .iter()
42 .find(|item| item.index == index)
43 .map(|item| item.offset)
44 });
45 if let Some(distance) = shown.flatten() {
46 return distance;
47 }
48 let first = state.first_visible_item_index_non_reactive();
49 let scrolled = state.first_visible_item_scroll_offset_non_reactive();
50 (index as f32 - first as f32) * state.average_item_size() - scrolled
51}
52
53fn animate(state: LazyListState, distance: f32, land: Option<(usize, f32)>) {
56 let Some(runtime) = cranpose_core::current_runtime_handle() else {
57 match land {
58 Some((index, offset)) => state.scroll_to_item(index, offset),
59 None => {
60 state.dispatch_scroll_delta(-distance);
61 }
62 }
63 return;
64 };
65 let settle = SettleAnimation::new(runtime, SpringParams::SETTLE_POLICY);
66 let stop = settle.clone();
67 let id = state.start_scroll_animation(Rc::new(move || stop.cancel()));
68 settle.start_settle(
69 0.0,
70 0.0,
71 distance,
72 move |delta| -state.dispatch_scroll_delta(-delta),
73 move |_| {
74 state.end_scroll_animation(id);
75 if let Some((index, offset)) = land {
76 state.scroll_to_item(index, offset);
77 }
78 },
79 );
80}