1use animato_core::{Animatable, Update};
4use animato_spring::{Decompose, SpringConfig, SpringN};
5use animato_timeline::{Timeline, TimelineState};
6use animato_tween::{KeyframeTrack, Tween, TweenBuilder};
7use dioxus::prelude::{Signal, use_signal};
8use std::fmt;
9use std::sync::{Arc, Mutex};
10
11#[derive(Clone)]
13pub struct TweenHandle<T: Animatable + Send + Sync + 'static> {
14 tween: Arc<Mutex<Tween<T>>>,
15 value: Signal<T>,
16 progress: Signal<f32>,
17 complete: Signal<bool>,
18}
19
20impl<T: Animatable + Send + Sync + 'static> fmt::Debug for TweenHandle<T> {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.debug_struct("TweenHandle").finish_non_exhaustive()
23 }
24}
25
26impl<T: Animatable + Send + Sync + 'static> TweenHandle<T> {
27 pub fn play(&self) {
29 crate::with_lock(&self.tween, |tween| {
30 if tween.is_complete() {
31 tween.reset();
32 }
33 tween.resume();
34 self.sync(tween);
35 });
36 }
37
38 pub fn pause(&self) {
40 crate::with_lock(&self.tween, Tween::pause);
41 }
42
43 pub fn resume(&self) {
45 crate::with_lock(&self.tween, Tween::resume);
46 }
47
48 pub fn reset(&self) {
50 crate::with_lock(&self.tween, |tween| {
51 tween.reset();
52 self.sync(tween);
53 });
54 }
55
56 pub fn reverse(&self) {
58 crate::with_lock(&self.tween, |tween| {
59 tween.reverse();
60 self.sync(tween);
61 });
62 }
63
64 pub fn seek(&self, progress: f32) {
66 crate::with_lock(&self.tween, |tween| {
67 tween.seek(progress);
68 self.sync(tween);
69 });
70 }
71
72 pub fn set_time_scale(&self, scale: f32) {
74 crate::with_lock(&self.tween, |tween| {
75 tween.time_scale = crate::finite_or(scale, 1.0).max(0.0);
76 });
77 }
78
79 pub fn value(&self) -> Signal<T> {
81 self.value
82 }
83
84 pub fn is_complete(&self) -> Signal<bool> {
86 self.complete
87 }
88
89 pub fn progress(&self) -> Signal<f32> {
91 self.progress
92 }
93
94 pub fn tick(&self, dt: f32) -> bool {
96 crate::with_lock(&self.tween, |tween| {
97 let running = tween.update(dt.max(0.0));
98 self.sync(tween);
99 running
100 })
101 }
102
103 fn sync(&self, tween: &Tween<T>) {
104 crate::set_signal(self.value, tween.value());
105 crate::set_signal(self.progress, tween.progress());
106 crate::set_signal(self.complete, tween.is_complete());
107 }
108}
109
110pub fn use_tween<T>(
112 from: T,
113 to: T,
114 config: impl FnOnce(TweenBuilder<T>) -> TweenBuilder<T>,
115) -> (Signal<T>, TweenHandle<T>)
116where
117 T: Animatable + Send + Sync + 'static,
118{
119 let tween = config(Tween::new(from, to)).build();
120 let value = use_signal({
121 let initial = tween.value();
122 move || initial
123 });
124 let progress = use_signal({
125 let initial = tween.progress();
126 move || initial
127 });
128 let complete = use_signal({
129 let initial = tween.is_complete();
130 move || initial
131 });
132
133 let handle = TweenHandle {
134 tween: Arc::new(Mutex::new(tween)),
135 value,
136 progress,
137 complete,
138 };
139
140 let loop_handle = handle.clone();
141 crate::spawn_animation_loop(move |dt| {
142 loop_handle.tick(dt);
143 true
144 });
145
146 (value, handle)
147}
148
149#[derive(Clone)]
151pub struct SpringHandle<T: Decompose + Send + Sync + Clone + 'static> {
152 spring: Arc<Mutex<SpringN<T>>>,
153 value: Signal<T>,
154 settled: Signal<bool>,
155}
156
157impl<T: Decompose + Send + Sync + Clone + 'static> fmt::Debug for SpringHandle<T> {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.debug_struct("SpringHandle").finish_non_exhaustive()
160 }
161}
162
163impl<T: Decompose + Send + Sync + Clone + 'static> SpringHandle<T> {
164 pub fn set_target(&self, target: T) {
166 crate::with_lock(&self.spring, |spring| {
167 spring.set_target(target);
168 crate::set_signal(self.settled, spring.is_settled());
169 });
170 }
171
172 pub fn snap_to(&self, value: T) {
174 crate::with_lock(&self.spring, |spring| {
175 spring.snap_to(value);
176 self.sync(spring);
177 });
178 }
179
180 pub fn value(&self) -> Signal<T> {
182 self.value
183 }
184
185 pub fn is_settled(&self) -> Signal<bool> {
187 self.settled
188 }
189
190 pub fn tick(&self, dt: f32) -> bool {
192 crate::with_lock(&self.spring, |spring| {
193 let running = spring.update(dt.max(0.0));
194 self.sync(spring);
195 running
196 })
197 }
198
199 fn sync(&self, spring: &SpringN<T>) {
200 crate::set_signal(self.value, spring.position());
201 crate::set_signal(self.settled, spring.is_settled());
202 }
203}
204
205pub fn use_spring<T>(initial: T, config: SpringConfig) -> (Signal<T>, SpringHandle<T>)
207where
208 T: Decompose + Send + Sync + Clone + 'static,
209{
210 let spring = SpringN::new(config, initial.clone());
211 let value = use_signal(move || initial);
212 let settled = use_signal(|| true);
213 let handle = SpringHandle {
214 spring: Arc::new(Mutex::new(spring)),
215 value,
216 settled,
217 };
218
219 let loop_handle = handle.clone();
220 crate::spawn_animation_loop(move |dt| {
221 loop_handle.tick(dt);
222 true
223 });
224
225 (value, handle)
226}
227
228#[derive(Clone)]
230pub struct TimelineHandle {
231 timeline: Arc<Mutex<Timeline>>,
232 progress: Signal<f32>,
233 complete: Signal<bool>,
234 state: Signal<TimelineState>,
235}
236
237impl fmt::Debug for TimelineHandle {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 f.debug_struct("TimelineHandle").finish_non_exhaustive()
240 }
241}
242
243impl TimelineHandle {
244 pub fn play(&self) {
246 crate::with_lock(&self.timeline, |timeline| {
247 timeline.play();
248 self.sync(timeline);
249 });
250 }
251
252 pub fn pause(&self) {
254 crate::with_lock(&self.timeline, |timeline| {
255 timeline.pause();
256 self.sync(timeline);
257 });
258 }
259
260 pub fn resume(&self) {
262 crate::with_lock(&self.timeline, |timeline| {
263 timeline.resume();
264 self.sync(timeline);
265 });
266 }
267
268 pub fn reset(&self) {
270 crate::with_lock(&self.timeline, |timeline| {
271 timeline.reset();
272 self.sync(timeline);
273 });
274 }
275
276 pub fn seek(&self, progress: f32) {
278 crate::with_lock(&self.timeline, |timeline| {
279 timeline.seek(progress);
280 self.sync(timeline);
281 });
282 }
283
284 pub fn set_time_scale(&self, scale: f32) {
286 crate::with_lock(&self.timeline, |timeline| {
287 timeline.set_time_scale(scale);
288 self.sync(timeline);
289 });
290 }
291
292 pub fn progress(&self) -> Signal<f32> {
294 self.progress
295 }
296
297 pub fn is_complete(&self) -> Signal<bool> {
299 self.complete
300 }
301
302 pub fn state(&self) -> Signal<TimelineState> {
304 self.state
305 }
306
307 pub fn tick(&self, dt: f32) -> bool {
309 crate::with_lock(&self.timeline, |timeline| {
310 let running = timeline.update(dt.max(0.0));
311 self.sync(timeline);
312 running
313 })
314 }
315
316 fn sync(&self, timeline: &Timeline) {
317 crate::set_signal(self.progress, timeline.progress());
318 crate::set_signal(self.complete, timeline.is_complete());
319 crate::set_signal(self.state, timeline.state());
320 }
321}
322
323pub fn use_timeline(builder: impl FnOnce(Timeline) -> Timeline) -> TimelineHandle {
325 let mut timeline = builder(Timeline::new());
326 timeline.play();
327
328 let progress = use_signal({
329 let initial = timeline.progress();
330 move || initial
331 });
332 let complete = use_signal({
333 let initial = timeline.is_complete();
334 move || initial
335 });
336 let state = use_signal({
337 let initial = timeline.state();
338 move || initial
339 });
340 let handle = TimelineHandle {
341 timeline: Arc::new(Mutex::new(timeline)),
342 progress,
343 complete,
344 state,
345 };
346
347 let loop_handle = handle.clone();
348 crate::spawn_animation_loop(move |dt| {
349 loop_handle.tick(dt);
350 true
351 });
352
353 handle
354}
355
356#[derive(Clone)]
358pub struct KeyframeHandle<T: Animatable + Send + Sync + 'static> {
359 track: Arc<Mutex<KeyframeTrack<T>>>,
360 value: Signal<T>,
361 progress: Signal<f32>,
362 complete: Signal<bool>,
363 paused: Arc<Mutex<bool>>,
364 time_scale: Arc<Mutex<f32>>,
365}
366
367impl<T: Animatable + Send + Sync + 'static> fmt::Debug for KeyframeHandle<T> {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 f.debug_struct("KeyframeHandle").finish_non_exhaustive()
370 }
371}
372
373impl<T: Animatable + Send + Sync + 'static> KeyframeHandle<T> {
374 pub fn play(&self) {
376 self.resume();
377 }
378
379 pub fn pause(&self) {
381 crate::with_lock(&self.paused, |paused| *paused = true);
382 }
383
384 pub fn resume(&self) {
386 crate::with_lock(&self.paused, |paused| *paused = false);
387 }
388
389 pub fn reset(&self) {
391 crate::with_lock(&self.track, |track| {
392 track.reset();
393 self.sync(track);
394 });
395 }
396
397 pub fn set_time_scale(&self, scale: f32) {
399 crate::with_lock(&self.time_scale, |time_scale| {
400 *time_scale = crate::finite_or(scale, 1.0).max(0.0);
401 });
402 }
403
404 pub fn value(&self) -> Signal<T> {
406 self.value
407 }
408
409 pub fn progress(&self) -> Signal<f32> {
411 self.progress
412 }
413
414 pub fn is_complete(&self) -> Signal<bool> {
416 self.complete
417 }
418
419 pub fn tick(&self, dt: f32) -> bool {
421 let paused = crate::with_lock(&self.paused, |paused| *paused);
422 if paused {
423 return true;
424 }
425
426 let time_scale = crate::with_lock(&self.time_scale, |scale| *scale);
427 crate::with_lock(&self.track, |track| {
428 let running = track.update(dt.max(0.0) * time_scale);
429 self.sync(track);
430 running
431 })
432 }
433
434 fn sync(&self, track: &KeyframeTrack<T>) {
435 if let Some(value) = track.value() {
436 crate::set_signal(self.value, value);
437 }
438 crate::set_signal(self.progress, track.progress());
439 crate::set_signal(self.complete, track.is_complete());
440 }
441}
442
443pub fn use_keyframes<T>(
448 builder: impl FnOnce(KeyframeTrack<T>) -> KeyframeTrack<T>,
449) -> (Signal<T>, KeyframeHandle<T>)
450where
451 T: Animatable + Send + Sync + 'static,
452{
453 let track = builder(KeyframeTrack::new());
454 let initial = track
455 .value()
456 .expect("use_keyframes requires at least one keyframe");
457 let progress_value = track.progress();
458 let complete_value = track.is_complete();
459
460 let value = use_signal(move || initial);
461 let progress = use_signal(move || progress_value);
462 let complete = use_signal(move || complete_value);
463 let handle = KeyframeHandle {
464 track: Arc::new(Mutex::new(track)),
465 value,
466 progress,
467 complete,
468 paused: Arc::new(Mutex::new(false)),
469 time_scale: Arc::new(Mutex::new(1.0)),
470 };
471
472 let loop_handle = handle.clone();
473 crate::spawn_animation_loop(move |dt| {
474 loop_handle.tick(dt);
475 true
476 });
477
478 (value, handle)
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use animato_core::Easing;
485 use animato_timeline::{At, TimelineState};
486 use approx::assert_relative_eq;
487 use dioxus::prelude::*;
488 use std::cell::RefCell;
489
490 thread_local! {
491 static TWEEN_CAPTURE: RefCell<Option<(Signal<f32>, TweenHandle<f32>)>> = const { RefCell::new(None) };
492 static SPRING_CAPTURE: RefCell<Option<(Signal<f32>, SpringHandle<f32>)>> = const { RefCell::new(None) };
493 static TIMELINE_CAPTURE: RefCell<Option<TimelineHandle>> = const { RefCell::new(None) };
494 static KEYFRAME_CAPTURE: RefCell<Option<(Signal<f32>, KeyframeHandle<f32>)>> = const { RefCell::new(None) };
495 }
496
497 #[allow(non_snake_case)]
498 fn TweenHookApp() -> Element {
499 let pair = use_tween(0.0_f32, 10.0, |builder| {
500 builder.duration(1.0).easing(Easing::Linear)
501 });
502 TWEEN_CAPTURE.with(|slot| *slot.borrow_mut() = Some(pair));
503
504 rsx! { div {} }
505 }
506
507 #[allow(non_snake_case)]
508 fn SpringHookApp() -> Element {
509 let pair = use_spring(0.0_f32, SpringConfig::snappy());
510 SPRING_CAPTURE.with(|slot| *slot.borrow_mut() = Some(pair));
511
512 rsx! { div {} }
513 }
514
515 #[allow(non_snake_case)]
516 fn TimelineHookApp() -> Element {
517 let handle = use_timeline(|timeline| {
518 timeline.add(
519 "fade",
520 Tween::new(0.0_f32, 1.0)
521 .duration(1.0)
522 .easing(Easing::Linear)
523 .build(),
524 At::Start,
525 )
526 });
527 TIMELINE_CAPTURE.with(|slot| *slot.borrow_mut() = Some(handle));
528
529 rsx! { div {} }
530 }
531
532 #[allow(non_snake_case)]
533 fn KeyframeHookApp() -> Element {
534 let pair = use_keyframes(|track| track.push(0.0, 0.0_f32).push(1.0, 10.0));
535 KEYFRAME_CAPTURE.with(|slot| *slot.borrow_mut() = Some(pair));
536
537 rsx! { div {} }
538 }
539
540 fn mount_tween() -> (VirtualDom, Signal<f32>, TweenHandle<f32>) {
541 TWEEN_CAPTURE.with(|slot| *slot.borrow_mut() = None);
542 let mut dom = VirtualDom::new(TweenHookApp);
543 dom.rebuild_in_place();
544 let (value, handle) = TWEEN_CAPTURE.with(|slot| {
545 slot.borrow()
546 .as_ref()
547 .cloned()
548 .expect("tween hook captured")
549 });
550 (dom, value, handle)
551 }
552
553 fn mount_spring() -> (VirtualDom, Signal<f32>, SpringHandle<f32>) {
554 SPRING_CAPTURE.with(|slot| *slot.borrow_mut() = None);
555 let mut dom = VirtualDom::new(SpringHookApp);
556 dom.rebuild_in_place();
557 let (value, handle) = SPRING_CAPTURE.with(|slot| {
558 slot.borrow()
559 .as_ref()
560 .cloned()
561 .expect("spring hook captured")
562 });
563 (dom, value, handle)
564 }
565
566 fn mount_timeline() -> (VirtualDom, TimelineHandle) {
567 TIMELINE_CAPTURE.with(|slot| *slot.borrow_mut() = None);
568 let mut dom = VirtualDom::new(TimelineHookApp);
569 dom.rebuild_in_place();
570 let handle = TIMELINE_CAPTURE.with(|slot| {
571 slot.borrow()
572 .as_ref()
573 .cloned()
574 .expect("timeline hook captured")
575 });
576 (dom, handle)
577 }
578
579 fn mount_keyframes() -> (VirtualDom, Signal<f32>, KeyframeHandle<f32>) {
580 KEYFRAME_CAPTURE.with(|slot| *slot.borrow_mut() = None);
581 let mut dom = VirtualDom::new(KeyframeHookApp);
582 dom.rebuild_in_place();
583 let (value, handle) = KEYFRAME_CAPTURE.with(|slot| {
584 slot.borrow()
585 .as_ref()
586 .cloned()
587 .expect("keyframe hook captured")
588 });
589 (dom, value, handle)
590 }
591
592 #[test]
593 fn tween_hook_handle_controls_signal_state() {
594 let (_dom, value, handle) = mount_tween();
595
596 assert_relative_eq!(crate::read_signal(value), 0.0);
597 assert_relative_eq!(crate::read_signal(handle.value()), 0.0);
598 assert_relative_eq!(crate::read_signal(handle.progress()), 0.0);
599 assert!(!crate::read_signal(handle.is_complete()));
600
601 assert!(handle.tick(0.25));
602 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
603 assert_relative_eq!(crate::read_signal(handle.progress()), 0.25, epsilon = 0.001);
604
605 handle.pause();
606 assert!(handle.tick(0.25));
607 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
608
609 handle.resume();
610 handle.set_time_scale(f32::NAN);
611 assert!(handle.tick(0.25));
612 assert_relative_eq!(crate::read_signal(value), 5.0, epsilon = 0.001);
613
614 handle.seek(1.0);
615 assert_relative_eq!(crate::read_signal(value), 10.0, epsilon = 0.001);
616 assert!(!handle.tick(0.0));
617 assert!(crate::read_signal(handle.is_complete()));
618 handle.play();
619 assert!(!crate::read_signal(handle.is_complete()));
620
621 handle.seek(0.25);
622 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
623 handle.reverse();
624 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
625 handle.reset();
626 assert_relative_eq!(crate::read_signal(value), 10.0, epsilon = 0.001);
627 assert_relative_eq!(crate::read_signal(handle.progress()), 0.0, epsilon = 0.001);
628 }
629
630 #[test]
631 fn spring_hook_handle_tracks_target_and_snap() {
632 let (_dom, value, handle) = mount_spring();
633
634 assert_relative_eq!(crate::read_signal(value), 0.0);
635 assert!(crate::read_signal(handle.is_settled()));
636
637 handle.set_target(1.0);
638 assert!(!crate::read_signal(handle.is_settled()));
639 assert!(handle.tick(1.0 / 60.0));
640 assert!(crate::read_signal(handle.value()) > 0.0);
641
642 handle.snap_to(2.0);
643 assert_relative_eq!(crate::read_signal(value), 2.0, epsilon = 0.001);
644 assert!(crate::read_signal(handle.is_settled()));
645 assert!(!handle.tick(0.0));
646 }
647
648 #[test]
649 fn timeline_hook_handle_controls_clock_state() {
650 let (_dom, handle) = mount_timeline();
651
652 assert_eq!(crate::read_signal(handle.state()), TimelineState::Playing);
653 assert_relative_eq!(crate::read_signal(handle.progress()), 0.0);
654 assert!(!crate::read_signal(handle.is_complete()));
655
656 assert!(handle.tick(0.25));
657 assert_relative_eq!(crate::read_signal(handle.progress()), 0.25, epsilon = 0.001);
658
659 handle.pause();
660 assert_eq!(crate::read_signal(handle.state()), TimelineState::Paused);
661 assert!(handle.tick(0.5));
662 assert_relative_eq!(crate::read_signal(handle.progress()), 0.25, epsilon = 0.001);
663
664 handle.resume();
665 handle.set_time_scale(2.0);
666 assert!(handle.tick(0.25));
667 assert_relative_eq!(crate::read_signal(handle.progress()), 0.75, epsilon = 0.001);
668
669 handle.seek(1.0);
670 assert!(crate::read_signal(handle.is_complete()));
671 assert_eq!(crate::read_signal(handle.state()), TimelineState::Completed);
672
673 handle.reset();
674 assert_relative_eq!(crate::read_signal(handle.progress()), 0.0, epsilon = 0.001);
675 assert_eq!(crate::read_signal(handle.state()), TimelineState::Idle);
676
677 handle.play();
678 assert_eq!(crate::read_signal(handle.state()), TimelineState::Playing);
679 }
680
681 #[test]
682 fn keyframe_hook_handle_controls_track_state() {
683 let (_dom, value, handle) = mount_keyframes();
684
685 assert_relative_eq!(crate::read_signal(value), 0.0);
686 assert_relative_eq!(crate::read_signal(handle.progress()), 0.0);
687 assert!(!crate::read_signal(handle.is_complete()));
688
689 assert!(handle.tick(0.25));
690 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
691
692 handle.pause();
693 assert!(handle.tick(0.25));
694 assert_relative_eq!(crate::read_signal(value), 2.5, epsilon = 0.001);
695
696 handle.play();
697 handle.set_time_scale(f32::INFINITY);
698 assert!(handle.tick(0.25));
699 assert_relative_eq!(crate::read_signal(value), 5.0, epsilon = 0.001);
700
701 handle.set_time_scale(2.0);
702 assert!(!handle.tick(0.25));
703 assert_relative_eq!(crate::read_signal(value), 10.0, epsilon = 0.001);
704 assert!(crate::read_signal(handle.is_complete()));
705
706 handle.reset();
707 assert_relative_eq!(crate::read_signal(value), 0.0, epsilon = 0.001);
708 assert!(!crate::read_signal(handle.is_complete()));
709 }
710}