1use super::widget_state::WidgetId;
6use std::collections::HashMap;
7
8pub mod easing {
10 pub fn linear(t: f64) -> f64 {
12 t
13 }
14
15 pub fn ease_in_quad(t: f64) -> f64 {
17 t * t
18 }
19
20 pub fn ease_out_quad(t: f64) -> f64 {
22 t * (2.0 - t)
23 }
24
25 pub fn ease_in_out_quad(t: f64) -> f64 {
27 if t < 0.5 {
28 2.0 * t * t
29 } else {
30 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
31 }
32 }
33
34 pub fn ease_out_cubic(t: f64) -> f64 {
36 1.0 - (1.0 - t).powi(3)
37 }
38
39 pub fn ease_in_cubic(t: f64) -> f64 {
41 t * t * t
42 }
43
44 pub fn ease_in_out_cubic(t: f64) -> f64 {
46 if t < 0.5 {
47 4.0 * t * t * t
48 } else {
49 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
50 }
51 }
52
53 pub fn ease_out_elastic(t: f64) -> f64 {
55 if t == 0.0 || t == 1.0 {
56 return t;
57 }
58 let c4 = (2.0 * std::f64::consts::PI) / 3.0;
59 2.0_f64.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
60 }
61
62 pub fn ease_out_back(t: f64) -> f64 {
64 let c1 = 1.70158;
65 let c3 = c1 + 1.0;
66 1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
67 }
68}
69
70pub type EasingFn = fn(f64) -> f64;
72
73#[derive(Clone, Debug)]
75pub struct AnimatedValue {
76 current: f64,
78 target: f64,
80 start_value: f64,
82 start_time: f64,
84 duration: f64,
86 easing: EasingFn,
88}
89
90impl AnimatedValue {
91 pub fn new(initial: f64) -> Self {
93 Self {
94 current: initial,
95 target: initial,
96 start_value: initial,
97 start_time: 0.0,
98 duration: 0.3,
99 easing: easing::ease_out_quad,
100 }
101 }
102
103 pub fn with_easing(mut self, easing: EasingFn) -> Self {
105 self.easing = easing;
106 self
107 }
108
109 pub fn with_duration(mut self, duration: f64) -> Self {
111 self.duration = duration;
112 self
113 }
114
115 pub fn animate_to(&mut self, target: f64, time: f64) {
117 if (self.target - target).abs() > 0.0001 {
118 self.start_value = self.current;
119 self.target = target;
120 self.start_time = time;
121 }
122 }
123
124 pub fn update(&mut self, time: f64) -> f64 {
126 if self.duration <= 0.0 {
127 self.current = self.target;
128 return self.current;
129 }
130
131 let elapsed = time - self.start_time;
132 let t = (elapsed / self.duration).clamp(0.0, 1.0);
133 let eased_t = (self.easing)(t);
134
135 self.current = self.start_value + (self.target - self.start_value) * eased_t;
136 self.current
137 }
138
139 pub fn is_animating(&self, time: f64) -> bool {
141 let elapsed = time - self.start_time;
142 elapsed < self.duration && (self.start_value - self.target).abs() > 0.0001
143 }
144
145 pub fn get(&self) -> f64 {
147 self.current
148 }
149
150 pub fn target(&self) -> f64 {
152 self.target
153 }
154
155 pub fn set(&mut self, value: f64) {
157 self.current = value;
158 self.target = value;
159 self.start_value = value;
160 }
161}
162
163#[derive(Clone, Debug)]
165pub struct AnimationState {
166 values: HashMap<WidgetId, AnimatedValue>,
168 bools: HashMap<WidgetId, AnimatedValue>,
170 default_duration: f64,
172 default_easing: EasingFn,
174}
175
176impl Default for AnimationState {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl AnimationState {
183 pub fn new() -> Self {
185 Self {
186 values: HashMap::new(),
187 bools: HashMap::new(),
188 default_duration: 0.3,
189 default_easing: easing::ease_out_quad,
190 }
191 }
192
193 pub fn set_default_duration(&mut self, duration: f64) {
195 self.default_duration = duration;
196 }
197
198 pub fn set_default_easing(&mut self, easing: EasingFn) {
200 self.default_easing = easing;
201 }
202
203 pub fn animate_value(&mut self, id: &WidgetId, target: f64, time: f64) -> f64 {
205 let entry = self.values.entry(id.clone()).or_insert_with(|| {
206 AnimatedValue::new(0.0)
208 .with_duration(self.default_duration)
209 .with_easing(self.default_easing)
210 });
211 entry.animate_to(target, time);
212 entry.update(time)
213 }
214
215 pub fn animate_bool(&mut self, id: &WidgetId, target: bool, time: f64) -> f64 {
217 let target_value = if target { 1.0 } else { 0.0 };
218 let entry = self.bools.entry(id.clone()).or_insert_with(|| {
219 AnimatedValue::new(0.0)
221 .with_duration(self.default_duration)
222 .with_easing(self.default_easing)
223 });
224 entry.animate_to(target_value, time);
225 entry.update(time)
226 }
227
228 pub fn is_animating(&self, id: &WidgetId, time: f64) -> bool {
230 self.values
231 .get(id)
232 .map(|v| v.is_animating(time))
233 .unwrap_or(false)
234 || self
235 .bools
236 .get(id)
237 .map(|v| v.is_animating(time))
238 .unwrap_or(false)
239 }
240
241 pub fn any_animating(&self, time: f64) -> bool {
243 self.values.values().any(|v| v.is_animating(time))
244 || self.bools.values().any(|v| v.is_animating(time))
245 }
246
247 pub fn remove(&mut self, id: &WidgetId) {
249 self.values.remove(id);
250 self.bools.remove(id);
251 }
252
253 pub fn clear(&mut self) {
255 self.values.clear();
256 self.bools.clear();
257 }
258}
259
260pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
262 a + (b - a) * t
263}
264
265pub fn lerp_color(
267 a: (f64, f64, f64, f64),
268 b: (f64, f64, f64, f64),
269 t: f64,
270) -> (f64, f64, f64, f64) {
271 (
272 lerp(a.0, b.0, t),
273 lerp(a.1, b.1, t),
274 lerp(a.2, b.2, t),
275 lerp(a.3, b.3, t),
276 )
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_easing_functions_bounds() {
285 let easing_fns = [
287 easing::linear,
288 easing::ease_in_quad,
289 easing::ease_out_quad,
290 easing::ease_in_out_quad,
291 easing::ease_out_cubic,
292 easing::ease_in_cubic,
293 easing::ease_in_out_cubic,
294 easing::ease_out_elastic,
295 easing::ease_out_back,
296 ];
297
298 for easing_fn in &easing_fns {
299 let result_0 = easing_fn(0.0);
301 assert!(
302 (result_0 - 0.0).abs() < 0.1,
303 "Easing function should be near 0 at t=0"
304 );
305
306 let result_1 = easing_fn(1.0);
308 assert!(
309 (result_1 - 1.0).abs() < 0.1,
310 "Easing function should be near 1 at t=1"
311 );
312 }
313 }
314
315 #[test]
316 fn test_easing_linear() {
317 assert_eq!(easing::linear(0.0), 0.0);
318 assert_eq!(easing::linear(0.5), 0.5);
319 assert_eq!(easing::linear(1.0), 1.0);
320 }
321
322 #[test]
323 fn test_easing_quad() {
324 assert_eq!(easing::ease_in_quad(0.0), 0.0);
325 assert_eq!(easing::ease_in_quad(0.5), 0.25);
326 assert_eq!(easing::ease_in_quad(1.0), 1.0);
327
328 assert_eq!(easing::ease_out_quad(0.0), 0.0);
329 assert_eq!(easing::ease_out_quad(0.5), 0.75);
330 assert_eq!(easing::ease_out_quad(1.0), 1.0);
331 }
332
333 #[test]
334 fn test_easing_cubic() {
335 assert_eq!(easing::ease_in_cubic(0.0), 0.0);
336 assert_eq!(easing::ease_in_cubic(0.5), 0.125);
337 assert_eq!(easing::ease_in_cubic(1.0), 1.0);
338
339 assert_eq!(easing::ease_out_cubic(0.0), 0.0);
340 assert_eq!(easing::ease_out_cubic(0.5), 0.875);
341 assert_eq!(easing::ease_out_cubic(1.0), 1.0);
342 }
343
344 #[test]
345 fn test_animated_value_creation() {
346 let value = AnimatedValue::new(5.0);
347 assert_eq!(value.get(), 5.0);
348 assert_eq!(value.target(), 5.0);
349 }
350
351 #[test]
352 fn test_animated_value_instant_set() {
353 let mut value = AnimatedValue::new(0.0);
354 value.set(10.0);
355 assert_eq!(value.get(), 10.0);
356 assert_eq!(value.target(), 10.0);
357 assert!(!value.is_animating(0.0));
358 }
359
360 #[test]
361 fn test_animated_value_animation() {
362 let mut value = AnimatedValue::new(0.0).with_duration(1.0);
363
364 value.animate_to(10.0, 0.0);
366
367 assert!((value.update(0.0) - 0.0).abs() < 0.1);
369
370 let mid = value.update(0.5);
372 assert!(mid > 3.0 && mid < 10.0);
373
374 assert!((value.update(1.0) - 10.0).abs() < 0.1);
376
377 assert!(!value.is_animating(1.5));
379 }
380
381 #[test]
382 fn test_animated_value_zero_duration() {
383 let mut value = AnimatedValue::new(0.0).with_duration(0.0);
384 value.animate_to(10.0, 0.0);
385
386 assert_eq!(value.update(0.0), 10.0);
388 }
389
390 #[test]
391 fn test_animation_state_value() {
392 let mut state = AnimationState::new();
393 let id = WidgetId::new("widget1");
394
395 let result = state.animate_value(&id, 5.0, 0.0);
397 assert!(result < 1.0);
399
400 let result = state.animate_value(&id, 5.0, 0.2);
402 assert!(result > 1.0 && result < 5.0);
403
404 state.animate_value(&id, 10.0, 0.5);
406 let result = state.animate_value(&id, 10.0, 0.7);
407 assert!(result > 5.0 && result <= 10.0);
408 }
409
410 #[test]
411 fn test_animation_state_bool() {
412 let mut state = AnimationState::new();
413 let id = WidgetId::new("widget1");
414
415 let result = state.animate_bool(&id, true, 0.0);
417 assert!(result < 0.3);
419
420 let result = state.animate_bool(&id, true, 0.2);
422 assert!(result > 0.3 && result <= 1.0);
423
424 state.animate_bool(&id, false, 1.0);
426 let result = state.animate_bool(&id, false, 1.2);
427 assert!(result >= 0.0 && result < 0.7);
428 }
429
430 #[test]
431 fn test_animation_state_is_animating() {
432 let mut state = AnimationState::new();
433 state.set_default_duration(1.0);
434 let id = WidgetId::new("widget1");
435
436 state.animate_value(&id, 10.0, 0.0);
438
439 assert!(state.is_animating(&id, 0.5));
441
442 state.animate_value(&id, 10.0, 2.0);
444 assert!(!state.is_animating(&id, 2.0));
445 }
446
447 #[test]
448 fn test_animation_state_any_animating() {
449 let mut state = AnimationState::new();
450 state.set_default_duration(1.0);
451 let id1 = WidgetId::new("widget1");
452 let id2 = WidgetId::new("widget2");
453
454 assert!(!state.any_animating(0.0));
456
457 state.animate_value(&id1, 10.0, 0.0);
459 assert!(state.any_animating(0.5));
460
461 state.animate_bool(&id2, true, 0.5);
463 assert!(state.any_animating(1.0));
464
465 state.animate_value(&id1, 10.0, 2.0);
467 state.animate_bool(&id2, true, 2.0);
468 assert!(!state.any_animating(2.0));
469 }
470
471 #[test]
472 fn test_animation_state_remove() {
473 let mut state = AnimationState::new();
474 let id = WidgetId::new("widget1");
475
476 state.animate_value(&id, 10.0, 0.0);
477 assert!(state.is_animating(&id, 0.0));
478
479 state.remove(&id);
480 assert!(!state.is_animating(&id, 0.0));
481 }
482
483 #[test]
484 fn test_animation_state_clear() {
485 let mut state = AnimationState::new();
486 let id1 = WidgetId::new("widget1");
487 let id2 = WidgetId::new("widget2");
488
489 state.animate_value(&id1, 10.0, 0.0);
490 state.animate_bool(&id2, true, 0.0);
491 assert!(state.any_animating(0.0));
492
493 state.clear();
494 assert!(!state.any_animating(0.0));
495 }
496
497 #[test]
498 fn test_lerp() {
499 assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
500 assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
501 assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
502
503 assert_eq!(lerp(5.0, 15.0, 0.5), 10.0);
504 }
505
506 #[test]
507 fn test_lerp_color() {
508 let black = (0.0, 0.0, 0.0, 1.0);
509 let white = (1.0, 1.0, 1.0, 1.0);
510
511 let result = lerp_color(black, white, 0.0);
512 assert_eq!(result, black);
513
514 let result = lerp_color(black, white, 1.0);
515 assert_eq!(result, white);
516
517 let result = lerp_color(black, white, 0.5);
518 assert_eq!(result, (0.5, 0.5, 0.5, 1.0));
519
520 let transparent_black = (0.0, 0.0, 0.0, 0.0);
522 let opaque_white = (1.0, 1.0, 1.0, 1.0);
523
524 let result = lerp_color(transparent_black, opaque_white, 0.5);
525 assert_eq!(result, (0.5, 0.5, 0.5, 0.5));
526 }
527
528 #[test]
529 fn test_custom_easing_and_duration() {
530 let mut state = AnimationState::new();
531 state.set_default_duration(2.0);
532 state.set_default_easing(easing::linear);
533
534 let id = WidgetId::new("widget1");
535
536 state.animate_value(&id, 10.0, 0.0);
538 let mid = state.animate_value(&id, 10.0, 1.0);
539
540 assert!((mid - 5.0).abs() < 0.1);
542 }
543}