virtualizer_adapter/
tween.rs

1/// A small tween helper for adapter-driven smooth scrolling.
2///
3/// This is deliberately minimal and framework-neutral:
4/// - The adapter provides the time source (`now_ms`).
5/// - The adapter applies the returned scroll offset to the real scroll container.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Tween {
9    pub from: u64,
10    pub to: u64,
11    pub start_ms: u64,
12    pub duration_ms: u64,
13    pub easing: Easing,
14}
15
16impl Tween {
17    /// Creates a tween.
18    ///
19    /// `duration_ms` is clamped to at least 1ms.
20    pub fn new(from: u64, to: u64, start_ms: u64, duration_ms: u64, easing: Easing) -> Self {
21        Self {
22            from,
23            to,
24            start_ms,
25            duration_ms: duration_ms.max(1),
26            easing,
27        }
28    }
29
30    pub fn is_done(&self, now_ms: u64) -> bool {
31        now_ms.saturating_sub(self.start_ms) >= self.duration_ms
32    }
33
34    /// Samples the tween at time `now_ms`.
35    pub fn sample(&self, now_ms: u64) -> u64 {
36        let elapsed = now_ms.saturating_sub(self.start_ms);
37        let t = (elapsed as f32 / self.duration_ms as f32).clamp(0.0, 1.0);
38        let eased = self.easing.sample(t);
39
40        let from = self.from as f32;
41        let to = self.to as f32;
42        let v = from + (to - from) * eased;
43        v.max(0.0) as u64
44    }
45
46    /// Retargets the tween while preserving continuity at `now_ms`.
47    pub fn retarget(&mut self, now_ms: u64, new_to: u64, duration_ms: u64) {
48        let cur = self.sample(now_ms);
49        *self = Self::new(cur, new_to, now_ms, duration_ms, self.easing);
50    }
51}
52
53/// Built-in easing curves.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub enum Easing {
57    Linear,
58    SmoothStep,
59    EaseInOutCubic,
60}
61
62impl Easing {
63    /// Samples the easing curve with `t` in `[0, 1]`.
64    pub fn sample(self, t: f32) -> f32 {
65        match self {
66            Self::Linear => t,
67            Self::SmoothStep => t * t * (3.0 - 2.0 * t),
68            Self::EaseInOutCubic => {
69                if t < 0.5 {
70                    4.0 * t * t * t
71                } else {
72                    let u = -2.0 * t + 2.0;
73                    1.0 - (u * u * u) / 2.0
74                }
75            }
76        }
77    }
78}