Skip to main content

ferroui_base/data/
compiled_binding.rs

1use crate::controls::INameScope;
2use crate::data::converters::IValueConverter;
3use crate::data::core::expression_nodes::ExpressionNode;
4use crate::data::core::parsers::ExpressionNodeFactory;
5use crate::data::compiled_binding_path::TypedPropertyElement;
6use crate::data::core::plugins::property_value_type;
7use crate::data::core::{BindingExpression, BindingExpressionOptions, TargetTypeConverter};
8use crate::data::{
9    BindingBase, BindingExpressionBase, BindingMode, BindingPriority, CompiledBindingPath, UpdateSourceTrigger,
10};
11use crate::utilities::CultureInfo;
12use crate::{BoxedValue, FerroObject, FerroProperty, Ref, StyledElement, WeakRef};
13use std::rc::{Rc, Weak};
14
15/// Resolves the binding mode and update source trigger defaults of a binding
16/// against the metadata of its target property.
17pub(crate) fn resolve_defaults_from_metadata(
18    mode: BindingMode,
19    update_source_trigger: UpdateSourceTrigger,
20    target: &FerroObject,
21    target_property: Option<&'static FerroProperty>,
22) -> (BindingMode, UpdateSourceTrigger) {
23    let trigger = if update_source_trigger == UpdateSourceTrigger::Default {
24        UpdateSourceTrigger::PropertyChanged
25    } else {
26        update_source_trigger
27    };
28    let mode = if mode == BindingMode::Default {
29        match target_property {
30            Some(p) => p.get_metadata_for(target).default_binding_mode(),
31            None => BindingMode::OneWay,
32        }
33    } else {
34        mode
35    };
36    (mode, trigger)
37}
38
39/// The data context source node for a binding to `target_property`.
40pub(crate) fn create_data_context_node(target_property: Option<&'static FerroProperty>) -> Rc<dyn ExpressionNode> {
41    ExpressionNodeFactory::create_data_context(target_property)
42}
43
44
45/// Declares the properties of a binding class: the fields (with interior
46/// mutability, as the binding is a mutable reference object), the getter and
47/// setter named after the property, and the chaining form of the setter used
48/// to configure a binding in one expression (`X::new(..).with_mode(..)`).
49macro_rules! binding_properties {
50    (
51        $(#[$struct_meta:meta])*
52        pub struct $name:ident {
53            $(
54                $(#[$meta:meta])*
55                $field:ident / $set:ident / $with:ident : $ty:ty = $default:expr,
56            )*
57        }
58    ) => {
59        $(#[$struct_meta])*
60        pub struct $name {
61            $($field: ::std::cell::RefCell<$ty>,)*
62        }
63
64        impl Default for $name {
65            fn default() -> Self {
66                Self { $($field: ::std::cell::RefCell::new($default),)* }
67            }
68        }
69
70        /// Compares by identity (reference equality), as the reference type
71        /// this mirrors.
72        impl PartialEq for $name {
73            #[inline]
74            fn eq(&self, other: &Self) -> bool {
75                ::std::ptr::eq(self, other)
76            }
77        }
78
79        impl $name {
80            /// An independent description for a development transaction. Keep it
81            /// only through preparation: explicit sources can own application objects.
82            #[cfg(all(debug_assertions, feature = "hot-reload"))]
83            pub fn snapshot_for_reload(&self) -> ::std::rc::Rc<Self> {
84                ::std::rc::Rc::new(Self {
85                    $($field: ::std::cell::RefCell::new(self.$field()),)*
86                })
87            }
88
89            $(
90                $(#[$meta])*
91                pub fn $field(&self) -> $ty {
92                    self.$field.borrow().clone()
93                }
94
95                pub fn $set(&self, value: $ty) {
96                    *self.$field.borrow_mut() = value;
97                }
98
99                /// The chaining form of the setter.
100                pub fn $with(self: ::std::rc::Rc<Self>, value: $ty) -> ::std::rc::Rc<Self> {
101                    self.$set(value);
102                    self
103                }
104            )*
105        }
106    };
107}
108pub(crate) use binding_properties;
109
110/// Whether an untyped binding value is the unset marker.
111pub(crate) fn is_unset(value: &Option<BoxedValue>) -> bool {
112    value.as_ref().is_some_and(|v| v.is::<crate::UnsetValueType>())
113}
114
115/// The source of a binding in the form the source nodes take: `None` when
116/// the source is the unset marker.
117pub(crate) fn explicit_source(source: Option<BoxedValue>) -> Option<Option<BoxedValue>> {
118    if is_unset(&source) {
119        None
120    } else {
121        Some(source)
122    }
123}
124
125binding_properties! {
126    /// A binding that uses compiled paths: typed accessors instead of member
127    /// lookup by name.
128    ///
129    /// A binding is a mutable reference object: it is created with
130    /// [`CompiledBinding::empty`] or [`CompiledBinding::new`], configured
131    /// property by property (or with the chaining `with_*` forms) and shared
132    /// as `Rc<CompiledBinding>`, which converts to `Rc<dyn BindingBase>`.
133    /// Handles compare by identity. The properties are read when the binding
134    /// is instantiated on a target: changing them afterwards affects later
135    /// instances only.
136    pub struct CompiledBinding {
137        /// The converter to use.
138        converter / set_converter / with_converter_value: Option<Rc<dyn IValueConverter>> = None,
139        /// The culture in which to evaluate the converter. `None` (the
140        /// default) uses the current culture.
141        converter_culture / set_converter_culture / with_converter_culture: Option<CultureInfo> = None,
142        /// A parameter to pass to the converter.
143        converter_parameter / set_converter_parameter / with_converter_parameter: Option<BoxedValue> = None,
144        /// The amount of time, in milliseconds, to wait before updating the
145        /// binding source after the value on the target changes.
146        delay / set_delay / with_delay: i32 = 0,
147        /// The value to use when the binding is unable to produce a value;
148        /// the unset marker for none.
149        fallback_value / set_fallback_value / with_fallback_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
150        /// The binding mode.
151        mode / set_mode / with_mode: BindingMode = BindingMode::Default,
152        /// The binding path.
153        path / set_path / with_path: Option<CompiledBindingPath> = None,
154        /// The binding priority.
155        priority / set_priority / with_priority: BindingPriority = BindingPriority::LocalValue,
156        /// The source for the binding; the unset marker (the default) binds
157        /// to the data context or to the element selected by the path.
158        source / set_source / with_source: Option<BoxedValue> = Some(FerroProperty::unset_value()),
159        /// The string format.
160        string_format / set_string_format / with_string_format: Option<String> = None,
161        /// The value to use when the binding produces null; the unset marker
162        /// for none.
163        target_null_value / set_target_null_value / with_target_null_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
164        /// What triggers updates of the binding source in two-way and
165        /// one-way-to-source bindings.
166        update_source_trigger / set_update_source_trigger / with_update_source_trigger: UpdateSourceTrigger = UpdateSourceTrigger::Default,
167        /// The anchor to use if the target is not an element.
168        default_anchor / set_default_anchor / with_default_anchor: Option<WeakRef<FerroObject>> = None,
169        /// The name scope of the place in markup where the binding was created.
170        name_scope / set_name_scope / with_name_scope: Option<Weak<dyn INameScope>> = None,
171    }
172}
173
174impl CompiledBinding {
175    /// Creates a binding without a path (the parameterless constructor).
176    pub fn empty() -> Rc<Self> {
177        Rc::new(Self::default())
178    }
179
180    /// Creates a binding with the given path.
181    pub fn new(path: CompiledBindingPath) -> Rc<Self> {
182        Self::empty().with_path(Some(path))
183    }
184
185    /// The chaining form of [`set_converter`](Self::set_converter), for a
186    /// converter.
187    pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IValueConverter>) -> Rc<Self> {
188        self.with_converter_value(Some(converter))
189    }
190}
191
192impl CompiledBinding {
193    /// Decides whether the binding can be instantiated as a typed binding
194    /// expression, and returns the path element that creates it.
195    fn typed_element_for(
196        &self,
197        target: &FerroObject,
198        target_property: Option<&'static FerroProperty>,
199    ) -> Option<Rc<dyn TypedPropertyElement>> {
200        // A path with a single typed property element is needed.
201        let typed = self.path.borrow().as_ref()?.single_typed_element()?.clone();
202
203        // A data context binding is needed.
204        if !is_unset(&self.source.borrow()) {
205            return None;
206        }
207
208        // It cannot have a converter, fallback value, string format, target
209        // null value or a non-default update source trigger.
210        if self.converter.borrow().is_some()
211            || !is_unset(&self.fallback_value.borrow())
212            || self.string_format.borrow().is_some()
213            || !is_unset(&self.target_null_value.borrow())
214            || !matches!(
215                self.update_source_trigger(),
216                UpdateSourceTrigger::Default | UpdateSourceTrigger::PropertyChanged
217            )
218            || self.delay() != 0
219        {
220            return None;
221        }
222
223        // The value must be directly assignable to the target property.
224        let target_property = target_property?;
225        let target_type = property_value_type(target_property);
226        if !crate::data::core::ValueTypes::is_assignable(typed.value_type(), target_type) {
227            return None;
228        }
229
230        // The typed expression listens for data context changes of a styled
231        // element.
232        if !target.is::<StyledElement>() {
233            return None;
234        }
235
236        // Data context bindings read their source from the parent of the
237        // target, which the typed expression does not support.
238        if target_property.id() == StyledElement::data_context_property().id() {
239            return None;
240        }
241
242        // The typed expression does not support data validation.
243        if target_property.get_metadata_for(target).enable_data_validation() == Some(true) {
244            return None;
245        }
246
247        // For modes that write back to the source, the source property must
248        // be settable and the target value must be assignable back to the
249        // source property type.
250        let (mode, _) =
251            resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, Some(target_property));
252        if matches!(mode, BindingMode::TwoWay | BindingMode::OneWayToSource)
253            && (!typed.can_set() || !crate::data::core::ValueTypes::is_assignable(target_type, typed.value_type()))
254        {
255            return None;
256        }
257
258        Some(typed)
259    }
260}
261
262impl BindingBase for CompiledBinding {
263    fn as_any(&self) -> Option<&dyn std::any::Any> {
264        Some(self)
265    }
266
267    fn create_instance(
268        &self,
269        target: &FerroObject,
270        target_property: Option<&'static FerroProperty>,
271        anchor: Option<&Ref<FerroObject>>,
272    ) -> Rc<dyn BindingExpressionBase> {
273        if let Some(typed) = self.typed_element_for(target, target_property) {
274            // The update source trigger is constrained to property-changed
275            // by the check, so only the mode needs to be resolved here.
276            let (mode, _) =
277                resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, target_property);
278            return typed.create_expression(mode, self.priority());
279        }
280
281        let enable_data_validation =
282            target_property.is_some_and(|p| p.get_metadata_for(target).enable_data_validation() == Some(true));
283        let mut nodes: Vec<Rc<dyn ExpressionNode>> = Vec::new();
284        let path = self.path();
285        let is_rooted = path.as_ref().is_some_and(|p| p.build_expression(&mut nodes));
286        let explicit_source = explicit_source(self.source());
287
288        // If the binding isn't rooted (doesn't have a source or start with an
289        // element that selects one) a data context source node is needed.
290        if explicit_source.is_none() && !is_rooted {
291            nodes.insert(0, create_data_context_node(target_property));
292        }
293
294        // If the first node is a source node then allow it to select the
295        // source; otherwise use the binding source if specified, falling back
296        // to the target.
297        let target_ref = target.to_ref();
298        let default_anchor = self.default_anchor().as_ref().and_then(WeakRef::upgrade);
299        let anchor = anchor.or(default_anchor.as_ref());
300        let source = match nodes.first().and_then(|n| n.as_source_node()) {
301            Some(source_node) => source_node.select_source(explicit_source.as_ref(), &target_ref, anchor),
302            None => match &explicit_source {
303                Some(source) => source.clone(),
304                None => Some(Rc::new(target_ref.clone()) as BoxedValue),
305            },
306        };
307
308        let (mode, trigger) =
309            resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, target_property);
310
311        BindingExpression::new(
312            source,
313            nodes,
314            BindingExpressionOptions {
315                delay: self.delay(),
316                fallback_value: self.fallback_value(),
317                converter: self.converter(),
318                converter_culture: self.converter_culture(),
319                converter_parameter: self.converter_parameter(),
320                enable_data_validation,
321                mode,
322                priority: self.priority(),
323                string_format: self.string_format(),
324                target_null_value: self.target_null_value(),
325                target_property,
326                target_type_converter: Some(TargetTypeConverter::get_default_converter()),
327                update_source_trigger: trigger,
328            },
329        )
330    }
331}