Skip to main content

ferroui_base/data/core/
i_property_info.rs

1use super::plugins::{get_property_value, property_value_type, set_property_value};
2use super::{ValueType, ValueTypes};
3use crate::data::{BindingError, BindingPriority};
4use crate::{AnyValue, BoxedValue, FerroObject, FerroProperty, FerroPropertyRegistry, Ref};
5
6/// Describes a property of an object that is not a registered property: its
7/// name, type and untyped accessors.
8pub trait IPropertyInfo {
9    /// The name of the property.
10    fn name(&self) -> &str;
11
12    /// Reads the property from `target`. Null is `None`.
13    fn get(&self, target: &dyn AnyValue) -> Option<BoxedValue>;
14
15    /// Reads the property from `target`. An `Err` is the equivalent of the
16    /// getter throwing, for example because `target` is not of the type that
17    /// declares the property.
18    fn try_get(&self, target: &dyn AnyValue) -> Result<Option<BoxedValue>, BindingError> {
19        Ok(self.get(target))
20    }
21
22    /// Writes the property on `target`. An `Err` is the equivalent of the
23    /// setter throwing.
24    fn set(&self, target: &dyn AnyValue, value: Option<&BoxedValue>) -> Result<(), BindingError>;
25
26    fn can_set(&self) -> bool;
27
28    fn can_get(&self) -> bool;
29
30    /// The type of the property's value.
31    fn property_type(&self) -> ValueType;
32
33    /// The implementing value, for casting to its concrete type
34    /// (`as_any()?.downcast_ref::<T>()`): the equivalent of `is`/`as` on the
35    /// contract. `None` for an implementation that does not expose itself.
36    fn as_any(&self) -> Option<&dyn std::any::Any> {
37        None
38    }
39
40    /// [`get`](Self::get) for an owner known as an untyped handle. The
41    /// accessors of binding paths read through the boxed forms, so that a
42    /// property description whose accessors need the handle of the owner
43    /// (not only a view of it) can override them; the defaults forward to
44    /// the forms taking a view. Override the three boxed forms together.
45    fn get_boxed(&self, target: &BoxedValue) -> Option<BoxedValue> {
46        self.get(&**target)
47    }
48
49    /// [`try_get`](Self::try_get) for an owner known as an untyped handle.
50    fn try_get_boxed(&self, target: &BoxedValue) -> Result<Option<BoxedValue>, BindingError> {
51        self.try_get(&**target)
52    }
53
54    /// [`set`](Self::set) for an owner known as an untyped handle.
55    fn set_boxed(&self, target: &BoxedValue, value: Option<&BoxedValue>) -> Result<(), BindingError> {
56        self.set(&**target, value)
57    }
58
59    /// The registered property this description is, if it is one (the cast
60    /// of a property description back to the registered property class).
61    fn as_ferro_property(&self) -> Option<&'static FerroProperty> {
62        None
63    }
64}
65
66/// A registered property is a property info: the untyped accessors read and
67/// write the property on an object of the class hierarchy.
68impl IPropertyInfo for FerroProperty {
69    fn as_any(&self) -> Option<&dyn std::any::Any> {
70        Some(self)
71    }
72
73    fn name(&self) -> &str {
74        FerroProperty::name(self)
75    }
76
77    /// Panics if `target` is not an object of the class hierarchy.
78    fn get(&self, target: &dyn AnyValue) -> Option<BoxedValue> {
79        match self.try_get(target) {
80            Ok(value) => value,
81            Err(error) => panic!("{error}"),
82        }
83    }
84
85    fn try_get(&self, target: &dyn AnyValue) -> Result<Option<BoxedValue>, BindingError> {
86        let object = target_object(self, target)?;
87        Ok(get_property_value(&object, registered(self)))
88    }
89
90    fn set(&self, target: &dyn AnyValue, value: Option<&BoxedValue>) -> Result<(), BindingError> {
91        let object = target_object(self, target)?;
92        set_property_value(&object, registered(self), value, BindingPriority::LocalValue)
93    }
94
95    fn can_set(&self) -> bool {
96        !self.is_read_only()
97    }
98
99    fn can_get(&self) -> bool {
100        true
101    }
102
103    fn property_type(&self) -> ValueType {
104        property_value_type(self)
105    }
106
107    fn as_ferro_property(&self) -> Option<&'static FerroProperty> {
108        Some(registered(self))
109    }
110}
111
112/// A registered property behind a shared handle (property definitions are
113/// `'static` and cannot themselves be put behind an `Rc`).
114struct RegisteredPropertyInfo(&'static FerroProperty);
115
116impl IPropertyInfo for RegisteredPropertyInfo {
117    fn name(&self) -> &str {
118        FerroProperty::name(self.0)
119    }
120
121    fn get(&self, target: &dyn AnyValue) -> Option<BoxedValue> {
122        IPropertyInfo::get(self.0, target)
123    }
124
125    fn try_get(&self, target: &dyn AnyValue) -> Result<Option<BoxedValue>, BindingError> {
126        IPropertyInfo::try_get(self.0, target)
127    }
128
129    fn set(&self, target: &dyn AnyValue, value: Option<&BoxedValue>) -> Result<(), BindingError> {
130        IPropertyInfo::set(self.0, target, value)
131    }
132
133    fn can_set(&self) -> bool {
134        IPropertyInfo::can_set(self.0)
135    }
136
137    fn can_get(&self) -> bool {
138        IPropertyInfo::can_get(self.0)
139    }
140
141    fn property_type(&self) -> ValueType {
142        IPropertyInfo::property_type(self.0)
143    }
144
145    fn as_ferro_property(&self) -> Option<&'static FerroProperty> {
146        Some(self.0)
147    }
148}
149
150impl FerroProperty {
151    /// The property as a shared property description
152    /// (`Rc<dyn IPropertyInfo>`), for the places that take a registered
153    /// property through the property description contract.
154    /// [`IPropertyInfo::as_ferro_property`] gives the property back.
155    pub fn as_property_info(&'static self) -> std::rc::Rc<dyn IPropertyInfo> {
156        std::rc::Rc::new(RegisteredPropertyInfo(self))
157    }
158}
159
160/// The registered (and so `'static`) definition of a property.
161fn registered(property: &FerroProperty) -> &'static FerroProperty {
162    FerroPropertyRegistry::instance()
163        .find_registered_by_id(property.id())
164        .expect("a property definition is registered when it is created")
165}
166
167fn target_object(property: &FerroProperty, target: &dyn AnyValue) -> Result<Ref<FerroObject>, BindingError> {
168    ValueTypes::as_object(target).ok_or_else(|| {
169        BindingError::message(format!(
170            "Unable to access Property '{}': the target ({}) is not an object.",
171            property.name(),
172            target.type_name()
173        ))
174    })
175}
176
177/// Handles compare by identity (reference equality), so that they can be
178/// held in property and untyped values.
179impl PartialEq for dyn IPropertyInfo {
180    #[inline]
181    fn eq(&self, other: &Self) -> bool {
182        std::ptr::addr_eq(self as *const Self, other as *const Self)
183    }
184}