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
14pub 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 target_handler: OnceCell<Rc<dyn Fn(&FerroPropertyChangedEventArgs<'_>)>>,
30 parent_handler: OnceCell<Rc<dyn Fn(&FerroPropertyChangedEventArgs<'_>)>>,
31}
32
33struct 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 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}