ferroui_base/animation/
animatable.rs1use 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#[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 collection_subscriptions: RefCell<Vec<(Transitions, u64)>>,
23 applying_transition_instance: RefCell<Option<Rc<TransitionInstance>>>,
26}
27
28struct TransitionState {
29 instance: RefCell<Option<Rc<dyn IDisposable>>>,
30 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 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 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 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 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 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 pub fn transitions_property() -> StyledProperty<Option<Transitions>> {
143 FerroProperty::register::<Animatable, _>("Transitions", None)
144 }
145 );
146} }
147
148impl Animatable {
149 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 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 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 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 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 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 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 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 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}