anim/core/
animatable.rs

1// anim
2//
3// A framework independent animation library for rust, works nicely with Iced and the others
4// Copyright: 2021, Joylei <leingliu@gmail.com>
5// License: MIT
6
7#![allow(non_snake_case)]
8
9use std::marker::PhantomData;
10
11///  generates output values based on its timing progress
12///
13/// see [`crate::Timeline`]
14///
15/// Types derives [`Animatable`]:
16/// - `bool`
17/// - `i8`
18/// - `u8`
19/// - `i16`
20/// - `u16`
21/// - `i32`
22/// - `u32`
23/// - `i64`
24/// - `u64`
25/// - `f32`
26/// - `f64`
27/// - `i128`
28/// - `u168`
29/// - `Unit`
30/// - `Tuple`
31/// - `char`
32/// - `Option<T>` where `T:Animatable`
33/// - `PhantomData<T>`
34/// - `[T;N]` where `T:Animatable`
35pub trait Animatable: Sized + Clone {
36    /// generates output values based on its timing progress
37    fn animate(&self, to: &Self, time: f64) -> Self;
38}
39
40//-------- primitives -----------
41macro_rules! impl_primitive {
42    ($ty:ident) => {
43        impl Animatable for $ty {
44            #[inline]
45            fn animate(&self, to: &Self, time: f64) -> Self {
46                if time == 0.0 {
47                    return *self;
48                }
49                if (1.0 - time).abs() < f64::EPSILON {
50                    return *to;
51                }
52                if self == to {
53                    return *self;
54                }
55                crate::utils::check_time(time);
56                let v = (*self as f64) * (1.0 - time) + (*to as f64) * time;
57                if *to >= *self {
58                    (v + 0.5) as Self
59                } else {
60                    (v - 0.5) as Self
61                }
62            }
63        }
64    };
65    ($ty:ident, float) => {
66        impl Animatable for $ty {
67            #[inline]
68            fn animate(&self, to: &Self, time: f64) -> Self {
69                if time == 0.0 {
70                    return *self;
71                }
72                if (1.0 - time).abs() < f64::EPSILON {
73                    return *to;
74                }
75                if (self - to).abs() < $ty::EPSILON {
76                    return *self;
77                }
78                crate::utils::check_time(time);
79                // from + (to-from) * time
80                let v = (*self as f64) * (1.0 - time) + (*to as f64) * time;
81                v as Self
82            }
83        }
84    };
85}
86
87impl_primitive!(u8);
88impl_primitive!(u16);
89impl_primitive!(u32);
90impl_primitive!(u64);
91impl_primitive!(u128);
92impl_primitive!(usize);
93impl_primitive!(i8);
94impl_primitive!(i16);
95impl_primitive!(i32);
96impl_primitive!(i64);
97impl_primitive!(i128);
98impl_primitive!(isize);
99impl_primitive!(f32, float);
100impl_primitive!(f64, float);
101
102impl Animatable for bool {
103    #[inline]
104    fn animate(&self, to: &Self, time: f64) -> Self {
105        if time < 1.0 {
106            *self
107        } else {
108            *to
109        }
110    }
111}
112
113impl Animatable for char {
114    #[inline]
115    fn animate(&self, to: &Self, time: f64) -> Self {
116        if self == to {
117            return *self;
118        }
119
120        let from_idx = *self as u32;
121        let to_idx = *to as u32;
122        let idx = from_idx.animate(&to_idx, time);
123        let n = if from_idx > to_idx {
124            from_idx - idx
125        } else {
126            idx - from_idx
127        };
128        let mut rng = *self..=*to;
129        match rng.nth(n as usize) {
130            Some(c) => c,
131            None => *self,
132        }
133    }
134}
135
136impl Animatable for () {
137    #[inline]
138    fn animate(&self, _to: &Self, _time: f64) -> Self {}
139}
140
141impl<T> Animatable for PhantomData<T> {
142    #[inline]
143    fn animate(&self, _to: &Self, _time: f64) -> Self {
144        Default::default()
145    }
146}
147
148impl<T: Animatable> Animatable for Option<T> {
149    #[inline]
150    fn animate(&self, to: &Self, time: f64) -> Self {
151        match (self, to) {
152            (Some(a), Some(b)) => Some(a.animate(b, time)),
153            _ => None,
154        }
155    }
156}
157
158impl<T: Animatable, const N: usize> Animatable for [T; N] {
159    #[inline]
160    fn animate(&self, to: &Self, time: f64) -> Self {
161        let mut res = self.clone();
162        self.iter()
163            .zip(to.iter())
164            .zip(res.iter_mut())
165            .for_each(|((a, b), c)| *c = a.animate(b, time));
166        res
167    }
168}
169
170//-------- tuples -----------
171
172macro_rules! impl_tuple {
173    ($($n:tt $name:ident)+) => {
174        impl<'de, $($name,)+> Animatable for ($($name,)+)
175        where
176            $($name: Animatable,)+
177        {
178            #[inline]
179            fn animate(&self, to: &Self, time: f64) -> Self
180            {
181                $(
182                    let $name = Animatable::animate(&self.$n, &to.$n, time);
183                )+
184                ($($name,)+)
185            }
186        }
187    }
188}
189
190impl_tuple!(0 T0);
191impl_tuple!(0 T0 1 T1);
192impl_tuple!(0 T0 1 T1 2 T2);
193impl_tuple!(0 T0 1 T1 2 T2 3 T3);
194impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4);
195impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5);
196impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6);
197impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7);
198impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8);
199impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9);
200impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10);
201impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11);
202impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12);
203impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13);
204impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14);
205impl_tuple!(0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15);
206
207#[cfg(test)]
208mod test {
209    use crate::Animatable;
210
211    #[test]
212    fn test_bool() {
213        let v = false.animate(&true, 0.0);
214        assert!(v == false);
215
216        let v = false.animate(&true, 0.5);
217        assert!(v == false);
218
219        let v = false.animate(&true, 1.0);
220        assert!(v == true);
221
222        let v = true.animate(&true, 0.3);
223        assert!(v == true);
224
225        let v = false.animate(&false, 0.2);
226        assert!(v == false);
227    }
228
229    #[test]
230    fn test_char() {
231        let v = 'a'.animate(&'e', 0.0);
232        assert_eq!(v, 'a');
233
234        let v = 'a'.animate(&'e', 0.5);
235        assert_eq!(v, 'c');
236
237        let v = 'a'.animate(&'e', 0.555);
238        assert_eq!(v, 'c');
239
240        let v = 'a'.animate(&'e', 1.0);
241        assert_eq!(v, 'e');
242    }
243}