Skip to main content

animato_js/
keyframe.rs

1//! Keyframe track bindings.
2
3use crate::easing::parse_easing;
4use crate::tween::lock;
5use crate::types::{f32_array, parse_loop_mode};
6use animato_core::{Playable, Update};
7use animato_tween::KeyframeTrack as CoreKeyframeTrack;
8use js_sys::Float32Array;
9use std::sync::{Arc, Mutex};
10use wasm_bindgen::prelude::*;
11
12type Shared<T> = Arc<Mutex<CoreKeyframeTrack<T>>>;
13
14macro_rules! shared_keyframes {
15    ($name:ident, $value_ty:ty) => {
16        #[derive(Clone, Debug)]
17        pub(crate) struct $name {
18            inner: Shared<$value_ty>,
19        }
20
21        impl $name {
22            pub(crate) fn new(inner: Shared<$value_ty>) -> Self {
23                Self { inner }
24            }
25        }
26
27        impl Update for $name {
28            fn update(&mut self, dt: f32) -> bool {
29                lock(&self.inner).update(dt)
30            }
31        }
32
33        impl Playable for $name {
34            fn duration(&self) -> f32 {
35                lock(&self.inner).duration()
36            }
37
38            fn reset(&mut self) {
39                lock(&self.inner).reset();
40            }
41
42            fn seek_to(&mut self, progress: f32) {
43                let mut track = lock(&self.inner);
44                Playable::seek_to(&mut *track, progress);
45            }
46
47            fn is_complete(&self) -> bool {
48                lock(&self.inner).is_complete()
49            }
50
51            fn as_any(&self) -> &dyn core::any::Any {
52                self
53            }
54
55            fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
56                self
57            }
58        }
59    };
60}
61
62shared_keyframes!(SharedKeyframeTrack, f32);
63shared_keyframes!(SharedKeyframeTrack2D, [f32; 2]);
64shared_keyframes!(SharedKeyframeTrack3D, [f32; 3]);
65shared_keyframes!(SharedKeyframeTrack4D, [f32; 4]);
66
67/// Scalar keyframe track.
68#[wasm_bindgen(js_name = KeyframeTrack)]
69#[derive(Clone, Debug)]
70pub struct KeyframeTrack {
71    inner: Shared<f32>,
72}
73
74#[wasm_bindgen(js_class = KeyframeTrack)]
75impl KeyframeTrack {
76    /// Create an empty keyframe track.
77    #[wasm_bindgen(constructor)]
78    pub fn new() -> Self {
79        Self {
80            inner: Arc::new(Mutex::new(CoreKeyframeTrack::new())),
81        }
82    }
83
84    /// Add a linear keyframe.
85    pub fn push(&self, time: f32, value: f32) {
86        let mut track = lock(&self.inner);
87        let next = core::mem::take(&mut *track).push(time, value);
88        *track = next;
89    }
90
91    /// Add a keyframe with easing applied to the following segment.
92    #[wasm_bindgen(js_name = pushEased)]
93    pub fn push_eased(&self, time: f32, value: f32, easing: &str) -> Result<(), JsValue> {
94        let easing = parse_easing(easing)?;
95        let mut track = lock(&self.inner);
96        let next = core::mem::take(&mut *track).push_eased(time, value, easing);
97        *track = next;
98        Ok(())
99    }
100
101    /// Advance by `dt` seconds.
102    pub fn update(&self, dt: f32) -> bool {
103        lock(&self.inner).update(dt)
104    }
105
106    /// Current value, or `NaN` if the track is empty.
107    pub fn value(&self) -> f32 {
108        lock(&self.inner).value().unwrap_or(f32::NAN)
109    }
110
111    /// Value at an absolute time, or `NaN` if the track is empty.
112    #[wasm_bindgen(js_name = valueAt)]
113    pub fn value_at(&self, seconds: f32) -> f32 {
114        lock(&self.inner).value_at(seconds).unwrap_or(f32::NAN)
115    }
116
117    /// Track duration in seconds.
118    pub fn duration(&self) -> f32 {
119        lock(&self.inner).duration()
120    }
121
122    /// Normalized track progress.
123    pub fn progress(&self) -> f32 {
124        lock(&self.inner).progress()
125    }
126
127    /// Whether playback is complete.
128    #[wasm_bindgen(js_name = isComplete)]
129    pub fn is_complete(&self) -> bool {
130        lock(&self.inner).is_complete()
131    }
132
133    /// Reset playback.
134    pub fn reset(&self) {
135        lock(&self.inner).reset();
136    }
137
138    /// Set loop mode by string.
139    #[wasm_bindgen(js_name = setLoopMode)]
140    pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
141        let mut track = lock(&self.inner);
142        let next = core::mem::take(&mut *track).looping(parse_loop_mode(mode)?);
143        *track = next;
144        Ok(())
145    }
146
147    pub(crate) fn shared(&self) -> SharedKeyframeTrack {
148        SharedKeyframeTrack::new(Arc::clone(&self.inner))
149    }
150}
151
152impl Default for KeyframeTrack {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158macro_rules! vector_track {
159    (
160        $class:ident,
161        $js_name:ident,
162        $shared:ident,
163        $value_ty:ty,
164        [$($value:ident),+]
165    ) => {
166        /// Vector keyframe track.
167        #[wasm_bindgen(js_name = $js_name)]
168        #[derive(Clone, Debug)]
169        pub struct $class {
170            inner: Shared<$value_ty>,
171        }
172
173        #[wasm_bindgen(js_class = $js_name)]
174        impl $class {
175            /// Create an empty vector keyframe track.
176            #[wasm_bindgen(constructor)]
177            pub fn new() -> Self {
178                Self {
179                    inner: Arc::new(Mutex::new(CoreKeyframeTrack::new())),
180                }
181            }
182
183            /// Add a linear vector keyframe.
184            pub fn push(&self, time: f32, $($value: f32),+) {
185                let mut track = lock(&self.inner);
186                let next = core::mem::take(&mut *track).push(time, [$($value),+]);
187                *track = next;
188            }
189
190            /// Add a vector keyframe with easing.
191            #[wasm_bindgen(js_name = pushEased)]
192            pub fn push_eased(&self, time: f32, $($value: f32,)+ easing: &str) -> Result<(), JsValue> {
193                let easing = parse_easing(easing)?;
194                let mut track = lock(&self.inner);
195                let next = core::mem::take(&mut *track).push_eased(time, [$($value),+], easing);
196                *track = next;
197                Ok(())
198            }
199
200            /// Advance by `dt` seconds.
201            pub fn update(&self, dt: f32) -> bool {
202                lock(&self.inner).update(dt)
203            }
204
205            /// Current vector value.
206            #[wasm_bindgen(js_name = toArray)]
207            pub fn to_array(&self) -> Float32Array {
208                let values = lock(&self.inner).value().unwrap_or_default();
209                f32_array(&values)
210            }
211
212            /// Value at an absolute time.
213            #[wasm_bindgen(js_name = valueAt)]
214            pub fn value_at(&self, seconds: f32) -> Float32Array {
215                let values = lock(&self.inner).value_at(seconds).unwrap_or_default();
216                f32_array(&values)
217            }
218
219            /// Track duration in seconds.
220            pub fn duration(&self) -> f32 {
221                lock(&self.inner).duration()
222            }
223
224            /// Normalized track progress.
225            pub fn progress(&self) -> f32 {
226                lock(&self.inner).progress()
227            }
228
229            /// Whether playback is complete.
230            #[wasm_bindgen(js_name = isComplete)]
231            pub fn is_complete(&self) -> bool {
232                lock(&self.inner).is_complete()
233            }
234
235            /// Reset playback.
236            pub fn reset(&self) {
237                lock(&self.inner).reset();
238            }
239
240            /// Set loop mode by string.
241            #[wasm_bindgen(js_name = setLoopMode)]
242            pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
243                let mut track = lock(&self.inner);
244                let next = core::mem::take(&mut *track).looping(parse_loop_mode(mode)?);
245                *track = next;
246                Ok(())
247            }
248
249            pub(crate) fn shared(&self) -> $shared {
250                $shared::new(Arc::clone(&self.inner))
251            }
252        }
253
254        impl Default for $class {
255            fn default() -> Self {
256                Self::new()
257            }
258        }
259    };
260}
261
262vector_track!(
263    KeyframeTrack2D,
264    KeyframeTrack2D,
265    SharedKeyframeTrack2D,
266    [f32; 2],
267    [x, y]
268);
269vector_track!(
270    KeyframeTrack3D,
271    KeyframeTrack3D,
272    SharedKeyframeTrack3D,
273    [f32; 3],
274    [x, y, z]
275);
276vector_track!(
277    KeyframeTrack4D,
278    KeyframeTrack4D,
279    SharedKeyframeTrack4D,
280    [f32; 4],
281    [x, y, z, w]
282);
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn keyframes_interpolate() {
290        let track = KeyframeTrack::new();
291        track.push(0.0, 0.0);
292        track.push_eased(1.0, 100.0, "linear").unwrap();
293        track.update(0.5);
294        assert_eq!(track.value(), 50.0);
295    }
296}