Skip to main content

ferroui_base/data/
template_binding_expression.rs

1use crate::data::converters::IValueConverter;
2use crate::data::core::plugins::{get_property_value, set_property_value};
3use crate::data::core::{
4    impl_untyped_binding_expression, ExpressionError, Publish, TargetTypeConverter, UntypedBindingExpression,
5    UntypedBindingExpressionBase, ValueTypes,
6};
7use crate::data::{BindingMode, BindingOperations, BindingPriority};
8use crate::logging::LogEventLevel;
9use crate::reactive::IDisposable;
10use crate::utilities::CultureInfo;
11use crate::{BoxedValue, FerroObject, FerroProperty, Ref, StyledElement};
12use std::cell::{Cell, RefCell};
13use std::rc::{Rc, Weak};
14
15/// A binding expression that reads a property of the templated parent of
16/// its target.
17pub struct TemplateBindingExpression {
18    this: Weak<TemplateBindingExpression>,
19    base: UntypedBindingExpressionBase,
20    converter: Option<Rc<dyn IValueConverter>>,
21    converter_culture: Option<CultureInfo>,
22    converter_parameter: Option<BoxedValue>,
23    mode: BindingMode,
24    property: Option<&'static FerroProperty>,
25    has_published_value: Cell<bool>,
26    target_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
27    parent_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28}
29
30impl TemplateBindingExpression {
31    pub fn new(
32        property: Option<&'static FerroProperty>,
33        converter: Option<Rc<dyn IValueConverter>>,
34        converter_culture: Option<CultureInfo>,
35        converter_parameter: Option<BoxedValue>,
36        mode: BindingMode,
37    ) -> Rc<Self> {
38        crate::perf_count!(TemplateBindingExpressionsCreated);
39        Rc::new_cyclic(|this: &Weak<TemplateBindingExpression>| Self {
40            this: this.clone(),
41            base: UntypedBindingExpressionBase::new(this.clone(), BindingPriority::Template, None, false),
42            converter,
43            converter_culture,
44            converter_parameter,
45            mode,
46            property,
47            has_published_value: Cell::new(false),
48            target_subscription: RefCell::new(None),
49            parent_subscription: RefCell::new(None),
50        })
51    }
52
53    fn try_get_templated_parent(&self) -> Option<Ref<FerroObject>> {
54        let target = self.base.try_get_target()?;
55        target.downcast_ref::<StyledElement>()?.templated_parent()
56    }
57
58    fn convert_to_target_type(&self, value: Option<BoxedValue>) -> Option<BoxedValue> {
59        let target_type = self.base.target_type();
60        if self.base.target_property().is_none() && target_type.is_object() {
61            return value;
62        }
63        match TargetTypeConverter::get_default_converter().try_convert(
64            value.as_ref(),
65            target_type,
66            &CultureInfo::invariant_culture(),
67        ) {
68            Some(result) => result,
69            None => {
70                let message = format!(
71                    "Could not convert '{}' ({}) to '{}'.",
72                    ValueTypes::to_display_string(value.as_ref()),
73                    value.as_ref().map_or("null", |v| (**v).type_name()),
74                    target_type
75                );
76                self.log_error(&message, LogEventLevel::Warning);
77                Some(FerroProperty::unset_value())
78            }
79        }
80    }
81
82    fn publish(&self) {
83        if self.mode == BindingMode::OneWayToSource {
84            return;
85        }
86
87        if let Some(templated_parent) = self.try_get_templated_parent() {
88            let mut value = match self.property {
89                Some(property) => get_property_value(&templated_parent, property),
90                None => Some(Rc::new(templated_parent.clone()) as BoxedValue),
91            };
92            let mut error: Option<ExpressionError> = None;
93            if let Some(converter) = &self.converter {
94                value = self.base.convert(
95                    &|| self.should_log_error(),
96                    &|| self.description(),
97                    &**converter,
98                    self.converter_culture.as_ref(),
99                    self.converter_parameter.as_ref(),
100                    value.as_ref(),
101                    self.base.target_type(),
102                    &mut error,
103                );
104            }
105            let value = self.convert_to_target_type(value);
106            self.base.publish_value(Publish::Value(value), error, false);
107            self.has_published_value.set(true);
108
109            if self.mode == BindingMode::OneTime {
110                self.stop();
111            }
112        } else if self.has_published_value.get() {
113            self.base.publish_value(Publish::Value(Some(FerroProperty::unset_value())), None, false);
114        }
115    }
116
117    fn unsubscribe_parent(&self) {
118        let subscription = self.parent_subscription.borrow_mut().take();
119        if let Some(s) = subscription {
120            s.dispose();
121        }
122    }
123
124    fn on_templated_parent_changed(&self) {
125        self.unsubscribe_parent();
126        if let Some(templated_parent) = self.try_get_templated_parent() {
127            let weak = self.this.clone();
128            let id = self.property.map(FerroProperty::id);
129            let subscription = templated_parent.property_changed(move |e| {
130                if Some(e.property().id()) == id {
131                    if let Some(this) = weak.upgrade() {
132                        this.publish();
133                    }
134                }
135            });
136            self.parent_subscription.replace(Some(subscription));
137        }
138        self.publish();
139    }
140
141    fn on_target_property_changed(&self, property_id: u32) {
142        if property_id == StyledElement::templated_parent_property().id() {
143            self.on_templated_parent_changed();
144        } else if matches!(self.mode, BindingMode::TwoWay | BindingMode::OneWayToSource)
145            && self.base.target_property().is_some_and(|p| p.id() == property_id)
146        {
147            if let (Some(target), Some(property)) = (self.base.try_get_target(), self.base.target_property()) {
148                self.write_value_to_source(get_property_value(&target, property));
149            }
150        }
151    }
152}
153
154impl UntypedBindingExpression for TemplateBindingExpression {
155    fn base(&self) -> &UntypedBindingExpressionBase {
156        &self.base
157    }
158
159    fn description(&self) -> String {
160        format!("{{TemplateBinding {}}}", self.property.map_or("", |p| p.name()))
161    }
162
163    fn start_core(&self) {
164        self.has_published_value.set(false);
165        self.on_templated_parent_changed();
166        if let Some(target) = self.base.try_get_target() {
167            let weak = self.this.clone();
168            let subscription = target.property_changed(move |e| {
169                if let Some(this) = weak.upgrade() {
170                    this.on_target_property_changed(e.property().id());
171                }
172            });
173            self.target_subscription.replace(Some(subscription));
174        }
175    }
176
177    fn stop_core(&self) {
178        self.unsubscribe_parent();
179        let subscription = self.target_subscription.borrow_mut().take();
180        if let Some(s) = subscription {
181            s.dispose();
182        }
183    }
184
185    fn write_value_to_source(&self, value: Option<BoxedValue>) -> bool {
186        let (Some(property), Some(templated_parent)) = (self.property, self.try_get_templated_parent()) else {
187            return false;
188        };
189        let mut value = value;
190        if let Some(converter) = &self.converter {
191            value = self.base.convert_back(
192                &|| self.should_log_error(),
193                &|| self.description(),
194                &**converter,
195                self.converter_culture.as_ref(),
196                self.converter_parameter.as_ref(),
197                value.as_ref(),
198                self.base.target_type(),
199            );
200        }
201        if !BindingOperations::is_do_nothing(value.as_ref()) {
202            // Written with the semantics of "set current value": the
203            // templated parent's own bindings and styles stay in place.
204            let target_type = crate::data::core::plugins::property_value_type(property);
205            match ValueTypes::try_convert(value.as_ref(), target_type) {
206                Some(Some(v)) if BindingOperations::is_unset(Some(&v)) || property.is_valid_value((*v).as_any()) => {
207                    templated_parent.set_current_value_untyped(property, (*v).as_any());
208                }
209                _ => {
210                    if BindingOperations::is_unset(value.as_ref()) {
211                        let _ = set_property_value(&templated_parent, property, value.as_ref(), BindingPriority::LocalValue);
212                    }
213                }
214            }
215        }
216        true
217    }
218}
219
220impl_untyped_binding_expression!(TemplateBindingExpression);
221
222impl Drop for TemplateBindingExpression {
223    fn drop(&mut self) {
224        if let Some(s) = self.parent_subscription.get_mut().take() {
225            s.dispose();
226        }
227        if let Some(s) = self.target_subscription.get_mut().take() {
228            s.dispose();
229        }
230    }
231}