Skip to main content

animato_timeline/
sequence.rs

1//! Sequence builder for back-to-back timeline entries.
2
3use crate::timeline::{At, Timeline};
4use alloc::boxed::Box;
5use alloc::string::String;
6use animato_core::Playable;
7
8/// Builder for timeline entries that play one after another.
9#[derive(Debug)]
10pub struct Sequence {
11    inner: Timeline,
12    cursor: f32,
13}
14
15impl Default for Sequence {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl Sequence {
22    /// Create an empty sequence.
23    pub fn new() -> Self {
24        Self {
25            inner: Timeline::new(),
26            cursor: 0.0,
27        }
28    }
29
30    /// Add an animation at the current sequence cursor.
31    pub fn then<A>(mut self, label: impl Into<String>, animation: A) -> Self
32    where
33        A: Playable + Send + 'static,
34    {
35        let duration = animation.duration().max(0.0);
36        self.inner = self.inner.add(label, animation, At::Absolute(self.cursor));
37        self.cursor += duration;
38        self
39    }
40
41    /// Add an animation and advance the cursor by an explicit duration.
42    ///
43    /// This is useful when the child has a longer internal duration but should
44    /// reserve a shorter or longer slot in the sequence.
45    pub fn then_for<A>(mut self, label: impl Into<String>, animation: A, duration: f32) -> Self
46    where
47        A: Playable + Send + 'static,
48    {
49        let duration = duration.max(0.0);
50        self.inner = self.inner.add_boxed_with_duration(
51            label,
52            Box::new(animation),
53            At::Absolute(self.cursor),
54            duration,
55        );
56        self.cursor += duration;
57        self
58    }
59
60    /// Insert a silent gap before the next entry.
61    pub fn gap(mut self, seconds: f32) -> Self {
62        self.cursor += seconds.max(0.0);
63        self
64    }
65
66    /// Consume the builder and return the completed timeline.
67    pub fn build(self) -> Timeline {
68        self.inner
69    }
70
71    /// Current cursor position in seconds.
72    pub fn cursor(&self) -> f32 {
73        self.cursor
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use animato_core::Update;
81    use animato_tween::Tween;
82
83    #[test]
84    fn sequence_places_entries_back_to_back() {
85        let mut timeline = Sequence::new()
86            .then("first", Tween::new(0.0_f32, 10.0).duration(1.0).build())
87            .then("second", Tween::new(0.0_f32, 20.0).duration(1.0).build())
88            .build();
89
90        timeline.play();
91        timeline.update(1.5);
92
93        assert_eq!(timeline.get::<Tween<f32>>("first").unwrap().value(), 10.0);
94        assert_eq!(timeline.get::<Tween<f32>>("second").unwrap().value(), 10.0);
95    }
96
97    #[test]
98    fn gap_delays_next_entry() {
99        let mut timeline = Sequence::new()
100            .then("first", Tween::new(0.0_f32, 10.0).duration(1.0).build())
101            .gap(0.5)
102            .then("second", Tween::new(0.0_f32, 20.0).duration(1.0).build())
103            .build();
104
105        timeline.play();
106        timeline.update(1.25);
107
108        assert_eq!(timeline.get::<Tween<f32>>("first").unwrap().value(), 10.0);
109        assert_eq!(timeline.get::<Tween<f32>>("second").unwrap().value(), 0.0);
110    }
111}