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 self.current = self.start;
814 return false;
815 }
816
817 let clamped = self.progress.clamp(-1e6, 1e6);
819 self.current = self.start + clamped;
820 true
821 }
822}
823
824pub trait Interpolate {
825 fn interpolate(&self, other: &Self, t: f32) -> Self;
826}
827
828impl Interpolate for f32 {
829 fn interpolate(&self, other: &Self, t: f32) -> Self {
830 self + (other - self) * t
831 }
832}
833
834impl Interpolate for crate::Color {
835 fn interpolate(&self, other: &Self, t: f32) -> Self {
836 let lerp = |a: u8, b: u8| {
837 (a as f32 + (b as f32 - a as f32) * t)
838 .round()
839 .clamp(0.0, 255.0) as u8
840 };
841 crate::Color(
842 lerp(self.0, other.0),
843 lerp(self.1, other.1),
844 lerp(self.2, other.2),
845 lerp(self.3, other.3),
846 )
847 }
848}
849
850impl Interpolate for crate::Vec2 {
851 fn interpolate(&self, other: &Self, t: f32) -> Self {
852 crate::Vec2 {
853 x: self.x.interpolate(&other.x, t),
854 y: self.y.interpolate(&other.y, t),
855 }
856 }
857}
858
859impl Interpolate for crate::Size {
860 fn interpolate(&self, other: &Self, t: f32) -> Self {
861 crate::Size {
862 width: self.width.interpolate(&other.width, t),
863 height: self.height.interpolate(&other.height, t),
864 }
865 }
866}
867
868impl Interpolate for crate::Rect {
869 fn interpolate(&self, other: &Self, t: f32) -> Self {
870 crate::Rect {
871 x: self.x.interpolate(&other.x, t),
872 y: self.y.interpolate(&other.y, t),
873 w: self.w.interpolate(&other.w, t),
874 h: self.h.interpolate(&other.h, t),
875 }
876 }
877}
878
879pub trait Clock: Send + Sync + 'static {
881 fn now(&self) -> Instant;
882}
883
884pub struct SystemClock;
885impl Clock for SystemClock {
886 fn now(&self) -> Instant {
887 Instant::now()
888 }
889}
890
891thread_local! {
892 static CLOCK: RefCell<Box<dyn Clock>> = RefCell::new(Box::new(SystemClock) as Box<dyn Clock>);
893}
894
895pub fn set_clock(clock: Box<dyn Clock>) {
897 CLOCK.with(|c| *c.borrow_mut() = clock);
898}
899pub fn ensure_system_clock() {
901 }
903
904#[derive(Clone)]
906pub struct TestClock {
907 pub t: Instant,
908}
909impl Clock for TestClock {
910 fn now(&self) -> Instant {
911 self.t
912 }
913}
914
915pub struct AnimatedValue<T: Interpolate + Clone> {
924 current: T,
925 target: T,
926 start: T,
927 spec: AnimationSpec,
928 keyframes: Option<KeyframesSpec<T>>,
929 iteration: u32,
930 start_time: Option<Instant>,
931 progress: f32,
933 velocity: f32,
934 spring_v0: f32,
936 last_update: Option<Instant>,
937}
938
939impl<T: Interpolate + Clone> AnimatedValue<T> {
940 pub fn new(initial: T, spec: AnimationSpec) -> Self {
941 Self {
942 current: initial.clone(),
943 target: initial.clone(),
944 start: initial,
945 spec,
946 keyframes: None,
947 iteration: 0,
948 start_time: None,
949 progress: 1.0,
950 velocity: 0.0,
951 spring_v0: 0.0,
952 last_update: None,
953 }
954 }
955
956 pub fn set_spec(&mut self, spec: AnimationSpec) {
957 self.spec = spec;
958 }
959
960 pub fn set_keyframes(&mut self, keyframes: KeyframesSpec<T>) {
963 self.keyframes = Some(keyframes);
964 self.start_time = Some(now());
965 self.last_update = None;
966 self.iteration = 0;
967 }
968
969 pub fn set_target(&mut self, target: T) {
970 self.keyframes = None;
975 self.start = self.current.clone();
976 self.target = target;
977 self.start_time = Some(now());
978 self.last_update = None;
979 self.iteration = 0;
980 if self.spec.spring.is_some() {
981 self.progress = 0.0;
983 self.spring_v0 = self.velocity;
984 }
985 }
986
987 pub fn snap_to(&mut self, value: T) {
989 self.current = value.clone();
990 self.target = value.clone();
991 self.start = value;
992 self.keyframes = None;
993 self.start_time = None;
994 self.progress = 1.0;
995 self.velocity = 0.0;
996 self.spring_v0 = 0.0;
997 self.last_update = None;
998 }
999
1000 pub fn update(&mut self) -> bool {
1001 let spring_spec = self.spec.spring;
1002 let mut still = if let Some(spring) = spring_spec {
1003 self.update_spring(&spring)
1004 } else if self.keyframes.is_some() {
1005 self.update_keyframes()
1006 } else {
1007 self.update_tween()
1008 };
1009
1010 if !still {
1011 if let Some(repeat) = &self.spec.repeat {
1012 let maxed = repeat
1013 .iterations
1014 .is_some_and(|max| self.iteration + 1 >= max);
1015 if !maxed {
1016 self.iteration += 1;
1017 if repeat.reverse {
1018 std::mem::swap(&mut self.start, &mut self.target);
1019 }
1020 self.progress = 0.0;
1021 self.velocity = 0.0;
1022 self.start_time = Some(now());
1023 self.last_update = None;
1024 still = true;
1025 }
1026 }
1027 }
1028
1029 still
1030 }
1031
1032 fn update_keyframes(&mut self) -> bool {
1033 let start = match self.start_time {
1034 Some(s) => s,
1035 None => return false,
1036 };
1037 let elapsed = now().saturating_duration_since(start);
1038 if elapsed < self.spec.delay {
1039 return true;
1040 }
1041 let animation_time = elapsed - self.spec.delay;
1042 if animation_time >= self.spec.duration {
1043 if let Some(ref kf) = self.keyframes {
1044 self.current = kf.evaluate(1.0);
1045 }
1046 self.start_time = None;
1047 return false;
1048 }
1049 let t = (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1050 let eased_t = self.spec.easing.interpolate(t).clamp(0.0, 1.0);
1051 if let Some(ref kf) = self.keyframes {
1052 self.current = kf.evaluate(eased_t);
1053 }
1054 true
1055 }
1056
1057 fn update_spring(&mut self, spring: &SpringSpec) -> bool {
1058 let start = match self.start_time {
1059 Some(s) => s,
1060 None => return false,
1061 };
1062
1063 let now = now();
1064 let elapsed = now.saturating_duration_since(start);
1065
1066 if elapsed < self.spec.delay {
1068 return true;
1069 }
1070
1071 let t = elapsed.as_secs_f32().max(0.0);
1072 let (progress, velocity) = spring_analytical(
1073 spring.damping_ratio,
1074 spring.stiffness,
1075 t,
1076 0.0,
1077 self.spring_v0,
1078 );
1079 let progress = progress.clamp(-0.1, 2.0);
1080
1081 if (progress - 1.0).abs() < spring.settle_progress
1082 && velocity.abs() < spring.settle_velocity
1083 {
1084 self.progress = 1.0;
1085 self.velocity = 0.0;
1086 self.spring_v0 = 0.0;
1087 self.current = self.target.clone();
1088 self.start_time = None;
1089 self.last_update = None;
1090 return false;
1091 }
1092
1093 self.progress = progress;
1094 self.velocity = velocity;
1095 self.current = self.start.interpolate(&self.target, self.progress);
1096 true
1097 }
1098
1099 fn update_tween(&mut self) -> bool {
1100 if let Some(start) = self.start_time {
1101 let elapsed = now().saturating_duration_since(start);
1102
1103 if elapsed < self.spec.delay {
1104 return true;
1105 }
1106
1107 let animation_time = elapsed - self.spec.delay;
1108
1109 if animation_time >= self.spec.duration {
1110 self.current = self.target.clone();
1111 self.start_time = None;
1112 return false;
1113 }
1114
1115 let t =
1116 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
1117 let eased_t = self.spec.easing.interpolate(t);
1118 let eased_t = eased_t.clamp(0.0, 1.0);
1119
1120 self.current = self.start.interpolate(&self.target, eased_t);
1121 true
1122 } else {
1123 false
1124 }
1125 }
1126
1127 pub fn get(&self) -> &T {
1128 &self.current
1129 }
1130
1131 pub fn is_animating(&self) -> bool {
1132 self.start_time.is_some()
1133 }
1134
1135 pub fn has_keyframes(&self) -> bool {
1136 self.keyframes.is_some()
1137 }
1138}
1139
1140#[cfg(test)]
1141mod tests {
1142 use super::*;
1143
1144 fn assert_in_out(ease: Easing) {
1145 assert!((ease.interpolate(0.0) - 0.0).abs() < 1e-4, "{ease:?} in(0)");
1147 assert!((ease.interpolate(1.0) - 1.0).abs() < 1e-4, "{ease:?} in(1)");
1148 }
1149
1150 #[test]
1151 fn godot_eases_pass_through_endpoints() {
1152 use Easing::*;
1153 for ease in [
1154 CubicIn,
1155 CubicOut,
1156 CubicInOut,
1157 QuartIn,
1158 QuartOut,
1159 QuartInOut,
1160 QuintIn,
1161 QuintOut,
1162 QuintInOut,
1163 SineIn,
1164 SineOut,
1165 SineInOut,
1166 ExpoIn,
1167 ExpoOut,
1168 ExpoInOut,
1169 CircIn,
1170 CircOut,
1171 CircInOut,
1172 BackIn,
1173 BackOut,
1174 BackInOut,
1175 ElasticIn,
1176 ElasticOut,
1177 ElasticInOut,
1178 BounceIn,
1179 BounceOut,
1180 BounceInOut,
1181 ] {
1182 assert_in_out(ease);
1183 }
1184 }
1185
1186 #[test]
1187 fn easing_direction_is_sane() {
1188 use Easing::*;
1189 let mid = [CubicOut, QuartOut, QuintOut, SineOut, ExpoOut, CircOut];
1190 for e in mid {
1191 assert!(e.interpolate(0.5) <= 1.0, "{e:?} stays below 1 at mid");
1192 assert!(e.interpolate(0.5) > 0.5, "{e:?} is ease-out at mid");
1193 }
1194 for e in [CubicIn, QuartIn, QuintIn, SineIn, ExpoIn, CircIn] {
1195 assert!(e.interpolate(0.5) < 0.5, "{e:?} is ease-in at mid");
1196 }
1197 for e in [BackOut, ElasticOut] {
1199 assert!(e.interpolate(0.5) > 1.0, "{e:?} overshoots");
1200 }
1201 for e in [BackIn, ElasticIn] {
1202 assert!(e.interpolate(0.5) < 0.0, "{e:?} undershoots");
1203 }
1204 }
1205
1206 #[test]
1207 fn bounce_matches_known_values() {
1208 use Easing::*;
1209 assert!((BounceOut.interpolate(0.0) - 0.0).abs() < 1e-4);
1210 assert!((BounceOut.interpolate(1.0) - 1.0).abs() < 1e-4);
1211 assert!((BounceOut.interpolate(1.0 / 2.75) - 1.0).abs() < 1e-4);
1213 assert!((BounceOut.interpolate(0.5) - 0.765625).abs() < 1e-4);
1214 }
1215}