Skip to main content

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) runs: State<usize>,
166    pub(crate) task: State<Option<TaskHandle>>,
167    pub(crate) last_direction: State<AnimDirection>,
168}
169impl<T: AnimatedValue> Copy for UseAnimation<T> {}
170
171impl<Animated: AnimatedValue> Deref for UseAnimation<Animated> {
172    type Target = State<Animated>;
173    fn deref(&self) -> &Self::Target {
174        &self.animated_value
175    }
176}
177
178impl<Animated: AnimatedValue> UseAnimation<Animated> {
179    /// Get the animated value.
180    pub fn get(&self) -> ReadRef<'static, Animated> {
181        self.animated_value.read()
182    }
183
184    /// Get the last configured direction used to animation.
185    pub fn direction(&self) -> ReadRef<'static, AnimDirection> {
186        self.last_direction.read()
187    }
188
189    /// Runs the animation normally.
190    pub fn start(&mut self) {
191        self.run(AnimDirection::Forward)
192    }
193
194    /// Runs the animation in reverse direction.
195    pub fn reverse(&mut self) {
196        self.run(AnimDirection::Reverse)
197    }
198
199    /// Reset the animation with the initial state.
200    pub fn reset(&mut self) {
201        if let Some(task) = self.task.write().take() {
202            task.cancel();
203        }
204
205        self.animated_value
206            .write()
207            .prepare(*self.last_direction.peek());
208
209        *self.has_run_yet.write() = true;
210    }
211
212    /// Finish the animation with the final state.
213    pub fn finish(&mut self) {
214        if let Some(task) = self.task.write().take() {
215            task.cancel();
216        }
217
218        self.animated_value
219            .write()
220            .finish(*self.last_direction.peek());
221
222        *self.has_run_yet.write() = true;
223    }
224
225    pub fn is_running(&self) -> State<bool> {
226        self.is_running
227    }
228
229    pub fn has_run_yet(&self) -> State<bool> {
230        self.has_run_yet
231    }
232
233    /// How many times the animation has been run.
234    pub fn runs(&self) -> State<usize> {
235        self.runs
236    }
237
238    /// Run the animation with a given [`AnimDirection`]
239    pub fn run(&self, mut direction: AnimDirection) {
240        let mut is_running = self.is_running;
241        let mut has_run_yet = self.has_run_yet;
242        let mut runs = self.runs;
243        let mut task = self.task;
244        let mut last_direction = self.last_direction;
245
246        let on_finish = self.config.peek().on_finish;
247        let mut animated_value = self.animated_value;
248
249        last_direction.set(direction);
250
251        // Cancel previous animations
252        if let Some(task) = task.write().take() {
253            task.cancel();
254        }
255
256        let peek_has_run_yet = *self.has_run_yet.peek();
257
258        let mut ticker = RenderingTicker::get();
259        let platform = Platform::get();
260        let animation_clock = AnimationClock::get();
261
262        // Prepare the animations with the the proper direction
263        animated_value.write().prepare(direction);
264
265        let animation_task = spawn(async move {
266            platform.send(UserEvent::RequestRedraw);
267
268            let mut index = 0u128;
269            let mut prev_frame = Instant::now();
270
271            if !peek_has_run_yet {
272                *has_run_yet.write() = true;
273            }
274            *runs.write() += 1;
275            is_running.set(true);
276
277            loop {
278                // Wait for the event loop to tick
279                ticker.tick().await;
280
281                // Request another redraw to move the animation forward
282                platform.send(UserEvent::RequestRedraw);
283
284                let elapsed = animation_clock.correct_elapsed_duration(prev_frame.elapsed());
285
286                index += elapsed.as_millis();
287
288                let is_finished = {
289                    let mut animated_value = animated_value.write();
290                    let is_finished = animated_value.is_finished(index, direction);
291                    // Advance the animations
292                    animated_value.advance(index, direction);
293
294                    is_finished
295                };
296
297                if is_finished {
298                    let delay = match on_finish {
299                        OnFinish::Reverse { delay } => {
300                            // Toggle direction
301                            direction.toggle();
302                            delay
303                        }
304                        OnFinish::Restart { delay } => delay,
305                        OnFinish::Nothing => {
306                            // Stop if all the animations are finished
307                            break;
308                        }
309                    };
310
311                    if !delay.is_zero() {
312                        Timer::after(delay).await;
313                    }
314
315                    index = 0;
316
317                    // Restart/reverse the animation
318                    animated_value.write().prepare(direction);
319                }
320
321                prev_frame = Instant::now();
322            }
323
324            is_running.set(false);
325            task.write().take();
326        });
327
328        // Cancel previous animations
329        task.write().replace(animation_task);
330    }
331}
332/// Animate your UI easily.
333///
334/// [`use_animation`] takes a callback to initialize the animated values and related configuration.
335///
336/// To animate a group of values at once you can just return a tuple of them.
337///
338/// Currently supports animating:
339/// - Numeric values (e.g width): [AnimNum](crate::prelude::AnimNum)
340/// - Colors using [AnimColor](crate::prelude::AnimColor)
341/// - Sequential animations: [AnimSequential](crate::prelude::AnimSequential)
342/// - Anything as long as you implement the [AnimatedValue] trait.
343///
344/// For each animated value you will need to specify the duration, optionally an ease function or what type of easing you want.
345///
346/// # Example
347///
348/// Here is an example that animates a value from `0.0` to `100.0` in `50` milliseconds.
349///
350/// ```rust, no_run
351/// # use freya::prelude::*;
352/// # use freya::animation::*;
353/// fn app() -> impl IntoElement {
354///     let animation = use_animation(|conf| {
355///         conf.on_creation(OnCreation::Run);
356///         AnimNum::new(0., 100.).time(50)
357///     });
358///
359///     let width = animation.get().value();
360///
361///     rect()
362///         .width(Size::px(width))
363///         .height(Size::fill())
364///         .background(Color::BLUE)
365/// }
366/// ```
367///
368/// You are not limited to just one animation per call, you can have as many as you want.
369///
370/// ```rust, no_run
371/// # use freya::prelude::*;
372/// # use freya::animation::*;
373/// fn app() -> impl IntoElement {
374///     let animation = use_animation(|conf| {
375///         conf.on_creation(OnCreation::Run);
376///         (
377///             AnimNum::new(0., 100.).time(50),
378///             AnimColor::new(Color::RED, Color::BLUE).time(50),
379///         )
380///     });
381///
382///     let (width, color) = animation.get().value();
383///
384///     rect()
385///         .width(Size::px(width))
386///         .height(Size::fill())
387///         .background(color)
388/// }
389/// ```
390///
391/// You can also tweak what to do once the animation has finished with [`AnimConfiguration::on_finish`].
392///
393/// ```rust, no_run
394/// # use freya::prelude::*;
395/// # use freya::animation::*;
396/// fn app() -> impl IntoElement {
397///     let animation = use_animation(|conf| {
398///         conf.on_finish(OnFinish::restart());
399///         // ...
400///         # AnimNum::new(0., 1.)
401///     });
402///
403///     // ...
404///     # rect()
405/// }
406/// ```
407///
408/// You can subscribe your animation to reactive [state](freya_core::prelude::use_state) values, these are considered dependencies.
409///
410/// ```rust, no_run
411/// # use freya::prelude::*;
412/// # use freya::animation::*;
413/// fn app() -> impl IntoElement {
414///     let value = use_state(|| 100.);
415///
416///     let animation = use_animation(move |conf| {
417///         conf.on_change(OnChange::Rerun);
418///         AnimNum::new(0., value()).time(50)
419///     });
420///
421///     // ...
422///     # rect()
423/// }
424/// ```
425///
426/// You may also use [use_animation_with_dependencies] to pass non-reactive dependencies as well.
427pub fn use_animation<Animated: AnimatedValue>(
428    mut run: impl 'static + FnMut(&mut AnimConfiguration) -> Animated,
429) -> UseAnimation<Animated> {
430    use_hook(|| {
431        let mut config = State::create(AnimConfiguration::default());
432        let mut animated_value = State::create(Animated::default());
433        let is_running = State::create(false);
434        let has_run_yet = State::create(false);
435        let runs = State::create(0);
436        let task = State::create(None);
437        let last_direction = State::create(AnimDirection::Forward);
438
439        let mut animation = UseAnimation {
440            animated_value,
441            config,
442            is_running,
443            has_run_yet,
444            runs,
445            task,
446            last_direction,
447        };
448
449        Effect::create_sync_with_gen(move |current_gen| {
450            let mut anim_conf = AnimConfiguration::default();
451            animated_value.set(run(&mut anim_conf));
452            *config.write() = anim_conf;
453            match config.peek().on_change {
454                OnChange::Finish if current_gen > 0 => {
455                    animation.finish();
456                }
457                OnChange::Rerun if current_gen > 0 => {
458                    let last_direction = *animation.last_direction.peek();
459                    animation.run(last_direction);
460                }
461                OnChange::Reset if current_gen > 0 => {
462                    animation.reset();
463                }
464                _ => {}
465            }
466        });
467
468        match config.peek().on_creation {
469            OnCreation::Run => {
470                animation.run(AnimDirection::Forward);
471            }
472            OnCreation::Finish => {
473                animation.finish();
474            }
475            _ => {}
476        }
477
478        animation
479    })
480}
481
482/// Like [use_animation] but supports passing manual dependencies.
483///
484/// ```rust, no_run
485/// # use freya::prelude::*;
486/// # use freya::animation::*;
487/// fn app() -> impl IntoElement {
488///     # let other_value = 1.;
489///     let animation = use_animation_with_dependencies(&other_value, |conf, other_value| {
490///         conf.on_change(OnChange::Rerun);
491///         AnimNum::new(0., *other_value).time(50)
492///     });
493///
494///     // ...
495///     # rect()
496/// }
497/// ```
498pub fn use_animation_with_dependencies<Animated: AnimatedValue, D: 'static + Clone + PartialEq>(
499    dependencies: &D,
500    mut run: impl 'static + FnMut(&mut AnimConfiguration, &D) -> Animated,
501) -> UseAnimation<Animated> {
502    let dependencies = use_reactive(dependencies);
503    use_hook(|| {
504        let mut config = State::create(AnimConfiguration::default());
505        let mut animated_value = State::create(Animated::default());
506        let is_running = State::create(false);
507        let has_run_yet = State::create(false);
508        let runs = State::create(0);
509        let task = State::create(None);
510        let last_direction = State::create(AnimDirection::Forward);
511
512        let mut animation = UseAnimation {
513            animated_value,
514            config,
515            is_running,
516            has_run_yet,
517            runs,
518            task,
519            last_direction,
520        };
521
522        Effect::create_sync_with_gen(move |current_gen| {
523            let dependencies = dependencies.read();
524            let mut anim_conf = AnimConfiguration::default();
525            animated_value.set(run(&mut anim_conf, &dependencies));
526            *config.write() = anim_conf;
527
528            match config.peek().on_change {
529                OnChange::Finish if current_gen > 0 => {
530                    animation.finish();
531                }
532                OnChange::Rerun if current_gen > 0 => {
533                    let last_direction = *animation.last_direction.peek();
534                    animation.run(last_direction);
535                }
536                OnChange::Reset if current_gen > 0 => {
537                    animation.reset();
538                }
539                _ => {}
540            }
541        });
542
543        match config.peek().on_creation {
544            OnCreation::Run => {
545                animation.run(AnimDirection::Forward);
546            }
547            OnCreation::Finish => {
548                animation.finish();
549            }
550            _ => {}
551        }
552
553        animation
554    })
555}
556
557macro_rules! impl_tuple_call {
558    ($(($($type:ident),*)),*) => {
559        $(
560            impl<$($type,)*> AnimatedValue for ($($type,)*)
561            where
562                $($type: AnimatedValue,)*
563            {
564                fn prepare(&mut self, direction: AnimDirection) {
565                    #[allow(non_snake_case)]
566                    let ($($type,)*) = self;
567                    $(
568                        $type.prepare(direction);
569                    )*
570                }
571
572                fn is_finished(&self, index: u128, direction: AnimDirection) -> bool {
573                    #[allow(non_snake_case)]
574                    let ($($type,)*) = self;
575                    $(
576                        if !$type.is_finished(index, direction) {
577                            return false;
578                        }
579                    )*
580                    true
581                }
582
583                fn advance(&mut self, index: u128, direction: AnimDirection) {
584                    #[allow(non_snake_case)]
585                    let ($($type,)*) = self;
586                    $(
587                        $type.advance(index, direction);
588                    )*
589                }
590
591                fn finish(&mut self, direction: AnimDirection) {
592                    #[allow(non_snake_case)]
593                    let ($($type,)*) = self;
594                    $(
595                        $type.finish(direction);
596                    )*
597                }
598
599               fn into_reversed(self) -> Self {
600                    #[allow(non_snake_case)]
601                    let ($($type,)*) = self;
602                    (
603                        $(
604                            $type.into_reversed(),
605                        )*
606                    )
607                }
608            }
609            impl<$($type,)*> ReadAnimatedValue for  ($($type,)*)
610            where
611                $($type: ReadAnimatedValue,)*
612            {
613                type Output = (
614                    $(
615                        <$type as ReadAnimatedValue>::Output,
616                    )*
617                );
618                fn value(&self) -> Self::Output {
619                    #[allow(non_snake_case)]
620                    let ($($type,)*) = self;
621                    (
622                        $(
623                            $type.value(),
624                        )*
625                    )
626                }
627            }
628        )*
629    };
630}
631
632impl_tuple_call!(
633    (T1),
634    (T1, T2),
635    (T1, T2, T3),
636    (T1, T2, T3, T4),
637    (T1, T2, T3, T4, T5),
638    (T1, T2, T3, T4, T5, T6),
639    (T1, T2, T3, T4, T5, T6, T7),
640    (T1, T2, T3, T4, T5, T6, T7, T8),
641    (T1, T2, T3, T4, T5, T6, T7, T8, T9),
642    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
643    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
644    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)
645);
646
647/// Create a transition animation that animates from the previous value to the current value.
648///
649/// This hook tracks the previous and current values of a reactive value and creates
650/// an animation that transitions between them when the value changes.
651///
652/// # Example
653///
654/// ```rust, no_run
655/// # use freya::prelude::*;
656/// # use freya::animation::*;
657/// fn app() -> impl IntoElement {
658///     let color = use_state(|| Color::RED);
659///     let animation =
660///         use_animation_transition(color, |from: Color, to| AnimColor::new(from, to).time(500));
661///
662///     rect()
663///         .background(&*animation.read())
664///         .width(Size::px(100.))
665///         .height(Size::px(100.))
666/// }
667/// ```
668pub fn use_animation_transition<Animated: AnimatedValue, T: Clone + PartialEq + 'static>(
669    value: impl IntoReadable<T>,
670    mut run: impl 'static + FnMut(T, T) -> Animated,
671) -> UseAnimation<Animated> {
672    let values = use_previous_and_current(value);
673    use_animation(move |conf| {
674        conf.on_change(OnChange::Rerun);
675        conf.on_creation(OnCreation::Run);
676        let (from, to) = values.read().clone();
677        run(from, to)
678    })
679}