Skip to main content

ferroui_base/
ferro_property_metadata.rs

1use crate::data::BindingMode;
2use crate::{FerroObject, TypeInfo};
3use rustc_hash::FxHashMap;
4use std::cell::RefCell;
5use std::rc::Rc;
6
7/// Metadata common to all property kinds.
8#[derive(Clone, Debug)]
9pub struct FerroPropertyMetadata {
10    default_binding_mode: BindingMode,
11    enable_data_validation: Option<bool>,
12    is_read_only: bool,
13}
14
15impl FerroPropertyMetadata {
16    pub fn new(default_binding_mode: BindingMode, enable_data_validation: Option<bool>) -> Self {
17        Self { default_binding_mode, enable_data_validation, is_read_only: false }
18    }
19
20    /// The default binding mode for the property.
21    pub fn default_binding_mode(&self) -> BindingMode {
22        if self.default_binding_mode == BindingMode::Default {
23            BindingMode::OneWay
24        } else {
25            self.default_binding_mode
26        }
27    }
28
29    /// Whether the property is interested in data validation.
30    ///
31    /// Data validation is validation performed at the target of a binding, for
32    /// example in a view model. To enable it on a binding, the property must
33    /// also enable it here.
34    pub fn enable_data_validation(&self) -> Option<bool> {
35        self.enable_data_validation
36    }
37
38    /// Whether the metadata has been frozen.
39    pub fn is_read_only(&self) -> bool {
40        self.is_read_only
41    }
42
43    /// Merges unset members from the base metadata.
44    pub fn merge(&mut self, base: &FerroPropertyMetadata) {
45        assert!(!self.is_read_only, "The metadata is read-only.");
46        if self.default_binding_mode == BindingMode::Default {
47            self.default_binding_mode = base.default_binding_mode();
48        }
49        if self.enable_data_validation.is_none() {
50            self.enable_data_validation = base.enable_data_validation;
51        }
52    }
53
54    /// Makes the metadata read-only.
55    pub fn freeze(&mut self) {
56        self.is_read_only = true;
57    }
58}
59
60/// The coercion callback of a styled property.
61pub type CoerceValueCallback<T> = Rc<dyn Fn(&FerroObject, T) -> T>;
62
63/// Metadata for styled properties.
64pub struct StyledPropertyMetadata<T> {
65    base: FerroPropertyMetadata,
66    default_value: Option<T>,
67    coerce: Option<CoerceValueCallback<T>>,
68}
69
70impl<T: Clone> Clone for StyledPropertyMetadata<T> {
71    fn clone(&self) -> Self {
72        Self { base: self.base.clone(), default_value: self.default_value.clone(), coerce: self.coerce.clone() }
73    }
74}
75
76impl<T> std::ops::Deref for StyledPropertyMetadata<T> {
77    type Target = FerroPropertyMetadata;
78    fn deref(&self) -> &FerroPropertyMetadata {
79        &self.base
80    }
81}
82
83impl<T: Clone + 'static> StyledPropertyMetadata<T> {
84    /// Creates metadata. A `default_value` of `None` inherits the default
85    /// value of the base metadata when merged.
86    pub fn new(default_value: Option<T>) -> Self {
87        Self {
88            base: FerroPropertyMetadata::new(BindingMode::Default, Some(false)),
89            default_value,
90            coerce: None,
91        }
92    }
93
94    pub fn with_default_binding_mode(mut self, mode: BindingMode) -> Self {
95        self.base.default_binding_mode = mode;
96        self
97    }
98
99    pub fn with_coerce(mut self, coerce: impl Fn(&FerroObject, T) -> T + 'static) -> Self {
100        self.coerce = Some(Rc::new(coerce));
101        self
102    }
103
104    pub fn with_enable_data_validation(mut self, enable: bool) -> Self {
105        self.base.enable_data_validation = Some(enable);
106        self
107    }
108
109    /// The default value of the property. Panics if the metadata has not been
110    /// merged with a base that provides one.
111    #[inline]
112    pub fn default_value(&self) -> &T {
113        self.default_value.as_ref().expect("metadata has no default value")
114    }
115
116    pub(crate) fn has_default_value(&self) -> bool {
117        self.default_value.is_some()
118    }
119
120    /// The value coercion callback, if any.
121    #[inline]
122    pub fn coerce_value(&self) -> Option<&CoerceValueCallback<T>> {
123        self.coerce.as_ref()
124    }
125}
126
127/// Metadata for direct properties.
128#[derive(Clone)]
129pub struct DirectPropertyMetadata<T> {
130    base: FerroPropertyMetadata,
131    unset_value: Option<T>,
132}
133
134impl<T> std::ops::Deref for DirectPropertyMetadata<T> {
135    type Target = FerroPropertyMetadata;
136    fn deref(&self) -> &FerroPropertyMetadata {
137        &self.base
138    }
139}
140
141impl<T: Clone + 'static> DirectPropertyMetadata<T> {
142    pub fn new(unset_value: Option<T>) -> Self {
143        Self { base: FerroPropertyMetadata::new(BindingMode::Default, None), unset_value }
144    }
145
146    pub fn with_default_binding_mode(mut self, mode: BindingMode) -> Self {
147        self.base.default_binding_mode = mode;
148        self
149    }
150
151    pub fn with_enable_data_validation(mut self, enable: bool) -> Self {
152        self.base.enable_data_validation = Some(enable);
153        self
154    }
155
156    /// The value to use when the property is set to the unset value.
157    #[inline]
158    pub fn unset_value(&self) -> &T {
159        self.unset_value.as_ref().expect("metadata has no unset value")
160    }
161}
162
163/// Behaviour shared by the typed metadata kinds so that [`MetadataTable`] can
164/// manage them generically.
165pub trait PropertyMetadata: Sized + 'static {
166    fn base(&self) -> &FerroPropertyMetadata;
167    fn merge(&mut self, base: &Self);
168    fn freeze(&mut self);
169    /// Returns metadata that is safe to use for any host type: a copy without
170    /// host-specific callbacks.
171    fn generate_type_safe_metadata(this: &Rc<Self>) -> Rc<Self>;
172}
173
174impl<T: Clone + 'static> PropertyMetadata for StyledPropertyMetadata<T> {
175    fn base(&self) -> &FerroPropertyMetadata {
176        &self.base
177    }
178
179    fn merge(&mut self, base: &Self) {
180        self.base.merge(&base.base);
181        if self.default_value.is_none() {
182            self.default_value = base.default_value.clone();
183        }
184        if self.coerce.is_none() {
185            self.coerce = base.coerce.clone();
186        }
187    }
188
189    fn freeze(&mut self) {
190        self.base.freeze();
191    }
192
193    fn generate_type_safe_metadata(this: &Rc<Self>) -> Rc<Self> {
194        if this.is_read_only() && this.coerce.is_none() {
195            return this.clone();
196        }
197        let mut copy = StyledPropertyMetadata::new(this.default_value.clone())
198            .with_default_binding_mode(this.default_binding_mode())
199            .with_enable_data_validation(this.enable_data_validation().unwrap_or(false));
200        copy.freeze();
201        Rc::new(copy)
202    }
203}
204
205impl<T: Clone + 'static> PropertyMetadata for DirectPropertyMetadata<T> {
206    fn base(&self) -> &FerroPropertyMetadata {
207        &self.base
208    }
209
210    fn merge(&mut self, base: &Self) {
211        self.base.merge(&base.base);
212        if self.unset_value.is_none() {
213            self.unset_value = base.unset_value.clone();
214        }
215    }
216
217    fn freeze(&mut self) {
218        self.base.freeze();
219    }
220
221    fn generate_type_safe_metadata(this: &Rc<Self>) -> Rc<Self> {
222        this.clone()
223    }
224}
225
226/// Per-host-type metadata storage of a property, with the lookup rules of the
227/// property system: the metadata registered for the nearest base class of the
228/// queried type wins, falling back to the type-safe default metadata.
229pub(crate) struct MetadataTable<M> {
230    default_metadata: Rc<M>,
231    inner: RefCell<TableInner<M>>,
232}
233
234struct TableInner<M> {
235    /// Fast path while only the registration metadata exists.
236    single: Option<(&'static TypeInfo, Rc<M>)>,
237    metadata: Vec<(&'static TypeInfo, Rc<M>)>,
238    /// The metadata of every type it was asked for, by the address of the type.
239    ///
240    /// A hash map, as the reference's dictionary of the metadata of each type (`_metadataCache`
241    /// of the property); the port scanned a vector, which grows with every type a common property
242    /// (`DataContext`, `Background`) is read on (DEVIATIONS.md, corrected divergences).
243    cache: FxHashMap<usize, Rc<M>>,
244}
245
246impl<M: PropertyMetadata> MetadataTable<M> {
247    pub fn new(host_type: &'static TypeInfo, mut metadata: M) -> Self {
248        metadata.freeze();
249        let metadata = Rc::new(metadata);
250        let default_metadata = M::generate_type_safe_metadata(&metadata);
251        Self {
252            default_metadata,
253            inner: RefCell::new(TableInner {
254                single: Some((host_type, metadata.clone())),
255                metadata: vec![(host_type, metadata)],
256                cache: FxHashMap::default(),
257            }),
258        }
259    }
260
261    /// A table for a property that shares its identity with `source` (added
262    /// owner) and optionally carries metadata for the new owner.
263    pub fn for_added_owner(source: &Self, owner_type: &'static TypeInfo, metadata: Option<M>) -> Self {
264        let mut entries = Vec::new();
265        if let Some(m) = metadata {
266            entries.push((owner_type, Rc::new(m)));
267        }
268        Self {
269            default_metadata: source.default_metadata.clone(),
270            inner: RefCell::new(TableInner { single: None, metadata: entries, cache: FxHashMap::default() }),
271        }
272    }
273
274    #[inline]
275    pub fn default_metadata(&self) -> &Rc<M> {
276        &self.default_metadata
277    }
278
279    pub fn get(&self, type_: &'static TypeInfo) -> Rc<M> {
280        let mut inner = self.inner.borrow_mut();
281        if let Some((host, single)) = &inner.single {
282            if Rc::ptr_eq(single, &self.default_metadata) {
283                return self.default_metadata.clone();
284            }
285            return if host.is_assignable_from(type_) { single.clone() } else { self.default_metadata.clone() };
286        }
287        let key = type_ as *const TypeInfo as usize;
288        if let Some(m) = inner.cache.get(&key) {
289            return m.clone();
290        }
291        let mut current = Some(type_);
292        let mut result = None;
293        while let Some(t) = current {
294            if let Some((_, m)) = inner.metadata.iter().find(|(mt, _)| std::ptr::eq(*mt, t)) {
295                result = Some(m.clone());
296                break;
297            }
298            current = t.base_type();
299        }
300        let result = result.unwrap_or_else(|| self.default_metadata.clone());
301        inner.cache.insert(key, result.clone());
302        result
303    }
304
305    pub fn override_metadata(&self, property_name: &str, type_: &'static TypeInfo, mut metadata: M) {
306        let already_set = self.inner.borrow().metadata.iter().any(|(t, _)| std::ptr::eq(*t, type_));
307        if already_set {
308            panic!("Metadata is already set for {property_name} on {type_}.");
309        }
310        let base = self.get(type_);
311        metadata.merge(&base);
312        metadata.freeze();
313        let mut inner = self.inner.borrow_mut();
314        inner.metadata.push((type_, Rc::new(metadata)));
315        inner.cache.clear();
316        inner.single = None;
317    }
318
319    pub fn unregister(&self, type_: &'static TypeInfo) {
320        let mut inner = self.inner.borrow_mut();
321        inner.metadata.retain(|(t, _)| !std::ptr::eq(*t, type_));
322        inner.cache.remove(&(type_ as *const TypeInfo as usize));
323        inner.single = None;
324    }
325}