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