Skip to main content

embedded_gui/
animation_timeline.rs

1use crate::animation::{Animation, AnimationError, AnimationId, AnimationManager};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum TimelineError {
5    Full,
6    Empty,
7    Animation(AnimationError),
8}
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CompositionMode {
12    Sequence,
13    Spawn,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct CompositionControls {
18    pub start_delay_ms: u32,
19    pub repeat_count: Option<u16>,
20    pub reverse: bool,
21}
22
23impl Default for CompositionControls {
24    fn default() -> Self {
25        Self {
26            start_delay_ms: 0,
27            repeat_count: Some(1),
28            reverse: false,
29        }
30    }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq)]
34pub enum TimelineStep {
35    Delay { duration_ms: u32 },
36    Animate { animation: Animation },
37    Label { id: u8 },
38}
39
40#[derive(Clone, Copy, Debug, PartialEq)]
41pub struct Keyframe {
42    pub value: f32,
43    pub duration_ms: u32,
44    pub easing: crate::Easing,
45}
46
47#[derive(Clone, Copy, Debug)]
48pub struct KeyframeTrack<const N: usize> {
49    keyframes: [Option<Keyframe>; N],
50    len: usize,
51    index: usize,
52    current: Option<Animation>,
53    current_from: f32,
54    done: bool,
55    callbacks: KeyframeTrackCallbacks,
56}
57
58impl<const N: usize> KeyframeTrack<N> {
59    pub const fn new() -> Self {
60        Self {
61            keyframes: [None; N],
62            len: 0,
63            index: 0,
64            current: None,
65            current_from: 0.0,
66            done: false,
67            callbacks: KeyframeTrackCallbacks {
68                on_segment_start: None,
69                on_segment_complete: None,
70            },
71        }
72    }
73
74    pub fn push(&mut self, keyframe: Keyframe) -> Result<(), TimelineError> {
75        if self.len >= N {
76            return Err(TimelineError::Full);
77        }
78        self.keyframes[self.len] = Some(keyframe);
79        self.len += 1;
80        Ok(())
81    }
82
83    pub fn reset(&mut self, start: f32) {
84        self.index = 0;
85        self.current = None;
86        self.current_from = start;
87        self.done = false;
88    }
89
90    pub fn set_callbacks(&mut self, callbacks: KeyframeTrackCallbacks) {
91        self.callbacks = callbacks;
92    }
93
94    pub fn tick(&mut self, dt_ms: u32) -> Result<(), TimelineError> {
95        if self.done {
96            return Ok(());
97        }
98        if self.len == 0 {
99            self.done = true;
100            return Err(TimelineError::Empty);
101        }
102
103        if self.current.is_none() {
104            let Some(kf) = self.keyframes[self.index] else {
105                self.done = true;
106                return Ok(());
107            };
108            if let Some(cb) = self.callbacks.on_segment_start {
109                cb(self.index, self.current_from, kf.value);
110            }
111            self.current = Some(Animation::new(
112                self.current_from,
113                kf.value,
114                kf.duration_ms,
115                kf.easing,
116            ));
117        }
118
119        let anim = self.current.as_mut().expect("animation exists");
120        anim.tick(dt_ms);
121        if anim.is_done() {
122            self.current_from = anim.value();
123            if let Some(cb) = self.callbacks.on_segment_complete {
124                cb(self.index, self.current_from);
125            }
126            self.current = None;
127            self.index += 1;
128            if self.index >= self.len {
129                self.done = true;
130            }
131        }
132        Ok(())
133    }
134
135    pub fn value(&self) -> Option<f32> {
136        if let Some(anim) = self.current {
137            Some(anim.value())
138        } else if self.done {
139            Some(self.current_from)
140        } else {
141            None
142        }
143    }
144
145    pub fn is_done(&self) -> bool {
146        self.done
147    }
148}
149
150impl<const N: usize> Default for KeyframeTrack<N> {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156#[derive(Clone, Copy, Debug)]
157pub struct KeyframeTrackCallbacks {
158    pub on_segment_start: Option<fn(usize, f32, f32)>,
159    pub on_segment_complete: Option<fn(usize, f32)>,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
163pub struct SequencePlayerStatus {
164    pub step_idx: usize,
165    pub active: bool,
166    pub done: bool,
167}
168
169impl<const TRACKS: usize, const STEPS: usize> SequencePlayer<TRACKS, STEPS> {
170    pub fn status(&self) -> SequencePlayerStatus {
171        SequencePlayerStatus {
172            step_idx: self.step_idx,
173            active: self.active.is_some(),
174            done: self.done,
175        }
176    }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq)]
180pub struct AnimationSequence<const STEPS: usize> {
181    steps: [Option<TimelineStep>; STEPS],
182    len: usize,
183}
184
185impl<const STEPS: usize> AnimationSequence<STEPS> {
186    pub const fn new() -> Self {
187        Self {
188            steps: [None; STEPS],
189            len: 0,
190        }
191    }
192
193    pub fn push_delay(&mut self, duration_ms: u32) -> Result<(), TimelineError> {
194        self.push_step(TimelineStep::Delay { duration_ms })
195    }
196
197    pub fn push_animation(&mut self, animation: Animation) -> Result<(), TimelineError> {
198        self.push_step(TimelineStep::Animate { animation })
199    }
200
201    pub fn push_step(&mut self, step: TimelineStep) -> Result<(), TimelineError> {
202        if self.len >= STEPS {
203            return Err(TimelineError::Full);
204        }
205        self.steps[self.len] = Some(step);
206        self.len += 1;
207        Ok(())
208    }
209
210    pub fn push_label(&mut self, id: u8) -> Result<(), TimelineError> {
211        self.push_step(TimelineStep::Label { id })
212    }
213
214    pub fn find_label(&self, id: u8) -> Option<usize> {
215        self.steps
216            .iter()
217            .take(self.len)
218            .position(|step| matches!(step, Some(TimelineStep::Label { id: x }) if *x == id))
219    }
220}
221
222impl<const STEPS: usize> Default for AnimationSequence<STEPS> {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228#[derive(Clone, Copy, Debug, PartialEq)]
229pub struct SequencePlayer<const TRACKS: usize, const STEPS: usize> {
230    manager: AnimationManager<TRACKS>,
231    sequence: AnimationSequence<STEPS>,
232    step_idx: usize,
233    delay_elapsed_ms: u32,
234    active: Option<AnimationId>,
235    repeat_mode: SequenceRepeatMode,
236    done: bool,
237}
238
239#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
240pub enum SequenceRepeatMode {
241    #[default]
242    Once,
243    Loop,
244}
245
246impl<const TRACKS: usize, const STEPS: usize> SequencePlayer<TRACKS, STEPS> {
247    pub const fn new(sequence: AnimationSequence<STEPS>) -> Self {
248        Self {
249            manager: AnimationManager::new(),
250            sequence,
251            step_idx: 0,
252            delay_elapsed_ms: 0,
253            active: None,
254            repeat_mode: SequenceRepeatMode::Once,
255            done: false,
256        }
257    }
258
259    pub fn set_repeat_mode(&mut self, mode: SequenceRepeatMode) {
260        self.repeat_mode = mode;
261    }
262
263    pub fn tick(&mut self, dt_ms: u32) -> Result<(), TimelineError> {
264        if self.done {
265            return Ok(());
266        }
267        if self.sequence.len == 0 {
268            self.done = true;
269            return Err(TimelineError::Empty);
270        }
271
272        if let Some(id) = self.active {
273            self.manager.tick(dt_ms);
274            if self.manager.animation(id).is_none() {
275                self.active = None;
276                self.step_idx += 1;
277            }
278            if self.step_idx >= self.sequence.len {
279                if self.repeat_mode == SequenceRepeatMode::Loop {
280                    self.step_idx = 0;
281                    self.done = false;
282                } else {
283                    self.done = true;
284                }
285            }
286            return Ok(());
287        }
288
289        let Some(step) = self.sequence.steps[self.step_idx] else {
290            self.done = true;
291            return Ok(());
292        };
293        match step {
294            TimelineStep::Label { .. } => {
295                self.step_idx += 1;
296            }
297            TimelineStep::Delay { duration_ms } => {
298                self.delay_elapsed_ms = self.delay_elapsed_ms.saturating_add(dt_ms);
299                if self.delay_elapsed_ms >= duration_ms {
300                    self.delay_elapsed_ms = 0;
301                    self.step_idx += 1;
302                }
303            }
304            TimelineStep::Animate { animation } => {
305                let id = self
306                    .manager
307                    .start(animation)
308                    .map_err(TimelineError::Animation)?;
309                self.active = Some(id);
310            }
311        }
312        if self.step_idx >= self.sequence.len {
313            if self.repeat_mode == SequenceRepeatMode::Loop {
314                self.step_idx = 0;
315            } else {
316                self.done = true;
317            }
318        }
319        Ok(())
320    }
321
322    pub fn active_value(&self) -> Option<f32> {
323        self.active.and_then(|id| self.manager.value(id))
324    }
325
326    pub fn is_done(&self) -> bool {
327        self.done
328    }
329
330    pub fn seek_to_label(&mut self, id: u8) -> Result<(), TimelineError> {
331        let Some(idx) = self.sequence.find_label(id) else {
332            return Err(TimelineError::Empty);
333        };
334        self.step_idx = idx.saturating_add(1);
335        self.delay_elapsed_ms = 0;
336        self.active = None;
337        self.done = false;
338        Ok(())
339    }
340}
341
342#[derive(Clone, Copy, Debug, PartialEq)]
343pub struct AnimationGroup<const N: usize> {
344    tracks: [Option<Animation>; N],
345    len: usize,
346}
347
348impl<const N: usize> AnimationGroup<N> {
349    pub const fn new() -> Self {
350        Self {
351            tracks: [None; N],
352            len: 0,
353        }
354    }
355
356    pub fn push(&mut self, animation: Animation) -> Result<(), TimelineError> {
357        if self.len >= N {
358            return Err(TimelineError::Full);
359        }
360        self.tracks[self.len] = Some(animation);
361        self.len += 1;
362        Ok(())
363    }
364
365    pub fn start<const TRACKS: usize>(
366        self,
367        manager: &mut AnimationManager<TRACKS>,
368    ) -> Result<[Option<AnimationId>; N], TimelineError> {
369        let mut ids = [None; N];
370        for (idx, track) in self.tracks.iter().enumerate().take(self.len) {
371            if let Some(anim) = track {
372                ids[idx] = Some(manager.start(*anim).map_err(TimelineError::Animation)?);
373            }
374        }
375        Ok(ids)
376    }
377}
378
379impl<const N: usize> Default for AnimationGroup<N> {
380    fn default() -> Self {
381        Self::new()
382    }
383}
384
385#[derive(Clone, Copy, Debug, PartialEq)]
386pub struct ComposedAnimation<const N: usize> {
387    mode: CompositionMode,
388    tracks: [Option<Animation>; N],
389    len: usize,
390    controls: CompositionControls,
391}
392
393impl<const N: usize> ComposedAnimation<N> {
394    pub const fn new(mode: CompositionMode) -> Self {
395        Self {
396            mode,
397            tracks: [None; N],
398            len: 0,
399            controls: CompositionControls {
400                start_delay_ms: 0,
401                repeat_count: Some(1),
402                reverse: false,
403            },
404        }
405    }
406
407    pub fn push(&mut self, animation: Animation) -> Result<(), TimelineError> {
408        if self.len >= N {
409            return Err(TimelineError::Full);
410        }
411        self.tracks[self.len] = Some(animation);
412        self.len += 1;
413        Ok(())
414    }
415
416    pub fn with_controls(mut self, controls: CompositionControls) -> Self {
417        self.controls = controls;
418        self
419    }
420}
421
422impl<const N: usize> Default for ComposedAnimation<N> {
423    fn default() -> Self {
424        Self::new(CompositionMode::Sequence)
425    }
426}
427
428#[derive(Clone, Copy, Debug, PartialEq, Eq)]
429pub struct ComposedAnimationStatus {
430    pub cycle: u16,
431    pub active: bool,
432    pub done: bool,
433}
434
435#[allow(unpredictable_function_pointer_comparisons)]
436#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
437pub struct ComposedAnimationCallbacks {
438    pub on_cycle_start: Option<fn(u16)>,
439    pub on_cycle_complete: Option<fn(u16)>,
440    pub on_done: Option<fn()>,
441}
442
443#[derive(Clone, Copy, Debug, PartialEq)]
444pub struct ComposedAnimationPlayer<const TRACKS: usize, const N: usize> {
445    manager: AnimationManager<TRACKS>,
446    composition: ComposedAnimation<N>,
447    ids: [Option<AnimationId>; N],
448    delay_elapsed_ms: u32,
449    started: bool,
450    cycles_completed: u16,
451    done: bool,
452    paused: bool,
453    callbacks: ComposedAnimationCallbacks,
454}
455
456impl<const TRACKS: usize, const N: usize> ComposedAnimationPlayer<TRACKS, N> {
457    pub const fn new(composition: ComposedAnimation<N>) -> Self {
458        Self {
459            manager: AnimationManager::new(),
460            composition,
461            ids: [None; N],
462            delay_elapsed_ms: 0,
463            started: false,
464            cycles_completed: 0,
465            done: false,
466            paused: false,
467            callbacks: ComposedAnimationCallbacks {
468                on_cycle_start: None,
469                on_cycle_complete: None,
470                on_done: None,
471            },
472        }
473    }
474
475    pub fn set_callbacks(&mut self, callbacks: ComposedAnimationCallbacks) {
476        self.callbacks = callbacks;
477    }
478
479    pub fn set_paused(&mut self, paused: bool) {
480        self.paused = paused;
481    }
482
483    pub fn restart(&mut self) {
484        self.ids = [None; N];
485        self.delay_elapsed_ms = 0;
486        self.started = false;
487        self.cycles_completed = 0;
488        self.done = false;
489    }
490
491    pub fn stop(&mut self) {
492        for id in self.ids.iter().flatten().copied() {
493            let _ = self.manager.stop(id);
494        }
495        self.ids = [None; N];
496        self.done = true;
497    }
498
499    pub fn seek_active_stepped(&mut self, elapsed_ms: u32, step_ms: u32) -> bool {
500        let mut any = false;
501        for id in self.ids.iter().flatten().copied() {
502            any |= self.manager.seek_stepped(id, elapsed_ms, step_ms);
503        }
504        any
505    }
506
507    pub fn tick(&mut self, dt_ms: u32) -> Result<(), TimelineError> {
508        if self.done {
509            return Ok(());
510        }
511        if self.paused {
512            return Ok(());
513        }
514        if self.composition.len == 0 {
515            self.done = true;
516            return Err(TimelineError::Empty);
517        }
518
519        if !self.started {
520            self.delay_elapsed_ms = self.delay_elapsed_ms.saturating_add(dt_ms);
521            if self.delay_elapsed_ms < self.composition.controls.start_delay_ms {
522                return Ok(());
523            }
524            self.delay_elapsed_ms = 0;
525            self.schedule_cycle()?;
526            self.started = true;
527            if let Some(cb) = self.callbacks.on_cycle_start {
528                cb(self.cycles_completed);
529            }
530            return Ok(());
531        }
532
533        self.manager.tick(dt_ms);
534        let any_active = self
535            .ids
536            .iter()
537            .flatten()
538            .any(|id| self.manager.animation(*id).is_some());
539        if any_active {
540            return Ok(());
541        }
542
543        self.cycles_completed = self.cycles_completed.saturating_add(1);
544        if let Some(cb) = self.callbacks.on_cycle_complete {
545            cb(self.cycles_completed);
546        }
547        if self
548            .composition
549            .controls
550            .repeat_count
551            .is_some_and(|count| self.cycles_completed >= count)
552        {
553            self.done = true;
554            if let Some(cb) = self.callbacks.on_done {
555                cb();
556            }
557            return Ok(());
558        }
559
560        self.ids = [None; N];
561        self.started = false;
562        Ok(())
563    }
564
565    pub fn status(&self) -> ComposedAnimationStatus {
566        ComposedAnimationStatus {
567            cycle: self.cycles_completed,
568            active: self
569                .ids
570                .iter()
571                .flatten()
572                .any(|id| self.manager.animation(*id).is_some()),
573            done: self.done,
574        }
575    }
576
577    fn schedule_cycle(&mut self) -> Result<(), TimelineError> {
578        let mut cumulative_delay = 0u32;
579        for idx in 0..self.composition.len {
580            let src_idx = if self.composition.controls.reverse {
581                self.composition.len - 1 - idx
582            } else {
583                idx
584            };
585            let Some(mut anim) = self.composition.tracks[src_idx] else {
586                continue;
587            };
588            if self.composition.controls.reverse {
589                anim.set_reversed(true);
590            }
591            let delay = match self.composition.mode {
592                CompositionMode::Spawn => 0,
593                CompositionMode::Sequence => cumulative_delay,
594            };
595            anim = anim.with_delay(anim.delay_ms.saturating_add(delay));
596            let id = self.manager.start(anim).map_err(TimelineError::Animation)?;
597            self.ids[idx] = Some(id);
598            if self.composition.mode == CompositionMode::Sequence {
599                let segment = anim
600                    .total_duration_ms(true, false)
601                    .unwrap_or(anim.duration_ms);
602                cumulative_delay = cumulative_delay.saturating_add(segment);
603            }
604        }
605        Ok(())
606    }
607}