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 on_complete: Option<Box<dyn FnMut()>>,
126}
127
128impl Tween {
129 pub fn new(from: f64, to: f64, duration_ticks: u64) -> Self {
135 Self {
136 from,
137 to,
138 duration_ticks,
139 start_tick: 0,
140 easing: ease_linear,
141 done: false,
142 on_complete: None,
143 }
144 }
145
146 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
151 self.easing = f;
152 self
153 }
154
155 pub fn on_complete(mut self, f: impl FnMut() + 'static) -> Self {
157 self.on_complete = Some(Box::new(f));
158 self
159 }
160
161 pub fn value(&mut self, tick: u64) -> f64 {
166 let was_done = self.done;
167 if self.done {
168 return self.to;
169 }
170
171 if self.duration_ticks == 0 {
172 self.done = true;
173 if !was_done && self.done {
174 if let Some(cb) = &mut self.on_complete {
175 cb();
176 }
177 }
178 return self.to;
179 }
180
181 let elapsed = tick.wrapping_sub(self.start_tick);
182 if elapsed >= self.duration_ticks {
183 self.done = true;
184 if !was_done && self.done {
185 if let Some(cb) = &mut self.on_complete {
186 cb();
187 }
188 }
189 return self.to;
190 }
191
192 if self.duration_ticks == 0 {
193 return self.to;
194 }
195 let progress = elapsed as f64 / self.duration_ticks as f64;
196 let eased = (self.easing)(clamp01(progress));
197 lerp(self.from, self.to, eased)
198 }
199
200 pub fn is_done(&self) -> bool {
202 self.done
203 }
204
205 pub fn reset(&mut self, tick: u64) {
207 self.start_tick = tick;
208 self.done = false;
209 }
210}
211
212#[non_exhaustive]
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum LoopMode {
216 Once,
218 Repeat,
220 PingPong,
222}
223
224#[derive(Clone, Copy)]
225struct KeyframeStop {
226 position: f64,
227 value: f64,
228}
229
230pub struct Keyframes {
256 duration_ticks: u64,
257 start_tick: u64,
258 stops: Vec<KeyframeStop>,
259 default_easing: fn(f64) -> f64,
260 segment_easing: Vec<fn(f64) -> f64>,
261 loop_mode: LoopMode,
262 done: bool,
263 on_complete: Option<Box<dyn FnMut()>>,
264}
265
266impl Keyframes {
267 pub fn new(duration_ticks: u64) -> Self {
273 Self {
274 duration_ticks,
275 start_tick: 0,
276 stops: Vec::new(),
277 default_easing: ease_linear,
278 segment_easing: Vec::new(),
279 loop_mode: LoopMode::Once,
280 done: false,
281 on_complete: None,
282 }
283 }
284
285 pub fn stop(mut self, position: f64, value: f64) -> Self {
289 self.stops.push(KeyframeStop {
290 position: clamp01(position),
291 value,
292 });
293 if self.stops.len() >= 2 {
294 self.segment_easing.push(self.default_easing);
295 }
296 self.stops.sort_by(|a, b| a.position.total_cmp(&b.position));
297 self
298 }
299
300 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
305 self.default_easing = f;
306 self.segment_easing.fill(f);
307 self
308 }
309
310 pub fn segment_easing(mut self, segment_index: usize, f: fn(f64) -> f64) -> Self {
315 if let Some(slot) = self.segment_easing.get_mut(segment_index) {
316 *slot = f;
317 }
318 self
319 }
320
321 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
323 self.loop_mode = mode;
324 self
325 }
326
327 pub fn on_complete(mut self, f: impl FnMut() + 'static) -> Self {
329 self.on_complete = Some(Box::new(f));
330 self
331 }
332
333 pub fn value(&mut self, tick: u64) -> f64 {
335 let was_done = self.done;
336 if self.stops.is_empty() {
337 self.done = true;
338 if !was_done && self.done {
339 if let Some(cb) = &mut self.on_complete {
340 cb();
341 }
342 }
343 return 0.0;
344 }
345 if self.stops.len() == 1 {
346 self.done = true;
347 if !was_done && self.done {
348 if let Some(cb) = &mut self.on_complete {
349 cb();
350 }
351 }
352 return self.stops[0].value;
353 }
354
355 let stops = &self.stops;
356
357 let end_value = stops.last().map_or(0.0, |s| s.value);
358 let loop_tick = match map_loop_tick(
359 tick,
360 self.start_tick,
361 self.duration_ticks,
362 self.loop_mode,
363 &mut self.done,
364 ) {
365 Some(v) => v,
366 None => {
367 if !was_done && self.done {
368 if let Some(cb) = &mut self.on_complete {
369 cb();
370 }
371 }
372 return end_value;
373 }
374 };
375
376 if self.duration_ticks == 0 {
377 return stops.last().map_or(0.0, |s| s.value);
378 }
379 let progress = loop_tick as f64 / self.duration_ticks as f64;
380
381 if progress <= stops[0].position {
382 return stops[0].value;
383 }
384 if progress >= 1.0 {
385 return end_value;
386 }
387
388 for i in 0..(stops.len() - 1) {
389 let a = stops[i];
390 let b = stops[i + 1];
391 if progress <= b.position {
392 let span = b.position - a.position;
393 if span <= f64::EPSILON {
394 return b.value;
395 }
396 let local = clamp01((progress - a.position) / span);
397 let easing = self
398 .segment_easing
399 .get(i)
400 .copied()
401 .unwrap_or(self.default_easing);
402 let eased = easing(local);
403 return lerp(a.value, b.value, eased);
404 }
405 }
406
407 end_value
408 }
409
410 pub fn is_done(&self) -> bool {
412 self.done
413 }
414
415 pub fn reset(&mut self, tick: u64) {
417 self.start_tick = tick;
418 self.done = false;
419 }
420}
421
422#[derive(Clone, Copy)]
423struct SequenceSegment {
424 from: f64,
425 to: f64,
426 duration_ticks: u64,
427 easing: fn(f64) -> f64,
428}
429
430pub struct Sequence {
449 segments: Vec<SequenceSegment>,
450 loop_mode: LoopMode,
451 start_tick: u64,
452 done: bool,
453 on_complete: Option<Box<dyn FnMut()>>,
454}
455
456impl Default for Sequence {
457 fn default() -> Self {
458 Self::new()
459 }
460}
461
462impl Sequence {
463 pub fn new() -> Self {
468 Self {
469 segments: Vec::new(),
470 loop_mode: LoopMode::Once,
471 start_tick: 0,
472 done: false,
473 on_complete: None,
474 }
475 }
476
477 pub fn then(mut self, from: f64, to: f64, duration_ticks: u64, easing: fn(f64) -> f64) -> Self {
479 self.segments.push(SequenceSegment {
480 from,
481 to,
482 duration_ticks,
483 easing,
484 });
485 self
486 }
487
488 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
490 self.loop_mode = mode;
491 self
492 }
493
494 pub fn on_complete(mut self, f: impl FnMut() + 'static) -> Self {
496 self.on_complete = Some(Box::new(f));
497 self
498 }
499
500 pub fn value(&mut self, tick: u64) -> f64 {
502 let was_done = self.done;
503 if self.segments.is_empty() {
504 self.done = true;
505 if !was_done && self.done {
506 if let Some(cb) = &mut self.on_complete {
507 cb();
508 }
509 }
510 return 0.0;
511 }
512
513 let total_duration = self
514 .segments
515 .iter()
516 .fold(0_u64, |acc, s| acc.saturating_add(s.duration_ticks));
517 let end_value = self.segments.last().map_or(0.0, |s| s.to);
518
519 let loop_tick = match map_loop_tick(
520 tick,
521 self.start_tick,
522 total_duration,
523 self.loop_mode,
524 &mut self.done,
525 ) {
526 Some(v) => v,
527 None => {
528 if !was_done && self.done {
529 if let Some(cb) = &mut self.on_complete {
530 cb();
531 }
532 }
533 return end_value;
534 }
535 };
536
537 let mut remaining = loop_tick;
538 for segment in &self.segments {
539 if segment.duration_ticks == 0 {
540 continue;
541 }
542 if remaining < segment.duration_ticks {
543 let progress = remaining as f64 / segment.duration_ticks as f64;
544 let eased = (segment.easing)(clamp01(progress));
545 return lerp(segment.from, segment.to, eased);
546 }
547 remaining -= segment.duration_ticks;
548 }
549
550 end_value
551 }
552
553 pub fn is_done(&self) -> bool {
555 self.done
556 }
557
558 pub fn reset(&mut self, tick: u64) {
560 self.start_tick = tick;
561 self.done = false;
562 }
563}
564
565pub struct Stagger {
589 from: f64,
590 to: f64,
591 duration_ticks: u64,
592 start_tick: u64,
593 delay_ticks: u64,
594 easing: fn(f64) -> f64,
595 loop_mode: LoopMode,
596 item_count: usize,
597 done: bool,
598 on_complete: Option<Box<dyn FnMut()>>,
599}
600
601impl Stagger {
602 pub fn new(from: f64, to: f64, duration_ticks: u64) -> Self {
606 Self {
607 from,
608 to,
609 duration_ticks,
610 start_tick: 0,
611 delay_ticks: 0,
612 easing: ease_linear,
613 loop_mode: LoopMode::Once,
614 item_count: 0,
615 done: false,
616 on_complete: None,
617 }
618 }
619
620 pub fn easing(mut self, f: fn(f64) -> f64) -> Self {
622 self.easing = f;
623 self
624 }
625
626 pub fn delay(mut self, ticks: u64) -> Self {
628 self.delay_ticks = ticks;
629 self
630 }
631
632 pub fn loop_mode(mut self, mode: LoopMode) -> Self {
635 self.loop_mode = mode;
636 self
637 }
638
639 pub fn on_complete(mut self, f: impl FnMut() + 'static) -> Self {
641 self.on_complete = Some(Box::new(f));
642 self
643 }
644
645 pub fn items(mut self, count: usize) -> Self {
651 self.item_count = count;
652 self
653 }
654
655 pub fn value(&mut self, tick: u64, item_index: usize) -> f64 {
657 let was_done = self.done;
658 if item_index >= self.item_count {
659 self.item_count = item_index + 1;
660 }
661
662 let total_cycle = self.total_cycle_ticks();
663
664 let effective_tick = if self.loop_mode == LoopMode::Once {
665 tick
666 } else {
667 let elapsed = tick.wrapping_sub(self.start_tick);
668 let mapped = match self.loop_mode {
669 LoopMode::Repeat => {
670 if total_cycle == 0 {
671 0
672 } else {
673 elapsed % total_cycle
674 }
675 }
676 LoopMode::PingPong => {
677 if total_cycle == 0 {
678 0
679 } else {
680 let full = total_cycle.saturating_mul(2);
681 let phase = elapsed % full;
682 if phase < total_cycle {
683 phase
684 } else {
685 full - phase
686 }
687 }
688 }
689 LoopMode::Once => unreachable!(),
690 };
691 self.start_tick.wrapping_add(mapped)
692 };
693
694 let delay = self.delay_ticks.wrapping_mul(item_index as u64);
695 let item_start = self.start_tick.wrapping_add(delay);
696
697 if effective_tick < item_start {
698 self.done = false;
699 return self.from;
700 }
701
702 if self.duration_ticks == 0 {
703 self.done = true;
704 if !was_done && self.done {
705 if let Some(cb) = &mut self.on_complete {
706 cb();
707 }
708 }
709 return self.to;
710 }
711
712 let elapsed = effective_tick - item_start;
713 if elapsed >= self.duration_ticks {
714 self.done = true;
715 if !was_done && self.done {
716 if let Some(cb) = &mut self.on_complete {
717 cb();
718 }
719 }
720 return self.to;
721 }
722
723 self.done = false;
724 if self.duration_ticks == 0 {
725 return self.to;
726 }
727 let progress = elapsed as f64 / self.duration_ticks as f64;
728 let eased = (self.easing)(clamp01(progress));
729 lerp(self.from, self.to, eased)
730 }
731
732 fn total_cycle_ticks(&self) -> u64 {
733 let max_delay = self
734 .delay_ticks
735 .wrapping_mul(self.item_count.saturating_sub(1) as u64);
736 self.duration_ticks.saturating_add(max_delay)
737 }
738
739 pub fn is_done(&self) -> bool {
741 self.done
742 }
743
744 pub fn reset(&mut self, tick: u64) {
746 self.start_tick = tick;
747 self.done = false;
748 }
749}
750
751fn map_loop_tick(
752 tick: u64,
753 start_tick: u64,
754 duration_ticks: u64,
755 loop_mode: LoopMode,
756 done: &mut bool,
757) -> Option<u64> {
758 if duration_ticks == 0 {
759 *done = true;
760 return None;
761 }
762
763 let elapsed = tick.wrapping_sub(start_tick);
764 match loop_mode {
765 LoopMode::Once => {
766 if elapsed >= duration_ticks {
767 *done = true;
768 None
769 } else {
770 *done = false;
771 Some(elapsed)
772 }
773 }
774 LoopMode::Repeat => {
775 *done = false;
776 Some(elapsed % duration_ticks)
777 }
778 LoopMode::PingPong => {
779 *done = false;
780 let cycle = duration_ticks.saturating_mul(2);
781 if cycle == 0 {
782 return Some(0);
783 }
784 let phase = elapsed % cycle;
785 if phase < duration_ticks {
786 Some(phase)
787 } else {
788 Some(cycle - phase)
789 }
790 }
791 }
792}
793
794pub struct Spring {
820 value: f64,
821 target: f64,
822 velocity: f64,
823 stiffness: f64,
824 damping: f64,
825 settled: bool,
826 on_settle: Option<Box<dyn FnMut()>>,
827}
828
829impl Spring {
830 pub fn new(initial: f64, stiffness: f64, damping: f64) -> Self {
835 Self {
836 value: initial,
837 target: initial,
838 velocity: 0.0,
839 stiffness,
840 damping,
841 settled: true,
842 on_settle: None,
843 }
844 }
845
846 pub fn on_settle(mut self, f: impl FnMut() + 'static) -> Self {
848 self.on_settle = Some(Box::new(f));
849 self
850 }
851
852 pub fn set_target(&mut self, target: f64) {
854 self.target = target;
855 self.settled = self.is_settled();
856 }
857
858 pub fn tick(&mut self) {
862 let displacement = self.target - self.value;
863 let spring_force = displacement * self.stiffness;
864 self.velocity = (self.velocity + spring_force) * self.damping;
865 self.value += self.velocity;
866
867 let is_settled = self.is_settled();
868 if !self.settled && is_settled {
869 self.settled = true;
870 if let Some(cb) = &mut self.on_settle {
871 cb();
872 }
873 }
874 }
875
876 pub fn value(&self) -> f64 {
878 self.value
879 }
880
881 pub fn is_settled(&self) -> bool {
886 (self.target - self.value).abs() < 0.01 && self.velocity.abs() < 0.01
887 }
888}
889
890fn clamp01(t: f64) -> f64 {
891 t.clamp(0.0, 1.0)
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use std::cell::Cell;
898 use std::rc::Rc;
899
900 fn assert_endpoints(f: fn(f64) -> f64) {
901 assert_eq!(f(0.0), 0.0);
902 assert_eq!(f(1.0), 1.0);
903 }
904
905 #[test]
906 fn easing_functions_have_expected_endpoints() {
907 let easing_functions: [fn(f64) -> f64; 9] = [
908 ease_linear,
909 ease_in_quad,
910 ease_out_quad,
911 ease_in_out_quad,
912 ease_in_cubic,
913 ease_out_cubic,
914 ease_in_out_cubic,
915 ease_out_elastic,
916 ease_out_bounce,
917 ];
918
919 for easing in easing_functions {
920 assert_endpoints(easing);
921 }
922 }
923
924 #[test]
925 fn tween_returns_start_middle_end_values() {
926 let mut tween = Tween::new(0.0, 10.0, 10);
927 tween.reset(100);
928
929 assert_eq!(tween.value(100), 0.0);
930 assert_eq!(tween.value(105), 5.0);
931 assert_eq!(tween.value(110), 10.0);
932 assert!(tween.is_done());
933 }
934
935 #[test]
936 fn tween_reset_restarts_animation() {
937 let mut tween = Tween::new(0.0, 1.0, 10);
938 tween.reset(0);
939 let _ = tween.value(10);
940 assert!(tween.is_done());
941
942 tween.reset(20);
943 assert!(!tween.is_done());
944 assert_eq!(tween.value(20), 0.0);
945 assert_eq!(tween.value(30), 1.0);
946 assert!(tween.is_done());
947 }
948
949 #[test]
950 fn tween_on_complete_fires_once() {
951 let count = Rc::new(Cell::new(0));
952 let callback_count = Rc::clone(&count);
953 let mut tween = Tween::new(0.0, 10.0, 10).on_complete(move || {
954 callback_count.set(callback_count.get() + 1);
955 });
956
957 tween.reset(0);
958 assert_eq!(count.get(), 0);
959
960 assert_eq!(tween.value(5), 5.0);
961 assert_eq!(count.get(), 0);
962
963 assert_eq!(tween.value(10), 10.0);
964 assert_eq!(count.get(), 1);
965
966 assert_eq!(tween.value(11), 10.0);
967 assert_eq!(count.get(), 1);
968 }
969
970 #[test]
971 fn spring_settles_to_target() {
972 let mut spring = Spring::new(0.0, 0.2, 0.85);
973 spring.set_target(10.0);
974
975 for _ in 0..300 {
976 spring.tick();
977 if spring.is_settled() {
978 break;
979 }
980 }
981
982 assert!(spring.is_settled());
983 assert!((spring.value() - 10.0).abs() < 0.01);
984 }
985
986 #[test]
987 fn spring_on_settle_fires_once() {
988 let count = Rc::new(Cell::new(0));
989 let callback_count = Rc::clone(&count);
990 let mut spring = Spring::new(0.0, 0.2, 0.85).on_settle(move || {
991 callback_count.set(callback_count.get() + 1);
992 });
993 spring.set_target(10.0);
994
995 for _ in 0..500 {
996 spring.tick();
997 if spring.is_settled() {
998 break;
999 }
1000 }
1001
1002 assert!(spring.is_settled());
1003 assert_eq!(count.get(), 1);
1004
1005 for _ in 0..50 {
1006 spring.tick();
1007 }
1008
1009 assert_eq!(count.get(), 1);
1010 }
1011
1012 #[test]
1013 fn lerp_interpolates_values() {
1014 assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
1015 assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
1016 assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
1017 }
1018
1019 #[test]
1020 fn keyframes_interpolates_across_multiple_stops() {
1021 let mut keyframes = Keyframes::new(100)
1022 .stop(0.0, 0.0)
1023 .stop(0.3, 100.0)
1024 .stop(0.7, 50.0)
1025 .stop(1.0, 80.0)
1026 .easing(ease_linear);
1027
1028 keyframes.reset(0);
1029 assert_eq!(keyframes.value(0), 0.0);
1030 assert_eq!(keyframes.value(15), 50.0);
1031 assert_eq!(keyframes.value(30), 100.0);
1032 assert_eq!(keyframes.value(50), 75.0);
1033 assert_eq!(keyframes.value(70), 50.0);
1034 assert_eq!(keyframes.value(85), 65.0);
1035 assert_eq!(keyframes.value(100), 80.0);
1036 assert!(keyframes.is_done());
1037 }
1038
1039 #[test]
1040 fn keyframes_repeat_loop_restarts() {
1041 let mut keyframes = Keyframes::new(10)
1042 .stop(0.0, 0.0)
1043 .stop(1.0, 10.0)
1044 .loop_mode(LoopMode::Repeat);
1045
1046 keyframes.reset(0);
1047 assert_eq!(keyframes.value(5), 5.0);
1048 assert_eq!(keyframes.value(10), 0.0);
1049 assert_eq!(keyframes.value(12), 2.0);
1050 assert!(!keyframes.is_done());
1051 }
1052
1053 #[test]
1054 fn keyframes_pingpong_reverses_direction() {
1055 let mut keyframes = Keyframes::new(10)
1056 .stop(0.0, 0.0)
1057 .stop(1.0, 10.0)
1058 .loop_mode(LoopMode::PingPong);
1059
1060 keyframes.reset(0);
1061 assert_eq!(keyframes.value(8), 8.0);
1062 assert_eq!(keyframes.value(10), 10.0);
1063 assert_eq!(keyframes.value(12), 8.0);
1064 assert_eq!(keyframes.value(15), 5.0);
1065 assert!(!keyframes.is_done());
1066 }
1067
1068 #[test]
1069 fn sequence_chains_segments_in_order() {
1070 let mut sequence = Sequence::new()
1071 .then(0.0, 100.0, 30, ease_linear)
1072 .then(100.0, 50.0, 20, ease_linear)
1073 .then(50.0, 200.0, 40, ease_linear);
1074
1075 sequence.reset(0);
1076 assert_eq!(sequence.value(15), 50.0);
1077 assert_eq!(sequence.value(30), 100.0);
1078 assert_eq!(sequence.value(40), 75.0);
1079 assert_eq!(sequence.value(50), 50.0);
1080 assert_eq!(sequence.value(70), 125.0);
1081 assert_eq!(sequence.value(90), 200.0);
1082 assert!(sequence.is_done());
1083 }
1084
1085 #[test]
1086 fn sequence_loop_modes_repeat_and_pingpong_work() {
1087 let mut repeat = Sequence::new()
1088 .then(0.0, 10.0, 10, ease_linear)
1089 .loop_mode(LoopMode::Repeat);
1090 repeat.reset(0);
1091 assert_eq!(repeat.value(12), 2.0);
1092 assert!(!repeat.is_done());
1093
1094 let mut pingpong = Sequence::new()
1095 .then(0.0, 10.0, 10, ease_linear)
1096 .loop_mode(LoopMode::PingPong);
1097 pingpong.reset(0);
1098 assert_eq!(pingpong.value(12), 8.0);
1099 assert!(!pingpong.is_done());
1100 }
1101
1102 #[test]
1103 fn stagger_applies_per_item_delay() {
1104 let mut stagger = Stagger::new(0.0, 100.0, 20).easing(ease_linear).delay(5);
1105
1106 stagger.reset(0);
1107 assert_eq!(stagger.value(4, 3), 0.0);
1108 assert_eq!(stagger.value(15, 3), 0.0);
1109 assert_eq!(stagger.value(20, 3), 25.0);
1110 assert_eq!(stagger.value(35, 3), 100.0);
1111 assert!(stagger.is_done());
1112 }
1113}