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