1use parking_lot::RwLock;
2use std::sync::OnceLock;
3use std::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)]
11pub enum Easing {
12 Linear,
13 EaseIn,
14 EaseOut,
15 EaseInOut,
16 SpringCrit {
18 omega: f32,
19 },
20 SpringGentle,
22 SpringBouncy,
24}
25
26impl Easing {
27 pub fn interpolate(&self, t: f32) -> f32 {
28 match self {
29 Easing::Linear => t,
30 Easing::EaseIn => t * t,
31 Easing::EaseOut => t * (2.0 - t),
32 Easing::EaseInOut => {
33 if t < 0.5 {
34 2.0 * t * t
35 } else {
36 -1.0 + (4.0 - 2.0 * t) * t
37 }
38 }
39 Easing::SpringCrit { omega } => {
40 let w = (*omega).max(0.0);
41 let tt = t.max(0.0);
42 1.0 - (1.0 + w * tt) * (-(w * tt)).exp()
44 }
45 Easing::SpringGentle => spring_underdamped_normalized(t, 0.5, 8.0),
46 Easing::SpringBouncy => spring_underdamped_normalized(t, 0.2, 12.0),
47 }
48 }
49}
50
51fn spring_underdamped_normalized(t: f32, zeta: f32, omega: f32) -> f32 {
52 let tt = t.max(0.0);
53 let z = zeta.clamp(0.0, 0.999);
54 let w = omega.max(0.0);
55 let wd = w * (1.0 - z * z).sqrt();
56 let exp_term = (-z * w * tt).exp();
57 let cos_term = (wd * tt).cos();
58 let sin_term = (wd * tt).sin();
59 let c = z / (1.0 - z * z).sqrt();
61 let y = 1.0 - exp_term * (cos_term + c * sin_term);
62 y.clamp(0.0, 1.0)
63}
64
65#[derive(Clone, Copy, Debug)]
66pub struct AnimationSpec {
67 pub duration: Duration,
68 pub easing: Easing,
69 pub delay: Duration,
70}
71
72impl Default for AnimationSpec {
73 fn default() -> Self {
74 Self {
75 duration: Duration::from_millis(300),
76 easing: Easing::EaseInOut,
77 delay: Duration::ZERO,
78 }
79 }
80}
81
82impl AnimationSpec {
83 pub fn tween(duration: Duration, easing: Easing) -> Self {
84 Self {
85 duration,
86 easing,
87 delay: Duration::ZERO,
88 }
89 }
90 pub fn spring_crit(omega: f32, duration: Duration) -> Self {
92 Self {
93 duration,
94 easing: Easing::SpringCrit { omega },
95 delay: Duration::ZERO,
96 }
97 }
98 pub fn spring_gentle() -> Self {
100 Self {
101 duration: Duration::from_millis(450),
102 easing: Easing::SpringGentle,
103 delay: Duration::ZERO,
104 }
105 }
106 pub fn spring_bouncy() -> Self {
108 Self {
109 duration: Duration::from_millis(700),
110 easing: Easing::SpringBouncy,
111 delay: Duration::ZERO,
112 }
113 }
114
115 pub fn fast() -> Self {
116 Self {
117 duration: Duration::from_millis(150),
118 easing: Easing::EaseOut,
119 delay: Duration::ZERO,
120 }
121 }
122
123 pub fn slow() -> Self {
124 Self {
125 duration: Duration::from_millis(600),
126 easing: Easing::EaseInOut,
127 delay: Duration::ZERO,
128 }
129 }
130}
131
132pub trait Interpolate {
133 fn interpolate(&self, other: &Self, t: f32) -> Self;
134}
135
136impl Interpolate for f32 {
137 fn interpolate(&self, other: &Self, t: f32) -> Self {
138 self + (other - self) * t
139 }
140}
141
142impl Interpolate for crate::Color {
143 fn interpolate(&self, other: &Self, t: f32) -> Self {
144 crate::Color(
145 (self.0 as f32 + (other.0 as f32 - self.0 as f32) * t) as u8,
146 (self.1 as f32 + (other.1 as f32 - self.1 as f32) * t) as u8,
147 (self.2 as f32 + (other.2 as f32 - self.2 as f32) * t) as u8,
148 (self.3 as f32 + (other.3 as f32 - self.3 as f32) * t) as u8,
149 )
150 }
151}
152
153pub trait Clock: Send + Sync + 'static {
155 fn now(&self) -> Instant;
156}
157
158pub struct SystemClock;
159impl Clock for SystemClock {
160 fn now(&self) -> Instant {
161 Instant::now()
162 }
163}
164
165static CLOCK: OnceLock<RwLock<Box<dyn Clock>>> = OnceLock::new();
166
167pub fn set_clock(clock: Box<dyn Clock>) {
169 let lock = CLOCK.get_or_init(|| RwLock::new(Box::new(SystemClock) as Box<dyn Clock>));
170 *lock.write() = clock;
171}
172pub(crate) fn ensure_system_clock() {
174 let lock = CLOCK.get_or_init(|| RwLock::new(Box::new(SystemClock) as Box<dyn Clock>));
175}
176
177#[derive(Clone)]
179pub struct TestClock {
180 pub t: Instant,
181}
182impl Clock for TestClock {
183 fn now(&self) -> Instant {
184 self.t
185 }
186}
187
188pub struct AnimatedValue<T: Interpolate + Clone> {
190 current: T,
191 target: T,
192 start: T,
193 spec: AnimationSpec,
194 start_time: Option<Instant>,
195}
196
197impl<T: Interpolate + Clone> AnimatedValue<T> {
198 pub fn new(initial: T, spec: AnimationSpec) -> Self {
199 Self {
200 current: initial.clone(),
201 target: initial.clone(),
202 start: initial,
203 spec,
204 start_time: None,
205 }
206 }
207
208 pub fn set_target(&mut self, target: T) {
209 if self.start_time.is_some() {
210 self.update();
211 self.start = self.current.clone();
212 } else {
213 self.start = self.current.clone();
214 }
215
216 self.target = target;
217 self.start_time = Some(now());
218 }
219
220 pub fn update(&mut self) -> bool {
221 if let Some(start) = self.start_time {
222 let elapsed = now().saturating_duration_since(start);
223
224 if elapsed < self.spec.delay {
225 return true; }
227
228 let animation_time = elapsed - self.spec.delay;
229
230 if animation_time >= self.spec.duration {
231 self.current = self.target.clone();
233 self.start_time = None;
234 return false;
235 }
236
237 let t =
238 (animation_time.as_secs_f32() / self.spec.duration.as_secs_f32()).clamp(0.0, 1.0);
239 let eased_t = self.spec.easing.interpolate(t);
240
241 let eased_t = eased_t.clamp(0.0, 1.0);
242
243 self.current = self.start.interpolate(&self.target, eased_t);
244 true
245 } else {
246 false
247 }
248 }
249
250 pub fn get(&self) -> &T {
251 &self.current
252 }
253
254 pub fn is_animating(&self) -> bool {
255 self.start_time.is_some()
256 }
257}