Skip to main content

ferroui_base/animation/animators/
animator.rs

1use crate::animation::{
2    Animatable, Animation, AnimationInstance, AnimatorKeyFrame, Clock, DisposeAnimationInstanceSubject, IAnimator,
3    IClock, KeySpline,
4};
5use crate::data::BindingPriority;
6use crate::reactive::{CompositeDisposable, IDisposable, IObservable};
7use crate::{AnyValue, FerroProperty, PropertyValue, Ref, StyledProperty, Visual};
8use std::any::TypeId;
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12/// The state every animator has: its key frames and its target property.
13#[derive(Default)]
14pub struct AnimatorBase {
15    key_frames: RefCell<Vec<Ref<AnimatorKeyFrame>>>,
16    property: Cell<Option<&'static FerroProperty>>,
17}
18
19impl AnimatorBase {
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// The target property.
25    #[inline]
26    pub fn property(&self) -> Option<&'static FerroProperty> {
27        self.property.get()
28    }
29
30    pub fn set_property(&self, value: Option<&'static FerroProperty>) {
31        self.property.set(value)
32    }
33
34    /// The number of key frames.
35    #[inline]
36    pub fn count(&self) -> usize {
37        self.key_frames.borrow().len()
38    }
39
40    /// The key frame at `index`. Panics when out of range.
41    pub fn get(&self, index: usize) -> Ref<AnimatorKeyFrame> {
42        self.key_frames.borrow()[index].clone()
43    }
44
45    /// A copy of the key frames.
46    pub fn to_vec(&self) -> Vec<Ref<AnimatorKeyFrame>> {
47        self.key_frames.borrow().clone()
48    }
49
50    pub(crate) fn push(&self, item: Ref<AnimatorKeyFrame>) {
51        self.key_frames.borrow_mut().push(item)
52    }
53
54    /// The target property as a styled property of value type `T`. Panics
55    /// when the animator has no property or the property has another type.
56    pub fn styled_property<T: PropertyValue>(&self) -> &'static StyledProperty<T> {
57        let Some(property) = self.property.get() else {
58            panic!("Animator has no property specified.");
59        };
60        match property.as_styled::<T>() {
61            Some(property) => property,
62            None => panic!(
63                "Unable to animate property '{}' of type {} as {}.",
64                property,
65                property.property_type_name(),
66                std::any::type_name::<T>()
67            ),
68        }
69    }
70}
71
72/// Base class for animators: interpolates the key frames of one property of
73/// an animation.
74///
75/// An animator implements [`interpolate`](Self::interpolate) for its value
76/// type; everything else has an implementation that subclasses override
77/// only for special targets (brushes, transforms).
78pub trait Animator: Sized + 'static {
79    /// The type of the values the animator produces, which is the value
80    /// type of the properties it animates.
81    type Value: PropertyValue + Default;
82
83    /// The key frames and the target property.
84    fn base(&self) -> &AnimatorBase;
85
86    /// Interpolates between two values using the specified progress.
87    fn interpolate(&self, progress: f64, old_value: &Self::Value, new_value: &Self::Value) -> Self::Value;
88
89    /// Reads a key frame value; `None` when it is not a value this animator
90    /// can animate, in which case the neutral value is used in its place.
91    #[inline]
92    fn typed_value(&self, untyped: &dyn AnyValue) -> Option<Self::Value> {
93        untyped.downcast_ref::<Self::Value>().cloned()
94    }
95
96    /// Validates a key frame that is being added. Panics to reject it.
97    fn validate(&self, _item: &Ref<AnimatorKeyFrame>) {}
98
99    /// Binds the values a running animation produces to the target property
100    /// of the control, at animation priority.
101    fn bind_animation(&self, control: &Animatable, instance: Rc<dyn IObservable<Self::Value>>) -> Rc<dyn IDisposable> {
102        control.bind(self.base().styled_property::<Self::Value>(), instance, BindingPriority::Animation)
103    }
104
105    /// Applies the animation to `control`: it runs while `match_` is true.
106    fn apply(
107        this: &Rc<Self>,
108        animation: &Animation,
109        control: &Animatable,
110        clock: Option<Rc<dyn IClock>>,
111        match_: Rc<dyn IObservable<bool>>,
112        on_complete: Option<Rc<dyn Fn()>>,
113        should_pause_on_invisible: bool,
114    ) -> Option<Rc<dyn IDisposable>> {
115        let visual_target = control.downcast_ref::<Visual>().map(Visual::to_ref);
116        Some(apply_with_visual(
117            this,
118            animation,
119            control,
120            clock,
121            match_,
122            on_complete,
123            should_pause_on_invisible,
124            visual_target,
125        ))
126    }
127}
128
129/// Applies an animation to `control`, tying its lifetime and visibility
130/// handling to `visual_target`.
131#[allow(clippy::too_many_arguments)]
132pub(crate) fn apply_with_visual<A: Animator>(
133    animator: &Rc<A>,
134    animation: &Animation,
135    control: &Animatable,
136    clock: Option<Rc<dyn IClock>>,
137    match_: Rc<dyn IObservable<bool>>,
138    on_complete: Option<Rc<dyn Fn()>>,
139    should_pause_on_invisible: bool,
140    visual_target: Option<Ref<Visual>>,
141) -> Rc<dyn IDisposable> {
142    let subject = Rc::new(DisposeAnimationInstanceSubject::new(
143        animator.clone(),
144        animation.to_ref(),
145        control.to_ref(),
146        clock,
147        on_complete,
148        should_pause_on_invisible,
149        visual_target,
150    ));
151    let subscription = match_.subscribe(subject.clone());
152    let subject: Rc<dyn IDisposable> = subject;
153    Rc::new(CompositeDisposable::from_disposables([subscription, subject]))
154}
155
156/// Runs the animation once: creates the instance that produces the values
157/// and binds it to the control.
158pub(crate) fn run<A: Animator>(
159    animator: &Rc<A>,
160    animation: &Ref<Animation>,
161    control: &Ref<Animatable>,
162    clock: Option<Rc<dyn IClock>>,
163    on_complete: Option<Rc<dyn Fn()>>,
164    should_pause_on_invisible: bool,
165    visual_target: Option<&Ref<Visual>>,
166) -> Rc<dyn IDisposable> {
167    let base_clock = clock.or_else(|| control.clock()).unwrap_or_else(Clock::global_clock);
168    let instance = AnimationInstance::new(
169        animation.clone(),
170        control,
171        animator.clone(),
172        base_clock,
173        on_complete,
174        should_pause_on_invisible,
175        visual_target,
176    );
177    animator.bind_animation(control, instance)
178}
179
180struct KeyFrameInfo<T> {
181    time: f64,
182    value: T,
183    key_spline: Option<Ref<KeySpline>>,
184}
185
186impl<T: Clone> KeyFrameInfo<T> {
187    fn from_key_frame<A: Animator<Value = T>>(animator: &A, source: &AnimatorKeyFrame, neutral_value: &T) -> Self {
188        Self {
189            time: source.cue().cue_value(),
190            value: get_typed_value(animator, source, neutral_value),
191            key_spline: source.key_spline().cloned(),
192        }
193    }
194}
195
196#[inline]
197fn get_typed_value<A: Animator>(animator: &A, frame: &AnimatorKeyFrame, neutral_value: &A::Value) -> A::Value {
198    frame
199        .with_value(|value| value.and_then(|value| animator.typed_value(value)))
200        .unwrap_or_else(|| neutral_value.clone())
201}
202
203/// Computes the value of the animation at `animation_time`, the eased
204/// position within an iteration. `neutral_value` stands in for the parts of
205/// the iteration that no key frame covers.
206pub(crate) fn interpolation_handler<A: Animator>(animator: &A, animation_time: f64, neutral_value: &A::Value) -> A::Value {
207    let (from, to) = {
208        let key_frames = animator.base().key_frames.borrow();
209        if key_frames.is_empty() {
210            return neutral_value.clone();
211        }
212        get_key_frames(animator, &key_frames, animation_time, neutral_value)
213    };
214
215    let mut progress = (animation_time - from.time) / (to.time - from.time);
216
217    if let Some(key_spline) = &to.key_spline {
218        progress = key_spline.get_spline_progress(progress);
219    }
220
221    animator.interpolate(progress, &from.value, &to.value)
222}
223
224fn get_key_frames<A: Animator>(
225    animator: &A,
226    key_frames: &[Ref<AnimatorKeyFrame>],
227    time: f64,
228    neutral_value: &A::Value,
229) -> (KeyFrameInfo<A::Value>, KeyFrameInfo<A::Value>) {
230    debug_assert!(!key_frames.is_empty());
231    let count = key_frames.len();
232
233    // Before or right at the first frame which isn't at time 0.0: interpolate between 0.0 and the first frame.
234    let first_frame = &key_frames[0];
235    let first_time = first_frame.cue().cue_value();
236    if time <= first_time && first_time > 0.0 {
237        let before_value = if first_frame.fill_before() {
238            get_typed_value(animator, first_frame, neutral_value)
239        } else {
240            neutral_value.clone()
241        };
242        return (
243            KeyFrameInfo { time: 0.0, value: before_value, key_spline: first_frame.key_spline().cloned() },
244            KeyFrameInfo::from_key_frame(animator, first_frame, neutral_value),
245        );
246    }
247
248    // Between two frames: interpolate between the previous frame and the next frame.
249    for i in 1..count {
250        let frame = &key_frames[i];
251        if time <= frame.cue().cue_value() {
252            return (
253                KeyFrameInfo::from_key_frame(animator, &key_frames[i - 1], neutral_value),
254                KeyFrameInfo::from_key_frame(animator, frame, neutral_value),
255            );
256        }
257    }
258
259    // Past the last frame which is at time 1.0: interpolate between the last two frames.
260    let last_frame = &key_frames[count - 1];
261    if last_frame.cue().cue_value() >= 1.0 {
262        if count == 1 {
263            let before_value = if last_frame.fill_before() {
264                get_typed_value(animator, last_frame, neutral_value)
265            } else {
266                neutral_value.clone()
267            };
268            return (
269                KeyFrameInfo { time: 0.0, value: before_value, key_spline: last_frame.key_spline().cloned() },
270                KeyFrameInfo::from_key_frame(animator, last_frame, neutral_value),
271            );
272        }
273
274        return (
275            KeyFrameInfo::from_key_frame(animator, &key_frames[count - 2], neutral_value),
276            KeyFrameInfo::from_key_frame(animator, last_frame, neutral_value),
277        );
278    }
279
280    // Past the last frame which isn't at time 1.0: interpolate between the last frame and 1.0.
281    let after_value = if last_frame.fill_after() {
282        get_typed_value(animator, last_frame, neutral_value)
283    } else {
284        neutral_value.clone()
285    };
286    (
287        KeyFrameInfo::from_key_frame(animator, last_frame, neutral_value),
288        KeyFrameInfo { time: 1.0, value: after_value, key_spline: last_frame.key_spline().cloned() },
289    )
290}
291
292impl<A: Animator> IAnimator for A {
293    fn property(&self) -> Option<&'static FerroProperty> {
294        self.base().property()
295    }
296
297    fn set_property(&self, value: Option<&'static FerroProperty>) {
298        self.base().set_property(value)
299    }
300
301    fn count(&self) -> usize {
302        self.base().count()
303    }
304
305    fn get(&self, index: usize) -> Ref<AnimatorKeyFrame> {
306        self.base().get(index)
307    }
308
309    fn add(&self, item: Ref<AnimatorKeyFrame>) {
310        self.validate(&item);
311        self.base().push(item)
312    }
313
314    fn animator_type(&self) -> TypeId {
315        TypeId::of::<A>()
316    }
317
318    fn apply(
319        self: Rc<Self>,
320        animation: &Animation,
321        control: &Animatable,
322        clock: Option<Rc<dyn IClock>>,
323        match_: Rc<dyn IObservable<bool>>,
324        on_complete: Option<Rc<dyn Fn()>>,
325        should_pause_on_invisible: bool,
326    ) -> Option<Rc<dyn IDisposable>> {
327        <A as Animator>::apply(&self, animation, control, clock, match_, on_complete, should_pause_on_invisible)
328    }
329}