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#[cfg(test)]
70mod tests {
71    use super::*;
72
73    // Minimal Lerp impl used only within these tests.
74    // The real impl for f64 is added in issue #351.
75    #[derive(Clone, Debug)]
76    struct TestVal(f64);
77
78    impl Lerp for TestVal {
79        fn lerp(a: &Self, b: &Self, t: f64) -> Self {
80            TestVal(a.0 + (b.0 - a.0) * t)
81        }
82    }
83
84    fn kf(ms: u64, v: f64) -> Keyframe<TestVal> {
85        Keyframe::new(Duration::from_millis(ms), TestVal(v), Easing::Linear)
86    }
87
88    #[test]
89    fn keyframe_new_should_store_all_fields() {
90        let ts = Duration::from_millis(500);
91        let kf = Keyframe::new(ts, TestVal(1.0), Easing::EaseInOut);
92        assert_eq!(kf.timestamp, ts);
93        assert!((kf.value.0 - 1.0).abs() < f64::EPSILON);
94        assert!(matches!(kf.easing, Easing::EaseInOut));
95    }
96
97    #[test]
98    fn keyframe_should_order_by_timestamp() {
99        let a = kf(100, 0.0);
100        let b = kf(200, 0.5);
101        let c = kf(300, 1.0);
102
103        assert!(a < b);
104        assert!(b < c);
105        assert!(a < c);
106
107        let mut frames = vec![c, a, b];
108        frames.sort();
109        assert_eq!(frames[0].timestamp, Duration::from_millis(100));
110        assert_eq!(frames[1].timestamp, Duration::from_millis(200));
111        assert_eq!(frames[2].timestamp, Duration::from_millis(300));
112    }
113
114    #[test]
115    fn keyframe_should_compare_equal_by_timestamp_only() {
116        let a = Keyframe::new(Duration::from_millis(100), TestVal(0.0), Easing::Linear);
117        let b = Keyframe::new(Duration::from_millis(100), TestVal(99.0), Easing::Hold);
118        // Same timestamp → equal regardless of value or easing.
119        assert_eq!(a, b);
120        assert_eq!(a.cmp(&b), Ordering::Equal);
121    }
122
123    #[test]
124    fn keyframe_should_be_less_than_later_keyframe() {
125        let early = kf(0, 0.0);
126        let late = kf(1000, 1.0);
127        assert!(early < late);
128        assert!(late > early);
129    }
130}