Skip to main content

floem/animate/
mod.rs

1#![deny(missing_docs)]
2
3//! Animations
4
5pub mod easing;
6
7pub use easing::{Bezier, Easing, Linear, Spring, Step, StepPosition};
8
9use crate::{
10    ViewId,
11    style::{Style, StylePropRef},
12    unit::UnitExt,
13    view::StackOffset,
14};
15
16use std::any::Any;
17use std::rc::Rc;
18
19use crate::platform::{Duration, Instant};
20use floem_reactive::{RwSignal, SignalGet, Trigger, UpdaterEffect};
21use smallvec::{SmallVec, smallvec};
22
23/// Holds a resolved prop, along with the associated frame id and easing function
24#[derive(Clone, Debug)]
25pub struct KeyFrameProp {
26    // the style prop value. This will either come from an animation frameor it will be pulled from the computed style
27    val: Rc<dyn Any>,
28    // the frame id
29    id: u16,
30    /// This easing will be used while animating towards this keyframe. while this prop is the lower one this easing function will not be used.
31    easing: Rc<dyn Easing>,
32}
33
34/// Defines whether the style in a key frame should be stored in the frame or it it should be pulled from the computed style
35#[derive(Clone, Debug)]
36pub enum KeyFrameStyle {
37    /// when computed style, props will be pulled from the computed style
38    Computed,
39    /// When using style, the props will be stored in the key frame
40    Style(Style),
41}
42impl From<Style> for KeyFrameStyle {
43    fn from(value: Style) -> Self {
44        Self::Style(value)
45    }
46}
47
48/// Holds the style properties for a keyframe as well as the easing function that should be used when animating towards this frame
49#[derive(Clone, Debug)]
50pub struct KeyFrame {
51    #[allow(unused)]
52    /// the key frame id. should be less than the maximum key frame number for a given animation
53    id: u16,
54    style: KeyFrameStyle,
55    /// This easing will be used while animating towards this keyframe.
56    easing: Rc<dyn Easing>,
57}
58impl KeyFrame {
59    /// Create a new keyframe with the given id
60    pub fn new(id: u16) -> Self {
61        Self {
62            id,
63            style: Style::default().into(),
64            easing: Rc::new(Spring::default()),
65        }
66    }
67
68    /// Apply a style to this keyframe.
69    pub fn style(mut self, style: impl Fn(Style) -> Style) -> Self {
70        let style = style(Style::new());
71        match &mut self.style {
72            cs @ KeyFrameStyle::Computed => *cs = style.into(),
73            KeyFrameStyle::Style(s) => s.apply_mut(&style),
74        }
75        self
76    }
77
78    /// Set this keyframe to pull its props from the computed style. The will completely overwrite any previously applied styles to this keyframe.
79    pub fn computed_style(mut self) -> Self {
80        self.style = KeyFrameStyle::Computed;
81        self
82    }
83
84    /// This easing function will be used while animating towards this keyframe
85    pub fn ease(mut self, easing: impl Easing + 'static) -> Self {
86        self.easing = Rc::new(easing);
87        self
88    }
89
90    /// Sets the easing function to the bezier ease in and out
91    pub fn ease_in_out(self) -> Self {
92        self.ease(Bezier::ease_in_out())
93    }
94
95    /// Sets the easing function to the default spring
96    pub fn ease_spring(self) -> Self {
97        self.ease(Spring::default())
98    }
99
100    /// Sets the easing function to a linear easing
101    pub fn ease_linear(self) -> Self {
102        self.ease(Linear)
103    }
104
105    /// Sets the easing function to the bezier ease in
106    pub fn ease_in(self) -> Self {
107        self.ease(Bezier::ease_in())
108    }
109
110    /// Sets the easing function to the bezier ease out
111    pub fn ease_out(self) -> Self {
112        self.ease(Bezier::ease_out())
113    }
114}
115
116/// Holds frame ids and marks if the frame is supposed to pull its props from a style or from the computed style
117#[derive(Debug, Clone, Copy, Eq)]
118enum PropFrameKind {
119    Normal(u16),
120    Computed(u16),
121}
122impl PropFrameKind {
123    const fn inner(self) -> u16 {
124        match self {
125            Self::Normal(val) => val,
126            Self::Computed(val) => val,
127        }
128    }
129}
130impl PartialOrd for PropFrameKind {
131    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
132        Some(self.cmp(other))
133    }
134}
135impl Ord for PropFrameKind {
136    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
137        self.inner().cmp(&other.inner())
138    }
139}
140impl PartialEq for PropFrameKind {
141    fn eq(&self, other: &Self) -> bool {
142        self.inner() == other.inner()
143    }
144}
145
146/// Holds the pair of frame ids that a single prop is animating between
147#[derive(Debug, Clone, Copy)]
148struct PropFrames {
149    // the closeset frame to the target idx that is less than or equal to current
150    lower_idx: Option<PropFrameKind>,
151    // the closeset frame to the target idx that is greater than current
152    upper_idx: Option<PropFrameKind>,
153}
154
155/// This cache enables looking up which keyframes contain a given prop, enabling animation of individual props,
156/// even if they are sparsely located in the keyframes, with multiple keyframes between each instance of the prop
157#[derive(Debug, Clone, Default)]
158pub(crate) struct PropCache {
159    /// A map of style properties to a list of all frame ids containing that prop
160    prop_map: imbl::HashMap<StylePropRef, SmallVec<[PropFrameKind; 5]>>,
161    /// a cached list of all keyframes that use the computed style instead of a separate style
162    computed_idxs: SmallVec<[u16; 2]>,
163}
164impl PropCache {
165    /// Find the pair of frames for a given prop at some given target index.
166    /// This will find the pair of frames with one lower than the target and one higher than the target.
167    /// If it cannot find both, it returns none.
168    fn get_prop_frames(&self, prop: StylePropRef, target_idx: u16) -> Option<PropFrames> {
169        self.prop_map.get(&prop).map(|frames| {
170            match frames.binary_search(&PropFrameKind::Normal(target_idx)) {
171                Ok(exact_idx) => {
172                    // Exact match found: lower is the exact match, upper is the next frame if it exists
173                    let lower = Some(frames[exact_idx]);
174                    let upper = frames.get(exact_idx + 1).copied();
175                    PropFrames {
176                        lower_idx: lower,
177                        upper_idx: upper,
178                    }
179                }
180                Err(pos) => {
181                    // No exact match found
182                    let lower = if pos > 0 {
183                        Some(frames[pos - 1]) // Largest smaller frame
184                    } else {
185                        None
186                    };
187                    let upper = frames.get(pos).copied(); // Smallest larger frame, if it exists
188                    PropFrames {
189                        lower_idx: lower,
190                        upper_idx: upper,
191                    }
192                }
193            }
194        })
195    }
196
197    fn insert_prop(&mut self, prop: StylePropRef, idx: PropFrameKind) {
198        match self.prop_map.entry(prop) {
199            imbl::hashmap::Entry::Occupied(mut oe) => {
200                if let Err(pos) = oe.get().binary_search(&idx) {
201                    oe.get_mut().insert(pos, idx)
202                }
203            }
204            imbl::hashmap::Entry::Vacant(ve) => {
205                ve.insert(smallvec![idx]);
206            }
207        }
208    }
209
210    fn insert_computed_prop(&mut self, prop: StylePropRef, idx: PropFrameKind) {
211        // computed props are inserted at the start of each call of `animate_into`.
212        // Therefore, if the cache does not already contain references to a prop, there will be nothing to animate between and we just don't insert anything.
213        if let imbl::hashmap::Entry::Occupied(mut oe) = self.prop_map.entry(prop) {
214            if let Err(pos) = oe.get().binary_search(&idx) {
215                oe.get_mut().insert(pos, idx)
216            } else {
217                unreachable!(
218                    "this should err because a computed prop shouldn't be inserted more than once. "
219                )
220            }
221        }
222    }
223
224    fn remove_prop(&mut self, prop: StylePropRef, idx: u16) {
225        if let imbl::hashmap::Entry::Occupied(mut oe) = self.prop_map.entry(prop)
226            && let Ok(pos) = oe.get().binary_search(&PropFrameKind::Normal(idx))
227        {
228            oe.get_mut().remove(pos);
229        }
230    }
231
232    // mark a frame id as for a computed style
233    fn insert_computed(&mut self, idx: u16) {
234        if let Err(pos) = self.computed_idxs.binary_search(&idx) {
235            self.computed_idxs.insert(pos, idx)
236        }
237    }
238
239    // removed a frame id from being marked as for a computed style
240    fn remove_computed(&mut self, idx: u16) {
241        if let Ok(pos) = self.computed_idxs.binary_search(&idx) {
242            self.computed_idxs.remove(pos);
243        }
244    }
245}
246
247/// Holds the allowance and state of the reverse once property of an animation.
248/// Reversing an animation is attempted when animation is being removed or hidden.
249#[derive(Debug, Clone, Copy)]
250pub enum ReverseOnce {
251    /// When `Never`, the animation will not be allowed to be set to be in reverse mode
252    Never,
253    /// When `Val`, the animation is allowed to be set to reverse until finished.
254    /// When `Val(true)` the animation will actually reverse
255    Val(bool),
256}
257impl ReverseOnce {
258    /// If the reverse once is not `Never` this will set the animation to start or end reversing until finished
259    pub fn set(&mut self, val: bool) {
260        if let Self::Val(v) = self {
261            *v = val;
262        }
263    }
264
265    /// return true if the animation should be reversing
266    pub const fn is_rev(self) -> bool {
267        match self {
268            Self::Never => false,
269            Self::Val(v) => v,
270        }
271    }
272}
273
274/// The mode to specify how the animation should repeat. See also [`Animation::advance`]
275#[derive(Clone, Debug)]
276pub enum RepeatMode {
277    // Once started, the animation will juggle between [`AnimState::PassInProgress`] and [`AnimState::PassFinished`],
278    // but will never reach [`AnimState::Completed`]
279    /// Repeat the animation forever
280    LoopForever,
281    // On every pass, we animate until `elapsed >= duration`, then we reset elapsed time to 0 and increment `repeat_count` is
282    // increased by 1. This process is repeated until `repeat_count >= times`, and then the animation is set
283    // to [`AnimState::Completed`].
284    /// Repeat the animation the specified number of times before the animation enters a Complete state
285    Times(usize),
286}
287
288#[derive(Debug, Clone)]
289pub(crate) enum AnimState {
290    Idle,
291    Stopped,
292    Paused {
293        elapsed: Option<Duration>,
294    },
295    /// How many passes(loops) there will be is controlled by the [`RepeatMode`] of the animation.
296    /// By default, the animation will only have a single pass,
297    /// but it can be set to [`RepeatMode::LoopForever`] to loop indefinitely.
298    PassInProgress {
299        started_on: Instant,
300        elapsed: Duration,
301    },
302    ExtMode {
303        started_on: Instant,
304        elapsed: Duration,
305    },
306    /// Depending on the [`RepeatMode`] of the animation, we either go back to `PassInProgress`
307    /// or advance to `Completed`.
308    PassFinished {
309        elapsed: Duration,
310        was_in_ext: bool,
311    },
312    // NOTE: If animation has `RepeatMode::LoopForever`, this state will never be reached.
313    Completed {
314        elapsed: Option<Duration>,
315        was_reversing: bool,
316    },
317}
318
319#[derive(Debug, PartialEq, Eq, Clone, Copy)]
320/// Represents the different states an animation can be in.
321pub enum AnimStateKind {
322    /// The animation is idle and has not started yet.
323    Idle,
324    /// The animation is paused and can be resumed.
325    Paused,
326    /// The animation is stopped and cannot be resumed.
327    Stopped,
328    /// The animation is currently in progress.
329    ///
330    /// In this state the animation is actively animating the properties of the view.
331    PassInProgress,
332    /// The animation has finished a pass but may repeat based on the repeat mode.
333    PassFinished,
334    /// The animation has completed all its passes and will not run again until started.
335    Completed,
336}
337
338#[derive(Debug, PartialEq, Eq, Clone, Copy)]
339/// Commands to control the state of an animation
340pub enum AnimStateCommand {
341    /// Pause the animation
342    Pause,
343    /// Resume the animation
344    Resume,
345    /// Start the animation
346    Start,
347    /// Stop the animation
348    Stop,
349    /// Start the animation in reverse
350    Reverse,
351}
352
353type EffectStateVec = SmallVec<[RwSignal<SmallVec<[(ViewId, StackOffset<Animation>); 1]>>; 1]>;
354
355/// The main animation struct
356///
357/// Use [`Animation::new`] or the [`Decorators::animation`](crate::views::Decorators::animation) method to build an animation.
358#[derive(Debug, Clone)]
359pub struct Animation {
360    pub(crate) state: AnimState,
361    pub(crate) effect_states: EffectStateVec,
362    pub(crate) auto_reverse: bool,
363    pub(crate) delay: Duration,
364    pub(crate) delay_on_reverse: bool,
365    pub(crate) duration: Duration,
366    pub(crate) repeat_mode: RepeatMode,
367    /// How many times the animation has been repeated so far
368    pub(crate) repeat_count: usize,
369    /// run on remove and run on create should be checked for and respected by any view that dynamically creates sub views
370    pub(crate) run_on_remove: bool,
371    pub(crate) run_on_create: bool,
372    pub(crate) reverse_once: ReverseOnce,
373    pub(crate) max_key_frame_num: u16,
374    pub(crate) apply_when_finished: bool,
375    pub(crate) folded_style: Style,
376    pub(crate) key_frames: imbl::HashMap<u16, KeyFrame>,
377    // frames should be added to this if when they are the lower frame, they return not done. check/run them before other frames
378    pub(crate) props_in_ext_progress: imbl::HashMap<StylePropRef, (KeyFrameProp, KeyFrameProp)>,
379    pub(crate) cache: PropCache,
380    /// This will fire at the start of each cycle of an animation.
381    pub(crate) on_start: Trigger,
382    /// This trigger will fire at the completion of an animations duration.
383    /// Animations are allowed to go on for longer than their duration, until the easing reports finished.
384    /// When waiting for the completion of an animation (such as to remove a view), this trigger should be preferred.
385    pub(crate) on_visual_complete: Trigger,
386    /// This trigger will fire at the total completion of an animation when the easing function of all props report finished.
387    pub(crate) on_complete: Trigger,
388    pub(crate) debug_description: Option<String>,
389}
390impl Default for Animation {
391    fn default() -> Self {
392        Self {
393            state: AnimState::Idle,
394            effect_states: SmallVec::new(),
395            auto_reverse: false,
396            delay: Duration::ZERO,
397            delay_on_reverse: false,
398            duration: Duration::from_millis(200),
399            repeat_mode: RepeatMode::Times(1),
400            repeat_count: 0,
401            run_on_remove: false,
402            run_on_create: false,
403            reverse_once: ReverseOnce::Val(false),
404            max_key_frame_num: 100,
405            apply_when_finished: false,
406            folded_style: Style::new(),
407            cache: Default::default(),
408            key_frames: imbl::HashMap::new(),
409            props_in_ext_progress: imbl::HashMap::new(),
410            on_start: Trigger::new(),
411            on_complete: Trigger::new(),
412            on_visual_complete: Trigger::new(),
413            debug_description: None,
414        }
415    }
416}
417
418/// # Methods for creating an animation, including methods that quickly initialize the animation for specific uses
419impl Animation {
420    /// Create a new animation
421    pub fn new() -> Self {
422        Self::default()
423    }
424
425    /// Quickly set a few properties on an animation to set up an animation to be used as a view transition (on creation and removal).
426    /// (Sets keyframes 0 and 100 to use the computed style until overridden)
427    pub fn view_transition(self) -> Self {
428        self.run_on_create(true)
429            .run_on_remove(true)
430            .initial_state(AnimStateCommand::Stop)
431            .keyframe(0, |f| f.computed_style().ease(Spring::gentle()))
432            .keyframe(100, |f| f.computed_style().ease(Spring::gentle()))
433    }
434
435    /// Quickly set an animation to be a view transition and override the default easing function on keyframes 0 and 100.
436    pub fn view_transition_with_ease(self, ease: impl Easing + 'static + Clone) -> Self {
437        self.view_transition()
438            .keyframe(0, |f| f.computed_style().ease(ease.clone()))
439            .keyframe(100, |f| f.computed_style().ease(ease.clone()))
440    }
441
442    /// Quickly set an animation to be a view transition and set the animation to animate from scale 0% to the "normal" computed style of a view (the view with no animations applied).
443    pub fn scale_effect(self) -> Self {
444        self.view_transition()
445            .keyframe(0, |f| f.style(|s| s.scale(0.pct())))
446            .debug_name("Scale the width and height from zero to the default")
447    }
448
449    /// Quickly set an animation to be a view transition and set the animation to animate from size(0, 0) to the "normal" computed style of a view (the view with no animations applied).
450    pub fn scale_size_effect(self) -> Self {
451        self.view_transition()
452            .keyframe(0, |f| f.style(|s| s.size(0, 0)))
453            .debug_name("Scale the width and height from zero to the default")
454    }
455}
456
457/// # Methods for setting properties on an `Animation`
458impl Animation {
459    /// Build a [`KeyFrame`]
460    ///
461    /// If there is a matching keyframe id, the style in this keyframe will only override the style values in the new style.
462    /// If you want the style to completely override style see [`Animation::keyframe_override`].
463    pub fn keyframe(mut self, frame_id: u16, key_frame: impl Fn(KeyFrame) -> KeyFrame) -> Self {
464        let frame = key_frame(KeyFrame::new(frame_id));
465        if let KeyFrameStyle::Style(ref style) = frame.style {
466            // this frame id now contains a style, so remove this frame id from being marked as computed (if it was).
467            self.cache.remove_computed(frame_id);
468            for prop in style.style_props() {
469                // mark that this frame contains the referenced props
470                self.cache
471                    .insert_prop(prop, PropFrameKind::Normal(frame_id));
472            }
473        } else {
474            self.cache.insert_computed(frame_id);
475        }
476
477        // mutate this keyframe's style to be updated with the new style
478        match self.key_frames.entry(frame_id) {
479            imbl::hashmap::Entry::Occupied(mut oe) => {
480                let e_frame = oe.get_mut();
481                match (&mut e_frame.style, frame.style) {
482                    (KeyFrameStyle::Computed, KeyFrameStyle::Computed) => {}
483                    (s @ KeyFrameStyle::Computed, KeyFrameStyle::Style(ns)) => {
484                        *s = KeyFrameStyle::Style(ns);
485                    }
486                    (s @ KeyFrameStyle::Style(_), KeyFrameStyle::Computed) => {
487                        *s = KeyFrameStyle::Computed;
488                    }
489                    (KeyFrameStyle::Style(s), KeyFrameStyle::Style(ns)) => {
490                        s.apply_mut(&ns);
491                    }
492                }
493                e_frame.easing = frame.easing;
494            }
495            imbl::hashmap::Entry::Vacant(ve) => {
496                ve.insert(frame);
497            }
498        }
499        self
500    }
501
502    /// Build and overwrite a [`KeyFrame`]
503    ///
504    /// If there is a matching keyframe id, the style in this keyframe will completely override the style in the frame that already exists.
505    /// If you want the style to only override the new values see [`Animation::keyframe`].
506    pub fn keyframe_override(
507        mut self,
508        frame_id: u16,
509        key_frame: impl Fn(KeyFrame) -> KeyFrame,
510    ) -> Self {
511        let frame = key_frame(KeyFrame::new(frame_id));
512        let frame_style = frame.style.clone();
513        if let Some(f) = self.key_frames.insert(frame_id, frame)
514            && let KeyFrameStyle::Style(style) = f.style
515        {
516            for prop in style.style_props() {
517                self.cache.remove_prop(prop, frame_id);
518            }
519        }
520        if let KeyFrameStyle::Style(style) = frame_style {
521            self.cache.insert_computed(frame_id);
522            for prop in style.style_props() {
523                self.cache
524                    .insert_prop(prop, PropFrameKind::Normal(frame_id));
525            }
526        } else {
527            self.cache.remove_computed(frame_id);
528        }
529        self
530    }
531
532    /// Sets the perceived duration of the animation.
533    ///
534    /// The total duration of an animation will run until all animating props return `finished`.
535    /// This is useful for spring animations which don't conform well to strict ending times.
536    pub const fn duration(mut self, duration: Duration) -> Self {
537        self.duration = duration;
538        self
539    }
540
541    /// Set properties on the animation while having access to the current duration.
542    pub fn with_duration(self, duration: impl FnOnce(Self, Duration) -> Self) -> Self {
543        let d = self.duration;
544        duration(self, d)
545    }
546
547    /// Conditionally apply properties to this animation if the condition is `true`.
548    pub fn apply_if(self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
549        if cond { f(self) } else { self }
550    }
551
552    /// Provides access to the on create trigger by calling the closure in once and then returning self.
553    pub fn on_create(self, on_create: impl FnOnce(Trigger) + 'static) -> Self {
554        on_create(self.on_start);
555        self
556    }
557
558    /// Provides access to the on visual complete trigger by calling the closure once and then returning self.
559    pub fn on_visual_complete(self, on_visual_complete: impl FnOnce(Trigger) + 'static) -> Self {
560        on_visual_complete(self.on_visual_complete);
561        self
562    }
563
564    /// Provides access to the on complete trigger by calling the closure once and then returning self.
565    pub fn on_complete(self, on_complete: impl FnOnce(Trigger) + 'static) -> Self {
566        on_complete(self.on_complete);
567        self
568    }
569
570    /// Set whether this animation should run when being created.
571    ///
572    /// I.e when being created by a dyn container or when being shown after being hidden.
573    pub const fn run_on_create(mut self, run_on_create: bool) -> Self {
574        self.run_on_create = run_on_create;
575        self
576    }
577
578    /// Set whether this animation should run when being created and not when being removed.
579    pub const fn only_on_create(mut self) -> Self {
580        self.run_on_remove = false;
581        self.run_on_create = true;
582        self
583    }
584
585    /// Set whether this animation should run when being removed.
586    /// I.e when being removed by a dyn container or when being hidden.
587    pub const fn run_on_remove(mut self, run_on_remove: bool) -> Self {
588        self.run_on_remove = run_on_remove;
589        self
590    }
591
592    /// Set whether this animation should run when being removed and not when being created.
593    pub const fn only_on_remove(mut self) -> Self {
594        self.run_on_remove = true;
595        self.run_on_create = false;
596        self
597    }
598
599    /// Set whether the properties from the final keyframe of this animation should be applied even when the animation is finished.
600    pub const fn apply_when_finished(mut self, apply: bool) -> Self {
601        self.apply_when_finished = apply;
602        self
603    }
604
605    /// Sets if this animation should auto reverse.
606    /// If true, the animation will reach the final key frame twice as fast and then animate backwards
607    pub const fn auto_reverse(mut self, auto_rev: bool) -> Self {
608        self.auto_reverse = auto_rev;
609        self
610    }
611
612    /// Sets if this animation should be allowed to be reversed when the view is being removed or hidden.
613    pub const fn reverse_on_exit(mut self, allow: bool) -> Self {
614        if allow {
615            self.reverse_once = ReverseOnce::Val(false);
616        } else {
617            self.reverse_once = ReverseOnce::Never;
618        }
619        self
620    }
621
622    /// Sets a delay for how long the animation should wait before starting.
623    pub const fn delay(mut self, delay: Duration) -> Self {
624        self.delay = delay;
625        self
626    }
627
628    /// Sets whether the animation should delay when reversing.
629    pub const fn delay_on_reverse(mut self, on_reverse: bool) -> Self {
630        self.delay_on_reverse = on_reverse;
631        self
632    }
633
634    /// Sets if the animation should the repeat forever.
635    pub const fn repeat(mut self, repeat: bool) -> Self {
636        self.repeat_mode = if repeat {
637            RepeatMode::LoopForever
638        } else {
639            RepeatMode::Times(1)
640        };
641        self
642    }
643
644    /// Sets the number of times the animation should repeat.
645    pub const fn repeat_times(mut self, times: usize) -> Self {
646        self.repeat_mode = RepeatMode::Times(times);
647        self
648    }
649
650    /// This is used to determine which keyframe is at 100% completion.
651    ///
652    /// The default is 100.
653    ///
654    /// If you need more than 100 keyframes, increase this number, but be aware, the keyframe numbers will then be as a percentage of the maximum.
655    ///
656    /// *This does not move existing keyframes.*
657    pub const fn max_key_frame(mut self, max: u16) -> Self {
658        self.max_key_frame_num = max;
659        self
660    }
661
662    /// Mutably sets the initial state of the animation
663    pub fn initial_state(mut self, command: AnimStateCommand) -> Self {
664        self.transition(command);
665        self
666    }
667
668    /// If `apply_initial` is false the initial command will not be applied to the animation.
669    /// This is useful if you want the effect to be subscribed to changes but not run the first time.
670    pub fn state(
671        mut self,
672        command: impl Fn() -> AnimStateCommand + 'static,
673        apply_initial: bool,
674    ) -> Self {
675        let states = RwSignal::new(SmallVec::new());
676        self.effect_states.push(states);
677        let initial_command = UpdaterEffect::new(command, move |command| {
678            for (view_id, stack_offset) in states.get_untracked() {
679                view_id.update_animation_state(stack_offset, command)
680            }
681        });
682        if apply_initial {
683            self.transition(initial_command);
684        }
685        self
686    }
687
688    /// The animation will receive a pause command any time the trigger function tracks any reactive updates.
689    pub fn pause(self, trigger: impl Fn() + 'static) -> Self {
690        self.state(
691            move || {
692                trigger();
693                AnimStateCommand::Pause
694            },
695            false,
696        )
697    }
698
699    /// The animation will receive a resume command any time the trigger function tracks any reactive updates.
700    pub fn resume(self, trigger: impl Fn() + 'static) -> Self {
701        self.state(
702            move || {
703                trigger();
704                AnimStateCommand::Resume
705            },
706            false,
707        )
708    }
709
710    /// The animation will receive a start command any time the trigger function tracks any reactive updates.
711    pub fn start(self, trigger: impl Fn() + 'static) -> Self {
712        self.state(
713            move || {
714                trigger();
715                AnimStateCommand::Start
716            },
717            false,
718        )
719    }
720
721    /// The animation will receive a reverse command any time the trigger function tracks any reactive updates.
722    ///
723    /// This will start the animation in reverse
724    pub fn reverse(self, trigger: impl Fn() + 'static) -> Self {
725        self.state(
726            move || {
727                trigger();
728                AnimStateCommand::Reverse
729            },
730            false,
731        )
732    }
733
734    /// The animation will receive a stop command any time the trigger function tracks any reactive updates.
735    pub fn stop(self, trigger: impl Fn() + 'static) -> Self {
736        self.state(
737            move || {
738                trigger();
739                AnimStateCommand::Stop
740            },
741            false,
742        )
743    }
744
745    /// Add a debug description to the animation
746    pub fn debug_name(mut self, description: impl Into<String>) -> Self {
747        match &mut self.debug_description {
748            Some(inner_desc) => {
749                inner_desc.push_str("; ");
750                inner_desc.push_str(&description.into())
751            }
752            val @ None => *val = Some(description.into()),
753        }
754        self
755    }
756
757    #[allow(unused)]
758    pub(crate) fn pause_mut(mut self) {
759        self.transition(AnimStateCommand::Pause)
760    }
761
762    #[allow(unused)]
763    pub(crate) fn resume_mut(mut self) {
764        self.transition(AnimStateCommand::Resume)
765    }
766
767    pub(crate) fn start_mut(&mut self) {
768        self.transition(AnimStateCommand::Start)
769    }
770
771    pub(crate) fn reverse_mut(&mut self) {
772        self.transition(AnimStateCommand::Reverse)
773    }
774
775    #[allow(unused)]
776    pub(crate) fn stop_mut(&mut self) {
777        self.transition(AnimStateCommand::Stop)
778    }
779
780    /// Matches the current state of the animation and returns the kind of state it is in.
781    pub const fn state_kind(&self) -> AnimStateKind {
782        match self.state {
783            AnimState::Idle => AnimStateKind::Idle,
784            AnimState::Stopped => AnimStateKind::Stopped,
785            AnimState::PassInProgress { .. } => AnimStateKind::PassInProgress,
786            AnimState::ExtMode { .. } => AnimStateKind::PassInProgress,
787            AnimState::PassFinished { .. } => AnimStateKind::PassFinished,
788            AnimState::Completed { .. } => AnimStateKind::Completed,
789            AnimState::Paused { .. } => AnimStateKind::Paused,
790        }
791    }
792
793    /// Returns the current amount of time that has elapsed since the animation started.
794    pub fn elapsed(&self) -> Option<Duration> {
795        match &self.state {
796            AnimState::Idle => None,
797            AnimState::Stopped => None,
798            AnimState::PassInProgress {
799                started_on,
800                elapsed,
801            }
802            | AnimState::ExtMode {
803                started_on,
804                elapsed,
805            } => {
806                let duration = Instant::now() - *started_on;
807                Some(*elapsed + duration)
808            }
809            AnimState::PassFinished { elapsed, .. } => Some(*elapsed),
810            AnimState::Completed { elapsed, .. } => *elapsed,
811            AnimState::Paused { elapsed } => *elapsed,
812        }
813    }
814
815    /// Advance the animation.
816    pub fn advance(&mut self) {
817        let use_delay = self.use_delay();
818        match &mut self.state {
819            AnimState::Idle => {
820                self.start_mut();
821                self.on_start.notify();
822            }
823            AnimState::PassInProgress {
824                started_on,
825                elapsed,
826            } => {
827                let now = Instant::now();
828                let duration = now - *started_on;
829                let og_elapsed = *elapsed;
830                *elapsed = duration;
831
832                let temp_elapsed = if *elapsed <= self.delay && use_delay {
833                    // The animation hasn't started yet
834                    Duration::ZERO
835                } else if use_delay {
836                    *elapsed - self.delay
837                } else {
838                    *elapsed
839                };
840
841                if temp_elapsed >= self.duration {
842                    if self.props_in_ext_progress.is_empty() {
843                        self.state = AnimState::PassFinished {
844                            elapsed: *elapsed,
845                            was_in_ext: false,
846                        };
847                    } else {
848                        self.on_visual_complete.notify();
849                        self.state = AnimState::ExtMode {
850                            started_on: *started_on,
851                            elapsed: og_elapsed,
852                        };
853                    }
854                }
855            }
856            AnimState::ExtMode {
857                started_on,
858                elapsed,
859            } => {
860                let now = Instant::now();
861                let duration = now - *started_on;
862                *elapsed = duration;
863
864                if self.props_in_ext_progress.is_empty() {
865                    self.state = AnimState::PassFinished {
866                        elapsed: *elapsed,
867                        was_in_ext: true,
868                    };
869                }
870            }
871            AnimState::PassFinished {
872                elapsed,
873                was_in_ext,
874            } => match self.repeat_mode {
875                RepeatMode::LoopForever => {
876                    if self.reverse_once.is_rev() {
877                        self.reverse_once.set(false);
878                    } else if self.auto_reverse {
879                        self.reverse_once.set(true);
880                    }
881                    self.state = AnimState::PassInProgress {
882                        started_on: Instant::now(),
883                        elapsed: Duration::ZERO,
884                    }
885                }
886                RepeatMode::Times(times) => {
887                    self.repeat_count += 1;
888                    if self.repeat_count >= times {
889                        let was_reversing = self.reverse_once.is_rev();
890                        self.reverse_once.set(false);
891                        self.on_complete.notify();
892                        if !*was_in_ext {
893                            self.on_visual_complete.notify();
894                        }
895                        self.state = AnimState::Completed {
896                            elapsed: Some(*elapsed),
897                            was_reversing,
898                        }
899                    } else {
900                        self.state = AnimState::PassInProgress {
901                            started_on: Instant::now(),
902                            elapsed: Duration::ZERO,
903                        }
904                    }
905                }
906            },
907            AnimState::Paused { .. } => {
908                debug_assert!(false, "Tried to advance a paused animation")
909            }
910            AnimState::Stopped => {
911                debug_assert!(false, "Tried to advance a stopped animation")
912            }
913            AnimState::Completed { was_reversing, .. } => {
914                if self.auto_reverse && !*was_reversing {
915                    self.reverse_mut();
916                } else {
917                    self.state = AnimState::Stopped;
918                }
919            }
920        }
921    }
922
923    pub(crate) fn transition(&mut self, command: AnimStateCommand) {
924        match command {
925            AnimStateCommand::Pause => {
926                self.state = AnimState::Paused {
927                    elapsed: self.elapsed(),
928                }
929            }
930            AnimStateCommand::Resume => {
931                if let AnimState::Paused { elapsed } = &self.state {
932                    self.state = AnimState::PassInProgress {
933                        started_on: Instant::now(),
934                        elapsed: elapsed.unwrap_or(Duration::ZERO),
935                    }
936                }
937            }
938            AnimStateCommand::Start => {
939                self.reverse_once.set(false);
940                Rc::make_mut(&mut self.folded_style.map).clear();
941                self.repeat_count = 0;
942                self.state = AnimState::PassInProgress {
943                    started_on: Instant::now(),
944                    elapsed: Duration::ZERO,
945                }
946            }
947            AnimStateCommand::Reverse => {
948                self.reverse_once.set(true);
949                Rc::make_mut(&mut self.folded_style.map).clear();
950                self.repeat_count = 0;
951                self.state = AnimState::PassInProgress {
952                    started_on: Instant::now(),
953                    elapsed: Duration::ZERO,
954                }
955            }
956            AnimStateCommand::Stop => {
957                self.repeat_count = 0;
958                self.state = AnimState::Stopped;
959            }
960        }
961    }
962
963    /// Get the total time the animation has been running as a percent (0. - 1.)
964    pub(crate) fn total_time_percent(&self) -> f64 {
965        if self.duration == Duration::ZERO {
966            return 0.;
967        }
968        let mut elapsed = self.elapsed().unwrap_or(Duration::ZERO);
969        if self.use_delay() {
970            elapsed = elapsed.saturating_sub(self.delay);
971        }
972        let percent = elapsed.as_secs_f64() / self.duration.as_secs_f64();
973
974        if self.reverse_once.is_rev() {
975            1. - percent
976        } else {
977            percent
978        }
979    }
980
981    fn is_reversing(&self) -> bool {
982        self.reverse_once.is_rev()
983    }
984
985    fn use_delay(&self) -> bool {
986        // going forward or if we are still supposed to delay on reverse
987        !self.is_reversing() || self.delay_on_reverse
988    }
989
990    /// Get the lower and upper keyframe ids from the cache for a prop and then resolve those id's into a pair of `KeyFrameProp`s that contain the prop value and easing function
991    pub(crate) fn get_current_kf_props(
992        &self,
993        prop: StylePropRef,
994        frame_target: u16,
995        computed_style: &Style,
996    ) -> Option<(KeyFrameProp, KeyFrameProp)> {
997        let PropFrames {
998            lower_idx,
999            upper_idx,
1000        } = self.cache.get_prop_frames(prop, frame_target)?;
1001
1002        let mut upper_computed = false;
1003
1004        let upper = {
1005            let upper = upper_idx?;
1006            let frame = self
1007                .key_frames
1008                .get(&upper.inner())
1009                .expect("If the value is in the cache, it should also be in the key frames");
1010
1011            let prop = match &frame.style {
1012                KeyFrameStyle::Computed => {
1013                    debug_assert!(
1014                        matches!(upper, PropFrameKind::Computed(_)),
1015                        "computed frame should have come from matching computed idx"
1016                    );
1017                    upper_computed = true;
1018                    computed_style
1019                        .map
1020                        .get(&prop.key)
1021                        .expect("was in the cache as a computed frame")
1022                        .clone()
1023                }
1024                KeyFrameStyle::Style(s) => s.map.get(&prop.key).expect("same as above").clone(),
1025            };
1026
1027            KeyFrameProp {
1028                id: upper.inner(),
1029                val: prop,
1030                easing: frame.easing.clone(),
1031            }
1032        };
1033
1034        let lower = {
1035            let lower = lower_idx?;
1036            let frame = self
1037                .key_frames
1038                .get(&lower.inner())
1039                .expect("If the value is in the cache, it should also be in the key frames");
1040
1041            let prop = match &frame.style {
1042                KeyFrameStyle::Computed => {
1043                    debug_assert!(
1044                        matches!(lower, PropFrameKind::Computed(_)),
1045                        "computed frame should have come from matching computed idx"
1046                    );
1047                    if upper_computed {
1048                        // both computed. nothing to animate
1049                        return None;
1050                    }
1051                    computed_style
1052                        .map
1053                        .get(&prop.key)
1054                        .expect("was in the cache as a computed frame")
1055                        .clone()
1056                }
1057                KeyFrameStyle::Style(s) => s.map.get(&prop.key).expect("same as above").clone(),
1058            };
1059
1060            KeyFrameProp {
1061                id: lower.inner(),
1062                val: prop,
1063                easing: frame.easing.clone(),
1064            }
1065        };
1066
1067        if self.is_reversing() {
1068            Some((upper, lower))
1069        } else {
1070            Some((lower, upper))
1071        }
1072    }
1073
1074    /// While advancing, this function can mutably apply it's animated props to a style.
1075    pub fn animate_into(&mut self, computed_style: &mut Style) {
1076        // TODO: OPTIMIZE. I've tried to make this efficient, but it would be good to work this over for eficiency because it is called on every frame during an animation.
1077        // Some work is repeated and could be improved.
1078
1079        let computed_idxs = self.cache.computed_idxs.clone();
1080        for computed_idx in &computed_idxs {
1081            // we add all of the props from the computed style to the cache because the computed style could change inbetween every frame.
1082            for prop in computed_style.style_props() {
1083                self.cache
1084                    .insert_computed_prop(prop, PropFrameKind::Computed(*computed_idx));
1085            }
1086        }
1087        let local_percents: Vec<_> = self
1088            .props_in_ext_progress
1089            .iter()
1090            .map(|(p, (l, u))| (*p, self.get_local_percent(l.id, u.id)))
1091            .collect();
1092
1093        self.props_in_ext_progress.retain(|p, (_l, u)| {
1094            let local_percent = local_percents
1095                .iter()
1096                .find(|&&(prop, _)| prop == *p)
1097                .map(|&(_, percent)| percent)
1098                .unwrap_or_default();
1099            !u.easing.finished(local_percent)
1100        });
1101        for (ext_prop, (l, u)) in &self.props_in_ext_progress {
1102            let local_percent = local_percents
1103                .iter()
1104                .find(|&&(prop, _)| prop == *ext_prop)
1105                .map(|&(_, percent)| percent)
1106                .unwrap_or_default();
1107
1108            let eased_time = u.easing.eval(local_percent);
1109            if let Some(interpolated) =
1110                (ext_prop.info().interpolate)(&*l.val.clone(), &*u.val.clone(), eased_time)
1111            {
1112                Rc::make_mut(&mut self.folded_style.map).insert(ext_prop.key, interpolated);
1113            }
1114        }
1115
1116        let percent = self.total_time_percent();
1117        let frame_target = (self.max_key_frame_num as f64 * percent).round() as u16;
1118
1119        let props = self.cache.prop_map.keys();
1120
1121        for prop in props {
1122            if self.props_in_ext_progress.contains_key(prop) {
1123                continue;
1124            }
1125            let Some((prev, target)) =
1126                self.get_current_kf_props(*prop, frame_target, computed_style)
1127            else {
1128                continue;
1129            };
1130            let local_percent = self.get_local_percent(prev.id, target.id);
1131            let easing = target.easing.clone();
1132            // TODO: Find a better way to find when an animation should enter ext mode rather than just starting to check after 97%.
1133            // this could miss getting a prop into ext mode
1134            if (local_percent > 0.97) && !easing.finished(local_percent) {
1135                self.props_in_ext_progress
1136                    .insert(*prop, (prev.clone(), target.clone()));
1137            } else {
1138                self.props_in_ext_progress.remove(prop);
1139            }
1140            let eased_time = easing.eval(local_percent);
1141            if let Some(interpolated) =
1142                (prop.info().interpolate)(&*prev.val.clone(), &*target.val.clone(), eased_time)
1143            {
1144                Rc::make_mut(&mut self.folded_style.map).insert(prop.key, interpolated);
1145            }
1146        }
1147
1148        computed_style.apply_mut(&self.folded_style);
1149
1150        // we remove all of the props in the computed style from the cache because the computed style could change inbetween every frame.
1151        for computed_idx in computed_idxs {
1152            for prop in computed_style.style_props() {
1153                self.cache.remove_prop(prop, computed_idx);
1154            }
1155        }
1156    }
1157
1158    /// For a given pair of frame ids, find where the full animation progress is within the subrange of the frame id pair.
1159    pub(crate) fn get_local_percent(&self, prev_frame: u16, target_frame: u16) -> f64 {
1160        // undo the frame change that get current key_frame props does so that low is actually lower
1161        let (low_frame, high_frame) = if self.is_reversing() {
1162            (target_frame as f64, prev_frame as f64)
1163        } else {
1164            (prev_frame as f64, target_frame as f64)
1165        };
1166        let total_num_frames = self.max_key_frame_num as f64;
1167        let low_frame_percent = low_frame / total_num_frames;
1168        let high_frame_percent = high_frame / total_num_frames;
1169        let keyframe_range = (high_frame_percent.max(0.001) - low_frame_percent.max(0.001)).abs();
1170        let total_time_percent = self.total_time_percent();
1171        let local = (total_time_percent - low_frame_percent) / keyframe_range;
1172
1173        if self.is_reversing() {
1174            1. - local
1175        } else {
1176            local
1177        }
1178    }
1179
1180    /// returns `true` if the animation is in the idle state
1181    pub fn is_idle(&self) -> bool {
1182        self.state_kind() == AnimStateKind::Idle
1183    }
1184
1185    /// returns `true` if the animation is in the pass in progress state
1186    pub fn is_in_progress(&self) -> bool {
1187        self.state_kind() == AnimStateKind::PassInProgress
1188    }
1189
1190    /// returns `true` if the animation is in the completed state
1191    pub fn is_completed(&self) -> bool {
1192        self.state_kind() == AnimStateKind::Completed
1193    }
1194
1195    /// returns `true` if the animation is in the stopped state
1196    pub fn is_stopped(&self) -> bool {
1197        self.state_kind() == AnimStateKind::Stopped
1198    }
1199
1200    /// returns true if the animation can advance, which either means the animation will transition states, or properties can be animated and updated
1201    pub const fn can_advance(&self) -> bool {
1202        match self.state_kind() {
1203            AnimStateKind::PassFinished
1204            | AnimStateKind::PassInProgress
1205            | AnimStateKind::Idle
1206            | AnimStateKind::Completed => true,
1207            AnimStateKind::Paused | AnimStateKind::Stopped => false,
1208        }
1209    }
1210
1211    /// returns true if the animation should auto reverse
1212    pub const fn is_auto_reverse(&self) -> bool {
1213        self.auto_reverse
1214    }
1215
1216    /// Returns true if the internal folded style of the animation should be applied.
1217    ///
1218    /// This is used when the animation cannot advance but the folded style should still be applied.
1219    /// For example, when the animation is paused or when `apply_when_finished` is set.
1220    pub fn should_apply_folded(&self) -> bool {
1221        self.apply_when_finished
1222            || match self.state_kind() {
1223                AnimStateKind::Paused => true,
1224                AnimStateKind::Idle
1225                | AnimStateKind::Stopped
1226                | AnimStateKind::PassInProgress
1227                | AnimStateKind::PassFinished
1228                | AnimStateKind::Completed => false,
1229            }
1230    }
1231
1232    /// Apply the folded (last computed) style values to the given computed style.
1233    ///
1234    /// This is used when the animation is paused or completed but should still
1235    /// apply its last interpolated values.
1236    pub fn apply_folded(&self, computed_style: &mut Style) {
1237        computed_style.apply_mut(&self.folded_style);
1238    }
1239}