aura_anim_core/timeline/
sequence.rs1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3pub struct Sequence<T: Animatable> {
21 children: Vec<Box<dyn Animation<T>>>,
22 current: T,
23 index: usize,
24 state: AnimationState,
25}
26
27impl<T: Animatable> Sequence<T> {
28 #[must_use]
30 pub fn new(initial: T) -> Self {
31 Self {
32 children: Vec::new(),
33 current: initial,
34 index: 0,
35 state: AnimationState::Idle,
36 }
37 }
38
39 #[must_use]
41 pub fn then(mut self, animation: impl Animation<T>) -> Self {
42 self.push(animation);
43 self
44 }
45
46 pub fn push(&mut self, animation: impl Animation<T>) {
48 self.children.push(Box::new(animation));
49 self.state = AnimationState::Running;
50 }
51
52 #[must_use]
54 pub fn len(&self) -> usize {
55 self.children.len()
56 }
57
58 #[must_use]
60 pub fn is_empty(&self) -> bool {
61 self.children.is_empty()
62 }
63
64 fn sync_current(&mut self) {
65 if let Some(child) = self.children.get(self.index) {
66 self.current = child.value().clone();
67 }
68 }
69}
70
71impl<T: Animatable> Animation<T> for Sequence<T> {
72 fn value(&self) -> &T {
73 &self.current
74 }
75
76 fn state(&self) -> AnimationState {
77 self.state
78 }
79
80 fn duration(&self) -> Option<Duration> {
81 self.children
82 .iter()
83 .try_fold(Duration::ZERO, |total, child| {
84 child.duration().map(|duration| total + duration)
85 })
86 }
87
88 fn tick(&mut self, delta: Duration) {
89 self.advance(delta);
90 }
91
92 fn advance(&mut self, delta: Duration) -> Duration {
93 if self.state != AnimationState::Running {
94 return delta;
95 }
96
97 let mut remaining = delta;
98 loop {
99 let Some(child) = self.children.get_mut(self.index) else {
100 self.state = AnimationState::Completed;
101 return remaining;
102 };
103
104 remaining = child.advance(remaining);
105 self.current = child.value().clone();
106 if child.state() != AnimationState::Completed {
107 return Duration::ZERO;
108 }
109
110 self.index += 1;
111 if self.index >= self.children.len() {
112 self.state = AnimationState::Completed;
113 return remaining;
114 }
115 if remaining.is_zero() {
116 return Duration::ZERO;
117 }
118 }
119 }
120
121 fn pause(&mut self) {
122 if self.state == AnimationState::Running {
123 if let Some(child) = self.children.get_mut(self.index) {
124 child.pause();
125 }
126 self.state = AnimationState::Paused;
127 }
128 }
129
130 fn resume(&mut self) {
131 if self.state == AnimationState::Paused {
132 if let Some(child) = self.children.get_mut(self.index) {
133 child.resume();
134 }
135 self.state = AnimationState::Running;
136 }
137 }
138
139 fn cancel(&mut self) {
140 if matches!(self.state, AnimationState::Running | AnimationState::Paused) {
141 if let Some(child) = self.children.get_mut(self.index) {
142 child.cancel();
143 }
144 self.state = AnimationState::Canceled;
145 }
146 }
147
148 #[allow(clippy::cast_possible_truncation)]
149 #[allow(clippy::cast_sign_loss)]
150 #[allow(clippy::cast_precision_loss)]
151 fn seek(&mut self, progress: f32) {
152 if self.children.is_empty() {
153 self.state = AnimationState::Completed;
154 return;
155 }
156
157 let progress = normalized(progress);
158 if let Some(total) = self.duration() {
159 let mut target = total.as_secs() * f64::from(progress);
160 for (index, child) in self.children.iter_mut().enumerate() {
161 let duration = child.duration().unwrap_or(Duration::ZERO).as_secs();
162 if target >= duration {
163 child.finish();
164 target -= duration;
165 self.index = index + 1;
166 } else {
167 let local = if duration <= 0.0 {
168 1.0
169 } else {
170 (target / duration) as f32
171 };
172 child.seek(local);
173 self.index = index;
174 break;
175 }
176 }
177 } else {
178 let len = self.children.len() as f32;
179 let scaled = progress * len;
180 let index = (scaled.floor()).min(len - 1.0);
181 let index_usize = index as usize;
182
183 for child in &mut self.children[..index_usize] {
184 child.finish();
185 }
186 self.index = index_usize;
187 self.children[index_usize].seek((scaled - index).clamp(0.0, 1.0));
188 }
189
190 if self.index >= self.children.len() {
191 self.index = self.children.len() - 1;
192 }
193 self.sync_current();
194 self.state = if progress >= 1.0 {
195 AnimationState::Completed
196 } else {
197 AnimationState::Running
198 };
199 }
200
201 fn finish(&mut self) {
202 for child in &mut self.children {
203 child.finish();
204 }
205 if let Some(child) = self.children.last() {
206 self.current = child.value().clone();
207 }
208 self.index = self.children.len();
209 self.state = AnimationState::Completed;
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::Sequence;
216 use crate::{Animation, AnimationState, Tween, timing::Timing};
217 use float_cmp::assert_approx_eq;
218
219 #[test]
220 fn empty_sequence_remains_idle_when_advanced() {
221 let mut sequence = Sequence::new(2.0_f32);
222
223 let overflow = sequence.advance(crate::timing::Duration::from_millis(10.0));
224
225 assert_eq!(sequence.state(), AnimationState::Idle);
226 assert_eq!(overflow, crate::timing::Duration::from_millis(10.0));
227 assert_approx_eq!(f32, *sequence.value(), 2.0);
228 }
229
230 #[test]
231 fn seek_updates_current_child_value() {
232 let mut sequence = Sequence::new(0.0_f32)
233 .then(Tween::between(0.0, 10.0, Timing::new(100.0)))
234 .then(Tween::between(10.0, 20.0, Timing::new(100.0)));
235
236 sequence.seek(0.75);
237
238 assert_eq!(sequence.state(), AnimationState::Running);
239 assert_approx_eq!(f32, *sequence.value(), 15.0);
240 }
241
242 #[test]
243 fn finish_uses_last_child_value() {
244 let mut sequence = Sequence::new(0.0_f32)
245 .then(Tween::between(0.0, 10.0, Timing::new(100.0)))
246 .then(Tween::between(10.0, 20.0, Timing::new(100.0)));
247
248 sequence.finish();
249
250 assert_eq!(sequence.state(), AnimationState::Completed);
251 assert_approx_eq!(f32, *sequence.value(), 20.0);
252 }
253}