Skip to main content

ferroui_base/rendering/composition/server/
server_object_animations.rs

1use super::{CompositionProperty, IAnimatedServerObject, ServerCompositor};
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        let now = self.compositor.upgrade().map_or(Duration::ZERO, |c| c.server_now());
77        let value = instance.animation.evaluate(now, instance.cached_variant.get());
78        instance.cached_variant.set(value);
79        value
80    }
81
82    fn update_target_property(&self, instance: &ServerObjectAnimationInstance) {
83        if !instance.needs_update.get() {
84            return;
85        }
86        instance.needs_update.set(false);
87        let Some(owner) = self.owner.upgrade() else { return };
88        let value = self.get_variant(instance);
89        if let Some(set) = instance.property.set_variant() {
90            set(owner.as_server_object_dyn(), value);
91        }
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        for instance in animations {
99            instance.animation.activate();
100        }
101    }
102
103    pub fn deactivated(&self) {
104        let animations = self.animations.borrow().clone();
105        for instance in animations {
106            instance.animation.deactivate();
107        }
108    }
109
110    pub fn on_set_direct_value(&self, property: &CompositionProperty) {
111        if let Some(subscriptions) = self.subscription(property) {
112            subscriptions.invalidate();
113        }
114    }
115
116    pub fn on_set_animated_value(
117        &self,
118        property: &'static CompositionProperty,
119        current_value: ExpressionVariant,
120        committed_at: Duration,
121        animation: Rc<dyn IAnimationInstance>,
122    ) {
123        let is_active = self.owner_is_active();
124        let old = self.animation(property);
125        if let (true, Some(old)) = (is_active, &old) {
126            old.animation.deactivate();
127        }
128        {
129            let mut animations = self.animations.borrow_mut();
130            animations.retain(|a| a.property.id() != property.id());
131            animations.push(Rc::new(ServerObjectAnimationInstance {
132                property,
133                cached_variant: Cell::new(ExpressionVariant::default()),
134                is_dirty: Cell::new(true),
135                needs_update: Cell::new(true),
136                animation: animation.clone(),
137            }));
138        }
139        // The instance names the objects it works with by id; they are
140        // resolved here, on the server, before it is initialized.
141        if let Some(compositor) = self.compositor.upgrade() {
142            animation.resolve(&compositor);
143        }
144        animation.initialize(committed_at, current_value, property);
145        if is_active {
146            animation.activate();
147        }
148        self.on_set_direct_value(property);
149    }
150
151    pub fn remove_animation_for_property(&self, property: &CompositionProperty) {
152        let removed = {
153            let mut animations = self.animations.borrow_mut();
154            let index = animations.iter().position(|a| a.property.id() == property.id());
155            index.map(|index| animations.remove(index))
156        };
157        if let Some(removed) = removed {
158            if self.owner_is_active() {
159                removed.animation.deactivate();
160            }
161        }
162        self.on_set_direct_value(property);
163    }
164
165    pub fn subscribe_to_invalidation(&self, member: &CompositionProperty, animation: Rc<dyn IAnimationInstance>) {
166        let store = match self.subscription(member) {
167            Some(store) => store,
168            None => {
169                let store = Rc::new(ServerObjectSubscriptionStore::new());
170                self.subscriptions.borrow_mut().push((member.id(), store.clone()));
171                store
172            }
173        };
174        let mut subscribers = store.subscribers.borrow_mut();
175        match subscribers.iter_mut().find(|(s, _)| same_animation(s, &animation)) {
176            Some(entry) => entry.1 += 1,
177            None => subscribers.push((animation, 1)),
178        }
179    }
180
181    pub fn unsubscribe_from_invalidation(&self, member: &CompositionProperty, animation: &Rc<dyn IAnimationInstance>) {
182        if let Some(store) = self.subscription(member) {
183            let mut subscribers = store.subscribers.borrow_mut();
184            if let Some(index) = subscribers.iter().position(|(s, _)| same_animation(s, animation)) {
185                subscribers[index].1 -= 1;
186                if subscribers[index].1 == 0 {
187                    subscribers.remove(index);
188                }
189            }
190        }
191    }
192
193    pub fn get_property_for_animation(&self, name: &str) -> ExpressionVariant {
194        let Some(owner) = self.owner.upgrade() else { return ExpressionVariant::default() };
195        let Some(property) = owner.get_composition_property(name) else { return ExpressionVariant::default() };
196        if let Some(subscriptions) = self.subscription(property) {
197            subscriptions.is_valid.set(true);
198        }
199        if let Some(animation) = self.animation(property) {
200            return self.get_variant(&animation);
201        }
202        property.get_variant().map(|get| get(owner.as_server_object_dyn())).unwrap_or_default()
203    }
204
205    pub fn evaluate_animations(&self) {
206        let animations = self.animations.borrow().clone();
207        for animation in animations {
208            if animation.is_dirty.get() {
209                self.update_target_property(&animation);
210            }
211        }
212    }
213
214    /// `this` is the handle of these animations.
215    pub fn notify_animation_instance_invalidated(this: &Rc<Self>, property: &CompositionProperty) {
216        match this.animation(property) {
217            Some(instance) => {
218                instance.is_dirty.set(true);
219                instance.needs_update.set(true);
220                if let Some(compositor) = this.compositor.upgrade() {
221                    compositor.animations().add_dirty_animated_object(this.clone());
222                }
223            }
224            None => debug_assert!(false, "the property has no animation"),
225        }
226    }
227}