Skip to main content

ff_filter/animation/
value.rs

1use std::time::Duration;
2
3use super::{AnimationTrack, Lerp};
4
5/// A value that is either constant or animated over time.
6///
7/// Use [`Static`](AnimatedValue::Static) for values that never change, and
8/// [`Track`](AnimatedValue::Track) for values driven by a keyframe
9/// [`AnimationTrack`].
10///
11/// Evaluated at build time via [`value_at(Duration::ZERO)`](Self::value_at) to
12/// set initial filter parameters.  Per-frame updates are wired up in issue
13/// #363.
14#[derive(Debug, Clone)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(
17    feature = "serde",
18    serde(bound(
19        serialize = "T: serde::Serialize",
20        deserialize = "T: serde::Deserialize<'de>",
21    ))
22)]
23pub enum AnimatedValue<T: Lerp> {
24    /// A constant value, independent of time.
25    Static(T),
26    /// A time-varying value driven by a keyframe track.
27    Track(AnimationTrack<T>),
28}
29
30impl<T: Lerp> AnimatedValue<T> {
31    /// Evaluates the value at time `t`.
32    ///
33    /// - `Static(v)` — returns a clone of `v` regardless of `t`.
34    /// - `Track(track)` — delegates to [`AnimationTrack::value_at`].
35    pub fn value_at(&self, t: Duration) -> T {
36        match self {
37            AnimatedValue::Static(v) => v.clone(),
38            AnimatedValue::Track(track) => track.value_at(t),
39        }
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::animation::{Easing, Keyframe};
47
48    #[test]
49    fn animated_value_static_should_return_constant_at_any_time() {
50        let v: AnimatedValue<f64> = AnimatedValue::Static(42.0);
51        assert!(
52            (v.value_at(Duration::ZERO) - 42.0).abs() < f64::EPSILON,
53            "expected 42.0 at t=0"
54        );
55        assert!(
56            (v.value_at(Duration::from_secs(9999)) - 42.0).abs() < f64::EPSILON,
57            "expected 42.0 at t=9999s"
58        );
59    }
60
61    #[test]
62    fn animated_value_track_should_delegate_to_track() {
63        let track = AnimationTrack::new()
64            .push(Keyframe::new(Duration::ZERO, 0.0_f64, Easing::Linear))
65            .push(Keyframe::new(
66                Duration::from_secs(1),
67                1.0_f64,
68                Easing::Linear,
69            ));
70        let v: AnimatedValue<f64> = AnimatedValue::Track(track);
71        let mid = v.value_at(Duration::from_millis(500));
72        assert!(
73            (mid - 0.5).abs() < 1e-9,
74            "expected 0.5 at midpoint, got {mid}"
75        );
76    }
77}