Skip to main content

ff_filter/animation/
keyframe.rs

1use std::cmp::Ordering;
2use std::time::Duration;
3
4use super::{Easing, Lerp};
5
6/// A single keyframe in an animation track.
7///
8/// The `easing` field controls the interpolation from **this** keyframe to the
9/// next one.  The last keyframe's `easing` is never used (there is no
10/// subsequent keyframe to interpolate toward).
11///
12/// # Ordering
13///
14/// Keyframes are ordered and compared by `timestamp` only.  Two keyframes at
15/// the same timestamp are considered equal regardless of their values or easing
16/// — this keeps binary-search by timestamp correct inside
17/// `AnimationTrack` (added in issue #350).
18#[derive(Debug, Clone)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(
21    feature = "serde",
22    serde(bound(
23        serialize = "T: serde::Serialize",
24        deserialize = "T: serde::Deserialize<'de>",
25    ))
26)]
27pub struct Keyframe<T: Lerp> {
28    /// Position of this keyframe on the timeline.
29    pub timestamp: Duration,
30    /// Value held at (and interpolated from) this keyframe.
31    pub value: T,
32    /// Easing applied for the transition from this keyframe to the next.
33    pub easing: Easing,
34}
35
36impl<T: Lerp> Keyframe<T> {
37    /// Creates a new keyframe.
38    pub fn new(timestamp: Duration, value: T, easing: Easing) -> Self {
39        Self {
40            timestamp,
41            value,
42            easing,
43        }
44    }
45}
46
47// ── Ordering by timestamp only ────────────────────────────────────────────────
48
49impl<T: Lerp> PartialEq for Keyframe<T> {
50    fn eq(&self, other: &Self) -> bool {
51        self.timestamp == other.timestamp
52    }
53}
54
55impl<T: Lerp> Eq for Keyframe<T> {}
56
57impl<T: Lerp> PartialOrd for Keyframe<T> {
58    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
59        Some(self.cmp(other))
60    }
61}
62
63impl<T: Lerp> Ord for Keyframe<T> {
64    fn cmp(&self, other: &Self) -> Ordering {
65        self.timestamp.cmp(&other.timestamp)
66    }
67}
68
69// ── Tests ─────────────────────────────────────────────────────────────────────
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    // Minimal Lerp impl used only within these tests.
76    // The real impl for f64 is added in issue #351.
77    #[derive(Clone, Debug)]
78    struct TestVal(f64);
79
80    impl Lerp for TestVal {
81        fn lerp(a: &Self, b: &Self, t: f64) -> Self {
82            TestVal(a.0 + (b.0 - a.0) * t)
83        }
84    }
85
86    fn kf(ms: u64, v: f64) -> Keyframe<TestVal> {
87        Keyframe::new(Duration::from_millis(ms), TestVal(v), Easing::Linear)
88    }
89
90    #[test]
91    fn keyframe_new_should_store_all_fields() {
92        let ts = Duration::from_millis(500);
93        let kf = Keyframe::new(ts, TestVal(1.0), Easing::EaseInOut);
94        assert_eq!(kf.timestamp, ts);
95        assert!((kf.value.0 - 1.0).abs() < f64::EPSILON);
96        assert!(matches!(kf.easing, Easing::EaseInOut));
97    }
98
99    #[test]
100    fn keyframe_should_order_by_timestamp() {
101        let a = kf(100, 0.0);
102        let b = kf(200, 0.5);
103        let c = kf(300, 1.0);
104
105        assert!(a < b);
106        assert!(b < c);
107        assert!(a < c);
108
109        let mut frames = vec![c, a, b];
110        frames.sort();
111        assert_eq!(frames[0].timestamp, Duration::from_millis(100));
112        assert_eq!(frames[1].timestamp, Duration::from_millis(200));
113        assert_eq!(frames[2].timestamp, Duration::from_millis(300));
114    }
115
116    #[test]
117    fn keyframe_should_compare_equal_by_timestamp_only() {
118        let a = Keyframe::new(Duration::from_millis(100), TestVal(0.0), Easing::Linear);
119        let b = Keyframe::new(Duration::from_millis(100), TestVal(99.0), Easing::Hold);
120        // Same timestamp → equal regardless of value or easing.
121        assert_eq!(a, b);
122        assert_eq!(a.cmp(&b), Ordering::Equal);
123    }
124
125    #[test]
126    fn keyframe_should_be_less_than_later_keyframe() {
127        let early = kf(0, 0.0);
128        let late = kf(1000, 1.0);
129        assert!(early < late);
130        assert!(late > early);
131    }
132}