1use std::cell::RefCell;
2use std::rc::Rc;
3use web_time::{Duration, Instant};
4
5pub(crate) fn now() -> Instant {
6 let clock = CLOCK.with(|clock| clock.borrow().clone());
7 clock.now()
8}
9
10#[derive(Clone, Copy, Debug)]
12pub struct SpringSpec {
13 pub damping_ratio: f32,
16 pub stiffness: f32,
18 pub settle_progress: f32,
21 pub settle_velocity: f32,
23}
24
25impl SpringSpec {
26 pub const fn new(damping_ratio: f32, stiffness: f32) -> Self {
27 Self {
28 damping_ratio,
29 stiffness,
30 settle_progress: 0.005,
31 settle_velocity: 0.1,
32 }
33 }
34 pub const fn gentle() -> Self {
36 Self::new(0.5, 200.0)
37 }
38 pub const fn bouncy() -> Self {
40 Self::new(0.2, 300.0)
41 }
42 pub const fn crit() -> Self {
44 Self::new(1.0, 200.0)
45 }
46 pub const fn stiff() -> Self {
48 Self::new(0.8, 600.0)
49 }
50
51 pub const fn with_settle_progress(mut self, threshold: f32) -> Self {
54 self.settle_progress = threshold;
55 self
56 }
57
58 pub const fn with_settle_velocity(mut self, threshold: f32) -> Self {
60 self.settle_velocity = threshold;
61 self
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
68pub struct CubicBezier {
69 pub p1x: f32,
70 pub p1y: f32,
71 pub p2x: f32,
72 pub p2y: f32,
73}
74
75impl CubicBezier {
76 pub const fn new(p1x: f32, p1y: f32, p2x: f32, p2y: f32) -> Self {
77 Self { p1x, p1y, p2x, p2y }
78 }
79}
80
81pub const EASING_EMPHASIZED_DECELERATE: CubicBezier = CubicBezier::new(0.05, 0.7, 0.1, 1.0);
83pub const EASING_STANDARD_DECELERATE: CubicBezier = CubicBezier::new(0.2, 0.0, 0.0, 1.0);
85
86#[derive(Clone, Copy, Debug)]
87#[non_exhaustive]
88pub enum Easing {
89 Linear,
90 EaseIn,
91 EaseOut,
92 EaseInOut,
93 SpringCrit {
95 omega: f32,
96 },
97 SpringGentle,
99 SpringBouncy,
101 FastOutSlowIn,
104 Custom(CubicBezier),
106 CubicIn,
108 CubicOut,
109 CubicInOut,
110 QuartIn,
111 QuartOut,
112 QuartInOut,
113 QuintIn,
114 QuintOut,
115 QuintInOut,
116 SineIn,
117 SineOut,
118 SineInOut,
119 ExpoIn,
120 ExpoOut,
121 ExpoInOut,
122 CircIn,
123 CircOut,
124 CircInOut,
125 BackIn,
127 BackOut,
128 BackInOut,
129 ElasticIn,
131 ElasticOut,
132 ElasticInOut,
133 BounceIn,
134 BounceOut,
135 BounceInOut,
136}
137
138#[derive(Clone, Copy, Debug, Default)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144pub enum EaseKind {
145 #[default]
146 Linear,
147 QuadOut,
148 CubicOut,
149 BackOut,
150}
151
152impl EaseKind {
153 pub fn apply(self, t: f32) -> f32 {
155 let ease = match self {
156 EaseKind::Linear => Easing::Linear,
157 EaseKind::QuadOut => Easing::EaseOut,
158 EaseKind::CubicOut => Easing::CubicOut,
159 EaseKind::BackOut => Easing::BackOut,
160 };
161 ease.interpolate(t.clamp(0.0, 1.0))
162 }
163}
164
165impl Easing {
166 pub fn interpolate(&self, t: f32) -> f32 {
167 match self {
168 Easing::Linear => t,
169 Easing::EaseIn => t * t,
170 Easing::EaseOut => t * (2.0 - t),
171 Easing::EaseInOut => {
172 if t < 0.5 {
173 2.0 * t * t
174 } else {
175 -1.0 + (4.0 - 2.0 * t) * t
176 }
177 }
178 Easing::SpringCrit { omega } => {
179 let w = (*omega).max(0.0);
180 let tt = t.max(0.0);
181 1.0 - (1.0 + w * tt) * (-(w * tt)).exp()
183 }
184 Easing::SpringGentle => spring_underdamped_normalized(t, 0.5, 8.0),
185 Easing::SpringBouncy => spring_underdamped_normalized(t, 0.2, 12.0),
186 Easing::FastOutSlowIn => eval_cubic_bezier(0.4, 0.0, 0.2, 1.0, t),
187 Easing::Custom(cb) => eval_cubic_bezier(cb.p1x, cb.p1y, cb.p2x, cb.p2y, t),
188 Easing::CubicIn => t * t * t,
189 Easing::CubicOut => {
190 let u = t - 1.0;
191 u * u * u + 1.0
192 }
193 Easing::CubicInOut => {
194 if t < 0.5 {
195 4.0 * t * t * t
196 } else {
197 let u = 2.0 * t - 2.0;
198 u * u * u / 2.0 + 1.0
199 }
200 }
201 Easing::QuartIn => t * t * t * t,
202 Easing::QuartOut => {
203 let u = t - 1.0;
204 1.0 - u * u * u * u
205 }
206 Easing::QuartInOut => {
207 if t < 0.5 {
208 8.0 * t * t * t * t
209 } else {
210 let u = -2.0 * t + 2.0;
211 1.0 - u * u * u * u / 2.0
212 }
213 }
214 Easing::QuintIn => t * t * t * t * t,
215 Easing::QuintOut => {
216 let u = t - 1.0;
217 u * u * u * u * u + 1.0
218 }
219 Easing::QuintInOut => {
220 if t < 0.5 {
221 16.0 * t * t * t * t * t
222 } else {
223 let u = -2.0 * t + 2.0;
224 1.0 - u * u * u * u * u / 2.0
225 }
226 }
227 Easing::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
228 Easing::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
229 Easing::SineInOut => 0.5 * (1.0 - (t * std::f32::consts::PI).cos()),
230 Easing::ExpoIn => {
231 if t <= 0.0 {
232 0.0
233 } else {
234 2.0f32.powf(10.0 * t - 10.0)
235 }
236 }
237 Easing::ExpoOut => {
238 if t >= 1.0 {
239 1.0
240 } else {
241 1.0 - 2.0f32.powf(-10.0 * t)
242 }
243 }
244 Easing::ExpoInOut => {
245 if t <= 0.0 {
246 0.0
247 } else if t >= 1.0 {
248 1.0
249 } else if t < 0.5 {
250 2.0f32.powf(20.0 * t - 10.0) / 2.0
251 } else {
252 (2.0 - 2.0f32.powf(-20.0 * t + 10.0)) / 2.0
253 }
254 }
255 Easing::CircIn => 1.0 - (1.0 - t * t).sqrt(),
256 Easing::CircOut => (1.0 - (t - 1.0) * (t - 1.0)).sqrt(),
257 Easing::CircInOut => {
258 if t < 0.5 {
259 (1.0 - (1.0 - 4.0 * t * t).sqrt()) / 2.0
260 } else {
261 ((1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0)).sqrt() + 1.0) / 2.0
262 }
263 }
264 Easing::BackIn => {
265 const C1: f32 = 1.70158;
266 const C3: f32 = C1 + 1.0;
267 C3 * t * t * t - C1 * t * t
268 }
269 Easing::BackOut => {
270 const C1: f32 = 1.70158;
271 const C3: f32 = C1 + 1.0;
272 let u = t - 1.0;
273 1.0 + C3 * u * u * u + C1 * u * u
274 }
275 Easing::BackInOut => {
276 const C1: f32 = 1.70158;
277 const C3: f32 = C1 + 1.0;
278 if t < 0.5 {
279 let u = 2.0 * t;
280 (C3 * u * u * u - C1 * u * u) / 2.0
281 } else {
282 let u = 2.0 * t - 2.0;
283 (C3 * u * u * u + C1 * u * u) / 2.0 + 1.0
284 }
285 }
286 Easing::ElasticIn => {
287 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
288 if t <= 0.0 {
289 0.0
290 } else if t >= 1.0 {
291 1.0
292 } else {
293 -(2.0f32.powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * C4).sin())
294 }
295 }
296 Easing::ElasticOut => {
297 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
298 if t <= 0.0 {
299 0.0
300 } else if t >= 1.0 {
301 1.0
302 } else {
303 2.0f32.powf(-10.0 * t) * ((10.0 * t - 0.75) * C4).sin() + 1.0
304 }
305 }
306 Easing::ElasticInOut => {
307 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
308 if t <= 0.0 {
309 0.0
310 } else if t >= 1.0 {
311 1.0
312 } else if t < 0.5 {
313 -(2.0f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C4).sin()) / 2.0
314 } else {
315 2.0f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C4).sin() / 2.0 + 1.0
316 }
317 }
318 Easing::BounceIn => 1.0 - bounce_out(1.0 - t),
319 Easing::BounceOut => bounce_out(t),
320 Easing::BounceInOut => {
321 if t < 0.5 {
322 (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
323 } else {
324 (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
325 }
326 }
327 }
328 }
329}
330
331fn bounce_out(t: f32) -> f32 {
333 const N1: f32 = 7.5625;
334 const D1: f32 = 2.75;
335 if t < 1.0 / D1 {
336 N1 * t * t
337 } else if t < 2.0 / D1 {
338 let t = t - 1.5 / D1;
339 N1 * t * t + 0.75
340 } else if t < 2.5 / D1 {
341 let t = t - 2.25 / D1;
342 N1 * t * t + 0.9375
343 } else {
344 let t = t - 2.625 / D1;
345 N1 * t * t + 0.984375
346 }
347}
348
349fn eval_cubic_bezier(p1x: f32, p1y: f32, p2x: f32, p2y: f32, t: f32) -> f32 {
353 let t = t.clamp(0.0, 1.0);
354 if t <= 0.0 {
355 return 0.0;
356 }
357 if t >= 1.0 {
358 return 1.0;
359 }
360 let mut u = t;
361 for _ in 0..6 {
362 let omu = 1.0 - u;
363 let x = 3.0 * omu * omu * u * p1x + 3.0 * omu * u * u * p2x + u * u * u;
364 let dx = 3.0 * omu * omu * p1x + 6.0 * omu * u * (p2x - p1x) + 3.0 * u * u * (1.0 - p2x);
365 if dx.abs() < 1e-10 {
366 break;
367 }
368 u -= (x - t) / dx;
369 u = u.clamp(0.0, 1.0);
370 }
371 let omu = 1.0 - u;
372 3.0 * omu * omu * u * p1y + 3.0 * omu * u * u * p2y + u * u * u
373}
374
375fn hermite_interpolate(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
381 let x2 = x * x;
382 let x3 = x2 * x;
383 h * t1 * (x - 2.0 * x2 + x3) + h * t2 * (x3 - x2) + y1 - (3.0 * x2 - 2.0 * x3) * (y1 - y2)
384}
385
386#[allow(dead_code)]
388fn hermite_differential(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
389 let x2 = x * x;
390 h * (t1 - 2.0 * x * (2.0 * t1 + t2) + 3.0 * (t1 + t2) * x2) - 6.0 * (x - x2) * (y1 - y2)
391}
392
393#[derive(Clone, Debug)]
398pub struct MonoSpline {
399 times: Vec<f32>,
400 values: Vec<f32>,
401 tangents: Vec<f32>,
402}
403
404impl MonoSpline {
405 pub fn new(times: Vec<f32>, values: Vec<f32>) -> Self {
414 assert!(times.len() >= 2, "MonoSpline requires at least 2 keyframes");
415 assert_eq!(times.len(), values.len());
416 assert!(
417 times.iter().all(|t| t.is_finite()),
418 "MonoSpline times must be finite"
419 );
420 assert!(
421 values.iter().all(|v| v.is_finite()),
422 "MonoSpline values must be finite"
423 );
424 if cfg!(debug_assertions) {
425 for w in times.windows(2) {
426 if w[1] <= w[0] {
427 log::warn!(
428 "MonoSpline: times not strictly ascending; degenerate segments coerce to zero slope"
429 );
430 break;
431 }
432 }
433 }
434 let n = times.len();
435 let mut tangents = vec![0.0; n];
436
437 let mut slopes = vec![0.0; n.saturating_sub(1)];
439 for i in 0..n - 1 {
440 let dt = times[i + 1] - times[i];
441 if dt > 0.0 {
442 slopes[i] = (values[i + 1] - values[i]) / dt;
443 } else {
444 log::warn!("MonoSpline: non-positive dt at segment {i}; coercing slope to 0");
445 slopes[i] = 0.0;
446 }
447 }
448
449 tangents[0] = slopes[0];
451 for i in 1..n - 1 {
452 tangents[i] = (slopes[i - 1] + slopes[i]) * 0.5;
453 }
454 tangents[n - 1] = slopes[n - 2];
455
456 for i in 0..n - 1 {
458 if slopes[i] == 0.0 {
459 tangents[i] = 0.0;
460 tangents[i + 1] = 0.0;
461 } else {
462 let a = tangents[i] / slopes[i];
463 let b = tangents[i + 1] / slopes[i];
464 let h = (a * a + b * b).sqrt();
465 if h > 9.0 {
466 let t = 3.0 / h;
467 tangents[i] = t * a * slopes[i];
468 tangents[i + 1] = t * b * slopes[i];
469 }
470 }
471 }
472
473 Self {
474 times,
475 values,
476 tangents,
477 }
478 }
479
480 pub fn evaluate(&self, t: f32) -> f32 {
483 let n = self.times.len();
484 let first = self.times[0];
485 let last = self.times[n - 1];
486
487 if t <= first {
488 return self.values[0] + (t - first) * self.tangents[0];
489 }
490 if t >= last {
491 return self.values[n - 1] + (t - last) * self.tangents[n - 1];
492 }
493
494 for i in 0..n - 1 {
495 if t >= self.times[i] && t <= self.times[i + 1] {
496 let h = self.times[i + 1] - self.times[i];
497 let x = (t - self.times[i]) / h;
498 return hermite_interpolate(
499 h,
500 x,
501 self.values[i],
502 self.values[i + 1],
503 self.tangents[i],
504 self.tangents[i + 1],
505 );
506 }
507 }
508
509 self.values[n - 1] }
511}
512
513fn spring_analytical(zeta: f32, stiffness: f32, t: f32, x0: f32, v0: f32) -> (f32, f32) {
515 if t <= 0.0 {
516 return (x0, v0);
517 }
518
519 let omega = if stiffness > 0.0 {
520 stiffness.sqrt()
521 } else {
522 return (x0 + v0 * t, v0);
523 };
524
525 let zeta = zeta.max(0.0);
526 let exp = (-zeta * omega * t).exp();
527 let a = 1.0 - x0; if (zeta - 1.0).abs() < 1e-6 {
530 let b = omega * a - v0;
532 let progress = 1.0 - (a + b * t) * exp;
533 let velocity = (a * omega - b + b * omega * t) * exp;
534 (progress, velocity)
535 } else if zeta < 1.0 {
536 let wd = omega * (1.0 - zeta * zeta).sqrt();
538 let c = (zeta * omega * a - v0) / wd;
539 let cos_wd = (wd * t).cos();
540 let sin_wd = (wd * t).sin();
541 let env = a * cos_wd + c * sin_wd;
542 let progress = 1.0 - exp * env;
543 let velocity =
544 exp * ((zeta * omega * a - wd * c) * cos_wd + (zeta * omega * c + wd * a) * sin_wd);
545 (progress, velocity)
546 } else {
547 let wd = omega * (zeta * zeta - 1.0).sqrt();
549 let d = (zeta * omega * a - v0) / wd;
550 let cosh_wd = (wd * t).cosh();
551 let sinh_wd = (wd * t).sinh();
552 let env = a * cosh_wd + d * sinh_wd;
553 let progress = 1.0 - exp * env;
554 let velocity =
555 exp * ((zeta * omega * a - wd * d) * cosh_wd + (zeta * omega * d - wd * a) * sinh_wd);
556 (progress, velocity)
557 }
558}
559
560fn spring_underdamped_normalized(t: f32, zeta: f32, omega: f32) -> f32 {
561 let tt = t.max(0.0);
562 let z = zeta.clamp(0.0, 0.999);
563 let w = omega.max(0.0);
564 let wd = w * (1.0 - z * z).sqrt();
565 let exp_term = (-z * w * tt).exp();
566 let cos_term = (wd * tt).cos();
567 let sin_term = (wd * tt).sin();
568 let c = z / (1.0 - z * z).sqrt();
570 let y = 1.0 - exp_term * (cos_term + c * sin_term);
571 y.clamp(0.0, 1.0)
572}
573
574#[derive(Clone, Copy, Debug)]
575pub struct AnimationSpec {
576 pub duration: Duration,
577 pub easing: Easing,
578 pub delay: Duration,
579 pub spring: Option<SpringSpec>,
581 pub repeat: Option<RepeatableSpec>,
583}
584
585impl Default for AnimationSpec {
586 fn default() -> Self {
587 Self {
588 duration: Duration::from_millis(300),
589 easing: Easing::EaseInOut,
590 delay: Duration::ZERO,
591 spring: None,
592 repeat: None,
593 }
594 }
595}
596
597impl AnimationSpec {
598 pub fn tween(duration: Duration, easing: Easing) -> Self {
599 Self {
600 duration,
601 easing,
602 delay: Duration::ZERO,
603 spring: None,
604 repeat: None,
605 }
606 }
607 pub fn spring(spring: SpringSpec) -> Self {
609 Self {
610 duration: Duration::ZERO,
611 easing: Easing::Linear,
612 delay: Duration::ZERO,
613 spring: Some(spring),
614 repeat: None,
615 }
616 }
617 pub fn spring_gentle() -> Self {
619 Self::spring(SpringSpec::gentle())
620 }
621 pub fn spring_bouncy() -> Self {
623 Self::spring(SpringSpec::bouncy())
624 }
625 pub fn spring_crit(omega: f32) -> Self {
627 Self::spring(SpringSpec::new(1.0, omega * omega))
628 }
629
630 pub fn fast() -> Self {
631 Self {
632 duration: Duration::from_millis(150),
633 easing: Easing::EaseOut,
634 delay: Duration::ZERO,
635 spring: None,
636 repeat: None,
637 }
638 }
639
640 pub fn slow() -> Self {
641 Self {
642 duration: Duration::from_millis(600),
643 easing: Easing::EaseInOut,
644 delay: Duration::ZERO,
645 spring: None,
646 repeat: None,
647 }
648 }
649
650 pub fn repeated(mut self, repeat: RepeatableSpec) -> Self {
653 self.repeat = Some(repeat);
654 self
655 }
656}
657
658#[derive(Clone, Debug)]
663pub struct KeyframesSpec<T: Clone> {
664 pub keyframes: Vec<(f32, T, Option<Easing>)>,
667}
668
669impl<T: Clone + Interpolate> KeyframesSpec<T> {
670 pub fn new(keyframes: Vec<(f32, T)>) -> Self {
671 assert!(!keyframes.is_empty(), "KeyframesSpec must not be empty");
672 let with_easing = keyframes.into_iter().map(|(t, v)| (t, v, None)).collect();
673 Self {
674 keyframes: with_easing,
675 }
676 }
677
678 pub fn with_easing(mut self, easing: Easing) -> Self {
680 if let Some(last) = self.keyframes.last_mut() {
681 last.2 = Some(easing);
682 }
683 self
684 }
685
686 pub fn evaluate(&self, t: f32) -> T {
687 let t = t.clamp(0.0, 1.0);
688 let kf = &self.keyframes;
689 if kf.is_empty() {
690 panic!("KeyframesSpec must have at least one keyframe");
691 }
692
693 for i in 0..kf.len() - 1 {
695 let (t0, _, _) = kf[i];
696 let (t1, ref v1, easing) = kf[i + 1];
697 if t >= t0 && t <= t1 {
698 let segment_t = if (t1 - t0).abs() < f32::EPSILON {
699 1.0
700 } else {
701 (t - t0) / (t1 - t0)
702 };
703 let eased_t = match easing {
704 Some(e) => e.interpolate(segment_t),
705 None => segment_t,
706 };
707 return kf[i].1.interpolate(v1, eased_t);
708 }
709 }
710 kf.last().unwrap().1.clone()
711 }
712}
713
714#[derive(Clone, Debug)]
721pub struct SplineKeyframes {
722 spline: MonoSpline,
723}
724
725impl SplineKeyframes {
726 pub fn new(keyframes: Vec<(f32, f32)>) -> Self {
731 assert!(
732 keyframes.len() >= 2,
733 "SplineKeyframes requires at least 2 keyframes"
734 );
735 let times: Vec<f32> = keyframes.iter().map(|(t, _)| *t).collect();
736 let values: Vec<f32> = keyframes.iter().map(|(_, v)| *v).collect();
737 Self {
738 spline: MonoSpline::new(times, values),
739 }
740 }
741
742 pub fn evaluate(&self, t: f32) -> f32 {
744 self.spline.evaluate(t.clamp(0.0, 1.0))
745 }
746
747 pub fn fingerprint(&self) -> String {
751 format!("{:?}", self.spline)
752 }
753}
754
755#[derive(Clone, Copy, Debug)]
760pub struct RepeatableSpec {
761 pub iterations: Option<u32>,
763 pub reverse: bool,
765 pub delay_between: Duration,
767}
768
769impl Default for RepeatableSpec {
770 fn default() -> Self {
771 Self {
772 iterations: None,
773 reverse: false,
774 delay_between: Duration::ZERO,
775 }
776 }
777}
778
779impl RepeatableSpec {
780 pub fn new(iterations: u32) -> Self {
781 Self {
782 iterations: Some(iterations),
783 reverse: false,
784 delay_between: Duration::ZERO,
785 }
786 }
787
788 pub fn infinite() -> Self {
789 Self {
790 iterations: None,
791 reverse: false,
792 delay_between: Duration::ZERO,
793 }
794 }
795
796 pub fn reverse(mut self) -> Self {
797 self.reverse = true;
798 self
799 }
800
801 pub fn delay_between(mut self, d: Duration) -> Self {
802 self.delay_between = d;
803 self
804 }
805}
806
807fn same_easing(a: Easing, b: Easing) -> bool {
808 match (a, b) {
809 (Easing::SpringCrit { omega: a }, Easing::SpringCrit { omega: b }) => {
810 a.to_bits() == b.to_bits()
811 }
812 (Easing::Custom(a), Easing::Custom(b)) => {
813 a.p1x.to_bits() == b.p1x.to_bits()
814 && a.p1y.to_bits() == b.p1y.to_bits()
815 && a.p2x.to_bits() == b.p2x.to_bits()
816 && a.p2y.to_bits() == b.p2y.to_bits()
817 }
818 (a, b) => std::mem::discriminant(&a) == std::mem::discriminant(&b),
819 }
820}
821
822fn same_spring(a: SpringSpec, b: SpringSpec) -> bool {
823 a.damping_ratio.to_bits() == b.damping_ratio.to_bits()
824 && a.stiffness.to_bits() == b.stiffness.to_bits()
825 && a.settle_progress.to_bits() == b.settle_progress.to_bits()
826 && a.settle_velocity.to_bits() == b.settle_velocity.to_bits()
827}
828
829fn same_repeat(a: Option<RepeatableSpec>, b: Option<RepeatableSpec>) -> bool {
830 match (a, b) {
831 (None, None) => true,
832 (Some(a), Some(b)) => {
833 a.iterations == b.iterations
834 && a.reverse == b.reverse
835 && a.delay_between == b.delay_between
836 }
837 _ => false,
838 }
839}
840
841fn same_animation_spec(a: &AnimationSpec, b: &AnimationSpec) -> bool {
842 a.duration == b.duration
843 && same_easing(a.easing, b.easing)
844 && a.delay == b.delay
845 && match (a.spring, b.spring) {
846 (None, None) => true,
847 (Some(a), Some(b)) => same_spring(a, b),
848 _ => false,
849 }
850 && same_repeat(a.repeat, b.repeat)
851}
852
853#[derive(Clone, Copy, Debug)]
857pub struct DecayAnimationSpec {
858 pub friction: f32,
860 pub stop_threshold: f32,
862}
863
864impl Default for DecayAnimationSpec {
865 fn default() -> Self {
866 Self {
867 friction: 0.8,
868 stop_threshold: 1.0,
869 }
870 }
871}
872
873impl DecayAnimationSpec {
874 pub fn new(friction: f32) -> Self {
875 Self {
876 friction: friction.clamp(0.01, 1.0),
877 stop_threshold: 1.0,
878 }
879 }
880}
881
882impl AnimatedValue<f32> {
883 pub fn set_target_with_velocity(&mut self, target: f32, velocity: f32) {
884 let start = self.current;
885 let distance = target - start;
886 if distance.abs() <= f32::EPSILON {
887 self.snap_to(target);
888 return;
889 }
890 self.set_target(target);
891 self.spring_v0 = if velocity.is_finite() {
892 velocity / distance
893 } else {
894 0.0
895 };
896 self.velocity = self.spring_v0;
897 }
898
899 pub fn current_velocity(&self) -> f32 {
900 if self.start_time.is_none() {
901 0.0
902 } else {
903 self.velocity * (self.target - self.start)
904 }
905 }
906
907 pub fn update_decay(&mut self, friction: f32, stop_threshold: f32) -> bool {
909 let _start = match self.start_time {
910 Some(s) => s,
911 None => return false,
912 };
913
914 let now = now();
915 let dt = match self.last_update {
916 Some(last) => now.saturating_duration_since(last).as_secs_f32().min(0.05),
917 None => 0.0,
918 };
919 self.last_update = Some(now);
920
921 if dt <= 0.0 {
922 return true;
923 }
924
925 if self.velocity.abs() < stop_threshold {
926 self.velocity = 0.0;
927 self.start_time = None;
928 return false;
929 }
930
931 self.velocity *= friction.powf(dt * 60.0);
932 let delta = self.velocity * dt;
933 let new_progress = self.progress + delta;
939 self.progress = new_progress;
940 if self.progress.abs() < 0.001 && self.velocity.abs() < stop_threshold {
945 self.progress = 0.0;
946 self.velocity = 0.0;
947 self.start_time = None;
948 self.current = self.start;
949 return false;
950 }
951
952 let clamped = self.progress.clamp(-1e6, 1e6);
954 self.current = self.start + clamped;
955 true
956 }
957}
958
959pub trait Interpolate {
960 fn interpolate(&self, other: &Self, t: f32) -> Self;
961}
962
963impl Interpolate for f32 {
964 fn interpolate(&self, other: &Self, t: f32) -> Self {
965 self + (other - self) * t
966 }
967}
968
969impl Interpolate for crate::Color {
970 fn interpolate(&self, other: &Self, t: f32) -> Self {
971 let lerp = |a: u8, b: u8| {
972 (a as f32 + (b as f32 - a as f32) * t)
973 .round()
974 .clamp(0.0, 255.0) as u8
975 };
976 crate::Color(
977 lerp(self.0, other.0),
978 lerp(self.1, other.1),
979 lerp(self.2, other.2),
980 lerp(self.3, other.3),
981 )
982 }
983}
984
985impl Interpolate for crate::Vec2 {
986 fn interpolate(&self, other: &Self, t: f32) -> Self {
987 crate::Vec2 {
988 x: self.x.interpolate(&other.x, t),
989 y: self.y.interpolate(&other.y, t),
990 }
991 }
992}
993
994impl Interpolate for crate::Size {
995 fn interpolate(&self, other: &Self, t: f32) -> Self {
996 crate::Size {
997 width: self.width.interpolate(&other.width, t),
998 height: self.height.interpolate(&other.height, t),
999 }
1000 }
1001}
1002
1003impl Interpolate for crate::Rect {
1004 fn interpolate(&self, other: &Self, t: f32) -> Self {
1005 crate::Rect {
1006 x: self.x.interpolate(&other.x, t),
1007 y: self.y.interpolate(&other.y, t),
1008 w: self.w.interpolate(&other.w, t),
1009 h: self.h.interpolate(&other.h, t),
1010 }
1011 }
1012}
1013
1014pub trait Clock: Send + Sync + 'static {
1016 fn now(&self) -> Instant;
1017}
1018
1019pub struct SystemClock;
1020impl Clock for SystemClock {
1021 fn now(&self) -> Instant {
1022 Instant::now()
1023 }
1024}
1025
1026thread_local! {
1027 static CLOCK: RefCell<Rc<dyn Clock>> = RefCell::new(Rc::new(SystemClock));
1028}
1029
1030pub fn set_clock(clock: Box<dyn Clock>) {
1032 let old = CLOCK.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), Rc::from(clock)));
1033 drop(old);
1034}
1035pub fn ensure_system_clock() {
1037 }
1039
1040#[derive(Clone)]
1042pub struct TestClock {
1043 pub t: Instant,
1044}
1045impl Clock for TestClock {
1046 fn now(&self) -> Instant {
1047 self.t
1048 }
1049}
1050
1051pub struct AnimatedValue<T: Interpolate + Clone> {
1060 current: T,
1061 target: T,
1062 start: T,
1063 spec: AnimationSpec,
1064 keyframes: Option<KeyframesSpec<T>>,
1065 iteration: u32,
1066 start_time: Option<Instant>,
1067 progress: f32,
1069 velocity: f32,
1070 spring_v0: f32,
1072 last_update: Option<Instant>,
1073 delay_until: Option<Instant>,
1074 delay_applied: bool,
1075 reverse: bool,
1076}
1077
1078impl<T: Interpolate + Clone> AnimatedValue<T> {
1079 pub fn new(initial: T, spec: AnimationSpec) -> Self {
1080 Self {
1081 current: initial.clone(),
1082 target: initial.clone(),
1083 start: initial,
1084 spec,
1085 keyframes: None,
1086 iteration: 0,
1087 start_time: None,
1088 progress: 1.0,
1089 velocity: 0.0,
1090 spring_v0: 0.0,
1091 last_update: None,
1092 delay_until: None,
1093 delay_applied: false,
1094 reverse: false,
1095 }
1096 }
1097
1098 pub fn set_spec(&mut self, spec: AnimationSpec) {
1099 assert!(
1100 self.keyframes.is_none() || spec.spring.is_none(),
1101 "spring animations cannot use keyframes"
1102 );
1103 let changed = !same_animation_spec(&self.spec, &spec);
1104 self.spec = spec;
1105 if changed {
1106 self.iteration = 0;
1107 self.reverse = false;
1108 if self.start_time.is_some() || self.delay_until.is_some() {
1109 self.start = self.current.clone();
1110 self.start_time = Some(now());
1111 self.last_update = None;
1112 self.delay_until = None;
1113 self.delay_applied = false;
1114 self.progress = 0.0;
1115 self.spring_v0 = self.velocity;
1116 }
1117 }
1118 }
1119
1120 pub fn set_keyframes(&mut self, keyframes: KeyframesSpec<T>) {
1123 assert!(
1124 !keyframes.keyframes.is_empty(),
1125 "KeyframesSpec must not be empty"
1126 );
1127 assert!(
1128 self.spec.spring.is_none(),
1129 "spring animations cannot use keyframes"
1130 );
1131 self.keyframes = Some(keyframes);
1132 self.start_time = Some(now());
1133 self.last_update = None;
1134 self.delay_until = None;
1135 self.delay_applied = false;
1136 self.iteration = 0;
1137 self.reverse = false;
1138 }
1139
1140 pub fn set_target(&mut self, target: T) {
1141 self.keyframes = None;
1146 self.start = self.current.clone();
1147 self.target = target;
1148 self.start_time = Some(now());
1149 self.last_update = None;
1150 self.delay_until = None;
1151 self.delay_applied = false;
1152 self.iteration = 0;
1153 self.reverse = false;
1154 if self.spec.spring.is_some() {
1155 self.progress = 0.0;
1157 self.spring_v0 = self.velocity;
1158 }
1159 }
1160
1161 pub fn snap_to(&mut self, value: T) {
1163 self.current = value.clone();
1164 self.target = value.clone();
1165 self.start = value;
1166 self.keyframes = None;
1167 self.start_time = None;
1168 self.progress = 1.0;
1169 self.velocity = 0.0;
1170 self.spring_v0 = 0.0;
1171 self.last_update = None;
1172 self.delay_until = None;
1173 self.delay_applied = true;
1174 self.iteration = 0;
1175 self.reverse = false;
1176 }
1177
1178 pub fn update(&mut self) -> bool {
1179 if self.waiting_for_repeat() {
1180 return true;
1181 }
1182 let spring_spec = self.spec.spring;
1183 let mut still = if let Some(spring) = spring_spec {
1184 self.update_spring(&spring)
1185 } else if self.keyframes.is_some() {
1186 self.update_keyframes()
1187 } else {
1188 self.update_tween()
1189 };
1190
1191 if !still && let Some(repeat) = self.spec.repeat {
1192 let maxed = repeat
1193 .iterations
1194 .is_some_and(|max| self.iteration + 1 >= max);
1195 if !maxed {
1196 self.iteration += 1;
1197 self.start_time = None;
1198 self.last_update = None;
1199 self.progress = 0.0;
1200 self.velocity = 0.0;
1201 self.spring_v0 = 0.0;
1202 self.delay_applied = true;
1203 self.reverse = repeat.reverse && !self.reverse;
1204 if self.keyframes.is_none() && repeat.reverse {
1205 std::mem::swap(&mut self.start, &mut self.target);
1206 }
1207 let start = now();
1208 if repeat.delay_between.is_zero() {
1209 self.start_time = Some(start);
1210 } else {
1211 self.delay_until = Some(start + repeat.delay_between);
1212 }
1213 still = true;
1214 }
1215 }
1216
1217 still
1218 }
1219
1220 fn waiting_for_repeat(&mut self) -> bool {
1221 let Some(until) = self.delay_until else {
1222 return false;
1223 };
1224 let current = now();
1225 if current < until {
1226 return true;
1227 }
1228 self.delay_until = None;
1229 self.start_time = Some(until);
1230 self.delay_applied = true;
1231 self.last_update = None;
1232 false
1233 }
1234
1235 fn update_keyframes(&mut self) -> bool {
1236 let start = match self.start_time {
1237 Some(s) => s,
1238 None => return false,
1239 };
1240 let elapsed = now().saturating_duration_since(start);
1241 let delay = if self.delay_applied {
1242 Duration::ZERO
1243 } else {
1244 self.spec.delay
1245 };
1246 if elapsed < delay {
1247 return true;
1248 }
1249 self.delay_applied = true;
1250 let animation_time = elapsed - delay;
1251 if animation_time >= self.spec.duration || self.spec.duration.is_zero() {
1252 if let Some(ref kf) = self.keyframes {
1253 self.current = kf.evaluate(if self.reverse { 0.0 } else { 1.0 });
1254 }
1255 self.start_time = None;
1256 return false;
1257 }
1258 let t = (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1259 let eased_t = self.spec.easing.interpolate(t);
1260 if let Some(ref kf) = self.keyframes {
1261 let sample_t = if self.reverse { 1.0 - eased_t } else { eased_t };
1262 self.current = kf.evaluate(sample_t);
1263 }
1264 true
1265 }
1266
1267 fn update_spring(&mut self, spring: &SpringSpec) -> bool {
1268 let start = match self.start_time {
1269 Some(s) => s,
1270 None => return false,
1271 };
1272
1273 let now = now();
1274 let elapsed = now.saturating_duration_since(start);
1275
1276 let delay = if self.delay_applied {
1277 Duration::ZERO
1278 } else {
1279 self.spec.delay
1280 };
1281 if elapsed < delay {
1282 return true;
1283 }
1284 self.delay_applied = true;
1285
1286 let t = (elapsed - delay).as_secs_f32().max(0.0);
1287 let (progress, velocity) = spring_analytical(
1288 spring.damping_ratio,
1289 spring.stiffness,
1290 t,
1291 0.0,
1292 self.spring_v0,
1293 );
1294 let progress = progress.clamp(-0.1, 2.0);
1295 if !progress.is_finite() || !velocity.is_finite() {
1296 self.progress = 1.0;
1297 self.velocity = 0.0;
1298 self.spring_v0 = 0.0;
1299 self.current = self.target.clone();
1300 self.start_time = None;
1301 self.last_update = None;
1302 return false;
1303 }
1304
1305 if (progress - 1.0).abs() < spring.settle_progress
1306 && velocity.abs() < spring.settle_velocity
1307 {
1308 self.progress = 1.0;
1309 self.velocity = 0.0;
1310 self.spring_v0 = 0.0;
1311 self.current = self.target.clone();
1312 self.start_time = None;
1313 self.last_update = None;
1314 return false;
1315 }
1316
1317 self.progress = progress;
1318 self.velocity = velocity;
1319 self.current = self.start.interpolate(&self.target, self.progress);
1320 true
1321 }
1322
1323 fn update_tween(&mut self) -> bool {
1324 if let Some(start) = self.start_time {
1325 let elapsed = now().saturating_duration_since(start);
1326 let delay = if self.delay_applied {
1327 Duration::ZERO
1328 } else {
1329 self.spec.delay
1330 };
1331
1332 if elapsed < delay {
1333 return true;
1334 }
1335 self.delay_applied = true;
1336
1337 let animation_time = elapsed - delay;
1338
1339 if animation_time >= self.spec.duration || self.spec.duration.is_zero() {
1340 self.current = self.target.clone();
1341 self.start_time = None;
1342 return false;
1343 }
1344
1345 let t =
1346 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1347 let eased_t = self.spec.easing.interpolate(t);
1348
1349 self.current = self.start.interpolate(&self.target, eased_t);
1350 true
1351 } else {
1352 false
1353 }
1354 }
1355
1356 pub fn get(&self) -> &T {
1357 &self.current
1358 }
1359
1360 pub fn is_animating(&self) -> bool {
1361 self.start_time.is_some() || self.delay_until.is_some()
1362 }
1363
1364 pub fn has_keyframes(&self) -> bool {
1365 self.keyframes.is_some()
1366 }
1367}
1368
1369#[cfg(test)]
1370mod tests {
1371 use super::*;
1372
1373 fn assert_in_out(ease: Easing) {
1374 assert!((ease.interpolate(0.0) - 0.0).abs() < 1e-4, "{ease:?} in(0)");
1376 assert!((ease.interpolate(1.0) - 1.0).abs() < 1e-4, "{ease:?} in(1)");
1377 }
1378
1379 #[test]
1380 fn godot_eases_pass_through_endpoints() {
1381 use Easing::*;
1382 for ease in [
1383 CubicIn,
1384 CubicOut,
1385 CubicInOut,
1386 QuartIn,
1387 QuartOut,
1388 QuartInOut,
1389 QuintIn,
1390 QuintOut,
1391 QuintInOut,
1392 SineIn,
1393 SineOut,
1394 SineInOut,
1395 ExpoIn,
1396 ExpoOut,
1397 ExpoInOut,
1398 CircIn,
1399 CircOut,
1400 CircInOut,
1401 BackIn,
1402 BackOut,
1403 BackInOut,
1404 ElasticIn,
1405 ElasticOut,
1406 ElasticInOut,
1407 BounceIn,
1408 BounceOut,
1409 BounceInOut,
1410 ] {
1411 assert_in_out(ease);
1412 }
1413 }
1414
1415 #[test]
1416 fn easing_direction_is_sane() {
1417 use Easing::*;
1418 let mid = [CubicOut, QuartOut, QuintOut, SineOut, ExpoOut, CircOut];
1419 for e in mid {
1420 assert!(e.interpolate(0.5) <= 1.0, "{e:?} stays below 1 at mid");
1421 assert!(e.interpolate(0.5) > 0.5, "{e:?} is ease-out at mid");
1422 }
1423 for e in [CubicIn, QuartIn, QuintIn, SineIn, ExpoIn, CircIn] {
1424 assert!(e.interpolate(0.5) < 0.5, "{e:?} is ease-in at mid");
1425 }
1426 for e in [BackOut, ElasticOut] {
1428 assert!(e.interpolate(0.5) > 1.0, "{e:?} overshoots");
1429 }
1430 for e in [BackIn, ElasticIn] {
1431 assert!(e.interpolate(0.5) < 0.0, "{e:?} undershoots");
1432 }
1433 }
1434
1435 #[test]
1436 fn ease_kind_endpoints_hold() {
1437 for ease in [
1438 EaseKind::Linear,
1439 EaseKind::QuadOut,
1440 EaseKind::CubicOut,
1441 EaseKind::BackOut,
1442 ] {
1443 assert!(ease.apply(0.0).abs() < 1e-4);
1444 assert!((ease.apply(1.0) - 1.0).abs() < 1e-4);
1445 }
1446 assert!(EaseKind::QuadOut.apply(0.5) > 0.5);
1447 }
1448
1449 #[test]
1450 fn bounce_matches_known_values() {
1451 use Easing::*;
1452 assert!((BounceOut.interpolate(0.0) - 0.0).abs() < 1e-4);
1453 assert!((BounceOut.interpolate(1.0) - 1.0).abs() < 1e-4);
1454 assert!((BounceOut.interpolate(1.0 / 2.75) - 1.0).abs() < 1e-4);
1456 assert!((BounceOut.interpolate(0.5) - 0.765625).abs() < 1e-4);
1457 }
1458
1459 #[test]
1460 fn monospline_duplicate_times_stay_finite() {
1461 let s = MonoSpline::new(vec![0.0, 0.5, 0.5, 1.0], vec![0.0, 1.0, 1.0, 2.0]);
1464 for t in [0.0, 0.25, 0.5, 0.75, 1.0] {
1465 assert!(s.evaluate(t).is_finite(), "t={t}");
1466 }
1467 }
1468
1469 #[test]
1470 fn scroll_offset_sanitizes_nan() {
1471 let st = crate::scroll::ScrollState::new();
1473 st.set_viewport_height(100.0);
1474 st.set_content_height(1000.0);
1475 st.set_offset(f32::NAN);
1476 assert!(st.get().is_finite());
1477 assert_eq!(st.get(), 0.0);
1478 let leftover = st.scroll_immediate(f32::NAN);
1479 assert!(leftover.is_finite());
1480 assert!(st.get().is_finite());
1481 }
1482}