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 #[inline]
140 pub fn raw_progress(&self) -> f32 {
141 let t = self.elapsed.as_secs_f64() / self.duration.as_secs_f64();
142 (t as f32).clamp(0.0, 1.0)
143 }
144}
145
146impl Animation for Fade {
147 fn tick(&mut self, dt: Duration) {
148 self.elapsed = self.elapsed.saturating_add(dt);
149 }
150
151 fn is_complete(&self) -> bool {
152 self.elapsed >= self.duration
153 }
154
155 fn value(&self) -> f32 {
156 (self.easing)(self.raw_progress())
157 }
158
159 fn reset(&mut self) {
160 self.elapsed = Duration::ZERO;
161 }
162
163 fn overshoot(&self) -> Duration {
164 self.elapsed.saturating_sub(self.duration)
165 }
166}
167
168#[derive(Debug, Clone, Copy)]
177pub struct Slide {
178 from: i16,
179 to: i16,
180 elapsed: Duration,
181 duration: Duration,
182 easing: EasingFn,
183}
184
185impl Slide {
186 pub fn new(from: i16, to: i16, duration: Duration) -> Self {
188 Self {
189 from,
190 to,
191 elapsed: Duration::ZERO,
192 duration: if duration.is_zero() {
193 Duration::from_nanos(1)
194 } else {
195 duration
196 },
197 easing: ease_out,
198 }
199 }
200
201 pub fn easing(mut self, easing: EasingFn) -> Self {
203 self.easing = easing;
204 self
205 }
206
207 fn progress(&self) -> f32 {
208 let t = self.elapsed.as_secs_f64() / self.duration.as_secs_f64();
209 (t as f32).clamp(0.0, 1.0)
210 }
211
212 pub fn position(&self) -> i16 {
214 let t = (self.easing)(self.progress());
215 let range = f32::from(self.to) - f32::from(self.from);
216 let pos = f32::from(self.from) + range * t;
217 pos.round().clamp(f32::from(i16::MIN), f32::from(i16::MAX)) as i16
218 }
219}
220
221impl Animation for Slide {
222 fn tick(&mut self, dt: Duration) {
223 self.elapsed = self.elapsed.saturating_add(dt);
224 }
225
226 fn is_complete(&self) -> bool {
227 self.elapsed >= self.duration
228 }
229
230 fn value(&self) -> f32 {
231 (self.easing)(self.progress())
232 }
233
234 fn reset(&mut self) {
235 self.elapsed = Duration::ZERO;
236 }
237
238 fn overshoot(&self) -> Duration {
239 self.elapsed.saturating_sub(self.duration)
240 }
241}
242
243#[derive(Debug, Clone, Copy)]
251pub struct Pulse {
252 frequency: f32,
253 phase: f32,
254}
255
256impl Pulse {
257 pub fn new(frequency: f32) -> Self {
261 Self {
262 frequency: frequency.abs().max(f32::MIN_POSITIVE),
263 phase: 0.0,
264 }
265 }
266
267 #[inline]
269 pub fn phase(&self) -> f32 {
270 self.phase
271 }
272}
273
274impl Animation for Pulse {
275 fn tick(&mut self, dt: Duration) {
276 self.phase += std::f32::consts::TAU * self.frequency * dt.as_secs_f32();
277 self.phase %= std::f32::consts::TAU;
279 }
280
281 fn is_complete(&self) -> bool {
282 false }
284
285 fn value(&self) -> f32 {
286 (self.phase.sin() + 1.0) / 2.0
288 }
289
290 fn reset(&mut self) {
291 self.phase = 0.0;
292 }
293}
294
295#[derive(Debug, Clone, Copy)]
303pub struct Sequence<A, B> {
304 first: A,
305 second: B,
306 first_done: bool,
307}
308
309impl<A: Animation, B: Animation> Sequence<A, B> {
310 pub fn new(first: A, second: B) -> Self {
312 Self {
313 first,
314 second,
315 first_done: false,
316 }
317 }
318}
319
320impl<A: Animation, B: Animation> Animation for Sequence<A, B> {
321 fn tick(&mut self, dt: Duration) {
322 if !self.first_done {
323 self.first.tick(dt);
324 if self.first.is_complete() {
325 self.first_done = true;
326 let os = self.first.overshoot();
328 if !os.is_zero() {
329 self.second.tick(os);
330 }
331 }
332 } else {
333 self.second.tick(dt);
334 }
335 }
336
337 fn is_complete(&self) -> bool {
338 self.first_done && self.second.is_complete()
339 }
340
341 fn value(&self) -> f32 {
342 if self.first_done {
343 self.second.value()
344 } else {
345 self.first.value()
346 }
347 }
348
349 fn reset(&mut self) {
350 self.first.reset();
351 self.second.reset();
352 self.first_done = false;
353 }
354
355 fn overshoot(&self) -> Duration {
356 if self.first_done {
357 self.second.overshoot()
358 } else {
359 Duration::ZERO
360 }
361 }
362}
363
364#[derive(Debug, Clone, Copy)]
372pub struct Parallel<A, B> {
373 a: A,
374 b: B,
375}
376
377impl<A: Animation, B: Animation> Parallel<A, B> {
378 pub fn new(a: A, b: B) -> Self {
380 Self { a, b }
381 }
382
383 #[inline]
385 pub fn first(&self) -> &A {
386 &self.a
387 }
388
389 #[inline]
391 pub fn second(&self) -> &B {
392 &self.b
393 }
394}
395
396impl<A: Animation, B: Animation> Animation for Parallel<A, B> {
397 fn tick(&mut self, dt: Duration) {
398 if !self.a.is_complete() {
399 self.a.tick(dt);
400 }
401 if !self.b.is_complete() {
402 self.b.tick(dt);
403 }
404 }
405
406 fn is_complete(&self) -> bool {
407 self.a.is_complete() && self.b.is_complete()
408 }
409
410 fn value(&self) -> f32 {
411 (self.a.value() + self.b.value()) / 2.0
412 }
413
414 fn reset(&mut self) {
415 self.a.reset();
416 self.b.reset();
417 }
418}
419
420#[derive(Debug, Clone, Copy)]
426pub struct Delayed<A> {
427 delay: Duration,
428 elapsed: Duration,
429 inner: A,
430 started: bool,
431}
432
433impl<A: Animation> Delayed<A> {
434 pub fn new(delay: Duration, inner: A) -> Self {
436 Self {
437 delay,
438 elapsed: Duration::ZERO,
439 inner,
440 started: false,
441 }
442 }
443
444 #[inline]
446 pub fn has_started(&self) -> bool {
447 self.started
448 }
449
450 #[inline]
452 pub fn inner(&self) -> &A {
453 &self.inner
454 }
455}
456
457impl<A: Animation> Animation for Delayed<A> {
458 fn tick(&mut self, dt: Duration) {
459 if !self.started {
460 self.elapsed = self.elapsed.saturating_add(dt);
461 if self.elapsed >= self.delay {
462 self.started = true;
463 let os = self.elapsed.saturating_sub(self.delay);
465 if !os.is_zero() {
466 self.inner.tick(os);
467 }
468 }
469 } else {
470 self.inner.tick(dt);
471 }
472 }
473
474 fn is_complete(&self) -> bool {
475 self.started && self.inner.is_complete()
476 }
477
478 fn value(&self) -> f32 {
479 if self.started {
480 self.inner.value()
481 } else {
482 0.0
483 }
484 }
485
486 fn reset(&mut self) {
487 self.elapsed = Duration::ZERO;
488 self.started = false;
489 self.inner.reset();
490 }
491
492 fn overshoot(&self) -> Duration {
493 if self.started {
494 self.inner.overshoot()
495 } else {
496 Duration::ZERO
497 }
498 }
499}
500
501pub mod callbacks;
502pub mod group;
503pub mod presets;
504pub mod spring;
505pub mod stagger;
506pub mod timeline;
507pub use callbacks::{AnimationEvent, Callbacks};
508pub use group::AnimationGroup;
509pub use presets::InvertedFade;
510pub use spring::Spring;
511pub use stagger::{StaggerMode, stagger_offsets, stagger_offsets_with_jitter};
512pub use timeline::Timeline;
513
514pub fn sequence<A: Animation, B: Animation>(a: A, b: B) -> Sequence<A, B> {
520 Sequence::new(a, b)
521}
522
523pub fn parallel<A: Animation, B: Animation>(a: A, b: B) -> Parallel<A, B> {
525 Parallel::new(a, b)
526}
527
528pub fn delay<A: Animation>(d: Duration, a: A) -> Delayed<A> {
530 Delayed::new(d, a)
531}
532
533#[cfg(test)]
538mod tests {
539 use super::*;
540
541 const MS_16: Duration = Duration::from_millis(16);
542 const MS_100: Duration = Duration::from_millis(100);
543 const MS_500: Duration = Duration::from_millis(500);
544 const SEC_1: Duration = Duration::from_secs(1);
545
546 #[test]
549 fn easing_linear_endpoints() {
550 assert!((linear(0.0) - 0.0).abs() < f32::EPSILON);
551 assert!((linear(1.0) - 1.0).abs() < f32::EPSILON);
552 }
553
554 #[test]
555 fn easing_linear_midpoint() {
556 assert!((linear(0.5) - 0.5).abs() < f32::EPSILON);
557 }
558
559 #[test]
560 fn easing_clamps_input() {
561 assert!((linear(-1.0) - 0.0).abs() < f32::EPSILON);
562 assert!((linear(2.0) - 1.0).abs() < f32::EPSILON);
563 assert!((ease_in(-0.5) - 0.0).abs() < f32::EPSILON);
564 assert!((ease_out(1.5) - 1.0).abs() < f32::EPSILON);
565 }
566
567 #[test]
568 fn ease_in_slower_start() {
569 assert!(ease_in(0.5) < linear(0.5));
571 }
572
573 #[test]
574 fn ease_out_faster_start() {
575 assert!(ease_out(0.5) > linear(0.5));
577 }
578
579 #[test]
580 fn ease_in_out_endpoints() {
581 assert!((ease_in_out(0.0) - 0.0).abs() < f32::EPSILON);
582 assert!((ease_in_out(1.0) - 1.0).abs() < f32::EPSILON);
583 }
584
585 #[test]
586 fn ease_in_out_midpoint() {
587 assert!((ease_in_out(0.5) - 0.5).abs() < 0.01);
588 }
589
590 #[test]
591 fn ease_in_cubic_endpoints() {
592 assert!((ease_in_cubic(0.0) - 0.0).abs() < f32::EPSILON);
593 assert!((ease_in_cubic(1.0) - 1.0).abs() < f32::EPSILON);
594 }
595
596 #[test]
597 fn ease_out_cubic_endpoints() {
598 assert!((ease_out_cubic(0.0) - 0.0).abs() < f32::EPSILON);
599 assert!((ease_out_cubic(1.0) - 1.0).abs() < f32::EPSILON);
600 }
601
602 #[test]
603 fn ease_in_cubic_slower_than_quadratic() {
604 assert!(ease_in_cubic(0.5) < ease_in(0.5));
605 }
606
607 #[test]
610 fn fade_starts_at_zero() {
611 let fade = Fade::new(SEC_1);
612 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
613 assert!(!fade.is_complete());
614 }
615
616 #[test]
617 fn fade_completes_after_duration() {
618 let mut fade = Fade::new(SEC_1);
619 fade.tick(SEC_1);
620 assert!(fade.is_complete());
621 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
622 }
623
624 #[test]
625 fn fade_midpoint() {
626 let mut fade = Fade::new(SEC_1);
627 fade.tick(MS_500);
628 assert!((fade.value() - 0.5).abs() < 0.01);
629 }
630
631 #[test]
632 fn fade_incremental_ticks() {
633 let mut fade = Fade::new(Duration::from_millis(160));
634 for _ in 0..10 {
635 fade.tick(MS_16);
636 }
637 assert!(fade.is_complete());
638 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
639 }
640
641 #[test]
642 fn fade_with_ease_in() {
643 let mut fade = Fade::new(SEC_1).easing(ease_in);
644 fade.tick(MS_500);
645 assert!((fade.value() - 0.25).abs() < 0.01);
647 }
648
649 #[test]
650 fn fade_clamps_overshoot() {
651 let mut fade = Fade::new(MS_100);
652 fade.tick(SEC_1); assert!(fade.is_complete());
654 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
655 }
656
657 #[test]
658 fn fade_reset() {
659 let mut fade = Fade::new(SEC_1);
660 fade.tick(SEC_1);
661 assert!(fade.is_complete());
662 fade.reset();
663 assert!(!fade.is_complete());
664 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
665 }
666
667 #[test]
668 fn fade_zero_duration() {
669 let mut fade = Fade::new(Duration::ZERO);
670 fade.tick(MS_16);
672 assert!(fade.is_complete());
673 }
674
675 #[test]
676 fn fade_raw_progress() {
677 let mut fade = Fade::new(SEC_1).easing(ease_in);
678 fade.tick(MS_500);
679 assert!((fade.raw_progress() - 0.5).abs() < 0.01);
681 assert!((fade.value() - 0.25).abs() < 0.01);
682 }
683
684 #[test]
687 fn slide_starts_at_from() {
688 let slide = Slide::new(0, 100, SEC_1);
689 assert_eq!(slide.position(), 0);
690 }
691
692 #[test]
693 fn slide_ends_at_to() {
694 let mut slide = Slide::new(0, 100, SEC_1);
695 slide.tick(SEC_1);
696 assert_eq!(slide.position(), 100);
697 }
698
699 #[test]
700 fn slide_negative_range() {
701 let mut slide = Slide::new(100, -50, SEC_1).easing(linear);
702 slide.tick(SEC_1);
703 assert_eq!(slide.position(), -50);
704 }
705
706 #[test]
707 fn slide_midpoint_with_linear() {
708 let mut slide = Slide::new(0, 100, SEC_1).easing(linear);
709 slide.tick(MS_500);
710 assert_eq!(slide.position(), 50);
711 }
712
713 #[test]
714 fn slide_reset() {
715 let mut slide = Slide::new(10, 90, SEC_1);
716 slide.tick(SEC_1);
717 assert_eq!(slide.position(), 90);
718 slide.reset();
719 assert_eq!(slide.position(), 10);
720 }
721
722 #[test]
725 fn pulse_starts_at_midpoint() {
726 let pulse = Pulse::new(1.0);
727 assert!((pulse.value() - 0.5).abs() < f32::EPSILON);
729 }
730
731 #[test]
732 fn pulse_never_completes() {
733 let mut pulse = Pulse::new(1.0);
734 for _ in 0..100 {
735 pulse.tick(MS_100);
736 }
737 assert!(!pulse.is_complete());
738 }
739
740 #[test]
741 fn pulse_value_bounded() {
742 let mut pulse = Pulse::new(2.0);
743 for _ in 0..200 {
744 pulse.tick(MS_16);
745 let v = pulse.value();
746 assert!((0.0..=1.0).contains(&v), "pulse value out of range: {v}");
747 }
748 }
749
750 #[test]
751 fn pulse_quarter_cycle_reaches_peak() {
752 let mut pulse = Pulse::new(1.0);
753 pulse.tick(Duration::from_millis(250));
755 assert!((pulse.value() - 1.0).abs() < 0.02);
756 }
757
758 #[test]
759 fn pulse_phase_wraps() {
760 let mut pulse = Pulse::new(1.0);
761 pulse.tick(Duration::from_secs(10)); assert!(pulse.phase() < std::f32::consts::TAU);
764 }
765
766 #[test]
767 fn pulse_reset() {
768 let mut pulse = Pulse::new(1.0);
769 pulse.tick(SEC_1);
770 pulse.reset();
771 assert!((pulse.phase() - 0.0).abs() < f32::EPSILON);
772 assert!((pulse.value() - 0.5).abs() < f32::EPSILON);
773 }
774
775 #[test]
776 fn pulse_zero_frequency_clamped() {
777 let mut pulse = Pulse::new(0.0);
778 pulse.tick(SEC_1);
780 }
782
783 #[test]
786 fn sequence_plays_first_then_second() {
787 let a = Fade::new(SEC_1);
788 let b = Fade::new(SEC_1);
789 let mut seq = sequence(a, b);
790
791 seq.tick(MS_500);
793 assert!(!seq.is_complete());
794 assert!((seq.value() - 0.5).abs() < 0.01);
795
796 seq.tick(MS_500);
798 assert!(!seq.is_complete());
800
801 seq.tick(MS_500);
803 assert!((seq.value() - 0.5).abs() < 0.01);
804
805 seq.tick(MS_500);
807 assert!(seq.is_complete());
808 assert!((seq.value() - 1.0).abs() < f32::EPSILON);
809 }
810
811 #[test]
812 fn sequence_reset() {
813 let mut seq = sequence(Fade::new(MS_100), Fade::new(MS_100));
814 seq.tick(Duration::from_millis(200));
815 assert!(seq.is_complete());
816
817 seq.reset();
818 assert!(!seq.is_complete());
819 assert!((seq.value() - 0.0).abs() < f32::EPSILON);
820 }
821
822 #[test]
825 fn parallel_ticks_both() {
826 let a = Fade::new(SEC_1);
827 let b = Fade::new(Duration::from_millis(500));
828 let mut par = parallel(a, b);
829
830 par.tick(MS_500);
831 assert!((par.value() - 0.75).abs() < 0.01);
833 assert!(!par.is_complete()); par.tick(MS_500);
836 assert!(par.is_complete());
837 }
838
839 #[test]
840 fn parallel_access_components() {
841 let par = parallel(Fade::new(SEC_1), Fade::new(SEC_1));
842 assert!((par.first().value() - 0.0).abs() < f32::EPSILON);
843 assert!((par.second().value() - 0.0).abs() < f32::EPSILON);
844 }
845
846 #[test]
847 fn parallel_reset() {
848 let mut par = parallel(Fade::new(MS_100), Fade::new(MS_100));
849 par.tick(MS_100);
850 assert!(par.is_complete());
851
852 par.reset();
853 assert!(!par.is_complete());
854 }
855
856 #[test]
859 fn delayed_waits_then_plays() {
860 let mut d = delay(MS_500, Fade::new(MS_500));
861
862 d.tick(Duration::from_millis(250));
864 assert!(!d.has_started());
865 assert!((d.value() - 0.0).abs() < f32::EPSILON);
866
867 d.tick(Duration::from_millis(250));
869 assert!(d.has_started());
870
871 d.tick(MS_500);
873 assert!(d.is_complete());
874 assert!((d.value() - 1.0).abs() < f32::EPSILON);
875 }
876
877 #[test]
878 fn delayed_forwards_overshoot() {
879 let mut d = delay(MS_100, Fade::new(SEC_1));
880
881 d.tick(Duration::from_millis(200));
883 assert!(d.has_started());
884 assert!((d.value() - 0.1).abs() < 0.02);
886 }
887
888 #[test]
889 fn delayed_reset() {
890 let mut d = delay(MS_100, Fade::new(MS_100));
891 d.tick(Duration::from_millis(200));
892 assert!(d.is_complete());
893
894 d.reset();
895 assert!(!d.has_started());
896 assert!(!d.is_complete());
897 }
898
899 #[test]
902 fn nested_sequence() {
903 let inner = sequence(Fade::new(MS_100), Fade::new(MS_100));
904 let mut outer = sequence(inner, Fade::new(MS_100));
905
906 outer.tick(Duration::from_millis(300));
907 assert!(outer.is_complete());
908 }
909
910 #[test]
911 fn delayed_parallel() {
912 let a = delay(MS_100, Fade::new(MS_100));
913 let b = Fade::new(Duration::from_millis(200));
914 let mut par = parallel(a, b);
915
916 par.tick(Duration::from_millis(200));
917 assert!(par.is_complete());
918 }
919
920 #[test]
921 fn parallel_of_sequences() {
922 let s1 = sequence(Fade::new(MS_100), Fade::new(MS_100));
923 let s2 = sequence(Fade::new(MS_100), Fade::new(MS_100));
924 let mut par = parallel(s1, s2);
925
926 par.tick(Duration::from_millis(200));
927 assert!(par.is_complete());
928 }
929
930 #[test]
933 fn zero_dt_is_noop() {
934 let mut fade = Fade::new(SEC_1);
935 fade.tick(Duration::ZERO);
936 assert!((fade.value() - 0.0).abs() < f32::EPSILON);
937 }
938
939 #[test]
940 fn very_small_dt() {
941 let mut fade = Fade::new(SEC_1);
942 fade.tick(Duration::from_nanos(1));
943 assert!(fade.value() < 0.001);
945 }
946
947 #[test]
948 fn very_large_dt() {
949 let mut fade = Fade::new(MS_100);
950 fade.tick(Duration::from_secs(3600));
951 assert!(fade.is_complete());
952 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
953 }
954
955 #[test]
956 fn rapid_small_ticks() {
957 let mut fade = Fade::new(SEC_1);
958 for _ in 0..1000 {
959 fade.tick(Duration::from_millis(1));
960 }
961 assert!(fade.is_complete());
962 }
963
964 #[test]
965 fn tick_after_complete_is_safe() {
966 let mut fade = Fade::new(MS_100);
967 fade.tick(SEC_1);
968 assert!(fade.is_complete());
969 fade.tick(SEC_1);
971 assert!((fade.value() - 1.0).abs() < f32::EPSILON);
972 }
973}