Skip to main content

inkling/
easing.rs

1//! Easing functions map linear time `0..=1` onto eased progress `0..=1`.
2//!
3//! Easing shapes *how progress moves over time*; it is independent of the
4//! [`RankMap`](crate::RankMap), which decides *where* a given progress value
5//! reveals.
6
7/// A timing curve. Input and output are both clamped to `0..=1`.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
9pub enum Easing {
10    #[default]
11    Linear,
12    /// Decelerates toward the end, good for a confident finish.
13    EaseOutCubic,
14    /// Strong deceleration; the reveal "lands".
15    EaseOutQuint,
16    /// Slow start and end, fast middle, the classic UI curve.
17    EaseInOutCubic,
18}
19
20impl Easing {
21    /// Apply the curve to a normalized time `t`.
22    pub fn apply(self, t: f32) -> f32 {
23        let t = t.clamp(0.0, 1.0);
24        match self {
25            Easing::Linear => t,
26            Easing::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
27            Easing::EaseOutQuint => 1.0 - (1.0 - t).powi(5),
28            Easing::EaseInOutCubic => {
29                if t < 0.5 {
30                    4.0 * t * t * t
31                } else {
32                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
33                }
34            }
35        }
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn endpoints_are_fixed() {
45        for e in [
46            Easing::Linear,
47            Easing::EaseOutCubic,
48            Easing::EaseOutQuint,
49            Easing::EaseInOutCubic,
50        ] {
51            assert!((e.apply(0.0) - 0.0).abs() < 1e-6);
52            assert!((e.apply(1.0) - 1.0).abs() < 1e-6);
53        }
54    }
55
56    #[test]
57    fn monotonic_non_decreasing() {
58        for e in [
59            Easing::EaseOutCubic,
60            Easing::EaseInOutCubic,
61            Easing::EaseOutQuint,
62        ] {
63            let mut prev = -1.0;
64            for i in 0..=100 {
65                let v = e.apply(i as f32 / 100.0);
66                assert!(v + 1e-6 >= prev, "{e:?} went backwards");
67                prev = v;
68            }
69        }
70    }
71}