Skip to main content

ferroui_base/animation/
animation.rs

1use crate::animation::cancellation::register_local;
2use crate::animation::easings::Easing;
3use crate::animation::key_frame::KeyFrameTimingMode;
4use crate::animation::{
5    Animatable, AnimationError, AnimationTask, AnimatorFactory, AnimatorKeyFrame, Cue, FillMode, IAnimator, IClock,
6    IterationCount, KeyFrames, PlaybackBehavior, PlaybackDirection, TimeSpan,
7};
8use crate::data::BindingMode;
9use crate::reactive::{CompositeDisposable, IDisposable, IObservable, Observable};
10use crate::animation::i_page_transition::continuation_context;
11use crate::threading::CancellationToken;
12use crate::{
13    ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, DirectPropertyMetadata, FerroObject,
14    FerroObjectImpl, FerroProperty, Ref, Visual,
15};
16use std::any::TypeId;
17use std::cell::{Cell, RefCell};
18use std::rc::Rc;
19
20/// Tracks the progress of an animation.
21#[repr(C)]
22pub struct Animation {
23    base: FerroObject,
24    duration: Cell<TimeSpan>,
25    iteration_count: Cell<IterationCount>,
26    playback_direction: Cell<PlaybackDirection>,
27    playback_behavior: Cell<PlaybackBehavior>,
28    fill_mode: Cell<FillMode>,
29    easing: RefCell<Easing>,
30    delay: Cell<TimeSpan>,
31    delay_between_iterations: Cell<TimeSpan>,
32    speed_ratio: Cell<f64>,
33    children: KeyFrames,
34}
35
36ferro_class!(Animation: FerroObject);
37crate::ferro_class_info!(Animation { new: Animation::new, interfaces: [Rc<dyn crate::animation::IAnimation>] });
38ferro_impl_classes!(Animation: FerroObjectImpl);
39
40macro_rules! direct_cell_property {
41    ($(#[$meta:meta])* $accessor:ident, $name:literal, $ty:ty, $getter:ident, $setter:ident, $field:ident, $unset:expr) => {
42        ferro_property!(for Animation;
43            $(#[$meta])*
44            pub fn $accessor() -> DirectProperty<Animation, $ty> {
45                FerroProperty::register_direct::<Animation, _>($name, |o| o.$getter(), Some(|o, v| o.$setter(v)), $unset)
46            }
47        );
48
49        pub fn $getter(&self) -> $ty {
50            self.$field.get()
51        }
52
53        pub fn $setter(&self, value: $ty) {
54            self.set_and_raise_cell(Self::$accessor(), &self.$field, value);
55        }
56    };
57}
58
59crate::ferro_properties! {
60    impl Animation, also [
61        Animation::duration_property,
62        Animation::iteration_count_property,
63        Animation::playback_direction_property,
64        Animation::playback_behavior_property,
65        Animation::fill_mode_property,
66        Animation::delay_property,
67        Animation::delay_between_iterations_property,
68        Animation::easing_property,
69        Animation::speed_ratio_property,
70    ] {}
71}
72
73impl Animation {
74    direct_cell_property!(
75        /// Defines the `Duration` property: the active time of one iteration.
76        duration_property, "Duration", TimeSpan, duration, set_duration, duration, TimeSpan::ZERO
77    );
78
79    direct_cell_property!(
80        /// Defines the `IterationCount` property: the repeat count.
81        iteration_count_property, "IterationCount", IterationCount, iteration_count, set_iteration_count,
82        iteration_count, IterationCount::new(1)
83    );
84
85    direct_cell_property!(
86        /// Defines the `PlaybackDirection` property.
87        playback_direction_property, "PlaybackDirection", PlaybackDirection, playback_direction,
88        set_playback_direction, playback_direction, PlaybackDirection::Normal
89    );
90
91    direct_cell_property!(
92        /// Defines the `PlaybackBehavior` property: whether the animation
93        /// pauses when its target is not effectively visible.
94        playback_behavior_property, "PlaybackBehavior", PlaybackBehavior, playback_behavior,
95        set_playback_behavior, playback_behavior, PlaybackBehavior::Auto
96    );
97
98    direct_cell_property!(
99        /// Defines the `FillMode` property: the value fill mode.
100        fill_mode_property, "FillMode", FillMode, fill_mode, set_fill_mode, fill_mode, FillMode::None
101    );
102
103    direct_cell_property!(
104        /// Defines the `Delay` property: the initial delay time.
105        delay_property, "Delay", TimeSpan, delay, set_delay, delay, TimeSpan::ZERO
106    );
107
108    direct_cell_property!(
109        /// Defines the `DelayBetweenIterations` property.
110        delay_between_iterations_property, "DelayBetweenIterations", TimeSpan, delay_between_iterations,
111        set_delay_between_iterations, delay_between_iterations, TimeSpan::ZERO
112    );
113
114    ferro_property!(for Animation;
115        /// Defines the `Easing` property: the easing function to be used.
116        pub fn easing_property() -> DirectProperty<Animation, Easing> {
117            FerroProperty::register_direct::<Animation, _>(
118                "Easing",
119                |o| o.easing(),
120                Some(|o, v| o.set_easing(v)),
121                Easing::default(),
122            )
123        }
124    );
125
126    ferro_property!(for Animation;
127        /// Defines the `SpeedRatio` property: the speed multiple.
128        pub fn speed_ratio_property() -> DirectProperty<Animation, f64> {
129            FerroProperty::register_direct_with::<Animation, _>(
130                "SpeedRatio",
131                |o| o.speed_ratio(),
132                Some(|o, v| o.set_speed_ratio(v)),
133                DirectPropertyMetadata::new(Some(1.0)).with_default_binding_mode(BindingMode::TwoWay),
134            )
135        }
136    );
137
138    /// Creates the class data; see [`FerroObject::construct`].
139    pub fn construct() -> Self {
140        Self {
141            base: FerroObject::construct(),
142            duration: Cell::new(TimeSpan::ZERO),
143            iteration_count: Cell::new(IterationCount::new(1)),
144            playback_direction: Cell::new(PlaybackDirection::Normal),
145            playback_behavior: Cell::new(PlaybackBehavior::Auto),
146            fill_mode: Cell::new(FillMode::None),
147            easing: RefCell::new(Easing::default()),
148            delay: Cell::new(TimeSpan::ZERO),
149            delay_between_iterations: Cell::new(TimeSpan::ZERO),
150            speed_ratio: Cell::new(1.0),
151            children: KeyFrames::new(),
152        }
153    }
154
155    pub fn new() -> Ref<Self> {
156        instantiate(Self::construct())
157    }
158
159    /// The easing function to be used for this animation.
160    pub fn easing(&self) -> Easing {
161        self.easing.borrow().clone()
162    }
163
164    pub fn set_easing(&self, value: impl Into<Easing>) {
165        self.set_and_raise(Self::easing_property(), &self.easing, value.into());
166    }
167
168    /// The speed multiple for this animation.
169    pub fn speed_ratio(&self) -> f64 {
170        self.speed_ratio.get()
171    }
172
173    pub fn set_speed_ratio(&self, value: f64) {
174        self.set_and_raise_cell(Self::speed_ratio_property(), &self.speed_ratio, value);
175    }
176
177    /// The children of the animation: its key frames.
178    pub fn children(&self) -> KeyFrames {
179        self.children.clone()
180    }
181
182    #[allow(clippy::type_complexity)]
183    fn interpret_keyframes(&self, control: &Animatable) -> (Vec<Rc<dyn IAnimator>>, Vec<Rc<dyn IDisposable>>, bool) {
184        let mut handler_list: Vec<((TypeId, &'static FerroProperty), AnimatorFactory)> = Vec::new();
185        let mut animator_key_frames: Vec<Ref<AnimatorKeyFrame>> = Vec::new();
186        let mut subscriptions: Vec<Rc<dyn IDisposable>> = Vec::new();
187        let mut animates_visibility = false;
188
189        for keyframe in self.children.snapshot().iter() {
190            for setter in keyframe.setters().snapshot().iter() {
191                let Some(property) = setter.property() else {
192                    panic!("No Setter property assigned.");
193                };
194
195                if property == Visual::is_visible_property().as_property() {
196                    animates_visibility = true;
197                }
198
199                let handler = Animation::get_animator(setter).or_else(|| Animation::get_animator_type(property));
200
201                let Some((type_, factory)) = handler else {
202                    panic!(
203                        "No animator registered for the property {property}. Add an animator to the Animation.Animators collection that matches this property to animate it."
204                    );
205                };
206
207                if !handler_list.iter().any(|((t, p), _)| *t == type_ && *p == property) {
208                    handler_list.push(((type_, property), factory.clone()));
209                }
210
211                let mut cue = keyframe.cue();
212
213                if keyframe.timing_mode() == KeyFrameTimingMode::TimeSpan {
214                    cue = Cue::new(keyframe.key_time().total_seconds() / self.duration().total_seconds());
215                }
216
217                let new_kf = AnimatorKeyFrame::new(Some(type_), Some(factory), cue, keyframe.key_spline());
218
219                subscriptions.push(new_kf.bind_setter(&**setter, control));
220
221                animator_key_frames.push(new_kf);
222            }
223        }
224
225        animator_key_frames.sort_by(|x, y| x.cue().cue_value().total_cmp(&y.cue().cue_value()));
226
227        let mut new_animator_instances: Vec<Rc<dyn IAnimator>> = Vec::new();
228
229        for ((_, property), factory) in &handler_list {
230            let new_instance = factory();
231            new_instance.set_property(Some(property));
232            new_animator_instances.push(new_instance);
233        }
234
235        let fill_mode = self.fill_mode();
236        for keyframe in animator_key_frames {
237            let animator = new_animator_instances
238                .iter()
239                .find(|a| Some(a.animator_type()) == keyframe.animator_type() && a.property() == keyframe.property())
240                .expect("Sequence contains no matching element");
241
242            if animator.count() == 0 && matches!(fill_mode, FillMode::Backward | FillMode::Both) {
243                keyframe.set_fill_before(true);
244            }
245
246            animator.add(keyframe);
247        }
248
249        if matches!(fill_mode, FillMode::Forward | FillMode::Both) {
250            for new_animator_instance in &new_animator_instances {
251                let count = new_animator_instance.count();
252                if count > 0 {
253                    new_animator_instance.get(count - 1).set_fill_after(true);
254                }
255            }
256        }
257
258        (new_animator_instances, subscriptions, animates_visibility)
259    }
260
261    /// Applies the animation to `control`: it runs while `match_` is true,
262    /// on `clock` (or on the control's clock, or on the global clock).
263    /// `on_complete` runs when the animation ends by itself.
264    /// `is_manually_started` tells an animation started by code from one
265    /// started by a style. Disposing the result removes the animation.
266    pub fn apply(
267        &self,
268        control: &Animatable,
269        clock: Option<Rc<dyn IClock>>,
270        match_: Rc<dyn IObservable<bool>>,
271        on_complete: Option<Rc<dyn Fn()>>,
272        is_manually_started: bool,
273    ) -> Rc<dyn IDisposable> {
274        let (animators, mut subscriptions, animates_visibility) = self.interpret_keyframes(control);
275
276        let should_pause_on_invisible = match self.playback_behavior.get() {
277            PlaybackBehavior::Auto => !(animates_visibility || is_manually_started),
278            PlaybackBehavior::Always => false,
279            PlaybackBehavior::OnlyIfVisible => true,
280        };
281
282        if animators.len() == 1 {
283            let subscription = animators[0].clone().apply(
284                self,
285                control,
286                clock,
287                match_,
288                on_complete,
289                should_pause_on_invisible,
290            );
291
292            if let Some(subscription) = subscription {
293                subscriptions.push(subscription);
294            }
295        } else {
296            // The completion action runs once every animator has completed,
297            // through the synchronization context that is current now.
298            let remaining = Rc::new(Cell::new(animators.len()));
299            let schedule_on_complete = on_complete.map(|on_complete| {
300                let context = continuation_context();
301                Rc::new(move || {
302                    let on_complete = on_complete.clone();
303                    context.post_local(move || on_complete());
304                })
305            });
306
307            for animator in &animators {
308                let animator_on_complete: Option<Rc<dyn Fn()>> = schedule_on_complete.as_ref().map(|schedule| {
309                    let schedule = schedule.clone();
310                    let remaining = remaining.clone();
311                    let completed = Cell::new(false);
312                    let action: Rc<dyn Fn()> = Rc::new(move || {
313                        if completed.replace(true) {
314                            return;
315                        }
316                        remaining.set(remaining.get() - 1);
317                        if remaining.get() == 0 {
318                            schedule();
319                        }
320                    });
321                    action
322                });
323
324                let subscription = animator.clone().apply(
325                    self,
326                    control,
327                    clock.clone(),
328                    match_.clone(),
329                    animator_on_complete,
330                    should_pause_on_invisible,
331                );
332
333                if let Some(subscription) = subscription {
334                    subscriptions.push(subscription);
335                }
336            }
337
338            if animators.is_empty() {
339                if let Some(schedule) = &schedule_on_complete {
340                    schedule();
341                }
342            }
343        }
344
345        Rc::new(CompositeDisposable::from_disposables(subscriptions))
346    }
347
348    /// Starts the animation on `control` and returns a task that completes
349    /// when it has ended or `cancellation_token` was cancelled.
350    pub fn run_async(&self, control: &Animatable, cancellation_token: CancellationToken) -> AnimationTask {
351        self.run_async_with_clock(control, None, cancellation_token)
352    }
353
354    /// [`run_async`](Self::run_async) on an explicit clock.
355    pub fn run_async_with_clock(
356        &self,
357        control: &Animatable,
358        clock: Option<Rc<dyn IClock>>,
359        cancellation_token: CancellationToken,
360    ) -> AnimationTask {
361        if cancellation_token.is_cancellation_requested() {
362            return AnimationTask::completed();
363        }
364
365        let run = AnimationTask::pending();
366
367        if self.iteration_count() == IterationCount::INFINITE {
368            run.try_set(Err(AnimationError("Looping animations must not use the Run method.".to_string())));
369        }
370
371        let subscriptions: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
372        let cancellation = Rc::new(RefCell::new(None));
373
374        let finish: Rc<dyn Fn()> = {
375            let run = run.clone();
376            let subscriptions = subscriptions.clone();
377            let cancellation = cancellation.clone();
378            Rc::new(move || {
379                run.try_set_result();
380                let subscriptions = subscriptions.borrow_mut().take();
381                if let Some(subscriptions) = subscriptions {
382                    subscriptions.dispose();
383                }
384                let cancellation: Option<crate::animation::cancellation::LocalCancellationRegistration> =
385                    cancellation.borrow_mut().take();
386                if let Some(cancellation) = cancellation {
387                    cancellation.dispose();
388                }
389            })
390        };
391
392        let applied = self.apply(control, clock, Observable::return_(true), Some(finish.clone()), true);
393        // The animation may have completed while it was being applied, in
394        // which case there is nothing left to dispose on cancellation.
395        *subscriptions.borrow_mut() = Some(applied);
396
397        let registration = register_local(&cancellation_token, move || finish());
398        if !run.is_completed() || !cancellation_token.is_cancellation_requested() {
399            *cancellation.borrow_mut() = Some(registration);
400        }
401
402        run
403    }
404}