1use std::f64::consts::PI;
8
9pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
14 a + (b - a) * t
15}
16
17pub fn ease_linear(t: f64) -> f64 {
19 clamp01(t)
20}
21
22pub fn ease_in_quad(t: f64) -> f64 {
24 let t = clamp01(t);
25 t * t
26}
27
28pub fn ease_out_quad(t: f64) -> f64 {
30 let t = clamp01(t);
31 1.0 - (1.0 - t) * (1.0 - t)
32}
33
34pub fn ease_in_out_quad(t: f64) -> f64 {
36 let t = clamp01(t);
37 if t < 0.5 {
38 2.0 * t * t
39 } else {
40 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
41 }
42}
43
44pub fn ease_in_cubic(t: f64) -> f64 {
46 let t = clamp01(t);
47 t * t * t
48}
49
50pub fn ease_out_cubic(t: f64) -> f64 {
52 let t = clamp01(t);
53 1.0 - (1.0 - t).powi(3)
54}
55
56pub fn ease_in_out_cubic(t: f64) -> f64 {
58 let t = clamp01(t);
59 if t < 0.5 {
60 4.0 * t * t * t
61 } else {
62 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
63 }
64}
65
66pub fn ease_out_elastic(t: f64) -> f64 {
68 let t = clamp01(t);
69 if t == 0.0 {
70 0.0
71 } else if t == 1.0 {
72 1.0
73 } else {
74 let c4 = (2.0 * PI) / 3.0;
75 2f64.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
76 }
77}
78
79pub fn ease_out_bounce(t: f64) -> f64 {
81 let t = clamp01(t);
82 let n1 = 7.5625;
83 let d1 = 2.75;
84
85 if t < 1.0 / d1 {
86 n1 * t * t
87 } else if t < 2.0 / d1 {
88 let t = t - 1.5 / d1;
89 n1 * t * t + 0.75
90 } else if t < 2.5 / d1 {
91 let t = t - 2.25 / d1;
92 n1 * t * t + 0.9375
93 } else {
94 let t = t - 2.625 / d1;
95 n1 * t * t + 0.984_375
96 }
97}
98
99pub struct Tween {
119 from: f64,
120 to: f64,
121 duration_ticks: u64,
122 start_tick: u64,
123 easing: fn(f64) -> f64,
124 done: bool,
125}
126
127impl Tween {
128 pub fn new(from: f64, to: f64, duration_ticks: u64) -> Self {
134 Self {
135 from,
136 to,
137 duration_ticks,
138 start_tick: 0,
139 easing: ease_linear,
140 done: false,
141 }
142 }
143
144 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
149 self.easing = f;
150 self
151 }
152
153 pub fn value(&mut self, tick: u64) -> f64 {
158 if self.done {
159 return self.to;
160 }
161
162 if self.duration_ticks == 0 {
163 self.done = true;
164 return self.to;
165 }
166
167 let elapsed = tick.wrapping_sub(self.start_tick);
168 if elapsed >= self.duration_ticks {
169 self.done = true;
170 return self.to;
171 }
172
173 let progress = elapsed as f64 / self.duration_ticks as f64;
174 let eased = (self.easing)(clamp01(progress));
175 lerp(self.from, self.to, eased)
176 }
177
178 pub fn is_done(&self) -> bool {
180 self.done
181 }
182
183 pub fn reset(&mut self, tick: u64) {
185 self.start_tick = tick;
186 self.done = false;
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum LoopMode {
193 Once,
195 Repeat,
197 PingPong,
199}
200
201#[derive(Clone, Copy)]
202struct KeyframeStop {
203 position: f64,
204 value: f64,
205}
206
207pub struct Keyframes {
233 duration_ticks: u64,
234 start_tick: u64,
235 stops: Vec<KeyframeStop>,
236 default_easing: fn(f64) -> f64,
237 segment_easing: Vec<fn(f64) -> f64>,
238 loop_mode: LoopMode,
239 done: bool,
240}
241
242impl Keyframes {
243 pub fn new(duration_ticks: u64) -> Self {
249 Self {
250 duration_ticks,
251 start_tick: 0,
252 stops: Vec::new(),
253 default_easing: ease_linear,
254 segment_easing: Vec::new(),
255 loop_mode: LoopMode::Once,
256 done: false,
257 }
258 }
259
260 pub fn stop(mut self, position: f64, value: f64) -> Self {
264 self.stops.push(KeyframeStop {
265 position: clamp01(position),
266 value,
267 });
268 if self.stops.len() >= 2 {
269 self.segment_easing.push(self.default_easing);
270 }
271 self
272 }
273
274 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
279 self.default_easing = f;
280 self.segment_easing.fill(f);
281 self
282 }
283
284 pub fn segment_easing(mut self, segment_index: usize, f: fn(f64) -> f64) -> Self {
289 if let Some(slot) = self.segment_easing.get_mut(segment_index) {
290 *slot = f;
291 }
292 self
293 }
294
295 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
297 self.loop_mode = mode;
298 self
299 }
300
301 pub fn value(&mut self, tick: u64) -> f64 {
303 if self.stops.is_empty() {
304 self.done = true;
305 return 0.0;
306 }
307 if self.stops.len() == 1 {
308 self.done = true;
309 return self.stops[0].value;
310 }
311
312 let mut stops = self.stops.clone();
313 stops.sort_by(|a, b| a.position.total_cmp(&b.position));
314
315 let end_value = stops.last().map_or(0.0, |s| s.value);
316 let loop_tick = match map_loop_tick(
317 tick,
318 self.start_tick,
319 self.duration_ticks,
320 self.loop_mode,
321 &mut self.done,
322 ) {
323 Some(v) => v,
324 None => return end_value,
325 };
326
327 let progress = loop_tick as f64 / self.duration_ticks as f64;
328
329 if progress <= stops[0].position {
330 return stops[0].value;
331 }
332 if progress >= 1.0 {
333 return end_value;
334 }
335
336 for i in 0..(stops.len() - 1) {
337 let a = stops[i];
338 let b = stops[i + 1];
339 if progress <= b.position {
340 let span = b.position - a.position;
341 if span <= f64::EPSILON {
342 return b.value;
343 }
344 let local = clamp01((progress - a.position) / span);
345 let easing = self
346 .segment_easing
347 .get(i)
348 .copied()
349 .unwrap_or(self.default_easing);
350 let eased = easing(local);
351 return lerp(a.value, b.value, eased);
352 }
353 }
354
355 end_value
356 }
357
358 pub fn is_done(&self) -> bool {
360 self.done
361 }
362
363 pub fn reset(&mut self, tick: u64) {
365 self.start_tick = tick;
366 self.done = false;
367 }
368}
369
370#[derive(Clone, Copy)]
371struct SequenceSegment {
372 from: f64,
373 to: f64,
374 duration_ticks: u64,
375 easing: fn(f64) -> f64,
376}
377
378pub struct Sequence {
397 segments: Vec<SequenceSegment>,
398 loop_mode: LoopMode,
399 start_tick: u64,
400 done: bool,
401}
402
403impl Default for Sequence {
404 fn default() -> Self {
405 Self::new()
406 }
407}
408
409impl Sequence {
410 pub fn new() -> Self {
415 Self {
416 segments: Vec::new(),
417 loop_mode: LoopMode::Once,
418 start_tick: 0,
419 done: false,
420 }
421 }
422
423 pub fn then(mut self, from: f64, to: f64, duration_ticks: u64, easing: fn(f64) -> f64) -> Self {
425 self.segments.push(SequenceSegment {
426 from,
427 to,
428 duration_ticks,
429 easing,
430 });
431 self
432 }
433
434 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
436 self.loop_mode = mode;
437 self
438 }
439
440 pub fn value(&mut self, tick: u64) -> f64 {
442 if self.segments.is_empty() {
443 self.done = true;
444 return 0.0;
445 }
446
447 let total_duration = self
448 .segments
449 .iter()
450 .fold(0_u64, |acc, s| acc.saturating_add(s.duration_ticks));
451 let end_value = self.segments.last().map_or(0.0, |s| s.to);
452
453 let loop_tick = match map_loop_tick(
454 tick,
455 self.start_tick,
456 total_duration,
457 self.loop_mode,
458 &mut self.done,
459 ) {
460 Some(v) => v,
461 None => return end_value,
462 };
463
464 let mut remaining = loop_tick;
465 for segment in &self.segments {
466 if segment.duration_ticks == 0 {
467 continue;
468 }
469 if remaining < segment.duration_ticks {
470 let progress = remaining as f64 / segment.duration_ticks as f64;
471 let eased = (segment.easing)(clamp01(progress));
472 return lerp(segment.from, segment.to, eased);
473 }
474 remaining -= segment.duration_ticks;
475 }
476
477 end_value
478 }
479
480 pub fn is_done(&self) -> bool {
482 self.done
483 }
484
485 pub fn reset(&mut self, tick: u64) {
487 self.start_tick = tick;
488 self.done = false;
489 }
490}
491
492pub struct Stagger {
516 from: f64,
517 to: f64,
518 duration_ticks: u64,
519 start_tick: u64,
520 delay_ticks: u64,
521 easing: fn(f64) -> f64,
522 loop_mode: LoopMode,
523 item_count: usize,
524 done: bool,
525}
526
527impl Stagger {
528 pub fn new(from: f64, to: f64, duration_ticks: u64) -> Self {
532 Self {
533 from,
534 to,
535 duration_ticks,
536 start_tick: 0,
537 delay_ticks: 0,
538 easing: ease_linear,
539 loop_mode: LoopMode::Once,
540 item_count: 0,
541 done: false,
542 }
543 }
544
545 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
547 self.easing = f;
548 self
549 }
550
551 pub fn delay(mut self, ticks: u64) -> Self {
553 self.delay_ticks = ticks;
554 self
555 }
556
557 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
560 self.loop_mode = mode;
561 self
562 }
563
564 pub fn items(mut self, count: usize) -> Self {
570 self.item_count = count;
571 self
572 }
573
574 pub fn value(&mut self, tick: u64, item_index: usize) -> f64 {
576 if item_index >= self.item_count {
577 self.item_count = item_index + 1;
578 }
579
580 let total_cycle = self.total_cycle_ticks();
581
582 let effective_tick = if self.loop_mode == LoopMode::Once {
583 tick
584 } else {
585 let elapsed = tick.wrapping_sub(self.start_tick);
586 let mapped = match self.loop_mode {
587 LoopMode::Repeat => {
588 if total_cycle == 0 {
589 0
590 } else {
591 elapsed % total_cycle
592 }
593 }
594 LoopMode::PingPong => {
595 if total_cycle == 0 {
596 0
597 } else {
598 let full = total_cycle.saturating_mul(2);
599 let phase = elapsed % full;
600 if phase < total_cycle {
601 phase
602 } else {
603 full - phase
604 }
605 }
606 }
607 LoopMode::Once => unreachable!(),
608 };
609 self.start_tick.wrapping_add(mapped)
610 };
611
612 let delay = self.delay_ticks.wrapping_mul(item_index as u64);
613 let item_start = self.start_tick.wrapping_add(delay);
614
615 if effective_tick < item_start {
616 self.done = false;
617 return self.from;
618 }
619
620 if self.duration_ticks == 0 {
621 self.done = true;
622 return self.to;
623 }
624
625 let elapsed = effective_tick - item_start;
626 if elapsed >= self.duration_ticks {
627 self.done = true;
628 return self.to;
629 }
630
631 self.done = false;
632 let progress = elapsed as f64 / self.duration_ticks as f64;
633 let eased = (self.easing)(clamp01(progress));
634 lerp(self.from, self.to, eased)
635 }
636
637 fn total_cycle_ticks(&self) -> u64 {
638 let max_delay = self
639 .delay_ticks
640 .wrapping_mul(self.item_count.saturating_sub(1) as u64);
641 self.duration_ticks.saturating_add(max_delay)
642 }
643
644 pub fn is_done(&self) -> bool {
646 self.done
647 }
648
649 pub fn reset(&mut self, tick: u64) {
651 self.start_tick = tick;
652 self.done = false;
653 }
654}
655
656fn map_loop_tick(
657 tick: u64,
658 start_tick: u64,
659 duration_ticks: u64,
660 loop_mode: LoopMode,
661 done: &mut bool,
662) -> Option<u64> {
663 if duration_ticks == 0 {
664 *done = true;
665 return None;
666 }
667
668 let elapsed = tick.wrapping_sub(start_tick);
669 match loop_mode {
670 LoopMode::Once => {
671 if elapsed >= duration_ticks {
672 *done = true;
673 None
674 } else {
675 *done = false;
676 Some(elapsed)
677 }
678 }
679 LoopMode::Repeat => {
680 *done = false;
681 Some(elapsed % duration_ticks)
682 }
683 LoopMode::PingPong => {
684 *done = false;
685 let cycle = duration_ticks.saturating_mul(2);
686 if cycle == 0 {
687 return Some(0);
688 }
689 let phase = elapsed % cycle;
690 if phase < duration_ticks {
691 Some(phase)
692 } else {
693 Some(cycle - phase)
694 }
695 }
696 }
697}
698
699pub struct Spring {
725 value: f64,
726 target: f64,
727 velocity: f64,
728 stiffness: f64,
729 damping: f64,
730}
731
732impl Spring {
733 pub fn new(initial: f64, stiffness: f64, damping: f64) -> Self {
738 Self {
739 value: initial,
740 target: initial,
741 velocity: 0.0,
742 stiffness,
743 damping,
744 }
745 }
746
747 pub fn set_target(&mut self, target: f64) {
749 self.target = target;
750 }
751
752 pub fn tick(&mut self) {
756 let displacement = self.target - self.value;
757 let spring_force = displacement * self.stiffness;
758 self.velocity = (self.velocity + spring_force) * self.damping;
759 self.value += self.velocity;
760 }
761
762 pub fn value(&self) -> f64 {
764 self.value
765 }
766
767 pub fn is_settled(&self) -> bool {
772 (self.target - self.value).abs() < 0.01 && self.velocity.abs() < 0.01
773 }
774}
775
776fn clamp01(t: f64) -> f64 {
777 t.clamp(0.0, 1.0)
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783
784 fn assert_endpoints(f: fn(f64) -> f64) {
785 assert_eq!(f(0.0), 0.0);
786 assert_eq!(f(1.0), 1.0);
787 }
788
789 #[test]
790 fn easing_functions_have_expected_endpoints() {
791 let easing_functions: [fn(f64) -> f64; 9] = [
792 ease_linear,
793 ease_in_quad,
794 ease_out_quad,
795 ease_in_out_quad,
796 ease_in_cubic,
797 ease_out_cubic,
798 ease_in_out_cubic,
799 ease_out_elastic,
800 ease_out_bounce,
801 ];
802
803 for easing in easing_functions {
804 assert_endpoints(easing);
805 }
806 }
807
808 #[test]
809 fn tween_returns_start_middle_end_values() {
810 let mut tween = Tween::new(0.0, 10.0, 10);
811 tween.reset(100);
812
813 assert_eq!(tween.value(100), 0.0);
814 assert_eq!(tween.value(105), 5.0);
815 assert_eq!(tween.value(110), 10.0);
816 assert!(tween.is_done());
817 }
818
819 #[test]
820 fn tween_reset_restarts_animation() {
821 let mut tween = Tween::new(0.0, 1.0, 10);
822 tween.reset(0);
823 let _ = tween.value(10);
824 assert!(tween.is_done());
825
826 tween.reset(20);
827 assert!(!tween.is_done());
828 assert_eq!(tween.value(20), 0.0);
829 assert_eq!(tween.value(30), 1.0);
830 assert!(tween.is_done());
831 }
832
833 #[test]
834 fn spring_settles_to_target() {
835 let mut spring = Spring::new(0.0, 0.2, 0.85);
836 spring.set_target(10.0);
837
838 for _ in 0..300 {
839 spring.tick();
840 if spring.is_settled() {
841 break;
842 }
843 }
844
845 assert!(spring.is_settled());
846 assert!((spring.value() - 10.0).abs() < 0.01);
847 }
848
849 #[test]
850 fn lerp_interpolates_values() {
851 assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
852 assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
853 assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
854 }
855
856 #[test]
857 fn keyframes_interpolates_across_multiple_stops() {
858 let mut keyframes = Keyframes::new(100)
859 .stop(0.0, 0.0)
860 .stop(0.3, 100.0)
861 .stop(0.7, 50.0)
862 .stop(1.0, 80.0)
863 .easing(ease_linear);
864
865 keyframes.reset(0);
866 assert_eq!(keyframes.value(0), 0.0);
867 assert_eq!(keyframes.value(15), 50.0);
868 assert_eq!(keyframes.value(30), 100.0);
869 assert_eq!(keyframes.value(50), 75.0);
870 assert_eq!(keyframes.value(70), 50.0);
871 assert_eq!(keyframes.value(85), 65.0);
872 assert_eq!(keyframes.value(100), 80.0);
873 assert!(keyframes.is_done());
874 }
875
876 #[test]
877 fn keyframes_repeat_loop_restarts() {
878 let mut keyframes = Keyframes::new(10)
879 .stop(0.0, 0.0)
880 .stop(1.0, 10.0)
881 .loop_mode(LoopMode::Repeat);
882
883 keyframes.reset(0);
884 assert_eq!(keyframes.value(5), 5.0);
885 assert_eq!(keyframes.value(10), 0.0);
886 assert_eq!(keyframes.value(12), 2.0);
887 assert!(!keyframes.is_done());
888 }
889
890 #[test]
891 fn keyframes_pingpong_reverses_direction() {
892 let mut keyframes = Keyframes::new(10)
893 .stop(0.0, 0.0)
894 .stop(1.0, 10.0)
895 .loop_mode(LoopMode::PingPong);
896
897 keyframes.reset(0);
898 assert_eq!(keyframes.value(8), 8.0);
899 assert_eq!(keyframes.value(10), 10.0);
900 assert_eq!(keyframes.value(12), 8.0);
901 assert_eq!(keyframes.value(15), 5.0);
902 assert!(!keyframes.is_done());
903 }
904
905 #[test]
906 fn sequence_chains_segments_in_order() {
907 let mut sequence = Sequence::new()
908 .then(0.0, 100.0, 30, ease_linear)
909 .then(100.0, 50.0, 20, ease_linear)
910 .then(50.0, 200.0, 40, ease_linear);
911
912 sequence.reset(0);
913 assert_eq!(sequence.value(15), 50.0);
914 assert_eq!(sequence.value(30), 100.0);
915 assert_eq!(sequence.value(40), 75.0);
916 assert_eq!(sequence.value(50), 50.0);
917 assert_eq!(sequence.value(70), 125.0);
918 assert_eq!(sequence.value(90), 200.0);
919 assert!(sequence.is_done());
920 }
921
922 #[test]
923 fn sequence_loop_modes_repeat_and_pingpong_work() {
924 let mut repeat = Sequence::new()
925 .then(0.0, 10.0, 10, ease_linear)
926 .loop_mode(LoopMode::Repeat);
927 repeat.reset(0);
928 assert_eq!(repeat.value(12), 2.0);
929 assert!(!repeat.is_done());
930
931 let mut pingpong = Sequence::new()
932 .then(0.0, 10.0, 10, ease_linear)
933 .loop_mode(LoopMode::PingPong);
934 pingpong.reset(0);
935 assert_eq!(pingpong.value(12), 8.0);
936 assert!(!pingpong.is_done());
937 }
938
939 #[test]
940 fn stagger_applies_per_item_delay() {
941 let mut stagger = Stagger::new(0.0, 100.0, 20).easing(ease_linear).delay(5);
942
943 stagger.reset(0);
944 assert_eq!(stagger.value(4, 3), 0.0);
945 assert_eq!(stagger.value(15, 3), 0.0);
946 assert_eq!(stagger.value(20, 3), 25.0);
947 assert_eq!(stagger.value(35, 3), 100.0);
948 assert!(stagger.is_done());
949 }
950}