1use std::sync::OnceLock;
2use std::time::{Duration, Instant};
3
4pub(crate) fn now() -> Instant {
5 CLOCK.get().map(|c| c.now()).unwrap_or_else(Instant::now)
6}
7
8#[derive(Clone, Copy, Debug)]
9pub enum Easing {
10 Linear,
11 EaseIn,
12 EaseOut,
13 EaseInOut,
14 Spring { damping: f32, stiffness: f32 },
15}
16
17impl Easing {
18 pub fn interpolate(&self, t: f32) -> f32 {
19 match self {
20 Easing::Linear => t,
21 Easing::EaseIn => t * t,
22 Easing::EaseOut => t * (2.0 - t),
23 Easing::EaseInOut => {
24 if t < 0.5 {
25 2.0 * t * t
26 } else {
27 -1.0 + (4.0 - 2.0 * t) * t
28 }
29 }
30 Easing::Spring { damping, stiffness } => {
31 let omega = (stiffness / damping).sqrt();
33 let zeta = damping / (2.0 * (stiffness * damping).sqrt());
34
35 if zeta < 1.0 {
36 let omega_d = omega * (1.0 - zeta * zeta).sqrt();
38 let t = t * 2.0; 1.0 - ((-zeta * omega * t).exp() * (omega_d * t).cos())
40 } else {
41 t * (2.0 - t)
43 }
44 }
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug)]
50pub struct AnimationSpec {
51 pub duration: Duration,
52 pub easing: Easing,
53 pub delay: Duration,
54}
55
56impl Default for AnimationSpec {
57 fn default() -> Self {
58 Self {
59 duration: Duration::from_millis(300),
60 easing: Easing::EaseInOut,
61 delay: Duration::ZERO,
62 }
63 }
64}
65
66impl AnimationSpec {
67 pub fn tween(duration: Duration, easing: Easing) -> Self {
68 Self {
69 duration,
70 easing,
71 delay: Duration::ZERO,
72 }
73 }
74 pub fn spring() -> Self {
75 Self {
76 duration: Duration::from_millis(500),
77 easing: Easing::Spring {
78 damping: 0.8,
79 stiffness: 200.0,
80 },
81 delay: Duration::ZERO,
82 }
83 }
84 pub fn spring_phys(damping: f32, stiffness: f32, duration: Duration) -> Self {
85 Self {
86 duration,
87 easing: Easing::Spring { damping, stiffness },
88 delay: Duration::ZERO,
89 }
90 }
91 pub fn fast() -> Self {
92 Self {
93 duration: Duration::from_millis(150),
94 easing: Easing::EaseOut,
95 delay: Duration::ZERO,
96 }
97 }
98
99 pub fn slow() -> Self {
100 Self {
101 duration: Duration::from_millis(600),
102 easing: Easing::EaseInOut,
103 delay: Duration::ZERO,
104 }
105 }
106}
107
108pub trait Interpolate {
109 fn interpolate(&self, other: &Self, t: f32) -> Self;
110}
111
112impl Interpolate for f32 {
113 fn interpolate(&self, other: &Self, t: f32) -> Self {
114 self + (other - self) * t
115 }
116}
117
118impl Interpolate for crate::Color {
119 fn interpolate(&self, other: &Self, t: f32) -> Self {
120 crate::Color(
121 (self.0 as f32 + (other.0 as f32 - self.0 as f32) * t) as u8,
122 (self.1 as f32 + (other.1 as f32 - self.1 as f32) * t) as u8,
123 (self.2 as f32 + (other.2 as f32 - self.2 as f32) * t) as u8,
124 (self.3 as f32 + (other.3 as f32 - self.3 as f32) * t) as u8,
125 )
126 }
127}
128
129pub trait Clock: Send + Sync + 'static {
131 fn now(&self) -> Instant;
132}
133
134pub struct SystemClock;
135impl Clock for SystemClock {
136 fn now(&self) -> Instant {
137 Instant::now()
138 }
139}
140
141static CLOCK: OnceLock<Box<dyn Clock>> = OnceLock::new();
142
143pub fn set_clock(clock: Box<dyn Clock>) {
145 let _ = CLOCK.set(clock);
146}
147pub(crate) fn ensure_system_clock() {
149 let _ = CLOCK.set(Box::new(SystemClock));
150}
151
152#[derive(Clone)]
154pub struct TestClock {
155 pub t: Instant,
156}
157impl Clock for TestClock {
158 fn now(&self) -> Instant {
159 self.t
160 }
161}
162
163pub struct AnimatedValue<T: Interpolate + Clone> {
165 current: T,
166 target: T,
167 start: T,
168 spec: AnimationSpec,
169 start_time: Option<Instant>,
170}
171
172impl<T: Interpolate + Clone> AnimatedValue<T> {
173 pub fn new(initial: T, spec: AnimationSpec) -> Self {
174 Self {
175 current: initial.clone(),
176 target: initial.clone(),
177 start: initial,
178 spec,
179 start_time: None,
180 }
181 }
182
183 pub fn set_target(&mut self, target: T) {
184 if self.start_time.is_none() {
185 self.start = self.current.clone();
186 }
187 self.target = target;
188 self.start_time = Some(now());
189 }
190
191 pub fn update(&mut self) -> bool {
192 if let Some(start) = self.start_time {
193 let elapsed = now().saturating_duration_since(start);
194
195 if elapsed < self.spec.delay {
196 return true; }
198
199 let animation_time = elapsed - self.spec.delay;
200
201 if animation_time >= self.spec.duration {
202 self.current = self.target.clone();
203 self.start_time = None;
204 return false; }
206
207 let t = animation_time.as_secs_f32() / self.spec.duration.as_secs_f32();
208 let eased_t = self.spec.easing.interpolate(t);
209 self.current = self.start.interpolate(&self.target, eased_t);
210
211 true } else {
213 false }
215 }
216
217 pub fn get(&self) -> &T {
218 &self.current
219 }
220
221 pub fn is_animating(&self) -> bool {
222 self.start_time.is_some()
223 }
224}