Skip to main content

ferroui_base/metadata/
typed_path.rs

1//! The typed path hook of a plain property declared in markup metadata.
2//!
3//! A compiled binding whose path is a single plain property of a model type
4//! is evaluated without boxing (`TypedBindingExpression<TSource, TValue>`)
5//! when the path element has typed accessors. Generated code builds such an
6//! element with `CompiledBindingPathBuilder::typed_property::<S, V>(..)`; a
7//! path built at run time from metadata gets it from
8//! [`MarkupProperty::typed_path_element`](super::MarkupProperty::typed_path_element),
9//! which the declaration macros generate for every `properties:` entry from
10//! its accessors.
11//!
12//! The hook produces the typed element when the instance type of the
13//! declaration (`this:`) is `Rc<S>` with `S: PartialEq` (a shared reference
14//! type, the `TSource : class` of the managed original) and the property
15//! type is a property value type: an element that follows the property
16//! change notifications of the source when `S: INotifyPropertyChanged`, one
17//! that reads the value once per source otherwise. For every other
18//! declaration (value types, handles of contracts, classes of the object
19//! model, and a property of the untyped value type) it produces nothing and the caller uses the untyped element. The
20//! choice is made by method resolution at the declaration (the most
21//! specific form that applies is selected), so a declaration needs no
22//! syntax for it.
23
24use crate::data::core::TypedClrPropertyInfo;
25use crate::data::model::INotifyPropertyChanged;
26use crate::data::{BindingError, CompiledBindingPathBuilder};
27use crate::PropertyValue;
28use std::marker::PhantomData;
29use std::rc::Rc;
30
31/// Adds the typed element of a plain property to a path under
32/// construction: `(builder, accepts_null) -> builder`, or `None` if the
33/// property has no typed form.
34pub type TypedPathElement = fn(&CompiledBindingPathBuilder, bool) -> Option<CompiledBindingPathBuilder>;
35
36/// The getter of a declared property, as the hook receives it.
37pub type TypedPathGetter<This, V> = Rc<dyn Fn(&This) -> Result<V, BindingError>>;
38/// The setter of a declared property, as the hook receives it.
39pub type TypedPathSetter<This, V> = Rc<dyn Fn(&This, V)>;
40
41/// Selects the typed form of a property of instance type `This` and
42/// property type `V`; see the [module documentation](self).
43#[doc(hidden)]
44pub struct TypedPathProbe<This, V>(PhantomData<(This, V)>);
45
46impl<This, V> TypedPathProbe<This, V> {
47    #[allow(clippy::new_without_default)]
48    pub const fn new() -> Self {
49        Self(PhantomData)
50    }
51}
52
53/// The declarations of a shared type that raises property change
54/// notifications (selected first).
55#[doc(hidden)]
56pub trait TypedPathNotifying<This, V> {
57    fn typed_path_element(
58        &self,
59        builder: &CompiledBindingPathBuilder,
60        accepts_null: bool,
61        name: &str,
62        get: Option<TypedPathGetter<This, V>>,
63        set: Option<TypedPathSetter<This, V>>,
64    ) -> Option<CompiledBindingPathBuilder>;
65}
66
67impl<S: INotifyPropertyChanged + PartialEq + 'static, V: PropertyValue> TypedPathNotifying<Rc<S>, V>
68    for &TypedPathProbe<Rc<S>, V>
69{
70    fn typed_path_element(
71        &self,
72        builder: &CompiledBindingPathBuilder,
73        accepts_null: bool,
74        name: &str,
75        get: Option<TypedPathGetter<Rc<S>, V>>,
76        set: Option<TypedPathSetter<Rc<S>, V>>,
77    ) -> Option<CompiledBindingPathBuilder> {
78        if is_untyped::<V>() {
79            return None;
80        }
81        let info = TypedClrPropertyInfo::<S, V>::from_handle_accessors(name, get, set);
82        Some(builder.typed_property_info_with(info, accepts_null))
83    }
84}
85
86/// The declarations of any other shared type (selected second).
87#[doc(hidden)]
88pub trait TypedPathShared<This, V> {
89    fn typed_path_element(
90        &self,
91        builder: &CompiledBindingPathBuilder,
92        accepts_null: bool,
93        name: &str,
94        get: Option<TypedPathGetter<This, V>>,
95        set: Option<TypedPathSetter<This, V>>,
96    ) -> Option<CompiledBindingPathBuilder>;
97}
98
99impl<S: PartialEq + 'static, V: PropertyValue> TypedPathShared<Rc<S>, V> for &&TypedPathProbe<Rc<S>, V> {
100    fn typed_path_element(
101        &self,
102        builder: &CompiledBindingPathBuilder,
103        accepts_null: bool,
104        name: &str,
105        get: Option<TypedPathGetter<Rc<S>, V>>,
106        set: Option<TypedPathSetter<Rc<S>, V>>,
107    ) -> Option<CompiledBindingPathBuilder> {
108        if is_untyped::<V>() {
109            return None;
110        }
111        let info = TypedClrPropertyInfo::<S, V>::from_handle_accessors(name, get, set);
112        Some(builder.typed_plain_property_info_with(info, accepts_null))
113    }
114}
115
116/// Whether `V` is the untyped value (the `object` of the managed original): a
117/// property of that type has no typed form. What a binding reads from it is
118/// the value the property holds, of whatever type that is, and a typed
119/// element would hand the box of the value over as the value.
120fn is_untyped<V: 'static>() -> bool {
121    let id = std::any::TypeId::of::<V>();
122    id == std::any::TypeId::of::<crate::BoxedValue>() || id == std::any::TypeId::of::<Option<crate::BoxedValue>>()
123}
124
125/// Every other declaration: no typed form (selected last).
126#[doc(hidden)]
127pub trait TypedPathFallback<This, V> {
128    fn typed_path_element(
129        &self,
130        _builder: &CompiledBindingPathBuilder,
131        _accepts_null: bool,
132        _name: &str,
133        _get: Option<TypedPathGetter<This, V>>,
134        _set: Option<TypedPathSetter<This, V>>,
135    ) -> Option<CompiledBindingPathBuilder> {
136        None
137    }
138}
139
140impl<This, V> TypedPathFallback<This, V> for TypedPathProbe<This, V> {}