1use crate::easing::parse_easing;
4use crate::error::non_negative;
5use crate::tween::lock;
6use crate::types::{f32_array, flat_points, points_to_array, vec2};
7use animato_core::{Playable, Update};
8use animato_path::{
9 DrawSvg, LineSegment, MorphPath as CoreMorphPath, MotionPath as CoreMotionPath,
10 MotionPathTween as CoreMotionPathTween, PathEvaluate,
11};
12use js_sys::Float32Array;
13use std::sync::{Arc, Mutex};
14use wasm_bindgen::prelude::*;
15
16#[derive(Clone, Debug)]
18pub(crate) struct SharedMotionPath {
19 inner: Arc<Mutex<CoreMotionPathTween>>,
20}
21
22impl SharedMotionPath {
23 pub(crate) fn new(inner: Arc<Mutex<CoreMotionPathTween>>) -> Self {
24 Self { inner }
25 }
26}
27
28impl Update for SharedMotionPath {
29 fn update(&mut self, dt: f32) -> bool {
30 lock(&self.inner).update(dt)
31 }
32}
33
34impl Playable for SharedMotionPath {
35 fn duration(&self) -> f32 {
36 lock(&self.inner).tween().duration
37 }
38
39 fn reset(&mut self) {
40 lock(&self.inner).reset();
41 }
42
43 fn seek_to(&mut self, progress: f32) {
44 lock(&self.inner).seek(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#[wasm_bindgen(js_name = MotionPath)]
62#[derive(Clone, Debug)]
63pub struct MotionPath {
64 inner: Arc<Mutex<CoreMotionPathTween>>,
65}
66
67#[wasm_bindgen(js_class = MotionPath)]
68impl MotionPath {
69 #[wasm_bindgen(constructor)]
71 pub fn new(svg_path: &str, duration: f32) -> Result<Self, JsValue> {
72 let path = CoreMotionPath::try_from_svg(svg_path)
73 .map_err(|err| JsValue::from_str(&err.to_string()))?;
74 Ok(Self {
75 inner: Arc::new(Mutex::new(
76 CoreMotionPathTween::new(path)
77 .duration(non_negative(duration, 1.0))
78 .build(),
79 )),
80 })
81 }
82
83 #[wasm_bindgen(js_name = line)]
85 pub fn line(from_x: f32, from_y: f32, to_x: f32, to_y: f32, duration: f32) -> Self {
86 Self {
87 inner: Arc::new(Mutex::new(
88 CoreMotionPathTween::new(LineSegment::new([from_x, from_y], [to_x, to_y]))
89 .duration(non_negative(duration, 1.0))
90 .build(),
91 )),
92 }
93 }
94
95 pub fn update(&self, dt: f32) -> bool {
97 lock(&self.inner).update(dt)
98 }
99
100 pub fn x(&self) -> f32 {
102 lock(&self.inner).value()[0]
103 }
104
105 pub fn y(&self) -> f32 {
107 lock(&self.inner).value()[1]
108 }
109
110 #[wasm_bindgen(js_name = toArray)]
112 pub fn to_array(&self) -> Float32Array {
113 let pos = lock(&self.inner).value();
114 vec2(pos[0], pos[1])
115 }
116
117 #[wasm_bindgen(js_name = rotationDeg)]
119 pub fn rotation_deg(&self) -> f32 {
120 lock(&self.inner).rotation_deg()
121 }
122
123 pub fn progress(&self) -> f32 {
125 lock(&self.inner).path_progress()
126 }
127
128 #[wasm_bindgen(js_name = isComplete)]
130 pub fn is_complete(&self) -> bool {
131 lock(&self.inner).is_complete()
132 }
133
134 pub fn reset(&self) {
136 lock(&self.inner).reset();
137 }
138
139 pub fn seek(&self, progress: f32) {
141 lock(&self.inner).seek(progress);
142 }
143
144 #[wasm_bindgen(js_name = setEasing)]
146 pub fn set_easing(&self, easing: &str) -> Result<(), JsValue> {
147 lock(&self.inner).tween_mut().easing = parse_easing(easing)?;
148 Ok(())
149 }
150
151 #[wasm_bindgen(js_name = setAutoRotate)]
153 pub fn set_auto_rotate(&self, yes: bool) {
154 lock(&self.inner).set_auto_rotate(yes);
155 }
156
157 #[wasm_bindgen(js_name = setOffsets)]
159 pub fn set_offsets(&self, start: f32, end: f32) {
160 lock(&self.inner).set_offsets(start, end);
161 }
162
163 #[wasm_bindgen(js_name = drawOn)]
165 pub fn draw_on(&self, progress: f32) -> Float32Array {
166 let values = lock(&self.inner).path().draw_on(progress);
167 f32_array(&[values.dash_array, values.dash_offset, values.progress()])
168 }
169
170 #[wasm_bindgen(js_name = drawOnReverse)]
172 pub fn draw_on_reverse(&self, progress: f32) -> Float32Array {
173 let values = lock(&self.inner).path().draw_on_reverse(progress);
174 f32_array(&[values.dash_array, values.dash_offset, values.progress()])
175 }
176
177 #[wasm_bindgen(js_name = totalLength)]
179 pub fn total_length(&self) -> f32 {
180 lock(&self.inner).path().arc_length()
181 }
182
183 pub(crate) fn shared(&self) -> SharedMotionPath {
184 SharedMotionPath::new(Arc::clone(&self.inner))
185 }
186}
187
188#[wasm_bindgen(js_name = MorphPath)]
190#[derive(Clone, Debug)]
191pub struct MorphPath {
192 inner: CoreMorphPath,
193}
194
195#[wasm_bindgen(js_class = MorphPath)]
196impl MorphPath {
197 #[wasm_bindgen(constructor)]
199 pub fn new(from: &Float32Array, to: &Float32Array, resolution: usize) -> Result<Self, JsValue> {
200 Ok(Self {
201 inner: CoreMorphPath::with_resolution(
202 flat_points(from)?,
203 flat_points(to)?,
204 resolution.max(2),
205 ),
206 })
207 }
208
209 pub fn evaluate(&self, progress: f32) -> Float32Array {
211 points_to_array(&self.inner.evaluate(progress))
212 }
213
214 #[wasm_bindgen(js_name = boundsAt)]
216 pub fn bounds_at(&self, progress: f32) -> Float32Array {
217 f32_array(&self.inner.bounds_at(progress))
218 }
219
220 #[wasm_bindgen(js_name = pointCount)]
222 pub fn point_count(&self) -> usize {
223 self.inner.point_count()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn motion_path_line_updates() {
233 let path = MotionPath::line(0.0, 0.0, 100.0, 0.0, 1.0);
234 path.update(0.5);
235 assert_eq!(path.x(), 50.0);
236 }
237}