1#![forbid(unsafe_code)]
2
3use std::time::Duration;
24
25pub type EasingFn = fn(f32) -> f32;
31
32#[inline]
34pub fn linear(t: f32) -> f32 {
35 t.clamp(0.0, 1.0)
36}
37
38#[inline]
40pub fn ease_in(t: f32) -> f32 {
41 let t = t.clamp(0.0, 1.0);
42 t * t
43}
44
45#[inline]
47pub fn ease_out(t: f32) -> f32 {
48 let t = t.clamp(0.0, 1.0);
49 1.0 - (1.0 - t) * (1.0 - t)
50}
51
52#[inline]
54pub fn ease_in_out(t: f32) -> f32 {
55 let t = t.clamp(0.0, 1.0);
56 if t < 0.5 {
57 2.0 * t * t
58 } else {
59 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
60 }
61}
62
63#[inline]
65pub fn ease_in_cubic(t: f32) -> f32 {
66 let t = t.clamp(0.0, 1.0);
67 t * t * t
68}
69
70#[inline]
72pub fn ease_out_cubic(t: f32) -> f32 {
73 let t = t.clamp(0.0, 1.0);
74 1.0 - (1.0 - t).powi(3)
75}
76
77pub trait Animation {
83 fn tick(&mut self, dt: Duration);
85
86 fn is_complete(&self) -> bool;
88
89 fn value(&self) -> f32;
91
92 fn reset(&mut self);
94
95 fn overshoot(&self) -> Duration {
99 Duration::ZERO
100 }
101}
102
103#[derive(Debug, Clone, Copy)]
112pub struct Fade {
113 elapsed: Duration,
114 duration: Duration,
115 easing: EasingFn,
116}
117
118impl Fade {
119 pub fn new(duration: Duration) -> Self {
121 Self {
122 elapsed: Duration::ZERO,
123 duration: if duration.is_zero() {
124 Duration::from_nanos(1)
125 } else {
126 duration
127 },
128 easing: linear,
129 }
130 }
131
132 pub fn easing(mut self, easing: EasingFn) -> Self {
134 self.easing = easing;
135 self
136 }
137
138 pub fn raw_progress(&self) -> f32 {
140 let t = self.elapsed.as_secs_f64() / self.duration.as_secs_f64();
141 (t as f32).clamp(0.0, 1.0)
142 }
143}
144
145impl Animation for Fade {
146 fn tick(&mut self, dt: Duration) {
147 self.elapsed = self.elapsed.saturating_add(dt);
148 }
149
150 fn is_complete(&self) -> bool {
151 self.elapsed >= self.duration
152 }
153
154 fn value(&self) -> f32 {
155 (self.easing)(self.raw_progress())
156 }
157
158 fn reset(&mut self) {
159 self.elapsed = Duration::ZERO;
160 }
161
162 fn overshoot(&self) -> Duration {
163 self.elapsed.saturating_sub(self.duration)
164 }
165}
166
167#[derive(Debug, Clone, Copy)]
176pub struct Slide {
177 from: i16,
178 to: i16,
179 elapsed: Duration,
180 duration: Duration,
181 easing: EasingFn,
182}
183
184impl Slide {
185 pub fn new(from: i16, to: i16, duration: Duration) -> Self {
187 Self {
188 from,
189 to,
190 elapsed: Duration::ZERO,
191 duration: if duration.is_zero() {
192 Duration::from_nanos(1)
193 } else {
194 duration
195 },
196 easing: ease_out,
197 }
198 }
199
200 pub fn easing(mut self, easing: EasingFn) -> Self {
202 self.easing = easing;
203 self
204 }
205
206 fn progress(&self) -> f32 {
207 let t = self.elapsed.as_secs_f64() / self.duration.as_secs_f64();
208 (t as f32).clamp(0.0, 1.0)
209 }
210
211 pub fn position(&self) -> i16 {
213 let t = (self.easing)(self.progress());
214 let range = f32::from(self.to) - f32::from(self.from);
215 let pos = f32::from(self.from) + range * t;
216 pos.round().clamp(f32::from(i16::MIN), f32::from(i16::MAX)) as i16
217 }
218}
219
220impl Animation for Slide {
221 fn tick(&mut self, dt: Duration) {
222 self.elapsed = self.elapsed.saturating_add(dt);
223 }
224
225 fn is_complete(&self) -> bool {
226 self.elapsed >= self.duration
227 }
228
229 fn value(&self) -> f32 {
230 (self.easing)(self.progress())
231 }
232
233 fn reset(&mut self) {
234 self.elapsed = Duration::ZERO;
235 }
236
237 fn overshoot(&self) -> Duration {
238 self.elapsed.saturating_sub(self.duration)
239 }
240}
241
242#[derive(Debug, Clone, Copy)]
250pub struct Pulse {
251 frequency: f32,
252 phase: f32,
253}
254
255impl Pulse {
256 pub fn new(frequency: f32) -> Self {
260 Self {
261 frequency: frequency.abs().max(f32::MIN_POSITIVE),
262 phase: 0.0,
263 }
264 }
265
266 pub fn phase(&self) -> f32 {
268 self.phase
269 }
270}
271
272impl Animation for Pulse {
273 fn tick(&mut self, dt: Duration) {
274 self.phase += std::f32::consts::TAU * self.frequency * dt.as_secs_f32();
275 self.phase %= std::f32::consts::TAU;
277 }
278
279 fn is_complete(&self) -> bool {
280 false }
282
283 fn value(&self) -> f32 {
284 (self.phase.sin() + 1.0) / 2.0
286 }
287
288 fn reset(&mut self) {
289 self.phase = 0.0;
290 }
291}
292
293#[derive(Debug, Clone, Copy)]
301pub struct Sequence<A, B> {
302 first: A,
303 second: B,
304 first_done: bool,
305}
306
307impl<A: Animation, B: Animation> Sequence<A, B> {
308 pub fn new(first: A, second: B) -> Self {
310 Self {
311 first,
312 second,
313 first_done: false,
314 }
315 }
316}
317
318impl<A: Animation, B: Animation> Animation for Sequence<A, B> {
319 fn tick(&mut self, dt: Duration) {
320 if !self.first_done {
321 self.first.tick(dt);
322 if self.first.is_complete() {
323 self.first_done = true;
324 let os = self.first.overshoot();
326 if !os.is_zero() {
327 self.second.tick(os);
328 }
329 }
330 } else {
331 self.second.tick(dt);
332 }
333 }
334
335 fn is_complete(&self) -> bool {
336 self.first_done && self.second.is_complete()
337 }
338
339 fn value(&self) -> f32 {
340 if self.first_done {
341 self.second.value()
342 } else {
343 self.first.value()
344 }
345 }
346
347 fn reset(&mut self) {
348 self.first.reset();
349 self.second.reset();
350 self.first_done = false;
351 }
352
353 fn overshoot(&self) -> Duration {
354 if self.first_done {
355 self.second.overshoot()
356 } else {
357 Duration::ZERO
358 }
359 }
360}
361
362#[derive(Debug, Clone, Copy)]
370pub struct Parallel<A, B> {
371 a: A,
372 b: B,
373}
374
375impl<A: Animation, B: Animation> Parallel<A, B> {
376 pub fn new(a: A, b: B) -> Self {
378 Self { a, b }
379 }
380
381 pub fn first(&self) -> &A {
383 &self.a
384 }
385
386 pub fn second(&self) -> &B {
388 &self.b
389 }
390}
391
392impl<A: Animation, B: Animation> Animation for Parallel<A, B> {
393 fn tick(&mut self, dt: Duration) {
394 if !self.a.is_complete() {
395 self.a.tick(dt);
396 }
397 if !self.b.is_complete() {
398 self.b.tick(dt);
399 }
400 }
401
402 fn is_complete(&self) -> bool {
403 self.a.is_complete() && self.b.is_complete()
404 }
405
406 fn value(&self) -> f32 {
407 (self.a.value() + self.b.value()) / 2.0
408 }
409
410 fn reset(&mut self) {
411 self.a.reset();
412 self.b.reset();
413 }
414}
415
416#[derive(Debug, Clone, Copy)]
422pub struct Delayed<A> {
423 delay: Duration,
424 elapsed: Duration,
425 inner: A,
426 started: bool,
427}
428
429impl<A: Animation> Delayed<A> {
430 pub fn new(delay: Duration, inner: A) -> Self {
432 Self {
433 delay,
434 elapsed: Duration::ZERO,
435 inner,
436 started: false,
437 }
438 }
439
440 pub fn has_started(&self) -> bool {
442 self.started
443 }
444
445 pub fn inner(&self) -> &A {
447 &self.inner
448 }
449}
450
451impl<A: Animation> Animation for Delayed<A> {
452 fn tick(&mut self, dt: Duration) {
453 if !self.started {
454 self.elapsed = self.elapsed.saturating_add(dt);
455 if self.elapsed >= self.delay {
456 self.started = true;
457 let os = self.elapsed.saturating_sub(self.delay);
459 if !os.is_zero() {
460 self.inner.tick(os);
461 }
462 }
463 } else {
464 self.inner.tick(dt);
465 }
466 }
467
468 fn is_complete(&self) -> bool {
469 self.started && self.inner.is_complete()
470 }
471
472 fn value(&self) -> f32 {
473 if self.started {
474 self.inner.value()
475 } else {
476 0.0
477 }
478 }
479
480 fn reset(&mut self) {
481 self.elapsed = Duration::ZERO;
482 self.started = false;
483 self.inner.reset();
484 }
485
486 fn overshoot(&self) -> Duration {
487 if self.started {
488 self.inner.overshoot()
489 } else {
490 Duration::ZERO
491 }
492 }
493}
494
495pub mod callbacks;
496pub mod group;
497pub mod presets;
498pub mod spring;
499pub mod stagger;
500pub mod timeline;
501pub use callbacks::{AnimationEvent, Callbacks};
502pub use group::AnimationGroup;
503pub use presets::InvertedFade;
504pub use spring::Spring;
505pub use stagger::{StaggerMode, stagger_offsets, stagger_offsets_with_jitter};
506pub use timeline::Timeline;
507
508pub fn sequence<A: Animation, B: Animation>(a: A, b: B) -> Sequence<A, B> {
514 Sequence::new(a, b)
515}
516
517pub fn parallel<A: Animation, B: Animation>(a: A, b: B) -> Parallel<A, B> {
519 Parallel::new(a, b)
520}
521
522pub fn delay<A: Animation>(d: Duration, a: A) -> Delayed<A> {
524 Delayed::new(d, a)
525}
526
527#[cfg(test)]
532mod tests {
533 use super::*;
534
535 const MS_16: Duration = Duration::from_millis(16);
536 const MS_100: Duration = Duration::from_millis(100);
537 const MS_500: Duration = Duration::from_millis(500);
538 const SEC_1: Duration = Duration::from_secs(1);
539
540 #[test]
543 fn easing_linear_endpoints() {
544 assert!((linear(0.0) - 0.0).abs() < f32::EPSILON);
545 assert!((linear(1.0) - 1.0).abs() < f32::EPSILON);
546 }
547
548 #[test]
549 fn easing_linear_midpoint() {
550 assert!((linear(0.5) - 0.5).abs() < f32::EPSILON);
551 }
552
553 #[test]
554 fn easing_clamps_input() {
555 assert!((linear(-1.0) - 0.0).abs() < f32::EPSILON);
556 assert!((linear(2.0) - 1.0).abs() < f32::EPSILON);
557 assert!((ease_in(-0.5) - 0.0).abs() < f32::EPSILON);
558 assert!((ease_out(1.5) - 1.0).abs() < f32::EPSILON);
559 }
560
561 #[test]
562 fn ease_in_slower_start() {
563 assert!(ease_in(0.5) < linear(0.5));
565 }
566
567 #[test]
568 fn ease_out_faster_start() {
569 assert!(ease_out(0.5) > linear(0.5));
571 }
572
573 #[test]
574 fn ease_in_out_endpoints() {
575 assert!((ease_in_out(0.0) - 0.0).abs() < f32::EPSILON);
576 assert!((ease_in_out(1.0) - 1.0).abs() < f32::EPSILON);
577 }
578
579 #[test]
580 fn ease_in_out_midpoint() {
581 assert!((ease_in_out(0.5) - 0.5).abs() < 0.01);
582 }
583
584 #[test]
585 fn ease_in_cubic_endpoints() {
586 assert!((ease_in_cubic(0.0) - 0.0).abs() < f32::EPSILON);
587 assert!((ease_in_cubic(1.0) - 1.0).abs() < f32::EPSILON);
588 }
589
590 #[test]
591 fn ease_out_cubic_endpoints() {
592 assert!((ease_out_cubic(0.0) - 0.0).abs() < f32::EPSILON);
593 assert!((ease_out_cubic(1.0) - 1.0).abs() < f32::EPSILON);
594 }
595
596 #[test]
597 fn ease_in_cubic_slower_than_quadratic() {
598 assert!(ease_in_cubic(0.5) < ease_in(0.5));
599 }
600
601 #[test]
604 fn fade_starts_at_zero() {
605 let fade = Fade::new(SEC_1);
606 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
607 assert!(!fade.is_complete());
608 }
609
610 #[test]
611 fn fade_completes_after_duration() {
612 let mut fade = Fade::new(SEC_1);
613 fade.tick(SEC_1);
614 assert!(fade.is_complete());
615 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
616 }
617
618 #[test]
619 fn fade_midpoint() {
620 let mut fade = Fade::new(SEC_1);
621 fade.tick(MS_500);
622 assert!((fade.value() - 0.5).abs() < 0.01);
623 }
624
625 #[test]
626 fn fade_incremental_ticks() {
627 let mut fade = Fade::new(Duration::from_millis(160));
628 for _ in 0..10 {
629 fade.tick(MS_16);
630 }
631 assert!(fade.is_complete());
632 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
633 }
634
635 #[test]
636 fn fade_with_ease_in() {
637 let mut fade = Fade::new(SEC_1).easing(ease_in);
638 fade.tick(MS_500);
639 assert!((fade.value() - 0.25).abs() < 0.01);
641 }
642
643 #[test]
644 fn fade_clamps_overshoot() {
645 let mut fade = Fade::new(MS_100);
646 fade.tick(SEC_1); assert!(fade.is_complete());
648 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
649 }
650
651 #[test]
652 fn fade_reset() {
653 let mut fade = Fade::new(SEC_1);
654 fade.tick(SEC_1);
655 assert!(fade.is_complete());
656 fade.reset();
657 assert!(!fade.is_complete());
658 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
659 }
660
661 #[test]
662 fn fade_zero_duration() {
663 let mut fade = Fade::new(Duration::ZERO);
664 fade.tick(MS_16);
666 assert!(fade.is_complete());
667 }
668
669 #[test]
670 fn fade_raw_progress() {
671 let mut fade = Fade::new(SEC_1).easing(ease_in);
672 fade.tick(MS_500);
673 assert!((fade.raw_progress() - 0.5).abs() < 0.01);
675 assert!((fade.value() - 0.25).abs() < 0.01);
676 }
677
678 #[test]
681 fn slide_starts_at_from() {
682 let slide = Slide::new(0, 100, SEC_1);
683 assert_eq!(slide.position(), 0);
684 }
685
686 #[test]
687 fn slide_ends_at_to() {
688 let mut slide = Slide::new(0, 100, SEC_1);
689 slide.tick(SEC_1);
690 assert_eq!(slide.position(), 100);
691 }
692
693 #[test]
694 fn slide_negative_range() {
695 let mut slide = Slide::new(100, -50, SEC_1).easing(linear);
696 slide.tick(SEC_1);
697 assert_eq!(slide.position(), -50);
698 }
699
700 #[test]
701 fn slide_midpoint_with_linear() {
702 let mut slide = Slide::new(0, 100, SEC_1).easing(linear);
703 slide.tick(MS_500);
704 assert_eq!(slide.position(), 50);
705 }
706
707 #[test]
708 fn slide_reset() {
709 let mut slide = Slide::new(10, 90, SEC_1);
710 slide.tick(SEC_1);
711 assert_eq!(slide.position(), 90);
712 slide.reset();
713 assert_eq!(slide.position(), 10);
714 }
715
716 #[test]
719 fn pulse_starts_at_midpoint() {
720 let pulse = Pulse::new(1.0);
721 assert!((pulse.value() - 0.5).abs() < f32::EPSILON);
723 }
724
725 #[test]
726 fn pulse_never_completes() {
727 let mut pulse = Pulse::new(1.0);
728 for _ in 0..100 {
729 pulse.tick(MS_100);
730 }
731 assert!(!pulse.is_complete());
732 }
733
734 #[test]
735 fn pulse_value_bounded() {
736 let mut pulse = Pulse::new(2.0);
737 for _ in 0..200 {
738 pulse.tick(MS_16);
739 let v = pulse.value();
740 assert!((0.0..=1.0).contains(&v), "pulse value out of range: {v}");
741 }
742 }
743
744 #[test]
745 fn pulse_quarter_cycle_reaches_peak() {
746 let mut pulse = Pulse::new(1.0);
747 pulse.tick(Duration::from_millis(250));
749 assert!((pulse.value() - 1.0).abs() < 0.02);
750 }
751
752 #[test]
753 fn pulse_phase_wraps() {
754 let mut pulse = Pulse::new(1.0);
755 pulse.tick(Duration::from_secs(10)); assert!(pulse.phase() < std::f32::consts::TAU);
758 }
759
760 #[test]
761 fn pulse_reset() {
762 let mut pulse = Pulse::new(1.0);
763 pulse.tick(SEC_1);
764 pulse.reset();
765 assert!((pulse.phase() - 0.0).abs() < f32::EPSILON);
766 assert!((pulse.value() - 0.5).abs() < f32::EPSILON);
767 }
768
769 #[test]
770 fn pulse_zero_frequency_clamped() {
771 let mut pulse = Pulse::new(0.0);
772 pulse.tick(SEC_1);
774 }
776
777 #[test]
780 fn sequence_plays_first_then_second() {
781 let a = Fade::new(SEC_1);
782 let b = Fade::new(SEC_1);
783 let mut seq = sequence(a, b);
784
785 seq.tick(MS_500);
787 assert!(!seq.is_complete());
788 assert!((seq.value() - 0.5).abs() < 0.01);
789
790 seq.tick(MS_500);
792 assert!(!seq.is_complete());
794
795 seq.tick(MS_500);
797 assert!((seq.value() - 0.5).abs() < 0.01);
798
799 seq.tick(MS_500);
801 assert!(seq.is_complete());
802 assert!((seq.value() - 1.0).abs() < f32::EPSILON);
803 }
804
805 #[test]
806 fn sequence_reset() {
807 let mut seq = sequence(Fade::new(MS_100), Fade::new(MS_100));
808 seq.tick(Duration::from_millis(200));
809 assert!(seq.is_complete());
810
811 seq.reset();
812 assert!(!seq.is_complete());
813 assert!((seq.value() - 0.0).abs() < f32::EPSILON);
814 }
815
816 #[test]
819 fn parallel_ticks_both() {
820 let a = Fade::new(SEC_1);
821 let b = Fade::new(Duration::from_millis(500));
822 let mut par = parallel(a, b);
823
824 par.tick(MS_500);
825 assert!((par.value() - 0.75).abs() < 0.01);
827 assert!(!par.is_complete()); par.tick(MS_500);
830 assert!(par.is_complete());
831 }
832
833 #[test]
834 fn parallel_access_components() {
835 let par = parallel(Fade::new(SEC_1), Fade::new(SEC_1));
836 assert!((par.first().value() - 0.0).abs() < f32::EPSILON);
837 assert!((par.second().value() - 0.0).abs() < f32::EPSILON);
838 }
839
840 #[test]
841 fn parallel_reset() {
842 let mut par = parallel(Fade::new(MS_100), Fade::new(MS_100));
843 par.tick(MS_100);
844 assert!(par.is_complete());
845
846 par.reset();
847 assert!(!par.is_complete());
848 }
849
850 #[test]
853 fn delayed_waits_then_plays() {
854 let mut d = delay(MS_500, Fade::new(MS_500));
855
856 d.tick(Duration::from_millis(250));
858 assert!(!d.has_started());
859 assert!((d.value() - 0.0).abs() < f32::EPSILON);
860
861 d.tick(Duration::from_millis(250));
863 assert!(d.has_started());
864
865 d.tick(MS_500);
867 assert!(d.is_complete());
868 assert!((d.value() - 1.0).abs() < f32::EPSILON);
869 }
870
871 #[test]
872 fn delayed_forwards_overshoot() {
873 let mut d = delay(MS_100, Fade::new(SEC_1));
874
875 d.tick(Duration::from_millis(200));
877 assert!(d.has_started());
878 assert!((d.value() - 0.1).abs() < 0.02);
880 }
881
882 #[test]
883 fn delayed_reset() {
884 let mut d = delay(MS_100, Fade::new(MS_100));
885 d.tick(Duration::from_millis(200));
886 assert!(d.is_complete());
887
888 d.reset();
889 assert!(!d.has_started());
890 assert!(!d.is_complete());
891 }
892
893 #[test]
896 fn nested_sequence() {
897 let inner = sequence(Fade::new(MS_100), Fade::new(MS_100));
898 let mut outer = sequence(inner, Fade::new(MS_100));
899
900 outer.tick(Duration::from_millis(300));
901 assert!(outer.is_complete());
902 }
903
904 #[test]
905 fn delayed_parallel() {
906 let a = delay(MS_100, Fade::new(MS_100));
907 let b = Fade::new(Duration::from_millis(200));
908 let mut par = parallel(a, b);
909
910 par.tick(Duration::from_millis(200));
911 assert!(par.is_complete());
912 }
913
914 #[test]
915 fn parallel_of_sequences() {
916 let s1 = sequence(Fade::new(MS_100), Fade::new(MS_100));
917 let s2 = sequence(Fade::new(MS_100), Fade::new(MS_100));
918 let mut par = parallel(s1, s2);
919
920 par.tick(Duration::from_millis(200));
921 assert!(par.is_complete());
922 }
923
924 #[test]
927 fn zero_dt_is_noop() {
928 let mut fade = Fade::new(SEC_1);
929 fade.tick(Duration::ZERO);
930 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
931 }
932
933 #[test]
934 fn very_small_dt() {
935 let mut fade = Fade::new(SEC_1);
936 fade.tick(Duration::from_nanos(1));
937 assert!(fade.value() < 0.001);
939 }
940
941 #[test]
942 fn very_large_dt() {
943 let mut fade = Fade::new(MS_100);
944 fade.tick(Duration::from_secs(3600));
945 assert!(fade.is_complete());
946 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
947 }
948
949 #[test]
950 fn rapid_small_ticks() {
951 let mut fade = Fade::new(SEC_1);
952 for _ in 0..1000 {
953 fade.tick(Duration::from_millis(1));
954 }
955 assert!(fade.is_complete());
956 }
957
958 #[test]
959 fn tick_after_complete_is_safe() {
960 let mut fade = Fade::new(MS_100);
961 fade.tick(SEC_1);
962 assert!(fade.is_complete());
963 fade.tick(SEC_1);
965 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
966 }
967}