Skip to main content

ferroui_markup/data/
binding.rs

1use ferroui_base::utilities::CultureInfo;
2use ferroui_base::controls::INameScope;
3use ferroui_base::data::converters::IValueConverter;
4use ferroui_base::data::core::parsers::TypeResolver;
5use ferroui_base::data::{
6    BindingBase, BindingExpressionBase, BindingMode, BindingPriority, ReflectionBinding, RelativeSource,
7    UpdateSourceTrigger,
8};
9use ferroui_base::{BoxedValue, FerroObject, FerroProperty, Ref, WeakRef};
10use std::ops::Deref;
11use std::rc::{Rc, Weak};
12
13/// A binding with a string path, resolved at run time. This is the binding
14/// that markup creates when bindings are not compiled.
15///
16/// The class derives from [`ReflectionBinding`], whose properties it has.
17/// Like it, a binding is a mutable reference object shared as `Rc<Binding>`,
18/// which converts to `Rc<dyn BindingBase>`; handles compare by identity.
19#[derive(Default)]
20pub struct Binding {
21    base: ReflectionBinding,
22}
23
24/// Compares by identity (reference equality), as the reference type this
25/// mirrors.
26impl PartialEq for Binding {
27    #[inline]
28    fn eq(&self, other: &Self) -> bool {
29        std::ptr::eq(self, other)
30    }
31}
32
33impl Binding {
34    /// Creates a binding without a path: binds to the source itself.
35    pub fn new() -> Rc<Self> {
36        Rc::new(Self::default())
37    }
38
39    /// Creates a binding with the given path.
40    pub fn with_path(path: &str) -> Rc<Self> {
41        Rc::new(Self { base: ReflectionBinding::construct(path) })
42    }
43
44    /// Creates a binding with the given path and mode.
45    pub fn with_path_and_mode(path: &str, mode: BindingMode) -> Rc<Self> {
46        Self::with_path(path).with_mode(mode)
47    }
48
49    /// The chaining form of `set_converter`, for a converter.
50    pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IValueConverter>) -> Rc<Self> {
51        self.set_converter(Some(converter));
52        self
53    }
54}
55
56/// The chaining forms of the setters of the base class.
57macro_rules! chaining_setters {
58    ($($with:ident => $set:ident: $ty:ty,)*) => {
59        impl Binding {
60            $(
61                /// The chaining form of the setter.
62                pub fn $with(self: Rc<Self>, value: $ty) -> Rc<Self> {
63                    self.$set(value);
64                    self
65                }
66            )*
67        }
68    };
69}
70
71chaining_setters! {
72    with_converter_value => set_converter: Option<Rc<dyn IValueConverter>>,
73    with_converter_culture => set_converter_culture: Option<CultureInfo>,
74    with_converter_parameter => set_converter_parameter: Option<BoxedValue>,
75    with_delay => set_delay: i32,
76    with_element_name => set_element_name: Option<String>,
77    with_fallback_value => set_fallback_value: Option<BoxedValue>,
78    with_mode => set_mode: BindingMode,
79    with_priority => set_priority: BindingPriority,
80    with_relative_source => set_relative_source: Option<Rc<RelativeSource>>,
81    with_source => set_source: Option<BoxedValue>,
82    with_string_format => set_string_format: Option<String>,
83    with_target_null_value => set_target_null_value: Option<BoxedValue>,
84    with_update_source_trigger => set_update_source_trigger: UpdateSourceTrigger,
85    with_type_resolver => set_type_resolver: Option<TypeResolver>,
86    with_default_anchor => set_default_anchor: Option<WeakRef<FerroObject>>,
87    with_name_scope => set_name_scope: Option<Weak<dyn INameScope>>,
88}
89
90impl Deref for Binding {
91    type Target = ReflectionBinding;
92
93    fn deref(&self) -> &ReflectionBinding {
94        &self.base
95    }
96}
97
98impl BindingBase for Binding {
99    fn create_instance(
100        &self,
101        target: &FerroObject,
102        target_property: Option<&'static FerroProperty>,
103        anchor: Option<&Ref<FerroObject>>,
104    ) -> Rc<dyn BindingExpressionBase> {
105        self.base.create_instance(target, target_property, anchor)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use ferroui_base::StyledElement;
113
114    #[test]
115    fn binding_with_empty_path_binds_to_data_context() {
116        let target = StyledElement::new();
117        let value: BoxedValue = Rc::new("foo".to_string());
118        target.set_data_context(Some(value));
119        let source = StyledElement::new();
120        // Bind one element's data context to the other's through `$parent`-free
121        // source binding: the path is empty, the source is explicit.
122        let binding = Binding::new();
123        binding.set_source(Some(Rc::new(target.clone().upcast::<FerroObject>())));
124        binding.set_path("DataContext".to_string());
125        source.bind_binding(StyledElement::data_context_property(), &binding);
126        let result = source.data_context().expect("a data context");
127        assert_eq!(result.downcast_ref::<String>().map(String::as_str), Some("foo"));
128    }
129
130    #[test]
131    fn malformed_path_is_reported_as_error() {
132        let target = StyledElement::new();
133        let binding = Binding::with_path("Foo.[");
134        assert!(binding.try_create_instance(&target, None, None).is_err());
135    }
136}