Skip to main content

ferroui_base/data/
binding_base.rs

1use crate::data::BindingExpressionBase;
2use crate::{FerroObject, FerroProperty, Ref};
3use std::rc::Rc;
4
5/// The base of bindings: a description from which binding expressions are
6/// instantiated on targets.
7pub trait BindingBase {
8    /// Creates a binding expression from the binding.
9    ///
10    /// `anchor` provides, if `target` is not an element, an object from which
11    /// to locate a data context or other elements.
12    ///
13    /// This is a low-level method which returns a binding expression that is
14    /// not yet connected to a binding sink, and so is inactive.
15    fn create_instance(
16        &self,
17        target: &FerroObject,
18        target_property: Option<&'static FerroProperty>,
19        anchor: Option<&Ref<FerroObject>>,
20    ) -> Rc<dyn BindingExpressionBase>;
21
22    /// The implementing value, for casting to its concrete type
23    /// (`as_any()?.downcast_ref::<T>()`): the equivalent of `is`/`as` on the
24    /// contract. `None` for an implementation that does not expose itself.
25    fn as_any(&self) -> Option<&dyn std::any::Any> {
26        None
27    }
28
29    /// The identity of the binding, used for reference equality.
30    #[doc(hidden)]
31    fn reference_id(&self) -> *const () {
32        self as *const Self as *const ()
33    }
34}
35
36/// Handles compare by identity (reference equality), so that they can be
37/// held in property and untyped values.
38impl PartialEq for dyn BindingBase {
39    #[inline]
40    fn eq(&self, other: &Self) -> bool {
41        self.reference_id() == other.reference_id()
42    }
43}
44
45/// A shared handle of a binding is itself usable where a binding is expected
46/// (`target.bind_binding(property, &binding)` with `binding: Rc<Binding>`).
47/// It has the identity of the binding it refers to.
48impl<T: BindingBase + ?Sized> BindingBase for Rc<T> {
49    fn create_instance(
50        &self,
51        target: &FerroObject,
52        target_property: Option<&'static FerroProperty>,
53        anchor: Option<&Ref<FerroObject>>,
54    ) -> Rc<dyn BindingExpressionBase> {
55        (**self).create_instance(target, target_property, anchor)
56    }
57
58    fn as_any(&self) -> Option<&dyn std::any::Any> {
59        (**self).as_any()
60    }
61
62    fn reference_id(&self) -> *const () {
63        (**self).reference_id()
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::data::{CompiledBinding, CompiledBindingPath, ReflectionBinding};
71
72    #[test]
73    fn a_binding_is_cast_to_its_concrete_class_through_any_handle() {
74        let reflection = ReflectionBinding::new("Name");
75        let contract: Rc<dyn BindingBase> = reflection.clone();
76        assert!(contract.as_any().is_some_and(|any| any.is::<ReflectionBinding>()));
77        // The handle of the concrete class, used as a binding itself.
78        let handle: &dyn BindingBase = &reflection;
79        let concrete = handle.as_any().and_then(|any| any.downcast_ref::<ReflectionBinding>());
80        assert!(concrete.is_some_and(|binding| std::ptr::eq(binding, &*reflection)));
81        // And the handle of the contract.
82        let handle: &dyn BindingBase = &contract;
83        assert!(handle.as_any().is_some_and(|any| any.is::<ReflectionBinding>()));
84
85        let compiled: Rc<dyn BindingBase> = CompiledBinding::new(CompiledBindingPath::new());
86        assert!(compiled.as_any().is_some_and(|any| any.is::<CompiledBinding>()));
87        assert!(!compiled.as_any().is_some_and(|any| any.is::<ReflectionBinding>()));
88    }
89}