Skip to main content

ferroui_base/data/core/
multi_binding_expression.rs

1use super::{
2    impl_untyped_binding_expression, IBindingExpressionSink, Publish, SinkRef, UntypedBindingExpression,
3    UntypedBindingExpressionBase, ValueTypes,
4};
5use crate::data::converters::IMultiValueConverter;
6use crate::data::{BindingBase, BindingExpressionBase, BindingNotification, BindingOperations, BindingPriority};
7use crate::utilities::CultureInfo;
8use crate::{BoxedValue, FerroProperty};
9use std::cell::RefCell;
10use std::rc::{Rc, Weak};
11
12/// The binding expression of a multi-binding: runs the child bindings and
13/// combines their values through a converter.
14pub struct MultiBindingExpression {
15    this: Weak<MultiBindingExpression>,
16    base: UntypedBindingExpressionBase,
17    bindings: Vec<Rc<dyn BindingBase>>,
18    converter: Option<Rc<dyn IMultiValueConverter>>,
19    converter_culture: Option<CultureInfo>,
20    converter_parameter: Option<BoxedValue>,
21    expressions: RefCell<Vec<Option<Rc<dyn BindingExpressionBase>>>>,
22    fallback_value: Option<BoxedValue>,
23    target_null_value: Option<BoxedValue>,
24    /// The current value of each child; `None` until the child has produced
25    /// one.
26    values: RefCell<Vec<Option<Option<BoxedValue>>>>,
27}
28
29impl MultiBindingExpression {
30    pub fn new(
31        priority: BindingPriority,
32        bindings: Vec<Rc<dyn BindingBase>>,
33        converter: Option<Rc<dyn IMultiValueConverter>>,
34        converter_culture: Option<CultureInfo>,
35        converter_parameter: Option<BoxedValue>,
36        fallback_value: Option<BoxedValue>,
37        target_null_value: Option<BoxedValue>,
38    ) -> Rc<Self> {
39        let count = bindings.len();
40        Rc::new_cyclic(|this: &Weak<MultiBindingExpression>| Self {
41            this: this.clone(),
42            base: UntypedBindingExpressionBase::new(this.clone(), priority, None, false),
43            bindings,
44            converter,
45            converter_culture,
46            converter_parameter,
47            expressions: RefCell::new(vec![None; count]),
48            fallback_value,
49            target_null_value,
50            values: RefCell::new(vec![None; count]),
51        })
52    }
53
54    pub fn converter(&self) -> Option<&Rc<dyn IMultiValueConverter>> {
55        self.converter.as_ref()
56    }
57
58    /// The culture in which the converter is evaluated; `None` for the
59    /// current culture.
60    pub fn converter_culture(&self) -> Option<&CultureInfo> {
61        self.converter_culture.as_ref()
62    }
63
64    fn publish(&self) {
65        let values: Vec<Option<BoxedValue>> = {
66            let values = self.values.borrow();
67            if values.iter().any(Option::is_none) {
68                return;
69            }
70            values.iter().map(|v| v.clone().expect("checked")).collect()
71        };
72
73        let target_type = self.base.target_type();
74        let has_target = self.base.target_property().is_some();
75
76        match &self.converter {
77            Some(converter) => {
78                let culture = self.converter_culture.clone().unwrap_or_else(CultureInfo::current_culture);
79                let converted = match converter.convert(&values, target_type, self.converter_parameter.as_ref(), &culture)
80                {
81                    Ok(v) => v,
82                    Err(_) => Some(FerroProperty::unset_value()),
83                };
84                let mut converted = BindingNotification::extract_value(converted.as_ref());
85                if BindingOperations::is_do_nothing(converted.as_ref()) {
86                    return;
87                }
88                if converted.is_none() {
89                    converted = self.target_null_value.clone();
90                }
91                if BindingOperations::is_unset(converted.as_ref()) {
92                    converted = self.fallback_value.clone();
93                }
94                if has_target && !BindingOperations::is_unset(converted.as_ref()) {
95                    converted = ValueTypes::try_convert(converted.as_ref(), target_type)
96                        .unwrap_or_else(|| Some(FerroProperty::unset_value()));
97                }
98                self.base.publish_value(Publish::Value(converted), None, false);
99            }
100            None => {
101                let list: BoxedValue = Rc::new(values);
102                let value = if has_target {
103                    ValueTypes::try_convert(Some(&list), target_type)
104                        .unwrap_or_else(|| Some(FerroProperty::unset_value()))
105                } else {
106                    Some(list)
107                };
108                self.base.publish_value(Publish::Value(value), None, false);
109            }
110        }
111    }
112}
113
114impl IBindingExpressionSink for MultiBindingExpression {
115    fn on_changed(&self, instance: &Rc<dyn BindingExpressionBase>, _has_value_changed: bool, _has_error_changed: bool) {
116        let index = self
117            .expressions
118            .borrow()
119            .iter()
120            .position(|e| e.as_ref().is_some_and(|e| std::ptr::addr_eq(Rc::as_ptr(e), Rc::as_ptr(instance))));
121        let Some(index) = index else { return };
122        let value = if instance.has_value() {
123            BindingNotification::extract_value(instance.get_value_boxed().as_ref())
124        } else {
125            Some(FerroProperty::unset_value())
126        };
127        self.values.borrow_mut()[index] = Some(value);
128        self.publish();
129    }
130
131    fn on_completed(&self, _instance: &Rc<dyn BindingExpressionBase>) {
132        // Nothing to do here.
133    }
134}
135
136impl UntypedBindingExpression for MultiBindingExpression {
137    fn base(&self) -> &UntypedBindingExpressionBase {
138        &self.base
139    }
140
141    fn description(&self) -> String {
142        "MultiBinding".to_string()
143    }
144
145    fn start_core(&self) {
146        let target = self.base.try_get_target().expect("MultiBindingExpression has no target.");
147        let sink: Weak<dyn IBindingExpressionSink> = self.this.clone();
148        for (i, binding) in self.bindings.iter().enumerate() {
149            let expression = binding.create_instance(&target, None, None);
150            let Some(untyped) = expression.as_untyped() else {
151                panic!("Unsupported BindingExpressionBase implementation.");
152            };
153            self.expressions.borrow_mut()[i] = Some(expression.clone());
154            untyped.base().attach(SinkRef::Other(sink.clone()), None, &target, None, self.base.priority());
155            untyped.start(true);
156        }
157    }
158
159    fn stop_core(&self) {
160        let expressions: Vec<_> = self.expressions.borrow_mut().iter_mut().map(Option::take).collect();
161        for expression in expressions.into_iter().flatten() {
162            expression.dispose();
163        }
164        for value in self.values.borrow_mut().iter_mut() {
165            *value = None;
166        }
167    }
168}
169
170impl_untyped_binding_expression!(MultiBindingExpression);