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
88impl PartialEq for CompositionProperty {
89    fn eq(&self, other: &Self) -> bool {
90        self.id == other.id
91    }
92}
93
94impl Eq for CompositionProperty {}
95
96impl std::hash::Hash for CompositionProperty {
97    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
98        self.id.hash(state);
99    }
100}
101
102impl std::fmt::Debug for CompositionProperty {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(f, "{}.{}#{}", self.owner_name, self.name, self.id)
105    }
106}
107
108/// A [`CompositionProperty`] with typed access to its field (upstream
109/// `CompositionProperty<T>`).
110pub struct CompositionPropertyOf<T> {
111    base: CompositionProperty,
112    get_field: fn(&dyn IServerObject) -> T,
113    set_field: fn(&dyn IServerObject, T),
114}
115
116impl<T> CompositionPropertyOf<T> {
117    /// The untyped property.
118    pub fn base(&self) -> &CompositionProperty {
119        &self.base
120    }
121
122    /// Reads the field of the property.
123    pub fn get_field(&self, obj: &dyn IServerObject) -> T {
124        (self.get_field)(obj)
125    }
126
127    /// Writes the field of the property, without any notification.
128    pub fn set_field(&self, obj: &dyn IServerObject, value: T) {
129        (self.set_field)(obj, value)
130    }
131}
132
133impl<T> std::ops::Deref for CompositionPropertyOf<T> {
134    type Target = CompositionProperty;
135
136    fn deref(&self) -> &CompositionProperty {
137        &self.base
138    }
139}
140
141/// The generated property block `P` embedded in a server object.
142///
143/// Panics if the object does not embed one: properties are only used with
144/// objects of the class that registered them.
145pub fn props_of<P: Any>(obj: &dyn IServerObject) -> &P {
146    match obj.get_props(TypeId::of::<P>()).and_then(|props| props.downcast_ref::<P>()) {
147        Some(props) => props,
148        None => panic!("the server object does not have the properties of {}", std::any::type_name::<P>()),
149    }
150}