aura_anim_core/timeline/
hold.rs1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3#[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 #[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}