Skip to main content

aura_anim_core/timeline/
hold.rs

1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3/// An animation that keeps a value unchanged for a fixed duration.
4#[derive(Debug, Clone)]
5pub struct Hold<T: Animatable> {
6    value: T,
7    elapsed: Duration,
8    duration: Duration,
9    state: AnimationState,
10}
11
12impl<T: Animatable> Hold<T> {
13    /// Creates a running hold animation.
14    #[must_use]
15    pub fn new(value: T, duration: impl Into<Duration>) -> Self {
16        Self {
17            value,
18            elapsed: Duration::ZERO,
19            duration: duration.into(),
20            state: AnimationState::Running,
21        }
22    }
23}
24
25impl<T: Animatable> Animation<T> for Hold<T> {
26    fn value(&self) -> &T {
27        &self.value
28    }
29
30    fn state(&self) -> AnimationState {
31        self.state
32    }
33
34    fn duration(&self) -> Option<Duration> {
35        Some(self.duration)
36    }
37
38    fn tick(&mut self, delta: Duration) {
39        self.advance(delta);
40    }
41
42    fn advance(&mut self, delta: Duration) -> Duration {
43        if self.state != AnimationState::Running {
44            return delta;
45        }
46        let remaining = self.duration.saturating_sub(self.elapsed);
47        let consumed = delta.min(remaining);
48        self.elapsed += consumed;
49        if self.elapsed >= self.duration {
50            self.state = AnimationState::Completed;
51        }
52        delta.saturating_sub(consumed)
53    }
54
55    fn pause(&mut self) {
56        if self.state == AnimationState::Running {
57            self.state = AnimationState::Paused;
58        }
59    }
60
61    fn resume(&mut self) {
62        if self.state == AnimationState::Paused {
63            self.state = AnimationState::Running;
64        }
65    }
66
67    fn cancel(&mut self) {
68        if matches!(self.state, AnimationState::Running | AnimationState::Paused) {
69            self.state = AnimationState::Canceled;
70        }
71    }
72
73    fn seek(&mut self, progress: f32) {
74        self.elapsed =
75            Duration::from_secs(self.duration.as_secs() * f64::from(normalized(progress)));
76        self.state = if normalized(progress) >= 1.0 {
77            AnimationState::Completed
78        } else {
79            AnimationState::Running
80        };
81    }
82
83    fn finish(&mut self) {
84        self.elapsed = self.duration;
85        self.state = AnimationState::Completed;
86    }
87
88    fn set_rate(&mut self, rate: f64) {
89        self.duration = self.duration.divided_by(rate);
90    }
91
92    fn into_value(self: Box<Self>) -> T {
93        self.value
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::Hold;
100    use crate::{Animation, AnimationState, timing::Duration};
101
102    #[test]
103    fn advance_returns_overflow_after_completion() {
104        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
105
106        let overflow = hold.advance(Duration::from_millis(80.0));
107
108        assert_eq!(overflow, Duration::from_millis(30.0));
109        assert_eq!(hold.state(), AnimationState::Completed);
110        assert_eq!(*hold.value(), 4);
111    }
112
113    #[test]
114    fn pause_resume_cancel_and_finish_update_state() {
115        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
116
117        hold.pause();
118        assert_eq!(hold.state(), AnimationState::Paused);
119        hold.resume();
120        assert_eq!(hold.state(), AnimationState::Running);
121        hold.cancel();
122        assert_eq!(hold.state(), AnimationState::Canceled);
123        hold.finish();
124        assert_eq!(hold.state(), AnimationState::Completed);
125    }
126
127    #[test]
128    fn seek_clamps_progress_and_updates_state() {
129        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
130
131        hold.seek(f32::NAN);
132        assert_eq!(hold.state(), AnimationState::Running);
133        hold.seek(2.0);
134        assert_eq!(hold.state(), AnimationState::Completed);
135    }
136
137    #[test]
138    fn rate_scales_hold_duration() {
139        let hold = Hold::new(4_i32, Duration::from_millis(100.0)).rate(2.0);
140
141        assert_eq!(hold.duration(), Some(Duration::from_millis(50.0)));
142    }
143}