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 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}