Skip to main content

ferroui_base/rendering/composition/animations/
animation_instance_base.rs

1use super::{IAnimationInstance, PropertySetSnapshot};
2use crate::rendering::composition::expressions::{ExpressionKeywords, IExpressionObject};
3use crate::rendering::composition::server::{
4    CompositionProperty, IAnimatedServerObject, IServerClockItem, ServerCompositor, ServerExpressionObject,
5    ServerObjectAnimations, ServerObjectId,
6};
7use std::cell::{Cell, RefCell};
8use std::collections::HashSet;
9use std::rc::{Rc, Weak};
10
11/// The base class for both key-frame and expression animation instances
12/// Is responsible for activation tracking and for subscribing to properties used in dependencies
13///
14/// A class "derives" from it by embedding it and forwarding the members of
15/// [`IAnimationInstance`] it does not override. The embedding class is
16/// created with `Rc::new_cyclic` and hands the weak handle of itself to the
17/// base, which subscribes it to the objects it tracks.
18///
19/// The target and the tracked objects are held weakly: the animations of
20/// the target hold the instance, and the instance must not keep the target
21/// alive in turn.
22pub struct AnimationInstanceBase {
23    this: Weak<dyn IAnimationInstance>,
24    tracked_objects: RefCell<Option<Vec<(Weak<dyn IAnimatedServerObject>, &'static CompositionProperty)>>>,
25    parameters: Rc<PropertySetSnapshot>,
26    target_object: ServerObjectId,
27    target: RefCell<Option<Weak<dyn IAnimatedServerObject>>>,
28    compositor: RefCell<Weak<ServerCompositor>>,
29    property: Cell<Option<&'static CompositionProperty>>,
30    invalidated: Cell<bool>,
31}
32
33impl AnimationInstanceBase {
34    pub fn new(this: Weak<dyn IAnimationInstance>, target: ServerObjectId, parameters: Rc<PropertySetSnapshot>) -> Self {
35        Self {
36            this,
37            tracked_objects: RefCell::new(None),
38            parameters,
39            target_object: target,
40            target: RefCell::new(None),
41            compositor: RefCell::new(Weak::new()),
42            property: Cell::new(None),
43            invalidated: Cell::new(false),
44        }
45    }
46
47    pub fn parameters(&self) -> &Rc<PropertySetSnapshot> {
48        &self.parameters
49    }
50
51    /// The id of the object whose property is animated.
52    pub fn target_object(&self) -> ServerObjectId {
53        self.target_object
54    }
55
56    /// The object whose property is animated, once resolved and while it is
57    /// alive.
58    pub fn target(&self) -> Option<Rc<dyn IAnimatedServerObject>> {
59        self.target.borrow().as_ref().and_then(Weak::upgrade)
60    }
61
62    /// The compositor of the target (`TargetObject.Compositor`), once
63    /// resolved and while it is alive.
64    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
65        self.compositor.borrow().upgrade()
66    }
67
68    /// The property the instance animates, once initialized.
69    pub fn property(&self) -> Option<&'static CompositionProperty> {
70        self.property.get()
71    }
72
73    /// The handle of the embedding instance as a clock item.
74    pub fn this_clock_item(&self) -> Option<Rc<dyn IServerClockItem>> {
75        self.this.upgrade().map(|this| this as Rc<dyn IServerClockItem>)
76    }
77
78    /// See [`IAnimationInstance::resolve`]: resolves the target and the
79    /// objects of the reference parameters on `compositor`.
80    ///
81    /// Upstream the instance holds its target. The target is the object
82    /// whose batch changes carry the instance, so its id names it here; an
83    /// id that names no animatable object leaves the instance without a
84    /// target, which it then treats as gone.
85    pub fn resolve(&self, compositor: &Rc<ServerCompositor>) {
86        *self.compositor.borrow_mut() = Rc::downgrade(compositor);
87        *self.target.borrow_mut() =
88            compositor.get_animated_object(self.target_object).map(|target| Rc::downgrade(&target));
89        self.parameters.resolve(compositor);
90    }
91
92    /// Calls `f` with the target as the expressions of the instance see it.
93    pub fn with_target_expression_object<R>(&self, f: impl FnOnce(Option<&dyn IExpressionObject>) -> R) -> R {
94        match self.target() {
95            Some(target) => {
96                let target = ServerExpressionObject(target);
97                f(Some(&target))
98            }
99            None => f(None),
100        }
101    }
102
103    /// `Initialize(property, trackedObjects)`.
104    pub fn initialize(&self, property: &'static CompositionProperty, tracked_objects: &HashSet<(String, String)>) {
105        if !tracked_objects.is_empty() {
106            let mut tracked_list = Vec::new();
107            for (name, member) in tracked_objects {
108                let obj = if name == ExpressionKeywords::TARGET {
109                    self.target()
110                } else {
111                    self.parameters.get_server_object_parameter(name)
112                };
113                if let Some(tracked) = obj {
114                    let Some(off) = tracked.get_composition_property(member) else {
115                        if cfg!(debug_assertions) {
116                            panic!("Attempting to subscribe to unknown field");
117                        }
118                        continue;
119                    };
120                    tracked_list.push((Rc::downgrade(&tracked), off));
121                }
122            }
123            *self.tracked_objects.borrow_mut() = Some(tracked_list);
124        }
125
126        self.property.set(Some(property));
127    }
128
129    /// The head of `Evaluate`: the evaluation makes the instance valid
130    /// again. The embedding class then evaluates its own core.
131    pub fn begin_evaluate(&self) {
132        self.invalidated.set(false);
133    }
134
135    fn tracked(&self) -> Vec<(Rc<dyn IAnimatedServerObject>, &'static CompositionProperty)> {
136        self.tracked_objects
137            .borrow()
138            .iter()
139            .flatten()
140            .filter_map(|(obj, member)| obj.upgrade().map(|obj| (obj, *member)))
141            .collect()
142    }
143
144    pub fn activate(&self) {
145        let Some(this) = self.this.upgrade() else { return };
146        for (obj, member) in self.tracked() {
147            obj.server_object().get_or_create_animations().subscribe_to_invalidation(member, this.clone());
148        }
149    }
150
151    pub fn deactivate(&self) {
152        let Some(this) = self.this.upgrade() else { return };
153        for (obj, member) in self.tracked() {
154            if let Some(animations) = obj.server_object().animations() {
155                animations.unsubscribe_from_invalidation(member, &this);
156            }
157        }
158    }
159
160    pub fn invalidate(&self) {
161        if self.invalidated.get() {
162            return;
163        }
164        self.invalidated.set(true);
165        let (Some(target), Some(property)) = (self.target(), self.property.get()) else { return };
166        if let Some(animations) = target.server_object().animations() {
167            ServerObjectAnimations::notify_animation_instance_invalidated(&animations, property);
168        }
169    }
170
171    pub fn on_tick(&self) {
172        self.invalidate()
173    }
174}
175