1use crate::animation::cancellation::register_local;
2use crate::animation::easings::Easing;
3use crate::animation::i_page_transition::continuation_context;
4use crate::animation::key_frame::KeyFrameTimingMode;
5use crate::animation::{
6 Animatable, AnimationError, AnimationTask, AnimatorFactory, AnimatorKeyFrame, Cue, FillMode,
7 IAnimator, IClock, IterationCount, KeyFrames, PlaybackBehavior, PlaybackDirection, TimeSpan,
8};
9use crate::data::{BindingMode, BindingPriority};
10use crate::reactive::{
11 CompositeDisposable, Disposable, IDisposable, IObservable, Observable, SerialDisposable,
12};
13use crate::threading::CancellationToken;
14use crate::{
15 ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty,
16 DirectPropertyMetadata, FerroObject, FerroObjectImpl, FerroProperty, Ref, Visual, WeakRef,
17};
18use std::any::TypeId;
19use std::cell::{Cell, RefCell};
20use std::rc::{Rc, Weak};
21
22struct PreviewTransform {
23 target: WeakRef<Visual>,
24 transform: Rc<dyn crate::media::ITransform>,
25 value: SerialDisposable,
26}
27impl Drop for PreviewTransform {
28 fn drop(&mut self) {
29 self.value.dispose();
30 }
31}
32crate::ferro_thread_local! {
33 static PREVIEW_TRANSFORMS: RefCell<Vec<Weak<PreviewTransform>>> = RefCell::new(Vec::new());
36}
37
38#[repr(C)]
40pub struct Animation {
41 base: FerroObject,
42 duration: Cell<TimeSpan>,
43 iteration_count: Cell<IterationCount>,
44 playback_direction: Cell<PlaybackDirection>,
45 playback_behavior: Cell<PlaybackBehavior>,
46 fill_mode: Cell<FillMode>,
47 easing: RefCell<Easing>,
48 delay: Cell<TimeSpan>,
49 delay_between_iterations: Cell<TimeSpan>,
50 speed_ratio: Cell<f64>,
51 children: KeyFrames,
52 preview_values: RefCell<Option<Rc<CompositeDisposable>>>,
53}
54
55ferro_class!(Animation: FerroObject);
56crate::ferro_class_info!(Animation { new: Animation::new, interfaces: [Rc<dyn crate::animation::IAnimation>] });
57ferro_impl_classes!(Animation: FerroObjectImpl);
58
59macro_rules! direct_cell_property {
60 ($(#[$meta:meta])* $accessor:ident, $name:literal, $ty:ty, $getter:ident, $setter:ident, $field:ident, $unset:expr) => {
61 ferro_property!(for Animation;
62 $(#[$meta])*
63 pub fn $accessor() -> DirectProperty<Animation, $ty> {
64 FerroProperty::register_direct::<Animation, _>($name, |o| o.$getter(), Some(|o, v| o.$setter(v)), $unset)
65 }
66 );
67
68 pub fn $getter(&self) -> $ty {
69 self.$field.get()
70 }
71
72 pub fn $setter(&self, value: $ty) {
73 self.set_and_raise_cell(Self::$accessor(), &self.$field, value);
74 }
75 };
76}
77
78crate::ferro_properties! {
79 impl Animation, also [
80 Animation::duration_property,
81 Animation::iteration_count_property,
82 Animation::playback_direction_property,
83 Animation::playback_behavior_property,
84 Animation::fill_mode_property,
85 Animation::delay_property,
86 Animation::delay_between_iterations_property,
87 Animation::easing_property,
88 Animation::speed_ratio_property,
89 ] {}
90}
91
92impl Animation {
93 direct_cell_property!(
94 duration_property, "Duration", TimeSpan, duration, set_duration, duration, TimeSpan::ZERO
96 );
97
98 direct_cell_property!(
99 iteration_count_property, "IterationCount", IterationCount, iteration_count, set_iteration_count,
101 iteration_count, IterationCount::new(1)
102 );
103
104 direct_cell_property!(
105 playback_direction_property, "PlaybackDirection", PlaybackDirection, playback_direction,
107 set_playback_direction, playback_direction, PlaybackDirection::Normal
108 );
109
110 direct_cell_property!(
111 playback_behavior_property, "PlaybackBehavior", PlaybackBehavior, playback_behavior,
114 set_playback_behavior, playback_behavior, PlaybackBehavior::Auto
115 );
116
117 direct_cell_property!(
118 fill_mode_property, "FillMode", FillMode, fill_mode, set_fill_mode, fill_mode, FillMode::None
120 );
121
122 direct_cell_property!(
123 delay_property, "Delay", TimeSpan, delay, set_delay, delay, TimeSpan::ZERO
125 );
126
127 direct_cell_property!(
128 delay_between_iterations_property, "DelayBetweenIterations", TimeSpan, delay_between_iterations,
130 set_delay_between_iterations, delay_between_iterations, TimeSpan::ZERO
131 );
132
133 ferro_property!(for Animation;
134 pub fn easing_property() -> DirectProperty<Animation, Easing> {
136 FerroProperty::register_direct::<Animation, _>(
137 "Easing",
138 |o| o.easing(),
139 Some(|o, v| o.set_easing(v)),
140 Easing::default(),
141 )
142 }
143 );
144
145 ferro_property!(for Animation;
146 pub fn speed_ratio_property() -> DirectProperty<Animation, f64> {
148 FerroProperty::register_direct_with::<Animation, _>(
149 "SpeedRatio",
150 |o| o.speed_ratio(),
151 Some(|o, v| o.set_speed_ratio(v)),
152 DirectPropertyMetadata::new(Some(1.0)).with_default_binding_mode(BindingMode::TwoWay),
153 )
154 }
155 );
156
157 pub fn construct() -> Self {
159 Self {
160 base: FerroObject::construct(),
161 duration: Cell::new(TimeSpan::ZERO),
162 iteration_count: Cell::new(IterationCount::new(1)),
163 playback_direction: Cell::new(PlaybackDirection::Normal),
164 playback_behavior: Cell::new(PlaybackBehavior::Auto),
165 fill_mode: Cell::new(FillMode::None),
166 easing: RefCell::new(Easing::default()),
167 delay: Cell::new(TimeSpan::ZERO),
168 delay_between_iterations: Cell::new(TimeSpan::ZERO),
169 speed_ratio: Cell::new(1.0),
170 children: KeyFrames::new(),
171 preview_values: RefCell::new(None),
172 }
173 }
174
175 pub fn new() -> Ref<Self> {
176 instantiate(Self::construct())
177 }
178
179 pub fn easing(&self) -> Easing {
181 self.easing.borrow().clone()
182 }
183
184 pub fn set_easing(&self, value: impl Into<Easing>) {
185 self.set_and_raise(Self::easing_property(), &self.easing, value.into());
186 }
187
188 pub fn speed_ratio(&self) -> f64 {
190 self.speed_ratio.get()
191 }
192
193 pub fn set_speed_ratio(&self, value: f64) {
194 self.set_and_raise_cell(Self::speed_ratio_property(), &self.speed_ratio, value);
195 }
196
197 pub fn children(&self) -> KeyFrames {
199 self.children.clone()
200 }
201
202 #[allow(clippy::type_complexity)]
203 fn interpret_keyframes(
204 &self,
205 control: &Animatable,
206 ) -> (Vec<Rc<dyn IAnimator>>, Vec<Rc<dyn IDisposable>>, bool) {
207 let mut handler_list: Vec<((TypeId, &'static FerroProperty), AnimatorFactory)> = Vec::new();
208 let mut animator_key_frames: Vec<Ref<AnimatorKeyFrame>> = Vec::new();
209 let mut subscriptions: Vec<Rc<dyn IDisposable>> = Vec::new();
210 let mut animates_visibility = false;
211
212 for keyframe in self.children.snapshot().iter() {
213 for setter in keyframe.setters().snapshot().iter() {
214 let Some(property) = setter.property() else {
215 panic!("No Setter property assigned.");
216 };
217
218 if property == Visual::is_visible_property().as_property() {
219 animates_visibility = true;
220 }
221
222 let handler = Animation::get_animator(setter)
223 .or_else(|| Animation::get_animator_type(property));
224
225 let Some((type_, factory)) = handler else {
226 panic!(
227 "No animator registered for the property {property}. Add an animator to the Animation.Animators collection that matches this property to animate it."
228 );
229 };
230
231 if !handler_list
232 .iter()
233 .any(|((t, p), _)| *t == type_ && *p == property)
234 {
235 handler_list.push(((type_, property), factory.clone()));
236 }
237
238 let mut cue = keyframe.cue();
239
240 if keyframe.timing_mode() == KeyFrameTimingMode::TimeSpan {
241 cue = Cue::new(
242 keyframe.key_time().total_seconds() / self.duration().total_seconds(),
243 );
244 }
245
246 let new_kf =
247 AnimatorKeyFrame::new(Some(type_), Some(factory), cue, keyframe.key_spline());
248
249 let subscription = new_kf.bind_setter(&**setter, control);
250 self.retain_preview_subscription(&subscription);
251 subscriptions.push(subscription);
252
253 animator_key_frames.push(new_kf);
254 }
255 }
256
257 animator_key_frames.sort_by(|x, y| x.cue().cue_value().total_cmp(&y.cue().cue_value()));
258
259 let mut new_animator_instances: Vec<Rc<dyn IAnimator>> = Vec::new();
260
261 for ((_, property), factory) in &handler_list {
262 let new_instance = factory();
263 new_instance.set_property(Some(property));
264 new_animator_instances.push(new_instance);
265 }
266
267 let fill_mode = self.fill_mode();
268 for keyframe in animator_key_frames {
269 let animator = new_animator_instances
270 .iter()
271 .find(|a| {
272 Some(a.animator_type()) == keyframe.animator_type()
273 && a.property() == keyframe.property()
274 })
275 .expect("Sequence contains no matching element");
276
277 if animator.count() == 0 && matches!(fill_mode, FillMode::Backward | FillMode::Both) {
278 keyframe.set_fill_before(true);
279 }
280
281 animator.add(keyframe);
282 }
283
284 if matches!(fill_mode, FillMode::Forward | FillMode::Both) {
285 for new_animator_instance in &new_animator_instances {
286 let count = new_animator_instance.count();
287 if count > 0 {
288 new_animator_instance.get(count - 1).set_fill_after(true);
289 }
290 }
291 }
292
293 (new_animator_instances, subscriptions, animates_visibility)
294 }
295
296 pub fn apply(
302 &self,
303 control: &Animatable,
304 clock: Option<Rc<dyn IClock>>,
305 match_: Rc<dyn IObservable<bool>>,
306 on_complete: Option<Rc<dyn Fn()>>,
307 is_manually_started: bool,
308 ) -> Rc<dyn IDisposable> {
309 let (animators, mut subscriptions, animates_visibility) = self.interpret_keyframes(control);
310
311 let should_pause_on_invisible = match self.playback_behavior.get() {
312 PlaybackBehavior::Auto => !(animates_visibility || is_manually_started),
313 PlaybackBehavior::Always => false,
314 PlaybackBehavior::OnlyIfVisible => true,
315 };
316
317 if animators.len() == 1 {
318 let subscription = animators[0].clone().apply(
319 self,
320 control,
321 clock,
322 match_,
323 on_complete,
324 should_pause_on_invisible,
325 );
326
327 if let Some(subscription) = subscription {
328 self.retain_preview_subscription(&subscription);
329 subscriptions.push(subscription);
330 }
331 } else {
332 let remaining = Rc::new(Cell::new(animators.len()));
335 let schedule_on_complete = on_complete.map(|on_complete| {
336 let context = continuation_context();
337 Rc::new(move || {
338 let on_complete = on_complete.clone();
339 context.post_local(move || on_complete());
340 })
341 });
342
343 for animator in &animators {
344 let animator_on_complete: Option<Rc<dyn Fn()>> =
345 schedule_on_complete.as_ref().map(|schedule| {
346 let schedule = schedule.clone();
347 let remaining = remaining.clone();
348 let completed = Cell::new(false);
349 let action: Rc<dyn Fn()> = Rc::new(move || {
350 if completed.replace(true) {
351 return;
352 }
353 remaining.set(remaining.get() - 1);
354 if remaining.get() == 0 {
355 schedule();
356 }
357 });
358 action
359 });
360
361 let subscription = animator.clone().apply(
362 self,
363 control,
364 clock.clone(),
365 match_.clone(),
366 animator_on_complete,
367 should_pause_on_invisible,
368 );
369
370 if let Some(subscription) = subscription {
371 self.retain_preview_subscription(&subscription);
372 subscriptions.push(subscription);
373 }
374 }
375
376 if animators.is_empty() {
377 if let Some(schedule) = &schedule_on_complete {
378 schedule();
379 }
380 }
381 }
382
383 Rc::new(CompositeDisposable::from_disposables(subscriptions))
384 }
385
386 pub fn apply_for_preview(
392 &self,
393 control: &Animatable,
394 clock: Option<Rc<dyn IClock>>,
395 on_complete: Option<Rc<dyn Fn()>>,
396 ) -> Rc<dyn IDisposable> {
397 let preview = Animation::new();
398 preview.set_duration(self.duration());
399 preview.set_iteration_count(self.iteration_count());
400 preview.set_playback_direction(self.playback_direction());
401 preview.set_playback_behavior(self.playback_behavior());
402 preview.set_fill_mode(self.fill_mode());
403 preview.set_easing(self.easing());
404 preview.set_delay(self.delay());
405 preview.set_delay_between_iterations(self.delay_between_iterations());
406 preview.set_speed_ratio(self.speed_ratio());
407 preview
408 .children()
409 .add_range(self.children().snapshot().iter().cloned());
410 let values = Rc::new(CompositeDisposable::new());
411 *preview.preview_values.borrow_mut() = Some(values.clone());
412 let applied = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
413 if let Some(visual) = control.downcast_ref::<Visual>() {
417 if visual.render_transform().is_some_and(|value| {
418 value
419 .as_any()
420 .is::<crate::media::transformation::TransformOperations>()
421 }) {
422 for keyframe in preview.children().snapshot().iter() {
423 for setter in keyframe.setters().snapshot().iter() {
424 let handler = Animation::get_animator(setter).or_else(|| {
425 setter.property().and_then(Animation::get_animator_type)
426 });
427 if handler.is_some_and(|(kind, _)| kind == TypeId::of::<crate::animation::animators::TransformAnimator>()) {
428 panic!("The native component animator cannot target an existing CSS TransformOperations value; use a native TransformGroup or a registered transform animator.");
429 }
430 }
431 }
432 }
433 }
434 preview.apply(
435 control,
436 clock,
437 Observable::single_value(true),
438 on_complete,
439 true,
440 )
441 }));
442 let applied = match applied {
443 Ok(applied) => applied,
444 Err(error) => {
445 values.dispose();
448 std::panic::resume_unwind(error);
449 }
450 };
451 Disposable::create(move || {
452 values.dispose();
455 applied.dispose();
456 drop(preview);
457 })
458 }
459
460 pub(crate) fn retain_preview_subscription(&self, subscription: &Rc<dyn IDisposable>) {
461 if let Some(values) = self.preview_values.borrow().clone() {
462 values.add(subscription.clone());
463 }
464 }
465
466 pub(crate) fn is_preview(&self) -> bool {
467 self.preview_values.borrow().is_some()
468 }
469
470 pub(crate) fn create_preview_transform(
471 &self,
472 target: &Visual,
473 transform: Rc<dyn crate::media::ITransform>,
474 ) -> bool {
475 let values = self.preview_values.borrow().clone();
476 if let Some(values) = values {
477 if !values.is_disposed() {
478 let lease = Rc::new(PreviewTransform {
479 target: target.to_ref().downgrade(),
480 transform: transform.clone(),
481 value: SerialDisposable::new(),
482 });
483 PREVIEW_TRANSFORMS.with(|entries| {
484 let mut entries = entries.borrow_mut();
485 entries.retain(|entry| entry.strong_count() > 0);
486 entries.push(Rc::downgrade(&lease));
487 });
488 let retained = lease.clone();
491 values.add(Disposable::create(move || drop(retained)));
492 lease.value.set(target.set_value_with_priority(
493 Visual::render_transform_property(),
494 Some(transform),
495 BindingPriority::Animation,
496 ));
497 }
498 true
499 } else {
500 false
501 }
502 }
503
504 pub(crate) fn retain_preview_transform(
505 &self,
506 target: &Visual,
507 transform: &Rc<dyn crate::media::ITransform>,
508 ) {
509 let Some(values) = self.preview_values.borrow().clone() else {
510 return;
511 };
512 let lease = PREVIEW_TRANSFORMS.with(|entries| {
513 let mut entries = entries.borrow_mut();
514 entries.retain(|entry| entry.strong_count() > 0);
515 entries.iter().filter_map(Weak::upgrade).find(|entry| {
516 Rc::ptr_eq(&entry.transform, transform)
517 && entry
518 .target
519 .upgrade()
520 .is_some_and(|owner| std::ptr::eq(&*owner, target))
521 })
522 });
523 if let Some(lease) = lease {
524 values.add(Disposable::create(move || drop(lease)));
525 }
526 }
527
528 pub fn run_async(
531 &self,
532 control: &Animatable,
533 cancellation_token: CancellationToken,
534 ) -> AnimationTask {
535 self.run_async_with_clock(control, None, cancellation_token)
536 }
537
538 pub fn run_async_with_clock(
540 &self,
541 control: &Animatable,
542 clock: Option<Rc<dyn IClock>>,
543 cancellation_token: CancellationToken,
544 ) -> AnimationTask {
545 if cancellation_token.is_cancellation_requested() {
546 return AnimationTask::completed();
547 }
548
549 let run = AnimationTask::pending();
550
551 if self.iteration_count() == IterationCount::INFINITE {
552 run.try_set(Err(AnimationError(
553 "Looping animations must not use the Run method.".to_string(),
554 )));
555 }
556
557 let subscriptions: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
558 let cancellation = Rc::new(RefCell::new(None));
559
560 let finish: Rc<dyn Fn()> = {
561 let run = run.clone();
562 let subscriptions = subscriptions.clone();
563 let cancellation = cancellation.clone();
564 Rc::new(move || {
565 run.try_set_result();
566 let subscriptions = subscriptions.borrow_mut().take();
567 if let Some(subscriptions) = subscriptions {
568 subscriptions.dispose();
569 }
570 let cancellation: Option<
571 crate::animation::cancellation::LocalCancellationRegistration,
572 > = cancellation.borrow_mut().take();
573 if let Some(cancellation) = cancellation {
574 cancellation.dispose();
575 }
576 })
577 };
578
579 let applied = self.apply(
580 control,
581 clock,
582 Observable::return_(true),
583 Some(finish.clone()),
584 true,
585 );
586 *subscriptions.borrow_mut() = Some(applied);
589
590 let registration = register_local(&cancellation_token, move || finish());
591 if !run.is_completed() || !cancellation_token.is_cancellation_requested() {
592 *cancellation.borrow_mut() = Some(registration);
593 }
594
595 run
596 }
597}