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