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// ── Tests ─────────────────────────────────────────────────────────────────────
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::animation::{Easing, Keyframe};
49
50    #[test]
51    fn animated_value_static_should_return_constant_at_any_time() {
52        let v: AnimatedValue<f64> = AnimatedValue::Static(42.0);
53        assert!(
54            (v.value_at(Duration::ZERO) - 42.0).abs() < f64::EPSILON,
55            "expected 42.0 at t=0"
56        );
57        assert!(
58            (v.value_at(Duration::from_secs(9999)) - 42.0).abs() < f64::EPSILON,
59            "expected 42.0 at t=9999s"
60        );
61    }
62
63    #[test]
64    fn animated_value_track_should_delegate_to_track() {
65        let track = AnimationTrack::new()
66            .push(Keyframe::new(Duration::ZERO, 0.0_f64, Easing::Linear))
67            .push(Keyframe::new(
68                Duration::from_secs(1),
69                1.0_f64,
70                Easing::Linear,
71            ));
72        let v: AnimatedValue<f64> = AnimatedValue::Track(track);
73        let mid = v.value_at(Duration::from_millis(500));
74        assert!(
75            (mid - 0.5).abs() < 1e-9,
76            "expected 0.5 at midpoint, got {mid}"
77        );
78    }
79}