1use crate::controls::NameScopeRef;
2use crate::data::core::expression_nodes::{
3 ArrayIndexerNode, CastTarget, ExpressionNode, FerroPropertyAccessorNode, FuncTransformNode,
4 LogicalAncestorElementNode, LogicalNotNode, MethodCommandNode, NamedElementNode, PropertyAccessorNode,
5 StreamNode, TemplatedParentNode, VisualAncestorElementNode,
6};
7use crate::data::core::plugins::{
8 IPropertyAccessorPlugin, IStreamPlugin, ObservableStreamPlugin, PropertyAccessorFactory,
9 PropertyInfoAccessorFactory, PropertyInfoAccessorPlugin, TaskStreamPlugin, UntypedAccessorPlugin,
10 AsNotifyPropertyChanged, UntypedCanExecute, UntypedCreateDelegate, UntypedExecute, UntypedGetter,
11 UntypedMember, UntypedSetter,
12};
13use crate::data::core::{
14 ClrPropertyInfo, IPropertyInfo, PropertyKind, TypedBindingExpression, TypedClrPropertyInfo, Value, ValueType,
15 ValueTypes, INDEXER_NAME,
16};
17use crate::data::{BindingError, BindingExpressionBase, BindingMode, BindingPriority};
18use crate::data::model::{BindableDictionary, BindableList, INotifyPropertyChanged};
19use crate::{AnyValue, BoxedValue, FerroProperty, ObjectType, PropertyValue, TypeInfo};
20use std::cell::RefCell;
21use std::fmt;
22use std::hash::Hash;
23use std::rc::Rc;
24
25type Execute = Rc<dyn Fn(&dyn AnyValue, Option<&BoxedValue>)>;
26type CanExecute = Rc<dyn Fn(&dyn AnyValue, Option<&BoxedValue>) -> bool>;
27type InpcLookup = Rc<dyn Fn(&dyn AnyValue) -> Option<&dyn INotifyPropertyChanged>>;
28type Cast = Rc<dyn Fn(Option<&BoxedValue>) -> Option<BoxedValue>>;
29
30#[derive(Clone)]
32enum PathElement {
33 Not,
34 FerroProperty { property: &'static FerroProperty, accepts_null: bool },
36 TypedProperty {
39 property: Rc<dyn IPropertyInfo>,
40 accessor_factory: PropertyAccessorFactory,
41 accepts_null: bool,
42 typed: Rc<dyn TypedPropertyElement>,
43 },
44 Property { property: Rc<dyn IPropertyInfo>, accessor_factory: PropertyAccessorFactory, accepts_null: bool },
46 Untyped {
48 name: Box<str>,
49 kind: CompiledBindingPathElementKind,
50 property_type: Option<ValueType>,
51 plugin: Rc<dyn IPropertyAccessorPlugin>,
52 accepts_null: bool,
53 },
54 MethodAsCommand {
55 method_name: Box<str>,
56 execute: Execute,
57 can_execute: Option<CanExecute>,
58 depends_on_properties: Vec<Box<str>>,
59 inpc: Option<InpcLookup>,
60 },
61 Stream(Rc<dyn IStreamPlugin>),
62 SelfElement,
63 Ancestor { ancestor_type: Option<&'static TypeInfo>, level: usize },
64 VisualAncestor { ancestor_type: Option<&'static TypeInfo>, level: usize },
65 ElementName { name_scope: std::rc::Weak<dyn crate::controls::INameScope>, name: Box<str> },
74 TemplatedParent,
75 ArrayElement { indices: Vec<i32> },
76 TypeCast { type_name: &'static str, cast: Cast },
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82pub enum CompiledBindingPathElementKind {
83 Not,
85 Property,
88 MethodAsDelegate,
90 MethodAsCommand,
92 Stream,
94 SelfElement,
96 Ancestor,
98 VisualAncestor,
100 ElementName,
102 TemplatedParent,
104 ArrayElement,
106 TypeCast,
108}
109
110#[derive(Clone, Copy)]
112pub struct CompiledBindingPathElement<'a>(&'a PathElement);
113
114impl<'a> CompiledBindingPathElement<'a> {
115 pub fn kind(&self) -> CompiledBindingPathElementKind {
117 use CompiledBindingPathElementKind as Kind;
118 match self.0 {
119 PathElement::Not => Kind::Not,
120 PathElement::FerroProperty { .. } | PathElement::TypedProperty { .. } | PathElement::Property { .. } => {
121 Kind::Property
122 }
123 PathElement::Untyped { kind, .. } => *kind,
124 PathElement::MethodAsCommand { .. } => Kind::MethodAsCommand,
125 PathElement::Stream(_) => Kind::Stream,
126 PathElement::SelfElement => Kind::SelfElement,
127 PathElement::Ancestor { .. } => Kind::Ancestor,
128 PathElement::VisualAncestor { .. } => Kind::VisualAncestor,
129 PathElement::ElementName { .. } => Kind::ElementName,
130 PathElement::TemplatedParent => Kind::TemplatedParent,
131 PathElement::ArrayElement { .. } => Kind::ArrayElement,
132 PathElement::TypeCast { .. } => Kind::TypeCast,
133 }
134 }
135
136 pub fn name(&self) -> Option<&'a str> {
139 match self.0 {
140 PathElement::FerroProperty { property, .. } => Some(property.name()),
141 PathElement::TypedProperty { property, .. } | PathElement::Property { property, .. } => {
142 Some(property.name())
143 }
144 PathElement::Untyped { name, .. } => Some(name),
145 PathElement::MethodAsCommand { method_name, .. } => Some(method_name),
146 PathElement::ElementName { name, .. } => Some(name),
147 _ => None,
148 }
149 }
150
151 pub fn property(&self) -> Option<Rc<dyn IPropertyInfo>> {
156 match self.0 {
157 PathElement::FerroProperty { property, .. } => Some(property.as_property_info()),
158 PathElement::TypedProperty { property, .. } | PathElement::Property { property, .. } => {
159 Some(property.clone())
160 }
161 _ => None,
162 }
163 }
164
165 pub fn property_type(&self) -> Option<ValueType> {
167 match self.0 {
168 PathElement::Untyped { property_type, .. } => *property_type,
169 _ => self.property().map(|property| property.property_type()),
170 }
171 }
172
173 pub fn is_typed_property(&self) -> bool {
177 matches!(self.0, PathElement::TypedProperty { .. })
178 }
179
180 pub fn accepts_null(&self) -> bool {
182 match self.0 {
183 PathElement::FerroProperty { accepts_null, .. }
184 | PathElement::TypedProperty { accepts_null, .. }
185 | PathElement::Property { accepts_null, .. }
186 | PathElement::Untyped { accepts_null, .. } => *accepts_null,
187 _ => false,
188 }
189 }
190
191 pub fn ancestor(&self) -> Option<(Option<&'static TypeInfo>, usize)> {
193 match self.0 {
194 PathElement::Ancestor { ancestor_type, level } | PathElement::VisualAncestor { ancestor_type, level } => {
195 Some((*ancestor_type, *level))
196 }
197 _ => None,
198 }
199 }
200
201 pub fn array_indices(&self) -> Option<&'a [i32]> {
203 match self.0 {
204 PathElement::ArrayElement { indices } => Some(indices),
205 _ => None,
206 }
207 }
208}
209
210pub(crate) trait TypedPropertyElement {
212 fn value_type(&self) -> ValueType;
213 fn can_set(&self) -> bool;
214 fn create_expression(&self, mode: BindingMode, priority: BindingPriority) -> Rc<dyn BindingExpressionBase>;
215}
216
217impl<S: PartialEq + 'static, V: PropertyValue> TypedPropertyElement for Rc<TypedClrPropertyInfo<S, V>> {
218 fn value_type(&self) -> ValueType {
219 ValueType::of::<V>()
220 }
221
222 fn can_set(&self) -> bool {
223 IPropertyInfo::can_set(&**self)
224 }
225
226 fn create_expression(&self, mode: BindingMode, priority: BindingPriority) -> Rc<dyn BindingExpressionBase> {
227 TypedBindingExpression::new(self.clone(), mode, priority)
228 }
229}
230
231#[derive(Clone)]
237pub struct CompiledBindingPath {
238 elements: Rc<[PathElement]>,
239}
240
241impl Default for CompiledBindingPath {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247impl PartialEq for CompiledBindingPath {
248 fn eq(&self, other: &Self) -> bool {
249 Rc::ptr_eq(&self.elements, &other.elements)
250 }
251}
252
253impl CompiledBindingPath {
254 pub fn new() -> Self {
256 Self { elements: Rc::new([]) }
257 }
258
259 pub(crate) fn build_expression(&self, result: &mut Vec<Rc<dyn ExpressionNode>>) -> bool {
262 let mut negated = 0;
263 let mut is_rooted = false;
264
265 for element in self.elements.iter() {
266 let node: Option<Rc<dyn ExpressionNode>> = match element {
267 PathElement::Not => {
268 negated += 1;
269 None
270 }
271 PathElement::FerroProperty { property, accepts_null } => {
272 Some(FerroPropertyAccessorNode::new(property, *accepts_null))
273 }
274 PathElement::Property { property, accessor_factory, accepts_null }
275 | PathElement::TypedProperty { property, accessor_factory, accepts_null, .. } => Some(PropertyAccessorNode::new(
276 property.name(),
277 Rc::new(PropertyInfoAccessorPlugin::new(property.clone(), accessor_factory.clone())),
278 *accepts_null,
279 )),
280 PathElement::MethodAsCommand { method_name, execute, can_execute, depends_on_properties, inpc } => {
281 let depends: Vec<&str> = depends_on_properties.iter().map(|s| &**s).collect();
282 Some(MethodCommandNode::new(
283 method_name,
284 execute.clone(),
285 can_execute.clone(),
286 &depends,
287 inpc.clone(),
288 ))
289 }
290 PathElement::Untyped { name, plugin, accepts_null, .. } => {
291 Some(PropertyAccessorNode::new(name, plugin.clone(), *accepts_null))
292 }
293 PathElement::ArrayElement { indices } => Some(ArrayIndexerNode::new(indices.clone())),
294 PathElement::VisualAncestor { ancestor_type, level } => {
295 is_rooted = true;
296 Some(VisualAncestorElementNode::new(*ancestor_type, *level))
297 }
298 PathElement::Ancestor { ancestor_type, level } => {
299 is_rooted = true;
300 Some(LogicalAncestorElementNode::new(*ancestor_type, *level))
301 }
302 PathElement::SelfElement => {
303 is_rooted = true;
304 None
305 }
306 PathElement::ElementName { name_scope, name } => {
307 is_rooted = true;
308 let name_scope = name_scope.upgrade().map(NameScopeRef);
309 Some(NamedElementNode::new(name_scope.as_ref(), name))
310 }
311 PathElement::Stream(plugin) => Some(StreamNode::new(plugin.clone())),
312 PathElement::TypeCast { cast, .. } => Some(FuncTransformNode::new(cast.clone())),
313 PathElement::TemplatedParent => {
314 is_rooted = true;
315 Some(TemplatedParentNode::new())
316 }
317 };
318 if let Some(node) = node {
319 result.push(node);
320 }
321 }
322
323 for _ in 0..negated {
324 result.push(LogicalNotNode::new());
325 }
326 is_rooted
327 }
328
329 pub(crate) fn single_typed_element(&self) -> Option<&Rc<dyn TypedPropertyElement>> {
332 match &*self.elements {
333 [PathElement::TypedProperty { typed, .. }] => Some(typed),
334 _ => None,
335 }
336 }
337
338 pub fn elements(&self) -> impl ExactSizeIterator<Item = CompiledBindingPathElement<'_>> + '_ {
341 self.elements.iter().map(CompiledBindingPathElement)
342 }
343
344 pub fn element(&self, index: usize) -> Option<CompiledBindingPathElement<'_>> {
346 self.elements.get(index).map(CompiledBindingPathElement)
347 }
348
349 pub fn len(&self) -> usize {
351 self.elements.len()
352 }
353
354 pub fn is_empty(&self) -> bool {
355 self.elements.is_empty()
356 }
357}
358
359impl fmt::Display for CompiledBindingPath {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 let mut first = true;
362 for element in self.elements.iter() {
363 let text = match element {
364 PathElement::Not => "!".to_string(),
365 PathElement::FerroProperty { property, .. } => {
366 format!("{}{}", if first { "" } else { "." }, property.name())
367 }
368 PathElement::Property { property, .. } | PathElement::TypedProperty { property, .. } => {
369 format!("{}{}", if first { "" } else { "." }, property.name())
370 }
371 PathElement::MethodAsCommand { method_name, .. } => {
372 format!("{}{}()", if first { "" } else { "." }, method_name)
373 }
374 PathElement::Untyped { name, kind, .. } => {
375 let is_method = *kind != CompiledBindingPathElementKind::Property;
376 format!("{}{}{}", if first { "" } else { "." }, name, if is_method { "()" } else { "" })
377 }
378 PathElement::Stream(_) => "^".to_string(),
379 PathElement::SelfElement => "$self".to_string(),
380 PathElement::Ancestor { ancestor_type, level } => {
381 format!("$parent[{}, {}]", ancestor_type.map_or("", |t| t.name()), level)
382 }
383 PathElement::VisualAncestor { ancestor_type, level } => {
384 format!("$visualParent[{}, {}]", ancestor_type.map_or("", |t| t.name()), level)
385 }
386 PathElement::ElementName { name, .. } => format!("#{name}"),
387 PathElement::TemplatedParent => "$templatedParent".to_string(),
388 PathElement::ArrayElement { indices } => {
389 format!("[{}]", indices.iter().map(i32::to_string).collect::<Vec<_>>().join(","))
390 }
391 PathElement::TypeCast { type_name, .. } => format!("({type_name})"),
392 };
393 f.write_str(&text)?;
394 first = matches!(element, PathElement::Not);
395 }
396 Ok(())
397 }
398}
399
400#[derive(Clone, Default)]
407pub struct CompiledBindingPathBuilder {
408 elements: Rc<RefCell<Vec<PathElement>>>,
409}
410
411impl PartialEq for CompiledBindingPathBuilder {
412 fn eq(&self, other: &Self) -> bool {
413 Rc::ptr_eq(&self.elements, &other.elements)
414 }
415}
416
417impl CompiledBindingPathBuilder {
418 pub fn new() -> Self {
419 Self::default()
420 }
421
422 pub fn not(&self) -> Self {
424 self.elements.borrow_mut().push(PathElement::Not);
425 self.clone()
426 }
427
428 pub fn ferro_property(&self, property: &'static FerroProperty) -> Self {
430 self.elements.borrow_mut().push(PathElement::FerroProperty { property, accepts_null: false });
431 self.clone()
432 }
433
434 pub fn ferro_property_with(&self, property: &'static FerroProperty, accepts_null: bool) -> Self {
438 self.elements.borrow_mut().push(PathElement::FerroProperty { property, accepts_null });
439 self.clone()
440 }
441
442 pub fn property(&self, info: Rc<dyn IPropertyInfo>, accessor_factory: PropertyAccessorFactory) -> Self {
445 self.property_with(info, accessor_factory, false)
446 }
447
448 pub fn property_with(
451 &self,
452 info: Rc<dyn IPropertyInfo>,
453 accessor_factory: PropertyAccessorFactory,
454 accepts_null: bool,
455 ) -> Self {
456 self.elements.borrow_mut().push(PathElement::Property { property: info, accessor_factory, accepts_null });
457 self.clone()
458 }
459
460 pub fn typed_property<S: INotifyPropertyChanged + PartialEq + 'static, V: PropertyValue>(
465 &self,
466 name: &str,
467 get: impl Fn(&S) -> V + 'static,
468 set: Option<Rc<dyn Fn(&S, V)>>,
469 ) -> Self {
470 ValueTypes::register_reference::<S>();
471 let info = Rc::new(TypedClrPropertyInfo::<S, V>::new(name, Some(Rc::new(get)), set).notifying());
472 self.elements.borrow_mut().push(PathElement::TypedProperty {
473 property: info.clone(),
474 accessor_factory: PropertyInfoAccessorFactory::create_inpc_property_accessor::<S>(),
475 accepts_null: false,
476 typed: Rc::new(info),
477 });
478 self.clone()
479 }
480
481 pub fn notifying_property<O: INotifyPropertyChanged + PartialEq + 'static, K: PropertyKind>(
484 &self,
485 name: &str,
486 get: impl Fn(&O) -> K::Typed + 'static,
487 set: impl Fn(&O, K::Typed) + 'static,
488 ) -> Self {
489 ValueTypes::register_reference::<O>();
490 self.property(
491 Rc::new(ClrPropertyInfo::read_write::<O, K>(name, get, set)),
492 PropertyInfoAccessorFactory::create_inpc_property_accessor::<O>(),
493 )
494 }
495
496 pub fn notifying_read_only_property<O: INotifyPropertyChanged + PartialEq + 'static, K: PropertyKind>(
499 &self,
500 name: &str,
501 get: impl Fn(&O) -> K::Typed + 'static,
502 ) -> Self {
503 ValueTypes::register_reference::<O>();
504 self.property(
505 Rc::new(ClrPropertyInfo::read_only::<O, K>(name, get)),
506 PropertyInfoAccessorFactory::create_inpc_property_accessor::<O>(),
507 )
508 }
509
510 pub fn command<O: 'static>(
512 &self,
513 method_name: &str,
514 execute: impl Fn(&O, Option<&BoxedValue>) + 'static,
515 can_execute: Option<Rc<dyn Fn(&O, Option<&BoxedValue>) -> bool>>,
516 depends_on_properties: &[&str],
517 ) -> Self {
518 self.elements.borrow_mut().push(PathElement::MethodAsCommand {
519 method_name: method_name.into(),
520 execute: Rc::new(move |o, p| {
521 if let Some(o) = o.downcast_ref::<O>() {
522 execute(o, p)
523 }
524 }),
525 can_execute: can_execute.map(|f| {
526 Rc::new(move |o: &dyn AnyValue, p: Option<&BoxedValue>| o.downcast_ref::<O>().is_some_and(|o| f(o, p)))
527 as CanExecute
528 }),
529 depends_on_properties: depends_on_properties.iter().map(|s| (*s).into()).collect(),
530 inpc: None,
531 });
532 self.clone()
533 }
534
535 pub fn notifying_command<O: INotifyPropertyChanged + 'static>(
539 &self,
540 method_name: &str,
541 execute: impl Fn(&O, Option<&BoxedValue>) + 'static,
542 can_execute: Option<Rc<dyn Fn(&O, Option<&BoxedValue>) -> bool>>,
543 depends_on_properties: &[&str],
544 ) -> Self {
545 self.command::<O>(method_name, execute, can_execute, depends_on_properties);
546 if let Some(PathElement::MethodAsCommand { inpc, .. }) = self.elements.borrow_mut().last_mut() {
547 *inpc = Some(Rc::new(|v: &dyn AnyValue| v.downcast_ref::<O>().map(|o| o as &dyn INotifyPropertyChanged)));
548 }
549 self.clone()
550 }
551
552 pub fn stream_task(&self) -> Self {
554 self.elements.borrow_mut().push(PathElement::Stream(Rc::new(TaskStreamPlugin)));
555 self.clone()
556 }
557
558 pub fn stream_observable(&self) -> Self {
560 self.elements.borrow_mut().push(PathElement::Stream(Rc::new(ObservableStreamPlugin)));
561 self.clone()
562 }
563
564 pub fn stream(&self, plugin: Rc<dyn IStreamPlugin>) -> Self {
566 self.elements.borrow_mut().push(PathElement::Stream(plugin));
567 self.clone()
568 }
569
570 pub fn self_(&self) -> Self {
572 self.elements.borrow_mut().push(PathElement::SelfElement);
573 self.clone()
574 }
575
576 pub fn ancestor(&self, ancestor_type: Option<&'static TypeInfo>, level: usize) -> Self {
578 self.elements.borrow_mut().push(PathElement::Ancestor { ancestor_type, level });
579 self.clone()
580 }
581
582 pub fn visual_ancestor(&self, ancestor_type: Option<&'static TypeInfo>, level: usize) -> Self {
584 self.elements.borrow_mut().push(PathElement::VisualAncestor { ancestor_type, level });
585 self.clone()
586 }
587
588 pub fn element_name(&self, name_scope: NameScopeRef, name: &str) -> Self {
590 let name_scope = std::rc::Rc::downgrade(&name_scope.0);
591 self.elements.borrow_mut().push(PathElement::ElementName { name_scope, name: name.into() });
592 self.clone()
593 }
594
595 pub fn indexer_property<O: 'static, K: PropertyKind>(
600 &self,
601 index: i32,
602 get: impl Fn(&O, i32) -> Result<K::Typed, BindingError> + 'static,
603 set: Option<Rc<dyn Fn(&O, i32, K::Typed) -> Result<(), BindingError>>>,
604 ) -> Self {
605 let info = match set {
606 Some(set) => ClrPropertyInfo::read_write_fallible::<O, K>(
607 INDEXER_NAME,
608 move |o| get(o, index),
609 move |o, v| set(o, index, v),
610 ),
611 None => ClrPropertyInfo::read_only_fallible::<O, K>(INDEXER_NAME, move |o| get(o, index)),
612 };
613 self.property(Rc::new(info), PropertyInfoAccessorFactory::create_indexer_property_accessor(index))
614 }
615
616 pub fn list_item<T: PropertyValue>(&self, index: i32) -> Self {
618 fn position<T: PropertyValue>(list: &BindableList<T>, index: i32) -> Result<usize, BindingError> {
619 usize::try_from(index).ok().filter(|i| *i < list.items().count()).ok_or_else(|| {
620 BindingError::message(
621 "Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')",
622 )
623 })
624 }
625 self.indexer_property::<BindableList<T>, Value<T>>(
626 index,
627 |list, index| {
628 let position = position(list, index)?;
629 list.items().try_get(position).ok_or_else(|| BindingError::message("Index was out of range."))
630 },
631 Some(Rc::new(|list, index, value| {
632 let position = position(list, index)?;
633 list.items().set(position, value);
634 Ok(())
635 })),
636 )
637 }
638
639 pub fn dictionary_item<K: Eq + Hash + Clone + fmt::Display + 'static, V: PropertyValue>(&self, key: K) -> Self {
642 let index = (&key as &dyn std::any::Any).downcast_ref::<i32>().copied();
644 let set_key = key.clone();
645 let info = ClrPropertyInfo::read_write_fallible::<BindableDictionary<K, V>, Value<V>>(
646 INDEXER_NAME,
647 move |dictionary| {
648 dictionary.items().try_get_value(&key).ok_or_else(|| {
649 BindingError::message(format!("The given key '{key}' was not present in the dictionary."))
650 })
651 },
652 move |dictionary, value| {
653 dictionary.items().set(set_key.clone(), value);
654 Ok(())
655 },
656 );
657 let accessor_factory = match index {
658 Some(index) => PropertyInfoAccessorFactory::create_indexer_property_accessor(index),
659 None => PropertyInfoAccessorFactory::create_inpc_property_accessor::<BindableDictionary<K, V>>(),
660 };
661 self.property(Rc::new(info), accessor_factory)
662 }
663
664 pub fn array_element(&self, indices: &[i32]) -> Self {
666 self.elements.borrow_mut().push(PathElement::ArrayElement { indices: indices.to_vec() });
667 self.clone()
668 }
669
670 pub fn type_cast<T: ObjectType>(&self) -> Self {
672 self.elements.borrow_mut().push(PathElement::TypeCast {
673 type_name: T::TYPE.name(),
674 cast: Rc::new(|v| {
675 v.filter(|v| ValueTypes::as_object(&***v).is_some_and(|o| o.is::<T>())).cloned()
676 }),
677 });
678 self.clone()
679 }
680
681 pub fn type_cast_value(&self, target: CastTarget) -> Self {
685 let type_name = match target {
686 CastTarget::Class(t) => t.name(),
687 CastTarget::Value(t) => t.name(),
688 };
689 let cast: Cast = Rc::new(move |v: Option<&BoxedValue>| v.filter(|v| target.is_instance(v)).cloned());
690 self.elements.borrow_mut().push(PathElement::TypeCast { type_name, cast });
691 self.clone()
692 }
693
694 pub fn templated_parent(&self) -> Self {
696 self.elements.borrow_mut().push(PathElement::TemplatedParent);
697 self.clone()
698 }
699
700 pub fn property_untyped(
706 &self,
707 name: &str,
708 property_type: ValueType,
709 get: UntypedGetter,
710 set: Option<UntypedSetter>,
711 notifier: Option<AsNotifyPropertyChanged>,
712 ) -> Self {
713 self.property_untyped_with(name, property_type, get, set, notifier, false)
714 }
715
716 pub fn property_untyped_with(
720 &self,
721 name: &str,
722 property_type: ValueType,
723 get: UntypedGetter,
724 set: Option<UntypedSetter>,
725 notifier: Option<AsNotifyPropertyChanged>,
726 accepts_null: bool,
727 ) -> Self {
728 self.untyped_member(name, UntypedMember::Property { property_type, get, set, notifier }, accepts_null)
729 }
730
731 pub fn command_untyped(
736 &self,
737 method_name: &str,
738 execute: UntypedExecute,
739 can_execute: Option<UntypedCanExecute>,
740 depends_on_properties: &[&str],
741 notifier: Option<AsNotifyPropertyChanged>,
742 ) -> Self {
743 self.untyped_member(
744 method_name,
745 UntypedMember::Command {
746 execute,
747 can_execute,
748 depends_on_properties: depends_on_properties.iter().map(|s| (*s).into()).collect(),
749 notifier,
750 },
751 false,
752 )
753 }
754
755 pub fn method_untyped(&self, name: &str, create_delegate: UntypedCreateDelegate, accepts_null: bool) -> Self {
762 self.untyped_member(name, UntypedMember::Method { create_delegate }, accepts_null)
763 }
764
765 fn untyped_member(&self, name: &str, member: UntypedMember, accepts_null: bool) -> Self {
766 let (kind, property_type) = match &member {
767 UntypedMember::Property { property_type, .. } => {
768 (CompiledBindingPathElementKind::Property, Some(*property_type))
769 }
770 UntypedMember::Command { .. } => (CompiledBindingPathElementKind::MethodAsCommand, None),
771 UntypedMember::Method { .. } => (CompiledBindingPathElementKind::MethodAsDelegate, None),
772 };
773 self.elements.borrow_mut().push(PathElement::Untyped {
774 name: name.into(),
775 kind,
776 property_type,
777 plugin: Rc::new(UntypedAccessorPlugin::new(name, member)),
778 accepts_null,
779 });
780 self.clone()
781 }
782
783 pub fn typed_plain_property_info_with<S: PartialEq + 'static, V: PropertyValue>(
789 &self,
790 info: TypedClrPropertyInfo<S, V>,
791 accepts_null: bool,
792 ) -> Self {
793 ValueTypes::register_reference::<S>();
794 let info = Rc::new(info);
795 self.elements.borrow_mut().push(PathElement::TypedProperty {
796 property: info.clone(),
797 accessor_factory: PropertyInfoAccessorFactory::create_plain_property_accessor(),
798 accepts_null,
799 typed: Rc::new(info),
800 });
801 self.clone()
802 }
803
804 pub fn build(&self) -> CompiledBindingPath {
806 CompiledBindingPath { elements: self.elements.borrow().clone().into() }
807 }
808
809 pub fn typed_property_info<S: INotifyPropertyChanged + PartialEq + 'static, V: PropertyValue>(
812 &self,
813 info: TypedClrPropertyInfo<S, V>,
814 ) -> Self {
815 self.typed_property_info_with(info, false)
816 }
817
818 pub fn typed_property_info_with<S: INotifyPropertyChanged + PartialEq + 'static, V: PropertyValue>(
822 &self,
823 info: TypedClrPropertyInfo<S, V>,
824 accepts_null: bool,
825 ) -> Self {
826 ValueTypes::register_reference::<S>();
827 let info = Rc::new(info.notifying());
828 self.elements.borrow_mut().push(PathElement::TypedProperty {
829 property: info.clone(),
830 accessor_factory: PropertyInfoAccessorFactory::create_inpc_property_accessor::<S>(),
831 accepts_null,
832 typed: Rc::new(info),
833 });
834 self.clone()
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use crate::data::model::Event;
842 use crate::{StaticType, StyledElement};
843
844 struct PathVm {
845 text: RefCell<String>,
846 property_changed: Event<str>,
847 }
848
849 impl PartialEq for PathVm {
850 fn eq(&self, other: &Self) -> bool {
851 std::ptr::eq(self, other)
852 }
853 }
854
855 impl INotifyPropertyChanged for PathVm {
856 fn property_changed(&self) -> &Event<str> {
857 &self.property_changed
858 }
859 }
860
861 #[test]
862 fn elements_of_a_path_can_be_inspected() {
863 use CompiledBindingPathElementKind as Kind;
864
865 let path = CompiledBindingPathBuilder::new()
866 .typed_property::<PathVm, String>("Text", |vm| vm.text.borrow().clone(), None)
867 .build();
868 assert_eq!(path.elements().len(), 1);
869 let element = path.element(0).expect("the element");
870 assert_eq!(element.kind(), Kind::Property);
871 assert!(element.is_typed_property());
872 assert_eq!(element.name(), Some("Text"));
873 assert_eq!(element.property_type(), Some(ValueType::of::<String>()));
874 let property = element.property().expect("the property");
875 assert_eq!(property.name(), "Text");
876 assert!(!property.can_set());
877 assert!(path.element(1).is_none());
878
879 let get: UntypedGetter = Rc::new(|_| Ok(None));
880 let path = CompiledBindingPathBuilder::new()
881 .not()
882 .ancestor(Some(StyledElement::TYPE), 2)
883 .ferro_property_with(StyledElement::data_context_property().as_property(), true)
884 .notifying_property::<PathVm, Value<String>>("Text", |vm| vm.text.borrow().clone(), |vm, v| {
885 vm.text.replace(v);
886 })
887 .property_untyped("Length", ValueType::of::<i32>(), get, None, None)
888 .method_untyped("Run", Rc::new(|owner| owner.clone()), false)
889 .command_untyped("Save", Rc::new(|_, _| {}), None, &[], None)
890 .command::<PathVm>("Load", |_, _| {}, None, &[])
891 .stream_task()
892 .array_element(&[1, 2])
893 .type_cast::<StyledElement>()
894 .build();
895 let kinds: Vec<Kind> = path.elements().map(|element| element.kind()).collect();
896 assert_eq!(
897 kinds,
898 [
899 Kind::Not,
900 Kind::Ancestor,
901 Kind::Property,
902 Kind::Property,
903 Kind::Property,
904 Kind::MethodAsDelegate,
905 Kind::MethodAsCommand,
906 Kind::MethodAsCommand,
907 Kind::Stream,
908 Kind::ArrayElement,
909 Kind::TypeCast,
910 ]
911 );
912 let names: Vec<Option<&str>> = path.elements().map(|element| element.name()).collect();
913 assert_eq!(
914 names,
915 [None, None, Some("DataContext"), Some("Text"), Some("Length"), Some("Run"), Some("Save"), Some("Load"), None, None, None]
916 );
917 let ancestor = path.element(1).unwrap().ancestor().expect("an ancestor");
918 assert!(ancestor.0.is_some_and(|type_| std::ptr::eq(type_, StyledElement::TYPE)));
919 assert_eq!(ancestor.1, 2);
920
921 let registered = path.element(2).unwrap();
922 assert!(registered.accepts_null() && !registered.is_typed_property());
923 assert!(registered.property().and_then(|p| p.as_ferro_property()).is_some());
924
925 let plain = path.element(3).unwrap();
926 assert!(!plain.is_typed_property() && !plain.accepts_null());
927 assert!(plain.property().is_some_and(|property| property.can_set()));
928
929 let untyped = path.element(4).unwrap();
930 assert!(untyped.property().is_none());
931 assert_eq!(untyped.property_type(), Some(ValueType::of::<i32>()));
932 assert_eq!(path.element(5).unwrap().property_type(), None);
933 assert_eq!(path.element(9).unwrap().array_indices(), Some(&[1, 2][..]));
934 assert_eq!(path.to_string(), "!$parent[StyledElement, 2].DataContext.Text.Length.Run().Save().Load()^[1,2](StyledElement)");
935 assert_eq!(CompiledBindingPath::new().elements().len(), 0);
936 }
937}