Skip to main content

ferroui_base/
direct_property.rs

1use crate::data::{BindingPriority, BindingValue};
2use crate::ferro_property::{FerroPropertyInit, PropertyRoutes};
3use crate::ferro_property_metadata::MetadataTable;
4use crate::property_store::{EffectiveValueDyn, IValueEntry};
5use crate::reactive::{IDisposable, IObservable};
6use crate::{
7    BoxedValue, ObjectType, DirectPropertyMetadata, DoNothingType, FerroObject, FerroProperty,
8    FerroPropertyMetadata, FerroPropertyRegistry, PropertyValue, TypeInfo, UnsetValueType,
9};
10use std::any::Any;
11use std::cell::OnceCell;
12use std::ops::Deref;
13use std::rc::Rc;
14
15/// The owner-specific accessors of a direct property.
16pub(crate) trait DirectPropertyAccessor<T> {
17    fn invoke_getter(&self, instance: &FerroObject) -> T;
18    fn invoke_setter(&self, instance: &FerroObject, value: BindingValue<T>);
19}
20
21/// Base class for direct properties.
22///
23/// Holds everything about a direct property that does not depend on the type
24/// of its owner.
25pub struct DirectPropertyBase<T: PropertyValue> {
26    base: FerroProperty,
27    owner: &'static TypeInfo,
28    metadata: MetadataTable<DirectPropertyMetadata<T>>,
29    accessor: OnceCell<&'static dyn DirectPropertyAccessor<T>>,
30}
31
32impl<T: PropertyValue> Deref for DirectPropertyBase<T> {
33    type Target = FerroProperty;
34    #[inline]
35    fn deref(&self) -> &FerroProperty {
36        &self.base
37    }
38}
39
40impl<T: PropertyValue> DirectPropertyBase<T> {
41    /// Converts an untyped value of another type to the value type of the
42    /// property with the implicit conversions of the managed original
43    /// (the `TryConvert` of the property, see
44    /// [`ValueTypes::try_convert_implicit`](crate::data::core::ValueTypes::try_convert_implicit)).
45    fn convert_untyped(value: &dyn Any) -> Option<T> {
46        let converted = crate::data::core::ValueTypes::try_convert_implicit(value, std::any::TypeId::of::<T>())?;
47        let converted: &dyn crate::AnyValue = &*converted;
48        converted.downcast_ref::<T>().cloned()
49    }
50
51    /// The untyped base of the property.
52    #[inline]
53    pub fn as_property(&'static self) -> &'static FerroProperty {
54        &self.base
55    }
56
57    /// The class that registered this instance of the property.
58    #[inline]
59    pub fn owner(&self) -> &'static TypeInfo {
60        self.owner
61    }
62
63    #[inline]
64    fn accessor(&self) -> &'static dyn DirectPropertyAccessor<T> {
65        *self.accessor.get().expect("property is not fully registered")
66    }
67
68    /// Gets the value of the property on the instance.
69    #[inline]
70    pub(crate) fn invoke_getter(&self, instance: &FerroObject) -> T {
71        self.accessor().invoke_getter(instance)
72    }
73
74    /// Sets the value of the property on the instance.
75    #[inline]
76    pub(crate) fn invoke_setter(&self, instance: &FerroObject, value: BindingValue<T>) {
77        self.accessor().invoke_setter(instance, value)
78    }
79
80    /// Gets the unset value for the property on the specified type.
81    pub fn get_unset_value(&self, type_: &'static TypeInfo) -> T {
82        self.get_metadata(type_).unset_value().clone()
83    }
84
85    /// Gets the unset value for the property on the type of `owner`.
86    pub fn get_unset_value_for(&self, owner: &FerroObject) -> T {
87        self.get_unset_value(owner.get_type())
88    }
89
90    /// Gets the property metadata for the specified type.
91    pub fn get_metadata(&self, type_: &'static TypeInfo) -> Rc<DirectPropertyMetadata<T>> {
92        self.metadata.get(type_)
93    }
94
95    /// Gets the property metadata for the type of `owner`.
96    pub fn get_metadata_for(&self, owner: &FerroObject) -> Rc<DirectPropertyMetadata<T>> {
97        self.metadata.get(owner.get_type())
98    }
99
100    /// Overrides the metadata for the property on class `TOwner`.
101    pub fn override_metadata<TOwner: ObjectType>(&self, metadata: DirectPropertyMetadata<T>) {
102        self.override_metadata_for(TOwner::TYPE, metadata);
103    }
104
105    /// Overrides the metadata for the property on the specified type.
106    pub fn override_metadata_for(&self, type_: &'static TypeInfo, metadata: DirectPropertyMetadata<T>) {
107        self.metadata.override_metadata(self.name(), type_, metadata);
108    }
109
110    /// Removes the metadata registered for the specified type.
111    pub fn unregister(&self, type_: &'static TypeInfo) {
112        self.metadata.unregister(type_);
113    }
114
115    #[track_caller]
116    fn invalid_value(&self) -> ! {
117        self.base.invalid_value_type()
118    }
119}
120
121impl<T: PropertyValue> PropertyRoutes for DirectPropertyBase<T> {
122    fn create_effective_value(&'static self, _o: &FerroObject) -> Rc<dyn EffectiveValueDyn> {
123        panic!("direct properties have no effective value");
124    }
125
126    fn route_clear_value(&'static self, o: &FerroObject) {
127        o.clear_direct_value(self);
128    }
129
130    fn route_coerce_default_value(&'static self, _o: &FerroObject) {
131        // Direct properties are not coerced.
132    }
133
134    fn route_get_value(&'static self, o: &FerroObject) -> BoxedValue {
135        Rc::new(o.get_direct_value(self))
136    }
137
138    fn route_get_base_value(&'static self, o: &FerroObject) -> BoxedValue {
139        Rc::new(o.get_direct_value(self))
140    }
141
142    fn route_get_default_value(&'static self, type_: &'static TypeInfo) -> BoxedValue {
143        Rc::new(self.get_unset_value(type_))
144    }
145
146    fn route_set_value(
147        &'static self,
148        o: &FerroObject,
149        value: &dyn Any,
150        _priority: BindingPriority,
151    ) -> Option<Rc<dyn IDisposable>> {
152        let this = self;
153        if value.is::<UnsetValueType>() {
154            o.clear_direct_value(this);
155        } else if value.is::<DoNothingType>() {
156        } else if let Some(v) = value.downcast_ref::<T>() {
157            o.set_direct_value(this, v.clone());
158        } else if let Some(v) = Self::convert_untyped(value) {
159            o.set_direct_value(this, v);
160        } else {
161            self.invalid_value();
162        }
163        None
164    }
165
166    fn route_set_current_value(&'static self, o: &FerroObject, value: &dyn Any) {
167        self.route_set_value(o, value, BindingPriority::LocalValue);
168    }
169
170    fn route_set_direct_value_unchecked(&'static self, o: &FerroObject, entry: &dyn IValueEntry) {
171        let mut value: Option<T> = None;
172        if entry.try_get_value(&mut value) {
173            if let Some(value) = value {
174                o.set_direct_value_unchecked(self, BindingValue::new(value));
175            }
176        } else if let Some(boxed) = entry.get_value_boxed() {
177            // An untyped entry (a binding expression): the value is already
178            // converted to the property type, or is a marker. Data validation
179            // is reported separately by the expression's sink.
180            if let Some(value) = boxed.downcast_ref::<T>() {
181                self.invoke_setter(o, BindingValue::new(value.clone()));
182            } else if boxed.is::<UnsetValueType>() {
183                self.invoke_setter(o, BindingValue::new(self.get_unset_value_for(o)));
184            }
185        }
186    }
187
188    fn route_bind(
189        &'static self,
190        o: &FerroObject,
191        source: Rc<dyn IObservable<BoxedValue>>,
192        _priority: BindingPriority,
193    ) -> Rc<dyn IDisposable> {
194        o.bind_direct_untyped(self, source)
195    }
196
197    fn route_get_metadata(&'static self, type_: &'static TypeInfo) -> FerroPropertyMetadata {
198        let metadata = self.get_metadata(type_);
199        let base: &FerroPropertyMetadata = &metadata;
200        base.clone()
201    }
202
203    fn route_unregister(&'static self, type_: &'static TypeInfo) {
204        self.unregister(type_)
205    }
206
207    fn route_is_valid_value(&'static self, value: &dyn Any) -> bool {
208        value.is::<T>()
209    }
210
211    fn as_any(&'static self) -> &'static dyn Any {
212        self
213    }
214
215    fn route_copy_value(&'static self, value: &dyn Any, out: &mut dyn Any) -> bool {
216        match (value.downcast_ref::<T>(), out.downcast_mut::<Option<T>>()) {
217            (Some(value), Some(out)) => {
218                *out = Some(value.clone());
219                true
220            }
221            _ => false,
222        }
223    }
224
225    fn route_box_value(&'static self, value: &dyn Any) -> Option<BoxedValue> {
226        value.downcast_ref::<T>().map(|value| Rc::new(value.clone()) as BoxedValue)
227    }
228
229    fn route_create_binding_entry(
230        &'static self,
231        _o: &FerroObject,
232        _frame: std::rc::Weak<dyn crate::property_store::ValueFrame>,
233        _source: Rc<dyn IObservable<BoxedValue>>,
234    ) -> Rc<dyn IValueEntry> {
235        panic!("direct properties have no value entries");
236    }
237}
238
239/// A direct property: a property whose value is stored in a field of its
240/// owner and accessed through a getter and an optional setter.
241pub struct DirectProperty<TOwner: ObjectType, T: PropertyValue> {
242    base: DirectPropertyBase<T>,
243    getter: fn(&TOwner) -> T,
244    setter: Option<fn(&TOwner, T)>,
245}
246
247impl<TOwner: ObjectType, T: PropertyValue> Deref for DirectProperty<TOwner, T> {
248    type Target = DirectPropertyBase<T>;
249    #[inline]
250    fn deref(&self) -> &DirectPropertyBase<T> {
251        &self.base
252    }
253}
254
255impl<TOwner: ObjectType, T: PropertyValue> DirectProperty<TOwner, T> {
256    pub(crate) fn create(
257        name: &str,
258        getter: fn(&TOwner) -> T,
259        setter: Option<fn(&TOwner, T)>,
260        metadata: DirectPropertyMetadata<T>,
261    ) -> &'static Self {
262        let base = FerroProperty::new::<T>(
263            name,
264            TOwner::TYPE,
265            FerroPropertyInit { inherits: false, is_attached: false, is_direct: true, is_read_only: setter.is_none() },
266            None,
267        );
268        Self::leak(Self {
269            base: DirectPropertyBase {
270                base,
271                owner: TOwner::TYPE,
272                metadata: MetadataTable::new(TOwner::TYPE, metadata),
273                accessor: OnceCell::new(),
274            },
275            getter,
276            setter,
277        })
278    }
279
280    fn leak(property: Self) -> &'static Self {
281        let property: &'static Self = Box::leak(Box::new(property));
282        let _ = property.base.accessor.set(property);
283        property.base.base.set_routes(&property.base);
284        property
285    }
286
287    /// The getter function.
288    #[inline]
289    pub fn getter(&self) -> fn(&TOwner) -> T {
290        self.getter
291    }
292
293    /// The setter function, if the property is writable.
294    #[inline]
295    pub fn setter(&self) -> Option<fn(&TOwner, T)> {
296        self.setter
297    }
298
299    /// Registers the direct property on another class, with accessors for
300    /// that class. The new property shares the identity of this one.
301    pub fn add_owner<TNewOwner: ObjectType>(
302        &'static self,
303        getter: fn(&TNewOwner) -> T,
304        setter: Option<fn(&TNewOwner, T)>,
305        metadata: Option<DirectPropertyMetadata<T>>,
306    ) -> &'static DirectProperty<TNewOwner, T> {
307        let metadata = metadata.map(|mut m| {
308            use crate::ferro_property_metadata::PropertyMetadata;
309            m.merge(&self.base.metadata.get(TOwner::TYPE));
310            m.freeze();
311            m
312        });
313        let result = DirectProperty::<TNewOwner, T>::leak(DirectProperty {
314            base: DirectPropertyBase {
315                base: FerroProperty::for_added_owner(&self.base.base, TNewOwner::TYPE, setter.is_none()),
316                owner: TNewOwner::TYPE,
317                metadata: MetadataTable::for_added_owner(&self.base.metadata, TNewOwner::TYPE, metadata),
318                accessor: OnceCell::new(),
319            },
320            getter,
321            setter,
322        });
323        FerroPropertyRegistry::instance().register(TNewOwner::TYPE, result);
324        result
325    }
326
327    fn owner_of<'a>(&self, instance: &'a FerroObject) -> &'a TOwner {
328        instance.downcast_ref::<TOwner>().unwrap_or_else(|| {
329            panic!(
330                "property '{}' is registered on {}, which {} does not derive from",
331                self.name(),
332                TOwner::TYPE,
333                instance.get_type()
334            )
335        })
336    }
337}
338
339impl<TOwner: ObjectType, T: PropertyValue> DirectPropertyAccessor<T> for DirectProperty<TOwner, T> {
340    fn invoke_getter(&self, instance: &FerroObject) -> T {
341        (self.getter)(self.owner_of(instance))
342    }
343
344    fn invoke_setter(&self, instance: &FerroObject, value: BindingValue<T>) {
345        let Some(setter) = self.setter else {
346            panic!("The property {} is readonly.", self.name());
347        };
348        if let Some(value) = value.into_option() {
349            setter(self.owner_of(instance), value);
350        }
351    }
352}
353
354/// Type-erased view of a direct property used by the registry.
355pub(crate) trait DirectPropertyDyn: 'static {
356    fn as_property(&self) -> &FerroProperty;
357    fn as_any(&self) -> &dyn Any;
358}
359
360impl<T: PropertyValue> DirectPropertyDyn for DirectPropertyBase<T> {
361    fn as_property(&self) -> &FerroProperty {
362        &self.base
363    }
364
365    fn as_any(&self) -> &dyn Any {
366        self
367    }
368}