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