aura_anim_core/timeline/
parallel.rs1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3type Compositor<T> = Box<dyn Fn(&[T]) -> T>;
4
5pub struct Parallel<T: Animatable> {
7 children: Vec<Box<dyn Animation<T>>>,
8 outputs: Vec<T>,
9 current: T,
10 compose: Compositor<T>,
11 state: AnimationState,
12}
13
14impl<T: Animatable> Parallel<T> {
15 #[must_use]
17 pub fn new(initial: T, compose: impl Fn(&[T]) -> T + 'static) -> Self {
18 Self {
19 children: Vec::new(),
20 outputs: Vec::new(),
21 current: initial,
22 compose: Box::new(compose),
23 state: AnimationState::Idle,
24 }
25 }
26
27 #[must_use]
29 pub fn with(mut self, animation: impl Animation<T>) -> Self {
30 self.push(animation);
31 self
32 }
33
34 pub fn push(&mut self, animation: impl Animation<T>) {
36 self.outputs.push(animation.value().clone());
37 self.children.push(Box::new(animation));
38 self.state = AnimationState::Running;
39 }
40
41 #[must_use]
43 pub fn len(&self) -> usize {
44 self.children.len()
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.children.is_empty()
51 }
52
53 fn compose_outputs(&mut self) {
54 if !self.outputs.is_empty() {
55 self.current = (self.compose)(&self.outputs);
56 }
57 }
58}
59
60impl<T: Animatable> Animation<T> for Parallel<T> {
61 fn value(&self) -> &T {
62 &self.current
63 }
64
65 fn state(&self) -> AnimationState {
66 self.state
67 }
68
69 fn duration(&self) -> Option<Duration> {
70 self.children
71 .iter()
72 .map(|child| child.duration())
73 .try_fold(Duration::ZERO, |longest, duration| {
74 duration.map(|duration| longest.max(duration))
75 })
76 }
77
78 fn tick(&mut self, delta: Duration) {
79 self.advance(delta);
80 }
81
82 fn advance(&mut self, delta: Duration) -> Duration {
83 if self.state != AnimationState::Running {
84 return delta;
85 }
86 if self.children.is_empty() {
87 self.state = AnimationState::Completed;
88 return delta;
89 }
90
91 let mut overflow = delta;
92 let mut completed = true;
93 for (index, child) in self.children.iter_mut().enumerate() {
94 let child_overflow = child.advance(delta);
95 overflow = overflow.min(child_overflow);
96 self.outputs[index] = child.value().clone();
97 completed &= child.state() == AnimationState::Completed;
98 }
99 self.compose_outputs();
100
101 if completed {
102 self.state = AnimationState::Completed;
103 overflow
104 } else {
105 Duration::ZERO
106 }
107 }
108
109 fn pause(&mut self) {
110 if self.state == AnimationState::Running {
111 for child in &mut self.children {
112 child.pause();
113 }
114 self.state = AnimationState::Paused;
115 }
116 }
117
118 fn resume(&mut self) {
119 if self.state == AnimationState::Paused {
120 for child in &mut self.children {
121 child.resume();
122 }
123 self.state = AnimationState::Running;
124 }
125 }
126
127 fn cancel(&mut self) {
128 if matches!(self.state, AnimationState::Running | AnimationState::Paused) {
129 for child in &mut self.children {
130 child.cancel();
131 }
132 self.state = AnimationState::Canceled;
133 }
134 }
135
136 fn seek(&mut self, progress: f32) {
137 let progress = normalized(progress);
138 let duration = self.duration();
139 for (index, child) in self.children.iter_mut().enumerate() {
140 #[allow(clippy::cast_possible_truncation)]
141 let child_progress = match (duration, child.duration()) {
142 (Some(total), Some(child_duration)) if !child_duration.is_zero() => {
143 (total.as_secs() * f64::from(progress) / child_duration.as_secs())
144 .clamp(0.0, 1.0) as f32
145 }
146 (Some(_), Some(_)) => 1.0,
147 _ => progress,
148 };
149 child.seek(child_progress);
150 self.outputs[index] = child.value().clone();
151 }
152 self.compose_outputs();
153 self.state = if progress >= 1.0 {
154 AnimationState::Completed
155 } else {
156 AnimationState::Running
157 };
158 }
159
160 fn finish(&mut self) {
161 for (index, child) in self.children.iter_mut().enumerate() {
162 child.finish();
163 self.outputs[index] = child.value().clone();
164 }
165 self.compose_outputs();
166 self.state = AnimationState::Completed;
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::Parallel;
173 use crate::{Animation, AnimationState, Tween, timing::Timing};
174 use float_cmp::assert_approx_eq;
175
176 #[test]
177 fn empty_parallel_remains_idle_when_advanced() {
178 let mut parallel = Parallel::new(2.0_f32, |values| values[0]);
179
180 let overflow = parallel.advance(crate::timing::Duration::from_millis(10.0));
181
182 assert_eq!(parallel.state(), AnimationState::Idle);
183 assert_eq!(overflow, crate::timing::Duration::from_millis(10.0));
184 assert_approx_eq!(f32, *parallel.value(), 2.0);
185 }
186
187 #[test]
188 fn seek_scales_progress_by_child_duration() {
189 let mut parallel = Parallel::new(0.0_f32, |values| values.iter().sum())
190 .with(Tween::between(0.0, 10.0, Timing::new(100.0)))
191 .with(Tween::between(0.0, 20.0, Timing::new(200.0)));
192
193 parallel.seek(0.5);
194
195 assert_eq!(parallel.state(), AnimationState::Running);
196 assert_approx_eq!(f32, *parallel.value(), 20.0);
197 }
198
199 #[test]
200 fn cancel_propagates_to_children() {
201 let mut parallel = Parallel::new(0.0_f32, |values| values.iter().sum())
202 .with(Tween::between(0.0, 10.0, Timing::new(100.0)))
203 .with(Tween::between(0.0, 20.0, Timing::new(200.0)));
204
205 parallel.cancel();
206 parallel.tick(crate::timing::Duration::from_millis(100.0));
207
208 assert_eq!(parallel.state(), AnimationState::Canceled);
209 assert_approx_eq!(f32, *parallel.value(), 0.0);
210 }
211}