freya_animation/
hook.rs

1use std::{
2    ops::Deref,
3    time::{
4        Duration,
5        Instant,
6    },
7};
8
9use async_io::Timer;
10use freya_core::prelude::*;
11
12#[derive(Default, PartialEq, Clone, Debug)]
13pub struct AnimConfiguration {
14    on_finish: OnFinish,
15    on_creation: OnCreation,
16    on_change: OnChange,
17}
18
19impl AnimConfiguration {
20    pub fn on_finish(&mut self, on_finish: OnFinish) -> &mut Self {
21        self.on_finish = on_finish;
22        self
23    }
24
25    pub fn on_creation(&mut self, on_creation: OnCreation) -> &mut Self {
26        self.on_creation = on_creation;
27        self
28    }
29
30    pub fn on_change(&mut self, on_change: OnChange) -> &mut Self {
31        self.on_change = on_change;
32        self
33    }
34}
35
36/// Controls the direction of the animation.
37#[derive(Clone, Copy, PartialEq)]
38pub enum AnimDirection {
39    Forward,
40    Reverse,
41}
42
43impl AnimDirection {
44    pub fn toggle(&mut self) {
45        match self {
46            Self::Forward => *self = Self::Reverse,
47            Self::Reverse => *self = Self::Forward,
48        }
49    }
50}
51
52/// What to do once the animation finishes.
53///
54/// By default it is [OnFinish::Nothing].
55#[derive(PartialEq, Clone, Copy, Default, Debug)]
56pub enum OnFinish {
57    /// Does nothing at all.
58    #[default]
59    Nothing,
60    /// Runs the animation in reverse direction.
61    Reverse {
62        /// Delay before reversing.
63        delay: Duration,
64    },
65    /// Runs the animation in the same direction again.
66    Restart {
67        /// Delay before restarting.
68        delay: Duration,
69    },
70}
71
72impl OnFinish {
73    /// Creates a new [OnFinish::Nothing] variant.
74    pub fn nothing() -> Self {
75        Self::Nothing
76    }
77
78    /// Creates a new [OnFinish::Reverse] variant with no delay.
79    pub fn reverse() -> Self {
80        Self::Reverse {
81            delay: Duration::ZERO,
82        }
83    }
84
85    /// Creates a new [OnFinish::Reverse] variant with a delay.
86    pub fn reverse_with_delay(delay: Duration) -> Self {
87        Self::Reverse { delay }
88    }
89
90    /// Creates a new [OnFinish::Restart] variant with no delay.
91    pub fn restart() -> Self {
92        Self::Restart {
93            delay: Duration::ZERO,
94        }
95    }
96
97    /// Creates a new [OnFinish::Restart] variant with a delay.
98    pub fn restart_with_delay(delay: Duration) -> Self {
99        Self::Restart { delay }
100    }
101}
102
103/// What to do once the animation gets created.
104///
105/// By default it is [OnCreation::Nothing]
106#[derive(PartialEq, Clone, Copy, Default, Debug)]
107pub enum OnCreation {
108    /// Does nothing at all.
109    #[default]
110    Nothing,
111    /// Runs the animation.
112    Run,
113    /// Set the values to the end of the animation. As if it had actually run.
114    Finish,
115}
116
117/// What to do once the animation dependencies change.
118///
119/// Defaults to [OnChange::Reset].
120#[derive(PartialEq, Clone, Copy, Default, Debug)]
121pub enum OnChange {
122    /// Reset to the initial state.
123    #[default]
124    Reset,
125    /// Set the values to the end of the animation.
126    Finish,
127    /// Reruns the animation.
128    Rerun,
129    /// Does nothing at all.
130    Nothing,
131}
132
133pub trait ReadAnimatedValue: Clone + 'static {
134    type Output;
135    fn value(&self) -> Self::Output;
136}
137
138pub trait AnimatedValue: Clone + Default + 'static {
139    fn prepare(&mut self, direction: AnimDirection);
140
141    fn is_finished(&self, index: u128, direction: AnimDirection) -> bool;
142
143    fn advance(&mut self, index: u128, direction: AnimDirection);
144
145    fn finish(&mut self, direction: AnimDirection);
146
147    fn into_reversed(self) -> Self;
148}
149
150#[derive(Default, Clone, Copy, PartialEq, Eq)]
151pub enum Ease {
152    In,
153    #[default]
154    Out,
155    InOut,
156}
157
158/// Animate your elements. Use [`use_animation`] to use this.
159#[derive(Clone, PartialEq)]
160pub struct UseAnimation<Animated: AnimatedValue> {
161    pub(crate) animated_value: State<Animated>,
162    pub(crate) config: State<AnimConfiguration>,
163    pub(crate) is_running: State<bool>,
164    pub(crate) has_run_yet: State<bool>,
165    pub(crate) task: State<Option<TaskHandle>>,
166    pub(crate) last_direction: State<AnimDirection>,
167}
168impl<T: AnimatedValue> Copy for UseAnimation<T> {}
169
170impl<Animated: AnimatedValue> Deref for UseAnimation<Animated> {
171    type Target = State<Animated>;
172    fn deref(&self) -> &Self::Target {
173        &self.animated_value
174    }
175}
176
177impl<Animated: AnimatedValue> UseAnimation<Animated> {
178    /// Get the animated value.
179    pub fn get(&self) -> ReadRef<'static, Animated> {
180        self.animated_value.read()
181    }
182
183    /// Get the last configured direction used to animation.
184    pub fn direction(&self) -> ReadRef<'static, AnimDirection> {
185        self.last_direction.read()
186    }
187
188    /// Runs the animation normally.
189    pub fn start(&mut self) {
190        self.run(AnimDirection::Forward)
191    }
192
193    /// Runs the animation in reverse direction.
194    pub fn reverse(&mut self) {
195        self.run(AnimDirection::Reverse)
196    }
197
198    /// Reset the animation with the initial state.
199    pub fn reset(&mut self) {
200        if let Some(task) = self.task.write().take() {
201            task.cancel();
202        }
203
204        self.animated_value
205            .write()
206            .prepare(*self.last_direction.peek());
207
208        *self.has_run_yet.write() = true;
209    }
210
211    /// Finish the animation with the final state.
212    pub fn finish(&mut self) {
213        if let Some(task) = self.task.write().take() {
214            task.cancel();
215        }
216
217        self.animated_value
218            .write()
219            .finish(*self.last_direction.peek());
220
221        *self.has_run_yet.write() = true;
222    }
223
224    pub fn is_running(&self) -> State<bool> {
225        self.is_running
226    }
227
228    pub fn has_run_yet(&self) -> State<bool> {
229        self.has_run_yet
230    }
231
232    /// Run the animation with a given [`AnimDirection`]
233    pub fn run(&self, mut direction: AnimDirection) {
234        let mut is_running = self.is_running;
235        let mut has_run_yet = self.has_run_yet;
236        let mut task = self.task;
237        let mut last_direction = self.last_direction;
238
239        let on_finish = self.config.peek().on_finish;
240        let mut animated_value = self.animated_value;
241
242        last_direction.set(direction);
243
244        // Cancel previous animations
245        if let Some(task) = task.write().take() {
246            task.cancel();
247        }
248
249        let peek_has_run_yet = *self.has_run_yet.peek();
250
251        let mut ticker = RenderingTicker::get();
252        let platform = Platform::get();
253        let animation_clock = AnimationClock::get();
254
255        // Prepare the animations with the the proper direction
256        animated_value.write().prepare(direction);
257
258        let animation_task = spawn(async move {
259            platform.send(UserEvent::RequestRedraw);
260
261            let mut index = 0u128;
262            let mut prev_frame = Instant::now();
263
264            if !peek_has_run_yet {
265                *has_run_yet.write() = true;
266            }
267            is_running.set(true);
268
269            loop {
270                // Wait for the event loop to tick
271                ticker.tick().await;
272
273                // Request another redraw to move the animation forward
274                platform.send(UserEvent::RequestRedraw);
275
276                let elapsed = animation_clock.correct_elapsed_duration(prev_frame.elapsed());
277
278                index += elapsed.as_millis();
279
280                let is_finished = {
281                    let mut animated_value = animated_value.write();
282                    let is_finished = animated_value.is_finished(index, direction);
283                    // Advance the animations
284                    animated_value.advance(index, direction);
285
286                    is_finished
287                };
288
289                if is_finished {
290                    let delay = match on_finish {
291                        OnFinish::Reverse { delay } => {
292                            // Toggle direction
293                            direction.toggle();
294                            delay
295                        }
296                        OnFinish::Restart { delay } => delay,
297                        OnFinish::Nothing => {
298                            // Stop if all the animations are finished
299                            break;
300                        }
301                    };
302
303                    if !delay.is_zero() {
304                        Timer::after(delay).await;
305                    }
306
307                    index = 0;
308
309                    // Restart/reverse the animation
310                    animated_value.write().prepare(direction);
311                }
312
313                prev_frame = Instant::now();
314            }
315
316            is_running.set(false);
317            task.write().take();
318        });
319
320        // Cancel previous animations
321        task.write().replace(animation_task);
322    }
323}
324/// Animate your UI easily.
325///
326/// [`use_animation`] takes a callback to initialize the animated values and related configuration.
327///
328/// To animate a group of values at once you can just return a tuple of them.
329///
330/// Currently supports animating:
331/// - Numeric values (e.g width): [AnimNum](crate::prelude::AnimNum)
332/// - Colors using [AnimColor](crate::prelude::AnimColor)
333/// - Sequential animations: [AnimSequential](crate::prelude::AnimSequential)
334/// - Anything as long as you implement the [AnimatedValue] trait.
335///
336/// For each animated value you will need to specify the duration, optionally an ease function or what type of easing you want.
337///
338/// # Example
339///
340/// Here is an example that animates a value from `0.0` to `100.0` in `50` milliseconds.
341///
342/// ```rust, no_run
343/// # use freya::prelude::*;
344/// # use freya::animation::*;
345/// fn app() -> impl IntoElement {
346///     let animation = use_animation(|conf| {
347///         conf.on_creation(OnCreation::Run);
348///         AnimNum::new(0., 100.).time(50)
349///     });
350///
351///     let width = animation.get().value();
352///
353///     rect()
354///         .width(Size::px(width))
355///         .height(Size::fill())
356///         .background(Color::BLUE)
357/// }
358/// ```
359///
360/// You are not limited to just one animation per call, you can have as many as you want.
361///
362/// ```rust, no_run
363/// # use freya::prelude::*;
364/// # use freya::animation::*;
365/// fn app() -> impl IntoElement {
366///     let animation = use_animation(|conf| {
367///         conf.on_creation(OnCreation::Run);
368///         (
369///             AnimNum::new(0., 100.).time(50),
370///             AnimColor::new(Color::RED, Color::BLUE).time(50),
371///         )
372///     });
373///
374///     let (width, color) = animation.get().value();
375///
376///     rect()
377///         .width(Size::px(width))
378///         .height(Size::fill())
379///         .background(color)
380/// }
381/// ```
382///
383/// You can also tweak what to do once the animation has finished with [`AnimConfiguration::on_finish`].
384///
385/// ```rust, no_run
386/// # use freya::prelude::*;
387/// # use freya::animation::*;
388/// fn app() -> impl IntoElement {
389///     let animation = use_animation(|conf| {
390///         conf.on_finish(OnFinish::restart());
391///         // ...
392///         # AnimNum::new(0., 1.)
393///     });
394///
395///     // ...
396///     # rect()
397/// }
398/// ```
399///
400/// You can subscribe your animation to reactive [state](freya_core::prelude::use_state) values, these are considered dependencies.
401///
402/// ```rust, no_run
403/// # use freya::prelude::*;
404/// # use freya::animation::*;
405/// fn app() -> impl IntoElement {
406///     let value = use_state(|| 100.);
407///
408///     let animation = use_animation(move |conf| {
409///         conf.on_change(OnChange::Rerun);
410///         AnimNum::new(0., value()).time(50)
411///     });
412///
413///     // ...
414///     # rect()
415/// }
416/// ```
417///
418/// You may also use [use_animation_with_dependencies] to pass non-reactive dependencies as well.
419pub fn use_animation<Animated: AnimatedValue>(
420    mut run: impl 'static + FnMut(&mut AnimConfiguration) -> Animated,
421) -> UseAnimation<Animated> {
422    use_hook(|| {
423        let mut config = State::create(AnimConfiguration::default());
424        let mut animated_value = State::create(Animated::default());
425        let is_running = State::create(false);
426        let has_run_yet = State::create(false);
427        let task = State::create(None);
428        let last_direction = State::create(AnimDirection::Forward);
429
430        let mut animation = UseAnimation {
431            animated_value,
432            config,
433            is_running,
434            has_run_yet,
435            task,
436            last_direction,
437        };
438
439        Effect::create_sync_with_gen(move |current_gen| {
440            let mut anim_conf = AnimConfiguration::default();
441            animated_value.set(run(&mut anim_conf));
442            *config.write() = anim_conf;
443            match config.peek().on_change {
444                OnChange::Finish if current_gen > 0 => {
445                    animation.finish();
446                }
447                OnChange::Rerun if current_gen > 0 => {
448                    let last_direction = *animation.last_direction.peek();
449                    animation.run(last_direction);
450                }
451                OnChange::Reset if current_gen > 0 => {
452                    animation.reset();
453                }
454                _ => {}
455            }
456        });
457
458        match config.peek().on_creation {
459            OnCreation::Run => {
460                animation.run(AnimDirection::Forward);
461            }
462            OnCreation::Finish => {
463                animation.finish();
464            }
465            _ => {}
466        }
467
468        animation
469    })
470}
471
472/// Like [use_animation] but supports passing manual dependencies.
473///
474/// ```rust, no_run
475/// # use freya::prelude::*;
476/// # use freya::animation::*;
477/// fn app() -> impl IntoElement {
478///     # let other_value = 1.;
479///     let animation = use_animation_with_dependencies(&other_value, |conf, other_value| {
480///         conf.on_change(OnChange::Rerun);
481///         AnimNum::new(0., *other_value).time(50)
482///     });
483///
484///     // ...
485///     # rect()
486/// }
487/// ```
488pub fn use_animation_with_dependencies<Animated: AnimatedValue, D: 'static + Clone + PartialEq>(
489    dependencies: &D,
490    mut run: impl 'static + FnMut(&mut AnimConfiguration, &D) -> Animated,
491) -> UseAnimation<Animated> {
492    let dependencies = use_reactive(dependencies);
493    use_hook(|| {
494        let mut config = State::create(AnimConfiguration::default());
495        let mut animated_value = State::create(Animated::default());
496        let is_running = State::create(false);
497        let has_run_yet = State::create(false);
498        let task = State::create(None);
499        let last_direction = State::create(AnimDirection::Forward);
500
501        let mut animation = UseAnimation {
502            animated_value,
503            config,
504            is_running,
505            has_run_yet,
506            task,
507            last_direction,
508        };
509
510        Effect::create_sync_with_gen(move |current_gen| {
511            let dependencies = dependencies.read();
512            let mut anim_conf = AnimConfiguration::default();
513            animated_value.set(run(&mut anim_conf, &dependencies));
514            *config.write() = anim_conf;
515
516            match config.peek().on_change {
517                OnChange::Finish if current_gen > 0 => {
518                    animation.finish();
519                }
520                OnChange::Rerun if current_gen > 0 => {
521                    let last_direction = *animation.last_direction.peek();
522                    animation.run(last_direction);
523                }
524                OnChange::Reset if current_gen > 0 => {
525                    animation.reset();
526                }
527                _ => {}
528            }
529        });
530
531        match config.peek().on_creation {
532            OnCreation::Run => {
533                animation.run(AnimDirection::Forward);
534            }
535            OnCreation::Finish => {
536                animation.finish();
537            }
538            _ => {}
539        }
540
541        animation
542    })
543}
544
545macro_rules! impl_tuple_call {
546    ($(($($type:ident),*)),*) => {
547        $(
548            impl<$($type,)*> AnimatedValue for ($($type,)*)
549            where
550                $($type: AnimatedValue,)*
551            {
552                fn prepare(&mut self, direction: AnimDirection) {
553                    #[allow(non_snake_case)]
554                    let ($($type,)*) = self;
555                    $(
556                        $type.prepare(direction);
557                    )*
558                }
559
560                fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
561                    #[allow(non_snake_case)]
562                    let ($($type,)*) = self;
563                    $(
564                        if !$type.is_finished(index, direction) {
565                            return false;
566                        }
567                    )*
568                    true
569                }
570
571                fn advance(&mut self, index: u128, direction: AnimDirection) {
572                    #[allow(non_snake_case)]
573                    let ($($type,)*) = self;
574                    $(
575                        $type.advance(index, direction);
576                    )*
577                }
578
579                fn finish(&mut self, direction: AnimDirection) {
580                    #[allow(non_snake_case)]
581                    let ($($type,)*) = self;
582                    $(
583                        $type.finish(direction);
584                    )*
585                }
586
587               fn into_reversed(self) -> Self {
588                    #[allow(non_snake_case)]
589                    let ($($type,)*) = self;
590                    (
591                        $(
592                            $type.into_reversed(),
593                        )*
594                    )
595                }
596            }
597            impl<$($type,)*> ReadAnimatedValue for  ($($type,)*)
598            where
599                $($type: ReadAnimatedValue,)*
600            {
601                type Output = (
602                    $(
603                        <$type as ReadAnimatedValue>::Output,
604                    )*
605                );
606                fn value(&self) -> Self::Output {
607                    #[allow(non_snake_case)]
608                    let ($($type,)*) = self;
609                    (
610                        $(
611                            $type.value(),
612                        )*
613                    )
614                }
615            }
616        )*
617    };
618}
619
620impl_tuple_call!(
621    (T1),
622    (T1, T2),
623    (T1, T2, T3),
624    (T1, T2, T3, T4),
625    (T1, T2, T3, T4, T5),
626    (T1, T2, T3, T4, T5, T6),
627    (T1, T2, T3, T4, T5, T6, T7),
628    (T1, T2, T3, T4, T5, T6, T7, T8),
629    (T1, T2, T3, T4, T5, T6, T7, T8, T9),
630    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
631    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
632    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)
633);
634
635/// Create a transition animation that animates from the previous value to the current value.
636///
637/// This hook tracks the previous and current values of a reactive value and creates
638/// an animation that transitions between them when the value changes.
639///
640/// # Example
641///
642/// ```rust, no_run
643/// # use freya::prelude::*;
644/// # use freya::animation::*;
645/// fn app() -> impl IntoElement {
646///     let color = use_state(|| Color::RED);
647///     let animation =
648///         use_animation_transition(color, |from: Color, to| AnimColor::new(from, to).time(500));
649///
650///     rect()
651///         .background(&*animation.read())
652///         .width(Size::px(100.))
653///         .height(Size::px(100.))
654/// }
655/// ```
656pub fn use_animation_transition<Animated: AnimatedValue, T: Clone + PartialEq + 'static>(
657    value: impl IntoReadable<T>,
658    mut run: impl 'static + FnMut(T, T) -> Animated,
659) -> UseAnimation<Animated> {
660    let values = use_previous_and_current(value);
661    use_animation(move |conf| {
662        conf.on_change(OnChange::Rerun);
663        conf.on_creation(OnCreation::Run);
664        let (from, to) = values.read().clone();
665        run(from, to)
666    })
667}