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