Skip to main content

aura_anim_core/timeline/
sequence.rs

1use crate::{Animatable, Animation, AnimationState, timeline::normalized, timing::Duration};
2
3/// Runs child animations one after another.
4///
5/// # Examples
6///
7/// ```
8/// use aura_anim_core::{
9///     Animation, Sequence, Tween,
10///     timing::{Duration, Timing},
11/// };
12///
13/// let mut sequence = Sequence::new(0.0_f32)
14///     .then(Tween::between(0.0, 1.0, Timing::new(100.0)))
15///     .then(Tween::between(1.0, 2.0, Timing::new(100.0)));
16///
17/// sequence.tick(Duration::from_millis(150.0));
18/// assert_eq!(*sequence.value(), 1.5);
19/// ```
20pub 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    /// Creates an empty sequence with an initial output value.
29    #[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    /// Appends an animation and returns the updated sequence.
40    #[must_use]
41    pub fn then(mut self, animation: impl Animation<T>) -> Self {
42        self.push(animation);
43        self
44    }
45
46    /// Appends an animation to the sequence.
47    pub fn push(&mut self, animation: impl Animation<T>) {
48        self.children.push(Box::new(animation));
49        self.state = AnimationState::Running;
50    }
51
52    /// Returns the number of child animations.
53    #[must_use]
54    pub fn len(&self) -> usize {
55        self.children.len()
56    }
57
58    /// Returns whether the sequence contains no child animations.
59    #[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}