Skip to main content

ferroui_base/rendering/composition/
composition_property_set.rs

1use super::animations::{ICompositionAnimation, PropertySetSnapshot, PropertySetSnapshotSource, PropertySetSnapshotSourceValue};
2use super::expressions::{ExpressionVariant, ExpressionVariantValue};
3use super::server::{CompositionProperty, PropertySetChanges, ServerCompositionPropertySet, ServerObjectId};
4use super::transport::BatchStreamWriter;
5use super::{AsCompositionObject, CompositionObject, Compositor, ICompositionObjectAnimations, ICompositorSerializable};
6use crate::media::Color;
7use crate::numerics::{Matrix3x2, Matrix4x4, Quaternion, Vector2, Vector3, Vector4};
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::rc::{Rc, Weak};
11
12/// Property sets are composition objects that allow storage of key values pairs
13/// that can be shared across the application and are not tied to the lifetime of another composition object.
14/// Property sets are most commonly used with animations, where they maintain key-value pairs
15/// that are referenced to drive portions of composition animations. Property sets
16/// provide the ability to insert key-value pairs or retrieve a value for a given key.
17/// A property set does not support a delete function – ensure you use property sets
18/// to store values that will be shared across the application.
19///
20/// A property set made with [`new`](Self::new) (or `Compositor::create_property_set`) is live,
21/// as in WinUI: it has a server counterpart that receives its entries with each batch, its
22/// entries can be animated with `start_animation`, and an expression that refers to it reads
23/// its current values. Upstream's property sets live on the UI thread only, and an expression
24/// sees a copy of them taken when its animation starts; the parameters of an animation are such
25/// a set (see `docs/porting/winui-composition.md`).
26pub struct CompositionPropertySet {
27    this: Weak<CompositionPropertySet>,
28    object: CompositionObject,
29    variants: RefCell<HashMap<String, ExpressionVariant>>,
30    objects: RefCell<HashMap<String, Rc<dyn AsCompositionObject>>>,
31    /// The values of a live set that the next batch carries.
32    pending: RefCell<PropertySetChanges>,
33    /// The entries whose animations the next batch starts; the animations themselves wait in
34    /// the pending animations of the composition object, by entry.
35    pending_animation_names: RefCell<Vec<String>>,
36}
37
38/// The result of reading a value of a [`CompositionPropertySet`].
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub enum CompositionGetValueStatus {
41    Succeeded,
42    TypeMismatch,
43    NotFound,
44}
45
46impl CompositionPropertySet {
47    /// Creates a live property set (`Compositor.CreatePropertySet`).
48    pub fn new(compositor: &Rc<Compositor>) -> Rc<CompositionPropertySet> {
49        let server = compositor.create_server_object(|compositor, _| ServerCompositionPropertySet::new(compositor));
50        Self::create(compositor, Some(server))
51    }
52
53    /// Creates a property set without a server counterpart: the parameters of an animation,
54    /// which are copied when the animation starts.
55    pub(crate) fn new_parameters(compositor: &Rc<Compositor>) -> Rc<CompositionPropertySet> {
56        Self::create(compositor, None)
57    }
58
59    fn create(compositor: &Rc<Compositor>, server: Option<ServerObjectId>) -> Rc<CompositionPropertySet> {
60        Rc::new_cyclic(|this| CompositionPropertySet {
61            this: this.clone(),
62            object: CompositionObject::new(compositor, server),
63            variants: RefCell::new(HashMap::new()),
64            objects: RefCell::new(HashMap::new()),
65            pending: RefCell::new(PropertySetChanges::default()),
66            pending_animation_names: RefCell::new(Vec::new()),
67        })
68    }
69
70    /// Whether the set is live (has a server counterpart).
71    pub fn is_live(&self) -> bool {
72        self.object.server().is_some()
73    }
74
75    fn register_for_serialization(&self) {
76        let this = self.this.clone();
77        self.object.register_for_serialization(move || {
78            this.upgrade().map(|this| this as Rc<dyn ICompositorSerializable>)
79        });
80    }
81
82    pub fn compositor(&self) -> &Rc<Compositor> {
83        self.object.compositor()
84    }
85
86    pub(crate) fn set(&self, key: &str, value: ExpressionVariant) {
87        self.objects.borrow_mut().remove(key);
88        self.variants.borrow_mut().insert(key.to_owned(), value);
89        if self.is_live() && !self.object.is_disposed() {
90            {
91                let mut pending = self.pending.borrow_mut();
92                // A value set after an animation was started in the same frame replaces it, as
93                // the value of a declared property does.
94                self.object.pending_animations().remove(CompositionProperty::dynamic(key));
95                pending.values.retain(|(name, _)| name != key);
96                pending.values.push((key.to_owned(), value));
97            }
98            self.register_for_serialization();
99        }
100    }
101
102    // For INTERNAL USE by CompositionAnimation ONLY, we DON'T support expression
103    // paths like SomeParam.SomePropertyObject.SomeValue
104    pub(crate) fn set_object(&self, key: &str, obj: Rc<dyn AsCompositionObject>) {
105        self.objects.borrow_mut().insert(key.to_owned(), obj);
106        self.variants.borrow_mut().remove(key);
107    }
108
109    pub fn insert_color(&self, property_name: &str, value: Color) {
110        self.set(property_name, value.into())
111    }
112
113    pub fn insert_matrix3x2(&self, property_name: &str, value: Matrix3x2) {
114        self.set(property_name, value.into())
115    }
116
117    pub fn insert_matrix4x4(&self, property_name: &str, value: Matrix4x4) {
118        self.set(property_name, value.into())
119    }
120
121    pub fn insert_quaternion(&self, property_name: &str, value: Quaternion) {
122        self.set(property_name, value.into())
123    }
124
125    pub fn insert_scalar(&self, property_name: &str, value: f32) {
126        self.set(property_name, value.into())
127    }
128
129    pub fn insert_vector2(&self, property_name: &str, value: Vector2) {
130        self.set(property_name, value.into())
131    }
132
133    pub fn insert_vector3(&self, property_name: &str, value: Vector3) {
134        self.set(property_name, value.into())
135    }
136
137    pub fn insert_vector4(&self, property_name: &str, value: Vector4) {
138        self.set(property_name, value.into())
139    }
140
141    fn try_get_variant<T: ExpressionVariantValue>(&self, key: &str) -> (CompositionGetValueStatus, T) {
142        let Some(v) = self.variants.borrow().get(key).copied() else {
143            let status = if self.objects.borrow().contains_key(key) {
144                CompositionGetValueStatus::TypeMismatch
145            } else {
146                CompositionGetValueStatus::NotFound
147            };
148            return (status, T::default());
149        };
150
151        match v.try_cast::<T>() {
152            Some(value) => (CompositionGetValueStatus::Succeeded, value),
153            None => (CompositionGetValueStatus::TypeMismatch, T::default()),
154        }
155    }
156
157    pub fn try_get_color(&self, property_name: &str) -> (CompositionGetValueStatus, Color) {
158        self.try_get_variant(property_name)
159    }
160
161    pub fn try_get_matrix3x2(&self, property_name: &str) -> (CompositionGetValueStatus, Matrix3x2) {
162        self.try_get_variant(property_name)
163    }
164
165    pub fn try_get_matrix4x4(&self, property_name: &str) -> (CompositionGetValueStatus, Matrix4x4) {
166        self.try_get_variant(property_name)
167    }
168
169    pub fn try_get_quaternion(&self, property_name: &str) -> (CompositionGetValueStatus, Quaternion) {
170        self.try_get_variant(property_name)
171    }
172
173    pub fn try_get_scalar(&self, property_name: &str) -> (CompositionGetValueStatus, f32) {
174        self.try_get_variant(property_name)
175    }
176
177    pub fn try_get_vector2(&self, property_name: &str) -> (CompositionGetValueStatus, Vector2) {
178        self.try_get_variant(property_name)
179    }
180
181    pub fn try_get_vector3(&self, property_name: &str) -> (CompositionGetValueStatus, Vector3) {
182        self.try_get_variant(property_name)
183    }
184
185    pub fn try_get_vector4(&self, property_name: &str) -> (CompositionGetValueStatus, Vector4) {
186        self.try_get_variant(property_name)
187    }
188
189    pub fn insert_boolean(&self, property_name: &str, value: bool) {
190        self.set(property_name, value.into())
191    }
192
193    pub fn try_get_boolean(&self, property_name: &str) -> (CompositionGetValueStatus, bool) {
194        self.try_get_variant(property_name)
195    }
196
197    pub(crate) fn clear_all(&self) {
198        self.objects.borrow_mut().clear();
199        self.variants.borrow_mut().clear();
200    }
201
202    pub(crate) fn clear(&self, key: &str) {
203        self.objects.borrow_mut().remove(key);
204        self.variants.borrow_mut().remove(key);
205    }
206
207    /// A snapshot read on the UI thread: a live property set it refers to is copied too, as the
208    /// UI thread cannot read its server counterpart.
209    pub(crate) fn snapshot(&self) -> PropertySetSnapshot {
210        self.snapshot_core(1, true).build()
211    }
212
213    /// The snapshot in the form that is sent to the server, which builds
214    /// the snapshot from it. A live property set it refers to is referred to by id.
215    pub(crate) fn snapshot_source(&self) -> PropertySetSnapshotSource {
216        self.snapshot_core(1, false)
217    }
218
219    fn snapshot_core(&self, allowed_nesting_level: i32, copy_live_sets: bool) -> PropertySetSnapshotSource {
220        let objects: Vec<(String, Rc<dyn AsCompositionObject>)> =
221            self.objects.borrow().iter().map(|(k, v)| (k.clone(), v.clone())).collect();
222        let variants = self.variants.borrow();
223        let mut dic = HashMap::with_capacity(objects.len() + variants.len());
224        for (key, value) in objects {
225            let type_name = value.composition_type_name();
226            let server = value.as_composition_object().server();
227            if let Some(ps) = value.clone().as_property_set().filter(|ps| copy_live_sets || !ps.is_live()) {
228                if allowed_nesting_level <= 0 {
229                    panic!("PropertySet depth limit reached");
230                }
231                dic.insert(
232                    key,
233                    PropertySetSnapshotSourceValue::PropertySet(ps.snapshot_core(allowed_nesting_level - 1, copy_live_sets)),
234                );
235            } else if let Some(server) = server {
236                dic.insert(key, PropertySetSnapshotSourceValue::Server(server));
237            } else {
238                panic!("Object of type {type_name} is not allowed");
239            }
240        }
241
242        for (key, value) in variants.iter() {
243            dic.insert(key.clone(), PropertySetSnapshotSourceValue::Variant(*value));
244        }
245
246        PropertySetSnapshotSource::new(dic)
247    }
248}
249
250impl AsCompositionObject for CompositionPropertySet {
251    fn as_composition_object(&self) -> &CompositionObject {
252        &self.object
253    }
254
255    fn composition_type_name(&self) -> &'static str {
256        "CompositionPropertySet"
257    }
258
259    fn as_property_set(self: Rc<Self>) -> Option<Rc<CompositionPropertySet>> {
260        Some(self)
261    }
262}
263
264impl ICompositorSerializable for CompositionPropertySet {
265    fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
266        self.object.try_get_server(c)
267    }
268
269    fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
270        self.object.begin_serialize_changes(c);
271        let mut changes = std::mem::take(&mut *self.pending.borrow_mut());
272        for name in std::mem::take(&mut *self.pending_animation_names.borrow_mut()) {
273            if let Some(factory) = self.object.pending_animations().get_and_remove(CompositionProperty::dynamic(&name)) {
274                changes.animations.push((name, factory));
275            }
276        }
277        writer.write_value(Some(changes));
278    }
279}
280
281impl ICompositionObjectAnimations for CompositionPropertySet {
282    /// Starts an animation of an entry of a live set. As in WinUI the entry must have been
283    /// inserted first; the animation starts from its current value.
284    fn try_start_animation(
285        &self,
286        property_name: &str,
287        animation: &dyn ICompositionAnimation,
288        final_value: Option<ExpressionVariant>,
289    ) -> bool {
290        let Some(server) = self.object.server() else { return false };
291        if !self.variants.borrow().contains_key(property_name) {
292            return false;
293        }
294        let factory = animation.create_instance(server, final_value);
295        self.object.pending_animations().set(CompositionProperty::dynamic(property_name), factory);
296        {
297            let mut names = self.pending_animation_names.borrow_mut();
298            if !names.iter().any(|name| name == property_name) {
299                names.push(property_name.to_owned());
300            }
301        }
302        self.register_for_serialization();
303        true
304    }
305
306    fn get_composition_property(&self, property_name: &str) -> Option<&'static CompositionProperty> {
307        self.variants.borrow().contains_key(property_name).then(|| CompositionProperty::dynamic(property_name))
308    }
309
310    fn composition_object(&self) -> &CompositionObject {
311        &self.object
312    }
313}