1use super::*;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
11#[serde(crate = "crate::serde")]
12pub enum Duration {
13 Frames(Numeric),
15 Milliseconds(Numeric),
17 Seconds(Numeric),
19}
20
21impl Default for Duration {
22 fn default() -> Self {
23 Self::Frames(Numeric::I64(0))
24 }
25}
26
27impl Display for Duration {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Duration::Frames(value) => write!(f, "{}f", value),
31 Duration::Milliseconds(value) => write!(f, "{}ms", value),
32 Duration::Seconds(value) => write!(f, "{}s", value),
33 }
34 }
35}
36
37impl Interpolatable for Duration {
38 fn interpolate(&self, other: &Self, t: f64) -> Self {
39 match (self, other) {
40 (Duration::Frames(start), Duration::Frames(end)) => {
41 Duration::Frames(start.interpolate(end, t))
42 }
43 (Duration::Milliseconds(start), Duration::Milliseconds(end)) => {
44 Duration::Milliseconds(start.interpolate(end, t))
45 }
46 (Duration::Seconds(start), Duration::Seconds(end)) => {
47 Duration::Seconds(start.interpolate(end, t))
48 }
49 _ => {
50 let start_ms = self.as_milliseconds_f64();
51 let end_ms = other.as_milliseconds_f64();
52 Duration::Milliseconds(Numeric::F64(start_ms + (end_ms - start_ms) * t))
53 }
54 }
55 }
56}
57
58impl Duration {
59 pub const NOMINAL_FRAME_MILLIS: f64 = 1000.0 / 60.0;
60
61 pub fn raw_value(&self) -> f64 {
63 match self {
64 Duration::Frames(value) | Duration::Milliseconds(value) | Duration::Seconds(value) => {
65 value.to_float()
66 }
67 }
68 }
69
70 pub fn is_frame_based(&self) -> bool {
72 matches!(self, Duration::Frames(_))
73 }
74
75 pub fn as_milliseconds_f64(&self) -> f64 {
78 match self {
79 Duration::Frames(value) => value.to_float() * Self::NOMINAL_FRAME_MILLIS,
80 Duration::Milliseconds(value) => value.to_float(),
81 Duration::Seconds(value) => value.to_float() * 1000.0,
82 }
83 }
84
85 pub fn as_frames_f64(&self) -> f64 {
88 match self {
89 Duration::Frames(value) => value.to_float(),
90 Duration::Milliseconds(value) => value.to_float() / Self::NOMINAL_FRAME_MILLIS,
91 Duration::Seconds(value) => value.to_float() * 1000.0 / Self::NOMINAL_FRAME_MILLIS,
92 }
93 }
94}
95
96macro_rules! impl_duration_from_number {
97 ($($type:ty),* $(,)?) => {
98 $(
99 impl From<$type> for Duration {
100 fn from(value: $type) -> Self {
101 Duration::Frames(Numeric::from(value))
102 }
103 }
104 )*
105 };
106}
107
108impl_duration_from_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
109
110pub struct TransitionQueueEntry<T> {
115 pub duration: Duration,
117 pub curve: EasingCurve,
119 pub ending_value: T,
121}
122
123#[cfg(debug_assertions)]
124impl<T> std::fmt::Debug for TransitionQueueEntry<T> {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("TransitionQueueEntry")
127 .field("duration", &self.duration)
128 .finish()
130 }
131}
132
133impl<T: Interpolatable> Interpolatable for Option<T> {
134 fn interpolate(&self, other: &Self, t: f64) -> Self {
135 match &self {
136 Self::Some(s) => match other {
137 Self::Some(o) => Some(s.interpolate(o, t)),
138 _ => None,
139 },
140 Self::None => None,
141 }
142 }
143}
144
145pub struct TransitionManager<T> {
147 queue: VecDeque<TransitionQueueEntry<T>>,
148 transition_checkpoint_value: T,
150 origin_frames_elapsed: u64,
152 origin_millis_elapsed: u64,
154}
155
156#[cfg(debug_assertions)]
157impl<T> std::fmt::Debug for TransitionManager<T> {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 f.debug_struct("TransitionManager")
160 .field("queue", &self.queue)
161 .finish()
163 }
164}
165
166impl<T: Interpolatable> TransitionManager<T> {
167 pub fn new(value: T, current_frames: u64, current_millis: u64) -> Self {
169 Self {
170 queue: VecDeque::new(),
171 transition_checkpoint_value: value,
172 origin_frames_elapsed: current_frames,
173 origin_millis_elapsed: current_millis,
174 }
175 }
176
177 pub fn push_transition(&mut self, transition: TransitionQueueEntry<T>) {
179 self.queue.push_back(transition);
180 }
181
182 pub fn reset_transitions(&mut self, current_frames: u64, current_millis: u64) {
184 let eased_value = self.compute_eased_value(current_frames, current_millis);
185 self.transition_checkpoint_value =
186 eased_value.unwrap_or(self.transition_checkpoint_value.clone());
187 self.queue.clear();
188 self.origin_frames_elapsed = current_frames;
189 self.origin_millis_elapsed = current_millis;
190 }
191
192 pub fn compute_eased_value(&mut self, frames_elapsed: u64, millis_elapsed: u64) -> Option<T> {
194 let global_frames = frames_elapsed;
195 let global_millis = millis_elapsed;
196 let mut completed_transition = false;
197
198 while let Some(current_transition) = self.queue.front() {
201 let elapsed = transition_elapsed(
202 current_transition.duration,
203 global_frames,
204 global_millis,
205 self.origin_frames_elapsed,
206 self.origin_millis_elapsed,
207 );
208 let duration = transition_duration_units(current_transition.duration);
209 if elapsed < duration {
210 break;
211 }
212 let curr = self.queue.pop_front()?;
213 advance_transition_origin(
214 curr.duration,
215 &mut self.origin_frames_elapsed,
216 &mut self.origin_millis_elapsed,
217 );
218 self.transition_checkpoint_value = curr.ending_value;
219 completed_transition = true;
220 }
221
222 let current_transition = match self.queue.front() {
223 Some(current_transition) => current_transition,
224 None => {
225 return if completed_transition {
226 Some(self.transition_checkpoint_value.clone())
227 } else {
228 None
229 };
230 }
231 };
232
233 let elapsed = transition_elapsed(
234 current_transition.duration,
235 global_frames,
236 global_millis,
237 self.origin_frames_elapsed,
238 self.origin_millis_elapsed,
239 );
240 let duration = transition_duration_units(current_transition.duration);
241 let progress = if duration <= f64::EPSILON {
242 1.0
243 } else {
244 (elapsed / duration).clamp(0.0, 1.0)
245 };
246 let interpolated_val = current_transition.curve.interpolate(
247 &self.transition_checkpoint_value,
248 ¤t_transition.ending_value,
249 progress,
250 );
251 Some(interpolated_val)
252 }
253}
254
255fn transition_duration_units(duration: Duration) -> f64 {
256 match duration {
257 Duration::Frames(value) | Duration::Milliseconds(value) => value.to_float().max(0.0),
258 Duration::Seconds(value) => (value.to_float() * 1000.0).max(0.0),
259 }
260}
261
262fn transition_elapsed(
263 duration: Duration,
264 global_frames: u64,
265 global_millis: u64,
266 origin_frames: u64,
267 origin_millis: u64,
268) -> f64 {
269 match duration {
270 Duration::Frames(_) => global_frames.saturating_sub(origin_frames) as f64,
271 Duration::Milliseconds(_) | Duration::Seconds(_) => {
272 global_millis.saturating_sub(origin_millis) as f64
273 }
274 }
275}
276
277fn advance_transition_origin(duration: Duration, origin_frames: &mut u64, origin_millis: &mut u64) {
278 match duration {
279 Duration::Frames(value) => {
280 *origin_frames = origin_frames.saturating_add(value.to_float().max(0.0).round() as u64)
281 }
282 Duration::Milliseconds(value) => {
283 *origin_millis = origin_millis.saturating_add(value.to_float().max(0.0).round() as u64)
284 }
285 Duration::Seconds(value) => {
286 *origin_millis =
287 origin_millis.saturating_add((value.to_float().max(0.0) * 1000.0).round() as u64)
288 }
289 }
290}
291
292pub enum EasingCurve {
295 Linear,
297 Hold,
299 InQuad,
301 OutQuad,
303 InOutQuad,
305 InBack,
307 OutBack,
309 InOutBack,
311 Custom(Box<dyn Fn(f64) -> f64>),
313}
314
315struct EasingEvaluators {}
316impl EasingEvaluators {
317 fn linear(t: f64) -> f64 {
318 t
319 }
320 #[allow(dead_code)]
321 fn none(t: f64) -> f64 {
322 if t == 1.0 {
323 1.0
324 } else {
325 0.0
326 }
327 }
328 fn in_quad(t: f64) -> f64 {
329 t * t
330 }
331 fn out_quad(t: f64) -> f64 {
332 1.0 - (1.0 - t) * (1.0 - t)
333 }
334 fn in_out_quad(t: f64) -> f64 {
335 if t < 0.5 {
336 2.0 * t * t
337 } else {
338 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
339 }
340 }
341 fn in_back(t: f64) -> f64 {
342 const C1: f64 = 1.70158;
343 const C3: f64 = C1 + 1.00;
344 C3 * t * t * t - C1 * t * t
345 }
346 fn out_back(t: f64) -> f64 {
347 const C1: f64 = 1.70158;
348 const C3: f64 = C1 + 1.00;
349 1.0 + C3 * (t - 1.0).powi(3) + C1 * (t - 1.0).powi(2)
350 }
351
352 fn in_out_back(t: f64) -> f64 {
353 const C1: f64 = 1.70158;
354 const C2: f64 = C1 * 1.525;
355 if t < 0.5 {
356 ((2.0 * t).powi(2) * ((C2 + 1.0) * 2.0 * t - C2)) / 2.0
357 } else {
358 ((2.0 * t - 2.0).powi(2) * ((C2 + 1.0) * (t * 2.0 - 2.0) + C2) + 2.0) / 2.0
359 }
360 }
361}
362
363impl EasingCurve {
364 pub fn interpolate<T: Interpolatable>(&self, v0: &T, v1: &T, t: f64) -> T {
366 let multiplier = match self {
367 EasingCurve::Linear => EasingEvaluators::linear(t),
368 EasingCurve::Hold => EasingEvaluators::none(t),
369 EasingCurve::InQuad => EasingEvaluators::in_quad(t),
370 EasingCurve::OutQuad => EasingEvaluators::out_quad(t),
371 EasingCurve::InOutQuad => EasingEvaluators::in_out_quad(t),
372 EasingCurve::InBack => EasingEvaluators::in_back(t),
373 EasingCurve::OutBack => EasingEvaluators::out_back(t),
374 EasingCurve::InOutBack => EasingEvaluators::in_out_back(t),
375 EasingCurve::Custom(evaluator) => (*evaluator)(t),
376 };
377
378 v0.interpolate(v1, multiplier)
379 }
380}
381
382impl<I: Clone + 'static> ImplToFromPaxAny for std::ops::Range<I> {}
383impl<T: 'static> ImplToFromPaxAny for Rc<T> {}
384impl<T: Clone + 'static> ImplToFromPaxAny for Weak<T> {}
385impl<T: Clone + 'static> ImplToFromPaxAny for Option<T> {}
386
387impl<T1: Clone + 'static, T2: Clone + 'static> ImplToFromPaxAny for (T1, T2) {}
388
389pub trait Interpolatable
391where
392 Self: Sized + Clone,
393{
394 fn interpolate(&self, _other: &Self, _t: f64) -> Self {
402 self.clone()
403 }
404}
405
406impl<I: Interpolatable> Interpolatable for std::ops::Range<I> {
407 fn interpolate(&self, _other: &Self, _t: f64) -> Self {
408 self.start.interpolate(&_other.start, _t)..self.end.interpolate(&_other.end, _t)
409 }
410}
411impl Interpolatable for () {}
412
413impl<T: ?Sized + Clone> Interpolatable for HashSet<T> {}
414impl<T: ?Sized + Clone> Interpolatable for VecDeque<T> {}
415impl<T: ?Sized> Interpolatable for Rc<T> {}
416impl<T: Interpolatable> Interpolatable for Weak<T> {}
417impl<T1: Interpolatable, T2: Interpolatable> Interpolatable for (T1, T2) {}
418impl<I: Interpolatable> Interpolatable for Vec<I> {
419 fn interpolate(&self, other: &Self, t: f64) -> Self {
420 if self.len() != other.len() {
421 return other.clone();
422 }
423
424 self.iter()
425 .enumerate()
426 .map(|(i, elem)| elem.interpolate(other.get(i).unwrap(), t))
427 .collect()
428 }
429}
430
431impl Interpolatable for kurbo::BezPath {}
432
433impl Interpolatable for Instant {}
434
435impl Interpolatable for char {}
436
437impl Interpolatable for f64 {
438 fn interpolate(&self, other: &f64, t: f64) -> f64 {
439 self + (*other - self) * t
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::{
446 Duration, EasingCurve, Interpolatable, Numeric, Rotation, TransitionManager,
447 TransitionQueueEntry,
448 };
449
450 #[test]
451 fn zero_duration_transition_applies_immediately() {
452 let mut tm = TransitionManager::new(0.0, 0, 0);
453 tm.push_transition(TransitionQueueEntry {
454 duration: Duration::Frames(0.into()),
455 curve: EasingCurve::Linear,
456 ending_value: 10.0,
457 });
458
459 assert_eq!(tm.compute_eased_value(0, 0), Some(10.0));
460 assert_eq!(tm.compute_eased_value(1, 16), None);
461 }
462
463 #[test]
464 fn completed_transition_returns_final_value_when_polled_late() {
465 let mut tm = TransitionManager::new(0.0, 0, 0);
466 tm.push_transition(TransitionQueueEntry {
467 duration: Duration::Frames(10.into()),
468 curve: EasingCurve::Linear,
469 ending_value: 10.0,
470 });
471
472 assert_eq!(tm.compute_eased_value(11, 183), Some(10.0));
473 assert_eq!(tm.compute_eased_value(12, 200), None);
474 }
475
476 #[test]
477 fn zero_duration_transition_chains_into_next_transition() {
478 let mut tm = TransitionManager::new(0.0, 0, 0);
479 tm.push_transition(TransitionQueueEntry {
480 duration: Duration::Frames(0.into()),
481 curve: EasingCurve::Linear,
482 ending_value: 10.0,
483 });
484 tm.push_transition(TransitionQueueEntry {
485 duration: Duration::Frames(10.into()),
486 curve: EasingCurve::Linear,
487 ending_value: 20.0,
488 });
489
490 assert_eq!(tm.compute_eased_value(0, 0), Some(10.0));
491 assert_eq!(tm.compute_eased_value(5, 83), Some(15.0));
492 assert_eq!(tm.compute_eased_value(10, 167), Some(20.0));
493 assert_eq!(tm.compute_eased_value(11, 183), None);
494 }
495
496 #[test]
497 fn millisecond_transition_uses_wall_clock() {
498 let mut tm = TransitionManager::new(0.0, 0, 0);
499 tm.push_transition(TransitionQueueEntry {
500 duration: Duration::Milliseconds(100.into()),
501 curve: EasingCurve::Linear,
502 ending_value: 10.0,
503 });
504
505 assert_eq!(tm.compute_eased_value(1, 50), Some(5.0));
506 assert_eq!(tm.compute_eased_value(2, 100), Some(10.0));
507 }
508
509 #[test]
510 fn rotation_interpolates_linearly_in_degrees() {
511 let start = Rotation::Degrees(Numeric::F64(-4.0));
512 let end = Rotation::Degrees(Numeric::F64(8.0));
513
514 let midpoint = start.interpolate(&end, 0.5);
515 assert!((midpoint.get_as_degrees() - 2.0).abs() < 0.0001);
516 }
517
518 #[test]
519 fn rotation_percent_normalizes_to_unit_interval() {
520 let rotation = Rotation::Percent(Numeric::F64(100.0));
521 assert!((rotation.to_float_0_1() - 1.0).abs() < f64::EPSILON);
522 }
523
524 #[test]
525 fn in_out_quad_eases_symmetrically() {
526 let start = 0.0f64;
527 let end = 10.0f64;
528
529 let first_quarter = EasingCurve::InOutQuad.interpolate(&start, &end, 0.25);
530 let midpoint = EasingCurve::InOutQuad.interpolate(&start, &end, 0.5);
531 let third_quarter = EasingCurve::InOutQuad.interpolate(&start, &end, 0.75);
532
533 assert!((first_quarter - 1.25).abs() < 0.0001);
534 assert!((midpoint - 5.0).abs() < 0.0001);
535 assert!((third_quarter - 8.75).abs() < 0.0001);
536 }
537
538 #[test]
539 fn option_interpolates_inner_value_when_both_sides_are_present() {
540 let start = Some(0.0f64);
541 let end = Some(10.0f64);
542
543 assert_eq!(start.interpolate(&end, 0.0), Some(0.0));
544 assert_eq!(start.interpolate(&end, 0.5), Some(5.0));
545 assert_eq!(start.interpolate(&end, 1.0), Some(10.0));
546 }
547
548 #[test]
549 fn integers_interpolate_in_both_directions_without_overflow() {
550 assert_eq!(100_u64.interpolate(&0, 0.5), 50);
551 assert_eq!(100_u8.interpolate(&0, 0.5), 50);
552 assert_eq!(i64::MIN.interpolate(&i64::MAX, 0.5), 0);
553 assert_eq!(0_u32.interpolate(&100, 0.5), 50);
554 }
555
556 #[test]
557 fn changed_vector_topology_switches_discretely() {
558 assert_eq!(vec![0.0].interpolate(&vec![2.0, 3.0], 0.5), vec![2.0, 3.0]);
559 assert_eq!(
560 vec![0.0, 2.0].interpolate(&vec![2.0, 4.0], 0.5),
561 vec![1.0, 3.0]
562 );
563 }
564}
565
566impl Interpolatable for bool {
567 fn interpolate(&self, _other: &bool, _t: f64) -> bool {
568 *self
569 }
570}
571
572impl Interpolatable for usize {
573 fn interpolate(&self, other: &usize, t: f64) -> usize {
574 (*self as f64 + (*other as f64 - *self as f64) * t) as usize
575 }
576}
577
578impl Interpolatable for isize {
579 fn interpolate(&self, other: &isize, t: f64) -> isize {
580 (*self as f64 + (*other as f64 - *self as f64) * t) as isize
581 }
582}
583
584impl Interpolatable for i64 {
585 fn interpolate(&self, other: &i64, t: f64) -> i64 {
586 (*self as f64 + (*other as f64 - *self as f64) * t) as i64
587 }
588}
589
590impl Interpolatable for i128 {
591 fn interpolate(&self, other: &i128, t: f64) -> i128 {
592 (*self as f64 + (*other as f64 - *self as f64) * t) as i128
593 }
594}
595
596impl Interpolatable for u128 {
597 fn interpolate(&self, other: &u128, t: f64) -> u128 {
598 (*self as f64 + (*other as f64 - *self as f64) * t) as u128
599 }
600}
601
602impl Interpolatable for u64 {
603 fn interpolate(&self, other: &u64, t: f64) -> u64 {
604 (*self as f64 + (*other as f64 - *self as f64) * t) as u64
605 }
606}
607
608impl Interpolatable for u8 {
609 fn interpolate(&self, other: &u8, t: f64) -> u8 {
610 (*self as f64 + (*other as f64 - *self as f64) * t) as u8
611 }
612}
613
614impl Interpolatable for u16 {
615 fn interpolate(&self, other: &u16, t: f64) -> u16 {
616 (*self as f64 + (*other as f64 - *self as f64) * t) as u16
617 }
618}
619
620impl Interpolatable for u32 {
621 fn interpolate(&self, other: &u32, t: f64) -> u32 {
622 (*self as f64 + (*other as f64 - *self as f64) * t) as u32
623 }
624}
625
626impl Interpolatable for i8 {
627 fn interpolate(&self, other: &i8, t: f64) -> i8 {
628 (*self as f64 + (*other as f64 - *self as f64) * t) as i8
629 }
630}
631
632impl Interpolatable for i16 {
633 fn interpolate(&self, other: &i16, t: f64) -> i16 {
634 (*self as f64 + (*other as f64 - *self as f64) * t) as i16
635 }
636}
637
638impl Interpolatable for i32 {
639 fn interpolate(&self, other: &i32, t: f64) -> i32 {
640 (*self as f64 + (*other as f64 - *self as f64) * t) as i32
641 }
642}
643
644impl Interpolatable for String {}
645
646pub struct Timeline {
648 pub playhead_position: usize,
650 pub frame_count: usize,
652 pub is_playing: bool,
654}