motion_canvas_rs/core/animation/
tween.rs1use crate::core::animation::base::Animation;
2use crate::elements::shapes::{PathData, PathNode};
3use glam::Vec2;
4use kurbo::Affine;
5use peniko::Color;
6use std::sync::{Arc, Mutex};
7use std::time::Duration;
8
9const DEFAULT_EASING: fn(f32) -> f32 = crate::core::easings::cubic_in_out;
10
11fn lerp(a: f32, b: f32, t: f32) -> f32 {
12 a + (b - a) * t
13}
14
15pub trait Tweenable: Clone + Send + Sync + std::fmt::Debug + 'static {
17 fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
19 fn state_hash(&self) -> u64;
21}
22
23impl Tweenable for f32 {
24 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
25 lerp(*a, *b, t)
26 }
27 fn state_hash(&self) -> u64 {
28 crate::assets::hash::hash_f32(*self)
29 }
30}
31
32impl Tweenable for Vec2 {
33 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
34 Vec2::new(lerp(a.x, b.x, t), lerp(a.y, b.y, t))
35 }
36 fn state_hash(&self) -> u64 {
37 crate::assets::hash::combine_hashes(
38 crate::assets::hash::hash_f32(self.x),
39 crate::assets::hash::hash_f32(self.y),
40 )
41 }
42}
43
44impl Tweenable for Vec<Vec2> {
45 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
46 if a.len() == b.len() {
47 a.iter()
48 .zip(b.iter())
49 .map(|(v1, v2)| Vec2::interpolate(v1, v2, t))
50 .collect()
51 } else if t >= 1.0 {
52 b.clone()
53 } else {
54 a.clone()
55 }
56 }
57 fn state_hash(&self) -> u64 {
58 let mut h = crate::assets::hash::Hasher::new();
59 for v in self {
60 h.update_u64(v.state_hash());
61 }
62 h.finish()
63 }
64}
65
66impl Tweenable for bool {
67 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
68 if t >= 1.0 {
69 *b
70 } else {
71 *a
72 }
73 }
74 fn state_hash(&self) -> u64 {
75 if *self {
76 1
77 } else {
78 0
79 }
80 }
81}
82
83impl Tweenable for String {
84 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
85 if t >= 1.0 {
86 b.clone()
87 } else {
88 a.clone()
89 }
90 }
91 fn state_hash(&self) -> u64 {
92 crate::assets::hash::hash_str(self)
93 }
94}
95
96impl Tweenable for Color {
97 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
98 let t = t.clamp(0.0, 1.0);
99 Color::rgba8(
100 lerp(a.r as f32, b.r as f32, t) as u8,
101 lerp(a.g as f32, b.g as f32, t) as u8,
102 lerp(a.b as f32, b.b as f32, t) as u8,
103 lerp(a.a as f32, b.a as f32, t) as u8,
104 )
105 }
106 fn state_hash(&self) -> u64 {
107 let mut h = crate::assets::hash::Hasher::new();
108 h.update_bytes(&[self.r, self.g, self.b, self.a]);
109 h.finish()
110 }
111}
112
113impl Tweenable for Affine {
114 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
115 let t = t as f64;
116 let c1 = a.as_coeffs();
117 let c2 = b.as_coeffs();
118 Affine::new([
119 c1[0] + (c2[0] - c1[0]) * t,
120 c1[1] + (c2[1] - c1[1]) * t,
121 c1[2] + (c2[2] - c1[2]) * t,
122 c1[3] + (c2[3] - c1[3]) * t,
123 c1[4] + (c2[4] - c1[4]) * t,
124 c1[5] + (c2[5] - c1[5]) * t,
125 ])
126 }
127 fn state_hash(&self) -> u64 {
128 let c = self.as_coeffs();
129 let mut h = crate::assets::hash::Hasher::new();
130 for val in c {
131 h.update_u64(val.to_bits() as u64);
132 }
133 h.finish()
134 }
135}
136
137impl<T: Tweenable> Tweenable for Option<T> {
138 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
139 match (a, b) {
140 (Some(va), Some(vb)) => Some(T::interpolate(va, vb, t)),
141 (Some(va), None) => {
142 if t < 0.5 {
143 Some(va.clone())
144 } else {
145 None
146 }
147 }
148 (None, Some(vb)) => {
149 if t >= 0.5 {
150 Some(vb.clone())
151 } else {
152 None
153 }
154 }
155 (None, None) => None,
156 }
157 }
158 fn state_hash(&self) -> u64 {
159 match self {
160 Some(v) => {
161 let mut h = crate::assets::hash::Hasher::new();
162 h.update_u64(1);
163 h.update_u64(v.state_hash());
164 h.finish()
165 }
166 None => 0,
167 }
168 }
169}
170
171pub struct SignalData<T> {
173 pub value: T,
175 pub initial: T,
177}
178
179#[derive(Clone)]
194pub struct Signal<T> {
195 pub data: Arc<Mutex<SignalData<T>>>,
196}
197
198pub trait FromVec2: Send + Sync + 'static {
201 fn from_vec2(v: Vec2) -> Self;
202}
203
204impl FromVec2 for Vec2 {
205 fn from_vec2(v: Vec2) -> Self {
206 v
207 }
208}
209
210impl FromVec2 for Affine {
211 fn from_vec2(v: Vec2) -> Self {
212 Affine::translate((v.x as f64, v.y as f64))
213 }
214}
215
216pub enum Target<T> {
218 Fixed(T),
220 Lazy(Arc<dyn Fn(&T) -> T + Send + Sync>),
222}
223
224impl<T: Clone> Clone for Target<T> {
225 fn clone(&self) -> Self {
226 match self {
227 Self::Fixed(v) => Self::Fixed(v.clone()),
228 Self::Lazy(f) => Self::Lazy(f.clone()),
229 }
230 }
231}
232impl<T: Tweenable + PartialEq> Signal<T> {
233 pub fn new(value: T) -> Self {
235 Self {
236 data: Arc::new(Mutex::new(SignalData {
237 value: value.clone(),
238 initial: value,
239 })),
240 }
241 }
242
243 pub fn get(&self) -> T {
245 self.data.lock().unwrap().value.clone()
246 }
247
248 pub fn set(&self, value: T) {
250 let mut data = self.data.lock().unwrap();
251 if data.value != value {
252 data.value = value;
253 }
254 }
255
256 pub fn state_hash(&self) -> u64 {
258 self.data.lock().unwrap().value.state_hash()
259 }
260
261 pub fn reset(&self) {
263 let mut data = self.data.lock().unwrap();
264 data.value = data.initial.clone();
265 }
266
267 pub fn to(&self, target: T, duration: Duration) -> SignalTween<T> {
269 SignalTween {
270 data: self.data.clone(),
271 start_value: None,
272 target: Target::Fixed(target),
273 target_value: None,
274 duration,
275 elapsed: Duration::ZERO,
276 easing: DEFAULT_EASING,
277 }
278 }
279
280 pub fn to_lazy<F>(&self, factory: F, duration: Duration) -> SignalTween<T>
284 where
285 F: Fn(&T) -> T + Send + Sync + 'static,
286 {
287 SignalTween {
288 data: self.data.clone(),
289 start_value: None,
290 target: Target::Lazy(Arc::new(factory)),
291 target_value: None,
292 duration,
293 elapsed: Duration::ZERO,
294 easing: DEFAULT_EASING,
295 }
296 }
297
298 pub fn follow(&self, path: &PathNode, duration: Duration) -> FollowPath<T>
300 where
301 T: FromVec2,
302 {
303 FollowPath {
304 data: self.data.clone(),
305 path_data: path.data.clone(),
306 duration,
307 elapsed: Duration::ZERO,
308 easing: DEFAULT_EASING,
309 }
310 }
311
312 pub fn bind<S: Tweenable + PartialEq, F>(
314 &self,
315 source: Signal<S>,
316 mapper: F,
317 ) -> crate::core::animation::binding::BindingNode<T, S>
318 where
319 F: Fn(S) -> T + Send + Sync + 'static,
320 {
321 crate::core::animation::binding::BindingNode::new(source, self.clone(), mapper)
322 }
323}
324
325pub struct SignalTween<T> {
327 data: Arc<Mutex<SignalData<T>>>,
328 start_value: Option<T>,
329 target: Target<T>,
330 target_value: Option<T>,
331 duration: Duration,
332 elapsed: Duration,
333 easing: fn(f32) -> f32,
334}
335
336impl<T: Tweenable> SignalTween<T> {
337 pub fn ease(mut self, easing: fn(f32) -> f32) -> Self {
339 self.easing = easing;
340 self
341 }
342}
343
344impl<T: Tweenable> Animation for SignalTween<T> {
345 fn update(&mut self, dt: Duration) -> (bool, Duration) {
346 if self.start_value.is_none() {
348 let current = self.data.lock().unwrap().value.clone();
349 self.start_value = Some(current.clone());
350
351 if self.target_value.is_none() {
353 match &self.target {
354 Target::Fixed(v) => self.target_value = Some(v.clone()),
355 Target::Lazy(f) => self.target_value = Some(f(¤t)),
356 }
357 }
358 }
359
360 let target = self.target_value.as_ref().unwrap();
361
362 if self.duration == Duration::ZERO {
363 let mut data = self.data.lock().unwrap();
364 data.value = target.clone();
365 return (true, dt);
366 }
367
368 self.elapsed += dt;
369 let finished = self.elapsed >= self.duration;
370 let leftover = if finished {
371 self.elapsed - self.duration
372 } else {
373 Duration::ZERO
374 };
375
376 let t_linear = (self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0);
377 let t_eased = (self.easing)(t_linear);
378
379 let start = self.start_value.as_ref().unwrap();
380 let mut data = self.data.lock().unwrap();
381 data.value = T::interpolate(start, target, t_eased);
382
383 (finished, leftover)
384 }
385
386 fn duration(&self) -> Duration {
387 self.duration
388 }
389
390 fn set_easing(&mut self, easing: fn(f32) -> f32) {
391 self.easing = easing;
392 }
393
394 fn reset(&mut self) {
395 self.start_value = None;
396 self.target_value = None;
397 self.elapsed = Duration::ZERO;
398
399 let mut data = self.data.lock().unwrap();
400 data.value = data.initial.clone();
401 }
402}
403
404pub struct FollowPath<T> {
406 data: Arc<Mutex<SignalData<T>>>,
407 path_data: Arc<PathData>,
408 duration: Duration,
409 elapsed: Duration,
410 easing: fn(f32) -> f32,
411}
412
413impl<T: Send + Sync + 'static> FollowPath<T> {
414 pub fn ease(mut self, easing: fn(f32) -> f32) -> Self {
416 self.easing = easing;
417 self
418 }
419}
420
421impl<T: Tweenable + FromVec2> Animation for FollowPath<T> {
422 fn update(&mut self, dt: Duration) -> (bool, Duration) {
423 if self.duration == Duration::ZERO {
424 let mut data = self.data.lock().unwrap();
425 data.value = T::from_vec2(self.path_data.sample(1.0));
426 return (true, dt);
427 }
428
429 self.elapsed += dt;
430 let finished = self.elapsed >= self.duration;
431 let leftover = if finished {
432 self.elapsed - self.duration
433 } else {
434 Duration::ZERO
435 };
436
437 let t_linear = (self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0);
438 let t_eased = (self.easing)(t_linear);
439
440 let mut data = self.data.lock().unwrap();
441 data.value = T::from_vec2(self.path_data.sample(t_eased));
442
443 (finished, leftover)
444 }
445
446 fn duration(&self) -> Duration {
447 self.duration
448 }
449
450 fn set_easing(&mut self, easing: fn(f32) -> f32) {
451 self.easing = easing;
452 }
453
454 fn reset(&mut self) {
455 self.elapsed = Duration::ZERO;
456 let mut data = self.data.lock().unwrap();
457 data.value = data.initial.clone();
458 }
459}
460
461impl<T: Tweenable> From<SignalTween<T>> for Box<dyn Animation> {
462 fn from(tween: SignalTween<T>) -> Self {
463 Box::new(tween)
464 }
465}
466
467impl<T: Tweenable + FromVec2> From<FollowPath<T>> for Box<dyn Animation> {
468 fn from(anim: FollowPath<T>) -> Self {
469 Box::new(anim)
470 }
471}