1#[cfg(not(feature = "std"))]
4use crate::math::F32Ext as _;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum Easing {
8 #[default]
9 Linear,
10 EaseIn,
11 EaseOut,
12 EaseInOut,
13 Smoothstep,
14 InCubic,
15 OutCubic,
16 InOutCubic,
17 InQuart,
18 OutQuart,
19 InOutQuart,
20 InQuint,
21 OutQuint,
22 InOutQuint,
23 InSine,
24 OutSine,
25 InOutSine,
26 InExpo,
27 OutExpo,
28 InOutExpo,
29 InCirc,
30 OutCirc,
31 InOutCirc,
32 InBack,
33 OutBack,
34 InOutBack,
35 InBounce,
36 OutBounce,
37 InOutBounce,
38 InElastic,
39 OutElastic,
40 InOutElastic,
41 Moook,
43}
44
45#[inline]
46pub fn apply_easing(t: f32, easing: Easing) -> f32 {
47 let t = t.clamp(0.0, 1.0);
48 const PI: f32 = core::f32::consts::PI;
49 #[inline]
50 fn out_bounce(t: f32) -> f32 {
51 const N1: f32 = 7.5625;
52 const D1: f32 = 2.75;
53 if t < 1.0 / D1 {
54 N1 * t * t
55 } else if t < 2.0 / D1 {
56 let t = t - 1.5 / D1;
57 N1 * t * t + 0.75
58 } else if t < 2.5 / D1 {
59 let t = t - 2.25 / D1;
60 N1 * t * t + 0.9375
61 } else {
62 let t = t - 2.625 / D1;
63 N1 * t * t + 0.984375
64 }
65 }
66 match easing {
67 Easing::Linear => t,
68 Easing::EaseIn => t * t,
69 Easing::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
70 Easing::EaseInOut => {
71 if t < 0.5 {
72 2.0 * t * t
73 } else {
74 1.0 - (-2.0 * t + 2.0).powi(2) * 0.5
75 }
76 }
77 Easing::Smoothstep => t * t * (3.0 - 2.0 * t),
78 Easing::InCubic => t.powi(3),
79 Easing::OutCubic => 1.0 - (1.0 - t).powi(3),
80 Easing::InOutCubic => {
81 if t < 0.5 {
82 4.0 * t.powi(3)
83 } else {
84 1.0 - (-2.0 * t + 2.0).powi(3) * 0.5
85 }
86 }
87 Easing::InQuart => t.powi(4),
88 Easing::OutQuart => 1.0 - (1.0 - t).powi(4),
89 Easing::InOutQuart => {
90 if t < 0.5 {
91 8.0 * t.powi(4)
92 } else {
93 1.0 - (-2.0 * t + 2.0).powi(4) * 0.5
94 }
95 }
96 Easing::InQuint => t.powi(5),
97 Easing::OutQuint => 1.0 - (1.0 - t).powi(5),
98 Easing::InOutQuint => {
99 if t < 0.5 {
100 16.0 * t.powi(5)
101 } else {
102 1.0 - (-2.0 * t + 2.0).powi(5) * 0.5
103 }
104 }
105 Easing::InSine => 1.0 - ((t * PI) / 2.0).cos(),
106 Easing::OutSine => ((t * PI) / 2.0).sin(),
107 Easing::InOutSine => -(PI * t).cos() * 0.5 + 0.5,
108 Easing::InExpo => {
109 if t <= 0.0 {
110 0.0
111 } else {
112 (2.0_f32).powf(10.0 * t - 10.0)
113 }
114 }
115 Easing::OutExpo => {
116 if t >= 1.0 {
117 1.0
118 } else {
119 1.0 - (2.0_f32).powf(-10.0 * t)
120 }
121 }
122 Easing::InOutExpo => {
123 if t <= 0.0 {
124 0.0
125 } else if t >= 1.0 {
126 1.0
127 } else if t < 0.5 {
128 (2.0_f32).powf(20.0 * t - 10.0) * 0.5
129 } else {
130 (2.0 - (2.0_f32).powf(-20.0 * t + 10.0)) * 0.5
131 }
132 }
133 Easing::InCirc => 1.0 - (1.0 - t * t).sqrt(),
134 Easing::OutCirc => (1.0 - (t - 1.0).powi(2)).sqrt(),
135 Easing::InOutCirc => {
136 if t < 0.5 {
137 (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) * 0.5
138 } else {
139 ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) * 0.5
140 }
141 }
142 Easing::InBack => {
143 let c1 = 1.70158;
144 let c3 = c1 + 1.0;
145 c3 * t.powi(3) - c1 * t.powi(2)
146 }
147 Easing::OutBack => {
148 let c1 = 1.70158;
149 let c3 = c1 + 1.0;
150 1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
151 }
152 Easing::InOutBack => {
153 let c1 = 1.70158;
154 let c2 = c1 * 1.525;
155 if t < 0.5 {
156 ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) * 0.5
157 } else {
158 ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) * 0.5
159 }
160 }
161 Easing::InBounce => 1.0 - out_bounce(1.0 - t),
162 Easing::OutBounce => out_bounce(t),
163 Easing::InOutBounce => {
164 if t < 0.5 {
165 (1.0 - out_bounce(1.0 - 2.0 * t)) * 0.5
166 } else {
167 (1.0 + out_bounce(2.0 * t - 1.0)) * 0.5
168 }
169 }
170 Easing::InElastic => {
171 if t <= 0.0 {
172 0.0
173 } else if t >= 1.0 {
174 1.0
175 } else {
176 let c4 = (2.0 * PI) / 3.0;
177 -(2.0_f32).powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * c4).sin()
178 }
179 }
180 Easing::OutElastic => {
181 if t <= 0.0 {
182 0.0
183 } else if t >= 1.0 {
184 1.0
185 } else {
186 let c4 = (2.0 * PI) / 3.0;
187 (2.0_f32).powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
188 }
189 }
190 Easing::InOutElastic => {
191 if t <= 0.0 {
192 0.0
193 } else if t >= 1.0 {
194 1.0
195 } else {
196 let c5 = (2.0 * PI) / 4.5;
197 if t < 0.5 {
198 -(2.0_f32).powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin() * 0.5
199 } else {
200 (2.0_f32).powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin() * 0.5 + 1.0
201 }
202 }
203 }
204 Easing::Moook => crate::animation_timing::moook_curve(t),
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct AnimationId(u16);
210
211impl AnimationId {
212 pub const fn new(id: u16) -> Self {
213 Self(id)
214 }
215
216 pub const fn raw(self) -> u16 {
217 self.0
218 }
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
222pub enum AnimationError {
223 Full,
224}
225
226#[derive(Clone, Copy, Debug, PartialEq)]
227pub struct Timer {
228 pub duration_ms: u32,
229 pub elapsed_ms: u32,
230 pub repeating: bool,
231}
232
233impl Timer {
234 pub const fn new(duration_ms: u32) -> Self {
235 Self {
236 duration_ms,
237 elapsed_ms: 0,
238 repeating: false,
239 }
240 }
241
242 pub const fn repeating(duration_ms: u32) -> Self {
243 Self {
244 duration_ms,
245 elapsed_ms: 0,
246 repeating: true,
247 }
248 }
249
250 pub fn reset(&mut self) {
251 self.elapsed_ms = 0;
252 }
253
254 pub fn tick(&mut self, dt_ms: u32) -> bool {
255 self.elapsed_ms = self.elapsed_ms.saturating_add(dt_ms);
256 if self.elapsed_ms >= self.duration_ms {
257 if self.repeating && self.duration_ms > 0 {
258 self.elapsed_ms %= self.duration_ms;
259 }
260 true
261 } else {
262 false
263 }
264 }
265
266 pub fn progress(&self) -> f32 {
267 if self.duration_ms == 0 {
268 return 1.0;
269 }
270 (self.elapsed_ms as f32 / self.duration_ms as f32).min(1.0)
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq)]
275pub struct Tween {
276 pub from: f32,
277 pub to: f32,
278 pub timer: Timer,
279 pub easing: Easing,
280}
281
282impl Tween {
283 pub const fn new(from: f32, to: f32, duration_ms: u32, easing: Easing) -> Self {
284 Self {
285 from,
286 to,
287 timer: Timer::new(duration_ms),
288 easing,
289 }
290 }
291
292 pub fn reset(&mut self) {
293 self.timer.reset();
294 }
295
296 pub fn tick(&mut self, dt_ms: u32) -> bool {
297 self.timer.tick(dt_ms)
298 }
299
300 pub fn value(&self) -> f32 {
301 let t = apply_easing(self.timer.progress(), self.easing);
302 self.from + (self.to - self.from) * t
303 }
304
305 pub fn is_done(&self) -> bool {
306 self.timer.progress() >= 1.0
307 }
308}
309
310#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
311pub enum RepeatMode {
312 #[default]
313 Once,
314 Loop,
315 PingPong,
316}
317
318#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
319pub enum AnimationState {
320 #[default]
321 Running,
322 Finished,
323}
324
325#[allow(unpredictable_function_pointer_comparisons)]
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
327pub struct AnimationHandlers {
328 pub on_started: Option<fn()>,
329 pub on_stopped: Option<fn(bool)>,
330}
331
332#[allow(unpredictable_function_pointer_comparisons)]
333#[derive(Clone, Copy, Debug, PartialEq)]
334pub struct Animation {
335 pub from: f32,
336 pub to: f32,
337 pub duration_ms: u32,
338 pub easing: Easing,
339 pub custom_curve: Option<fn(f32) -> f32>,
340 pub custom_interpolator: Option<fn(f32, f32, f32) -> f32>,
341 pub handlers: AnimationHandlers,
342 pub delay_ms: u32,
343 pub repeat_mode: RepeatMode,
344 pub repeat_count: Option<u16>,
345 elapsed_ms: u32,
346 iteration: u16,
347 reversed: bool,
348 started: bool,
349 finished: bool,
350}
351
352impl Animation {
353 pub const fn new(from: f32, to: f32, duration_ms: u32, easing: Easing) -> Self {
354 Self {
355 from,
356 to,
357 duration_ms,
358 easing,
359 custom_curve: None,
360 custom_interpolator: None,
361 handlers: AnimationHandlers {
362 on_started: None,
363 on_stopped: None,
364 },
365 delay_ms: 0,
366 repeat_mode: RepeatMode::Once,
367 repeat_count: None,
368 elapsed_ms: 0,
369 iteration: 0,
370 reversed: false,
371 started: false,
372 finished: false,
373 }
374 }
375
376 pub const fn with_delay(mut self, delay_ms: u32) -> Self {
377 self.delay_ms = delay_ms;
378 self
379 }
380
381 pub const fn with_repeat_mode(mut self, repeat_mode: RepeatMode) -> Self {
382 self.repeat_mode = repeat_mode;
383 self
384 }
385
386 pub const fn with_repeat_count(mut self, repeat_count: Option<u16>) -> Self {
387 self.repeat_count = repeat_count;
388 self
389 }
390
391 pub fn with_custom_curve(mut self, curve: fn(f32) -> f32) -> Self {
392 self.custom_curve = Some(curve);
393 self
394 }
395
396 pub fn clear_custom_curve(&mut self) {
397 self.custom_curve = None;
398 }
399
400 pub fn with_custom_interpolator(mut self, interpolator: fn(f32, f32, f32) -> f32) -> Self {
401 self.custom_interpolator = Some(interpolator);
402 self
403 }
404
405 pub fn clear_custom_interpolator(&mut self) {
406 self.custom_interpolator = None;
407 }
408
409 pub fn set_reversed(&mut self, reversed: bool) {
410 self.reversed = reversed;
411 }
412
413 pub fn set_handlers(&mut self, handlers: AnimationHandlers) {
414 self.handlers = handlers;
415 }
416
417 pub fn reset(&mut self) {
418 self.elapsed_ms = 0;
419 self.iteration = 0;
420 self.started = false;
421 self.finished = false;
422 }
423
424 pub fn set_elapsed(&mut self, elapsed_ms: u32) {
425 self.elapsed_ms = elapsed_ms;
426 self.finished = self.resolve_finished();
427 }
428
429 pub fn tick(&mut self, dt_ms: u32) -> AnimationState {
430 if self.finished {
431 return AnimationState::Finished;
432 }
433 self.elapsed_ms = self.elapsed_ms.saturating_add(dt_ms);
434 self.emit_started_if_ready();
435 self.finished = self.resolve_finished();
436 if self.finished {
437 if let Some(cb) = self.handlers.on_stopped {
438 cb(true);
439 }
440 }
441 if self.finished {
442 AnimationState::Finished
443 } else {
444 AnimationState::Running
445 }
446 }
447
448 pub fn value(&self) -> f32 {
449 if self.delay_ms > 0 && self.elapsed_ms < self.delay_ms {
450 return if self.reversed { self.to } else { self.from };
451 }
452
453 let duration = self.duration_ms.max(1);
454 let active_elapsed = self.elapsed_ms.saturating_sub(self.delay_ms);
455 let local_time = active_elapsed % duration;
456 let mut progress = local_time as f32 / duration as f32;
457
458 if self.finished && self.repeat_mode == RepeatMode::Once {
459 progress = 1.0;
460 }
461
462 let iteration = self.current_iteration();
463 let ping_pong_reverse = self.repeat_mode == RepeatMode::PingPong && (iteration % 2 == 1);
464 if ping_pong_reverse {
465 progress = 1.0 - progress;
466 }
467 if self.reversed {
468 progress = 1.0 - progress;
469 }
470
471 let t = if let Some(curve) = self.custom_curve {
472 curve(progress)
473 } else {
474 apply_easing(progress, self.easing)
475 };
476 if let Some(interpolator) = self.custom_interpolator {
477 interpolator(self.from, self.to, t)
478 } else {
479 self.from + (self.to - self.from) * t
480 }
481 }
482
483 pub fn is_done(&self) -> bool {
484 self.finished
485 }
486
487 pub fn elapsed_ms(&self) -> u32 {
488 self.elapsed_ms
489 }
490
491 pub fn iteration(&self) -> u16 {
492 self.current_iteration()
493 }
494
495 fn current_iteration(&self) -> u16 {
496 if self.delay_ms > 0 && self.elapsed_ms < self.delay_ms {
497 return 0;
498 }
499 let duration = self.duration_ms.max(1);
500 let active_elapsed = self.elapsed_ms.saturating_sub(self.delay_ms);
501 (active_elapsed / duration) as u16
502 }
503
504 fn resolve_finished(&mut self) -> bool {
505 if self.repeat_mode != RepeatMode::Once {
506 if let Some(limit) = self.repeat_count {
507 let iteration = self.current_iteration();
508 self.iteration = iteration;
509 return iteration >= limit;
510 }
511 return false;
512 }
513
514 let total = self.delay_ms.saturating_add(self.duration_ms);
515 self.elapsed_ms >= total
516 }
517
518 pub(crate) fn notify_stopped(&mut self, finished: bool) {
519 if let Some(cb) = self.handlers.on_stopped {
520 cb(finished);
521 }
522 }
523
524 fn emit_started_if_ready(&mut self) {
525 if self.started {
526 return;
527 }
528 if self.delay_ms > 0 && self.elapsed_ms < self.delay_ms {
529 return;
530 }
531 self.started = true;
532 if let Some(cb) = self.handlers.on_started {
533 cb();
534 }
535 }
536
537 pub fn duration_from_speed(delta: f32, units_per_second: f32) -> u32 {
538 if delta <= 0.0 || units_per_second <= 0.0 {
539 return 0;
540 }
541 ((delta / units_per_second) * 1000.0).ceil() as u32
542 }
543
544 pub fn total_duration_ms(
545 &self,
546 include_delay: bool,
547 include_repeat_count: bool,
548 ) -> Option<u32> {
549 let base = if include_delay {
550 self.duration_ms.saturating_add(self.delay_ms)
551 } else {
552 self.duration_ms
553 };
554 if !include_repeat_count || self.repeat_mode == RepeatMode::Once {
555 return Some(base);
556 }
557 self.repeat_count
558 .map(|count| base.saturating_mul(count as u32))
559 }
560}
561
562#[derive(Clone, Copy, Debug, PartialEq)]
563struct AnimationTrack {
564 id: AnimationId,
565 animation: Animation,
566 last_iteration: u16,
567}
568
569#[allow(unpredictable_function_pointer_comparisons)]
570#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
571pub struct AnimationManagerCallbacks {
572 pub on_start: Option<fn(AnimationId)>,
573 pub on_repeat: Option<fn(AnimationId, u16)>,
574 pub on_complete: Option<fn(AnimationId, bool)>,
575}
576
577#[derive(Clone, Copy, Debug, PartialEq)]
578pub struct AnimationManager<const N: usize> {
579 tracks: [Option<AnimationTrack>; N],
580 next_id: u16,
581 paused: bool,
582 callbacks: AnimationManagerCallbacks,
583}
584
585impl<const N: usize> Default for AnimationManager<N> {
586 fn default() -> Self {
587 Self::new()
588 }
589}
590
591impl<const N: usize> AnimationManager<N> {
592 pub const fn new() -> Self {
593 Self {
594 tracks: [None; N],
595 next_id: 1,
596 paused: false,
597 callbacks: AnimationManagerCallbacks {
598 on_start: None,
599 on_repeat: None,
600 on_complete: None,
601 },
602 }
603 }
604
605 pub fn set_callbacks(&mut self, callbacks: AnimationManagerCallbacks) {
606 self.callbacks = callbacks;
607 }
608
609 pub fn start(&mut self, animation: Animation) -> Result<AnimationId, AnimationError> {
610 let id = AnimationId::new(self.next_id);
611 self.next_id = self.next_id.wrapping_add(1).max(1);
612
613 if let Some(slot) = self.tracks.iter_mut().find(|slot| slot.is_none()) {
614 *slot = Some(AnimationTrack {
615 id,
616 animation,
617 last_iteration: 0,
618 });
619 if let Some(cb) = self.callbacks.on_start {
620 cb(id);
621 }
622 Ok(id)
623 } else {
624 Err(AnimationError::Full)
625 }
626 }
627
628 pub fn stop(&mut self, id: AnimationId) -> bool {
629 for slot in &mut self.tracks {
630 if slot.as_ref().is_some_and(|track| track.id == id) {
631 if let Some(track) = slot.as_mut() {
632 track.animation.notify_stopped(false);
633 }
634 *slot = None;
635 if let Some(cb) = self.callbacks.on_complete {
636 cb(id, false);
637 }
638 return true;
639 }
640 }
641 false
642 }
643
644 pub fn tick(&mut self, dt_ms: u32) {
645 if self.paused {
646 return;
647 }
648 for slot in &mut self.tracks {
649 if let Some(track) = slot.as_mut() {
650 track.animation.tick(dt_ms);
651 let iteration = track.animation.iteration();
652 if iteration > track.last_iteration {
653 track.last_iteration = iteration;
654 if let Some(cb) = self.callbacks.on_repeat {
655 cb(track.id, iteration);
656 }
657 }
658 if track.animation.is_done() {
659 if let Some(cb) = self.callbacks.on_complete {
660 cb(track.id, true);
661 }
662 *slot = None;
663 }
664 }
665 }
666 }
667
668 pub fn value(&self, id: AnimationId) -> Option<f32> {
669 self.tracks
670 .iter()
671 .flatten()
672 .find(|track| track.id == id)
673 .map(|track| track.animation.value())
674 }
675
676 pub fn animation(&self, id: AnimationId) -> Option<&Animation> {
677 self.tracks
678 .iter()
679 .flatten()
680 .find(|track| track.id == id)
681 .map(|track| &track.animation)
682 }
683
684 pub fn animation_mut(&mut self, id: AnimationId) -> Option<&mut Animation> {
685 self.tracks
686 .iter_mut()
687 .flatten()
688 .find(|track| track.id == id)
689 .map(|track| &mut track.animation)
690 }
691
692 pub fn active_count(&self) -> usize {
693 self.tracks.iter().flatten().count()
694 }
695
696 pub fn set_paused(&mut self, paused: bool) {
697 self.paused = paused;
698 }
699
700 pub fn is_paused(&self) -> bool {
701 self.paused
702 }
703
704 pub fn seek(&mut self, id: AnimationId, elapsed_ms: u32) -> bool {
705 if let Some(track) = self
706 .tracks
707 .iter_mut()
708 .flatten()
709 .find(|track| track.id == id)
710 {
711 track.animation.set_elapsed(elapsed_ms);
712 track.last_iteration = track.animation.iteration();
713 true
714 } else {
715 false
716 }
717 }
718
719 pub fn seek_stepped(&mut self, id: AnimationId, elapsed_ms: u32, step_ms: u32) -> bool {
720 let Some(track) = self
721 .tracks
722 .iter_mut()
723 .flatten()
724 .find(|track| track.id == id)
725 else {
726 return false;
727 };
728 let step = step_ms.max(1);
729 let current = track.animation.elapsed_ms();
730 if elapsed_ms <= current {
731 track.animation.set_elapsed(elapsed_ms);
732 track.last_iteration = track.animation.iteration();
733 return true;
734 }
735 let mut cursor = current;
736 while cursor < elapsed_ms {
737 cursor = core::cmp::min(cursor.saturating_add(step), elapsed_ms);
738 track.animation.set_elapsed(cursor);
739 }
740 track.last_iteration = track.animation.iteration();
741 true
742 }
743
744 pub fn replay_stepped<F>(
745 &mut self,
746 id: AnimationId,
747 elapsed_ms: u32,
748 step_ms: u32,
749 mut on_sample: F,
750 ) -> bool
751 where
752 F: FnMut(f32),
753 {
754 let Some(track) = self
755 .tracks
756 .iter_mut()
757 .flatten()
758 .find(|track| track.id == id)
759 else {
760 return false;
761 };
762 let step = step_ms.max(1);
763 let current = track.animation.elapsed_ms();
764 if elapsed_ms <= current {
765 track.animation.set_elapsed(elapsed_ms);
766 on_sample(track.animation.value());
767 track.last_iteration = track.animation.iteration();
768 return true;
769 }
770 let mut cursor = current;
771 while cursor < elapsed_ms {
772 cursor = core::cmp::min(cursor.saturating_add(step), elapsed_ms);
773 track.animation.set_elapsed(cursor);
774 on_sample(track.animation.value());
775 }
776 track.last_iteration = track.animation.iteration();
777 true
778 }
779
780 pub fn set_next_id_for_test(&mut self, id: u16) {
781 self.next_id = id.max(1);
782 }
783}
784
785#[derive(Clone, Copy, Debug, PartialEq)]
786pub struct SpringAnimator {
787 pub value: f32,
788 pub velocity: f32,
789 pub target: f32,
790 pub stiffness: f32,
791 pub damping: f32,
792}
793
794impl SpringAnimator {
795 pub const fn new(value: f32, target: f32) -> Self {
796 Self {
797 value,
798 velocity: 0.0,
799 target,
800 stiffness: 120.0,
801 damping: 16.0,
802 }
803 }
804
805 pub fn tick(&mut self, dt_ms: u32) -> f32 {
806 let dt = (dt_ms as f32 / 1000.0).max(0.001);
807 let force = self.stiffness * (self.target - self.value) - self.damping * self.velocity;
808 self.velocity += force * dt;
809 self.value += self.velocity * dt;
810 self.value
811 }
812}
813
814#[derive(Clone, Copy, Debug, PartialEq)]
815pub struct InertiaAnimator {
816 pub value: f32,
817 pub velocity: f32,
818 pub friction_per_second: f32,
819}
820
821impl InertiaAnimator {
822 pub const fn new(value: f32, velocity: f32) -> Self {
823 Self {
824 value,
825 velocity,
826 friction_per_second: 0.88,
827 }
828 }
829
830 pub fn tick(&mut self, dt_ms: u32) -> f32 {
831 let dt = (dt_ms as f32 / 1000.0).max(0.001);
832 self.value += self.velocity * dt;
833 self.velocity *= self.friction_per_second.powf(dt);
834 self.value
835 }
836}
837
838#[derive(Clone, Copy, Debug, PartialEq)]
839pub struct PathPoint {
840 pub x: f32,
841 pub y: f32,
842}
843
844impl PathPoint {
845 pub const fn new(x: f32, y: f32) -> Self {
846 Self { x, y }
847 }
848}
849
850#[derive(Clone, Copy, Debug, PartialEq)]
851pub struct PathAnimator<const N: usize> {
852 points: [Option<PathPoint>; N],
853 len: usize,
854 pub timer: Timer,
855 pub easing: Easing,
856}
857
858impl<const N: usize> PathAnimator<N> {
859 pub const fn new(duration_ms: u32, easing: Easing) -> Self {
860 Self {
861 points: [None; N],
862 len: 0,
863 timer: Timer::new(duration_ms),
864 easing,
865 }
866 }
867
868 pub fn push_point(&mut self, point: PathPoint) -> Result<(), AnimationError> {
869 if self.len >= N {
870 return Err(AnimationError::Full);
871 }
872 self.points[self.len] = Some(point);
873 self.len += 1;
874 Ok(())
875 }
876
877 pub fn reset(&mut self) {
878 self.timer.reset();
879 }
880
881 pub fn tick(&mut self, dt_ms: u32) -> bool {
882 self.timer.tick(dt_ms)
883 }
884
885 pub fn value(&self) -> Option<PathPoint> {
886 if self.len == 0 {
887 return None;
888 }
889 if self.len == 1 {
890 return self.points[0];
891 }
892 let t = apply_easing(self.timer.progress(), self.easing);
893 let segs = (self.len - 1) as f32;
894 let pos = (t * segs).clamp(0.0, segs);
895 let idx = pos.floor() as usize;
896 let local = pos - idx as f32;
897 let a = self.points[idx]?;
898 let b = self.points[(idx + 1).min(self.len - 1)]?;
899 Some(PathPoint {
900 x: a.x + (b.x - a.x) * local,
901 y: a.y + (b.y - a.y) * local,
902 })
903 }
904}