Skip to main content

ferroui_base/data/
multi_binding.rs

1use crate::collections::FerroList;
2use crate::data::compiled_binding::binding_properties;
3use crate::data::converters::{IMultiValueConverter, StringFormatMultiValueConverter};
4use crate::data::core::plugins::property_value_type;
5use crate::data::core::{MultiBindingExpression, ValueType};
6use crate::data::{BindingBase, BindingExpressionBase, BindingMode, BindingPriority, RelativeSource};
7use crate::utilities::CultureInfo;
8use crate::{BoxedValue, FerroObject, FerroProperty, Ref};
9use std::rc::Rc;
10
11binding_properties! {
12    /// A binding that aggregates multiple bindings through a converter.
13    ///
14    /// A binding is a mutable reference object: it is created with
15    /// [`MultiBinding::new`], configured property by property (or with the
16    /// chaining `with_*` forms) and shared as `Rc<MultiBinding>`, which
17    /// converts to `Rc<dyn BindingBase>`. Handles compare by identity. The
18    /// properties, and the contents of the collection of child bindings, are
19    /// read when the binding is instantiated on a target: changing them
20    /// afterwards affects later instances only.
21    pub struct MultiBinding {
22        /// The collection of child bindings. The list is a shared handle:
23        /// children are added to the list the getter returns.
24        bindings / set_bindings / with_bindings_list: FerroList<Rc<dyn BindingBase>> = FerroList::new(),
25        /// The converter to use.
26        converter / set_converter / with_converter_value: Option<Rc<dyn IMultiValueConverter>> = None,
27        /// The culture in which to evaluate the converter. `None` (the
28        /// default) uses the current culture.
29        converter_culture / set_converter_culture / with_converter_culture: Option<CultureInfo> = None,
30        /// A parameter to pass to the converter.
31        converter_parameter / set_converter_parameter / with_converter_parameter: Option<BoxedValue> = None,
32        /// The value to use when the binding is unable to produce a value;
33        /// the unset marker for none.
34        fallback_value / set_fallback_value / with_fallback_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
35        /// The binding mode. Only one-time and one-way are supported.
36        mode / set_mode / with_mode: BindingMode = BindingMode::OneWay,
37        /// The binding priority.
38        priority / set_priority / with_priority: BindingPriority = BindingPriority::LocalValue,
39        /// The relative source for the binding.
40        relative_source / set_relative_source / with_relative_source: Option<Rc<RelativeSource>> = None,
41        /// The string format.
42        string_format / set_string_format / with_string_format: Option<String> = None,
43        /// The value to use when the binding produces null; the unset marker
44        /// for none.
45        target_null_value / set_target_null_value / with_target_null_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
46    }
47}
48
49impl MultiBinding {
50    /// Creates a multi-binding without child bindings.
51    pub fn new() -> Rc<Self> {
52        Rc::new(Self::default())
53    }
54
55    /// The chaining form of adding child bindings: replaces the contents of
56    /// the collection of child bindings.
57    pub fn with_bindings(self: Rc<Self>, bindings: Vec<Rc<dyn BindingBase>>) -> Rc<Self> {
58        let list = self.bindings();
59        list.clear();
60        list.add_range(bindings);
61        self
62    }
63
64    /// The chaining form of [`set_converter`](Self::set_converter), for a
65    /// converter.
66    pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IMultiValueConverter>) -> Rc<Self> {
67        self.with_converter_value(Some(converter))
68    }
69}
70
71impl BindingBase for MultiBinding {
72    fn as_any(&self) -> Option<&dyn std::any::Any> {
73        Some(self)
74    }
75
76    fn create_instance(
77        &self,
78        _target: &FerroObject,
79        target_property: Option<&'static FerroProperty>,
80        _anchor: Option<&Ref<FerroObject>>,
81    ) -> Rc<dyn BindingExpressionBase> {
82        let target_type = target_property.map_or_else(ValueType::object, property_value_type);
83        let mut converter = self.converter();
84
85        // The string format is only respected if the type of the target
86        // property accepts a string.
87        let string_format = self.string_format();
88        if let Some(format) = string_format.as_deref().filter(|s| !s.trim().is_empty()) {
89            if target_type.is_string() || target_type.is_object() {
90                converter = Some(Rc::new(StringFormatMultiValueConverter::new(format, converter)));
91            }
92        }
93
94        MultiBindingExpression::new(
95            self.priority(),
96            self.bindings().to_vec(),
97            converter,
98            self.converter_culture(),
99            self.converter_parameter(),
100            self.fallback_value(),
101            self.target_null_value(),
102        )
103    }
104}