Skip to main content

ferroui_base/animation/
animatable.rs

1use crate::animation::{Clock, IClock, ITransition, TransitionInstance, Transitions};
2use crate::collections::{CollectionChangedHandler, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
3use crate::data::BindingPriority;
4use crate::reactive::IDisposable;
5use crate::{
6    ferro_class, ferro_property, instantiate, BoxedValue, FerroObject, FerroObjectExtensions, FerroObjectImpl,
7    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledProperty, StyledPropertyOptions,
8    UnsetValueType,
9};
10use std::any::Any;
11use std::cell::{Cell, RefCell};
12use std::rc::Rc;
13
14/// Base class for all animatable objects.
15#[repr(C)]
16pub struct Animatable {
17    base: FerroObject,
18    transitions_enabled: Cell<bool>,
19    is_subscribed_to_transitions_collection: Cell<bool>,
20    transition_state: RefCell<Option<Vec<(Rc<dyn ITransition>, Rc<TransitionState>)>>>,
21    /// The collections whose changes are being listened to, with the tokens
22    /// of the subscriptions.
23    collection_subscriptions: RefCell<Vec<(Transitions, u64)>>,
24    /// The progress of the transition that is being applied right now, as
25    /// reported by the transition while it binds its values.
26    applying_transition_instance: RefCell<Option<Rc<TransitionInstance>>>,
27    /// Per-application context, kept separate from the shared transition definition.
28    /// A scoped guard permits nested transition applications without heap allocation.
29    transition_context: RefCell<TransitionContextState>,
30}
31
32struct TransitionState {
33    instance: RefCell<Option<Rc<dyn IDisposable>>>,
34    /// The progress behind `instance`, when the transition reported one.
35    progress: RefCell<Option<Rc<TransitionInstance>>>,
36    base_value: RefCell<BoxedValue>,
37    interruption_state: RefCell<Option<Rc<dyn Any>>>,
38}
39
40#[derive(Default)]
41struct TransitionContextState {
42    applying: bool,
43    interruption: Option<Rc<dyn Any>>,
44}
45
46/// Restores the surrounding application even if user transition code unwinds.
47struct TransitionContext<'a> {
48    context: &'a RefCell<TransitionContextState>,
49    previous: Option<TransitionContextState>,
50}
51
52impl<'a> TransitionContext<'a> {
53    fn enter(context: &'a RefCell<TransitionContextState>, state: Option<Rc<dyn Any>>) -> Self {
54        let previous = context.replace(TransitionContextState { applying: true, interruption: state });
55        Self { context, previous: Some(previous) }
56    }
57
58    fn finish(mut self) -> Option<Rc<dyn Any>> {
59        self.context.replace(self.previous.take().expect("active transition context")).interruption
60    }
61}
62
63impl Drop for TransitionContext<'_> {
64    fn drop(&mut self) {
65        if let Some(previous) = self.previous.take() {
66            self.context.replace(previous);
67        }
68    }
69}
70
71ferro_class!(Animatable: FerroObject);
72crate::ferro_class_info!(Animatable { new: Animatable::new });
73
74crate::ferro_overrides! { impl FerroObjectImpl for Animatable {
75    /// Starts transitions when the base value of a property with a
76    /// transition changes, and tracks the transitions collection.
77    ///
78    /// Classes deriving from `Animatable` must not override this member;
79    /// override `on_property_changed` instead.
80    fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
81        if change.property() == Self::transitions_property().as_property() && change.is_effective_value_change() {
82            let (old_transitions, new_transitions) = change.get_old_and_new_value::<Option<Transitions>>();
83
84            // When transitions are replaced, we add the new transitions before removing the old
85            // transitions, so that when the old transition being disposed causes the value to
86            // change, there is a corresponding entry in the transition states. This means that we
87            // need to account for any transitions present in both the old and new transitions
88            // collections.
89            if let Some(new_transitions) = &new_transitions {
90                let mut to_add = new_transitions.snapshot();
91
92                if let Some(old_transitions) = &old_transitions {
93                    if new_transitions.count() > 0 && old_transitions.count() > 0 {
94                        to_add = Rc::new(Self::except(&to_add, &old_transitions.snapshot()));
95                    }
96                }
97
98                // Subscribe to collection changes only if transitions are already enabled,
99                // i.e. control is attached to the visual tree
100                if this.transitions_enabled.get() {
101                    this.subscribe_to_collection(new_transitions);
102                    this.is_subscribed_to_transitions_collection.set(true);
103                }
104
105                this.add_transitions(&to_add);
106            }
107
108            if let Some(old_transitions) = &old_transitions {
109                let mut to_remove = old_transitions.snapshot();
110
111                if let Some(new_transitions) = &new_transitions {
112                    if old_transitions.count() > 0 && new_transitions.count() > 0 {
113                        to_remove = Rc::new(Self::except(&to_remove, &new_transitions.snapshot()));
114                    }
115                }
116
117                this.unsubscribe_from_collection(old_transitions);
118                this.remove_transitions(&to_remove);
119            }
120        } else if this.transitions_enabled.get()
121            && !change.property().is_direct()
122            && change.priority() > BindingPriority::Animation
123            && this.transition_state.borrow().is_some()
124        {
125            if let Some(transitions) = this.transitions() {
126                let transitions = transitions.snapshot();
127                for transition in transitions.iter().rev() {
128                    let property = transition.property();
129                    if property != change.property() {
130                        continue;
131                    }
132                    let Some(state) = this.try_get_state(transition) else { continue };
133
134                    let mut old_value = state.base_value.borrow().clone();
135                    let new_value = this.get_animation_base_value(property);
136
137                    if *old_value != *new_value {
138                        *state.base_value.borrow_mut() = new_value.clone();
139
140                        // We need to transition from the current animated value if present,
141                        // instead of the old base value.
142                        let animated_value = this.get_value_untyped(property);
143
144                        if *new_value != *animated_value {
145                            old_value = animated_value;
146                        }
147
148                        let clock = this.clock().unwrap_or_else(Clock::global_clock);
149                        let interruption_state = state.interruption_state.borrow_mut().take();
150                        let instance = state.instance.borrow_mut().take();
151                        *state.progress.borrow_mut() = None;
152                        if let Some(instance) = instance {
153                            instance.dispose();
154                        }
155                        *this.applying_transition_instance.borrow_mut() = None;
156                        let context = TransitionContext::enter(&this.transition_context, interruption_state);
157                        let instance = transition.apply(this, clock, &old_value, &new_value);
158                        *state.interruption_state.borrow_mut() = context.finish();
159                        *state.progress.borrow_mut() = this.applying_transition_instance.borrow_mut().take();
160                        *state.instance.borrow_mut() = Some(instance);
161                        return;
162                    }
163                }
164            }
165        }
166
167        Self::parent_on_property_changed_core(this, change);
168    }
169} }
170
171crate::ferro_properties! { impl Animatable {
172    ferro_property!(
173        /// Defines the `Clock` property.
174        pub fn clock_property() -> StyledProperty<Option<Rc<dyn IClock>>> {
175            FerroProperty::register_with::<Animatable, _>("Clock", StyledPropertyOptions::new(None).inherits(true))
176        }
177    );
178
179    ferro_property!(
180        /// Defines the `Transitions` property.
181        pub fn transitions_property() -> StyledProperty<Option<Transitions>> {
182            FerroProperty::register::<Animatable, _>("Transitions", None)
183        }
184    );
185} }
186
187impl Animatable {
188    /// Creates the class data; see [`FerroObject::construct`].
189    pub fn construct() -> Self {
190        Self {
191            base: FerroObject::construct(),
192            transitions_enabled: Cell::new(true),
193            is_subscribed_to_transitions_collection: Cell::new(false),
194            transition_state: RefCell::new(None),
195            collection_subscriptions: RefCell::new(Vec::new()),
196            applying_transition_instance: RefCell::new(None),
197            transition_context: RefCell::new(TransitionContextState::default()),
198        }
199    }
200
201    pub fn new() -> Ref<Self> {
202        instantiate(Self::construct())
203    }
204
205    /// The clock which controls the animations and transitions on the
206    /// object and, by inheritance, on its descendants. The global clock is
207    /// used when there is none.
208    pub fn clock(&self) -> Option<Rc<dyn IClock>> {
209        self.get_value(Self::clock_property())
210    }
211
212    pub fn set_clock(&self, value: Option<Rc<dyn IClock>>) {
213        self.set_value(Self::clock_property(), value)
214    }
215
216    /// The property transitions of the object.
217    pub fn transitions(&self) -> Option<Transitions> {
218        self.get_value(Self::transitions_property())
219    }
220
221    pub fn set_transitions(&self, value: Option<Transitions>) {
222        self.set_value(Self::transitions_property(), value)
223    }
224
225    /// Enables transitions for the object.
226    ///
227    /// This is called when the object is attached to the visual tree, so
228    /// that transitions do not run on its initial property values.
229    pub fn enable_transitions(&self) {
230        if !self.transitions_enabled.get() {
231            self.transitions_enabled.set(true);
232
233            if let Some(transitions) = self.transitions() {
234                if !self.is_subscribed_to_transitions_collection.get() {
235                    self.is_subscribed_to_transitions_collection.set(true);
236                    self.subscribe_to_collection(&transitions);
237                }
238
239                self.add_transitions(&transitions.snapshot());
240            }
241        }
242    }
243
244    /// Disables transitions for the object.
245    ///
246    /// This is called when the object is detached from the visual tree.
247    pub fn disable_transitions(&self) {
248        if self.transitions_enabled.get() {
249            self.transitions_enabled.set(false);
250
251            if let Some(transitions) = self.transitions() {
252                if self.is_subscribed_to_transitions_collection.get() {
253                    self.is_subscribed_to_transitions_collection.set(false);
254                    self.unsubscribe_from_collection(&transitions);
255                }
256
257                self.remove_transitions(&transitions.snapshot());
258            }
259        }
260    }
261
262    /// The items of `first` that are not in `second`, without duplicates.
263    fn except(first: &[Rc<dyn ITransition>], second: &[Rc<dyn ITransition>]) -> Vec<Rc<dyn ITransition>> {
264        let mut result: Vec<Rc<dyn ITransition>> = Vec::new();
265        for item in first {
266            if !second.contains(item) && !result.contains(item) {
267                result.push(item.clone());
268            }
269        }
270        result
271    }
272
273    fn subscribe_to_collection(&self, transitions: &Transitions) {
274        // The collection must not keep the object alive: it is usually owned
275        // by the object's own value store.
276        let weak = self.to_ref().downgrade();
277        let handler: Rc<CollectionChangedHandler<Rc<dyn ITransition>>> =
278            Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ITransition>>| {
279                if let Some(this) = weak.upgrade() {
280                    this.transitions_collection_changed(e);
281                }
282            });
283        let token = transitions.add_collection_changed(handler);
284        self.collection_subscriptions.borrow_mut().push((transitions.clone(), token));
285    }
286
287    fn unsubscribe_from_collection(&self, transitions: &Transitions) {
288        let token = {
289            let mut subscriptions = self.collection_subscriptions.borrow_mut();
290            subscriptions.iter().rposition(|(t, _)| t.ptr_eq(transitions)).map(|index| subscriptions.remove(index).1)
291        };
292        if let Some(token) = token {
293            transitions.remove_collection_changed(token);
294        }
295    }
296
297    fn transitions_collection_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ITransition>>) {
298        if !self.transitions_enabled.get() {
299            return;
300        }
301
302        match e.action {
303            NotifyCollectionChangedAction::Add => self.add_transitions(e.new_items),
304            NotifyCollectionChangedAction::Remove => self.remove_transitions(e.old_items),
305            NotifyCollectionChangedAction::Replace => {
306                self.remove_transitions(e.old_items);
307                self.add_transitions(e.new_items);
308            }
309            NotifyCollectionChangedAction::Reset => panic!("Transitions collection cannot be reset."),
310            NotifyCollectionChangedAction::Move => {}
311        }
312    }
313
314    fn add_transitions(&self, items: &[Rc<dyn ITransition>]) {
315        if !self.transitions_enabled.get() {
316            return;
317        }
318
319        for t in items {
320            let base_value = self.get_animation_base_value(t.property());
321            let mut transition_state = self.transition_state.borrow_mut();
322            let transition_state = transition_state.get_or_insert_with(Vec::new);
323            if transition_state.iter().any(|(key, _)| key == t) {
324                panic!("An item with the same key has already been added.");
325            }
326            transition_state.push((
327                t.clone(),
328                Rc::new(TransitionState {
329                    instance: RefCell::new(None),
330                    progress: RefCell::new(None),
331                    base_value: RefCell::new(base_value),
332                    interruption_state: RefCell::new(None),
333                }),
334            ));
335        }
336        if self.transition_state.borrow().is_none() {
337            *self.transition_state.borrow_mut() = Some(Vec::new());
338        }
339    }
340
341    fn remove_transitions(&self, items: &[Rc<dyn ITransition>]) {
342        if self.transition_state.borrow().is_none() {
343            return;
344        }
345
346        for t in items {
347            let Some(state) = self.try_get_state(t) else { continue };
348            let instance = state.instance.borrow_mut().take();
349            *state.progress.borrow_mut() = None;
350            *state.interruption_state.borrow_mut() = None;
351            if let Some(instance) = instance {
352                instance.dispose();
353            }
354            if let Some(transition_state) = &mut *self.transition_state.borrow_mut() {
355                transition_state.retain(|(key, _)| key != t);
356            }
357        }
358    }
359
360    /// The progress of the transition that `transition` is currently
361    /// running on this object: the instance created when the transition was
362    /// last applied. `None` when the transition is not one of the object's
363    /// transitions or has not been applied.
364    pub fn try_get_transition_instance(&self, transition: &Rc<dyn ITransition>) -> Option<Rc<TransitionInstance>> {
365        self.try_get_state(transition).and_then(|state| state.progress.borrow().clone())
366    }
367
368    /// Called by a transition while it is being applied, with the progress
369    /// that drives the values it binds.
370    pub(crate) fn report_transition_instance(&self, instance: &Rc<TransitionInstance>) {
371        *self.applying_transition_instance.borrow_mut() = Some(instance.clone());
372    }
373
374    /// Takes state left by the preceding application of the same transition on this object.
375    ///
376    /// This is only available synchronously inside [`ITransition::apply`]. A spring transition
377    /// can retain its last sampled velocity here, without storing mutable animation state in
378    /// a transition definition that may be shared by several controls. Removal or visual-tree
379    /// detachment discards the state. Normal duration-based transitions do not use this hook.
380    pub fn take_transition_interruption_state<T: Any>(&self) -> Option<Rc<T>> {
381        self.transition_context.borrow_mut().interruption.take()?.downcast().ok()
382    }
383
384    /// Reports state to carry into the next application of this transition on this object.
385    ///
386    /// Call only from [`ITransition::apply`]. The state may be updated by the running animation,
387    /// but must not strongly own the target object. Outside a transition application this has
388    /// no effect. Store zero velocity when an animation completes or is accessibility-canceled.
389    pub fn report_transition_interruption_state<T: Any>(&self, state: Rc<T>) {
390        let mut context = self.transition_context.borrow_mut();
391        if context.applying {
392            context.interruption = Some(state);
393        }
394    }
395
396    fn try_get_state(&self, transition: &Rc<dyn ITransition>) -> Option<Rc<TransitionState>> {
397        let transition_state = self.transition_state.borrow();
398        transition_state
399            .as_ref()
400            .and_then(|states| states.iter().find(|(key, _)| key == transition).map(|(_, state)| state.clone()))
401    }
402
403    fn get_animation_base_value(&self, property: &'static FerroProperty) -> BoxedValue {
404        let value = self.get_base_value_untyped(property);
405
406        if value.is::<UnsetValueType>() {
407            self.get_value_untyped(property)
408        } else {
409            value
410        }
411    }
412}