1use std::cell::RefCell;
2use web_time::{Duration, Instant};
3
4pub(crate) fn now() -> Instant {
5 CLOCK.with(|c| c.borrow().now())
6}
7
8#[derive(Clone, Copy, Debug)]
10pub struct SpringSpec {
11 pub damping_ratio: f32,
14 pub stiffness: f32,
16 pub settle_progress: f32,
19 pub settle_velocity: f32,
21}
22
23impl SpringSpec {
24 pub const fn new(damping_ratio: f32, stiffness: f32) -> Self {
25 Self {
26 damping_ratio,
27 stiffness,
28 settle_progress: 0.005,
29 settle_velocity: 0.1,
30 }
31 }
32 pub const fn gentle() -> Self {
34 Self::new(0.5, 200.0)
35 }
36 pub const fn bouncy() -> Self {
38 Self::new(0.2, 300.0)
39 }
40 pub const fn crit() -> Self {
42 Self::new(1.0, 200.0)
43 }
44 pub const fn stiff() -> Self {
46 Self::new(0.8, 600.0)
47 }
48
49 pub const fn with_settle_progress(mut self, threshold: f32) -> Self {
52 self.settle_progress = threshold;
53 self
54 }
55
56 pub const fn with_settle_velocity(mut self, threshold: f32) -> Self {
58 self.settle_velocity = threshold;
59 self
60 }
61}
62
63#[derive(Clone, Copy, Debug)]
66pub struct CubicBezier {
67 pub p1x: f32,
68 pub p1y: f32,
69 pub p2x: f32,
70 pub p2y: f32,
71}
72
73impl CubicBezier {
74 pub const fn new(p1x: f32, p1y: f32, p2x: f32, p2y: f32) -> Self {
75 Self { p1x, p1y, p2x, p2y }
76 }
77}
78
79pub const EASING_EMPHASIZED_DECELERATE: CubicBezier = CubicBezier::new(0.05, 0.7, 0.1, 1.0);
81pub const EASING_STANDARD_DECELERATE: CubicBezier = CubicBezier::new(0.2, 0.0, 0.0, 1.0);
83
84#[derive(Clone, Copy, Debug)]
85#[non_exhaustive]
86pub enum Easing {
87 Linear,
88 EaseIn,
89 EaseOut,
90 EaseInOut,
91 SpringCrit {
93 omega: f32,
94 },
95 SpringGentle,
97 SpringBouncy,
99 FastOutSlowIn,
102 Custom(CubicBezier),
104 CubicIn,
106 CubicOut,
107 CubicInOut,
108 QuartIn,
109 QuartOut,
110 QuartInOut,
111 QuintIn,
112 QuintOut,
113 QuintInOut,
114 SineIn,
115 SineOut,
116 SineInOut,
117 ExpoIn,
118 ExpoOut,
119 ExpoInOut,
120 CircIn,
121 CircOut,
122 CircInOut,
123 BackIn,
125 BackOut,
126 BackInOut,
127 ElasticIn,
129 ElasticOut,
130 ElasticInOut,
131 BounceIn,
132 BounceOut,
133 BounceInOut,
134}
135
136#[derive(Clone, Copy, Debug, Default)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub enum EaseKind {
143 #[default]
144 Linear,
145 QuadOut,
146 CubicOut,
147 BackOut,
148}
149
150impl EaseKind {
151 pub fn apply(self, t: f32) -> f32 {
153 let ease = match self {
154 EaseKind::Linear => Easing::Linear,
155 EaseKind::QuadOut => Easing::EaseOut,
156 EaseKind::CubicOut => Easing::CubicOut,
157 EaseKind::BackOut => Easing::BackOut,
158 };
159 ease.interpolate(t.clamp(0.0, 1.0))
160 }
161}
162
163impl Easing {
164 pub fn interpolate(&self, t: f32) -> f32 {
165 match self {
166 Easing::Linear => t,
167 Easing::EaseIn => t * t,
168 Easing::EaseOut => t * (2.0 - t),
169 Easing::EaseInOut => {
170 if t < 0.5 {
171 2.0 * t * t
172 } else {
173 -1.0 + (4.0 - 2.0 * t) * t
174 }
175 }
176 Easing::SpringCrit { omega } => {
177 let w = (*omega).max(0.0);
178 let tt = t.max(0.0);
179 1.0 - (1.0 + w * tt) * (-(w * tt)).exp()
181 }
182 Easing::SpringGentle => spring_underdamped_normalized(t, 0.5, 8.0),
183 Easing::SpringBouncy => spring_underdamped_normalized(t, 0.2, 12.0),
184 Easing::FastOutSlowIn => eval_cubic_bezier(0.4, 0.0, 0.2, 1.0, t),
185 Easing::Custom(cb) => eval_cubic_bezier(cb.p1x, cb.p1y, cb.p2x, cb.p2y, t),
186 Easing::CubicIn => t * t * t,
187 Easing::CubicOut => {
188 let u = t - 1.0;
189 u * u * u + 1.0
190 }
191 Easing::CubicInOut => {
192 if t < 0.5 {
193 4.0 * t * t * t
194 } else {
195 let u = 2.0 * t - 2.0;
196 u * u * u / 2.0 + 1.0
197 }
198 }
199 Easing::QuartIn => t * t * t * t,
200 Easing::QuartOut => {
201 let u = t - 1.0;
202 1.0 - u * u * u * u
203 }
204 Easing::QuartInOut => {
205 if t < 0.5 {
206 8.0 * t * t * t * t
207 } else {
208 let u = -2.0 * t + 2.0;
209 1.0 - u * u * u * u / 2.0
210 }
211 }
212 Easing::QuintIn => t * t * t * t * t,
213 Easing::QuintOut => {
214 let u = t - 1.0;
215 u * u * u * u * u + 1.0
216 }
217 Easing::QuintInOut => {
218 if t < 0.5 {
219 16.0 * t * t * t * t * t
220 } else {
221 let u = -2.0 * t + 2.0;
222 1.0 - u * u * u * u * u / 2.0
223 }
224 }
225 Easing::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
226 Easing::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
227 Easing::SineInOut => 0.5 * (1.0 - (t * std::f32::consts::PI).cos()),
228 Easing::ExpoIn => {
229 if t <= 0.0 {
230 0.0
231 } else {
232 2.0f32.powf(10.0 * t - 10.0)
233 }
234 }
235 Easing::ExpoOut => {
236 if t >= 1.0 {
237 1.0
238 } else {
239 1.0 - 2.0f32.powf(-10.0 * t)
240 }
241 }
242 Easing::ExpoInOut => {
243 if t <= 0.0 {
244 0.0
245 } else if t >= 1.0 {
246 1.0
247 } else if t < 0.5 {
248 2.0f32.powf(20.0 * t - 10.0) / 2.0
249 } else {
250 (2.0 - 2.0f32.powf(-20.0 * t + 10.0)) / 2.0
251 }
252 }
253 Easing::CircIn => 1.0 - (1.0 - t * t).sqrt(),
254 Easing::CircOut => (1.0 - (t - 1.0) * (t - 1.0)).sqrt(),
255 Easing::CircInOut => {
256 if t < 0.5 {
257 (1.0 - (1.0 - 4.0 * t * t).sqrt()) / 2.0
258 } else {
259 ((1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0)).sqrt() + 1.0) / 2.0
260 }
261 }
262 Easing::BackIn => {
263 const C1: f32 = 1.70158;
264 const C3: f32 = C1 + 1.0;
265 C3 * t * t * t - C1 * t * t
266 }
267 Easing::BackOut => {
268 const C1: f32 = 1.70158;
269 const C3: f32 = C1 + 1.0;
270 let u = t - 1.0;
271 1.0 + C3 * u * u * u + C1 * u * u
272 }
273 Easing::BackInOut => {
274 const C1: f32 = 1.70158;
275 const C3: f32 = C1 + 1.0;
276 if t < 0.5 {
277 let u = 2.0 * t;
278 (C3 * u * u * u - C1 * u * u) / 2.0
279 } else {
280 let u = 2.0 * t - 2.0;
281 (C3 * u * u * u + C1 * u * u) / 2.0 + 1.0
282 }
283 }
284 Easing::ElasticIn => {
285 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
286 if t <= 0.0 {
287 0.0
288 } else if t >= 1.0 {
289 1.0
290 } else {
291 -(2.0f32.powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * C4).sin())
292 }
293 }
294 Easing::ElasticOut => {
295 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
296 if t <= 0.0 {
297 0.0
298 } else if t >= 1.0 {
299 1.0
300 } else {
301 2.0f32.powf(-10.0 * t) * ((10.0 * t - 0.75) * C4).sin() + 1.0
302 }
303 }
304 Easing::ElasticInOut => {
305 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
306 if t <= 0.0 {
307 0.0
308 } else if t >= 1.0 {
309 1.0
310 } else if t < 0.5 {
311 -(2.0f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C4).sin()) / 2.0
312 } else {
313 2.0f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C4).sin() / 2.0 + 1.0
314 }
315 }
316 Easing::BounceIn => 1.0 - bounce_out(1.0 - t),
317 Easing::BounceOut => bounce_out(t),
318 Easing::BounceInOut => {
319 if t < 0.5 {
320 (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
321 } else {
322 (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
323 }
324 }
325 }
326 }
327}
328
329fn bounce_out(t: f32) -> f32 {
331 const N1: f32 = 7.5625;
332 const D1: f32 = 2.75;
333 if t < 1.0 / D1 {
334 N1 * t * t
335 } else if t < 2.0 / D1 {
336 let t = t - 1.5 / D1;
337 N1 * t * t + 0.75
338 } else if t < 2.5 / D1 {
339 let t = t - 2.25 / D1;
340 N1 * t * t + 0.9375
341 } else {
342 let t = t - 2.625 / D1;
343 N1 * t * t + 0.984375
344 }
345}
346
347fn eval_cubic_bezier(p1x: f32, p1y: f32, p2x: f32, p2y: f32, t: f32) -> f32 {
351 let t = t.clamp(0.0, 1.0);
352 if t <= 0.0 {
353 return 0.0;
354 }
355 if t >= 1.0 {
356 return 1.0;
357 }
358 let mut u = t;
359 for _ in 0..6 {
360 let omu = 1.0 - u;
361 let x = 3.0 * omu * omu * u * p1x + 3.0 * omu * u * u * p2x + u * u * u;
362 let dx = 3.0 * omu * omu * p1x + 6.0 * omu * u * (p2x - p1x) + 3.0 * u * u * (1.0 - p2x);
363 if dx.abs() < 1e-10 {
364 break;
365 }
366 u -= (x - t) / dx;
367 u = u.clamp(0.0, 1.0);
368 }
369 let omu = 1.0 - u;
370 3.0 * omu * omu * u * p1y + 3.0 * omu * u * u * p2y + u * u * u
371}
372
373fn hermite_interpolate(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
379 let x2 = x * x;
380 let x3 = x2 * x;
381 h * t1 * (x - 2.0 * x2 + x3) + h * t2 * (x3 - x2) + y1 - (3.0 * x2 - 2.0 * x3) * (y1 - y2)
382}
383
384#[allow(dead_code)]
386fn hermite_differential(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
387 let x2 = x * x;
388 h * (t1 - 2.0 * x * (2.0 * t1 + t2) + 3.0 * (t1 + t2) * x2) - 6.0 * (x - x2) * (y1 - y2)
389}
390
391#[derive(Clone, Debug)]
396pub struct MonoSpline {
397 times: Vec<f32>,
398 values: Vec<f32>,
399 tangents: Vec<f32>,
400}
401
402impl MonoSpline {
403 pub fn new(times: Vec<f32>, values: Vec<f32>) -> Self {
412 assert!(times.len() >= 2, "MonoSpline requires at least 2 keyframes");
413 assert_eq!(times.len(), values.len());
414 assert!(
415 times.iter().all(|t| t.is_finite()),
416 "MonoSpline times must be finite"
417 );
418 assert!(
419 values.iter().all(|v| v.is_finite()),
420 "MonoSpline values must be finite"
421 );
422 if cfg!(debug_assertions) {
423 for w in times.windows(2) {
424 if !(w[1] > w[0]) {
425 log::warn!(
426 "MonoSpline: times not strictly ascending; degenerate segments coerce to zero slope"
427 );
428 break;
429 }
430 }
431 }
432 let n = times.len();
433 let mut tangents = vec![0.0; n];
434
435 let mut slopes = vec![0.0; n.saturating_sub(1)];
437 for i in 0..n - 1 {
438 let dt = times[i + 1] - times[i];
439 if dt > 0.0 {
440 slopes[i] = (values[i + 1] - values[i]) / dt;
441 } else {
442 log::warn!("MonoSpline: non-positive dt at segment {i}; coercing slope to 0");
443 slopes[i] = 0.0;
444 }
445 }
446
447 tangents[0] = slopes[0];
449 for i in 1..n - 1 {
450 tangents[i] = (slopes[i - 1] + slopes[i]) * 0.5;
451 }
452 tangents[n - 1] = slopes[n - 2];
453
454 for i in 0..n - 1 {
456 if slopes[i] == 0.0 {
457 tangents[i] = 0.0;
458 tangents[i + 1] = 0.0;
459 } else {
460 let a = tangents[i] / slopes[i];
461 let b = tangents[i + 1] / slopes[i];
462 let h = (a * a + b * b).sqrt();
463 if h > 9.0 {
464 let t = 3.0 / h;
465 tangents[i] = t * a * slopes[i];
466 tangents[i + 1] = t * b * slopes[i];
467 }
468 }
469 }
470
471 Self {
472 times,
473 values,
474 tangents,
475 }
476 }
477
478 pub fn evaluate(&self, t: f32) -> f32 {
481 let n = self.times.len();
482 let first = self.times[0];
483 let last = self.times[n - 1];
484
485 if t <= first {
486 return self.values[0] + (t - first) * self.tangents[0];
487 }
488 if t >= last {
489 return self.values[n - 1] + (t - last) * self.tangents[n - 1];
490 }
491
492 for i in 0..n - 1 {
493 if t >= self.times[i] && t <= self.times[i + 1] {
494 let h = self.times[i + 1] - self.times[i];
495 let x = (t - self.times[i]) / h;
496 return hermite_interpolate(
497 h,
498 x,
499 self.values[i],
500 self.values[i + 1],
501 self.tangents[i],
502 self.tangents[i + 1],
503 );
504 }
505 }
506
507 self.values[n - 1] }
509}
510
511fn spring_analytical(zeta: f32, stiffness: f32, t: f32, x0: f32, v0: f32) -> (f32, f32) {
513 if t <= 0.0 {
514 return (x0, v0);
515 }
516
517 let omega = if stiffness > 0.0 {
518 stiffness.sqrt()
519 } else {
520 return (x0 + v0 * t, v0);
521 };
522
523 let zeta = zeta.max(0.0);
524 let exp = (-zeta * omega * t).exp();
525 let a = 1.0 - x0; if (zeta - 1.0).abs() < 1e-6 {
528 let b = v0 + omega * a;
530 let progress = 1.0 - (a + b * t) * exp;
531 let velocity = (a * omega - b + b * omega * t) * exp;
532 (progress, velocity)
533 } else if zeta < 1.0 {
534 let wd = omega * (1.0 - zeta * zeta).sqrt();
536 let c = (v0 + zeta * omega * a) / wd;
537 let cos_wd = (wd * t).cos();
538 let sin_wd = (wd * t).sin();
539 let env = a * cos_wd + c * sin_wd;
540 let progress = 1.0 - exp * env;
541 let velocity =
542 exp * ((zeta * omega * a - wd * c) * cos_wd + (zeta * omega * c + wd * a) * sin_wd);
543 (progress, velocity)
544 } else {
545 let wd = omega * (zeta * zeta - 1.0).sqrt();
547 let d = (v0 + zeta * omega * a) / wd;
548 let cosh_wd = (wd * t).cosh();
549 let sinh_wd = (wd * t).sinh();
550 let env = a * cosh_wd + d * sinh_wd;
551 let progress = 1.0 - exp * env;
552 let velocity =
553 exp * ((zeta * omega * a - wd * d) * cosh_wd + (zeta * omega * d - wd * a) * sinh_wd);
554 (progress, velocity)
555 }
556}
557
558fn spring_underdamped_normalized(t: f32, zeta: f32, omega: f32) -> f32 {
559 let tt = t.max(0.0);
560 let z = zeta.clamp(0.0, 0.999);
561 let w = omega.max(0.0);
562 let wd = w * (1.0 - z * z).sqrt();
563 let exp_term = (-z * w * tt).exp();
564 let cos_term = (wd * tt).cos();
565 let sin_term = (wd * tt).sin();
566 let c = z / (1.0 - z * z).sqrt();
568 let y = 1.0 - exp_term * (cos_term + c * sin_term);
569 y.clamp(0.0, 1.0)
570}
571
572#[derive(Clone, Copy, Debug)]
573pub struct AnimationSpec {
574 pub duration: Duration,
575 pub easing: Easing,
576 pub delay: Duration,
577 pub spring: Option<SpringSpec>,
579 pub repeat: Option<RepeatableSpec>,
581}
582
583impl Default for AnimationSpec {
584 fn default() -> Self {
585 Self {
586 duration: Duration::from_millis(300),
587 easing: Easing::EaseInOut,
588 delay: Duration::ZERO,
589 spring: None,
590 repeat: None,
591 }
592 }
593}
594
595impl AnimationSpec {
596 pub fn tween(duration: Duration, easing: Easing) -> Self {
597 Self {
598 duration,
599 easing,
600 delay: Duration::ZERO,
601 spring: None,
602 repeat: None,
603 }
604 }
605 pub fn spring(spring: SpringSpec) -> Self {
607 Self {
608 duration: Duration::ZERO,
609 easing: Easing::Linear,
610 delay: Duration::ZERO,
611 spring: Some(spring),
612 repeat: None,
613 }
614 }
615 pub fn spring_gentle() -> Self {
617 Self::spring(SpringSpec::gentle())
618 }
619 pub fn spring_bouncy() -> Self {
621 Self::spring(SpringSpec::bouncy())
622 }
623 pub fn spring_crit(omega: f32) -> Self {
625 Self::spring(SpringSpec::new(1.0, omega * omega))
626 }
627
628 pub fn fast() -> Self {
629 Self {
630 duration: Duration::from_millis(150),
631 easing: Easing::EaseOut,
632 delay: Duration::ZERO,
633 spring: None,
634 repeat: None,
635 }
636 }
637
638 pub fn slow() -> Self {
639 Self {
640 duration: Duration::from_millis(600),
641 easing: Easing::EaseInOut,
642 delay: Duration::ZERO,
643 spring: None,
644 repeat: None,
645 }
646 }
647
648 pub fn repeated(mut self, repeat: RepeatableSpec) -> Self {
651 self.repeat = Some(repeat);
652 self
653 }
654}
655
656#[derive(Clone, Debug)]
661pub struct KeyframesSpec<T: Clone> {
662 pub keyframes: Vec<(f32, T, Option<Easing>)>,
665}
666
667impl<T: Clone + Interpolate> KeyframesSpec<T> {
668 pub fn new(keyframes: Vec<(f32, T)>) -> Self {
669 let with_easing = keyframes.into_iter().map(|(t, v)| (t, v, None)).collect();
670 Self {
671 keyframes: with_easing,
672 }
673 }
674
675 pub fn with_easing(mut self, easing: Easing) -> Self {
677 if let Some(last) = self.keyframes.last_mut() {
678 last.2 = Some(easing);
679 }
680 self
681 }
682
683 pub fn evaluate(&self, t: f32) -> T {
684 let t = t.clamp(0.0, 1.0);
685 let kf = &self.keyframes;
686 if kf.is_empty() {
687 panic!("KeyframesSpec must have at least one keyframe");
688 }
689
690 for i in 0..kf.len() - 1 {
692 let (t0, _, _) = kf[i];
693 let (t1, ref v1, easing) = kf[i + 1];
694 if t >= t0 && t <= t1 {
695 let segment_t = if (t1 - t0).abs() < f32::EPSILON {
696 1.0
697 } else {
698 (t - t0) / (t1 - t0)
699 };
700 let eased_t = match easing {
701 Some(e) => e.interpolate(segment_t),
702 None => segment_t,
703 };
704 return kf[i].1.interpolate(v1, eased_t);
705 }
706 }
707 kf.last().unwrap().1.clone()
708 }
709}
710
711#[derive(Clone, Debug)]
718pub struct SplineKeyframes {
719 spline: MonoSpline,
720}
721
722impl SplineKeyframes {
723 pub fn new(keyframes: Vec<(f32, f32)>) -> Self {
728 assert!(
729 keyframes.len() >= 2,
730 "SplineKeyframes requires at least 2 keyframes"
731 );
732 let times: Vec<f32> = keyframes.iter().map(|(t, _)| *t).collect();
733 let values: Vec<f32> = keyframes.iter().map(|(_, v)| *v).collect();
734 Self {
735 spline: MonoSpline::new(times, values),
736 }
737 }
738
739 pub fn evaluate(&self, t: f32) -> f32 {
741 self.spline.evaluate(t.clamp(0.0, 1.0))
742 }
743
744 pub fn fingerprint(&self) -> String {
748 format!("{:?}", self.spline)
749 }
750}
751
752#[derive(Clone, Copy, Debug)]
757pub struct RepeatableSpec {
758 pub iterations: Option<u32>,
760 pub reverse: bool,
762 pub delay_between: Duration,
764}
765
766impl Default for RepeatableSpec {
767 fn default() -> Self {
768 Self {
769 iterations: None,
770 reverse: false,
771 delay_between: Duration::ZERO,
772 }
773 }
774}
775
776impl RepeatableSpec {
777 pub fn new(iterations: u32) -> Self {
778 Self {
779 iterations: Some(iterations),
780 reverse: false,
781 delay_between: Duration::ZERO,
782 }
783 }
784
785 pub fn infinite() -> Self {
786 Self {
787 iterations: None,
788 reverse: false,
789 delay_between: Duration::ZERO,
790 }
791 }
792
793 pub fn reverse(mut self) -> Self {
794 self.reverse = true;
795 self
796 }
797
798 pub fn delay_between(mut self, d: Duration) -> Self {
799 self.delay_between = d;
800 self
801 }
802}
803
804#[derive(Clone, Copy, Debug)]
808pub struct DecayAnimationSpec {
809 pub friction: f32,
811 pub stop_threshold: f32,
813}
814
815impl Default for DecayAnimationSpec {
816 fn default() -> Self {
817 Self {
818 friction: 0.8,
819 stop_threshold: 1.0,
820 }
821 }
822}
823
824impl DecayAnimationSpec {
825 pub fn new(friction: f32) -> Self {
826 Self {
827 friction: friction.clamp(0.01, 1.0),
828 stop_threshold: 1.0,
829 }
830 }
831}
832
833impl AnimatedValue<f32> {
834 pub fn update_decay(&mut self, friction: f32, stop_threshold: f32) -> bool {
836 let _start = match self.start_time {
837 Some(s) => s,
838 None => return false,
839 };
840
841 let now = now();
842 let dt = match self.last_update {
843 Some(last) => now.saturating_duration_since(last).as_secs_f32().min(0.05),
844 None => 0.0,
845 };
846 self.last_update = Some(now);
847
848 if dt <= 0.0 {
849 return true;
850 }
851
852 if self.velocity.abs() < stop_threshold {
853 self.velocity = 0.0;
854 self.start_time = None;
855 return false;
856 }
857
858 self.velocity *= friction.powf(dt * 60.0);
859 let delta = self.velocity * dt;
860 let new_progress = self.progress + delta;
866 self.progress = new_progress;
867 if self.progress.abs() < 0.001 && self.velocity.abs() < stop_threshold {
872 self.progress = 0.0;
873 self.velocity = 0.0;
874 self.start_time = None;
875 self.current = self.start;
876 return false;
877 }
878
879 let clamped = self.progress.clamp(-1e6, 1e6);
881 self.current = self.start + clamped;
882 true
883 }
884}
885
886pub trait Interpolate {
887 fn interpolate(&self, other: &Self, t: f32) -> Self;
888}
889
890impl Interpolate for f32 {
891 fn interpolate(&self, other: &Self, t: f32) -> Self {
892 self + (other - self) * t
893 }
894}
895
896impl Interpolate for crate::Color {
897 fn interpolate(&self, other: &Self, t: f32) -> Self {
898 let lerp = |a: u8, b: u8| {
899 (a as f32 + (b as f32 - a as f32) * t)
900 .round()
901 .clamp(0.0, 255.0) as u8
902 };
903 crate::Color(
904 lerp(self.0, other.0),
905 lerp(self.1, other.1),
906 lerp(self.2, other.2),
907 lerp(self.3, other.3),
908 )
909 }
910}
911
912impl Interpolate for crate::Vec2 {
913 fn interpolate(&self, other: &Self, t: f32) -> Self {
914 crate::Vec2 {
915 x: self.x.interpolate(&other.x, t),
916 y: self.y.interpolate(&other.y, t),
917 }
918 }
919}
920
921impl Interpolate for crate::Size {
922 fn interpolate(&self, other: &Self, t: f32) -> Self {
923 crate::Size {
924 width: self.width.interpolate(&other.width, t),
925 height: self.height.interpolate(&other.height, t),
926 }
927 }
928}
929
930impl Interpolate for crate::Rect {
931 fn interpolate(&self, other: &Self, t: f32) -> Self {
932 crate::Rect {
933 x: self.x.interpolate(&other.x, t),
934 y: self.y.interpolate(&other.y, t),
935 w: self.w.interpolate(&other.w, t),
936 h: self.h.interpolate(&other.h, t),
937 }
938 }
939}
940
941pub trait Clock: Send + Sync + 'static {
943 fn now(&self) -> Instant;
944}
945
946pub struct SystemClock;
947impl Clock for SystemClock {
948 fn now(&self) -> Instant {
949 Instant::now()
950 }
951}
952
953thread_local! {
954 static CLOCK: RefCell<Box<dyn Clock>> = RefCell::new(Box::new(SystemClock) as Box<dyn Clock>);
955}
956
957pub fn set_clock(clock: Box<dyn Clock>) {
959 CLOCK.with(|c| *c.borrow_mut() = clock);
960}
961pub fn ensure_system_clock() {
963 }
965
966#[derive(Clone)]
968pub struct TestClock {
969 pub t: Instant,
970}
971impl Clock for TestClock {
972 fn now(&self) -> Instant {
973 self.t
974 }
975}
976
977pub struct AnimatedValue<T: Interpolate + Clone> {
986 current: T,
987 target: T,
988 start: T,
989 spec: AnimationSpec,
990 keyframes: Option<KeyframesSpec<T>>,
991 iteration: u32,
992 start_time: Option<Instant>,
993 progress: f32,
995 velocity: f32,
996 spring_v0: f32,
998 last_update: Option<Instant>,
999}
1000
1001impl<T: Interpolate + Clone> AnimatedValue<T> {
1002 pub fn new(initial: T, spec: AnimationSpec) -> Self {
1003 Self {
1004 current: initial.clone(),
1005 target: initial.clone(),
1006 start: initial,
1007 spec,
1008 keyframes: None,
1009 iteration: 0,
1010 start_time: None,
1011 progress: 1.0,
1012 velocity: 0.0,
1013 spring_v0: 0.0,
1014 last_update: None,
1015 }
1016 }
1017
1018 pub fn set_spec(&mut self, spec: AnimationSpec) {
1019 self.spec = spec;
1020 }
1021
1022 pub fn set_keyframes(&mut self, keyframes: KeyframesSpec<T>) {
1025 self.keyframes = Some(keyframes);
1026 self.start_time = Some(now());
1027 self.last_update = None;
1028 self.iteration = 0;
1029 }
1030
1031 pub fn set_target(&mut self, target: T) {
1032 self.keyframes = None;
1037 self.start = self.current.clone();
1038 self.target = target;
1039 self.start_time = Some(now());
1040 self.last_update = None;
1041 self.iteration = 0;
1042 if self.spec.spring.is_some() {
1043 self.progress = 0.0;
1045 self.spring_v0 = self.velocity;
1046 }
1047 }
1048
1049 pub fn snap_to(&mut self, value: T) {
1051 self.current = value.clone();
1052 self.target = value.clone();
1053 self.start = value;
1054 self.keyframes = None;
1055 self.start_time = None;
1056 self.progress = 1.0;
1057 self.velocity = 0.0;
1058 self.spring_v0 = 0.0;
1059 self.last_update = None;
1060 }
1061
1062 pub fn update(&mut self) -> bool {
1063 let spring_spec = self.spec.spring;
1064 let mut still = if let Some(spring) = spring_spec {
1065 self.update_spring(&spring)
1066 } else if self.keyframes.is_some() {
1067 self.update_keyframes()
1068 } else {
1069 self.update_tween()
1070 };
1071
1072 if !still && let Some(repeat) = &self.spec.repeat {
1073 let maxed = repeat
1074 .iterations
1075 .is_some_and(|max| self.iteration + 1 >= max);
1076 if !maxed {
1077 self.iteration += 1;
1078 if repeat.reverse {
1079 std::mem::swap(&mut self.start, &mut self.target);
1080 }
1081 self.progress = 0.0;
1082 self.velocity = 0.0;
1083 self.start_time = Some(now());
1084 self.last_update = None;
1085 still = true;
1086 }
1087 }
1088
1089 still
1090 }
1091
1092 fn update_keyframes(&mut self) -> bool {
1093 let start = match self.start_time {
1094 Some(s) => s,
1095 None => return false,
1096 };
1097 let elapsed = now().saturating_duration_since(start);
1098 if elapsed < self.spec.delay {
1099 return true;
1100 }
1101 let animation_time = elapsed - self.spec.delay;
1102 if animation_time >= self.spec.duration {
1103 if let Some(ref kf) = self.keyframes {
1104 self.current = kf.evaluate(1.0);
1105 }
1106 self.start_time = None;
1107 return false;
1108 }
1109 let t = (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1110 let eased_t = self.spec.easing.interpolate(t);
1111 if let Some(ref kf) = self.keyframes {
1112 self.current = kf.evaluate(eased_t);
1113 }
1114 true
1115 }
1116
1117 fn update_spring(&mut self, spring: &SpringSpec) -> bool {
1118 let start = match self.start_time {
1119 Some(s) => s,
1120 None => return false,
1121 };
1122
1123 let now = now();
1124 let elapsed = now.saturating_duration_since(start);
1125
1126 if elapsed < self.spec.delay {
1128 return true;
1129 }
1130
1131 let t = elapsed.as_secs_f32().max(0.0);
1132 let (progress, velocity) = spring_analytical(
1133 spring.damping_ratio,
1134 spring.stiffness,
1135 t,
1136 0.0,
1137 self.spring_v0,
1138 );
1139 let progress = progress.clamp(-0.1, 2.0);
1140
1141 if (progress - 1.0).abs() < spring.settle_progress
1142 && velocity.abs() < spring.settle_velocity
1143 {
1144 self.progress = 1.0;
1145 self.velocity = 0.0;
1146 self.spring_v0 = 0.0;
1147 self.current = self.target.clone();
1148 self.start_time = None;
1149 self.last_update = None;
1150 return false;
1151 }
1152
1153 self.progress = progress;
1154 self.velocity = velocity;
1155 self.current = self.start.interpolate(&self.target, self.progress);
1156 true
1157 }
1158
1159 fn update_tween(&mut self) -> bool {
1160 if let Some(start) = self.start_time {
1161 let elapsed = now().saturating_duration_since(start);
1162
1163 if elapsed < self.spec.delay {
1164 return true;
1165 }
1166
1167 let animation_time = elapsed - self.spec.delay;
1168
1169 if animation_time >= self.spec.duration {
1170 self.current = self.target.clone();
1171 self.start_time = None;
1172 return false;
1173 }
1174
1175 let t =
1176 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1177 let eased_t = self.spec.easing.interpolate(t);
1178
1179 self.current = self.start.interpolate(&self.target, eased_t);
1180 true
1181 } else {
1182 false
1183 }
1184 }
1185
1186 pub fn get(&self) -> &T {
1187 &self.current
1188 }
1189
1190 pub fn is_animating(&self) -> bool {
1191 self.start_time.is_some()
1192 }
1193
1194 pub fn has_keyframes(&self) -> bool {
1195 self.keyframes.is_some()
1196 }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202
1203 fn assert_in_out(ease: Easing) {
1204 assert!((ease.interpolate(0.0) - 0.0).abs() < 1e-4, "{ease:?} in(0)");
1206 assert!((ease.interpolate(1.0) - 1.0).abs() < 1e-4, "{ease:?} in(1)");
1207 }
1208
1209 #[test]
1210 fn godot_eases_pass_through_endpoints() {
1211 use Easing::*;
1212 for ease in [
1213 CubicIn,
1214 CubicOut,
1215 CubicInOut,
1216 QuartIn,
1217 QuartOut,
1218 QuartInOut,
1219 QuintIn,
1220 QuintOut,
1221 QuintInOut,
1222 SineIn,
1223 SineOut,
1224 SineInOut,
1225 ExpoIn,
1226 ExpoOut,
1227 ExpoInOut,
1228 CircIn,
1229 CircOut,
1230 CircInOut,
1231 BackIn,
1232 BackOut,
1233 BackInOut,
1234 ElasticIn,
1235 ElasticOut,
1236 ElasticInOut,
1237 BounceIn,
1238 BounceOut,
1239 BounceInOut,
1240 ] {
1241 assert_in_out(ease);
1242 }
1243 }
1244
1245 #[test]
1246 fn easing_direction_is_sane() {
1247 use Easing::*;
1248 let mid = [CubicOut, QuartOut, QuintOut, SineOut, ExpoOut, CircOut];
1249 for e in mid {
1250 assert!(e.interpolate(0.5) <= 1.0, "{e:?} stays below 1 at mid");
1251 assert!(e.interpolate(0.5) > 0.5, "{e:?} is ease-out at mid");
1252 }
1253 for e in [CubicIn, QuartIn, QuintIn, SineIn, ExpoIn, CircIn] {
1254 assert!(e.interpolate(0.5) < 0.5, "{e:?} is ease-in at mid");
1255 }
1256 for e in [BackOut, ElasticOut] {
1258 assert!(e.interpolate(0.5) > 1.0, "{e:?} overshoots");
1259 }
1260 for e in [BackIn, ElasticIn] {
1261 assert!(e.interpolate(0.5) < 0.0, "{e:?} undershoots");
1262 }
1263 }
1264
1265 #[test]
1266 fn ease_kind_endpoints_hold() {
1267 for ease in [
1268 EaseKind::Linear,
1269 EaseKind::QuadOut,
1270 EaseKind::CubicOut,
1271 EaseKind::BackOut,
1272 ] {
1273 assert!(ease.apply(0.0).abs() < 1e-4);
1274 assert!((ease.apply(1.0) - 1.0).abs() < 1e-4);
1275 }
1276 assert!(EaseKind::QuadOut.apply(0.5) > 0.5);
1277 }
1278
1279 #[test]
1280 fn bounce_matches_known_values() {
1281 use Easing::*;
1282 assert!((BounceOut.interpolate(0.0) - 0.0).abs() < 1e-4);
1283 assert!((BounceOut.interpolate(1.0) - 1.0).abs() < 1e-4);
1284 assert!((BounceOut.interpolate(1.0 / 2.75) - 1.0).abs() < 1e-4);
1286 assert!((BounceOut.interpolate(0.5) - 0.765625).abs() < 1e-4);
1287 }
1288
1289 #[test]
1290 fn monospline_duplicate_times_stay_finite() {
1291 let s = MonoSpline::new(vec![0.0, 0.5, 0.5, 1.0], vec![0.0, 1.0, 1.0, 2.0]);
1294 for t in [0.0, 0.25, 0.5, 0.75, 1.0] {
1295 assert!(s.evaluate(t).is_finite(), "t={t}");
1296 }
1297 }
1298
1299 #[test]
1300 fn scroll_offset_sanitizes_nan() {
1301 let st = crate::scroll::ScrollState::new();
1303 st.set_viewport_height(100.0);
1304 st.set_content_height(1000.0);
1305 st.set_offset(f32::NAN);
1306 assert!(st.get().is_finite());
1307 assert_eq!(st.get(), 0.0);
1308 let leftover = st.scroll_immediate(f32::NAN);
1309 assert!(leftover.is_finite());
1310 assert!(st.get().is_finite());
1311 }
1312}