1use parking_lot::RwLock;
2use std::sync::OnceLock;
3use web_time::{Duration, Instant};
4
5pub(crate) fn now() -> Instant {
6 let lock = CLOCK.get_or_init(|| RwLock::new(Box::new(SystemClock) as Box<dyn Clock>));
7 lock.read().now()
8}
9
10#[derive(Clone, Copy, Debug)]
12pub struct SpringSpec {
13 pub damping_ratio: f32,
16 pub stiffness: f32,
18}
19
20impl SpringSpec {
21 pub const fn new(damping_ratio: f32, stiffness: f32) -> Self {
22 Self {
23 damping_ratio,
24 stiffness,
25 }
26 }
27 pub const fn gentle() -> Self {
29 Self::new(0.5, 200.0)
30 }
31 pub const fn bouncy() -> Self {
33 Self::new(0.2, 300.0)
34 }
35 pub const fn crit() -> Self {
37 Self::new(1.0, 200.0)
38 }
39 pub const fn stiff() -> Self {
41 Self::new(0.8, 600.0)
42 }
43}
44
45#[derive(Clone, Copy, Debug)]
46#[non_exhaustive]
47pub enum Easing {
48 Linear,
49 EaseIn,
50 EaseOut,
51 EaseInOut,
52 SpringCrit {
54 omega: f32,
55 },
56 SpringGentle,
58 SpringBouncy,
60 FastOutSlowIn,
63}
64
65impl Easing {
66 pub fn interpolate(&self, t: f32) -> f32 {
67 match self {
68 Easing::Linear => t,
69 Easing::EaseIn => t * t,
70 Easing::EaseOut => t * (2.0 - t),
71 Easing::EaseInOut => {
72 if t < 0.5 {
73 2.0 * t * t
74 } else {
75 -1.0 + (4.0 - 2.0 * t) * t
76 }
77 }
78 Easing::SpringCrit { omega } => {
79 let w = (*omega).max(0.0);
80 let tt = t.max(0.0);
81 1.0 - (1.0 + w * tt) * (-(w * tt)).exp()
83 }
84 Easing::SpringGentle => spring_underdamped_normalized(t, 0.5, 8.0),
85 Easing::SpringBouncy => spring_underdamped_normalized(t, 0.2, 12.0),
86 Easing::FastOutSlowIn => eval_cubic_bezier(0.4, 0.0, 0.2, 1.0, t),
87 }
88 }
89}
90
91fn eval_cubic_bezier(p1x: f32, p1y: f32, p2x: f32, p2y: f32, t: f32) -> f32 {
95 let t = t.clamp(0.0, 1.0);
96 if t <= 0.0 {
97 return 0.0;
98 }
99 if t >= 1.0 {
100 return 1.0;
101 }
102 let mut u = t;
103 for _ in 0..6 {
104 let omu = 1.0 - u;
105 let x = 3.0 * omu * omu * u * p1x + 3.0 * omu * u * u * p2x + u * u * u;
106 let dx = 3.0 * omu * omu * p1x + 6.0 * omu * u * (p2x - p1x) + 3.0 * u * u * (1.0 - p2x);
107 if dx.abs() < 1e-10 {
108 break;
109 }
110 u -= (x - t) / dx;
111 u = u.clamp(0.0, 1.0);
112 }
113 let omu = 1.0 - u;
114 3.0 * omu * omu * u * p1y + 3.0 * omu * u * u * p2y + u * u * u
115}
116
117fn spring_underdamped_normalized(t: f32, zeta: f32, omega: f32) -> f32 {
118 let tt = t.max(0.0);
119 let z = zeta.clamp(0.0, 0.999);
120 let w = omega.max(0.0);
121 let wd = w * (1.0 - z * z).sqrt();
122 let exp_term = (-z * w * tt).exp();
123 let cos_term = (wd * tt).cos();
124 let sin_term = (wd * tt).sin();
125 let c = z / (1.0 - z * z).sqrt();
127 let y = 1.0 - exp_term * (cos_term + c * sin_term);
128 y.clamp(0.0, 1.0)
129}
130
131#[derive(Clone, Copy, Debug)]
132pub struct AnimationSpec {
133 pub duration: Duration,
134 pub easing: Easing,
135 pub delay: Duration,
136 pub spring: Option<SpringSpec>,
138 pub repeat: Option<RepeatableSpec>,
140}
141
142impl Default for AnimationSpec {
143 fn default() -> Self {
144 Self {
145 duration: Duration::from_millis(300),
146 easing: Easing::EaseInOut,
147 delay: Duration::ZERO,
148 spring: None,
149 repeat: None,
150 }
151 }
152}
153
154impl AnimationSpec {
155 pub fn tween(duration: Duration, easing: Easing) -> Self {
156 Self {
157 duration,
158 easing,
159 delay: Duration::ZERO,
160 spring: None,
161 repeat: None,
162 }
163 }
164 pub fn spring(spring: SpringSpec) -> Self {
166 Self {
167 duration: Duration::ZERO,
168 easing: Easing::Linear,
169 delay: Duration::ZERO,
170 spring: Some(spring),
171 repeat: None,
172 }
173 }
174 pub fn spring_gentle() -> Self {
176 Self::spring(SpringSpec::gentle())
177 }
178 pub fn spring_bouncy() -> Self {
180 Self::spring(SpringSpec::bouncy())
181 }
182 pub fn spring_crit(omega: f32) -> Self {
184 Self::spring(SpringSpec::new(1.0, omega * omega))
185 }
186
187 pub fn fast() -> Self {
188 Self {
189 duration: Duration::from_millis(150),
190 easing: Easing::EaseOut,
191 delay: Duration::ZERO,
192 spring: None,
193 repeat: None,
194 }
195 }
196
197 pub fn slow() -> Self {
198 Self {
199 duration: Duration::from_millis(600),
200 easing: Easing::EaseInOut,
201 delay: Duration::ZERO,
202 spring: None,
203 repeat: None,
204 }
205 }
206
207 pub fn repeated(mut self, repeat: RepeatableSpec) -> Self {
210 self.repeat = Some(repeat);
211 self
212 }
213}
214
215#[derive(Clone, Debug)]
220pub struct KeyframesSpec<T: Clone> {
221 pub keyframes: Vec<(f32, T, Option<Easing>)>,
224}
225
226impl<T: Clone + Interpolate> KeyframesSpec<T> {
227 pub fn new(keyframes: Vec<(f32, T)>) -> Self {
228 let with_easing = keyframes.into_iter().map(|(t, v)| (t, v, None)).collect();
229 Self {
230 keyframes: with_easing,
231 }
232 }
233
234 pub fn with_easing(mut self, easing: Easing) -> Self {
236 if let Some(last) = self.keyframes.last_mut() {
237 last.2 = Some(easing);
238 }
239 self
240 }
241
242 pub fn evaluate(&self, t: f32) -> T {
243 let t = t.clamp(0.0, 1.0);
244 let kf = &self.keyframes;
245 if kf.is_empty() {
246 panic!("KeyframesSpec must have at least one keyframe");
247 }
248 for i in 0..kf.len() - 1 {
250 let (t0, _, _) = kf[i];
251 let (t1, ref v1, easing) = kf[i + 1];
252 if t >= t0 && t <= t1 {
253 let segment_t = if (t1 - t0).abs() < f32::EPSILON {
254 1.0
255 } else {
256 (t - t0) / (t1 - t0)
257 };
258 let eased_t = match easing {
259 Some(e) => e.interpolate(segment_t),
260 None => segment_t,
261 };
262 return kf[i].1.interpolate(v1, eased_t);
263 }
264 }
265 kf.last().unwrap().1.clone()
266 }
267}
268
269#[derive(Clone, Copy, Debug)]
274pub struct RepeatableSpec {
275 pub iterations: Option<u32>,
277 pub reverse: bool,
279 pub delay_between: Duration,
281}
282
283impl Default for RepeatableSpec {
284 fn default() -> Self {
285 Self {
286 iterations: None,
287 reverse: false,
288 delay_between: Duration::ZERO,
289 }
290 }
291}
292
293impl RepeatableSpec {
294 pub fn new(iterations: u32) -> Self {
295 Self {
296 iterations: Some(iterations),
297 reverse: false,
298 delay_between: Duration::ZERO,
299 }
300 }
301
302 pub fn infinite() -> Self {
303 Self {
304 iterations: None,
305 reverse: false,
306 delay_between: Duration::ZERO,
307 }
308 }
309
310 pub fn reverse(mut self) -> Self {
311 self.reverse = true;
312 self
313 }
314
315 pub fn delay_between(mut self, d: Duration) -> Self {
316 self.delay_between = d;
317 self
318 }
319}
320
321#[derive(Clone, Copy, Debug)]
325pub struct DecayAnimationSpec {
326 pub friction: f32,
328 pub stop_threshold: f32,
330}
331
332impl Default for DecayAnimationSpec {
333 fn default() -> Self {
334 Self {
335 friction: 0.8,
336 stop_threshold: 1.0,
337 }
338 }
339}
340
341impl DecayAnimationSpec {
342 pub fn new(friction: f32) -> Self {
343 Self {
344 friction: friction.clamp(0.01, 1.0),
345 stop_threshold: 1.0,
346 }
347 }
348}
349
350impl AnimatedValue<f32> {
351 pub fn update_decay(&mut self, friction: f32, stop_threshold: f32) -> bool {
353 let _start = match self.start_time {
354 Some(s) => s,
355 None => return false,
356 };
357
358 let now = now();
359 let dt = match self.last_update {
360 Some(last) => now.saturating_duration_since(last).as_secs_f32().min(0.05),
361 None => 0.0,
362 };
363 self.last_update = Some(now);
364
365 if dt <= 0.0 {
366 return true;
367 }
368
369 if self.velocity.abs() < stop_threshold {
370 self.velocity = 0.0;
371 self.start_time = None;
372 return false;
373 }
374
375 self.velocity *= friction.powf(dt * 60.0);
376 let delta = self.velocity * dt;
377 let new_progress = self.progress + delta;
383 self.progress = new_progress;
384 if self.progress.abs() < 0.001 && self.velocity.abs() < stop_threshold {
389 self.progress = 0.0;
390 self.velocity = 0.0;
391 self.start_time = None;
392 return false;
393 }
394
395 self.current = self.start.interpolate(&self.target, self.progress);
396 true
397 }
398}
399
400pub trait Interpolate {
401 fn interpolate(&self, other: &Self, t: f32) -> Self;
402}
403
404impl Interpolate for f32 {
405 fn interpolate(&self, other: &Self, t: f32) -> Self {
406 self + (other - self) * t
407 }
408}
409
410impl Interpolate for crate::Color {
411 fn interpolate(&self, other: &Self, t: f32) -> Self {
412 let lerp = |a: u8, b: u8| {
413 (a as f32 + (b as f32 - a as f32) * t)
414 .round()
415 .clamp(0.0, 255.0) as u8
416 };
417 crate::Color(
418 lerp(self.0, other.0),
419 lerp(self.1, other.1),
420 lerp(self.2, other.2),
421 lerp(self.3, other.3),
422 )
423 }
424}
425
426impl Interpolate for crate::Vec2 {
427 fn interpolate(&self, other: &Self, t: f32) -> Self {
428 crate::Vec2 {
429 x: self.x.interpolate(&other.x, t),
430 y: self.y.interpolate(&other.y, t),
431 }
432 }
433}
434
435impl Interpolate for crate::Size {
436 fn interpolate(&self, other: &Self, t: f32) -> Self {
437 crate::Size {
438 width: self.width.interpolate(&other.width, t),
439 height: self.height.interpolate(&other.height, t),
440 }
441 }
442}
443
444impl Interpolate for crate::Rect {
445 fn interpolate(&self, other: &Self, t: f32) -> Self {
446 crate::Rect {
447 x: self.x.interpolate(&other.x, t),
448 y: self.y.interpolate(&other.y, t),
449 w: self.w.interpolate(&other.w, t),
450 h: self.h.interpolate(&other.h, t),
451 }
452 }
453}
454
455pub trait Clock: Send + Sync + 'static {
457 fn now(&self) -> Instant;
458}
459
460pub struct SystemClock;
461impl Clock for SystemClock {
462 fn now(&self) -> Instant {
463 Instant::now()
464 }
465}
466
467static CLOCK: OnceLock<RwLock<Box<dyn Clock>>> = OnceLock::new();
468
469pub fn set_clock(clock: Box<dyn Clock>) {
471 let lock = CLOCK.get_or_init(|| RwLock::new(Box::new(SystemClock) as Box<dyn Clock>));
472 *lock.write() = clock;
473}
474pub fn ensure_system_clock() {
476 let _ = CLOCK.get_or_init(|| RwLock::new(Box::new(SystemClock) as Box<dyn Clock>));
477}
478
479#[derive(Clone)]
481pub struct TestClock {
482 pub t: Instant,
483}
484impl Clock for TestClock {
485 fn now(&self) -> Instant {
486 self.t
487 }
488}
489
490pub struct AnimatedValue<T: Interpolate + Clone> {
499 current: T,
500 target: T,
501 start: T,
502 spec: AnimationSpec,
503 keyframes: Option<KeyframesSpec<T>>,
504 iteration: u32,
505 start_time: Option<Instant>,
506 progress: f32,
508 velocity: f32,
509 last_update: Option<Instant>,
510}
511
512impl<T: Interpolate + Clone> AnimatedValue<T> {
513 pub fn new(initial: T, spec: AnimationSpec) -> Self {
514 Self {
515 current: initial.clone(),
516 target: initial.clone(),
517 start: initial,
518 spec,
519 keyframes: None,
520 iteration: 0,
521 start_time: None,
522 progress: 1.0,
523 velocity: 0.0,
524 last_update: None,
525 }
526 }
527
528 pub fn set_spec(&mut self, spec: AnimationSpec) {
529 self.spec = spec;
530 }
531
532 pub fn set_keyframes(&mut self, keyframes: KeyframesSpec<T>) {
535 self.keyframes = Some(keyframes);
536 self.start_time = Some(now());
537 self.last_update = None;
538 self.iteration = 0;
539 }
540
541 pub fn set_target(&mut self, target: T) {
542 if self.start_time.is_some() {
543 self.update();
544 }
545 self.keyframes = None;
546 self.start = self.current.clone();
547 self.target = target;
548 self.start_time = Some(now());
549 self.last_update = None;
550 self.iteration = 0;
551 if self.spec.spring.is_some() {
552 self.progress = 0.0;
554 }
555 }
556
557 pub fn snap_to(&mut self, value: T) {
559 self.current = value.clone();
560 self.target = value.clone();
561 self.start = value;
562 self.keyframes = None;
563 self.start_time = None;
564 self.progress = 1.0;
565 self.velocity = 0.0;
566 self.last_update = None;
567 }
568
569 pub fn update(&mut self) -> bool {
570 let spring_spec = self.spec.spring;
571 let still = if let Some(spring) = spring_spec {
572 self.update_spring(&spring)
573 } else if self.keyframes.is_some() {
574 self.update_keyframes()
575 } else {
576 self.update_tween()
577 };
578
579 if !still {
580 if let Some(repeat) = &self.spec.repeat {
582 let maxed = repeat
583 .iterations
584 .is_some_and(|max| self.iteration + 1 >= max);
585 if !maxed {
586 self.iteration += 1;
587 if repeat.reverse {
588 std::mem::swap(&mut self.start, &mut self.target);
589 }
590 self.progress = 0.0;
591 self.velocity = 0.0;
592 self.start_time = Some(now());
593 self.last_update = None;
594 return true;
595 }
596 }
597 }
598
599 still
600 }
601
602 fn update_keyframes(&mut self) -> bool {
603 let start = match self.start_time {
604 Some(s) => s,
605 None => return false,
606 };
607 let elapsed = now().saturating_duration_since(start);
608 if elapsed < self.spec.delay {
609 return true;
610 }
611 let animation_time = elapsed - self.spec.delay;
612 if animation_time >= self.spec.duration {
613 if let Some(ref kf) = self.keyframes {
614 self.current = kf.evaluate(1.0);
615 }
616 self.start_time = None;
617 return false;
618 }
619 let t = (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
620 let eased_t = self.spec.easing.interpolate(t).clamp(0.0, 1.0);
621 if let Some(ref kf) = self.keyframes {
622 self.current = kf.evaluate(eased_t);
623 }
624 true
625 }
626
627 fn update_spring(&mut self, spring: &SpringSpec) -> bool {
628 let start = match self.start_time {
629 Some(s) => s,
630 None => return false,
631 };
632
633 let now = now();
634 let dt = match self.last_update {
635 Some(last) => now.saturating_duration_since(last).as_secs_f32().min(0.05),
636 None => 0.0,
637 };
638 self.last_update = Some(now);
639
640 let elapsed = now.saturating_duration_since(start);
642 if elapsed < self.spec.delay {
643 return true;
644 }
645
646 if dt <= 0.0 {
647 return true;
648 }
649
650 let k = spring.stiffness;
652 let d = 2.0 * spring.damping_ratio * k.sqrt();
653 let displacement = self.progress - 1.0;
654
655 if displacement.abs() < 0.005 && self.velocity.abs() < 0.1 {
656 self.progress = 1.0;
658 self.velocity = 0.0;
659 self.current = self.target.clone();
660 self.start_time = None;
661 self.last_update = None;
662 return false;
663 }
664
665 let acceleration = -k * displacement - d * self.velocity;
667 self.velocity += acceleration * dt;
668 self.progress += self.velocity * dt;
669
670 self.progress = self.progress.clamp(-0.1, 2.0);
672
673 self.current = self.start.interpolate(&self.target, self.progress);
674 true
675 }
676
677 fn update_tween(&mut self) -> bool {
678 if let Some(start) = self.start_time {
679 let elapsed = now().saturating_duration_since(start);
680
681 if elapsed < self.spec.delay {
682 return true;
683 }
684
685 let animation_time = elapsed - self.spec.delay;
686
687 if animation_time >= self.spec.duration {
688 self.current = self.target.clone();
689 self.start_time = None;
690 return false;
691 }
692
693 let t =
694 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
695 let eased_t = self.spec.easing.interpolate(t);
696 let eased_t = eased_t.clamp(0.0, 1.0);
697
698 self.current = self.start.interpolate(&self.target, eased_t);
699 true
700 } else {
701 false
702 }
703 }
704
705 pub fn get(&self) -> &T {
706 &self.current
707 }
708
709 pub fn is_animating(&self) -> bool {
710 self.start_time.is_some()
711 }
712
713 pub fn has_keyframes(&self) -> bool {
714 self.keyframes.is_some()
715 }
716}