Skip to main content

ferroui_base/rendering/composition/server/
composition_property.rs

1use super::IServerObject;
2use crate::rendering::composition::expressions::ExpressionVariant;
3use std::any::{Any, TypeId};
4use std::sync::atomic::{AtomicI32, Ordering};
5
6static NEXT_ID: AtomicI32 = AtomicI32::new(1);
7
8/// The identity of a property of a server-side composition object: what
9/// animations, expressions and change notifications refer to.
10///
11/// Properties are registered once per process and are plain data (ids,
12/// names and function pointers), so the UI thread and the render thread
13/// share them.
14pub struct CompositionProperty {
15    id: i32,
16    name: &'static str,
17    owner: TypeId,
18    owner_name: &'static str,
19    get_variant: Option<fn(&dyn IServerObject) -> ExpressionVariant>,
20    set_variant: Option<fn(&dyn IServerObject, ExpressionVariant)>,
21}
22
23impl CompositionProperty {
24    fn new(
25        id: i32,
26        name: &'static str,
27        owner: TypeId,
28        owner_name: &'static str,
29        get_variant: Option<fn(&dyn IServerObject) -> ExpressionVariant>,
30        set_variant: Option<fn(&dyn IServerObject, ExpressionVariant)>,
31    ) -> Self {
32        Self { id, name, owner, owner_name, get_variant, set_variant }
33    }
34
35    pub fn id(&self) -> i32 {
36        self.id
37    }
38
39    pub fn name(&self) -> &'static str {
40        self.name
41    }
42
43    /// The type that declares the property: the generated property block
44    /// of the server class.
45    pub fn owner(&self) -> TypeId {
46        self.owner
47    }
48
49    /// The name of the server class that declares the property.
50    pub fn owner_name(&self) -> &'static str {
51        self.owner_name
52    }
53
54    /// Reads the property of an object as an expression variant; `None` for
55    /// a property whose type has no variant form.
56    pub fn get_variant(&self) -> Option<fn(&dyn IServerObject) -> ExpressionVariant> {
57        self.get_variant
58    }
59
60    /// Stores an expression variant into the field of the property, without
61    /// any notification; `None` for a property that cannot be animated.
62    ///
63    /// This is the type-erased form of `set_field` an animation uses to
64    /// write the value it evaluated.
65    pub fn set_variant(&self) -> Option<fn(&dyn IServerObject, ExpressionVariant)> {
66        self.set_variant
67    }
68
69    /// Registers a property of the server class whose generated property
70    /// block is `TOwner`.
71    pub fn register<TOwner: Any, TField>(
72        name: &'static str,
73        owner_name: &'static str,
74        get_field: fn(&dyn IServerObject) -> TField,
75        set_field: fn(&dyn IServerObject, TField),
76        get_variant: Option<fn(&dyn IServerObject) -> ExpressionVariant>,
77        set_variant: Option<fn(&dyn IServerObject, ExpressionVariant)>,
78    ) -> CompositionPropertyOf<TField> {
79        let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
80        CompositionPropertyOf {
81            base: CompositionProperty::new(id, name, TypeId::of::<TOwner>(), owner_name, get_variant, set_variant),
82            get_field,
83            set_field,
84        }
85    }
86}
87
88/// The dynamic properties by name (see [`CompositionProperty::dynamic`]).
89static DYNAMIC_PROPERTIES: std::sync::OnceLock<
90    std::sync::Mutex<std::collections::HashMap<String, &'static CompositionProperty>>,
91> = std::sync::OnceLock::new();
92
93impl CompositionProperty {
94    /// The property of a property set named `name`: the entries of a `CompositionPropertySet`
95    /// are animatable properties that are not declared by a class. They are registered once per
96    /// process and name, like the declared properties, so that the animations of the server and
97    /// the subscriptions of expressions can refer to them by identity.
98    ///
99    /// A dynamic property has no field accessors (`get_variant` and `set_variant` are `None`):
100    /// the object that holds it reads and writes it through
101    /// [`IAnimatedServerObject::read_property_variant`](super::IAnimatedServerObject::read_property_variant)
102    /// and [`write_property_variant`](super::IAnimatedServerObject::write_property_variant).
103    ///
104    /// Not from upstream, whose property sets live on the UI thread only (see
105    /// `docs/porting/winui-composition.md`). Each distinct name stays registered for the life of
106    /// the process; the names come from the keys of property sets and from expressions.
107    pub fn dynamic(name: &str) -> &'static CompositionProperty {
108        let mut properties = DYNAMIC_PROPERTIES
109            .get_or_init(Default::default)
110            .lock()
111            .unwrap_or_else(std::sync::PoisonError::into_inner);
112        if let Some(property) = properties.get(name) {
113            return property;
114        }
115        let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
116        let name: &'static str = Box::leak(name.to_owned().into_boxed_str());
117        let property: &'static CompositionProperty = Box::leak(Box::new(CompositionProperty::new(
118            id,
119            name,
120            TypeId::of::<DynamicPropertyOwner>(),
121            "CompositionPropertySet",
122            None,
123            None,
124        )));
125        properties.insert(name.to_owned(), property);
126        property
127    }
128
129    /// Whether the property is an entry of a property set
130    /// ([`dynamic`](Self::dynamic)).
131    pub fn is_dynamic(&self) -> bool {
132        self.owner == TypeId::of::<DynamicPropertyOwner>()
133    }
134}
135
136/// The owner type of the dynamic properties.
137struct DynamicPropertyOwner;
138
139impl PartialEq for CompositionProperty {
140    fn eq(&self, other: &Self) -> bool {
141        self.id == other.id
142    }
143}
144
145impl Eq for CompositionProperty {}
146
147impl std::hash::Hash for CompositionProperty {
148    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
149        self.id.hash(state);
150    }
151}
152
153impl std::fmt::Debug for CompositionProperty {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        write!(f, "{}.{}#{}", self.owner_name, self.name, self.id)
156    }
157}
158
159/// A [`CompositionProperty`] with typed access to its field (upstream
160/// `CompositionProperty<T>`).
161pub struct CompositionPropertyOf<T> {
162    base: CompositionProperty,
163    get_field: fn(&dyn IServerObject) -> T,
164    set_field: fn(&dyn IServerObject, T),
165}
166
167impl<T> CompositionPropertyOf<T> {
168    /// The untyped property.
169    pub fn base(&self) -> &CompositionProperty {
170        &self.base
171    }
172
173    /// Reads the field of the property.
174    pub fn get_field(&self, obj: &dyn IServerObject) -> T {
175        (self.get_field)(obj)
176    }
177
178    /// Writes the field of the property, without any notification.
179    pub fn set_field(&self, obj: &dyn IServerObject, value: T) {
180        (self.set_field)(obj, value)
181    }
182}
183
184impl<T> std::ops::Deref for CompositionPropertyOf<T> {
185    type Target = CompositionProperty;
186
187    fn deref(&self) -> &CompositionProperty {
188        &self.base
189    }
190}
191
192/// The generated property block `P` embedded in a server object.
193///
194/// Panics if the object does not embed one: properties are only used with
195/// objects of the class that registered them.
196pub fn props_of<P: Any>(obj: &dyn IServerObject) -> &P {
197    match obj.get_props(TypeId::of::<P>()).and_then(|props| props.downcast_ref::<P>()) {
198        Some(props) => props,
199        None => panic!("the server object does not have the properties of {}", std::any::type_name::<P>()),
200    }
201}