Skip to main content

ferroui_base/data/
reflection_binding.rs

1use crate::controls::{INameScope, NameScopeRef};
2use crate::data::compiled_binding::{
3    binding_properties, create_data_context_node, explicit_source, resolve_defaults_from_metadata,
4};
5use crate::data::converters::IValueConverter;
6use crate::data::core::expression_nodes::{ExpressionNode, NamedElementNode};
7use crate::data::core::parsers::{BindingExpressionGrammar, ExpressionNodeFactory, TypeResolver};
8use crate::data::core::{BindingExpression, BindingExpressionOptions, ExpressionParseException, TargetTypeConverter};
9use crate::data::{
10    BindingBase, BindingExpressionBase, BindingMode, BindingPriority, RelativeSource, UpdateSourceTrigger,
11};
12use crate::utilities::CultureInfo;
13use crate::{BoxedValue, FerroObject, FerroProperty, Ref, WeakRef};
14use std::rc::{Rc, Weak};
15
16binding_properties! {
17    /// A binding whose path is text, resolved at run time against the
18    /// metadata that the objects along the path declared: registered
19    /// properties for objects of the class hierarchy, declared model metadata
20    /// for everything else.
21    ///
22    /// A binding is a mutable reference object: it is created with
23    /// [`ReflectionBinding::empty`] or [`ReflectionBinding::new`], configured
24    /// property by property (or with the chaining `with_*` forms) and shared
25    /// as `Rc<ReflectionBinding>`, which converts to `Rc<dyn BindingBase>`.
26    /// Handles compare by identity. The properties are read when the binding
27    /// is instantiated on a target: changing them afterwards affects later
28    /// instances only.
29    pub struct ReflectionBinding {
30        /// The converter to use.
31        converter / set_converter / with_converter_value: Option<Rc<dyn IValueConverter>> = None,
32        /// The culture in which to evaluate the converter. `None` (the
33        /// default) uses the current culture.
34        converter_culture / set_converter_culture / with_converter_culture: Option<CultureInfo> = None,
35        /// A parameter to pass to the converter.
36        converter_parameter / set_converter_parameter / with_converter_parameter: Option<BoxedValue> = None,
37        /// The amount of time, in milliseconds, to wait before updating the
38        /// binding source after the value on the target changes.
39        delay / set_delay / with_delay: i32 = 0,
40        /// The name of the element to use as the binding source.
41        element_name / set_element_name / with_element_name: Option<String> = None,
42        /// The value to use when the binding is unable to produce a value;
43        /// the unset marker for none.
44        fallback_value / set_fallback_value / with_fallback_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
45        /// The binding mode.
46        mode / set_mode / with_mode: BindingMode = BindingMode::Default,
47        /// The binding path.
48        path / set_path / with_path: String = String::new(),
49        /// The binding priority.
50        priority / set_priority / with_priority: BindingPriority = BindingPriority::LocalValue,
51        /// The relative source for the binding.
52        relative_source / set_relative_source / with_relative_source: Option<Rc<RelativeSource>> = None,
53        /// The source for the binding; the unset marker (the default) for
54        /// none.
55        source / set_source / with_source: Option<BoxedValue> = Some(FerroProperty::unset_value()),
56        /// The string format.
57        string_format / set_string_format / with_string_format: Option<String> = None,
58        /// The value to use when the binding produces null; the unset marker
59        /// for none.
60        target_null_value / set_target_null_value / with_target_null_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
61        /// What triggers updates of the binding source in two-way and
62        /// one-way-to-source bindings.
63        update_source_trigger / set_update_source_trigger / with_update_source_trigger: UpdateSourceTrigger = UpdateSourceTrigger::Default,
64        /// Resolves the type names in the path (casts, attached properties,
65        /// ancestor types).
66        type_resolver / set_type_resolver / with_type_resolver: Option<TypeResolver> = None,
67        /// The anchor to use if the target is not an element.
68        default_anchor / set_default_anchor / with_default_anchor: Option<WeakRef<FerroObject>> = None,
69        /// The name scope in which named elements are looked up.
70        name_scope / set_name_scope / with_name_scope: Option<Weak<dyn INameScope>> = None,
71    }
72}
73
74impl ReflectionBinding {
75    /// Creates a binding without a path (the parameterless constructor).
76    pub fn empty() -> Rc<Self> {
77        Rc::new(Self::default())
78    }
79
80    /// Creates a binding with the given path.
81    pub fn new(path: &str) -> Rc<Self> {
82        Rc::new(Self::construct(path))
83    }
84
85    /// The class data of a binding with the given path, for a type that
86    /// derives from this one.
87    pub fn construct(path: &str) -> Self {
88        let result = Self::default();
89        result.set_path(path.to_string());
90        result
91    }
92
93    /// The chaining form of [`set_converter`](Self::set_converter), for a
94    /// converter.
95    pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IValueConverter>) -> Rc<Self> {
96        self.with_converter_value(Some(converter))
97    }
98
99    fn get_name_scope(&self) -> Option<NameScopeRef> {
100        self.name_scope().as_ref().and_then(Weak::upgrade).map(NameScopeRef)
101    }
102
103    fn create_source_node(&self, target_property: Option<&'static FerroProperty>) -> Option<Rc<dyn ExpressionNode>> {
104        let element_name = self.element_name();
105        if let Some(element_name) = element_name.as_deref().filter(|n| !n.is_empty()) {
106            let name_scope =
107                self.get_name_scope().expect("Cannot create ElementName binding when NameScope is null");
108            return Some(NamedElementNode::new(Some(&name_scope), element_name));
109        }
110        if let Some(relative_source) = self.relative_source() {
111            return ExpressionNodeFactory::create_relative_source(&relative_source);
112        }
113        Some(create_data_context_node(target_property))
114    }
115
116    /// Instantiates the binding, reporting a malformed path as an error
117    /// instead of panicking.
118    pub fn try_create_instance(
119        &self,
120        target: &FerroObject,
121        target_property: Option<&'static FerroProperty>,
122        anchor: Option<&Ref<FerroObject>>,
123    ) -> Result<Rc<BindingExpression>, ExpressionParseException> {
124        let mut nodes: Vec<Rc<dyn ExpressionNode>> = Vec::new();
125        let mut is_rooted = false;
126        let enable_data_validation =
127            target_property.is_some_and(|p| p.get_metadata_for(target).enable_data_validation() == Some(true));
128
129        // Build the expression nodes from the binding path.
130        let path = self.path();
131        let explicit_source = explicit_source(self.source());
132        if !path.is_empty() {
133            let (ast, _source_mode) = BindingExpressionGrammar::parse(&path)?;
134            let name_scope = self.get_name_scope();
135            is_rooted = ExpressionNodeFactory::create_from_ast(
136                &ast,
137                self.type_resolver().as_ref(),
138                name_scope.as_ref(),
139                &mut nodes,
140            )?;
141        }
142
143        // If the binding isn't rooted (doesn't have a source or start with
144        // `$parent`, `$self`, `#elementName` etc.) a source node is needed.
145        // Its kind depends on the element name and relative source of the
146        // binding and defaults to a data context node.
147        if explicit_source.is_none() && !is_rooted {
148            if let Some(source_node) = self.create_source_node(target_property) {
149                nodes.insert(0, source_node);
150            }
151        }
152
153        // If the first node is a source node then allow it to select the
154        // source; otherwise use the binding source if specified, falling back
155        // to the target.
156        let target_ref = target.to_ref();
157        let default_anchor = self.default_anchor().as_ref().and_then(WeakRef::upgrade);
158        let anchor = anchor.or(default_anchor.as_ref());
159        let source = match nodes.first().and_then(|n| n.as_source_node()) {
160            Some(source_node) => source_node.select_source(explicit_source.as_ref(), &target_ref, anchor),
161            None => match &explicit_source {
162                Some(source) => source.clone(),
163                None => Some(Rc::new(target_ref.clone()) as BoxedValue),
164            },
165        };
166
167        let (mode, trigger) =
168            resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, target_property);
169
170        Ok(BindingExpression::new(
171            source,
172            nodes,
173            BindingExpressionOptions {
174                delay: self.delay(),
175                fallback_value: self.fallback_value(),
176                converter: self.converter(),
177                converter_culture: self.converter_culture(),
178                converter_parameter: self.converter_parameter(),
179                enable_data_validation,
180                mode,
181                priority: self.priority(),
182                string_format: self.string_format(),
183                target_null_value: self.target_null_value(),
184                target_property,
185                target_type_converter: Some(TargetTypeConverter::get_reflection_converter()),
186                update_source_trigger: trigger,
187            },
188        ))
189    }
190}
191
192impl BindingBase for ReflectionBinding {
193    fn as_any(&self) -> Option<&dyn std::any::Any> {
194        Some(self)
195    }
196
197    /// Panics if the path is malformed (a programmer error, as in the
198    /// managed implementation); use
199    /// [`try_create_instance`](ReflectionBinding::try_create_instance) for
200    /// paths that come from user input.
201    fn create_instance(
202        &self,
203        target: &FerroObject,
204        target_property: Option<&'static FerroProperty>,
205        anchor: Option<&Ref<FerroObject>>,
206    ) -> Rc<dyn BindingExpressionBase> {
207        match self.try_create_instance(target, target_property, anchor) {
208            Ok(expression) => expression,
209            Err(e) => panic!("{e}"),
210        }
211    }
212}