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
89#[cfg(test)]
90mod tests {
91    use super::Hold;
92    use crate::{Animation, AnimationState, timing::Duration};
93
94    #[test]
95    fn advance_returns_overflow_after_completion() {
96        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
97
98        let overflow = hold.advance(Duration::from_millis(80.0));
99
100        assert_eq!(overflow, Duration::from_millis(30.0));
101        assert_eq!(hold.state(), AnimationState::Completed);
102        assert_eq!(*hold.value(), 4);
103    }
104
105    #[test]
106    fn pause_resume_cancel_and_finish_update_state() {
107        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
108
109        hold.pause();
110        assert_eq!(hold.state(), AnimationState::Paused);
111        hold.resume();
112        assert_eq!(hold.state(), AnimationState::Running);
113        hold.cancel();
114        assert_eq!(hold.state(), AnimationState::Canceled);
115        hold.finish();
116        assert_eq!(hold.state(), AnimationState::Completed);
117    }
118
119    #[test]
120    fn seek_clamps_progress_and_updates_state() {
121        let mut hold = Hold::new(4_i32, Duration::from_millis(50.0));
122
123        hold.seek(f32::NAN);
124        assert_eq!(hold.state(), AnimationState::Running);
125        hold.seek(2.0);
126        assert_eq!(hold.state(), AnimationState::Completed);
127    }
128}