ferroui_base/rendering/composition/server/
server_object_animations.rs1use 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 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
43pub 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 instance.is_dirty.set(false);
76 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 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 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 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 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 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 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 pub fn stop_subchannel_animation(&self, property: &'static CompositionProperty, components: &[usize]) {
241 let Some(instance) = self.animation(property) else { return };
242 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 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 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 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}