Skip to main content

ferroui_base/animation/animators/
transform_animator.rs

1use crate::animation::animators::{apply_with_visual, Animator, AnimatorBase, DoubleAnimator};
2use crate::animation::{Animatable, Animation, IAnimator, IClock};
3use crate::logging::{LogArea, LogEventLevel, Logger};
4use crate::media::transformation::TransformOperations;
5use crate::media::{
6    ITransform, RotateTransform, Rotate3DTransform, ScaleTransform, SkewTransform, Transform, TransformGroup,
7    TranslateTransform,
8};
9use crate::reactive::{Disposable, IDisposable, IObservable};
10use crate::{Ref, Visual};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14/// Animator that handles `f64` properties of [`Transform`]s: redirects the
15/// animation to the matching transform in the render transform of the
16/// animated visual, creating a default render transform if there is none.
17#[derive(Default)]
18pub struct TransformAnimator {
19    base: AnimatorBase,
20    double_animator: RefCell<Option<Rc<DoubleAnimator>>>,
21}
22
23impl TransformAnimator {
24    pub fn new() -> Self {
25        Self::default()
26    }
27}
28
29impl Animator for TransformAnimator {
30    type Value = f64;
31
32    fn base(&self) -> &AnimatorBase {
33        &self.base
34    }
35
36    fn apply(
37        this: &Rc<Self>,
38        animation: &Animation,
39        control: &Animatable,
40        clock: Option<Rc<dyn IClock>>,
41        obs_match: Rc<dyn IObservable<bool>>,
42        on_complete: Option<Rc<dyn Fn()>>,
43        should_pause_on_invisible: bool,
44    ) -> Option<Rc<dyn IDisposable>> {
45        let Some(ctrl) = control.downcast_ref::<Visual>() else {
46            panic!("Unable to cast object of type '{}' to type 'Visual'.", control.get_type());
47        };
48
49        let Some(property) = this.base.property() else {
50            panic!("Animator has no property specified.");
51        };
52
53        // Check if the Target Property is Transform derived.
54        if Transform::TYPE.is_assignable_from(property.owner_type()) {
55            if ctrl.render_transform().is_some_and(|t| t.as_any().is::<TransformOperations>()) {
56                // HACK: This animator cannot reasonably animate CSS transforms at the moment.
57                return Some(Disposable::empty());
58            }
59
60            let render_transform: Rc<dyn ITransform> = match ctrl.render_transform() {
61                Some(render_transform) => render_transform,
62                None => {
63                    let normal_transform = TransformGroup::new();
64
65                    // Add the transforms according to MS Expression Blend's
66                    // default RenderTransform order.
67                    let children = normal_transform.children();
68                    children.add(ScaleTransform::new().upcast());
69                    children.add(SkewTransform::new().upcast());
70                    children.add(RotateTransform::new().upcast());
71                    children.add(TranslateTransform::new().upcast());
72                    children.add(Rotate3DTransform::new().upcast());
73
74                    let render_transform: Rc<dyn ITransform> = normal_transform.into();
75                    ctrl.set_render_transform(Some(render_transform.clone()));
76                    render_transform
77                }
78            };
79
80            let double_animator = {
81                let mut double_animator = this.double_animator.borrow_mut();
82                double_animator
83                    .get_or_insert_with(|| {
84                        let double_animator = Rc::new(DoubleAnimator::new());
85                        for keyframe in this.base.to_vec() {
86                            IAnimator::add(&*double_animator, keyframe);
87                        }
88                        double_animator.base().set_property(Some(property));
89                        double_animator
90                    })
91                    .clone()
92            };
93
94            let visual_target = Some(ctrl.to_ref());
95            let clock = clock.or_else(|| control.clock());
96
97            if let Some(render_transform) = render_transform.as_object() {
98                // It's a transform object so let's target that.
99                if render_transform.get_type() == property.owner_type() {
100                    let target: Ref<Animatable> =
101                        render_transform.to_ref().cast().expect("a transform is an animatable");
102                    return Some(apply_with_visual(
103                        &double_animator,
104                        animation,
105                        &target,
106                        clock,
107                        obs_match,
108                        on_complete,
109                        should_pause_on_invisible,
110                        visual_target,
111                    ));
112                }
113                // It's a TransformGroup and try finding the target there.
114                else if let Some(group) = render_transform.downcast_ref::<TransformGroup>() {
115                    if render_transform.get_type() == TransformGroup::TYPE {
116                        for transform in group.children().iter() {
117                            if transform.get_type() == property.owner_type() {
118                                let target: Ref<Animatable> = transform.upcast();
119                                return Some(apply_with_visual(
120                                    &double_animator,
121                                    animation,
122                                    &target,
123                                    clock,
124                                    obs_match,
125                                    on_complete,
126                                    should_pause_on_invisible,
127                                    visual_target,
128                                ));
129                            }
130                        }
131                    }
132                }
133            }
134
135            if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::ANIMATIONS) {
136                logger.log(
137                    None,
138                    &format!(
139                        "Cannot find the appropriate transform: \"{}\" in {}.",
140                        property.owner_type(),
141                        control.get_type()
142                    ),
143                );
144            }
145        } else if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::ANIMATIONS) {
146            logger.log(
147                None,
148                &format!(
149                    "Cannot apply animation: Target property owner {} is not a Transform object.",
150                    property.owner_type()
151                ),
152            );
153        }
154
155        None
156    }
157
158    fn interpolate(&self, _progress: f64, _old_value: &f64, _new_value: &f64) -> f64 {
159        0.0
160    }
161}