Skip to main content

ferroui_base/data/core/
clr_property_info.rs

1use super::{IPropertyInfo, ValueType, ValueTypes};
2use crate::data::BindingError;
3use crate::{AnyValue, BoxedValue, PropertyValue};
4use std::marker::PhantomData;
5use std::rc::Rc;
6
7/// The untyped getter of a [`ClrPropertyInfo`].
8pub type PropertyGetter = Rc<dyn Fn(&dyn AnyValue) -> Option<BoxedValue>>;
9
10/// The untyped getter of a [`ClrPropertyInfo`] that can fail: an `Err` is the
11/// equivalent of the getter throwing (an index out of range, a missing key).
12pub type FalliblePropertyGetter = Rc<dyn Fn(&dyn AnyValue) -> Result<Option<BoxedValue>, BindingError>>;
13
14/// The untyped setter of a [`ClrPropertyInfo`].
15pub type PropertySetter = Rc<dyn Fn(&dyn AnyValue, Option<&BoxedValue>) -> Result<(), BindingError>>;
16
17/// A property description built from accessor closures. This is what
18/// compiled bindings use to read and write plain properties.
19pub struct ClrPropertyInfo {
20    name: Box<str>,
21    getter: Option<PropertyGetter>,
22    /// The getter of a property whose read can fail; used instead of `getter`.
23    fallible_getter: Option<FalliblePropertyGetter>,
24    setter: Option<PropertySetter>,
25    property_type: ValueType,
26    /// The type that declares the property, when the accessors are typed:
27    /// its name and the test of whether a value is of that type.
28    owner: Option<(&'static str, fn(&dyn AnyValue) -> bool)>,
29    /// Accessors that take the owner as an untyped handle; used by the boxed
30    /// forms of the contract instead of the accessors above.
31    boxed_getter: Option<BoxedPropertyGetter>,
32    boxed_setter: Option<BoxedPropertySetter>,
33}
34
35/// The getter of a [`ClrPropertyInfo`] that takes the owner as an untyped
36/// handle (see [`ClrPropertyInfo::with_boxed_accessors`]).
37pub type BoxedPropertyGetter = Rc<dyn Fn(&BoxedValue) -> Result<Option<BoxedValue>, BindingError>>;
38
39/// The setter of a [`ClrPropertyInfo`] that takes the owner as an untyped
40/// handle.
41pub type BoxedPropertySetter = Rc<dyn Fn(&BoxedValue, Option<&BoxedValue>) -> Result<(), BindingError>>;
42
43/// States how the typed value of a property maps to the untyped values that
44/// flow through a binding.
45pub trait PropertyKind: 'static {
46    /// The type the property's accessors work with.
47    type Typed;
48
49    /// The value type reported for the property.
50    fn value_type() -> ValueType;
51
52    /// Called once when a property of this kind is described.
53    fn register() {}
54
55    fn to_untyped(value: Self::Typed) -> Option<BoxedValue>;
56
57    fn from_untyped(value: Option<&BoxedValue>) -> Option<Self::Typed>;
58}
59
60/// A property of value type `T` that is never null.
61pub struct Value<T>(PhantomData<T>);
62
63impl<T: PropertyValue> PropertyKind for Value<T> {
64    type Typed = T;
65
66    fn value_type() -> ValueType {
67        ValueType::of::<T>()
68    }
69
70    #[inline]
71    fn to_untyped(value: T) -> Option<BoxedValue> {
72        Some(Rc::new(value))
73    }
74
75    fn from_untyped(value: Option<&BoxedValue>) -> Option<T> {
76        let value = value?;
77        if let Some(v) = value.downcast_ref::<T>() {
78            return Some(v.clone());
79        }
80        let converted = ValueTypes::try_convert(Some(value), ValueType::of::<T>())??;
81        converted.downcast_ref::<T>().cloned()
82    }
83}
84
85/// A property of type `Option<T>`: `None` is null in bindings.
86pub struct Maybe<T>(PhantomData<T>);
87
88impl<T: PropertyValue> PropertyKind for Maybe<T> {
89    type Typed = Option<T>;
90
91    fn value_type() -> ValueType {
92        ValueType::of::<Option<T>>()
93    }
94
95    fn register() {
96        ValueTypes::register_nullable::<T>();
97    }
98
99    #[inline]
100    fn to_untyped(value: Option<T>) -> Option<BoxedValue> {
101        value.map(|v| Rc::new(v) as BoxedValue)
102    }
103
104    fn from_untyped(value: Option<&BoxedValue>) -> Option<Option<T>> {
105        let Some(value) = value else { return Some(None) };
106        if let Some(v) = value.downcast_ref::<Option<T>>() {
107            return Some(v.clone());
108        }
109        Value::<T>::from_untyped(Some(value)).map(Some)
110    }
111}
112
113/// A property that refers to a shared model object: `Option<Rc<M>>`. The
114/// object itself travels through bindings (no copy, identity preserved).
115pub struct ModelRef<M>(PhantomData<M>);
116
117impl<M: PartialEq + 'static> PropertyKind for ModelRef<M> {
118    type Typed = Option<Rc<M>>;
119
120    fn value_type() -> ValueType {
121        ValueType::of::<M>()
122    }
123
124    fn register() {
125        ValueTypes::register_reference::<M>();
126    }
127
128    #[inline]
129    fn to_untyped(value: Option<Rc<M>>) -> Option<BoxedValue> {
130        value.map(|v| v as BoxedValue)
131    }
132
133    fn from_untyped(value: Option<&BoxedValue>) -> Option<Option<Rc<M>>> {
134        let Some(value) = value else { return Some(None) };
135        if let Some(v) = value.downcast_ref::<Option<Rc<M>>>() {
136            return Some(v.clone());
137        }
138        let any: Rc<dyn std::any::Any> = value.clone();
139        any.downcast::<M>().ok().map(Some)
140    }
141}
142
143/// A property holding an untyped value (`Option<BoxedValue>`), passed
144/// through as it is.
145pub struct Untyped;
146
147impl PropertyKind for Untyped {
148    type Typed = Option<BoxedValue>;
149
150    fn value_type() -> ValueType {
151        ValueType::object()
152    }
153
154    #[inline]
155    fn to_untyped(value: Option<BoxedValue>) -> Option<BoxedValue> {
156        value
157    }
158
159    fn from_untyped(value: Option<&BoxedValue>) -> Option<Option<BoxedValue>> {
160        match value {
161            None => Some(None),
162            Some(v) => match v.downcast_ref::<Option<BoxedValue>>() {
163                Some(inner) => Some(inner.clone()),
164                None => Some(Some(v.clone())),
165            },
166        }
167    }
168}
169
170fn invalid_cast(name: &str, value: Option<&BoxedValue>, target: ValueType) -> BindingError {
171    BindingError::message(format!(
172        "Object of type '{}' cannot be converted to type '{}' for property '{}'.",
173        value.map_or("null", |v| (**v).type_name()),
174        target,
175        name
176    ))
177}
178
179impl ClrPropertyInfo {
180    /// Creates a property description from untyped accessors.
181    pub fn new(
182        name: &str,
183        getter: Option<PropertyGetter>,
184        setter: Option<PropertySetter>,
185        property_type: ValueType,
186    ) -> Self {
187        Self { name: name.into(), getter, fallible_getter: None, setter, property_type, owner: None, boxed_getter: None, boxed_setter: None }
188    }
189
190    /// Creates a property description from untyped accessors, with a getter
191    /// that can fail.
192    pub fn new_fallible(
193        name: &str,
194        getter: Option<FalliblePropertyGetter>,
195        setter: Option<PropertySetter>,
196        property_type: ValueType,
197    ) -> Self {
198        Self { name: name.into(), getter: None, fallible_getter: getter, setter, property_type, owner: None, boxed_getter: None, boxed_setter: None }
199    }
200
201    /// Adds accessors that take the owner as an untyped handle. A property
202    /// description built over accessors that need the handle of the owner
203    /// and not only a view of it (the invokers of markup metadata) is read
204    /// and written through them by the boxed forms of the contract
205    /// ([`IPropertyInfo::get_boxed`], [`IPropertyInfo::try_get_boxed`],
206    /// [`IPropertyInfo::set_boxed`]).
207    pub fn with_boxed_accessors(
208        mut self,
209        getter: Option<BoxedPropertyGetter>,
210        setter: Option<BoxedPropertySetter>,
211    ) -> Self {
212        self.boxed_getter = getter;
213        self.boxed_setter = setter;
214        self
215    }
216
217    fn with_owner<O: 'static>(mut self) -> Self {
218        fn is_owner<O: 'static>(value: &dyn AnyValue) -> bool {
219            value.downcast_ref::<O>().is_some()
220        }
221        self.owner = Some((ValueType::of::<O>().name(), is_owner::<O>));
222        self
223    }
224
225    fn typed_getter<O: 'static, K: PropertyKind>(get: impl Fn(&O) -> K::Typed + 'static) -> PropertyGetter {
226        Rc::new(move |o: &dyn AnyValue| o.downcast_ref::<O>().and_then(|o| K::to_untyped(get(o))))
227    }
228
229    fn typed_fallible_getter<O: 'static, K: PropertyKind>(
230        get: impl Fn(&O) -> Result<K::Typed, BindingError> + 'static,
231    ) -> FalliblePropertyGetter {
232        Rc::new(move |o: &dyn AnyValue| match o.downcast_ref::<O>() {
233            Some(o) => get(o).map(K::to_untyped),
234            None => Ok(None),
235        })
236    }
237
238    fn typed_setter<O: 'static, K: PropertyKind>(
239        name: &str,
240        set: impl Fn(&O, K::Typed) -> Result<(), BindingError> + 'static,
241    ) -> PropertySetter {
242        let owned_name: Box<str> = name.into();
243        Rc::new(move |o: &dyn AnyValue, v: Option<&BoxedValue>| {
244            let Some(o) = o.downcast_ref::<O>() else {
245                return Err(BindingError::message("The property owner is of the wrong type."));
246            };
247            match K::from_untyped(v) {
248                Some(v) => set(o, v),
249                None => Err(invalid_cast(&owned_name, v, K::value_type())),
250            }
251        })
252    }
253
254    /// A read-only property of owner type `O` whose getter can fail, the way
255    /// the getter of an indexer throws for a bad index or key.
256    pub fn read_only_fallible<O: 'static, K: PropertyKind>(
257        name: &str,
258        get: impl Fn(&O) -> Result<K::Typed, BindingError> + 'static,
259    ) -> Self {
260        K::register();
261        Self::new_fallible(name, Some(Self::typed_fallible_getter::<O, K>(get)), None, K::value_type())
262            .with_owner::<O>()
263    }
264
265    /// A read-write property of owner type `O` whose getter and setter can
266    /// fail.
267    pub fn read_write_fallible<O: 'static, K: PropertyKind>(
268        name: &str,
269        get: impl Fn(&O) -> Result<K::Typed, BindingError> + 'static,
270        set: impl Fn(&O, K::Typed) -> Result<(), BindingError> + 'static,
271    ) -> Self {
272        K::register();
273        Self::new_fallible(
274            name,
275            Some(Self::typed_fallible_getter::<O, K>(get)),
276            Some(Self::typed_setter::<O, K>(name, set)),
277            K::value_type(),
278        )
279        .with_owner::<O>()
280    }
281
282    /// A read-only property of owner type `O`, from a typed getter.
283    pub fn read_only<O: 'static, K: PropertyKind>(name: &str, get: impl Fn(&O) -> K::Typed + 'static) -> Self {
284        K::register();
285        Self::new(name, Some(Self::typed_getter::<O, K>(get)), None, K::value_type()).with_owner::<O>()
286    }
287
288    /// A read-write property of owner type `O`, from typed accessors.
289    pub fn read_write<O: 'static, K: PropertyKind>(
290        name: &str,
291        get: impl Fn(&O) -> K::Typed + 'static,
292        set: impl Fn(&O, K::Typed) + 'static,
293    ) -> Self {
294        Self::read_write_validated::<O, K>(name, get, move |o, v| {
295            set(o, v);
296            Ok(())
297        })
298    }
299
300    /// A read-write property whose setter can reject values.
301    pub fn read_write_validated<O: 'static, K: PropertyKind>(
302        name: &str,
303        get: impl Fn(&O) -> K::Typed + 'static,
304        set: impl Fn(&O, K::Typed) -> Result<(), BindingError> + 'static,
305    ) -> Self {
306        K::register();
307        let setter = Self::typed_setter::<O, K>(name, set);
308        Self::new(name, Some(Self::typed_getter::<O, K>(get)), Some(setter), K::value_type()).with_owner::<O>()
309    }
310}
311
312impl IPropertyInfo for ClrPropertyInfo {
313    fn as_any(&self) -> Option<&dyn std::any::Any> {
314        Some(self)
315    }
316
317    fn name(&self) -> &str {
318        &self.name
319    }
320
321    fn get(&self, target: &dyn AnyValue) -> Option<BoxedValue> {
322        match (&self.getter, &self.fallible_getter) {
323            (Some(getter), _) => getter(target),
324            // The failure of a fallible getter is only visible through
325            // `try_get`.
326            (None, Some(getter)) => getter(target).ok().flatten(),
327            (None, None) => panic!("Property {} doesn't have a getter.", self.name),
328        }
329    }
330
331    fn try_get(&self, target: &dyn AnyValue) -> Result<Option<BoxedValue>, BindingError> {
332        if let Some((owner_name, is_owner)) = self.owner {
333            if !is_owner(target) {
334                return Err(BindingError::message(format!(
335                    "Unable to cast object of type '{}' to type '{}'.",
336                    target.type_name(),
337                    owner_name
338                )));
339            }
340        }
341        match &self.fallible_getter {
342            Some(getter) if self.getter.is_none() => getter(target),
343            _ => Ok(self.get(target)),
344        }
345    }
346
347    fn set(&self, target: &dyn AnyValue, value: Option<&BoxedValue>) -> Result<(), BindingError> {
348        match &self.setter {
349            Some(setter) => setter(target, value),
350            None => panic!("Property {} doesn't have a setter.", self.name),
351        }
352    }
353
354    fn get_boxed(&self, target: &BoxedValue) -> Option<BoxedValue> {
355        match &self.boxed_getter {
356            Some(getter) => getter(target).ok().flatten(),
357            None => self.get(&**target),
358        }
359    }
360
361    fn try_get_boxed(&self, target: &BoxedValue) -> Result<Option<BoxedValue>, BindingError> {
362        match &self.boxed_getter {
363            Some(getter) => getter(target),
364            None => self.try_get(&**target),
365        }
366    }
367
368    fn set_boxed(&self, target: &BoxedValue, value: Option<&BoxedValue>) -> Result<(), BindingError> {
369        match &self.boxed_setter {
370            Some(setter) => setter(target, value),
371            None => self.set(&**target, value),
372        }
373    }
374
375    fn can_set(&self) -> bool {
376        self.setter.is_some() || self.boxed_setter.is_some()
377    }
378
379    fn can_get(&self) -> bool {
380        self.getter.is_some() || self.fallible_getter.is_some() || self.boxed_getter.is_some()
381    }
382
383    fn property_type(&self) -> ValueType {
384        self.property_type
385    }
386}