Skip to main content

ferroui_base/rendering/composition/server/
server_object_animations.rs

1use super::{CompositionProperty, IAnimatedServerObject, ServerCompositor, ServerObjectId};
2use crate::rendering::composition::animations::IAnimationInstance;
3use crate::rendering::composition::expressions::ExpressionVariant;
4use std::cell::{Cell, RefCell};
5use std::rc::{Rc, Weak};
6use std::time::Duration;
7
8struct ServerObjectSubscriptionStore {
9    is_valid: Cell<bool>,
10    /// The subscribed animations with their reference counts.
11    subscribers: RefCell<Vec<(Rc<dyn IAnimationInstance>, u32)>>,
12}
13
14impl ServerObjectSubscriptionStore {
15    fn new() -> Self {
16        Self { is_valid: Cell::new(true), subscribers: RefCell::new(Vec::new()) }
17    }
18
19    fn invalidate(&self) {
20        if !self.is_valid.get() {
21            return;
22        }
23        self.is_valid.set(false);
24        let subscribers: Vec<_> = self.subscribers.borrow().iter().map(|(s, _)| s.clone()).collect();
25        for subscriber in subscribers {
26            subscriber.invalidate();
27        }
28    }
29}
30
31struct ServerObjectAnimationInstance {
32    property: &'static CompositionProperty,
33    cached_variant: Cell<ExpressionVariant>,
34    is_dirty: Cell<bool>,
35    needs_update: Cell<bool>,
36    animation: Rc<dyn IAnimationInstance>,
37}
38
39fn same_animation(a: &Rc<dyn IAnimationInstance>, b: &Rc<dyn IAnimationInstance>) -> bool {
40    std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b))
41}
42
43/// The animations of a server object and the animations of other objects
44/// that depend on its properties.
45pub struct ServerObjectAnimations {
46    owner: Weak<dyn IAnimatedServerObject>,
47    compositor: Weak<ServerCompositor>,
48    subscriptions: RefCell<Vec<(i32, Rc<ServerObjectSubscriptionStore>)>>,
49    animations: RefCell<Vec<Rc<ServerObjectAnimationInstance>>>,
50}
51
52impl ServerObjectAnimations {
53    pub fn new(owner: Weak<dyn IAnimatedServerObject>, compositor: Weak<ServerCompositor>) -> Self {
54        Self { owner, compositor, subscriptions: RefCell::new(Vec::new()), animations: RefCell::new(Vec::new()) }
55    }
56
57    fn owner_is_active(&self) -> bool {
58        self.owner.upgrade().is_some_and(|owner| owner.server_object().is_active())
59    }
60
61    fn subscription(&self, property: &CompositionProperty) -> Option<Rc<ServerObjectSubscriptionStore>> {
62        self.subscriptions.borrow().iter().find(|(id, _)| *id == property.id()).map(|(_, store)| store.clone())
63    }
64
65    fn animation(&self, property: &CompositionProperty) -> Option<Rc<ServerObjectAnimationInstance>> {
66        self.animations.borrow().iter().find(|a| a.property.id() == property.id()).cloned()
67    }
68
69    fn get_variant(&self, instance: &ServerObjectAnimationInstance) -> ExpressionVariant {
70        if !instance.is_dirty.get() {
71            return instance.cached_variant.get();
72        }
73        // Set before evaluating the animation to prevent stack overflows
74        // due to potential cyclic references.
75        instance.is_dirty.set(false);
76        // The time of the animations: the time of the compositor scaled by
77        // the global playback rate.
78        let now = self.compositor.upgrade().map_or(Duration::ZERO, |c| c.animations().animation_time(c.server_now()));
79        let value = instance.animation.evaluate(now, instance.cached_variant.get());
80        instance.cached_variant.set(value);
81        value
82    }
83
84    fn update_target_property(&self, instance: &ServerObjectAnimationInstance) {
85        if !instance.needs_update.get() {
86            return;
87        }
88        instance.needs_update.set(false);
89        let Some(owner) = self.owner.upgrade() else { return };
90        let value = self.get_variant(instance);
91        owner.write_property_variant(instance.property, value);
92        owner.notify_animated_value_changed(instance.property);
93        self.on_set_direct_value(instance.property);
94    }
95
96    pub fn activated(&self) {
97        let animations = self.animations.borrow().clone();
98        let any = !animations.is_empty();
99        for instance in animations {
100            instance.animation.activate();
101            instance.is_dirty.set(true);
102            instance.needs_update.set(true);
103        }
104        if any {
105            self.schedule_evaluation();
106        }
107    }
108
109    /// Queues the object for the evaluation of its dirty animations with the next frame.
110    ///
111    /// Not from upstream, where an animation is first evaluated when something it reads
112    /// invalidates it, or its clock ticks: an animation that reads nothing (a constant
113    /// expression, `"0.25"`) never wrote its property. WinUI applies it, so a started or
114    /// activated animation is evaluated once (DEVIATIONS.md, WinUI composition).
115    fn schedule_evaluation(&self) {
116        let (Some(owner), Some(compositor)) = (self.owner.upgrade(), self.compositor.upgrade()) else { return };
117        if let Some(this) = owner.server_object().animations() {
118            compositor.animations().add_dirty_animated_object(this);
119        }
120    }
121
122    pub fn deactivated(&self) {
123        let animations = self.animations.borrow().clone();
124        for instance in animations {
125            instance.animation.deactivate();
126        }
127    }
128
129    pub fn on_set_direct_value(&self, property: &CompositionProperty) {
130        if let Some(subscriptions) = self.subscription(property) {
131            subscriptions.invalidate();
132        }
133    }
134
135    pub fn on_set_animated_value(
136        &self,
137        property: &'static CompositionProperty,
138        current_value: ExpressionVariant,
139        committed_at: Duration,
140        animation: Rc<dyn IAnimationInstance>,
141    ) {
142        // Animations run on the clock of the animations, which the global
143        // playback rate scales.
144        let committed_at = self
145            .compositor
146            .upgrade()
147            .map_or(committed_at, |c| c.animations().animation_time(committed_at));
148        let is_active = self.owner_is_active();
149        let old = self.animation(property);
150        // An animation of components of the property (`Offset.Y`) joins the
151        // animation of other components that runs already (`Offset.X`).
152        if let Some(old) = &old {
153            if let (Some(running), Some(started)) = (old.animation.as_subchannel(), animation.as_subchannel()) {
154                if let Some(compositor) = self.compositor.upgrade() {
155                    animation.resolve(&compositor);
156                }
157                running.merge(started, committed_at, current_value);
158                self.on_set_direct_value(property);
159                return;
160            }
161        }
162        if let (true, Some(old)) = (is_active, &old) {
163            old.animation.deactivate();
164        }
165        // A replaced animation is over for the batches it belongs to; the
166        // new one starts from the current value (WinUI applies the stop
167        // behavior on `StopAnimation` only).
168        if let Some(old) = &old {
169            old.animation.release_completion();
170        }
171        {
172            let mut animations = self.animations.borrow_mut();
173            animations.retain(|a| a.property.id() != property.id());
174            animations.push(Rc::new(ServerObjectAnimationInstance {
175                property,
176                cached_variant: Cell::new(ExpressionVariant::default()),
177                is_dirty: Cell::new(true),
178                needs_update: Cell::new(true),
179                animation: animation.clone(),
180            }));
181        }
182        // The instance names the objects it works with by id; they are
183        // resolved here, on the server, before it is initialized.
184        if let Some(compositor) = self.compositor.upgrade() {
185            animation.resolve(&compositor);
186        }
187        animation.initialize(committed_at, current_value, property);
188        if is_active {
189            animation.activate();
190        }
191        self.on_set_direct_value(property);
192        if is_active {
193            self.schedule_evaluation();
194        }
195    }
196
197    pub fn remove_animation_for_property(&self, property: &CompositionProperty) {
198        let removed = {
199            let mut animations = self.animations.borrow_mut();
200            let index = animations.iter().position(|a| a.property.id() == property.id());
201            index.map(|index| animations.remove(index))
202        };
203        if let Some(removed) = removed {
204            if self.owner_is_active() {
205                removed.animation.deactivate();
206            }
207            removed.animation.release_completion();
208        }
209        self.on_set_direct_value(property);
210    }
211
212    /// `StopAnimation` of the property: removes its animation and applies
213    /// the stop behavior of the animation (WinUI `AnimationStopBehavior`;
214    /// upstream only removes the animation).
215    pub fn stop_animation_for_property(&self, property: &'static CompositionProperty) {
216        let removed = {
217            let mut animations = self.animations.borrow_mut();
218            let index = animations.iter().position(|a| a.property.id() == property.id());
219            index.map(|index| animations.remove(index))
220        };
221        let Some(removed) = removed else { return };
222        if self.owner_is_active() {
223            removed.animation.deactivate();
224        }
225        if let Some(owner) = self.owner.upgrade() {
226            let current = owner.read_property_variant(property);
227            if let Some(value) = removed.animation.value_on_stop(current) {
228                owner.write_property_variant(property, value);
229                owner.notify_animated_value_changed(property);
230            }
231        }
232        removed.animation.release_completion();
233        self.on_set_direct_value(property);
234    }
235
236    /// `StopAnimation` of a sub-channel of the property (`Offset.X`): stops
237    /// the animations of the sub-channels that share a component with
238    /// `components` and applies their stop behavior. An animation of the
239    /// whole property is left running.
240    pub fn stop_subchannel_animation(&self, property: &'static CompositionProperty, components: &[usize]) {
241        let Some(instance) = self.animation(property) else { return };
242        // Deviation (winui-composition.md): stopping a sub-channel of a
243        // property animated as a whole does nothing.
244        let Some(subchannels) = instance.animation.as_subchannel() else { return };
245        let Some(owner) = self.owner.upgrade() else { return };
246        let current = owner.read_property_variant(property);
247        let (value, empty) = subchannels.stop_components(components, current);
248        if empty {
249            self.animations.borrow_mut().retain(|a| a.property.id() != property.id());
250        }
251        if let Some(value) = value {
252            instance.cached_variant.set(value);
253            owner.write_property_variant(property, value);
254            owner.notify_animated_value_changed(property);
255        }
256        self.on_set_direct_value(property);
257    }
258
259    /// Puts the animation of `property`, if it has one, under the
260    /// animation controller whose property set has the id `controller`
261    /// (`TryGetAnimationController` of an animation started without one).
262    pub fn attach_controller(&self, property: &CompositionProperty, controller: ServerObjectId) {
263        if let Some(instance) = self.animation(property) {
264            instance.animation.attach_controller(controller);
265        }
266    }
267
268    /// Releases the batches of every animation of the object: its
269    /// composition object is disposed, which stops its animations for the
270    /// batches (WinUI).
271    pub fn release_completions(&self) {
272        let animations = self.animations.borrow().clone();
273        for instance in animations {
274            instance.animation.release_completion();
275        }
276    }
277
278    pub fn subscribe_to_invalidation(&self, member: &CompositionProperty, animation: Rc<dyn IAnimationInstance>) {
279        let store = match self.subscription(member) {
280            Some(store) => store,
281            None => {
282                let store = Rc::new(ServerObjectSubscriptionStore::new());
283                self.subscriptions.borrow_mut().push((member.id(), store.clone()));
284                store
285            }
286        };
287        let mut subscribers = store.subscribers.borrow_mut();
288        match subscribers.iter_mut().find(|(s, _)| same_animation(s, &animation)) {
289            Some(entry) => entry.1 += 1,
290            None => subscribers.push((animation, 1)),
291        }
292    }
293
294    pub fn unsubscribe_from_invalidation(&self, member: &CompositionProperty, animation: &Rc<dyn IAnimationInstance>) {
295        if let Some(store) = self.subscription(member) {
296            let mut subscribers = store.subscribers.borrow_mut();
297            if let Some(index) = subscribers.iter().position(|(s, _)| same_animation(s, animation)) {
298                subscribers[index].1 -= 1;
299                if subscribers[index].1 == 0 {
300                    subscribers.remove(index);
301                }
302            }
303        }
304    }
305
306    pub fn get_property_for_animation(&self, name: &str) -> ExpressionVariant {
307        let Some(owner) = self.owner.upgrade() else { return ExpressionVariant::default() };
308        let Some(property) = owner.get_composition_property(name) else { return ExpressionVariant::default() };
309        if let Some(subscriptions) = self.subscription(property) {
310            subscriptions.is_valid.set(true);
311        }
312        if let Some(animation) = self.animation(property) {
313            return self.get_variant(&animation);
314        }
315        owner.read_property_variant(property)
316    }
317
318    pub fn evaluate_animations(&self) {
319        let animations = self.animations.borrow().clone();
320        for animation in animations {
321            if animation.is_dirty.get() {
322                self.update_target_property(&animation);
323            }
324        }
325    }
326
327    /// `this` is the handle of these animations.
328    pub fn notify_animation_instance_invalidated(this: &Rc<Self>, property: &CompositionProperty) {
329        match this.animation(property) {
330            Some(instance) => {
331                instance.is_dirty.set(true);
332                instance.needs_update.set(true);
333                if let Some(compositor) = this.compositor.upgrade() {
334                    compositor.animations().add_dirty_animated_object(this.clone());
335                }
336            }
337            None => debug_assert!(false, "the property has no animation"),
338        }
339    }
340}