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}