lilt/
animated.rs

1use crate::traits::{AnimationTime, FloatRepresentable, Interpolable};
2/// Wraps state to enable interpolated transitions
3///
4/// # Example
5///
6/// ```rust
7/// use lilt::Animated;
8/// use std::time::Instant;
9///
10/// struct MyViewState {
11///     animated_toggle: Animated<bool, Instant>,
12/// }
13/// // Initialize
14/// let mut state = MyViewState {
15///     animated_toggle: Animated::new(false),
16/// };
17/// // Update
18/// let now = std::time::Instant::now();
19/// state
20///     .animated_toggle
21///     .transition(!state.animated_toggle.value, now);
22/// // Animate
23/// let animated_width = state.animated_toggle.animate_bool(0., 100., now);
24/// let animated_width = state.animated_toggle.animate(
25///    |on| if on { 0. } else { 100. },
26///    now,
27/// );
28/// ```
29///
30/// An `Animated` struct represents a single animation axis. Multiple axes require multiple `Animated` structs.
31/// For example - to animate an x and a y position on the screen with different durations you'd need to
32/// wrap multiple float values independently.
33///
34/// ```rust
35/// use std::time::Instant;
36/// use lilt::Animated;
37///
38/// struct MyState {
39///     animated_x: Animated<f32, Instant>,
40///     animated_y: Animated<f32, Instant>,
41/// }
42/// ```
43#[derive(Clone, Debug, Default)]
44pub struct Animated<T, Time>
45where
46    T: FloatRepresentable + Clone + Copy + PartialEq,
47    Time: AnimationTime,
48{
49    animation: Animation<Time>,
50    pub value: T,
51    last_value: T,
52}
53
54impl<T, Time> Animated<T, Time>
55where
56    T: FloatRepresentable + Clone + Copy + PartialEq,
57    Time: AnimationTime,
58{
59    /// Creates an animated value with specified animation settings
60    pub fn new_with_settings(value: T, duration_ms: f32, easing: Easing) -> Self {
61        let mut animation = Animation::default(value.float_value());
62        animation.settings.duration_ms = duration_ms;
63        animation.settings.easing = easing;
64        Animated {
65            value,
66            last_value: value,
67            animation,
68        }
69    }
70    /// Creates an animated value with a default animation
71    pub fn new(value: T) -> Self {
72        Self {
73            value,
74            last_value: value,
75            animation: Animation::default(value.float_value()),
76        }
77    }
78    /// Specifies the duration of the animation in milliseconds
79    pub fn duration(mut self, duration_ms: f32) -> Self {
80        self.animation.settings.duration_ms = duration_ms;
81        self
82    }
83    /// Specifies the easing with which to animate transitions
84    pub fn easing(mut self, easing: Easing) -> Self {
85        self.animation.settings.easing = easing;
86        self
87    }
88    /// Delays the animation by the given number of milliseconds
89    pub fn delay(mut self, delay_ms: f32) -> Self {
90        self.animation.delay_ms = delay_ms;
91        self
92    }
93    /// Repeats animations the specified number of times
94    /// Passing a repetition count of 1 plays the animation twice in total
95    pub fn repeat(mut self, count: u32) -> Self {
96        self.animation.repetitions = count;
97        self
98    }
99    /// Repeats transitions forever
100    pub fn repeat_forever(mut self) -> Self {
101        self.animation.repeat_forever = true;
102        self
103    }
104    /// Automatically play repetitions in reverse after they complete
105    pub fn auto_reverse(mut self) -> Self {
106        self.animation.auto_reverse_repetitions = true;
107        self
108    }
109    /// Begins a transition as soon as the animation is created
110    pub fn auto_start(mut self, new_value: T, at: Time) -> Self {
111        self.transition(new_value, at);
112        self
113    }
114    /// Applies an alternative duration while animating backwards
115    pub fn asymmetric_duration(mut self, duration_ms: f32) -> Self {
116        self.animation.asymmetric_settings = Some(AnimationSettings {
117            duration_ms,
118            easing: self
119                .animation
120                .asymmetric_settings
121                .map(|a| a.easing)
122                .unwrap_or(self.animation.settings.easing),
123        });
124        self
125    }
126    /// Applies an alternative easing while animating backwards
127    pub fn asymmetric_easing(mut self, easing: Easing) -> Self {
128        self.animation.asymmetric_settings = Some(AnimationSettings {
129            duration_ms: self
130                .animation
131                .asymmetric_settings
132                .map(|a| a.duration_ms)
133                .unwrap_or(self.animation.settings.duration_ms),
134            easing,
135        });
136        self
137    }
138    /// Updates the wrapped state & begins an animation
139    pub fn transition(&mut self, new_value: T, at: Time) {
140        self.transition_internal(new_value, at, false);
141    }
142    /// Updates the wrapped state & instantaneously completes an animation.
143    /// Ignores animation settings such as delay & duration.
144    pub fn transition_instantaneous(&mut self, new_value: T, at: Time) {
145        self.transition_internal(new_value, at, true);
146    }
147    fn transition_internal(&mut self, new_value: T, at: Time, instantaneous: bool) {
148        if self.value != new_value {
149            self.last_value = self.value;
150            self.value = new_value;
151            self.animation
152                .transition(new_value.float_value(), at, instantaneous)
153        }
154    }
155    /// Returns whether the animation is complete, given the current time
156    pub fn in_progress(&self, time: Time) -> bool {
157        self.animation.in_progress(time)
158    }
159    /// Interpolates between states of any value that implements `Interpolable`, given the current time
160    pub fn animate<I>(&self, map: impl Fn(T) -> I, time: Time) -> I
161    where
162        I: Interpolable,
163    {
164        // The generic T values are arbitrary targets that may not be continuous,
165        // so we can't store an interrupted T in the case that it's something like
166        // an int or enum - therefore we store the interrupted float representation.
167        //
168        // Given ONLY a function which maps T values to interpolable values,
169        // we need some way to go from an interrupt float & a unit progress value
170        // to the final interpolable value.
171        //
172        // The only way to do so without storing interpolable values is to represent
173        // the interrupt float (origin) as an interpolable value and interpolate between
174        // that and the current destination.
175        let interrupted_range = self.value.float_value() - self.last_value.float_value();
176        let unit_interrupt_value = if interrupted_range == 0. {
177            0.
178        } else {
179            (self.animation.origin - self.last_value.float_value()) / interrupted_range
180        };
181        let interrupt_interpolable =
182            map(self.last_value).interpolated(map(self.value), unit_interrupt_value);
183        interrupt_interpolable
184            .interpolated(map(self.value), self.animation.eased_unit_progress(time))
185    }
186    // Just for nicer testing
187    #[allow(dead_code)]
188    fn linear_progress(&self, time: Time) -> f32 {
189        self.animation.linear_progress(time)
190    }
191    #[allow(dead_code)]
192    fn eased_progress(&self, time: Time) -> f32 {
193        self.animation.eased_progress(time)
194    }
195}
196
197impl<T, Time> Animated<T, Time>
198where
199    T: FloatRepresentable + Clone + Copy + PartialEq,
200    Time: AnimationTime,
201{
202    /// Interpolates to `equal` when the wrapped value matches the provided `value`
203    /// Otherwise interpolate towards `default`
204    pub fn animate_if_eq<I>(&self, value: T, equal: I, default: I, time: Time) -> I
205    where
206        I: Interpolable + Clone,
207    {
208        self.animate(
209            |v| {
210                if v == value {
211                    equal.clone()
212                } else {
213                    default.clone()
214                }
215            },
216            time,
217        )
218    }
219}
220
221impl<T, Time> Animated<T, Time>
222where
223    T: FloatRepresentable + Clone + Copy + PartialEq,
224    T: Interpolable,
225    Time: AnimationTime,
226{
227    pub fn animate_wrapped(&self, time: Time) -> T {
228        self.animate(|v| v, time)
229    }
230}
231
232impl<Time> Animated<bool, Time>
233where
234    Time: AnimationTime,
235{
236    /// Interpolates any value that implements `Interpolable`, given the current time
237    pub fn animate_bool<I>(&self, false_value: I, true_value: I, time: Time) -> I
238    where
239        I: Interpolable + Clone,
240    {
241        self.animate(
242            move |b| {
243                if b {
244                    true_value.clone()
245                } else {
246                    false_value.clone()
247                }
248            },
249            time,
250        )
251    }
252}
253
254#[derive(Clone, Copy, Debug, Default)]
255struct Animation<Time>
256where
257    Time: AnimationTime,
258{
259    origin: f32,
260    destination: f32,
261    delay_ms: f32,
262    settings: AnimationSettings,
263    asymmetric_settings: Option<AnimationSettings>,
264    repetitions: u32,
265    auto_reverse_repetitions: bool,
266    repeat_forever: bool,
267    transition_time: Option<Time>,
268}
269
270#[derive(Clone, Copy, Debug, Default)]
271struct AnimationSettings {
272    duration_ms: f32,
273    easing: Easing,
274}
275
276impl<Time> Animation<Time>
277where
278    Time: AnimationTime,
279{
280    fn default(origin: f32) -> Self {
281        Animation {
282            origin,
283            destination: origin,
284            settings: AnimationSettings {
285                duration_ms: 100.,
286                easing: Easing::EaseInOut,
287            },
288            asymmetric_settings: None,
289            delay_ms: 0.,
290            repetitions: 1,
291            auto_reverse_repetitions: false,
292            repeat_forever: false,
293            transition_time: None,
294        }
295    }
296
297    fn transition(&mut self, destination: f32, time: Time, instantaneous: bool) {
298        if self.destination != destination {
299            if instantaneous {
300                self.origin = destination;
301                self.destination = destination;
302                return;
303            }
304            if self.in_progress(time) {
305                let eased_progress = self.eased_progress(time);
306                self.origin = eased_progress;
307            } else {
308                self.origin = self.destination;
309            }
310            self.transition_time = Some(time);
311            self.destination = destination;
312        }
313    }
314
315    fn current_progress(&self, time: Time) -> Progress {
316        let Some(transition_time) = self.transition_time else {
317            return Progress {
318                linear_unit_progress: 0.,
319                eased_unit_progress: 0.,
320                complete: true,
321            };
322        };
323        let elapsed = f32::max(0., time.elapsed_since(transition_time) - self.delay_ms);
324
325        let settings;
326        let elapsed_current;
327        let auto_reversing;
328
329        if self.auto_reverse_repetitions {
330            let asymmetry = self.asymmetric_settings.unwrap_or(self.settings);
331            let combined_durations = self.settings.duration_ms + asymmetry.duration_ms;
332            let first_animation = elapsed % combined_durations - self.settings.duration_ms < 0.;
333            if first_animation {
334                elapsed_current = elapsed % combined_durations;
335                settings = self.settings;
336                auto_reversing = false;
337            } else {
338                settings = asymmetry;
339                elapsed_current = elapsed % combined_durations - self.settings.duration_ms;
340                auto_reversing = true;
341            }
342        } else if self.destination < self.origin {
343            settings = self.asymmetric_settings.unwrap_or(self.settings);
344            elapsed_current = elapsed;
345            auto_reversing = false;
346        } else {
347            settings = self.settings;
348            elapsed_current = elapsed;
349            auto_reversing = false;
350        }
351
352        let total_duration = self.total_duration();
353        if total_duration == 0. {
354            return Progress {
355                linear_unit_progress: 1.,
356                eased_unit_progress: settings.easing.value(1.),
357                complete: true,
358            };
359        }
360
361        let complete = !self.repeat_forever && elapsed >= total_duration;
362        let repeat = elapsed_current / settings.duration_ms;
363        let progress = if complete { 1. } else { repeat % 1. };
364        if auto_reversing && !complete {
365            Progress {
366                linear_unit_progress: 1. - progress,
367                eased_unit_progress: settings.easing.value(1. - progress),
368                complete,
369            }
370        } else {
371            Progress {
372                linear_unit_progress: progress,
373                eased_unit_progress: settings.easing.value(progress),
374                complete,
375            }
376        }
377    }
378
379    fn linear_unit_progress(&self, time: Time) -> f32 {
380        self.current_progress(time).linear_unit_progress
381    }
382
383    fn eased_unit_progress(&self, time: Time) -> f32 {
384        self.current_progress(time).eased_unit_progress
385    }
386
387    fn total_duration(&self) -> f32 {
388        let true_repetitions = if self.auto_reverse_repetitions {
389            (self.repetitions * 2) + 1
390        } else {
391            self.repetitions
392        } as f32;
393        if true_repetitions > 1. {
394            if true_repetitions % 2. == 0. {
395                self.settings.duration_ms * (true_repetitions * 0.5)
396                    + self
397                        .asymmetric_settings
398                        .unwrap_or(self.settings)
399                        .duration_ms
400                        * (true_repetitions * 0.5)
401            } else {
402                self.settings.duration_ms * ((true_repetitions - true_repetitions % 2.) * 0.5)
403                    + self
404                        .asymmetric_settings
405                        .unwrap_or(self.settings)
406                        .duration_ms
407                        * ((true_repetitions - true_repetitions % 2.) * 0.5)
408                    + self.settings.duration_ms
409            }
410        } else if self.destination < self.origin {
411            self.asymmetric_settings
412                .unwrap_or(self.settings)
413                .duration_ms
414                * true_repetitions
415        } else {
416            self.settings.duration_ms * true_repetitions
417        }
418    }
419
420    fn linear_progress(&self, time: Time) -> f32 {
421        self.origin + (self.linear_unit_progress(time) * self.progress_range())
422    }
423
424    fn eased_progress(&self, time: Time) -> f32 {
425        self.origin + (self.eased_unit_progress(time) * self.progress_range())
426    }
427
428    fn progress_range(&self) -> f32 {
429        self.destination - self.origin
430    }
431
432    fn in_progress(&self, time: Time) -> bool {
433        !self.current_progress(time).complete
434    }
435}
436
437struct Progress {
438    linear_unit_progress: f32,
439    eased_unit_progress: f32,
440    complete: bool,
441}
442
443#[derive(Clone, Copy, Debug, PartialEq, Default)]
444pub enum Easing {
445    #[default]
446    Linear,
447    EaseIn,
448    EaseOut,
449    EaseInOut,
450    EaseInQuad,
451    EaseOutQuad,
452    EaseInOutQuad,
453    EaseInCubic,
454    EaseOutCubic,
455    EaseInOutCubic,
456    EaseInQuart,
457    EaseOutQuart,
458    EaseInOutQuart,
459    EaseInQuint,
460    EaseOutQuint,
461    EaseInOutQuint,
462    EaseInExpo,
463    EaseOutExpo,
464    EaseInOutExpo,
465    EaseInCirc,
466    EaseOutCirc,
467    EaseInOutCirc,
468    EaseInBack,
469    EaseOutBack,
470    EaseInOutBack,
471    EaseInElastic,
472    EaseOutElastic,
473    EaseInOutElastic,
474    EaseInBounce,
475    EaseOutBounce,
476    EaseInOutBounce,
477    Custom(fn(f32) -> f32),
478}
479
480impl Easing {
481    pub fn value(self, x: f32) -> f32 {
482        const PI: f32 = std::f32::consts::PI;
483
484        match self {
485            Easing::Linear => x,
486            Easing::EaseIn => 1.0 - f32::cos((x * PI) / 2.0),
487            Easing::EaseOut => f32::sin((x * PI) / 2.0),
488            Easing::EaseInOut => -(f32::cos(PI * x) - 1.0) / 2.0,
489            Easing::EaseInQuad => x.powi(2),
490            Easing::EaseOutQuad => 1.0 - (1.0 - x) * (1.0 - x),
491            Easing::EaseInOutQuad => {
492                if x < 0.5 {
493                    2.0 * x.powi(2)
494                } else {
495                    1.0 - (-2.0 * x + 2.0).powi(2) / 2.0
496                }
497            }
498            Easing::EaseInCubic => x.powi(3),
499            Easing::EaseOutCubic => 1.0 - (1.0 - x).powi(3),
500            Easing::EaseInOutCubic => {
501                if x < 0.5 {
502                    4.0 * x.powi(3)
503                } else {
504                    1.0 - (-2.0 * x + 2.0).powi(3) / 2.0
505                }
506            }
507            Easing::EaseInQuart => x.powi(4),
508            Easing::EaseOutQuart => 1.0 - (1.0 - x).powi(4),
509            Easing::EaseInOutQuart => {
510                if x < 0.5 {
511                    8.0 * x.powi(4)
512                } else {
513                    1.0 - (-2.0 * x + 2.0).powi(4) / 2.0
514                }
515            }
516            Easing::EaseInQuint => x.powi(5),
517            Easing::EaseOutQuint => 1.0 - (1.0 - x).powi(5),
518            Easing::EaseInOutQuint => {
519                if x < 0.5 {
520                    16.0 * x.powi(5)
521                } else {
522                    1.0 - (-2.0 * x + 2.0).powi(5) / 2.0
523                }
524            }
525            Easing::EaseInExpo => {
526                if x == 0.0 {
527                    0.0
528                } else {
529                    (2.0_f32).powf(10.0 * x - 10.0)
530                }
531            }
532            Easing::EaseOutExpo => {
533                if x == 1.0 {
534                    1.0
535                } else {
536                    1.0 - (2.0_f32).powf(-10.0 * x)
537                }
538            }
539            Easing::EaseInOutExpo => match x {
540                0.0 => 0.0,
541                1.0 => 1.0,
542                x if x < 0.5 => (2.0_f32).powf(20.0 * x - 10.0) / 2.0,
543                _ => (2.0 - (2.0_f32).powf(-20.0 * x + 10.0)) / 2.0,
544            },
545            Easing::EaseInCirc => 1.0 - (1.0 - x.powi(2)).sqrt(),
546            Easing::EaseOutCirc => (1.0 - (x - 1.0).powi(2)).sqrt(),
547            Easing::EaseInOutCirc => {
548                if x < 0.5 {
549                    (1.0 - (1.0 - (2.0 * x).powi(2)).sqrt()) / 2.0
550                } else {
551                    (1.0 + (1.0 - (-2.0 * x + 2.0).powi(2)).sqrt()) / 2.0
552                }
553            }
554            Easing::EaseInBack => {
555                const C1: f32 = 1.70158;
556                const C3: f32 = C1 + 1.0;
557
558                C3 * x.powi(3) - C1 * x.powi(2)
559            }
560            Easing::EaseOutBack => {
561                const C1: f32 = 1.70158;
562                const C3: f32 = C1 + 1.0;
563
564                1.0 + C3 * (x - 1.0).powi(3) + C1 * (x - 1.0).powi(2)
565            }
566            Easing::EaseInOutBack => {
567                const C1: f32 = 1.70158;
568                const C2: f32 = C1 * 1.525;
569
570                if x < 0.5 {
571                    ((2.0 * x).powi(2) * ((C2 + 1.0) * 2.0 * x - C2)) / 2.0
572                } else {
573                    ((2.0 * x - 2.0).powi(2) * ((C2 + 1.0) * (x * 2.0 - 2.0) + C2) + 2.0) / 2.0
574                }
575            }
576            Easing::EaseInElastic => {
577                const C4: f32 = (2.0 * PI) / 3.0;
578
579                if x == 0.0 {
580                    0.0
581                } else if x == 1.0 {
582                    1.0
583                } else {
584                    -(2.0_f32.powf(10.0 * x - 10.0)) * f32::sin((x * 10.0 - 10.75) * C4)
585                }
586            }
587            Easing::EaseOutElastic => {
588                const C4: f32 = (2.0 * PI) / 3.0;
589
590                if x == 0.0 {
591                    0.0
592                } else if x == 1.0 {
593                    1.0
594                } else {
595                    2.0_f32.powf(-10.0 * x) * f32::sin((x * 10.0 - 0.75) * C4) + 1.0
596                }
597            }
598            Easing::EaseInOutElastic => {
599                const C5: f32 = (2.0 * PI) / 4.5;
600
601                if x == 0.0 {
602                    0.0
603                } else if x == 1.0 {
604                    1.0
605                } else if x < 0.5 {
606                    -(2.0_f32.powf(20.0 * x - 10.0) * f32::sin((20.0 * x - 11.125) * C5)) / 2.0
607                } else {
608                    (2.0_f32.powf(-20.0 * x + 10.0) * f32::sin((20.0 * x - 11.125) * C5)) / 2.0
609                        + 1.0
610                }
611            }
612            Easing::EaseInBounce => 1.0 - Self::EaseOutBounce.value(1.0 - x),
613            Easing::EaseOutBounce => {
614                const N1: f32 = 7.5625;
615                const D1: f32 = 2.75;
616
617                if x < 1.0 / D1 {
618                    N1 * x.powi(2)
619                } else if x < 2.0 / D1 {
620                    N1 * (x - 1.5 / D1).powi(2) + 0.75
621                } else if x < 2.5 / D1 {
622                    N1 * (x - 2.25 / D1).powi(2) + 0.9375
623                } else {
624                    N1 * (x - 2.625 / D1).powi(2) + 0.984375
625                }
626            }
627            Easing::EaseInOutBounce => {
628                if x < 0.5 {
629                    (1.0 - Self::EaseOutBounce.value(1.0 - 2.0 * x)) / 2.0
630                } else {
631                    (1.0 + Self::EaseOutBounce.value(2.0 * x - 1.0)) / 2.0
632                }
633            }
634            Easing::Custom(f) => f(x),
635        }
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    #[test]
644    fn test_repeat_forever() {
645        let mut anim = Animated::new(0.)
646            .duration(1000.)
647            .easing(Easing::Linear)
648            .repeat_forever();
649
650        anim.transition(10.0, 0.0);
651
652        // Test progression over multiple cycles
653        assert_eq!(anim.animate_wrapped(0.0), 0.0);
654        assert_eq!(anim.animate_wrapped(500.0), 5.0);
655        assert_eq!(anim.animate_wrapped(1000.0), 0.0);
656        assert_eq!(anim.animate_wrapped(1500.0), 5.0);
657        assert_eq!(anim.animate_wrapped(2000.0), 0.0);
658        assert_eq!(anim.animate_wrapped(2500.0), 5.0);
659
660        // Ensure animation is still in progress after multiple cycles
661        assert!(anim.in_progress(10000.0));
662    }
663
664    fn plot_easing(easing: Easing) {
665        const WIDTH: usize = 80;
666        const HEIGHT: usize = 40;
667        let mut plot = vec![vec![' '; WIDTH]; HEIGHT];
668
669        for x in 0..WIDTH {
670            let t = x as f32 / (WIDTH - 1) as f32;
671            let y = easing.value(t);
672            let y_scaled = ((1.0 - y) * (HEIGHT - 20) as f32).round() as usize + 10;
673            let y_scaled = y_scaled.min(HEIGHT - 1);
674            plot[y_scaled][x] = '*';
675        }
676
677        println!("\nPlot for {:?}:", easing);
678        for row in plot {
679            println!("{}", row.iter().collect::<String>());
680        }
681        println!();
682    }
683
684    #[test]
685    fn visualize_all_easings() {
686        let easings = [
687            Easing::Linear,
688            Easing::EaseIn,
689            Easing::EaseOut,
690            Easing::EaseInOut,
691            Easing::EaseInQuad,
692            Easing::EaseOutQuad,
693            Easing::EaseInOutQuad,
694            Easing::EaseInCubic,
695            Easing::EaseOutCubic,
696            Easing::EaseInOutCubic,
697            Easing::EaseInQuart,
698            Easing::EaseOutQuart,
699            Easing::EaseInOutQuart,
700            Easing::EaseInQuint,
701            Easing::EaseOutQuint,
702            Easing::EaseInOutQuint,
703            Easing::EaseInExpo,
704            Easing::EaseOutExpo,
705            Easing::EaseInOutExpo,
706            Easing::EaseInCirc,
707            Easing::EaseOutCirc,
708            Easing::EaseInOutCirc,
709            Easing::EaseInBack,
710            Easing::EaseOutBack,
711            Easing::EaseInOutBack,
712            Easing::EaseInElastic,
713            Easing::EaseOutElastic,
714            Easing::EaseInOutElastic,
715            Easing::EaseInBounce,
716            Easing::EaseOutBounce,
717            Easing::EaseInOutBounce,
718        ];
719
720        for easing in &easings {
721            plot_easing(*easing);
722        }
723    }
724
725    #[test]
726    fn test_custom_easing() {
727        let custom_ease = Easing::Custom(|x| x.powi(2)); // Quadratic ease-in
728        assert_eq!(custom_ease.value(0.0), 0.0);
729        assert_eq!(custom_ease.value(0.5), 0.25);
730        assert_eq!(custom_ease.value(1.0), 1.0);
731    }
732
733    #[test]
734    fn test_linear_progress() {
735        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
736        anim.transition(10.0, 0.0);
737
738        assert_eq!(anim.animate_wrapped(0.0), 0.0);
739        assert_eq!(anim.animate_wrapped(500.0), 5.0);
740        assert_eq!(anim.animate_wrapped(1000.0), 10.0);
741        assert_eq!(anim.animate_wrapped(1500.0), 10.0); // Stays at destination after completion
742    }
743
744    #[test]
745    fn test_eased_progress_with_easing() {
746        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::EaseIn);
747        anim.transition(10.0, 0.0);
748
749        assert_eq!(anim.animate_wrapped(0.0), 0.0);
750        assert!(anim.animate_wrapped(500.0) < 5.0); // Should be less than linear due to ease-in
751        assert_eq!(anim.animate_wrapped(1000.0), 10.0);
752    }
753
754    #[test]
755    fn test_in_progress() {
756        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::EaseIn);
757        assert!(!anim.in_progress(0.0));
758
759        anim.transition(10.0, 0.0);
760        assert!(anim.in_progress(0.0));
761        assert!(anim.in_progress(500.0));
762        assert!(!anim.in_progress(1000.0));
763    }
764
765    #[test]
766    fn test_repetitions() {
767        let mut anim = Animated::new(0.)
768            .duration(1000.)
769            .easing(Easing::Linear)
770            .repeat(3);
771        anim.transition(10.0, 0.0);
772
773        assert_eq!(anim.animate_wrapped(1500.0), 5.0); // Middle of second repetition
774        assert_eq!(anim.animate_wrapped(3000.0), 10.0); // End of third repetition
775        assert_eq!(anim.animate_wrapped(3500.0), 10.0); // Stays at destination after all repetitions
776        assert!(!anim.in_progress(5001.));
777    }
778
779    #[test]
780    fn test_auto_reverse_repetitions() {
781        let mut anim = Animated::new(0.)
782            .duration(1000.)
783            .easing(Easing::Linear)
784            .auto_reverse()
785            .repeat(2);
786        anim.transition(10.0, 0.0);
787
788        assert_eq!(anim.animate_wrapped(500.0), 5.0); // Middle of first forward
789        assert_eq!(anim.animate_wrapped(1500.0), 5.0); // Middle of first reverse
790        assert_eq!(anim.animate_wrapped(2500.0), 5.0); // Middle of second forward
791        assert_eq!(anim.animate_wrapped(3500.0), 5.0); // Middle of second reverse
792        assert_eq!(anim.animate_wrapped(4000.0), 0.0); // End at start position
793        assert!(!anim.in_progress(5000.));
794    }
795
796    #[test]
797    fn test_auto_reverse_repetitions_bool() {
798        let anim = Animated::new(false)
799            .duration(1000.)
800            .auto_start(true, 0.)
801            .repeat(2)
802            .auto_reverse();
803        let map = |b| if b { 1. } else { 0. };
804        anim.animate(map, 500.);
805        assert_eq!(anim.animate(map, 500.0), 0.5); // Middle of first forward
806        assert_eq!(anim.animate(map, 1500.0), 0.5); // Middle of first reverse
807        assert_eq!(anim.animate(map, 2500.0), 0.5); // Middle of second forward
808        assert_eq!(anim.animate(map, 3500.0), 0.5); // Middle of second reverse
809        assert!(anim.in_progress(3500.));
810        assert_eq!(anim.animate(map, 4000.0), 0.0); // End at start position
811        assert!(!anim.in_progress(5000.01));
812    }
813
814    #[test]
815    fn test_delay() {
816        let mut anim = Animated::new(0.)
817            .duration(1000.)
818            .easing(Easing::Linear)
819            .delay(500.);
820        anim.transition(10.0, 0.0);
821
822        assert_eq!(anim.animate_wrapped(250.0), 0.0); // Still in delay
823        assert_eq!(anim.animate_wrapped(750.0), 2.5); // 25% progress after delay
824        assert_eq!(anim.animate_wrapped(1500.0), 10.0); // Completed
825    }
826
827    #[test]
828    fn test_interruption() {
829        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
830        anim.transition(10.0, 0.0);
831
832        assert_eq!(anim.animate_wrapped(500.0), 5.0);
833
834        anim.transition(20.0, 500.0); // Interrupt halfway
835        assert_eq!(anim.animate_wrapped(1000.0), 12.5); // Halfway to new destination
836        assert_eq!(anim.animate_wrapped(1500.0), 20.0); // Completed to new destination
837    }
838
839    #[test]
840    fn test_instant_animation() {
841        let mut anim = Animated::new(0.).duration(0.).easing(Easing::Linear);
842        assert_eq!(anim.animate_wrapped(0.0), 0.0);
843        // If animation duration is 0.0 the transition should happen instantly
844        // & require a redraw without any time passing
845        anim.transition(10.0, 0.0);
846        assert_eq!(anim.animate_wrapped(0.0), 10.0);
847    }
848
849    #[test]
850    fn test_progression() {
851        let mut anim = Animated::new(0.).duration(1.).easing(Easing::Linear);
852        // With a duration of 1.0 & linear timing we should be halfway to our
853        // destination at 0.5
854        anim.transition(10.0, 0.5);
855        assert_eq!(anim.value, 10.);
856        assert_eq!(anim.animate_wrapped(1.0), 5.0);
857        assert_eq!(anim.animate_wrapped(1.5), 10.0);
858
859        // Progression backward
860        anim.transition(0.0, 1.5);
861        assert_eq!(anim.value, 0.);
862        assert_eq!(anim.linear_progress(2.5), 0.0);
863
864        // Progression forward in fractions
865        anim.transition(10.0, 3.);
866        assert_eq!(anim.value, 10.);
867        assert!(approximately_equal(anim.animate_wrapped(3.), 0.0));
868        assert!(approximately_equal(anim.animate_wrapped(3.2), 2.0));
869        assert!(approximately_equal(anim.animate_wrapped(3.8), 8.0));
870        assert!(approximately_equal(anim.animate_wrapped(4.0), 10.0));
871    }
872
873    #[test]
874    fn test_progression_negative() {
875        let mut anim = Animated::new(0.).duration(1.).easing(Easing::EaseInOut);
876
877        anim.transition(-10.0, 0.0);
878        assert_eq!(anim.animate_wrapped(0.5), -5.0);
879        assert_eq!(anim.animate_wrapped(1.0), -10.0);
880
881        assert!(anim.eased_progress(0.25) > anim.linear_progress(0.25));
882        assert!(anim.eased_progress(0.5) == anim.linear_progress(0.5));
883        assert!(anim.eased_progress(0.75) < anim.linear_progress(0.75));
884
885        anim.transition(0.0, 1.0);
886        assert_eq!(anim.animate_wrapped(1.5), -5.0);
887        assert_eq!(anim.animate_wrapped(2.0), 0.0);
888    }
889
890    #[test]
891    fn test_multiple_interrupts_start_forward() {
892        let mut anim = Animated::new(0.).duration(1.).easing(Easing::EaseInOut);
893        anim.transition(1.0, 0.);
894        assert!(anim.in_progress(0.5));
895        let progress_at_interrupt = anim.eased_progress(0.5);
896        assert_eq!(progress_at_interrupt, Easing::EaseInOut.value(0.5));
897        anim.transition(0.0, 0.5);
898        assert_eq!(anim.eased_progress(0.5), progress_at_interrupt);
899        assert!(anim.in_progress(0.7));
900        anim.transition(1.0, 0.7);
901        assert!(anim.in_progress(0.9));
902    }
903
904    #[test]
905    fn test_asymmetric() {
906        let mut anim = Animated::new(0.)
907            .duration(1000.)
908            .easing(Easing::Linear)
909            .asymmetric_duration(2000.)
910            .asymmetric_easing(Easing::EaseInOut);
911
912        anim.transition(10.0, 0.0);
913        assert_eq!(anim.animate_wrapped(500.0), 5.0); // 50% forward
914        assert_eq!(anim.animate_wrapped(1000.0), 10.); // 100% forward
915
916        anim.transition(0.0, 1000.0);
917        assert!(anim.animate_wrapped(1500.0) > 7.5); // 25% backwards
918        assert_eq!(anim.animate_wrapped(2000.0), 5.0); // 50% backwards
919        assert!(anim.animate_wrapped(2500.0) < 2.5); // 75% backwards
920        assert_eq!(anim.animate_wrapped(3000.0), 0.0); // 100% backwards
921
922        anim.transition(10.0, 3000.0);
923        assert_eq!(anim.animate_wrapped(3250.0), 2.5); // 25% second forward
924        assert_eq!(anim.animate_wrapped(3500.0), 5.0); // 50% second forward
925        assert_eq!(anim.animate_wrapped(3750.0), 7.5); // 75% second forward
926        assert_eq!(anim.animate_wrapped(4000.0), 10.0); // 100% second forward
927    }
928
929    #[test]
930    fn test_asymmetric_auto_reversal() {
931        let mut anim = Animated::new(0.)
932            .duration(1000.)
933            .easing(Easing::Linear)
934            .asymmetric_duration(2000.)
935            .auto_reverse()
936            .repeat(1);
937
938        anim.transition(10.0, 0.0);
939
940        // ->
941        assert_eq!(anim.animate_wrapped(500.0), 5.0); // 50% forward
942        assert_eq!(anim.animate_wrapped(1000.0), 10.); // 100% forward
943
944        // <-
945        assert_eq!(anim.animate_wrapped(1500.0), 7.5); // 25% backwards
946        assert_eq!(anim.animate_wrapped(2000.0), 5.0); // 50% backwards
947        assert_eq!(anim.animate_wrapped(2500.0), 2.5); // 75% backwards
948        assert_eq!(anim.animate_wrapped(3000.0), 0.0); // 100% backwards
949
950        // ->
951        assert_eq!(anim.animate_wrapped(3250.0), 2.5); // 25% second forward
952        assert_eq!(anim.animate_wrapped(3500.0), 5.0); // 50% second forward
953        assert_eq!(anim.animate_wrapped(3750.0), 7.5); // 75% second forward
954        assert_eq!(anim.animate_wrapped(4000.0), 10.0); // 100% second forward
955
956        assert!(anim.eased_progress(3250.0) == anim.linear_progress(3250.0)); // 25% forward
957        assert!(anim.eased_progress(3500.0) == anim.linear_progress(3500.0)); // 50% forward
958        assert!(anim.eased_progress(3750.0) == anim.linear_progress(3750.0)); // 75% forward
959    }
960
961    #[test]
962    fn test_auto_reversal() {
963        let mut anim = Animated::new(0.)
964            .duration(1000.)
965            .auto_reverse()
966            .repeat(1)
967            .easing(Easing::Linear);
968
969        anim.transition(10.0, 0.0);
970
971        assert_eq!(anim.animate_wrapped(0.0), 0.0);
972
973        // ->
974        assert_eq!(anim.animate_wrapped(250.0), 2.5); // 25% forward
975        assert_eq!(anim.animate_wrapped(500.0), 5.0); // 50% forward
976        assert_eq!(anim.animate_wrapped(750.0), 7.5); // 75% forward
977        assert_eq!(anim.animate_wrapped(1000.0), 10.0); // 100% forward
978
979        // <-
980        assert_eq!(anim.animate_wrapped(1250.0), 7.5); // 25% backwards
981        assert_eq!(anim.animate_wrapped(1500.0), 5.0); // 50% backwards
982        assert_eq!(anim.animate_wrapped(1750.0), 2.5); // 75% backwards
983        assert_eq!(anim.animate_wrapped(2000.0), 0.0); // 100% backwards
984
985        // ->
986        assert_eq!(anim.animate_wrapped(2250.0), 2.5); // 25% forward
987        assert_eq!(anim.animate_wrapped(2500.0), 5.0); // 50% forward
988        assert_eq!(anim.animate_wrapped(2750.0), 7.5); // 75% forward
989        assert_eq!(anim.animate_wrapped(3000.0), 10.0); // 100% forward
990    }
991
992    #[test]
993    fn test_negative_values() {
994        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
995        anim.transition(-10.0, 0.0);
996        assert_eq!(anim.animate_wrapped(0.0), 0.0);
997        assert_eq!(anim.animate_wrapped(500.0), -5.0);
998        assert_eq!(anim.animate_wrapped(1000.0), -10.0);
999        anim.transition(-5.0, 1000.0);
1000        assert_eq!(anim.animate_wrapped(1000.0), -10.0);
1001        assert_eq!(anim.animate_wrapped(1500.0), -7.5);
1002        assert_eq!(anim.animate_wrapped(2000.0), -5.0);
1003    }
1004
1005    #[test]
1006    fn test_negative_to_positive_transition() {
1007        let mut anim = Animated::new(-5.).duration(1000.).easing(Easing::Linear);
1008        anim.transition(5.0, 0.0);
1009        assert_eq!(anim.animate_wrapped(0.0), -5.0);
1010        assert_eq!(anim.animate_wrapped(500.0), 0.0);
1011        assert_eq!(anim.animate_wrapped(1000.0), 5.0);
1012    }
1013
1014    #[test]
1015    fn test_interruption_with_negative_values() {
1016        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1017        anim.transition(-10.0, 0.0);
1018        assert_eq!(anim.animate_wrapped(250.0), -2.5);
1019        anim.transition(5.0, 250.0); // Interrupt at 25%
1020        assert_eq!(anim.animate_wrapped(750.0), 1.25); // Halfway to new destination
1021        assert_eq!(anim.animate_wrapped(1250.0), 5.0); // Completed to new destination
1022    }
1023
1024    #[test]
1025    fn test_multiple_interruptions() {
1026        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1027
1028        anim.transition(10.0, 0.0);
1029        assert_eq!(anim.animate_wrapped(500.0), 5.);
1030
1031        anim.transition(15.0, 500.0); // First interruption
1032        assert_eq!(anim.animate_wrapped(1000.0), 10.); // 50% to new destination
1033
1034        anim.transition(0.0, 1000.0); // Second interruption
1035        assert!(approximately_equal(anim.animate_wrapped(1500.0), 5.)); // Halfway to final destination
1036        assert_eq!(anim.animate_wrapped(2000.0), 0.0); // Completed to final destination
1037    }
1038
1039    #[test]
1040    fn test_interrupt_unchanged_destination() {
1041        // Interrupts that don't change the destination shouldn't change duration.
1042        // Despite the majority of other cases where we have to.
1043        let mut anim_a = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1044        let mut anim_b = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1045        anim_a.transition(10., 0.);
1046        anim_b.transition(10., 0.);
1047
1048        anim_a.transition(10., 250.);
1049        assert_eq!(anim_a.linear_progress(250.), anim_b.linear_progress(250.));
1050        assert_eq!(anim_a.linear_progress(500.), anim_b.linear_progress(500.));
1051        assert_eq!(anim_a.linear_progress(750.), anim_b.linear_progress(750.));
1052        assert_eq!(anim_a.linear_progress(1000.), anim_b.linear_progress(1000.));
1053    }
1054
1055    #[test]
1056    fn test_interruption_with_direction_change() {
1057        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1058        anim.transition(10.0, 0.0);
1059        assert_eq!(anim.animate_wrapped(500.0), 5.0);
1060        anim.transition(-5.0, 500.0); // Interrupt and change direction
1061        assert!(approximately_equal(anim.animate_wrapped(1000.0), 0.0)); // Halfway back to new destination
1062        assert_eq!(anim.animate_wrapped(1500.0), -5.0); // Completed to new destination
1063    }
1064
1065    #[test]
1066    fn test_zero_duration_transition() {
1067        let mut anim = Animated::new(0.).duration(0.).easing(Easing::Linear);
1068        anim.transition(10.0, 0.0);
1069        assert_eq!(anim.animate_wrapped(0.0), 10.0); // Should immediately reach the destination
1070        assert!(!anim.in_progress(0.0)); // Should not be in progress
1071    }
1072
1073    #[test]
1074    fn test_interruption_at_completion() {
1075        let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
1076        anim.transition(10.0, 0.0);
1077        assert_eq!(anim.animate_wrapped(1000.0), 10.0); // Completed
1078        anim.transition(20.0, 1000.0); // Interrupt right at completion
1079        assert_eq!(anim.animate_wrapped(1500.0), 15.0); // Halfway to new destination
1080        assert_eq!(anim.animate_wrapped(2000.0), 20.0); // Completed to new destination
1081    }
1082
1083    #[test]
1084    fn test_animate() {
1085        let mut anim = Animated::new(0.0f32)
1086            .duration(1000.0)
1087            .easing(Easing::Linear);
1088        anim.transition(10.0, 0.0);
1089
1090        let result = anim.animate_wrapped(500.0);
1091        assert_eq!(result, 5.0);
1092
1093        let result = anim.animate(|v| v * 2.0, 750.0);
1094        assert_eq!(result, 15.0);
1095    }
1096
1097    #[test]
1098    fn test_animate_if_eq() {
1099        let mut anim = Animated::new(0.0f32)
1100            .duration(1000.0)
1101            .easing(Easing::Linear);
1102        anim.transition(10.0, 0.0);
1103
1104        let result = anim.animate_if_eq(10.0, 100.0, 0.0, 500.0);
1105        assert_eq!(result, 50.0);
1106
1107        let result = anim.animate_if_eq(5.0, 100.0, 0.0, 500.0);
1108        assert_eq!(result, 0.0);
1109    }
1110
1111    #[test]
1112    fn test_animate_bool() {
1113        let mut anim = Animated::new(false).duration(1000.0).easing(Easing::Linear);
1114        anim.transition(true, 0.0);
1115
1116        let result = anim.animate_bool(0.0, 10.0, 500.0);
1117        assert_eq!(result, 5.0);
1118
1119        let result = anim.animate_bool(0.0, 10.0, 1000.0);
1120        assert_eq!(result, 10.0);
1121    }
1122
1123    #[test]
1124    fn test_animate_with_interruption() {
1125        let mut anim = Animated::new(0.0f32)
1126            .duration(1000.0)
1127            .easing(Easing::Linear);
1128        anim.transition(10.0, 0.0);
1129
1130        let result = anim.animate_wrapped(500.0);
1131        assert_eq!(result, 5.0);
1132
1133        anim.transition(20.0, 500.0);
1134        let result = anim.animate(|v| v, 1000.0);
1135        assert_eq!(result, 12.5);
1136
1137        let result = anim.animate_wrapped(1500.0);
1138        assert_eq!(result, 20.0);
1139    }
1140
1141    #[test]
1142    fn test_animate_with_custom_easing() {
1143        let custom_ease = Easing::Custom(|x| x.powi(2)); // Quadratic ease-in
1144        let mut anim = Animated::new(0.0f32).duration(1000.0).easing(custom_ease);
1145        anim.transition(10.0, 0.0);
1146
1147        let result = anim.animate(|v| v, 500.0);
1148        assert_eq!(result, 2.5); // (0.5^2 * 10)
1149
1150        let result = anim.animate(|v| v, 750.0);
1151        assert_eq!(result, 5.625); // (0.75^2 * 10)
1152    }
1153
1154    #[test]
1155    fn test_no_change_after_completion() {
1156        let anim = Animated::new(false)
1157            .duration(400.)
1158            .auto_start(true, 0.)
1159            .repeat(2)
1160            .auto_reverse();
1161        // Begin
1162        assert_eq!(anim.animate_bool(0., 10., 800.), 0.);
1163        assert_eq!(anim.animate_bool(0., 10., 1000.), 5.);
1164        assert_eq!(anim.animate_bool(0., 10., 1200.), 10.);
1165        assert_eq!(anim.animate_bool(0., 10., 1400.), 5.);
1166        assert_eq!(anim.animate_bool(0., 10., 1600.), 0.);
1167        assert_eq!(anim.animate_bool(0., 10., 1800.), 5.);
1168
1169        // Completion
1170        assert_eq!(anim.animate_bool(0., 10., 2000.), 10.);
1171
1172        // No changes after completion
1173        assert_eq!(anim.animate_bool(0., 10., 2200.), 10.);
1174        assert_eq!(anim.animate_bool(0., 10., 2400.), 10.);
1175        assert_eq!(anim.animate_bool(0., 10., 2600.), 10.);
1176        assert_eq!(anim.animate_bool(0., 10., 2800.), 10.);
1177        assert_eq!(anim.animate_bool(0., 10., 3000.), 10.);
1178    }
1179
1180    fn approximately_equal(a: f32, b: f32) -> bool {
1181        let close = f32::abs(a - b) < 1e-5;
1182        if !close {
1183            dbg!(a, b);
1184        }
1185        close
1186    }
1187}