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 {
399 self.clone()
400 }
401}
402
403impl<I: Interpolatable> Interpolatable for std::ops::Range<I> {
404 fn interpolate(&self, _other: &Self, _t: f64) -> Self {
405 self.start.interpolate(&_other.start, _t)..self.end.interpolate(&_other.end, _t)
406 }
407}
408impl Interpolatable for () {}
409
410impl<T: ?Sized + Clone> Interpolatable for HashSet<T> {}
411impl<T: ?Sized + Clone> Interpolatable for VecDeque<T> {}
412impl<T: ?Sized> Interpolatable for Rc<T> {}
413impl<T: Interpolatable> Interpolatable for Weak<T> {}
414impl<T1: Interpolatable, T2: Interpolatable> Interpolatable for (T1, T2) {}
415impl<I: Interpolatable> Interpolatable for Vec<I> {
416 fn interpolate(&self, other: &Self, t: f64) -> Self {
417 assert_eq!(
419 self.len(),
420 other.len(),
421 "cannot interpolate between vecs of different lengths"
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
549impl Interpolatable for bool {
550 fn interpolate(&self, _other: &bool, _t: f64) -> bool {
551 *self
552 }
553}
554
555impl Interpolatable for usize {
556 fn interpolate(&self, other: &usize, t: f64) -> usize {
557 (*self as f64 + (*other - self) as f64 * t) as usize
558 }
559}
560
561impl Interpolatable for isize {
562 fn interpolate(&self, other: &isize, t: f64) -> isize {
563 (*self as f64 + (*other - self) as f64 * t) as isize
564 }
565}
566
567impl Interpolatable for i64 {
568 fn interpolate(&self, other: &i64, t: f64) -> i64 {
569 (*self as f64 + (*other - self) as f64 * t) as i64
570 }
571}
572
573impl Interpolatable for i128 {
574 fn interpolate(&self, other: &i128, t: f64) -> i128 {
575 (*self as f64 + (*other - self) as f64 * t) as i128
576 }
577}
578
579impl Interpolatable for u128 {
580 fn interpolate(&self, other: &u128, t: f64) -> u128 {
581 (*self as f64 + (*other - self) as f64 * t) as u128
582 }
583}
584
585impl Interpolatable for u64 {
586 fn interpolate(&self, other: &u64, t: f64) -> u64 {
587 (*self as f64 + (*other - self) as f64 * t) as u64
588 }
589}
590
591impl Interpolatable for u8 {
592 fn interpolate(&self, other: &u8, t: f64) -> u8 {
593 (*self as f64 + (*other - *self) as f64 * t) as u8
594 }
595}
596
597impl Interpolatable for u16 {
598 fn interpolate(&self, other: &u16, t: f64) -> u16 {
599 (*self as f64 + (*other - *self) as f64 * t) as u16
600 }
601}
602
603impl Interpolatable for u32 {
604 fn interpolate(&self, other: &u32, t: f64) -> u32 {
605 (*self as f64 + (*other - *self) as f64 * t) as u32
606 }
607}
608
609impl Interpolatable for i8 {
610 fn interpolate(&self, other: &i8, t: f64) -> i8 {
611 (*self as f64 + (*other - *self) as f64 * t) as i8
612 }
613}
614
615impl Interpolatable for i16 {
616 fn interpolate(&self, other: &i16, t: f64) -> i16 {
617 (*self as f64 + (*other - *self) as f64 * t) as i16
618 }
619}
620
621impl Interpolatable for i32 {
622 fn interpolate(&self, other: &i32, t: f64) -> i32 {
623 (*self as f64 + (*other - *self) as f64 * t) as i32
624 }
625}
626
627impl Interpolatable for String {}
628
629pub struct Timeline {
631 pub playhead_position: usize,
633 pub frame_count: usize,
635 pub is_playing: bool,
637}