freya_hooks/use_animation/
hook.rs

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/// Controls the direction of the animation.
64#[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/// What to do once the animation finishes. By default it is [`Stop`](OnFinish::Stop)
80#[derive(PartialEq, Clone, Copy, Default)]
81pub enum OnFinish {
82    #[default]
83    Stop,
84    Reverse,
85    Restart,
86}
87
88/// What to do once the animation dependencies change. By default it is [`Reset`](OnDepsChange::Reset)
89#[derive(PartialEq, Clone, Copy, Default)]
90pub enum OnDepsChange {
91    #[default]
92    Reset,
93    Finish,
94    Rerun,
95}
96
97/// Animate your elements. Use [`use_animation`] to use this.
98#[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    /// Get the animated value.
123    pub fn get(&self) -> ReadOnlySignal<Animated> {
124        self.context.read().value.into()
125    }
126
127    /// Reset the animation to the default state.
128    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    /// Finish the animation with the final state.
146    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    /// Checks if there is any animation running.
161    pub fn is_running(&self) -> bool {
162        *self.is_running.read()
163    }
164
165    /// Checks if it has run yet, by subscribing.
166    pub fn has_run_yet(&self) -> bool {
167        *self.has_run_yet.read()
168    }
169
170    /// Checks if it has run yet, doesn't subscribe. Useful for when you just mounted your component.
171    pub fn peek_has_run_yet(&self) -> bool {
172        *self.has_run_yet.peek()
173    }
174
175    /// Runs the animation in reverse direction.
176    pub fn reverse(&self) {
177        self.run(AnimDirection::Reverse)
178    }
179
180    /// Runs the animation normally.
181    pub fn start(&self) {
182        self.run(AnimDirection::Forward)
183    }
184
185    /// Run the animation with a given [`AnimDirection`]
186    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        // Cancel previous animations
200        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            // Prepare the animations with the the proper direction
214            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                // Wait for the event loop to tick
223                ticker.tick().await;
224
225                // Its okay to stop this animation if the value has been dropped
226                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                // Advance the animations
237                value.write().advance(index, direction);
238
239                prev_frame = Instant::now();
240
241                if is_finished {
242                    if OnFinish::Reverse == on_finish {
243                        // Toggle direction
244                        direction.toggle();
245                    }
246                    match on_finish {
247                        OnFinish::Restart | OnFinish::Reverse => {
248                            index = 0;
249
250                            // Restart the animation
251                            value.write().prepare(direction);
252                        }
253                        OnFinish::Stop => {
254                            // Stop if all the animations are finished
255                            break;
256                        }
257                    }
258                }
259            }
260
261            is_running.set(false);
262            task.write().take();
263        });
264
265        // Cancel previous animations
266        task.write().replace(animation_task);
267    }
268}
269
270/// Animate your elements easily.
271///
272/// [`use_animation`] takes an callback to initialize the animated values and related configuration.
273///
274/// To animate a group of values at once you can just return a tuple of them.
275///
276/// Currently supports animating numeric values (e.g width, padding, rotation, offsets) using [crate::AnimNum] or colors using [crate::AnimColor].
277/// For each animated value you will need specify the duration, optionally an ease function or what type of easing you want.
278///
279/// For animations where you want to animate a value after one another you may use [crate::AnimSequential].
280///
281/// # Example
282///
283/// Here is an example that animates a value from `0.0` to `100.0` in `50` milliseconds.
284///
285/// ```rust, no_run
286/// # use freya::prelude::*;
287/// fn main() {
288///     launch(app);
289/// }
290///
291/// fn app() -> Element {
292///     let animation = use_animation(|conf| {
293///         conf.auto_start(true);
294///         AnimNum::new(0., 100.).time(50)
295///     });
296///
297///     let width = animation.get().read().read();
298///
299///     rsx!(rect {
300///         width: "{width}",
301///         height: "100%",
302///         background: "blue"
303///     })
304/// }
305/// ```
306///
307/// You are not limited to just one animation per call, you can have as many as you want.
308///
309/// ```rust,no_run
310/// # use freya::prelude::*;
311/// fn app() -> Element {
312///     let animation = use_animation(|conf| {
313///         conf.auto_start(true);
314///         (
315///             AnimNum::new(0., 100.).time(50),
316///             AnimColor::new("red", "blue").time(50),
317///         )
318///     });
319///
320///     let (width, color) = &*animation.get().read_unchecked();
321///
322///     rsx!(rect {
323///         width: "{width.read()}",
324///         height: "100%",
325///         background: "{color.read()}"
326///     })
327/// }
328/// ```
329///
330/// You can also tweak what to do once the animation has finished with [`AnimConfiguration::on_finish`].
331///
332/// ```rust,no_run
333/// # use freya::prelude::*;
334/// fn app() -> Element {
335///     let animation = use_animation(|conf| {
336///         conf.on_finish(OnFinish::Restart);
337///         (
338///             AnimNum::new(0., 100.).time(50),
339///             AnimColor::new("red", "blue").time(50),
340///         )
341///     });
342///
343///     let (width, color) = &*animation.get().read_unchecked();
344///
345///     rsx!(rect {
346///         width: "{width.read()}",
347///         height: "100%",
348///         background: "{color.read()}"
349///     })
350/// }
351/// ```
352pub 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);