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