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