Skip to main content

ferroui_base/animation/
animation_instance.rs

1use crate::animation::animators::{interpolation_handler, Animator};
2use crate::animation::easings::Easing;
3use crate::animation::{
4    Animatable, Animation, Clock, FillMode, IClock, IterationType, PlayState, PlaybackDirection, TimeSpan,
5};
6use crate::data::BindingPriority;
7use crate::reactive::{SingleSubscriberObservableBase, IDisposable, IObservable, IObserver, ObservableError};
8use crate::{Ref, Visual, WeakRef};
9use std::cell::{Cell, RefCell};
10use std::fmt;
11use std::rc::{Rc, Weak};
12
13const PRECISION_IN_TICKS: i64 = 10_000;
14
15/// An error that ends a running animation: an animation setting that is
16/// invalid at the time of a frame.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct AnimationError(pub String);
19
20impl fmt::Display for AnimationError {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        f.write_str(&self.0)
23    }
24}
25
26impl std::error::Error for AnimationError {}
27
28/// Handles interpolation and time-related functions for keyframe
29/// animations: one run of one animator of an animation on one control. It
30/// is the observable that is bound to the animated property.
31pub(crate) struct AnimationInstance<A: Animator> {
32    this: Weak<AnimationInstance<A>>,
33    core: SingleSubscriberObservableBase<A::Value>,
34    animator: Rc<A>,
35    animation: Ref<Animation>,
36    // Weak: the control's value store owns the binding that owns this
37    // instance.
38    target_control: WeakRef<Animatable>,
39    visual_target: Option<WeakRef<Visual>>,
40    on_complete_action: Option<Rc<dyn Fn()>>,
41    timer_sub: RefCell<Option<Rc<dyn IDisposable>>>,
42    property_changed_sub: RefCell<Option<Rc<dyn IDisposable>>>,
43    visibility_changed_sub: RefCell<Option<Rc<dyn IDisposable>>>,
44    detached_sub: RefCell<Option<Rc<dyn IDisposable>>>,
45    base_clock: Rc<dyn IClock>,
46    clock: RefCell<Option<Rc<Clock>>>,
47    ease_func: RefCell<Easing>,
48    neutral_value: RefCell<A::Value>,
49    fill_mode: Cell<FillMode>,
50    is_first_frame: Cell<bool>,
51    is_in_first_initial_delay: Cell<bool>,
52    last_interp_value: RefCell<A::Value>,
53    initial_kf_value: RefCell<A::Value>,
54    iteration_count: Cell<Option<i64>>,
55    initial_delay: Cell<TimeSpan>,
56    iteration_delay: Cell<TimeSpan>,
57    duration: Cell<TimeSpan>,
58    time_prev: Cell<TimeSpan>,
59    anim_time_prev: Cell<f64>,
60    playback_direction: Cell<PlaybackDirection>,
61    playback_direction_prev: Cell<PlaybackDirection>,
62    speed_ratio: Cell<f64>,
63    speed_ratio_prev: Cell<f64>,
64    time_moves_backwards: Cell<bool>,
65    time_of_last_change: Cell<TimeSpan>,
66    anim_time_of_last_change: Cell<f64>,
67    should_pause_on_invisible: bool,
68}
69
70/// Holds the instance strongly: a running animation stays alive until it
71/// completes or is disposed, whether or not its subscription is kept.
72struct StepObserver<A: Animator>(Rc<AnimationInstance<A>>);
73
74impl<A: Animator> IObserver<TimeSpan> for StepObserver<A> {
75    fn on_next(&self, value: TimeSpan) {
76        self.0.step(value);
77    }
78}
79
80impl<A: Animator> AnimationInstance<A> {
81    /// Creates the instance. Panics when a setting of the animation is
82    /// invalid.
83    pub(crate) fn new(
84        animation: Ref<Animation>,
85        control: &Ref<Animatable>,
86        animator: Rc<A>,
87        base_clock: Rc<dyn IClock>,
88        on_complete: Option<Rc<dyn Fn()>>,
89        should_pause_on_invisible: bool,
90        visual_target: Option<&Ref<Visual>>,
91    ) -> Rc<Self> {
92        let ease_func = animation.easing();
93        let instance = Rc::new_cyclic(|this| Self {
94            this: this.clone(),
95            core: SingleSubscriberObservableBase::new(),
96            animator,
97            animation,
98            target_control: control.downgrade(),
99            visual_target: visual_target.map(Ref::downgrade),
100            on_complete_action: on_complete,
101            timer_sub: RefCell::new(None),
102            property_changed_sub: RefCell::new(None),
103            visibility_changed_sub: RefCell::new(None),
104            detached_sub: RefCell::new(None),
105            base_clock,
106            clock: RefCell::new(None),
107            ease_func: RefCell::new(ease_func),
108            neutral_value: RefCell::new(A::Value::default()),
109            fill_mode: Cell::new(FillMode::None),
110            is_first_frame: Cell::new(true),
111            is_in_first_initial_delay: Cell::new(true),
112            last_interp_value: RefCell::new(A::Value::default()),
113            initial_kf_value: RefCell::new(A::Value::default()),
114            iteration_count: Cell::new(None),
115            initial_delay: Cell::new(TimeSpan::ZERO),
116            iteration_delay: Cell::new(TimeSpan::ZERO),
117            duration: Cell::new(TimeSpan::ZERO),
118            time_prev: Cell::new(TimeSpan::ZERO),
119            anim_time_prev: Cell::new(0.0),
120            playback_direction: Cell::new(PlaybackDirection::Normal),
121            playback_direction_prev: Cell::new(PlaybackDirection::Normal),
122            speed_ratio: Cell::new(1.0),
123            speed_ratio_prev: Cell::new(1.0),
124            time_moves_backwards: Cell::new(false),
125            time_of_last_change: Cell::new(TimeSpan::ZERO),
126            anim_time_of_last_change: Cell::new(0.0),
127            should_pause_on_invisible,
128        });
129        if let Err(error) = instance.fetch_properties() {
130            panic!("{error}");
131        }
132        instance
133    }
134
135    fn fetch_properties(&self) -> Result<(), AnimationError> {
136        let animation = &self.animation;
137        *self.ease_func.borrow_mut() = animation.easing();
138
139        self.speed_ratio_prev.set(self.speed_ratio.get());
140        self.speed_ratio.set(animation.speed_ratio());
141
142        if self.speed_ratio.get() < 0.0 {
143            return Err(AnimationError("SpeedRatio value cannot be negative.".to_string()));
144        }
145
146        self.initial_delay.set(animation.delay());
147
148        if self.initial_delay.get() < TimeSpan::ZERO {
149            return Err(AnimationError("Delay value cannot be negative.".to_string()));
150        }
151
152        self.duration.set(animation.duration());
153
154        if self.duration.get() < TimeSpan::ZERO {
155            return Err(AnimationError("Duration value cannot be negative.".to_string()));
156        }
157
158        self.iteration_delay.set(animation.delay_between_iterations());
159
160        if self.iteration_delay.get() < TimeSpan::ZERO {
161            return Err(AnimationError("DelayBetweenIterations value cannot be negative.".to_string()));
162        }
163
164        let iteration_count = animation.iteration_count();
165        if iteration_count.repeat_type() == IterationType::Many {
166            if iteration_count.value() > i64::MAX as u64 {
167                return Err(AnimationError(
168                    "IterationCount value cannot be larger than long.MaxValue.".to_string(),
169                ));
170            }
171            self.iteration_count.set(Some(iteration_count.value() as i64));
172        } else {
173            self.iteration_count.set(None);
174        }
175
176        self.playback_direction_prev.set(self.playback_direction.get());
177        self.playback_direction.set(animation.playback_direction());
178        self.fill_mode.set(animation.fill_mode());
179        Ok(())
180    }
181
182    fn unsubscribed(&self) {
183        // Guard against reentrancy: `do_complete` can trigger `unsubscribed`
184        // via the completion action's disposal chain, and then the completion
185        // itself calls it again.
186        let timer_sub = self.timer_sub.borrow_mut().take();
187        let Some(timer_sub) = timer_sub else { return };
188
189        // Animation may have been stopped before it has finished.
190        if self.can_apply_final_fill() {
191            let value = self.last_interp_value.borrow().clone();
192            self.apply_final_fill(value);
193        }
194
195        let property_changed_sub = self.property_changed_sub.borrow_mut().take();
196        if let Some(subscription) = property_changed_sub {
197            subscription.dispose();
198        }
199        timer_sub.dispose();
200
201        let visibility_changed_sub = self.visibility_changed_sub.borrow_mut().take();
202        if let Some(subscription) = visibility_changed_sub {
203            subscription.dispose();
204        }
205        let detached_sub = self.detached_sub.borrow_mut().take();
206        if let Some(subscription) = detached_sub {
207            subscription.dispose();
208        }
209
210        let clock = self.clock.borrow().clone();
211        if let Some(clock) = clock {
212            clock.set_play_state(PlayState::Stop);
213        }
214    }
215
216    fn subscribed(&self) {
217        let clock = Clock::with_parent(&self.base_clock);
218        *self.clock.borrow_mut() = Some(clock.clone());
219        let Some(this) = self.this.upgrade() else { return };
220        let timer_sub = clock.subscribe(Rc::new(StepObserver(this)));
221        *self.timer_sub.borrow_mut() = Some(timer_sub);
222
223        if let Some(visual) = self.visual_target.as_ref().and_then(WeakRef::upgrade) {
224            if self.should_pause_on_invisible {
225                let weak_clock = Rc::downgrade(&clock);
226                let weak_visual = visual.downgrade();
227                let subscription = visual.is_effectively_visible_changed(move || {
228                    let (Some(clock), Some(visual)) = (weak_clock.upgrade(), weak_visual.upgrade()) else { return };
229                    if clock.play_state() == PlayState::Stop {
230                        return;
231                    }
232                    if visual.is_effectively_visible() {
233                        if clock.play_state() == PlayState::Pause {
234                            clock.set_play_state(PlayState::Run);
235                        }
236                    } else if clock.play_state() == PlayState::Run {
237                        clock.set_play_state(PlayState::Pause);
238                    }
239                });
240                *self.visibility_changed_sub.borrow_mut() = Some(subscription);
241
242                // If already invisible when animation starts, pause immediately.
243                if !visual.is_effectively_visible() {
244                    clock.set_play_state(PlayState::Pause);
245                }
246            }
247
248            // Stop and dispose the animation when detached from the visual tree.
249            let this = self.this.clone();
250            let subscription = visual.detached_from_visual_tree(move |_| {
251                if let Some(this) = this.upgrade() {
252                    this.do_complete(false);
253                }
254            });
255            *self.detached_sub.borrow_mut() = Some(subscription);
256        }
257
258        if let Some(control) = self.target_control.upgrade() {
259            let this = self.this.clone();
260            let property = self.animator.base().property();
261            let subscription = control.property_changed(move |e| {
262                if Some(e.property()) == property && e.priority() > BindingPriority::Animation {
263                    if let Some(this) = this.upgrade() {
264                        this.update_neutral_value();
265                    }
266                }
267            });
268            *self.property_changed_sub.borrow_mut() = Some(subscription);
269        }
270        self.update_neutral_value();
271    }
272
273    pub(crate) fn step(&self, frame_tick: TimeSpan) {
274        let paused = self.clock.borrow().as_ref().is_some_and(|clock| clock.play_state() == PlayState::Pause);
275        if paused {
276            return;
277        }
278
279        // The animated object is gone without the animation having been
280        // disposed: end it, which releases the clock.
281        if self.target_control.upgrade().is_none() {
282            self.do_complete(true);
283            return;
284        }
285
286        if let Err(error) = self.internal_step(frame_tick) {
287            let error: ObservableError = Rc::new(error);
288            self.core.publish_error(error, || self.unsubscribed());
289        }
290    }
291
292    fn can_apply_final_fill(&self) -> bool {
293        matches!(self.fill_mode.get(), FillMode::Forward | FillMode::Both)
294    }
295
296    fn apply_final_fill(&self, interpolated_value: A::Value) {
297        let property = self.animator.base().styled_property::<A::Value>();
298        if let Some(control) = self.target_control.upgrade() {
299            control.set_value(property, interpolated_value);
300        }
301    }
302
303    fn do_complete(&self, stop_as_is: bool) {
304        if !stop_as_is {
305            // Update the last value because the completion might perform the
306            // final fill, and the filled value has to be snapped to the last
307            // value that the animation would reach if it was left running in
308            // its current state.
309            let is_iteration_reversed = Self::is_iteration_reversed(
310                self.is_alternating_playback_direction(),
311                self.iteration_count.get().map(|count| count + 1),
312            );
313            let value = self.find_edge_value(self.is_anim_time_going_backwards(), is_iteration_reversed);
314            *self.last_interp_value.borrow_mut() = value;
315        }
316
317        if let Some(on_complete) = &self.on_complete_action {
318            on_complete();
319        }
320        self.core.publish_completed(|| self.unsubscribed());
321    }
322
323    fn do_initial_delay(&self) {
324        if self.is_in_first_initial_delay.get() && !matches!(self.fill_mode.get(), FillMode::Backward | FillMode::Both)
325        {
326            return;
327        }
328
329        let value = self.initial_kf_value.borrow().clone();
330        self.core.publish_next(value);
331    }
332
333    fn do_iteration_delay(&self, is_iteration_reversed: bool) {
334        let value = self.find_edge_value(false, is_iteration_reversed);
335        *self.last_interp_value.borrow_mut() = value.clone();
336        self.core.publish_next(value);
337    }
338
339    fn do_play_states(&self) {
340        let clock_stopped = self.clock.borrow().as_ref().is_some_and(|clock| clock.play_state() == PlayState::Stop);
341        if clock_stopped || self.base_clock.play_state() == PlayState::Stop {
342            self.do_complete(true);
343        }
344
345        if self.is_first_frame.get() {
346            // In first frame of animation we determine the expected direction of time.
347            let direction = self.animation.playback_direction();
348            self.time_moves_backwards
349                .set(matches!(direction, PlaybackDirection::Reverse | PlaybackDirection::AlternateReverse));
350
351            let base = self.animator.base();
352            let count = base.count();
353            assert!(count != 0, "Sequence contains no elements");
354            let initial_key_frame = if self.time_moves_backwards.get() { base.get(count - 1) } else { base.get(0) };
355            let initial_value = initial_key_frame
356                .with_value(|value| value.and_then(|value| self.animator.typed_value(value)))
357                .unwrap_or_else(|| self.neutral_value.borrow().clone());
358            *self.initial_kf_value.borrow_mut() = initial_value;
359
360            self.is_first_frame.set(false);
361        }
362    }
363
364    fn is_iteration_reversed(is_alternating_playback_direction: bool, iteration_index: Option<i64>) -> bool {
365        if is_alternating_playback_direction {
366            // An unknown index (an infinite animation) compares unequal to
367            // zero.
368            iteration_index.is_none_or(|index| (index & 1) != 0)
369        } else {
370            false
371        }
372    }
373
374    fn find_edge_value(&self, is_anim_time_going_backwards: bool, is_iteration_reversed: bool) -> A::Value {
375        let final_ease_value = if is_anim_time_going_backwards ^ is_iteration_reversed { 0.0 } else { 1.0 };
376        self.interpolate(final_ease_value)
377    }
378
379    /// Eases `normalized` and interpolates the key frames at the result.
380    fn interpolate(&self, normalized: f64) -> A::Value {
381        let ease_func = self.ease_func.borrow().clone();
382        let eased_time = ease_func.ease(normalized);
383        let neutral_value = self.neutral_value.borrow().clone();
384        interpolation_handler(&*self.animator, eased_time, &neutral_value)
385    }
386
387    fn is_anim_time_going_backwards(&self) -> bool {
388        matches!(self.playback_direction.get(), PlaybackDirection::Reverse | PlaybackDirection::AlternateReverse)
389    }
390
391    fn is_alternating_playback_direction(&self) -> bool {
392        matches!(self.playback_direction.get(), PlaybackDirection::Alternate | PlaybackDirection::AlternateReverse)
393    }
394
395    /// Handles all possible cases of applying the initial delay and
396    /// clamping. Returns whether the time is inside the delay.
397    fn apply_initial_delay(&self, anim_time: &mut i64) -> bool {
398        let delay = self.initial_delay.get().ticks() / PRECISION_IN_TICKS;
399        if self.iteration_count.get().is_some() {
400            *anim_time -= delay;
401            if *anim_time <= 0 {
402                if *anim_time < -delay {
403                    *anim_time = -delay;
404                }
405                return true;
406            }
407        } else {
408            // Determine the interval of initial delay.
409            let low = -delay;
410            let high = 0;
411
412            // Handle all 3 location cases based on above interval.
413            if *anim_time < low {
414                *anim_time += delay;
415                if *anim_time > 0 {
416                    *anim_time = 0;
417                    return true;
418                }
419            } else if *anim_time > high {
420                *anim_time -= delay;
421                if *anim_time < 0 {
422                    *anim_time = 0;
423                    return true;
424                }
425            } else {
426                *anim_time = 0;
427                return true;
428            }
429        }
430        false
431    }
432
433    fn apply_limited_clamp(&self, anim_time: &mut f64) {
434        if self.time_moves_backwards.get() {
435            if *anim_time > 0.0 {
436                *anim_time = 0.0;
437            }
438        } else if *anim_time < 0.0 {
439            *anim_time = 0.0;
440        }
441    }
442
443    fn internal_step(&self, time: TimeSpan) -> Result<(), AnimationError> {
444        self.do_play_states();
445
446        self.fetch_properties()?;
447
448        if self.speed_ratio.get() != self.speed_ratio_prev.get()
449            || self.playback_direction.get() != self.playback_direction_prev.get()
450        {
451            // Remember the time when speed changed.
452            // All we can know is that it changed some time between current and prev frame.
453            // We assume that this happened exactly at prev frame.
454            self.time_of_last_change.set(self.time_prev.get());
455            self.anim_time_of_last_change.set(self.anim_time_prev.get());
456        }
457
458        let is_anim_time_going_backwards = self.is_anim_time_going_backwards();
459
460        // Combine SpeedRatio and PlaybackDirection into a single signed speed ratio.
461        let speed_ratio = if is_anim_time_going_backwards { -self.speed_ratio.get() } else { self.speed_ratio.get() };
462
463        // Calculate animation time. That's time that has passed inside
464        // the animation since its beginning.
465        let time_since_last_change = time - self.time_of_last_change.get();
466        let anim_time_since_last_change =
467            time_since_last_change.ticks() as f64 * speed_ratio / PRECISION_IN_TICKS as f64;
468        let mut anim_time_exact = self.anim_time_of_last_change.get() + anim_time_since_last_change;
469
470        let iteration_count = self.iteration_count.get();
471        if iteration_count.is_some() {
472            // Make sure animation time is inside a valid interval.
473            self.apply_limited_clamp(&mut anim_time_exact);
474        }
475
476        self.time_prev.set(time);
477        self.anim_time_prev.set(anim_time_exact);
478        let anim_time = anim_time_exact as i64;
479
480        // Get animation time oriented in the direction of time. Animation running in
481        // same direction as it was on first frame will always increase this variable.
482        let mut anim_time_positive = if self.time_moves_backwards.get() { -anim_time } else { anim_time };
483
484        if self.initial_delay.get() > TimeSpan::ZERO {
485            let is_currently_inside_delay = self.apply_initial_delay(&mut anim_time_positive);
486            if is_currently_inside_delay {
487                self.do_initial_delay();
488                return Ok(());
489            }
490            self.is_in_first_initial_delay.set(false);
491        }
492
493        let iter_duration = self.duration.get().ticks() / PRECISION_IN_TICKS;
494        let iter_delay = self.iteration_delay.get().ticks() / PRECISION_IN_TICKS;
495        let iter_duration_total = iter_duration + iter_delay;
496
497        // An iteration with no duration can't be interpolated, no matter how long the
498        // delay between iterations is: end the animation and snap to its final value.
499        if iter_duration <= 0 {
500            self.do_complete(false);
501            return Ok(());
502        }
503
504        // Calculate current iteration info.
505        let mut iter_index = anim_time_positive / iter_duration_total;
506        let mut iter_time = anim_time_positive % iter_duration_total;
507
508        let mut playback_reversed = anim_time_positive < 0;
509
510        if playback_reversed {
511            // Animation time is behind the starting point of animation.
512
513            // First negative iteration has index -1, first positive iteration has index 0.
514            iter_index -= 1;
515
516            // Move iteration delay to the front of iteration, which is (when moving
517            // backwards through animation time) effectively at the end of iteration.
518            iter_time = -iter_time;
519        }
520
521        playback_reversed ^= self.time_moves_backwards.get()
522            ^ Self::is_iteration_reversed(self.is_alternating_playback_direction(), Some(iter_index));
523
524        let iters_until_end = iteration_count.map(|count| count - iter_index);
525
526        // End animation when limit is reached.
527        if iters_until_end.is_some_and(|iters| iters <= 0) {
528            self.do_complete(false);
529            return Ok(());
530        }
531
532        if iter_time > iter_duration && iter_time <= iter_duration_total && iter_delay > 0 {
533            // The last iteration's trailing delay should be skipped.
534            if iters_until_end.is_some_and(|iters| iters <= 1) {
535                self.do_complete(false);
536                return Ok(());
537            }
538
539            self.do_iteration_delay(playback_reversed);
540        } else if iter_time <= iter_duration {
541            // Ease and interpolate.
542            let mut normalized = iter_time as f64 / iter_duration as f64;
543            if playback_reversed {
544                normalized = 1.0 - normalized;
545            }
546
547            let value = self.interpolate(normalized);
548            *self.last_interp_value.borrow_mut() = value.clone();
549            self.core.publish_next(value);
550        }
551        Ok(())
552    }
553
554    fn update_neutral_value(&self) {
555        let property = self.animator.base().styled_property::<A::Value>();
556        let Some(control) = self.target_control.upgrade() else { return };
557
558        let value = match control.get_base_value(property) {
559            Some(base_value) => base_value,
560            None => control.get_value(property),
561        };
562        *self.neutral_value.borrow_mut() = value;
563    }
564}
565
566impl<A: Animator> IObservable<A::Value> for AnimationInstance<A> {
567    fn subscribe(&self, observer: Rc<dyn IObserver<A::Value>>) -> Rc<dyn IDisposable> {
568        self.core.subscribe(observer, || self.subscribed());
569        self.this.upgrade().expect("the instance is alive while it is being subscribed to")
570    }
571}
572
573impl<A: Animator> IDisposable for AnimationInstance<A> {
574    fn dispose(&self) {
575        self.core.dispose(|| self.unsubscribed());
576    }
577}