Skip to main content

ferroui_base/data/
indexer_descriptor.rs

1use crate::data::{BindingMode, BindingPriority};
2use crate::reactive::{IDisposable, IObservable, IObserver};
3use crate::{BoxedValue, FerroObject, FerroObjectExtensions, FerroProperty, Ref};
4use std::rc::Rc;
5
6/// Holds a description of a binding for the binding indexer of
7/// [`FerroObject`] ([`FerroObject::indexer`] and
8/// [`FerroObject::bind_indexer`]).
9#[derive(Clone, Default)]
10pub struct IndexerDescriptor {
11    /// The binding mode.
12    pub mode: BindingMode,
13    /// The binding priority.
14    pub priority: BindingPriority,
15    /// The source property.
16    pub property: Option<&'static FerroProperty>,
17    /// The source object.
18    pub source: Option<Ref<FerroObject>>,
19    /// The source observable. If `None`, then `source`.`property` is used.
20    pub source_observable: Option<Rc<dyn IObservable<BoxedValue>>>,
21}
22
23impl IndexerDescriptor {
24    /// Creates an empty description.
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// A description of the binding.
30    pub fn description(&self) -> String {
31        format!(
32            "{}.{}",
33            self.source.as_ref().map_or("", |s| s.get_type().name()),
34            self.property.map_or("", |p| p.name())
35        )
36    }
37
38    /// Modifies the binding mode.
39    pub fn with_mode(mut self, mode: BindingMode) -> Self {
40        self.mode = mode;
41        self
42    }
43
44    /// Modifies the binding priority.
45    pub fn with_priority(mut self, priority: BindingPriority) -> Self {
46        self.priority = priority;
47        self
48    }
49}
50
51/// Makes a two-way binding (the `!` and `~` operators of the description).
52impl std::ops::Not for IndexerDescriptor {
53    type Output = IndexerDescriptor;
54
55    fn not(self) -> IndexerDescriptor {
56        self.with_mode(BindingMode::TwoWay)
57    }
58}
59
60impl IObservable<BoxedValue> for IndexerDescriptor {
61    /// Panics if the description has neither a source observable nor a
62    /// source object and property.
63    fn subscribe(&self, observer: Rc<dyn IObserver<BoxedValue>>) -> Rc<dyn IDisposable> {
64        if self.source_observable.is_none() && self.source.is_none() {
65            panic!("Cannot subscribe to IndexerDescriptor.");
66        }
67        let Some(property) = self.property else {
68            panic!("Cannot subscribe to IndexerDescriptor.");
69        };
70        match (&self.source_observable, &self.source) {
71            (Some(observable), _) => observable.subscribe(observer),
72            (None, Some(source)) => source.get_observable_untyped(property).subscribe(observer),
73            (None, None) => unreachable!("checked above"),
74        }
75    }
76}
77
78impl FerroProperty {
79    /// Gets a description of a binding to the property, for the binding
80    /// indexer of [`FerroObject`]. This is the `!` operator of a property:
81    /// `!Border.BackgroundProperty` is `Border::background_property().bind()`,
82    /// and the two-way forms `!!Property` and `~Property` are
83    /// `!Property.bind()`.
84    pub fn bind(&'static self) -> IndexerDescriptor {
85        IndexerDescriptor { priority: BindingPriority::LocalValue, property: Some(self), ..IndexerDescriptor::default() }
86    }
87}