Skip to main content

animato_timeline/
group.rs

1//! Animation groups built on top of [`Timeline`].
2
3use crate::{At, Sequence, Timeline};
4use alloc::format;
5use alloc::vec::Vec;
6use animato_core::{
7    AnimationIntrospection, AnimationKind, Inspectable, Playable, PlaybackState, Update,
8};
9use animato_tween::StaggerPattern;
10use core::fmt;
11
12/// A group of animations controlled as a single playable unit.
13pub struct AnimationGroup {
14    inner: Timeline,
15}
16
17impl fmt::Debug for AnimationGroup {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        f.debug_struct("AnimationGroup")
20            .field("inner", &self.inner)
21            .finish()
22    }
23}
24
25impl AnimationGroup {
26    /// Create a group from an existing timeline.
27    pub fn from_timeline(timeline: Timeline) -> Self {
28        Self { inner: timeline }
29    }
30
31    /// Play all animations at the same time.
32    pub fn parallel<A>(animations: Vec<A>) -> Self
33    where
34        A: Playable + Send + 'static,
35    {
36        let mut timeline = Timeline::new();
37        for (index, animation) in animations.into_iter().enumerate() {
38            timeline = timeline.add(format!("item_{index}"), animation, At::Start);
39        }
40        Self::from_timeline(timeline)
41    }
42
43    /// Play animations one after another.
44    pub fn sequence<A>(animations: Vec<A>) -> Self
45    where
46        A: Playable + Send + 'static,
47    {
48        let mut sequence = Sequence::new();
49        for (index, animation) in animations.into_iter().enumerate() {
50            sequence = sequence.then(format!("item_{index}"), animation);
51        }
52        Self::from_timeline(sequence.build())
53    }
54
55    /// Play animations with start delays from a [`StaggerPattern`].
56    pub fn stagger<A>(animations: Vec<A>, pattern: StaggerPattern) -> Self
57    where
58        A: Playable + Send + 'static,
59    {
60        let total = animations.len();
61        let mut timeline = Timeline::new();
62        for (index, animation) in animations.into_iter().enumerate() {
63            timeline = timeline.add(
64                format!("item_{index}"),
65                animation,
66                At::Absolute(pattern.delay(index, total)),
67            );
68        }
69        Self::from_timeline(timeline)
70    }
71
72    /// Access the wrapped timeline.
73    pub fn timeline(&self) -> &Timeline {
74        &self.inner
75    }
76
77    /// Access the wrapped timeline mutably.
78    pub fn timeline_mut(&mut self) -> &mut Timeline {
79        &mut self.inner
80    }
81
82    /// Begin group playback.
83    pub fn play(&mut self) {
84        self.inner.play();
85    }
86
87    /// Pause group playback.
88    pub fn pause(&mut self) {
89        self.inner.pause();
90    }
91
92    /// Resume group playback.
93    pub fn resume(&mut self) {
94        self.inner.resume();
95    }
96
97    /// Reset group playback.
98    pub fn reset(&mut self) {
99        self.inner.reset();
100    }
101
102    /// Seek by normalized progress.
103    pub fn seek(&mut self, progress: f32) {
104        self.inner.seek(progress);
105    }
106
107    /// Seek to the mirrored progress position.
108    pub fn reverse(&mut self) {
109        let progress = self.inner.progress();
110        self.inner.seek(1.0 - progress);
111    }
112
113    /// Set time scale for the entire group.
114    pub fn set_time_scale(&mut self, scale: f32) {
115        self.inner.set_time_scale(scale);
116    }
117
118    /// Group duration in seconds.
119    pub fn duration(&self) -> f32 {
120        self.inner.duration()
121    }
122
123    /// Normalized progress.
124    pub fn progress(&self) -> f32 {
125        self.inner.progress()
126    }
127
128    /// `true` when the group has completed.
129    pub fn is_complete(&self) -> bool {
130        self.inner.is_complete()
131    }
132
133    /// Register a callback fired once when the group completes.
134    #[cfg(feature = "std")]
135    pub fn on_complete(mut self, f: impl FnMut() + Send + 'static) -> Self {
136        self.inner = self.inner.on_complete(f);
137        self
138    }
139}
140
141impl Update for AnimationGroup {
142    fn update(&mut self, dt: f32) -> bool {
143        self.inner.update(dt)
144    }
145}
146
147impl Playable for AnimationGroup {
148    fn duration(&self) -> f32 {
149        self.inner.duration()
150    }
151
152    fn reset(&mut self) {
153        AnimationGroup::reset(self);
154    }
155
156    fn seek_to(&mut self, progress: f32) {
157        self.seek(progress);
158    }
159
160    fn is_complete(&self) -> bool {
161        AnimationGroup::is_complete(self)
162    }
163
164    fn as_any(&self) -> &dyn core::any::Any {
165        self
166    }
167
168    fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
169        self
170    }
171}
172
173impl Inspectable for AnimationGroup {
174    fn introspect(&self) -> AnimationIntrospection {
175        AnimationIntrospection::new(
176            AnimationKind::Group,
177            self.progress(),
178            self.inner.elapsed(),
179            Some(self.duration()),
180            match self.inner.state() {
181                crate::TimelineState::Idle => PlaybackState::Idle,
182                crate::TimelineState::Playing => PlaybackState::Playing,
183                crate::TimelineState::Paused => PlaybackState::Paused,
184                crate::TimelineState::Completed => PlaybackState::Complete,
185            },
186            None,
187        )
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use animato_tween::Tween;
195
196    #[test]
197    fn parallel_completes_when_last_child_finishes() {
198        let mut group = AnimationGroup::parallel(alloc::vec![
199            Tween::new(0.0_f32, 1.0).duration(0.5).build(),
200            Tween::new(0.0_f32, 1.0).duration(1.0).build(),
201        ]);
202        group.play();
203        assert!(group.update(0.75));
204        assert!(!group.update(0.25));
205    }
206
207    #[test]
208    fn sequence_orders_children() {
209        let mut group = AnimationGroup::sequence(alloc::vec![
210            Tween::new(0.0_f32, 10.0).duration(1.0).build(),
211            Tween::new(0.0_f32, 20.0).duration(1.0).build(),
212        ]);
213        group.play();
214        group.update(1.5);
215        assert_eq!(
216            group
217                .timeline()
218                .get::<Tween<f32>>("item_0")
219                .expect("first")
220                .value(),
221            10.0
222        );
223        assert_eq!(
224            group
225                .timeline()
226                .get::<Tween<f32>>("item_1")
227                .expect("second")
228                .value(),
229            10.0
230        );
231    }
232}