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
136impl Easing {
137 pub fn interpolate(&self, t: f32) -> f32 {
138 match self {
139 Easing::Linear => t,
140 Easing::EaseIn => t * t,
141 Easing::EaseOut => t * (2.0 - t),
142 Easing::EaseInOut => {
143 if t < 0.5 {
144 2.0 * t * t
145 } else {
146 -1.0 + (4.0 - 2.0 * t) * t
147 }
148 }
149 Easing::SpringCrit { omega } => {
150 let w = (*omega).max(0.0);
151 let tt = t.max(0.0);
152 1.0 - (1.0 + w * tt) * (-(w * tt)).exp()
154 }
155 Easing::SpringGentle => spring_underdamped_normalized(t, 0.5, 8.0),
156 Easing::SpringBouncy => spring_underdamped_normalized(t, 0.2, 12.0),
157 Easing::FastOutSlowIn => eval_cubic_bezier(0.4, 0.0, 0.2, 1.0, t),
158 Easing::Custom(cb) => eval_cubic_bezier(cb.p1x, cb.p1y, cb.p2x, cb.p2y, t),
159 Easing::CubicIn => t * t * t,
160 Easing::CubicOut => {
161 let u = t - 1.0;
162 u * u * u + 1.0
163 }
164 Easing::CubicInOut => {
165 if t < 0.5 {
166 4.0 * t * t * t
167 } else {
168 let u = 2.0 * t - 2.0;
169 u * u * u / 2.0 + 1.0
170 }
171 }
172 Easing::QuartIn => t * t * t * t,
173 Easing::QuartOut => {
174 let u = t - 1.0;
175 1.0 - u * u * u * u
176 }
177 Easing::QuartInOut => {
178 if t < 0.5 {
179 8.0 * t * t * t * t
180 } else {
181 let u = -2.0 * t + 2.0;
182 1.0 - u * u * u * u / 2.0
183 }
184 }
185 Easing::QuintIn => t * t * t * t * t,
186 Easing::QuintOut => {
187 let u = t - 1.0;
188 u * u * u * u * u + 1.0
189 }
190 Easing::QuintInOut => {
191 if t < 0.5 {
192 16.0 * t * t * t * t * t
193 } else {
194 let u = -2.0 * t + 2.0;
195 1.0 - u * u * u * u * u / 2.0
196 }
197 }
198 Easing::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
199 Easing::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
200 Easing::SineInOut => 0.5 * (1.0 - (t * std::f32::consts::PI).cos()),
201 Easing::ExpoIn => {
202 if t <= 0.0 {
203 0.0
204 } else {
205 2.0f32.powf(10.0 * t - 10.0)
206 }
207 }
208 Easing::ExpoOut => {
209 if t >= 1.0 {
210 1.0
211 } else {
212 1.0 - 2.0f32.powf(-10.0 * t)
213 }
214 }
215 Easing::ExpoInOut => {
216 if t <= 0.0 {
217 0.0
218 } else if t >= 1.0 {
219 1.0
220 } else if t < 0.5 {
221 2.0f32.powf(20.0 * t - 10.0) / 2.0
222 } else {
223 (2.0 - 2.0f32.powf(-20.0 * t + 10.0)) / 2.0
224 }
225 }
226 Easing::CircIn => 1.0 - (1.0 - t * t).sqrt(),
227 Easing::CircOut => (1.0 - (t - 1.0) * (t - 1.0)).sqrt(),
228 Easing::CircInOut => {
229 if t < 0.5 {
230 (1.0 - (1.0 - 4.0 * t * t).sqrt()) / 2.0
231 } else {
232 ((1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0)).sqrt() + 1.0) / 2.0
233 }
234 }
235 Easing::BackIn => {
236 const C1: f32 = 1.70158;
237 const C3: f32 = C1 + 1.0;
238 C3 * t * t * t - C1 * t * t
239 }
240 Easing::BackOut => {
241 const C1: f32 = 1.70158;
242 const C3: f32 = C1 + 1.0;
243 let u = t - 1.0;
244 1.0 + C3 * u * u * u + C1 * u * u
245 }
246 Easing::BackInOut => {
247 const C1: f32 = 1.70158;
248 const C3: f32 = C1 + 1.0;
249 if t < 0.5 {
250 let u = 2.0 * t;
251 (C3 * u * u * u - C1 * u * u) / 2.0
252 } else {
253 let u = 2.0 * t - 2.0;
254 (C3 * u * u * u + C1 * u * u) / 2.0 + 1.0
255 }
256 }
257 Easing::ElasticIn => {
258 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
259 if t <= 0.0 {
260 0.0
261 } else if t >= 1.0 {
262 1.0
263 } else {
264 -(2.0f32.powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * C4).sin())
265 }
266 }
267 Easing::ElasticOut => {
268 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
269 if t <= 0.0 {
270 0.0
271 } else if t >= 1.0 {
272 1.0
273 } else {
274 2.0f32.powf(-10.0 * t) * ((10.0 * t - 0.75) * C4).sin() + 1.0
275 }
276 }
277 Easing::ElasticInOut => {
278 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
279 if t <= 0.0 {
280 0.0
281 } else if t >= 1.0 {
282 1.0
283 } else if t < 0.5 {
284 -(2.0f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C4).sin()) / 2.0
285 } else {
286 2.0f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C4).sin() / 2.0 + 1.0
287 }
288 }
289 Easing::BounceIn => 1.0 - bounce_out(1.0 - t),
290 Easing::BounceOut => bounce_out(t),
291 Easing::BounceInOut => {
292 if t < 0.5 {
293 (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
294 } else {
295 (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
296 }
297 }
298 }
299 }
300}
301
302fn bounce_out(t: f32) -> f32 {
304 const N1: f32 = 7.5625;
305 const D1: f32 = 2.75;
306 if t < 1.0 / D1 {
307 N1 * t * t
308 } else if t < 2.0 / D1 {
309 let t = t - 1.5 / D1;
310 N1 * t * t + 0.75
311 } else if t < 2.5 / D1 {
312 let t = t - 2.25 / D1;
313 N1 * t * t + 0.9375
314 } else {
315 let t = t - 2.625 / D1;
316 N1 * t * t + 0.984375
317 }
318}
319
320fn eval_cubic_bezier(p1x: f32, p1y: f32, p2x: f32, p2y: f32, t: f32) -> f32 {
324 let t = t.clamp(0.0, 1.0);
325 if t <= 0.0 {
326 return 0.0;
327 }
328 if t >= 1.0 {
329 return 1.0;
330 }
331 let mut u = t;
332 for _ in 0..6 {
333 let omu = 1.0 - u;
334 let x = 3.0 * omu * omu * u * p1x + 3.0 * omu * u * u * p2x + u * u * u;
335 let dx = 3.0 * omu * omu * p1x + 6.0 * omu * u * (p2x - p1x) + 3.0 * u * u * (1.0 - p2x);
336 if dx.abs() < 1e-10 {
337 break;
338 }
339 u -= (x - t) / dx;
340 u = u.clamp(0.0, 1.0);
341 }
342 let omu = 1.0 - u;
343 3.0 * omu * omu * u * p1y + 3.0 * omu * u * u * p2y + u * u * u
344}
345
346fn hermite_interpolate(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
352 let x2 = x * x;
353 let x3 = x2 * x;
354 h * t1 * (x - 2.0 * x2 + x3) + h * t2 * (x3 - x2) + y1 - (3.0 * x2 - 2.0 * x3) * (y1 - y2)
355}
356
357#[allow(dead_code)]
359fn hermite_differential(h: f32, x: f32, y1: f32, y2: f32, t1: f32, t2: f32) -> f32 {
360 let x2 = x * x;
361 h * (t1 - 2.0 * x * (2.0 * t1 + t2) + 3.0 * (t1 + t2) * x2) - 6.0 * (x - x2) * (y1 - y2)
362}
363
364#[derive(Clone, Debug)]
369pub struct MonoSpline {
370 times: Vec<f32>,
371 values: Vec<f32>,
372 tangents: Vec<f32>,
373}
374
375impl MonoSpline {
376 pub fn new(times: Vec<f32>, values: Vec<f32>) -> Self {
380 assert!(times.len() >= 2, "MonoSpline requires at least 2 keyframes");
381 assert_eq!(times.len(), values.len());
382 let n = times.len();
383 let mut tangents = vec![0.0; n];
384
385 let mut slopes = vec![0.0; n.saturating_sub(1)];
387 for i in 0..n - 1 {
388 let dt = times[i + 1] - times[i];
389 slopes[i] = (values[i + 1] - values[i]) / dt;
390 }
391
392 tangents[0] = slopes[0];
394 for i in 1..n - 1 {
395 tangents[i] = (slopes[i - 1] + slopes[i]) * 0.5;
396 }
397 tangents[n - 1] = slopes[n - 2];
398
399 for i in 0..n - 1 {
401 if slopes[i] == 0.0 {
402 tangents[i] = 0.0;
403 tangents[i + 1] = 0.0;
404 } else {
405 let a = tangents[i] / slopes[i];
406 let b = tangents[i + 1] / slopes[i];
407 let h = (a * a + b * b).sqrt();
408 if h > 9.0 {
409 let t = 3.0 / h;
410 tangents[i] = t * a * slopes[i];
411 tangents[i + 1] = t * b * slopes[i];
412 }
413 }
414 }
415
416 Self {
417 times,
418 values,
419 tangents,
420 }
421 }
422
423 pub fn evaluate(&self, t: f32) -> f32 {
426 let n = self.times.len();
427 let first = self.times[0];
428 let last = self.times[n - 1];
429
430 if t <= first {
431 return self.values[0] + (t - first) * self.tangents[0];
432 }
433 if t >= last {
434 return self.values[n - 1] + (t - last) * self.tangents[n - 1];
435 }
436
437 for i in 0..n - 1 {
438 if t >= self.times[i] && t <= self.times[i + 1] {
439 let h = self.times[i + 1] - self.times[i];
440 let x = (t - self.times[i]) / h;
441 return hermite_interpolate(
442 h,
443 x,
444 self.values[i],
445 self.values[i + 1],
446 self.tangents[i],
447 self.tangents[i + 1],
448 );
449 }
450 }
451
452 self.values[n - 1] }
454}
455
456fn spring_analytical(zeta: f32, stiffness: f32, t: f32, x0: f32, v0: f32) -> (f32, f32) {
458 if t <= 0.0 {
459 return (x0, v0);
460 }
461
462 let omega = if stiffness > 0.0 {
463 stiffness.sqrt()
464 } else {
465 return (x0 + v0 * t, v0);
466 };
467
468 let zeta = zeta.max(0.0);
469 let exp = (-zeta * omega * t).exp();
470 let a = 1.0 - x0; if (zeta - 1.0).abs() < 1e-6 {
473 let b = v0 + omega * a;
475 let progress = 1.0 - (a + b * t) * exp;
476 let velocity = (a * omega - b + b * omega * t) * exp;
477 (progress, velocity)
478 } else if zeta < 1.0 {
479 let wd = omega * (1.0 - zeta * zeta).sqrt();
481 let c = (v0 + zeta * omega * a) / wd;
482 let cos_wd = (wd * t).cos();
483 let sin_wd = (wd * t).sin();
484 let env = a * cos_wd + c * sin_wd;
485 let progress = 1.0 - exp * env;
486 let velocity =
487 exp * ((zeta * omega * a - wd * c) * cos_wd + (zeta * omega * c + wd * a) * sin_wd);
488 (progress, velocity)
489 } else {
490 let wd = omega * (zeta * zeta - 1.0).sqrt();
492 let d = (v0 + zeta * omega * a) / wd;
493 let cosh_wd = (wd * t).cosh();
494 let sinh_wd = (wd * t).sinh();
495 let env = a * cosh_wd + d * sinh_wd;
496 let progress = 1.0 - exp * env;
497 let velocity =
498 exp * ((zeta * omega * a - wd * d) * cosh_wd + (zeta * omega * d - wd * a) * sinh_wd);
499 (progress, velocity)
500 }
501}
502
503fn spring_underdamped_normalized(t: f32, zeta: f32, omega: f32) -> f32 {
504 let tt = t.max(0.0);
505 let z = zeta.clamp(0.0, 0.999);
506 let w = omega.max(0.0);
507 let wd = w * (1.0 - z * z).sqrt();
508 let exp_term = (-z * w * tt).exp();
509 let cos_term = (wd * tt).cos();
510 let sin_term = (wd * tt).sin();
511 let c = z / (1.0 - z * z).sqrt();
513 let y = 1.0 - exp_term * (cos_term + c * sin_term);
514 y.clamp(0.0, 1.0)
515}
516
517#[derive(Clone, Copy, Debug)]
518pub struct AnimationSpec {
519 pub duration: Duration,
520 pub easing: Easing,
521 pub delay: Duration,
522 pub spring: Option<SpringSpec>,
524 pub repeat: Option<RepeatableSpec>,
526}
527
528impl Default for AnimationSpec {
529 fn default() -> Self {
530 Self {
531 duration: Duration::from_millis(300),
532 easing: Easing::EaseInOut,
533 delay: Duration::ZERO,
534 spring: None,
535 repeat: None,
536 }
537 }
538}
539
540impl AnimationSpec {
541 pub fn tween(duration: Duration, easing: Easing) -> Self {
542 Self {
543 duration,
544 easing,
545 delay: Duration::ZERO,
546 spring: None,
547 repeat: None,
548 }
549 }
550 pub fn spring(spring: SpringSpec) -> Self {
552 Self {
553 duration: Duration::ZERO,
554 easing: Easing::Linear,
555 delay: Duration::ZERO,
556 spring: Some(spring),
557 repeat: None,
558 }
559 }
560 pub fn spring_gentle() -> Self {
562 Self::spring(SpringSpec::gentle())
563 }
564 pub fn spring_bouncy() -> Self {
566 Self::spring(SpringSpec::bouncy())
567 }
568 pub fn spring_crit(omega: f32) -> Self {
570 Self::spring(SpringSpec::new(1.0, omega * omega))
571 }
572
573 pub fn fast() -> Self {
574 Self {
575 duration: Duration::from_millis(150),
576 easing: Easing::EaseOut,
577 delay: Duration::ZERO,
578 spring: None,
579 repeat: None,
580 }
581 }
582
583 pub fn slow() -> Self {
584 Self {
585 duration: Duration::from_millis(600),
586 easing: Easing::EaseInOut,
587 delay: Duration::ZERO,
588 spring: None,
589 repeat: None,
590 }
591 }
592
593 pub fn repeated(mut self, repeat: RepeatableSpec) -> Self {
596 self.repeat = Some(repeat);
597 self
598 }
599}
600
601#[derive(Clone, Debug)]
606pub struct KeyframesSpec<T: Clone> {
607 pub keyframes: Vec<(f32, T, Option<Easing>)>,
610}
611
612impl<T: Clone + Interpolate> KeyframesSpec<T> {
613 pub fn new(keyframes: Vec<(f32, T)>) -> Self {
614 let with_easing = keyframes.into_iter().map(|(t, v)| (t, v, None)).collect();
615 Self {
616 keyframes: with_easing,
617 }
618 }
619
620 pub fn with_easing(mut self, easing: Easing) -> Self {
622 if let Some(last) = self.keyframes.last_mut() {
623 last.2 = Some(easing);
624 }
625 self
626 }
627
628 pub fn evaluate(&self, t: f32) -> T {
629 let t = t.clamp(0.0, 1.0);
630 let kf = &self.keyframes;
631 if kf.is_empty() {
632 panic!("KeyframesSpec must have at least one keyframe");
633 }
634
635 for i in 0..kf.len() - 1 {
637 let (t0, _, _) = kf[i];
638 let (t1, ref v1, easing) = kf[i + 1];
639 if t >= t0 && t <= t1 {
640 let segment_t = if (t1 - t0).abs() < f32::EPSILON {
641 1.0
642 } else {
643 (t - t0) / (t1 - t0)
644 };
645 let eased_t = match easing {
646 Some(e) => e.interpolate(segment_t),
647 None => segment_t,
648 };
649 return kf[i].1.interpolate(v1, eased_t);
650 }
651 }
652 kf.last().unwrap().1.clone()
653 }
654}
655
656#[derive(Clone, Debug)]
663pub struct SplineKeyframes {
664 spline: MonoSpline,
665}
666
667impl SplineKeyframes {
668 pub fn new(keyframes: Vec<(f32, f32)>) -> Self {
673 assert!(
674 keyframes.len() >= 2,
675 "SplineKeyframes requires at least 2 keyframes"
676 );
677 let times: Vec<f32> = keyframes.iter().map(|(t, _)| *t).collect();
678 let values: Vec<f32> = keyframes.iter().map(|(_, v)| *v).collect();
679 Self {
680 spline: MonoSpline::new(times, values),
681 }
682 }
683
684 pub fn evaluate(&self, t: f32) -> f32 {
686 self.spline.evaluate(t.clamp(0.0, 1.0))
687 }
688}
689
690#[derive(Clone, Copy, Debug)]
695pub struct RepeatableSpec {
696 pub iterations: Option<u32>,
698 pub reverse: bool,
700 pub delay_between: Duration,
702}
703
704impl Default for RepeatableSpec {
705 fn default() -> Self {
706 Self {
707 iterations: None,
708 reverse: false,
709 delay_between: Duration::ZERO,
710 }
711 }
712}
713
714impl RepeatableSpec {
715 pub fn new(iterations: u32) -> Self {
716 Self {
717 iterations: Some(iterations),
718 reverse: false,
719 delay_between: Duration::ZERO,
720 }
721 }
722
723 pub fn infinite() -> Self {
724 Self {
725 iterations: None,
726 reverse: false,
727 delay_between: Duration::ZERO,
728 }
729 }
730
731 pub fn reverse(mut self) -> Self {
732 self.reverse = true;
733 self
734 }
735
736 pub fn delay_between(mut self, d: Duration) -> Self {
737 self.delay_between = d;
738 self
739 }
740}
741
742#[derive(Clone, Copy, Debug)]
746pub struct DecayAnimationSpec {
747 pub friction: f32,
749 pub stop_threshold: f32,
751}
752
753impl Default for DecayAnimationSpec {
754 fn default() -> Self {
755 Self {
756 friction: 0.8,
757 stop_threshold: 1.0,
758 }
759 }
760}
761
762impl DecayAnimationSpec {
763 pub fn new(friction: f32) -> Self {
764 Self {
765 friction: friction.clamp(0.01, 1.0),
766 stop_threshold: 1.0,
767 }
768 }
769}
770
771impl AnimatedValue<f32> {
772 pub fn update_decay(&mut self, friction: f32, stop_threshold: f32) -> bool {
774 let _start = match self.start_time {
775 Some(s) => s,
776 None => return false,
777 };
778
779 let now = now();
780 let dt = match self.last_update {
781 Some(last) => now.saturating_duration_since(last).as_secs_f32().min(0.05),
782 None => 0.0,
783 };
784 self.last_update = Some(now);
785
786 if dt <= 0.0 {
787 return true;
788 }
789
790 if self.velocity.abs() < stop_threshold {
791 self.velocity = 0.0;
792 self.start_time = None;
793 return false;
794 }
795
796 self.velocity *= friction.powf(dt * 60.0);
797 let delta = self.velocity * dt;
798 let new_progress = self.progress + delta;
804 self.progress = new_progress;
805 if self.progress.abs() < 0.001 && self.velocity.abs() < stop_threshold {
810 self.progress = 0.0;
811 self.velocity = 0.0;
812 self.start_time = None;
813 return false;
814 }
815
816 self.current = self.start.interpolate(&self.target, self.progress);
817 true
818 }
819}
820
821pub trait Interpolate {
822 fn interpolate(&self, other: &Self, t: f32) -> Self;
823}
824
825impl Interpolate for f32 {
826 fn interpolate(&self, other: &Self, t: f32) -> Self {
827 self + (other - self) * t
828 }
829}
830
831impl Interpolate for crate::Color {
832 fn interpolate(&self, other: &Self, t: f32) -> Self {
833 let lerp = |a: u8, b: u8| {
834 (a as f32 + (b as f32 - a as f32) * t)
835 .round()
836 .clamp(0.0, 255.0) as u8
837 };
838 crate::Color(
839 lerp(self.0, other.0),
840 lerp(self.1, other.1),
841 lerp(self.2, other.2),
842 lerp(self.3, other.3),
843 )
844 }
845}
846
847impl Interpolate for crate::Vec2 {
848 fn interpolate(&self, other: &Self, t: f32) -> Self {
849 crate::Vec2 {
850 x: self.x.interpolate(&other.x, t),
851 y: self.y.interpolate(&other.y, t),
852 }
853 }
854}
855
856impl Interpolate for crate::Size {
857 fn interpolate(&self, other: &Self, t: f32) -> Self {
858 crate::Size {
859 width: self.width.interpolate(&other.width, t),
860 height: self.height.interpolate(&other.height, t),
861 }
862 }
863}
864
865impl Interpolate for crate::Rect {
866 fn interpolate(&self, other: &Self, t: f32) -> Self {
867 crate::Rect {
868 x: self.x.interpolate(&other.x, t),
869 y: self.y.interpolate(&other.y, t),
870 w: self.w.interpolate(&other.w, t),
871 h: self.h.interpolate(&other.h, t),
872 }
873 }
874}
875
876pub trait Clock: Send + Sync + 'static {
878 fn now(&self) -> Instant;
879}
880
881pub struct SystemClock;
882impl Clock for SystemClock {
883 fn now(&self) -> Instant {
884 Instant::now()
885 }
886}
887
888thread_local! {
889 static CLOCK: RefCell<Box<dyn Clock>> = RefCell::new(Box::new(SystemClock) as Box<dyn Clock>);
890}
891
892pub fn set_clock(clock: Box<dyn Clock>) {
894 CLOCK.with(|c| *c.borrow_mut() = clock);
895}
896pub fn ensure_system_clock() {
898 }
900
901#[derive(Clone)]
903pub struct TestClock {
904 pub t: Instant,
905}
906impl Clock for TestClock {
907 fn now(&self) -> Instant {
908 self.t
909 }
910}
911
912pub struct AnimatedValue<T: Interpolate + Clone> {
921 current: T,
922 target: T,
923 start: T,
924 spec: AnimationSpec,
925 keyframes: Option<KeyframesSpec<T>>,
926 iteration: u32,
927 start_time: Option<Instant>,
928 progress: f32,
930 velocity: f32,
931 spring_v0: f32,
933 last_update: Option<Instant>,
934}
935
936impl<T: Interpolate + Clone> AnimatedValue<T> {
937 pub fn new(initial: T, spec: AnimationSpec) -> Self {
938 Self {
939 current: initial.clone(),
940 target: initial.clone(),
941 start: initial,
942 spec,
943 keyframes: None,
944 iteration: 0,
945 start_time: None,
946 progress: 1.0,
947 velocity: 0.0,
948 spring_v0: 0.0,
949 last_update: None,
950 }
951 }
952
953 pub fn set_spec(&mut self, spec: AnimationSpec) {
954 self.spec = spec;
955 }
956
957 pub fn set_keyframes(&mut self, keyframes: KeyframesSpec<T>) {
960 self.keyframes = Some(keyframes);
961 self.start_time = Some(now());
962 self.last_update = None;
963 self.iteration = 0;
964 }
965
966 pub fn set_target(&mut self, target: T) {
967 self.keyframes = None;
972 self.start = self.current.clone();
973 self.target = target;
974 self.start_time = Some(now());
975 self.last_update = None;
976 self.iteration = 0;
977 if self.spec.spring.is_some() {
978 self.progress = 0.0;
980 self.spring_v0 = self.velocity;
981 }
982 }
983
984 pub fn snap_to(&mut self, value: T) {
986 self.current = value.clone();
987 self.target = value.clone();
988 self.start = value;
989 self.keyframes = None;
990 self.start_time = None;
991 self.progress = 1.0;
992 self.velocity = 0.0;
993 self.spring_v0 = 0.0;
994 self.last_update = None;
995 }
996
997 pub fn update(&mut self) -> bool {
998 let spring_spec = self.spec.spring;
999 let mut still = if let Some(spring) = spring_spec {
1000 self.update_spring(&spring)
1001 } else if self.keyframes.is_some() {
1002 self.update_keyframes()
1003 } else {
1004 self.update_tween()
1005 };
1006
1007 if !still {
1008 if let Some(repeat) = &self.spec.repeat {
1010 let maxed = repeat
1011 .iterations
1012 .is_some_and(|max| self.iteration + 1 >= max);
1013 if !maxed {
1014 self.iteration += 1;
1015 if repeat.reverse {
1016 std::mem::swap(&mut self.start, &mut self.target);
1017 }
1018 self.progress = 0.0;
1019 self.velocity = 0.0;
1020 self.start_time = Some(now());
1021 self.last_update = None;
1022 still = true;
1023 }
1024 }
1025 }
1026
1027 still
1028 }
1029
1030 fn update_keyframes(&mut self) -> bool {
1031 let start = match self.start_time {
1032 Some(s) => s,
1033 None => return false,
1034 };
1035 let elapsed = now().saturating_duration_since(start);
1036 if elapsed < self.spec.delay {
1037 return true;
1038 }
1039 let animation_time = elapsed - self.spec.delay;
1040 if animation_time >= self.spec.duration {
1041 if let Some(ref kf) = self.keyframes {
1042 self.current = kf.evaluate(1.0);
1043 }
1044 self.start_time = None;
1045 return false;
1046 }
1047 let t = (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1048 let eased_t = self.spec.easing.interpolate(t).clamp(0.0, 1.0);
1049 if let Some(ref kf) = self.keyframes {
1050 self.current = kf.evaluate(eased_t);
1051 }
1052 true
1053 }
1054
1055 fn update_spring(&mut self, spring: &SpringSpec) -> bool {
1056 let start = match self.start_time {
1057 Some(s) => s,
1058 None => return false,
1059 };
1060
1061 let now = now();
1062 let elapsed = now.saturating_duration_since(start);
1063
1064 if elapsed < self.spec.delay {
1066 return true;
1067 }
1068
1069 let t = elapsed.as_secs_f32().max(0.0);
1070 let (progress, velocity) = spring_analytical(
1071 spring.damping_ratio,
1072 spring.stiffness,
1073 t,
1074 0.0,
1075 self.spring_v0,
1076 );
1077 let progress = progress.clamp(-0.1, 2.0);
1078
1079 if (progress - 1.0).abs() < spring.settle_progress
1081 && velocity.abs() < spring.settle_velocity
1082 {
1083 self.progress = 1.0;
1084 self.velocity = 0.0;
1085 self.spring_v0 = 0.0;
1086 self.current = self.target.clone();
1087 self.start_time = None;
1088 self.last_update = None;
1089 return false;
1090 }
1091
1092 self.progress = progress;
1093 self.velocity = velocity;
1094 self.current = self.start.interpolate(&self.target, self.progress);
1095 true
1096 }
1097
1098 fn update_tween(&mut self) -> bool {
1099 if let Some(start) = self.start_time {
1100 let elapsed = now().saturating_duration_since(start);
1101
1102 if elapsed < self.spec.delay {
1103 return true;
1104 }
1105
1106 let animation_time = elapsed - self.spec.delay;
1107
1108 if animation_time >= self.spec.duration {
1109 self.current = self.target.clone();
1110 self.start_time = None;
1111 return false;
1112 }
1113
1114 let t =
1115 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1116 let eased_t = self.spec.easing.interpolate(t);
1117 let eased_t = eased_t.clamp(0.0, 1.0);
1118
1119 self.current = self.start.interpolate(&self.target, eased_t);
1120 true
1121 } else {
1122 false
1123 }
1124 }
1125
1126 pub fn get(&self) -> &T {
1127 &self.current
1128 }
1129
1130 pub fn is_animating(&self) -> bool {
1131 self.start_time.is_some()
1132 }
1133
1134 pub fn has_keyframes(&self) -> bool {
1135 self.keyframes.is_some()
1136 }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142
1143 fn assert_in_out(ease: Easing) {
1144 assert!((ease.interpolate(0.0) - 0.0).abs() < 1e-4, "{ease:?} in(0)");
1146 assert!((ease.interpolate(1.0) - 1.0).abs() < 1e-4, "{ease:?} in(1)");
1147 }
1148
1149 #[test]
1150 fn godot_eases_pass_through_endpoints() {
1151 use Easing::*;
1152 for ease in [
1153 CubicIn,
1154 CubicOut,
1155 CubicInOut,
1156 QuartIn,
1157 QuartOut,
1158 QuartInOut,
1159 QuintIn,
1160 QuintOut,
1161 QuintInOut,
1162 SineIn,
1163 SineOut,
1164 SineInOut,
1165 ExpoIn,
1166 ExpoOut,
1167 ExpoInOut,
1168 CircIn,
1169 CircOut,
1170 CircInOut,
1171 BackIn,
1172 BackOut,
1173 BackInOut,
1174 ElasticIn,
1175 ElasticOut,
1176 ElasticInOut,
1177 BounceIn,
1178 BounceOut,
1179 BounceInOut,
1180 ] {
1181 assert_in_out(ease);
1182 }
1183 }
1184
1185 #[test]
1186 fn easing_direction_is_sane() {
1187 use Easing::*;
1188 let mid = [CubicOut, QuartOut, QuintOut, SineOut, ExpoOut, CircOut];
1189 for e in mid {
1190 assert!(e.interpolate(0.5) <= 1.0, "{e:?} stays below 1 at mid");
1191 assert!(e.interpolate(0.5) > 0.5, "{e:?} is ease-out at mid");
1192 }
1193 for e in [CubicIn, QuartIn, QuintIn, SineIn, ExpoIn, CircIn] {
1194 assert!(e.interpolate(0.5) < 0.5, "{e:?} is ease-in at mid");
1195 }
1196 for e in [BackOut, ElasticOut] {
1198 assert!(e.interpolate(0.5) > 1.0, "{e:?} overshoots");
1199 }
1200 for e in [BackIn, ElasticIn] {
1201 assert!(e.interpolate(0.5) < 0.0, "{e:?} undershoots");
1202 }
1203 }
1204
1205 #[test]
1206 fn bounce_matches_known_values() {
1207 use Easing::*;
1208 assert!((BounceOut.interpolate(0.0) - 0.0).abs() < 1e-4);
1209 assert!((BounceOut.interpolate(1.0) - 1.0).abs() < 1e-4);
1210 assert!((BounceOut.interpolate(1.0 / 2.75) - 1.0).abs() < 1e-4);
1212 assert!((BounceOut.interpolate(0.5) - 0.765625).abs() < 1e-4);
1213 }
1214}