1use std::cell::{Cell, RefCell};
30use std::collections::{HashMap, VecDeque};
31use std::hash::Hash;
32use std::ops::ControlFlow;
33use std::rc::Rc;
34use std::time::Duration;
35
36use day_spec::{AnimSpec, Color, Point, Rect, Size};
37
38use crate::frame::{FrameClock, FrameHandle};
39
40pub trait Lerp: Clone {
46 fn lerp(&self, to: &Self, t: f64) -> Self;
47}
48
49#[inline]
51pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
52 a + (b - a) * t
53}
54
55impl Lerp for f64 {
56 fn lerp(&self, to: &Self, t: f64) -> Self {
57 lerp(*self, *to, t)
58 }
59}
60
61impl Lerp for f32 {
62 fn lerp(&self, to: &Self, t: f64) -> Self {
63 lerp(*self as f64, *to as f64, t) as f32
64 }
65}
66
67impl Lerp for Point {
68 fn lerp(&self, to: &Self, t: f64) -> Self {
69 Point::new(lerp(self.x, to.x, t), lerp(self.y, to.y, t))
70 }
71}
72
73impl Lerp for Size {
74 fn lerp(&self, to: &Self, t: f64) -> Self {
75 Size::new(
76 lerp(self.width, to.width, t).max(0.0),
77 lerp(self.height, to.height, t).max(0.0),
78 )
79 }
80}
81
82impl Lerp for Rect {
83 fn lerp(&self, to: &Self, t: f64) -> Self {
84 Rect {
85 origin: self.origin.lerp(&to.origin, t),
86 size: self.size.lerp(&to.size, t),
87 }
88 }
89}
90
91impl Lerp for Color {
95 fn lerp(&self, to: &Self, t: f64) -> Self {
96 mix_oklab(*self, *to, t)
97 }
98}
99
100impl<A: Lerp, B: Lerp> Lerp for (A, B) {
101 fn lerp(&self, to: &Self, t: f64) -> Self {
102 (self.0.lerp(&to.0, t), self.1.lerp(&to.1, t))
103 }
104}
105
106impl<A: Lerp, B: Lerp, C: Lerp> Lerp for (A, B, C) {
107 fn lerp(&self, to: &Self, t: f64) -> Self {
108 (
109 self.0.lerp(&to.0, t),
110 self.1.lerp(&to.1, t),
111 self.2.lerp(&to.2, t),
112 )
113 }
114}
115
116impl<T: Lerp, const N: usize> Lerp for [T; N] {
117 fn lerp(&self, to: &Self, t: f64) -> Self {
118 std::array::from_fn(|i| self[i].lerp(&to[i], t))
119 }
120}
121
122impl<T: Lerp> Lerp for Vec<T> {
126 fn lerp(&self, to: &Self, t: f64) -> Self {
127 to.iter()
128 .enumerate()
129 .map(|(i, b)| match self.get(i) {
130 Some(a) => a.lerp(b, t),
131 None => b.clone(),
132 })
133 .collect()
134 }
135}
136
137impl<T: Lerp> Lerp for Option<T> {
140 fn lerp(&self, to: &Self, t: f64) -> Self {
141 match (self, to) {
142 (Some(a), Some(b)) => Some(a.lerp(b, t)),
143 _ if t < 0.5 => self.clone(),
144 _ => to.clone(),
145 }
146 }
147}
148
149fn to_linear(c: f64) -> f64 {
152 if c <= 0.04045 {
153 c / 12.92
154 } else {
155 ((c + 0.055) / 1.055).powf(2.4)
156 }
157}
158
159fn to_srgb(c: f64) -> f64 {
160 if c <= 0.003_130_8 {
161 c * 12.92
162 } else {
163 1.055 * c.powf(1.0 / 2.4) - 0.055
164 }
165}
166
167fn oklab(c: Color) -> [f64; 3] {
168 let (r, g, b) = (to_linear(c.r), to_linear(c.g), to_linear(c.b));
169 let l = (0.412_221_470_8 * r + 0.536_332_536_3 * g + 0.051_445_992_9 * b).cbrt();
170 let m = (0.211_903_498_2 * r + 0.680_699_545_1 * g + 0.107_396_956_6 * b).cbrt();
171 let s = (0.088_302_461_9 * r + 0.281_718_837_6 * g + 0.629_978_700_5 * b).cbrt();
172 [
173 0.210_454_255_3 * l + 0.793_617_785_0 * m - 0.004_072_046_8 * s,
174 1.977_998_495_1 * l - 2.428_592_205_0 * m + 0.450_593_709_9 * s,
175 0.025_904_037_1 * l + 0.782_771_766_2 * m - 0.808_675_766_0 * s,
176 ]
177}
178
179fn from_oklab([l, a, b]: [f64; 3], alpha: f64) -> Color {
180 let l_ = l + 0.396_337_777_4 * a + 0.215_803_757_3 * b;
181 let m_ = l - 0.105_561_345_8 * a - 0.063_854_172_8 * b;
182 let s_ = l - 0.089_484_177_5 * a - 1.291_485_548_0 * b;
183 let (l3, m3, s3) = (l_ * l_ * l_, m_ * m_ * m_, s_ * s_ * s_);
184 let r = 4.076_741_662_1 * l3 - 3.307_711_591_3 * m3 + 0.230_969_929_2 * s3;
185 let g = -1.268_438_004_6 * l3 + 2.609_757_401_1 * m3 - 0.341_319_396_5 * s3;
186 let bl = -0.004_196_086_3 * l3 - 0.703_418_614_7 * m3 + 1.707_614_701_0 * s3;
187 let ch = |v: f64| to_srgb(v.max(0.0)).clamp(0.0, 1.0);
188 Color::rgba(ch(r), ch(g), ch(bl), alpha.clamp(0.0, 1.0))
189}
190
191pub fn mix_oklab(a: Color, b: Color, t: f64) -> Color {
194 if t == 0.0 {
196 return a;
197 }
198 if t == 1.0 {
199 return b;
200 }
201 let (p, q) = (oklab(a), oklab(b));
202 from_oklab(
203 [
204 lerp(p[0], q[0], t),
205 lerp(p[1], q[1], t),
206 lerp(p[2], q[2], t),
207 ],
208 lerp(a.a, b.a, t),
209 )
210}
211
212#[derive(Clone, Copy, Debug, PartialEq)]
216pub struct Sample {
217 pub progress: f64,
220 pub done: bool,
222}
223
224#[derive(Clone, Copy, Debug, PartialEq)]
230pub struct Timing {
231 pub spec: AnimSpec,
232}
233
234impl Timing {
235 pub fn new(spec: AnimSpec) -> Self {
236 Timing { spec }
237 }
238
239 fn cycle(&self) -> f64 {
241 (self.spec.duration_ms as f64 / 1000.0).max(0.0)
242 }
243
244 pub fn total(&self) -> Option<f64> {
246 if self.spec.repeat == u32::MAX {
247 return None;
248 }
249 Some(self.spec.delay_ms as f64 / 1000.0 + self.cycle() * (self.spec.repeat as f64 + 1.0))
250 }
251
252 pub fn sample(&self, elapsed: f64) -> Sample {
254 let t = elapsed - self.spec.delay_ms as f64 / 1000.0;
255 if t < 0.0 || !t.is_finite() {
256 return Sample {
257 progress: 0.0,
258 done: false,
259 };
260 }
261 let one = self.cycle();
262 let reverses = self.spec.autoreverse;
263 let cycles = self.spec.repeat.saturating_add(1);
264 let forever = self.spec.repeat == u32::MAX;
265 if one <= 0.0 {
266 return Sample {
267 progress: 1.0,
268 done: true,
269 };
270 }
271 let index = (t / one).floor();
272 if !forever && index >= cycles as f64 {
273 let back = reverses && cycles.is_multiple_of(2);
275 return Sample {
276 progress: if back { 0.0 } else { 1.0 },
277 done: true,
278 };
279 }
280 let mut local = t - index * one;
281 if reverses && (index as u64) % 2 == 1 {
282 local = one - local;
283 }
284 Sample {
287 progress: self.spec.curve.fraction(local, one),
288 done: false,
289 }
290 }
291}
292
293pub fn stagger(spec: AnimSpec, index: usize, count: usize, spread_ms: u32) -> AnimSpec {
297 let step = if count > 1 {
298 spread_ms as f64 / (count - 1) as f64
299 } else {
300 0.0
301 };
302 AnimSpec {
303 delay_ms: spec.delay_ms + (step * index as f64).round() as u32,
304 ..spec
305 }
306}
307
308pub fn animate(
318 clock: FrameClock,
319 timing: Timing,
320 mut step: impl FnMut(Sample) + 'static,
321) -> FrameHandle {
322 let finish = |timing: &Timing| {
326 (crate::reduce_motion_now() && timing.total().is_some()).then_some(Sample {
327 progress: if timing.spec.duration_ms > 0
328 && timing.spec.autoreverse
329 && timing.spec.repeat % 2 == 1
330 {
331 0.0
332 } else {
333 1.0
334 },
335 done: true,
336 })
337 };
338 let mut start: Option<Duration> = None;
339 clock.subscribe(move |frame| {
340 let t0 = *start.get_or_insert(frame.timestamp);
341 let elapsed = frame.timestamp.saturating_sub(t0).as_secs_f64();
342 let sample = finish(&timing).unwrap_or_else(|| timing.sample(elapsed));
343 step(sample);
344 if sample.done {
345 ControlFlow::Break(())
346 } else {
347 ControlFlow::Continue(())
348 }
349 })
350}
351
352struct Flight<T> {
353 from: T,
354 to: T,
355 handle: Option<FrameHandle>,
356}
357
358pub struct Tweened<T: Lerp + 'static> {
371 shown: day_reactive::Signal<T>,
372 flight: Rc<RefCell<Flight<T>>>,
373 clock: FrameClock,
374 moving: Rc<Cell<bool>>,
375}
376
377impl<T: Lerp + 'static> Clone for Tweened<T> {
378 fn clone(&self) -> Self {
379 Tweened {
380 shown: self.shown,
381 flight: self.flight.clone(),
382 clock: self.clock,
383 moving: self.moving.clone(),
384 }
385 }
386}
387
388impl<T: Lerp + 'static> Tweened<T> {
389 pub fn new(initial: T) -> Self {
391 Self::on(FrameClock::current(), initial)
392 }
393
394 pub fn on(clock: FrameClock, initial: T) -> Self {
396 Tweened {
397 shown: day_reactive::Signal::new(initial.clone()),
398 flight: Rc::new(RefCell::new(Flight {
399 from: initial.clone(),
400 to: initial,
401 handle: None,
402 })),
403 clock,
404 moving: Rc::new(Cell::new(false)),
405 }
406 }
407
408 pub fn get(&self) -> T {
410 self.shown.get()
411 }
412
413 pub fn get_untracked(&self) -> T {
415 self.shown.get_untracked()
416 }
417
418 pub fn target(&self) -> T {
420 self.flight.borrow().to.clone()
421 }
422
423 pub fn is_animating(&self) -> bool {
425 self.moving.get()
426 }
427
428 pub fn set(&self, value: T) {
430 let mut f = self.flight.borrow_mut();
431 if let Some(h) = f.handle.take() {
432 h.cancel();
433 }
434 f.from = value.clone();
435 f.to = value.clone();
436 drop(f);
437 self.moving.set(false);
438 self.shown.set(value);
439 }
440
441 pub fn animate_to(&self, target: T, spec: AnimSpec) {
443 if crate::reduce_motion_now() && spec.repeat != u32::MAX {
446 self.set(target);
447 return;
448 }
449 let from = self.shown.get_untracked();
450 {
451 let mut f = self.flight.borrow_mut();
452 if let Some(h) = f.handle.take() {
453 h.cancel();
454 }
455 f.from = from;
456 f.to = target;
457 }
458 self.moving.set(true);
459 let (flight, shown, moving) = (self.flight.clone(), self.shown, self.moving.clone());
460 let handle = animate(self.clock, Timing::new(spec), move |s| {
461 let v = {
462 let f = flight.borrow();
463 if s.done {
464 f.to.clone()
465 } else {
466 f.from.lerp(&f.to, s.progress)
467 }
468 };
469 shown.set(v);
470 if s.done {
471 moving.set(false);
472 }
473 });
474 self.flight.borrow_mut().handle = Some(handle);
475 }
476}
477
478#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
484pub enum Change {
485 Kept,
487 Entering,
489 Leaving,
492}
493
494#[derive(Clone, Debug, PartialEq)]
496pub struct Pair<T> {
497 pub from: T,
498 pub to: T,
499 pub change: Change,
500}
501
502impl<T: Lerp> Pair<T> {
503 pub fn at(&self, t: f64) -> T {
505 self.from.lerp(&self.to, t)
506 }
507}
508
509#[derive(Clone, Debug, PartialEq)]
537pub struct Pairs<T> {
538 items: Vec<Pair<T>>,
539}
540
541impl<T> Default for Pairs<T> {
542 fn default() -> Self {
543 Pairs { items: Vec::new() }
544 }
545}
546
547impl<T: Clone> Pairs<T> {
548 pub fn by_key<K: Eq + Hash>(
562 from: &[T],
563 to: &[T],
564 mut key: impl FnMut(&T) -> K,
565 mut absent: impl FnMut(&T, Change) -> T,
566 ) -> Self {
567 let mut waiting: HashMap<K, VecDeque<usize>> = HashMap::with_capacity(from.len());
568 for (i, item) in from.iter().enumerate() {
569 waiting.entry(key(item)).or_default().push_back(i);
570 }
571 let mut matched = vec![false; from.len()];
572 let mut items = Vec::with_capacity(from.len().max(to.len()));
573 for item in to {
574 let found = waiting.get_mut(&key(item)).and_then(VecDeque::pop_front);
575 items.push(match found {
576 Some(i) => {
577 matched[i] = true;
578 Pair {
579 from: from[i].clone(),
580 to: item.clone(),
581 change: Change::Kept,
582 }
583 }
584 None => Pair {
585 from: absent(item, Change::Entering),
586 to: item.clone(),
587 change: Change::Entering,
588 },
589 });
590 }
591 for (item, _) in from.iter().zip(&matched).filter(|(_, m)| !**m) {
592 items.push(Pair {
593 from: item.clone(),
594 to: absent(item, Change::Leaving),
595 change: Change::Leaving,
596 });
597 }
598 Pairs { items }
599 }
600}
601
602impl<T> Pairs<T> {
603 pub fn at_with<U>(&self, t: f64, mut f: impl FnMut(&Pair<T>, f64) -> U) -> Vec<U> {
606 self.items.iter().map(|p| f(p, t)).collect()
607 }
608
609 pub fn at_each<U>(
612 &self,
613 mut progress: impl FnMut(usize, &Pair<T>) -> f64,
614 mut f: impl FnMut(&Pair<T>, f64) -> U,
615 ) -> Vec<U> {
616 self.items
617 .iter()
618 .enumerate()
619 .map(|(i, p)| f(p, progress(i, p)))
620 .collect()
621 }
622
623 pub fn settled(&self) -> impl Iterator<Item = &T> {
626 self.items
627 .iter()
628 .filter(|p| p.change != Change::Leaving)
629 .map(|p| &p.to)
630 }
631
632 pub fn counts(&self) -> (usize, usize, usize) {
634 self.items
635 .iter()
636 .fold((0, 0, 0), |(k, e, l), p| match p.change {
637 Change::Kept => (k + 1, e, l),
638 Change::Entering => (k, e + 1, l),
639 Change::Leaving => (k, e, l + 1),
640 })
641 }
642
643 pub fn iter(&self) -> std::slice::Iter<'_, Pair<T>> {
644 self.items.iter()
645 }
646
647 pub fn len(&self) -> usize {
648 self.items.len()
649 }
650
651 pub fn is_empty(&self) -> bool {
652 self.items.is_empty()
653 }
654}
655
656impl<T: Lerp> Pairs<T> {
657 pub fn at(&self, t: f64) -> Vec<T> {
660 self.items.iter().map(|p| p.at(t)).collect()
661 }
662}
663
664impl<T> std::ops::Index<usize> for Pairs<T> {
665 type Output = Pair<T>;
666 fn index(&self, i: usize) -> &Pair<T> {
667 &self.items[i]
668 }
669}
670
671impl<T> FromIterator<Pair<T>> for Pairs<T> {
672 fn from_iter<I: IntoIterator<Item = Pair<T>>>(iter: I) -> Self {
673 Pairs {
674 items: iter.into_iter().collect(),
675 }
676 }
677}
678
679impl<T> IntoIterator for Pairs<T> {
680 type Item = Pair<T>;
681 type IntoIter = std::vec::IntoIter<Pair<T>>;
682 fn into_iter(self) -> Self::IntoIter {
683 self.items.into_iter()
684 }
685}
686
687impl<'a, T> IntoIterator for &'a Pairs<T> {
688 type Item = &'a Pair<T>;
689 type IntoIter = std::slice::Iter<'a, Pair<T>>;
690 fn into_iter(self) -> Self::IntoIter {
691 self.items.iter()
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 fn near(a: f64, b: f64) -> bool {
700 (a - b).abs() < 1e-9
701 }
702
703 #[test]
704 fn linear_timing_runs_its_duration_then_stops() {
705 let t = Timing::new(AnimSpec::linear(200));
706 assert_eq!(t.sample(0.0).progress, 0.0);
707 assert!(near(t.sample(0.1).progress, 0.5));
708 assert!(!t.sample(0.199).done);
709 let end = t.sample(0.2);
710 assert!(end.done && end.progress == 1.0);
711 assert_eq!(t.total(), Some(0.2));
712 }
713
714 #[test]
715 fn delay_holds_the_start_and_repeat_autoreverse_ends_where_it_began() {
716 let t = Timing::new(AnimSpec {
717 delay_ms: 100,
718 repeat: 1,
719 autoreverse: true,
720 ..AnimSpec::linear(100)
721 });
722 assert_eq!(t.sample(0.05).progress, 0.0);
723 assert!(near(t.sample(0.15).progress, 0.5)); assert!(near(t.sample(0.275).progress, 0.25)); let end = t.sample(0.31);
726 assert!(end.done && end.progress == 0.0);
727 assert!(near(t.total().unwrap(), 0.3));
728 let forever = Timing::new(AnimSpec {
729 repeat: u32::MAX,
730 ..AnimSpec::linear(100)
731 });
732 assert!(!forever.sample(1000.0).done);
733 assert_eq!(forever.total(), None);
734 }
735
736 #[test]
737 fn a_bouncy_spring_overshoots_and_ends_exactly_on_target() {
738 let t = Timing::new(AnimSpec::spring(0.5, 0.4));
739 let peak = (1..50)
740 .map(|i| t.sample(i as f64 * 0.01).progress)
741 .fold(0.0, f64::max);
742 assert!(peak > 1.0, "an under-damped spring overshoots: peak {peak}");
743 let end = t.sample(0.5);
744 assert!(end.done && end.progress == 1.0);
745 }
746
747 #[test]
748 fn stagger_spreads_delays_across_the_items() {
749 let s = AnimSpec::linear(300);
750 assert_eq!(stagger(s, 0, 5, 400).delay_ms, 0);
751 assert_eq!(stagger(s, 2, 5, 400).delay_ms, 200);
752 assert_eq!(stagger(s, 4, 5, 400).delay_ms, 400);
753 assert_eq!(stagger(s, 0, 1, 400).delay_ms, 0);
754 }
755
756 #[test]
757 fn tweened_values_ride_the_frame_clock_and_release_it_on_arrival() {
758 use crate::tree::RNode;
759 use day_spec::FrameStamp;
760 use std::collections::VecDeque;
761 let pending: Rc<RefCell<VecDeque<day_spec::FrameCallback>>> = Rc::default();
762 let q = pending.clone();
763 crate::frame::install_frame_requester(move |_, cb| {
764 q.borrow_mut().push_back(cb);
765 Box::new(|| {})
766 });
767 let fire = |ts: f64| {
768 let cb = pending
769 .borrow_mut()
770 .pop_front()
771 .expect("a frame was requested");
772 cb(FrameStamp {
773 timestamp: ts,
774 target_timestamp: None,
775 });
776 };
777 let v = Tweened::on(FrameClock::for_root(RNode::default()), 0.0);
778 v.animate_to(100.0, AnimSpec::linear(100));
779 assert!(v.is_animating());
780 fire(5.0); assert_eq!(v.get_untracked(), 0.0);
782 fire(5.05);
783 assert!(near(v.get_untracked(), 50.0));
784 v.animate_to(0.0, AnimSpec::linear(100));
788 fire(5.06);
789 fire(5.07); assert!(near(v.get_untracked(), 50.0));
791 fire(5.12);
792 assert!(near(v.get_untracked(), 25.0));
793 fire(5.18);
794 assert_eq!(v.get_untracked(), 0.0);
795 assert!(!v.is_animating());
796 assert!(
797 pending.borrow().is_empty(),
798 "no frames requested after arrival"
799 );
800 }
801
802 #[test]
803 fn values_interpolate_and_colors_keep_their_endpoints() {
804 assert!(near(10.0.lerp(&20.0, 0.25), 12.5));
805 let r = Rect::new(0.0, 0.0, 10.0, 10.0).lerp(&Rect::new(10.0, 10.0, 30.0, 10.0), 0.5);
806 assert_eq!(r, Rect::new(5.0, 5.0, 20.0, 10.0));
807 let (blue, yellow) = (Color::rgb(0.0, 0.0, 1.0), Color::rgb(1.0, 1.0, 0.0));
808 let a = blue.lerp(&yellow, 0.0);
809 let b = blue.lerp(&yellow, 1.0);
810 assert_eq!(a, blue);
811 assert_eq!(b, yellow);
812 let mid = blue.lerp(&yellow, 0.5);
814 assert!(mid.r + mid.g + mid.b > 1.6, "{mid:?}");
815 assert_eq!(
816 vec![0.0, 1.0].lerp(&vec![2.0, 3.0, 9.0], 0.5),
817 vec![1.0, 2.0, 9.0]
818 );
819 assert_eq!(Some(1.0).lerp(&None, 0.4), Some(1.0));
820 assert_eq!(Some(1.0).lerp(&None, 0.6), None);
821 }
822
823 #[derive(Clone, Debug, PartialEq)]
827 struct Item {
828 key: &'static str,
829 at: f64,
830 }
831
832 impl Lerp for Item {
833 fn lerp(&self, to: &Self, t: f64) -> Self {
834 Item {
835 key: to.key,
836 at: lerp(self.at, to.at, t),
837 }
838 }
839 }
840
841 fn item(key: &'static str, at: f64) -> Item {
842 Item { key, at }
843 }
844
845 fn from_zero(i: &Item, _: Change) -> Item {
847 item(i.key, 0.0)
848 }
849
850 fn by_key(from: &[Item], to: &[Item]) -> Pairs<Item> {
851 Pairs::by_key(from, to, |i| i.key, from_zero)
852 }
853
854 fn changes(p: &Pairs<Item>) -> Vec<(&'static str, Change)> {
855 p.iter().map(|p| (p.to.key, p.change)).collect()
856 }
857
858 #[test]
859 fn items_are_kept_entering_or_leaving_in_arriving_order_then_leaving() {
860 let p = by_key(
861 &[item("a", 1.0), item("b", 2.0), item("c", 3.0)],
862 &[item("c", 30.0), item("d", 40.0), item("a", 10.0)],
863 );
864 assert_eq!(
865 changes(&p),
866 vec![
867 ("c", Change::Kept),
868 ("d", Change::Entering),
869 ("a", Change::Kept),
870 ("b", Change::Leaving),
871 ]
872 );
873 assert_eq!(p.counts(), (2, 1, 1));
874 assert_eq!((p[0].from.at, p[0].to.at), (3.0, 30.0));
877 assert_eq!((p[1].from.at, p[1].to.at), (0.0, 40.0));
878 assert_eq!((p[2].from.at, p[2].to.at), (1.0, 10.0));
879 assert_eq!((p[3].from.at, p[3].to.at), (2.0, 0.0));
880 }
881
882 #[test]
883 fn the_stand_in_is_asked_for_with_the_item_and_its_side() {
884 let mut asked = Vec::new();
885 let p = Pairs::by_key(
886 &[item("gone", 5.0), item("stays", 1.0)],
887 &[item("stays", 2.0), item("new", 7.0)],
888 |i| i.key,
889 |i, change| {
890 asked.push((i.key, i.at, change));
891 match change {
893 Change::Entering => item(i.key, i.at - 100.0),
894 Change::Leaving => item(i.key, i.at + 100.0),
895 Change::Kept => unreachable!("a kept item has both ends"),
896 }
897 },
898 );
899 assert_eq!(
900 asked,
901 vec![
902 ("new", 7.0, Change::Entering),
903 ("gone", 5.0, Change::Leaving)
904 ]
905 );
906 assert_eq!(p[1].from.at, -93.0);
907 assert_eq!(p[2].to.at, 105.0);
908 }
909
910 #[test]
911 fn repeated_keys_pair_in_order_and_the_surplus_enters_or_leaves() {
912 let p = by_key(
914 &[item("red", 1.0), item("red", 2.0), item("red", 3.0)],
915 &[item("red", 10.0), item("red", 20.0)],
916 );
917 assert_eq!(p.counts(), (2, 0, 1));
918 assert_eq!((p[0].from.at, p[1].from.at), (1.0, 2.0));
919 assert_eq!((p[2].from.at, p[2].change), (3.0, Change::Leaving));
920 let p = by_key(&[item("red", 1.0)], &[item("red", 10.0), item("red", 20.0)]);
922 assert_eq!(p.counts(), (1, 1, 0));
923 assert_eq!((p[0].from.at, p[1].change), (1.0, Change::Entering));
924 }
925
926 #[test]
927 fn an_empty_side_makes_everything_enter_or_leave() {
928 let some = [item("a", 1.0), item("b", 2.0)];
929 let arriving = by_key(&[], &some);
930 assert_eq!(arriving.counts(), (0, 2, 0));
931 assert_eq!(arriving.at(0.0), vec![item("a", 0.0), item("b", 0.0)]);
932 let departing = by_key(&some, &[]);
933 assert_eq!(departing.counts(), (0, 0, 2));
934 assert_eq!(departing.at(1.0), vec![item("a", 0.0), item("b", 0.0)]);
935 assert!(by_key(&[], &[]).is_empty());
936 assert!(Pairs::<Item>::default().is_empty());
937 }
938
939 #[test]
940 fn a_permutation_keeps_everything_and_follows_the_new_order() {
941 let from = [item("a", 0.0), item("b", 1.0), item("c", 2.0)];
942 let to = [item("c", 0.0), item("a", 1.0), item("b", 2.0)];
943 let p = by_key(&from, &to);
944 assert_eq!(p.counts(), (3, 0, 0));
945 assert_eq!(
946 p.at(0.0),
947 vec![item("c", 2.0), item("a", 0.0), item("b", 1.0)]
948 );
949 assert_eq!(p.at(1.0), to.to_vec());
950 }
951
952 #[test]
953 fn sampling_runs_from_the_old_state_to_the_new_one_and_past_it() {
954 let p = by_key(&[item("a", 0.0), item("b", 8.0)], &[item("a", 10.0)]);
955 assert_eq!(p.at(0.0), vec![item("a", 0.0), item("b", 8.0)]);
956 assert_eq!(p.at(0.25), vec![item("a", 2.5), item("b", 6.0)]);
957 assert_eq!(p.at(1.0), vec![item("a", 10.0), item("b", 0.0)]);
959 assert_eq!(p.at(1.1)[0].at, 11.0);
961 assert_eq!(
962 p.settled().cloned().collect::<Vec<_>>(),
963 vec![item("a", 10.0)]
964 );
965 }
966
967 #[test]
968 fn at_with_hands_each_pair_and_t_to_the_caller() {
969 let p = by_key(&[item("a", 0.0)], &[item("a", 4.0), item("b", 2.0)]);
970 let got = p.at_with(0.5, |pair, t| (pair.change, pair.at(t).at));
971 assert_eq!(got, vec![(Change::Kept, 2.0), (Change::Entering, 1.0)]);
972 }
973
974 #[test]
975 fn at_each_gives_every_item_its_own_progress() {
976 let to: Vec<Item> = ["a", "b", "c"].iter().map(|k| item(k, 10.0)).collect();
978 let p = by_key(&[], &to);
979 let elapsed = 0.5;
980 let got = p.at_each(
981 |i, _| ((elapsed - i as f64 * 0.25) / 0.5).clamp(0.0, 1.0),
982 |pair, t| pair.at(t).at,
983 );
984 assert_eq!(got, vec![10.0, 5.0, 0.0]);
985 }
986
987 #[test]
988 fn pairs_collected_directly_may_share_a_destination() {
989 let p: Pairs<Point> = [
991 (Point::new(0.0, 0.0), Point::new(3.0, 0.0)),
992 (Point::new(1.0, 0.0), Point::new(3.0, 0.0)),
993 ]
994 .into_iter()
995 .map(|(from, to)| Pair {
996 from,
997 to,
998 change: Change::Kept,
999 })
1000 .collect();
1001 assert_eq!(p.len(), 2);
1002 let mid = p.at(0.5);
1003 assert_eq!(mid, vec![Point::new(1.5, 0.0), Point::new(2.0, 0.0)]);
1004 assert_eq!(p.at(1.0), vec![Point::new(3.0, 0.0); 2]);
1005 }
1006
1007 #[test]
1008 fn pairs_of_library_types_interpolate_with_their_own_lerp() {
1009 let (red, blue) = (Color::rgb(1.0, 0.0, 0.0), Color::rgb(0.0, 0.0, 1.0));
1010 let from = [(1u8, Rect::new(0.0, 0.0, 10.0, 10.0), red)];
1011 let to = [(1u8, Rect::new(20.0, 0.0, 10.0, 30.0), blue)];
1012 let p = Pairs::by_key(
1013 &from.map(|(_, r, c)| (r, c)),
1014 &to.map(|(_, r, c)| (r, c)),
1015 |_| 1u8,
1016 |x, _| *x,
1017 );
1018 let (rect, color) = p.at(0.5)[0];
1019 assert_eq!(rect, Rect::new(10.0, 0.0, 10.0, 20.0));
1020 assert!(color != red && color != blue);
1021 assert_eq!(p.at(1.0)[0].1, blue);
1022 }
1023
1024 #[test]
1025 fn keys_are_derived_once_per_item() {
1026 let calls = Cell::new(0);
1027 let from = [item("a", 0.0), item("b", 0.0), item("c", 0.0)];
1028 let to = [item("b", 0.0), item("d", 0.0)];
1029 let _ = Pairs::by_key(
1030 &from,
1031 &to,
1032 |i| {
1033 calls.set(calls.get() + 1);
1034 i.key
1035 },
1036 from_zero,
1037 );
1038 assert_eq!(calls.get(), from.len() + to.len());
1039 }
1040
1041 #[test]
1042 fn iteration_and_indexing_see_the_same_pairs() {
1043 let p = by_key(&[item("a", 1.0)], &[item("a", 2.0), item("b", 3.0)]);
1044 let by_ref: Vec<_> = (&p).into_iter().map(|p| p.to.key).collect();
1045 let by_iter: Vec<_> = p.iter().map(|p| p.to.key).collect();
1046 assert_eq!(by_ref, by_iter);
1047 assert_eq!(p[1].to.key, "b");
1048 let owned: Vec<Pair<Item>> = p.clone().into_iter().collect();
1049 assert_eq!(owned.len(), p.len());
1050 assert_eq!(owned[0], p[0]);
1051 }
1052
1053 #[test]
1058 fn every_item_is_accounted_for_exactly_once() {
1059 const KEYS: [&str; 5] = ["a", "b", "c", "d", "e"];
1060 let mut seed = 0x9E37_79B9_7F4A_7C15u64;
1061 let mut next = move |n: usize| {
1062 seed ^= seed << 13;
1063 seed ^= seed >> 7;
1064 seed ^= seed << 17;
1065 (seed % n as u64) as usize
1066 };
1067 for round in 0..200 {
1068 let len_a = next(9);
1069 let len_b = next(9);
1070 let from: Vec<Item> = (0..len_a)
1072 .map(|i| item(KEYS[next(KEYS.len())], i as f64))
1073 .collect();
1074 let to: Vec<Item> = (0..len_b)
1075 .map(|i| item(KEYS[next(KEYS.len())], 100.0 + i as f64))
1076 .collect();
1077 let p = Pairs::by_key(
1078 &from,
1079 &to,
1080 |i| i.key,
1081 |i, c| match c {
1082 Change::Entering => item(i.key, -1.0),
1083 _ => item(i.key, -2.0),
1084 },
1085 );
1086 let arriving: Vec<&Item> = p.settled().collect();
1087 assert_eq!(arriving, to.iter().collect::<Vec<_>>(), "round {round}");
1088 let mut starts: Vec<f64> = p
1089 .iter()
1090 .filter(|p| p.change != Change::Entering)
1091 .map(|p| p.from.at)
1092 .collect();
1093 starts.sort_by(f64::total_cmp);
1094 let all: Vec<f64> = from.iter().map(|i| i.at).collect();
1095 assert_eq!(starts, all, "round {round}");
1096 let kept = KEYS
1097 .iter()
1098 .map(|k| {
1099 let a = from.iter().filter(|i| i.key == *k).count();
1100 let b = to.iter().filter(|i| i.key == *k).count();
1101 a.min(b)
1102 })
1103 .sum::<usize>();
1104 let (k, e, l) = p.counts();
1105 assert_eq!(
1106 (k, e, l),
1107 (kept, len_b - kept, len_a - kept),
1108 "round {round}"
1109 );
1110 for pair in &p {
1111 match pair.change {
1112 Change::Kept => assert_eq!(pair.from.key, pair.to.key),
1113 Change::Entering => assert_eq!(pair.from.at, -1.0),
1114 Change::Leaving => assert_eq!(pair.to.at, -2.0),
1115 }
1116 }
1117 }
1118 }
1119}