Skip to main content

aura_anim_core/timeline/
parallel.rs

1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3type Compositor<T> = Box<dyn Fn(&[T]) -> T>;
4
5/// Runs child animations together and composes their output values.
6pub 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    /// Creates an empty parallel animation with an output compositor.
16    #[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    /// Appends an animation and returns the updated parallel composition.
28    #[must_use]
29    pub fn with(mut self, animation: impl Animation<T>) -> Self {
30        self.push(animation);
31        self
32    }
33
34    /// Appends an animation to the parallel composition.
35    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    /// Returns the number of child animations.
42    #[must_use]
43    pub fn len(&self) -> usize {
44        self.children.len()
45    }
46
47    /// Returns whether the composition contains no child animations.
48    #[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}