ferroui_markup/data/
binding.rs1use 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#[derive(Default)]
20pub struct Binding {
21 base: ReflectionBinding,
22}
23
24impl PartialEq for Binding {
27 #[inline]
28 fn eq(&self, other: &Self) -> bool {
29 std::ptr::eq(self, other)
30 }
31}
32
33impl Binding {
34 pub fn new() -> Rc<Self> {
36 Rc::new(Self::default())
37 }
38
39 pub fn with_path(path: &str) -> Rc<Self> {
41 Rc::new(Self { base: ReflectionBinding::construct(path) })
42 }
43
44 pub fn with_path_and_mode(path: &str, mode: BindingMode) -> Rc<Self> {
46 Self::with_path(path).with_mode(mode)
47 }
48
49 pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IValueConverter>) -> Rc<Self> {
51 self.set_converter(Some(converter));
52 self
53 }
54}
55
56macro_rules! chaining_setters {
58 ($($with:ident => $set:ident: $ty:ty,)*) => {
59 impl Binding {
60 $(
61 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 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}