1use crate::easing::{easing_name, parse_easing};
4use crate::error::non_negative;
5use crate::types::{f32_array, loop_from_count, parse_loop_mode};
6use animato_core::{Playable, Update};
7use animato_tween::{Loop, Tween as CoreTween, TweenState};
8use js_sys::Float32Array;
9use std::sync::{Arc, Mutex};
10use wasm_bindgen::prelude::*;
11
12type Shared<T> = Arc<Mutex<CoreTween<T>>>;
13
14pub(crate) fn lock<T>(shared: &Arc<Mutex<T>>) -> std::sync::MutexGuard<'_, T> {
15 shared
16 .lock()
17 .expect("animato-js shared animation lock poisoned")
18}
19
20pub(crate) fn state_name(state: &TweenState) -> &'static str {
21 match state {
22 TweenState::Idle => "idle",
23 TweenState::Running => "running",
24 TweenState::Paused => "paused",
25 TweenState::Completed => "completed",
26 }
27}
28
29macro_rules! shared_update {
30 ($name:ident, $value_ty:ty) => {
31 #[derive(Clone, Debug)]
32 pub(crate) struct $name {
33 inner: Shared<$value_ty>,
34 }
35
36 impl $name {
37 pub(crate) fn new(inner: Shared<$value_ty>) -> Self {
38 Self { inner }
39 }
40 }
41
42 impl Update for $name {
43 fn update(&mut self, dt: f32) -> bool {
44 lock(&self.inner).update(dt)
45 }
46 }
47
48 impl Playable for $name {
49 fn duration(&self) -> f32 {
50 lock(&self.inner).duration
51 }
52
53 fn reset(&mut self) {
54 lock(&self.inner).reset();
55 }
56
57 fn seek_to(&mut self, progress: f32) {
58 lock(&self.inner).seek(progress);
59 }
60
61 fn is_complete(&self) -> bool {
62 lock(&self.inner).is_complete()
63 }
64
65 fn as_any(&self) -> &dyn core::any::Any {
66 self
67 }
68
69 fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
70 self
71 }
72 }
73 };
74}
75
76shared_update!(SharedTween, f32);
77shared_update!(SharedTween2D, [f32; 2]);
78shared_update!(SharedTween3D, [f32; 3]);
79shared_update!(SharedTween4D, [f32; 4]);
80
81#[wasm_bindgen(js_name = Tween)]
83#[derive(Clone, Debug)]
84pub struct Tween {
85 inner: Shared<f32>,
86}
87
88#[wasm_bindgen(js_class = Tween)]
89impl Tween {
90 #[wasm_bindgen(constructor)]
92 pub fn new(from: f32, to: f32, duration: f32) -> Self {
93 Self {
94 inner: Arc::new(Mutex::new(
95 CoreTween::new(from, to)
96 .duration(non_negative(duration, 1.0))
97 .build(),
98 )),
99 }
100 }
101
102 pub fn update(&self, dt: f32) -> bool {
104 lock(&self.inner).update(dt)
105 }
106
107 pub fn value(&self) -> f32 {
109 lock(&self.inner).value()
110 }
111
112 pub fn progress(&self) -> f32 {
114 lock(&self.inner).progress()
115 }
116
117 #[wasm_bindgen(js_name = easedProgress)]
119 pub fn eased_progress(&self) -> f32 {
120 lock(&self.inner).eased_progress()
121 }
122
123 pub fn state(&self) -> String {
125 state_name(lock(&self.inner).state()).to_owned()
126 }
127
128 pub fn easing(&self) -> String {
130 easing_name(&lock(&self.inner).easing).to_owned()
131 }
132
133 #[wasm_bindgen(js_name = isComplete)]
135 pub fn is_complete(&self) -> bool {
136 lock(&self.inner).is_complete()
137 }
138
139 pub fn pause(&self) {
141 lock(&self.inner).pause();
142 }
143
144 pub fn resume(&self) {
146 lock(&self.inner).resume();
147 }
148
149 pub fn reset(&self) {
151 lock(&self.inner).reset();
152 }
153
154 pub fn reverse(&self) {
156 lock(&self.inner).reverse();
157 }
158
159 pub fn seek(&self, progress: f32) {
161 lock(&self.inner).seek(progress);
162 }
163
164 #[wasm_bindgen(js_name = setEasing)]
166 pub fn set_easing(&self, name: &str) -> Result<(), JsValue> {
167 lock(&self.inner).easing = parse_easing(name)?;
168 Ok(())
169 }
170
171 #[wasm_bindgen(js_name = setCubicBezier)]
173 pub fn set_cubic_bezier(&self, x1: f32, y1: f32, x2: f32, y2: f32) {
174 lock(&self.inner).easing = animato_core::Easing::CubicBezier(x1, y1, x2, y2);
175 }
176
177 #[wasm_bindgen(js_name = setTimeScale)]
179 pub fn set_time_scale(&self, scale: f32) {
180 lock(&self.inner).time_scale = non_negative(scale, 1.0);
181 }
182
183 #[wasm_bindgen(js_name = setDelay)]
185 pub fn set_delay(&self, delay: f32) {
186 lock(&self.inner).delay = non_negative(delay, 0.0);
187 }
188
189 #[wasm_bindgen(js_name = setLoopCount)]
191 pub fn set_loop_count(&self, count: u32) {
192 lock(&self.inner).looping = loop_from_count(count);
193 }
194
195 #[wasm_bindgen(js_name = setLoopMode)]
197 pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
198 lock(&self.inner).looping = parse_loop_mode(mode)?;
199 Ok(())
200 }
201
202 #[wasm_bindgen(js_name = setPingPong)]
204 pub fn set_ping_pong(&self) {
205 lock(&self.inner).looping = Loop::PingPong;
206 }
207
208 #[wasm_bindgen(js_name = setForever)]
210 pub fn set_forever(&self) {
211 lock(&self.inner).looping = Loop::Forever;
212 }
213
214 pub(crate) fn shared(&self) -> SharedTween {
215 SharedTween::new(Arc::clone(&self.inner))
216 }
217}
218
219macro_rules! vector_tween {
220 (
221 $class:ident,
222 $js_name:ident,
223 $shared:ident,
224 $value_ty:ty,
225 [$($from:ident),+],
226 [$($to:ident),+],
227 $array_fn:ident,
228 [$($component:ident : $index:tt),+]
229 ) => {
230 #[wasm_bindgen(js_name = $js_name)]
232 #[derive(Clone, Debug)]
233 pub struct $class {
234 inner: Shared<$value_ty>,
235 }
236
237 #[wasm_bindgen(js_class = $js_name)]
238 impl $class {
239 #[wasm_bindgen(constructor)]
241 #[allow(clippy::too_many_arguments)]
242 pub fn new($($from: f32,)+ $($to: f32,)+ duration: f32) -> Self {
243 Self {
244 inner: Arc::new(Mutex::new(
245 CoreTween::new([$($from),+], [$($to),+])
246 .duration(non_negative(duration, 1.0))
247 .build(),
248 )),
249 }
250 }
251
252 pub fn update(&self, dt: f32) -> bool {
254 lock(&self.inner).update(dt)
255 }
256
257 #[wasm_bindgen(js_name = toArray)]
259 pub fn to_array(&self) -> Float32Array {
260 let value = lock(&self.inner).value();
261 f32_array(&value)
262 }
263
264 $(
265 pub fn $component(&self) -> f32 {
267 lock(&self.inner).value()[$index]
268 }
269 )+
270
271 pub fn progress(&self) -> f32 {
273 lock(&self.inner).progress()
274 }
275
276 #[wasm_bindgen(js_name = easedProgress)]
278 pub fn eased_progress(&self) -> f32 {
279 lock(&self.inner).eased_progress()
280 }
281
282 #[wasm_bindgen(js_name = isComplete)]
284 pub fn is_complete(&self) -> bool {
285 lock(&self.inner).is_complete()
286 }
287
288 pub fn pause(&self) {
290 lock(&self.inner).pause();
291 }
292
293 pub fn resume(&self) {
295 lock(&self.inner).resume();
296 }
297
298 pub fn reset(&self) {
300 lock(&self.inner).reset();
301 }
302
303 pub fn reverse(&self) {
305 lock(&self.inner).reverse();
306 }
307
308 pub fn seek(&self, progress: f32) {
310 lock(&self.inner).seek(progress);
311 }
312
313 #[wasm_bindgen(js_name = setEasing)]
315 pub fn set_easing(&self, name: &str) -> Result<(), JsValue> {
316 lock(&self.inner).easing = parse_easing(name)?;
317 Ok(())
318 }
319
320 #[wasm_bindgen(js_name = setTimeScale)]
322 pub fn set_time_scale(&self, scale: f32) {
323 lock(&self.inner).time_scale = non_negative(scale, 1.0);
324 }
325
326 #[wasm_bindgen(js_name = setLoopMode)]
328 pub fn set_loop_mode(&self, mode: &str) -> Result<(), JsValue> {
329 lock(&self.inner).looping = parse_loop_mode(mode)?;
330 Ok(())
331 }
332
333 pub(crate) fn shared(&self) -> $shared {
334 $shared::new(Arc::clone(&self.inner))
335 }
336 }
337 };
338}
339
340vector_tween!(
341 Tween2D,
342 Tween2D,
343 SharedTween2D,
344 [f32; 2],
345 [from_x, from_y],
346 [to_x, to_y],
347 vec2,
348 [x: 0, y: 1]
349);
350
351vector_tween!(
352 Tween3D,
353 Tween3D,
354 SharedTween3D,
355 [f32; 3],
356 [from_x, from_y, from_z],
357 [to_x, to_y, to_z],
358 vec3,
359 [x: 0, y: 1, z: 2]
360);
361
362vector_tween!(
363 Tween4D,
364 Tween4D,
365 SharedTween4D,
366 [f32; 4],
367 [from_x, from_y, from_z, from_w],
368 [to_x, to_y, to_z, to_w],
369 vec4,
370 [x: 0, y: 1, z: 2, w: 3]
371);
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn scalar_tween_updates() {
379 let tween = Tween::new(0.0, 10.0, 1.0);
380 tween.set_easing("linear").unwrap();
381 assert!(tween.update(0.5));
382 assert_eq!(tween.value(), 5.0);
383 }
384
385 #[test]
386 fn vector_tween_updates() {
387 let tween = Tween2D::new(0.0, 0.0, 10.0, 20.0, 1.0);
388 tween.update(0.5);
389 assert_eq!(tween.x(), 5.0);
390 assert_eq!(tween.y(), 10.0);
391 }
392}