1use std::time::Duration;
7
8#[derive(Debug, Clone, Copy, PartialEq, Default)]
10pub enum Easing {
11 Linear,
12 EaseIn,
13 EaseOut,
14 #[default]
15 EaseInOut,
16 EaseInQuad,
17 EaseOutQuad,
18 EaseInOutQuad,
19 EaseInCubic,
20 EaseOutCubic,
21 EaseInOutCubic,
22 Spring {
23 stiffness: f32,
24 damping: f32,
25 },
26}
27
28impl Easing {
29 pub fn apply(&self, t: f32) -> f32 {
31 let t = t.clamp(0.0, 1.0);
32 match self {
33 Self::Linear => t,
34 Self::EaseIn => t * t,
35 Self::EaseOut => t * (2.0 - t),
36 Self::EaseInOut => {
37 if t < 0.5 {
38 2.0 * t * t
39 } else {
40 -1.0 + (4.0 - 2.0 * t) * t
41 }
42 }
43 Self::EaseInQuad => t * t,
44 Self::EaseOutQuad => 1.0 - (1.0 - t) * (1.0 - t),
45 Self::EaseInOutQuad => {
46 if t < 0.5 {
47 2.0 * t * t
48 } else {
49 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
50 }
51 }
52 Self::EaseInCubic => t * t * t,
53 Self::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
54 Self::EaseInOutCubic => {
55 if t < 0.5 {
56 4.0 * t * t * t
57 } else {
58 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
59 }
60 }
61 Self::Spring { stiffness, damping } => {
62 let omega = stiffness.sqrt();
63 let zeta = damping / (2.0 * omega);
64 if zeta < 1.0 {
65 let wd = omega * (1.0 - zeta * zeta).sqrt();
66 1.0 - (-zeta * omega * t).exp()
67 * ((wd * t).cos() + zeta / (1.0 - zeta * zeta).sqrt() * (wd * t).sin())
68 } else {
69 1.0 - (1.0 + omega * t) * (-omega * t).exp()
70 }
71 }
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum AnimationState {
79 Pending,
80 Running,
81 Paused,
82 Completed,
83}
84
85#[derive(Debug, Clone)]
87pub struct Animation {
88 id: String,
89 from: f64,
90 to: f64,
91 duration: Duration,
92 delay: Duration,
93 easing: Easing,
94 repeat: RepeatMode,
95 state: AnimationState,
96 elapsed: Duration,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum RepeatMode {
102 Once,
103 Loop,
104 PingPong,
105 Count(u32),
106}
107
108impl Animation {
109 pub fn new(id: impl Into<String>, from: f64, to: f64, duration: Duration) -> Self {
110 Self {
111 id: id.into(),
112 from,
113 to,
114 duration,
115 delay: Duration::ZERO,
116 easing: Easing::default(),
117 repeat: RepeatMode::Once,
118 state: AnimationState::Pending,
119 elapsed: Duration::ZERO,
120 }
121 }
122
123 pub fn easing(mut self, easing: Easing) -> Self {
124 self.easing = easing;
125 self
126 }
127
128 pub fn delay(mut self, delay: Duration) -> Self {
129 self.delay = delay;
130 self
131 }
132
133 pub fn repeat(mut self, mode: RepeatMode) -> Self {
134 self.repeat = mode;
135 self
136 }
137
138 pub fn id(&self) -> &str {
139 &self.id
140 }
141
142 pub fn state(&self) -> AnimationState {
143 self.state
144 }
145
146 pub fn start(&mut self) {
148 self.state = AnimationState::Running;
149 }
150
151 pub fn pause(&mut self) {
153 if self.state == AnimationState::Running {
154 self.state = AnimationState::Paused;
155 }
156 }
157
158 pub fn reset(&mut self) {
160 self.elapsed = Duration::ZERO;
161 self.state = AnimationState::Pending;
162 }
163
164 pub fn tick(&mut self, dt: Duration) -> f64 {
166 if self.state != AnimationState::Running {
167 return self.current_value();
168 }
169
170 self.elapsed += dt;
171
172 if self.elapsed < self.delay {
174 return self.from;
175 }
176
177 let active_elapsed = self.elapsed - self.delay;
178 let raw_t = active_elapsed.as_secs_f64() / self.duration.as_secs_f64();
179
180 let t = match self.repeat {
181 RepeatMode::Once => {
182 if raw_t >= 1.0 {
183 self.state = AnimationState::Completed;
184 1.0
185 } else {
186 raw_t
187 }
188 }
189 RepeatMode::Loop => raw_t.fract(),
190 RepeatMode::PingPong => {
191 let cycle = raw_t % 2.0;
192 if cycle > 1.0 { 2.0 - cycle } else { cycle }
193 }
194 RepeatMode::Count(n) => {
195 if raw_t >= n as f64 {
196 self.state = AnimationState::Completed;
197 1.0
198 } else {
199 raw_t.fract()
200 }
201 }
202 };
203
204 let eased = self.easing.apply(t as f32) as f64;
205 self.from + (self.to - self.from) * eased
206 }
207
208 fn current_value(&self) -> f64 {
209 match self.state {
210 AnimationState::Pending => self.from,
211 AnimationState::Completed => self.to,
212 _ => {
213 if self.elapsed < self.delay {
214 return self.from;
215 }
216 let active = self.elapsed - self.delay;
217 let t = (active.as_secs_f64() / self.duration.as_secs_f64()).clamp(0.0, 1.0);
218 let eased = self.easing.apply(t as f32) as f64;
219 self.from + (self.to - self.from) * eased
220 }
221 }
222 }
223}
224
225pub struct AnimationManager {
227 animations: Vec<Animation>,
228}
229
230impl AnimationManager {
231 pub fn new() -> Self {
232 Self {
233 animations: Vec::new(),
234 }
235 }
236
237 pub fn add(&mut self, mut anim: Animation) {
238 anim.start();
239 self.animations.push(anim);
240 }
241
242 pub fn tick(&mut self, dt: Duration) -> Vec<(String, f64)> {
244 let values: Vec<(String, f64)> = self
245 .animations
246 .iter_mut()
247 .map(|a| {
248 let val = a.tick(dt);
249 (a.id().to_string(), val)
250 })
251 .collect();
252
253 self.animations
255 .retain(|a| a.state != AnimationState::Completed);
256
257 values
258 }
259
260 pub fn get(&self, id: &str) -> Option<&Animation> {
261 self.animations.iter().find(|a| a.id() == id)
262 }
263
264 pub fn cancel(&mut self, id: &str) {
265 self.animations.retain(|a| a.id() != id);
266 }
267
268 pub fn active_count(&self) -> usize {
269 self.animations.len()
270 }
271}
272
273impl Default for AnimationManager {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn easing_linear() {
285 assert!((Easing::Linear.apply(0.5) - 0.5).abs() < 0.001);
286 }
287
288 #[test]
289 fn easing_endpoints() {
290 for easing in [
291 Easing::Linear,
292 Easing::EaseIn,
293 Easing::EaseOut,
294 Easing::EaseInOut,
295 Easing::EaseInCubic,
296 Easing::EaseOutCubic,
297 ] {
298 assert!((easing.apply(0.0)).abs() < 0.001, "{easing:?} at 0");
299 assert!((easing.apply(1.0) - 1.0).abs() < 0.001, "{easing:?} at 1");
300 }
301 }
302
303 #[test]
304 fn easing_clamps() {
305 assert!((Easing::Linear.apply(-0.5)).abs() < 0.001);
306 assert!((Easing::Linear.apply(1.5) - 1.0).abs() < 0.001);
307 }
308
309 #[test]
310 fn animation_basic() {
311 let mut anim = Animation::new("x", 0.0, 100.0, Duration::from_millis(1000));
312 anim.start();
313 let val = anim.tick(Duration::from_millis(500));
314 assert!(val > 0.0 && val < 100.0);
315 }
316
317 #[test]
318 fn animation_completes() {
319 let mut anim = Animation::new("x", 0.0, 100.0, Duration::from_millis(100));
320 anim.start();
321 let val = anim.tick(Duration::from_millis(200));
322 assert!((val - 100.0).abs() < 0.01);
323 assert_eq!(anim.state(), AnimationState::Completed);
324 }
325
326 #[test]
327 fn animation_delay() {
328 let mut anim = Animation::new("x", 0.0, 1.0, Duration::from_millis(100))
329 .delay(Duration::from_millis(50));
330 anim.start();
331 let val = anim.tick(Duration::from_millis(25));
332 assert!((val - 0.0).abs() < 0.001); }
334
335 #[test]
336 fn animation_pause_resume() {
337 let mut anim = Animation::new("x", 0.0, 100.0, Duration::from_millis(1000));
338 anim.start();
339 anim.tick(Duration::from_millis(300));
340 anim.pause();
341 let val_paused = anim.tick(Duration::from_millis(500));
342 anim.start();
343 let val_resumed = anim.tick(Duration::from_millis(100));
344 assert!(val_resumed > val_paused || (val_resumed - val_paused).abs() < 0.01);
345 }
346
347 #[test]
348 fn animation_reset() {
349 let mut anim = Animation::new("x", 0.0, 100.0, Duration::from_millis(100));
350 anim.start();
351 anim.tick(Duration::from_millis(200));
352 anim.reset();
353 assert_eq!(anim.state(), AnimationState::Pending);
354 }
355
356 #[test]
357 fn animation_loop() {
358 let mut anim =
359 Animation::new("x", 0.0, 1.0, Duration::from_millis(100)).repeat(RepeatMode::Loop);
360 anim.start();
361 anim.tick(Duration::from_millis(150));
362 assert_eq!(anim.state(), AnimationState::Running); }
364
365 #[test]
366 fn animation_pingpong() {
367 let mut anim = Animation::new("x", 0.0, 1.0, Duration::from_millis(100))
368 .repeat(RepeatMode::PingPong)
369 .easing(Easing::Linear);
370 anim.start();
371 let val = anim.tick(Duration::from_millis(150));
373 assert!(val < 0.6);
374 }
375
376 #[test]
377 fn manager_add_and_tick() {
378 let mut mgr = AnimationManager::new();
379 mgr.add(Animation::new("a", 0.0, 1.0, Duration::from_millis(100)));
380 mgr.add(Animation::new("b", 10.0, 20.0, Duration::from_millis(200)));
381 let vals = mgr.tick(Duration::from_millis(50));
382 assert_eq!(vals.len(), 2);
383 }
384
385 #[test]
386 fn manager_removes_completed() {
387 let mut mgr = AnimationManager::new();
388 mgr.add(Animation::new("short", 0.0, 1.0, Duration::from_millis(50)));
389 mgr.add(Animation::new("long", 0.0, 1.0, Duration::from_millis(500)));
390 mgr.tick(Duration::from_millis(100));
391 assert_eq!(mgr.active_count(), 1);
392 }
393
394 #[test]
395 fn manager_cancel() {
396 let mut mgr = AnimationManager::new();
397 mgr.add(Animation::new("a", 0.0, 1.0, Duration::from_millis(500)));
398 mgr.cancel("a");
399 assert_eq!(mgr.active_count(), 0);
400 }
401
402 #[test]
403 fn easing_spring() {
404 let spring = Easing::Spring {
405 stiffness: 100.0,
406 damping: 10.0,
407 };
408 let val = spring.apply(0.5);
409 assert!(val > 0.0);
410 }
411}