1use dioxus_core::prelude::{
2 spawn,
3 use_hook,
4 Task,
5};
6use dioxus_hooks::{
7 use_memo,
8 use_reactive,
9 use_signal,
10 Dependency,
11};
12use dioxus_signals::{
13 Memo,
14 ReadOnlySignal,
15 Readable,
16 Signal,
17 Writable,
18};
19use tokio::time::Instant;
20
21use super::AnimatedValue;
22use crate::{
23 use_platform,
24 UsePlatform,
25};
26
27#[derive(Default, PartialEq, Clone)]
28pub struct AnimConfiguration {
29 on_finish: OnFinish,
30 auto_start: bool,
31 on_deps_change: OnDepsChange,
32}
33
34impl AnimConfiguration {
35 pub fn on_finish(&mut self, on_finish: OnFinish) -> &mut Self {
36 self.on_finish = on_finish;
37 self
38 }
39
40 pub fn auto_start(&mut self, auto_start: bool) -> &mut Self {
41 self.auto_start = auto_start;
42 self
43 }
44
45 pub fn on_deps_change(&mut self, on_deps_change: OnDepsChange) -> &mut Self {
46 self.on_deps_change = on_deps_change;
47 self
48 }
49}
50
51#[derive(Clone)]
52pub struct AnimationContext<Animated: AnimatedValue> {
53 value: Signal<Animated>,
54 conf: AnimConfiguration,
55}
56
57impl<Animated: AnimatedValue> PartialEq for AnimationContext<Animated> {
58 fn eq(&self, other: &Self) -> bool {
59 self.value.eq(&other.value) && self.conf.eq(&other.conf)
60 }
61}
62
63#[derive(Clone, Copy)]
65pub enum AnimDirection {
66 Forward,
67 Reverse,
68}
69
70impl AnimDirection {
71 pub fn toggle(&mut self) {
72 match self {
73 Self::Forward => *self = Self::Reverse,
74 Self::Reverse => *self = Self::Forward,
75 }
76 }
77}
78
79#[derive(PartialEq, Clone, Copy, Default)]
81pub enum OnFinish {
82 #[default]
83 Stop,
84 Reverse,
85 Restart,
86}
87
88#[derive(PartialEq, Clone, Copy, Default)]
90pub enum OnDepsChange {
91 #[default]
92 Reset,
93 Finish,
94 Rerun,
95}
96
97#[derive(Clone)]
99pub struct UseAnimation<Animated: AnimatedValue> {
100 pub(crate) context: Memo<AnimationContext<Animated>>,
101 pub(crate) platform: UsePlatform,
102 pub(crate) is_running: Signal<bool>,
103 pub(crate) has_run_yet: Signal<bool>,
104 pub(crate) task: Signal<Option<Task>>,
105 pub(crate) last_direction: Signal<AnimDirection>,
106}
107
108impl<T: AnimatedValue> PartialEq for UseAnimation<T> {
109 fn eq(&self, other: &Self) -> bool {
110 self.context.eq(&other.context)
111 && self.platform.eq(&other.platform)
112 && self.is_running.eq(&other.is_running)
113 && self.has_run_yet.eq(&other.has_run_yet)
114 && self.task.eq(&other.task)
115 && self.last_direction.eq(&other.last_direction)
116 }
117}
118
119impl<T: AnimatedValue> Copy for UseAnimation<T> {}
120
121impl<Animated: AnimatedValue> UseAnimation<Animated> {
122 pub fn get(&self) -> ReadOnlySignal<Animated> {
124 self.context.read().value.into()
125 }
126
127 pub fn reset(&self) {
129 let mut has_run_yet = self.has_run_yet;
130 let mut task = self.task;
131
132 has_run_yet.set(false);
133
134 if let Some(task) = task.write().take() {
135 task.cancel();
136 }
137
138 self.context
139 .peek()
140 .value
141 .write_unchecked()
142 .prepare(AnimDirection::Forward);
143 }
144
145 pub fn finish(&self) {
147 let mut task = self.task;
148
149 if let Some(task) = task.write().take() {
150 task.cancel();
151 }
152
153 self.context
154 .peek()
155 .value
156 .write_unchecked()
157 .finish(*self.last_direction.peek());
158 }
159
160 pub fn is_running(&self) -> bool {
162 *self.is_running.read()
163 }
164
165 pub fn has_run_yet(&self) -> bool {
167 *self.has_run_yet.read()
168 }
169
170 pub fn peek_has_run_yet(&self) -> bool {
172 *self.has_run_yet.peek()
173 }
174
175 pub fn reverse(&self) {
177 self.run(AnimDirection::Reverse)
178 }
179
180 pub fn start(&self) {
182 self.run(AnimDirection::Forward)
183 }
184
185 pub fn run(&self, mut direction: AnimDirection) {
187 let context = &self.context.peek();
188 let platform = self.platform;
189 let mut is_running = self.is_running;
190 let mut has_run_yet = self.has_run_yet;
191 let mut task = self.task;
192 let mut last_direction = self.last_direction;
193
194 let on_finish = context.conf.on_finish;
195 let mut value = context.value;
196
197 last_direction.set(direction);
198
199 if let Some(task) = task.write().take() {
201 task.cancel();
202 }
203
204 let peek_has_run_yet = self.peek_has_run_yet();
205 let mut ticker = platform.new_ticker();
206
207 let animation_task = spawn(async move {
208 platform.request_animation_frame();
209
210 let mut index = 0u128;
211 let mut prev_frame = Instant::now();
212
213 value.write().prepare(direction);
215
216 if !peek_has_run_yet {
217 *has_run_yet.write() = true;
218 }
219 is_running.set(true);
220
221 loop {
222 ticker.tick().await;
224
225 if value.try_peek().is_err() {
227 break;
228 }
229
230 platform.request_animation_frame();
231
232 index += prev_frame.elapsed().as_millis();
233
234 let is_finished = value.peek().is_finished(index, direction);
235
236 value.write().advance(index, direction);
238
239 prev_frame = Instant::now();
240
241 if is_finished {
242 if OnFinish::Reverse == on_finish {
243 direction.toggle();
245 }
246 match on_finish {
247 OnFinish::Restart | OnFinish::Reverse => {
248 index = 0;
249
250 value.write().prepare(direction);
252 }
253 OnFinish::Stop => {
254 break;
256 }
257 }
258 }
259 }
260
261 is_running.set(false);
262 task.write().take();
263 });
264
265 task.write().replace(animation_task);
267 }
268}
269
270pub fn use_animation<Animated: AnimatedValue>(
353 run: impl 'static + Fn(&mut AnimConfiguration) -> Animated,
354) -> UseAnimation<Animated> {
355 let platform = use_platform();
356 let is_running = use_signal(|| false);
357 let has_run_yet = use_signal(|| false);
358 let task = use_signal(|| None);
359 let last_direction = use_signal(|| AnimDirection::Reverse);
360 let mut prev_value = use_signal::<Option<Signal<Animated>>>(|| None);
361
362 let context = use_memo(move || {
363 if let Some(prev_value) = prev_value.take() {
364 prev_value.manually_drop();
365 }
366 let mut conf = AnimConfiguration::default();
367 let value = run(&mut conf);
368 let value = Signal::new(value);
369 prev_value.set(Some(value));
370 AnimationContext { value, conf }
371 });
372
373 let animation = UseAnimation {
374 context,
375 platform,
376 is_running,
377 has_run_yet,
378 task,
379 last_direction,
380 };
381
382 use_hook(move || {
383 if animation.context.read().conf.auto_start {
384 animation.run(AnimDirection::Forward);
385 }
386 });
387
388 use_memo(move || {
389 let context = context.read();
390 if *has_run_yet.peek() {
391 match context.conf.on_deps_change {
392 OnDepsChange::Finish => animation.finish(),
393 OnDepsChange::Rerun => {
394 let last_direction = *animation.last_direction.peek();
395 animation.run(last_direction);
396 }
397 _ => {}
398 }
399 }
400 });
401
402 animation
403}
404
405pub fn use_animation_with_dependencies<Animated: PartialEq + AnimatedValue, D: Dependency>(
406 deps: D,
407 run: impl 'static + Fn(&mut AnimConfiguration, D::Out) -> Animated,
408) -> UseAnimation<Animated>
409where
410 D::Out: 'static + Clone,
411{
412 let platform = use_platform();
413 let is_running = use_signal(|| false);
414 let has_run_yet = use_signal(|| false);
415 let task = use_signal(|| None);
416 let last_direction = use_signal(|| AnimDirection::Reverse);
417 let mut prev_value = use_signal::<Option<Signal<Animated>>>(|| None);
418
419 let context = use_memo(use_reactive(deps, move |deps| {
420 if let Some(prev_value) = prev_value.take() {
421 prev_value.manually_drop();
422 }
423 let mut conf = AnimConfiguration::default();
424 let value = run(&mut conf, deps);
425 let value = Signal::new(value);
426 prev_value.set(Some(value));
427 AnimationContext { value, conf }
428 }));
429
430 let animation = UseAnimation {
431 context,
432 platform,
433 is_running,
434 has_run_yet,
435 task,
436 last_direction,
437 };
438
439 use_memo(move || {
440 let context = context.read();
441 if *has_run_yet.peek() {
442 match context.conf.on_deps_change {
443 OnDepsChange::Finish => animation.finish(),
444 OnDepsChange::Rerun => {
445 animation.run(*animation.last_direction.peek());
446 }
447 _ => {}
448 }
449 }
450 });
451
452 use_hook(move || {
453 if animation.context.read().conf.auto_start {
454 animation.run(AnimDirection::Forward);
455 }
456 });
457
458 animation
459}
460
461macro_rules! impl_tuple_call {
462 ($(($($type:ident),*)),*) => {
463 $(
464 impl<$($type,)*> AnimatedValue for ($($type,)*)
465 where
466 $($type: AnimatedValue,)*
467 {
468 fn prepare(&mut self, direction: AnimDirection) {
469 #[allow(non_snake_case)]
470 let ($($type,)*) = self;
471 $(
472 $type.prepare(direction);
473 )*
474 }
475
476 fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
477 #[allow(non_snake_case)]
478 let ($($type,)*) = self;
479 $(
480 if !$type.is_finished(index, direction) {
481 return false;
482 }
483 )*
484 true
485 }
486
487 fn advance(&mut self, index: u128, direction: AnimDirection) {
488 #[allow(non_snake_case)]
489 let ($($type,)*) = self;
490 $(
491 $type.advance(index, direction);
492 )*
493 }
494
495 fn finish(&mut self, direction: AnimDirection) {
496 #[allow(non_snake_case)]
497 let ($($type,)*) = self;
498 $(
499 $type.finish(direction);
500 )*
501 }
502 }
503 )*
504 };
505}
506
507impl_tuple_call!(
508 (T1),
509 (T1, T2),
510 (T1, T2, T3),
511 (T1, T2, T3, T4),
512 (T1, T2, T3, T4, T5),
513 (T1, T2, T3, T4, T5, T6),
514 (T1, T2, T3, T4, T5, T6, T7),
515 (T1, T2, T3, T4, T5, T6, T7, T8),
516 (T1, T2, T3, T4, T5, T6, T7, T8, T9),
517 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
518 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
519 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12),
520 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13),
521 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14),
522 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15),
523 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16),
524 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17),
525 (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)
526);