Skip to main content

ferroui_base/
ferro_property.rs

1use crate::data::{BindingMode, BindingPriority};
2use crate::property_store::{EffectiveValueDyn, IValueEntry};
3use crate::reactive::{Disposable, IDisposable, IObservable};
4use crate::utilities::HandlerList;
5use crate::{
6    AttachedProperty, DirectProperty, DirectPropertyMetadata, FerroObject, FerroPropertyChangedEventArgs,
7    FerroPropertyMetadata, FerroPropertyRegistry, ObjectType, StaticType, StyledProperty, StyledPropertyMetadata,
8    TypeInfo,
9};
10use std::any::{Any, TypeId};
11use std::cell::{Cell, OnceCell};
12use std::fmt;
13use std::hash::{Hash, Hasher};
14use std::rc::Rc;
15use std::sync::atomic::{AtomicU32, Ordering};
16
17/// The bounds every property value type must satisfy.
18pub trait PropertyValue: Clone + PartialEq + 'static {}
19
20impl<T: Clone + PartialEq + 'static> PropertyValue for T {}
21
22/// A value of any property value type, for untyped access to properties.
23///
24/// Implemented for every type that can be a property value. Unlike
25/// [`Any`], it supports equality, so untyped values can themselves be
26/// property values (for example a data context).
27pub trait AnyValue: Any {
28    /// The value as [`Any`]. Call [`as_any`](trait.AnyValue.html#method.as_any)
29    /// on `dyn AnyValue` instead.
30    ///
31    /// The trait is implemented for every `PartialEq` type, which includes
32    /// `Rc<dyn AnyValue>` itself. The trait methods therefore have names that
33    /// cannot be reached by accident on a boxed value: the inherent methods
34    /// of `dyn AnyValue` are the API, and they dereference through the box.
35    #[doc(hidden)]
36    fn any_value_as_any(&self) -> &dyn Any;
37
38    /// Compares with another untyped value: equal if both hold the same type
39    /// and the values are equal. Use `==` on `dyn AnyValue` instead.
40    #[doc(hidden)]
41    fn any_value_eq(&self, other: &dyn AnyValue) -> bool;
42
43    /// The Rust type name of the contained value. Use `type_name` on
44    /// `dyn AnyValue` instead.
45    #[doc(hidden)]
46    fn any_value_type_name(&self) -> &'static str;
47}
48
49impl<T: PartialEq + 'static> AnyValue for T {
50    #[inline]
51    fn any_value_as_any(&self) -> &dyn Any {
52        self
53    }
54
55    fn any_value_eq(&self, other: &dyn AnyValue) -> bool {
56        other.any_value_as_any().downcast_ref::<T>().is_some_and(|other| value_equals(self, other))
57    }
58
59    fn any_value_type_name(&self) -> &'static str {
60        std::any::type_name::<T>()
61    }
62}
63
64/// Deviation (DEVIATIONS.md, Property system): the comparer of the reference
65/// in place of `PartialEq` alone.
66///
67/// Whether two property values are equal as the reference compares them
68/// (`EqualityComparer<T>.Default.Equals`, and `object.Equals` for boxed
69/// values): `==`, and a floating-point value that is not a number equals
70/// another that is not a number, bare or nullable. With `==` alone a value
71/// store whose value is NaN (a `Width` that is not set) reports a change
72/// every time the value is set again.
73#[inline]
74pub fn value_equals<T: PartialEq + 'static>(a: &T, b: &T) -> bool {
75    a == b || both_not_a_number(a, b)
76}
77
78#[inline]
79fn both_not_a_number<T: 'static>(a: &T, b: &T) -> bool {
80    let (a, b): (&dyn Any, &dyn Any) = (a, b);
81    if let (Some(a), Some(b)) = (a.downcast_ref::<f64>(), b.downcast_ref::<f64>()) {
82        return a.is_nan() && b.is_nan();
83    }
84    if let (Some(a), Some(b)) = (a.downcast_ref::<f32>(), b.downcast_ref::<f32>()) {
85        return a.is_nan() && b.is_nan();
86    }
87    if let (Some(Some(a)), Some(Some(b))) = (a.downcast_ref::<Option<f64>>(), b.downcast_ref::<Option<f64>>()) {
88        return a.is_nan() && b.is_nan();
89    }
90    if let (Some(Some(a)), Some(Some(b))) = (a.downcast_ref::<Option<f32>>(), b.downcast_ref::<Option<f32>>()) {
91        return a.is_nan() && b.is_nan();
92    }
93    false
94}
95
96impl dyn AnyValue {
97    /// The contained value as [`Any`], for downcasting.
98    #[inline]
99    pub fn as_any(&self) -> &dyn Any {
100        self.any_value_as_any()
101    }
102
103    /// The [`TypeId`] of the contained value.
104    #[inline]
105    pub fn value_type_id(&self) -> TypeId {
106        self.any_value_as_any().type_id()
107    }
108
109    /// The Rust type name of the contained value.
110    #[inline]
111    pub fn type_name(&self) -> &'static str {
112        self.any_value_type_name()
113    }
114
115    /// Whether the contained value is of type `T`.
116    #[inline]
117    pub fn is<T: 'static>(&self) -> bool {
118        self.any_value_as_any().is::<T>()
119    }
120
121    /// The contained value, if it is of type `T`.
122    #[inline]
123    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
124        self.any_value_as_any().downcast_ref::<T>()
125    }
126
127    /// Compares with another untyped value: equal if both hold the same type
128    /// and the values are equal.
129    #[inline]
130    pub fn value_eq(&self, other: &dyn AnyValue) -> bool {
131        self.any_value_eq(other)
132    }
133}
134
135impl PartialEq for dyn AnyValue {
136    fn eq(&self, other: &Self) -> bool {
137        self.any_value_eq(other)
138    }
139}
140
141impl fmt::Debug for dyn AnyValue {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "AnyValue<{}>", self.any_value_type_name())
144    }
145}
146
147/// An untyped, shared property value. The contained value is always exactly
148/// of the property's value type (or one of the marker types
149/// [`UnsetValueType`] and [`DoNothingType`]).
150pub type BoxedValue = Rc<dyn AnyValue>;
151
152/// The type of the marker value that represents an unset property value.
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154pub struct UnsetValueType;
155
156impl fmt::Display for UnsetValueType {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.write_str("(unset)")
159    }
160}
161
162/// The type of the marker value that tells a binding target to do nothing.
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164pub struct DoNothingType;
165
166impl fmt::Display for DoNothingType {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        f.write_str("(do nothing)")
169    }
170}
171
172/// A callback invoked before and after a property change notification is
173/// raised on an object (`true` before, `false` after).
174pub type NotifyingCallback = Box<dyn Fn(&FerroObject, bool)>;
175
176/// Untyped operations that a property routes to its typed implementation.
177pub(crate) trait PropertyRoutes {
178    fn create_effective_value(&'static self, o: &FerroObject) -> Rc<dyn EffectiveValueDyn>;
179    fn route_clear_value(&'static self, o: &FerroObject);
180    fn route_coerce_default_value(&'static self, o: &FerroObject);
181    fn route_get_value(&'static self, o: &FerroObject) -> BoxedValue;
182    fn route_get_base_value(&'static self, o: &FerroObject) -> BoxedValue;
183    fn route_get_default_value(&'static self, type_: &'static TypeInfo) -> BoxedValue;
184    fn route_set_value(&'static self, o: &FerroObject, value: &dyn Any, priority: BindingPriority)
185        -> Option<Rc<dyn IDisposable>>;
186    fn route_set_current_value(&'static self, o: &FerroObject, value: &dyn Any);
187    fn route_set_direct_value_unchecked(&'static self, o: &FerroObject, entry: &dyn IValueEntry);
188    fn route_bind(
189        &'static self,
190        o: &FerroObject,
191        source: Rc<dyn IObservable<BoxedValue>>,
192        priority: BindingPriority,
193    ) -> Rc<dyn IDisposable>;
194    fn route_get_metadata(&'static self, type_: &'static TypeInfo) -> FerroPropertyMetadata;
195    fn route_unregister(&'static self, type_: &'static TypeInfo);
196    fn route_is_valid_value(&'static self, value: &dyn Any) -> bool;
197    /// The typed property behind the routes, for downcasting.
198    fn as_any(&'static self) -> &'static dyn Any;
199    /// Clones `value`, which must hold exactly the property's value type `T`,
200    /// into `out`, which must be an `&mut Option<T>`. Returns false if either
201    /// has a different type.
202    fn route_copy_value(&'static self, value: &dyn Any, out: &mut dyn Any) -> bool;
203    /// Clones `value`, which must hold exactly the property's value type `T`,
204    /// into a box of its own. `None` if it has a different type.
205    fn route_box_value(&'static self, value: &dyn Any) -> Option<BoxedValue>;
206    /// Creates a value entry for `frame` that reads its values from `source`.
207    /// Only supported by styled properties.
208    fn route_create_binding_entry(
209        &'static self,
210        o: &FerroObject,
211        frame: std::rc::Weak<dyn crate::property_store::ValueFrame>,
212        source: Rc<dyn IObservable<BoxedValue>>,
213    ) -> Rc<dyn IValueEntry>;
214}
215
216type ChangedHandler = dyn Fn(&FerroPropertyChangedEventArgs<'_>);
217
218/// The stream of change notifications of a property across all objects.
219pub struct PropertyChangedObservable {
220    handlers: HandlerList<ChangedHandler>,
221}
222
223impl PropertyChangedObservable {
224    fn new() -> Self {
225        Self { handlers: HandlerList::new() }
226    }
227
228    /// Subscribes to changes of the property on any object.
229    pub fn subscribe(&'static self, handler: impl Fn(&FerroPropertyChangedEventArgs<'_>) + 'static) -> Rc<dyn IDisposable> {
230        let token = self.handlers.add(Rc::new(handler));
231        Disposable::create(move || {
232            self.handlers.remove(token);
233        })
234    }
235
236    /// Subscribes a handler that is only invoked for objects of class
237    /// `TTarget` (or classes derived from it).
238    pub fn add_class_handler<TTarget: ObjectType>(
239        &'static self,
240        handler: impl Fn(&TTarget, &FerroPropertyChangedEventArgs<'_>) + 'static,
241    ) -> Rc<dyn IDisposable> {
242        self.subscribe(move |e| {
243            if let Some(target) = e.sender().downcast_ref::<TTarget>() {
244                handler(target, e);
245            }
246        })
247    }
248
249    #[inline]
250    pub(crate) fn notify(&self, e: &FerroPropertyChangedEventArgs<'_>) {
251        if self.handlers.is_empty() {
252            return;
253        }
254        crate::perf_count!(PropertyChangesWithPropertyHandlers);
255        for (_, handler) in self.handlers.snapshot().iter() {
256            handler(e);
257        }
258    }
259}
260
261static NEXT_ID: AtomicU32 = AtomicU32::new(0);
262
263/// Base class for properties: the untyped identity and description of a
264/// property that can be set on a [`FerroObject`].
265pub struct FerroProperty {
266    name: Box<str>,
267    property_type: TypeId,
268    property_type_name: &'static str,
269    owner_type: &'static TypeInfo,
270    id: u32,
271    inherits: bool,
272    is_attached: bool,
273    is_direct: bool,
274    is_read_only: bool,
275    assign_binding: Cell<bool>,
276    host_type: Cell<Option<&'static TypeInfo>>,
277    notifying: Option<Rc<dyn Fn(&FerroObject, bool)>>,
278    changed: Rc<PropertyChangedObservable>,
279    routes: OnceCell<&'static dyn PropertyRoutes>,
280}
281
282pub(crate) struct FerroPropertyInit {
283    pub inherits: bool,
284    pub is_attached: bool,
285    pub is_direct: bool,
286    pub is_read_only: bool,
287}
288
289impl FerroProperty {
290    pub(crate) fn new<T: 'static>(
291        name: &str,
292        owner_type: &'static TypeInfo,
293        init: FerroPropertyInit,
294        notifying: Option<NotifyingCallback>,
295    ) -> Self {
296        assert!(!name.contains('.'), "'name' may not contain periods.");
297        Self {
298            name: name.into(),
299            property_type: TypeId::of::<T>(),
300            property_type_name: std::any::type_name::<T>(),
301            owner_type,
302            id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
303            inherits: init.inherits,
304            is_attached: init.is_attached,
305            is_direct: init.is_direct,
306            is_read_only: init.is_read_only,
307            assign_binding: Cell::new(false),
308            host_type: Cell::new(None),
309            notifying: notifying.map(Rc::from),
310            changed: Rc::new(PropertyChangedObservable::new()),
311            routes: OnceCell::new(),
312        }
313    }
314
315    /// Creates the base of a property that shares its identity with `source`
316    /// but has a different owner.
317    pub(crate) fn for_added_owner(source: &FerroProperty, owner_type: &'static TypeInfo, is_read_only: bool) -> Self {
318        Self {
319            name: source.name.clone(),
320            property_type: source.property_type,
321            property_type_name: source.property_type_name,
322            owner_type,
323            id: source.id,
324            inherits: source.inherits,
325            is_attached: source.is_attached,
326            is_direct: source.is_direct,
327            is_read_only,
328            assign_binding: source.assign_binding.clone(),
329            host_type: source.host_type.clone(),
330            notifying: source.notifying.clone(),
331            changed: source.changed.clone(),
332            routes: OnceCell::new(),
333        }
334    }
335
336    pub(crate) fn set_routes(&self, routes: &'static dyn PropertyRoutes) {
337        if self.routes.set(routes).is_err() {
338            panic!("property routes are already set");
339        }
340    }
341
342    #[inline]
343    pub(crate) fn routes(&self) -> &'static dyn PropertyRoutes {
344        *self.routes.get().expect("property is not fully registered")
345    }
346
347    /// The class an attached property is declared for: the `THost` of its
348    /// registration, the type of the objects its accessors (`GetX`/`SetX`)
349    /// take. `None` for properties that are not attached.
350    #[inline]
351    pub fn host_type(&self) -> Option<&'static TypeInfo> {
352        self.host_type.get()
353    }
354
355    /// The name of the property.
356    #[inline]
357    pub fn name(&self) -> &str {
358        &self.name
359    }
360
361    /// The type of the property's value.
362    #[inline]
363    pub fn property_type(&self) -> TypeId {
364        self.property_type
365    }
366
367    /// The Rust type name of the property's value type.
368    #[inline]
369    pub fn property_type_name(&self) -> &'static str {
370        self.property_type_name
371    }
372
373    // The errors of the typed property routes. They only depend on the
374    // property, so they are compiled once instead of once per value type.
375
376    /// The message of a value that is not of the value type of the property.
377    #[cold]
378    pub(crate) fn invalid_value_message(&self) -> String {
379        format!("Invalid value for property '{}': expected {}.", self.name(), self.property_type_name())
380    }
381
382    /// Panics for a value that is not of the value type of the property.
383    #[cold]
384    #[track_caller]
385    pub(crate) fn invalid_value_type(&self) -> ! {
386        panic!("Invalid value for property '{}': expected {}.", self.name(), self.property_type_name())
387    }
388
389    /// Panics for a value that the validation of the property rejects.
390    #[cold]
391    #[track_caller]
392    pub(crate) fn value_not_valid(&self) -> ! {
393        panic!("the value is not valid for property '{}'", self.name())
394    }
395
396    /// The class that registered the property.
397    #[inline]
398    pub fn owner_type(&self) -> &'static TypeInfo {
399        self.owner_type
400    }
401
402    /// Whether the property inherits its value.
403    #[inline]
404    pub fn inherits(&self) -> bool {
405        self.inherits
406    }
407
408    /// Whether this is an attached property.
409    #[inline]
410    pub fn is_attached(&self) -> bool {
411        self.is_attached
412    }
413
414    /// Whether this is a direct property.
415    #[inline]
416    pub fn is_direct(&self) -> bool {
417        self.is_direct
418    }
419
420    /// Whether this is a readonly property.
421    #[inline]
422    pub fn is_read_only(&self) -> bool {
423        self.is_read_only
424    }
425
426    /// Whether a binding given for this property in markup is assigned to
427    /// the property as its value rather than bound to it (the "assign
428    /// binding" attribute of the managed property). The property's value
429    /// type is then a binding type. A fact about the definition: it is the
430    /// same for every owner and is not part of the metadata.
431    #[inline]
432    pub fn assign_binding(&self) -> bool {
433        self.assign_binding.get()
434    }
435
436    /// Marks the property as one that bindings are assigned to. Part of the
437    /// definition of the property: call it where the property is registered
438    /// (styled and attached properties state it through
439    /// [`StyledPropertyOptions::assign_binding`]).
440    pub fn set_assign_binding(&self, value: bool) {
441        self.assign_binding.set(value);
442    }
443
444    /// An integer ID that uniquely identifies the property; shared by all
445    /// owners added with `add_owner`.
446    #[inline]
447    pub fn id(&self) -> u32 {
448        self.id
449    }
450
451    /// Notifications of changes to this property on any object.
452    ///
453    /// The handlers are invoked after the object's own change handling.
454    #[inline]
455    pub fn changed(&'static self) -> &'static PropertyChangedObservable {
456        // The observable is shared between a property and its added owners.
457        &self.changed
458    }
459
460    #[inline]
461    pub(crate) fn notify_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
462        self.changed.notify(e)
463    }
464
465    #[inline]
466    pub(crate) fn notifying(&self) -> Option<&dyn Fn(&FerroObject, bool)> {
467        self.notifying.as_deref()
468    }
469
470    /// Gets the base metadata of the property on the specified type.
471    pub fn get_metadata(&self, type_: &'static TypeInfo) -> FerroPropertyMetadata {
472        self.routes().route_get_metadata(type_)
473    }
474
475    /// Gets the base metadata of the property on the type of `owner`.
476    pub fn get_metadata_for(&self, owner: &FerroObject) -> FerroPropertyMetadata {
477        self.routes().route_get_metadata(owner.get_type())
478    }
479
480    /// The default value of the property for `type_`, untyped: the default of
481    /// the metadata of a styled or attached property (upstream's internal
482    /// `IStyledPropertyAccessor.GetDefaultValue(Type)`), the unset value of a
483    /// direct property. For tests only: the snapshot of the property defaults
484    /// (`property_defaults_tests` of `ferroui-controls`) reads it.
485    #[cfg(feature = "testing")]
486    #[doc(hidden)]
487    pub fn default_value_boxed(&self, type_: &'static TypeInfo) -> BoxedValue {
488        self.routes().route_get_default_value(type_)
489    }
490
491    /// Removes the metadata registered for the property on the specified
492    /// type.
493    pub fn unregister(&self, type_: &'static TypeInfo) {
494        self.routes().route_unregister(type_)
495    }
496
497    /// Checks whether `value` is valid for the property: of the property's
498    /// value type and accepted by its validation callback.
499    pub fn is_valid_value(&self, value: &dyn Any) -> bool {
500        self.routes().route_is_valid_value(value)
501    }
502
503    /// The property as a styled (or attached) property with value type `T`:
504    /// the typed view needed to read, set and bind it without boxing.
505    /// `None` for direct properties and when `T` is not the property's value
506    /// type.
507    pub fn as_styled<T: crate::PropertyValue>(&'static self) -> Option<&'static StyledProperty<T>> {
508        self.routes.get().and_then(|routes| routes.as_any().downcast_ref::<StyledProperty<T>>())
509    }
510
511    /// The marker value that represents an unset property value.
512    pub fn unset_value() -> BoxedValue {
513        crate::ferro_thread_local! {
514            static UNSET: BoxedValue = Rc::new(UnsetValueType);
515        }
516        UNSET.with(Rc::clone)
517    }
518
519    /// Registers a styled property with a default value.
520    pub fn register<TOwner: ObjectType, TValue: PropertyValue>(
521        name: &str,
522        default_value: TValue,
523    ) -> &'static StyledProperty<TValue> {
524        Self::register_with::<TOwner, TValue>(name, StyledPropertyOptions::new(default_value))
525    }
526
527    /// Registers a styled property.
528    pub fn register_with<TOwner: ObjectType, TValue: PropertyValue>(
529        name: &str,
530        options: StyledPropertyOptions<TValue>,
531    ) -> &'static StyledProperty<TValue> {
532        let assign_binding = options.assign_binding;
533        let (metadata, inherits, validate, notifying) = options.into_parts();
534        let result = StyledProperty::create(name, TOwner::TYPE, TOwner::TYPE, metadata, inherits, validate, notifying, false);
535        result.set_assign_binding(assign_binding);
536        FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
537        result
538    }
539
540    /// Registers an attached property with a default value.
541    ///
542    /// `TOwner` is the class that registers the property and `THost` the class
543    /// of objects the property can be set on.
544    pub fn register_attached<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
545        name: &str,
546        default_value: TValue,
547    ) -> &'static AttachedProperty<TValue> {
548        Self::register_attached_with::<TOwner, THost, TValue>(name, StyledPropertyOptions::new(default_value))
549    }
550
551    /// Registers an attached property.
552    pub fn register_attached_with<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
553        name: &str,
554        options: StyledPropertyOptions<TValue>,
555    ) -> &'static AttachedProperty<TValue> {
556        let assign_binding = options.assign_binding;
557        let (metadata, inherits, validate, notifying) = options.into_parts();
558        let result = AttachedProperty::create(name, TOwner::TYPE, THost::TYPE, metadata, inherits, validate, notifying);
559        result.set_assign_binding(assign_binding);
560        result.host_type.set(Some(THost::TYPE));
561        let registry = FerroPropertyRegistry::instance();
562        registry.register(TOwner::TYPE, result);
563        registry.register_attached(THost::TYPE, result);
564        result
565    }
566
567    /// Registers a direct property: a property whose value is stored in a
568    /// field of the owner and accessed through a getter and setter.
569    pub fn register_direct<TOwner: ObjectType, TValue: PropertyValue>(
570        name: &str,
571        getter: fn(&TOwner) -> TValue,
572        setter: Option<fn(&TOwner, TValue)>,
573        unset_value: TValue,
574    ) -> &'static DirectProperty<TOwner, TValue> {
575        Self::register_direct_with(name, getter, setter, DirectPropertyMetadata::new(Some(unset_value)))
576    }
577
578    /// Registers a direct property with explicit metadata.
579    pub fn register_direct_with<TOwner: ObjectType, TValue: PropertyValue>(
580        name: &str,
581        getter: fn(&TOwner) -> TValue,
582        setter: Option<fn(&TOwner, TValue)>,
583        metadata: DirectPropertyMetadata<TValue>,
584    ) -> &'static DirectProperty<TOwner, TValue> {
585        let result = DirectProperty::create(name, getter, setter, metadata);
586        FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
587        result
588    }
589}
590
591impl PartialEq for FerroProperty {
592    #[inline]
593    fn eq(&self, other: &Self) -> bool {
594        self.id == other.id
595    }
596}
597
598impl Eq for FerroProperty {}
599
600impl Hash for FerroProperty {
601    fn hash<H: Hasher>(&self, state: &mut H) {
602        self.id.hash(state)
603    }
604}
605
606impl fmt::Debug for FerroProperty {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        f.write_str(&self.name)
609    }
610}
611
612impl fmt::Display for FerroProperty {
613    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614        f.write_str(&self.name)
615    }
616}
617
618/// Options for registering a styled or attached property.
619pub struct StyledPropertyOptions<T> {
620    default_value: T,
621    inherits: bool,
622    default_binding_mode: BindingMode,
623    validate: Option<Box<dyn Fn(&T) -> bool>>,
624    coerce: Option<Rc<dyn Fn(&FerroObject, T) -> T>>,
625    enable_data_validation: bool,
626    assign_binding: bool,
627    notifying: Option<NotifyingCallback>,
628}
629
630impl<T: PropertyValue> StyledPropertyOptions<T> {
631    pub fn new(default_value: T) -> Self {
632        Self {
633            default_value,
634            inherits: false,
635            default_binding_mode: BindingMode::OneWay,
636            validate: None,
637            coerce: None,
638            enable_data_validation: false,
639            assign_binding: false,
640            notifying: None,
641        }
642    }
643
644    /// Whether the property inherits its value from the parent object.
645    pub fn inherits(mut self, inherits: bool) -> Self {
646        self.inherits = inherits;
647        self
648    }
649
650    /// The default binding mode for the property.
651    pub fn default_binding_mode(mut self, mode: BindingMode) -> Self {
652        self.default_binding_mode = mode;
653        self
654    }
655
656    /// A value validation callback. Setting a value for which it returns
657    /// false is rejected.
658    pub fn validate(mut self, validate: impl Fn(&T) -> bool + 'static) -> Self {
659        self.validate = Some(Box::new(validate));
660        self
661    }
662
663    /// A value coercion callback.
664    pub fn coerce(mut self, coerce: impl Fn(&FerroObject, T) -> T + 'static) -> Self {
665        self.coerce = Some(Rc::new(coerce));
666        self
667    }
668
669    /// Whether the property is interested in data validation.
670    pub fn enable_data_validation(mut self, enable: bool) -> Self {
671        self.enable_data_validation = enable;
672        self
673    }
674
675    /// Whether a binding given for the property in markup is assigned to the
676    /// property instead of bound to it; see [`FerroProperty::assign_binding`].
677    pub fn assign_binding(mut self, assign_binding: bool) -> Self {
678        self.assign_binding = assign_binding;
679        self
680    }
681
682    /// A callback invoked before and after change notifications are raised.
683    pub fn notifying(mut self, notifying: impl Fn(&FerroObject, bool) + 'static) -> Self {
684        self.notifying = Some(Box::new(notifying));
685        self
686    }
687
688    #[allow(clippy::type_complexity)]
689    fn into_parts(self) -> (StyledPropertyMetadata<T>, bool, Option<Box<dyn Fn(&T) -> bool>>, Option<NotifyingCallback>) {
690        let mut metadata = StyledPropertyMetadata::new(Some(self.default_value))
691            .with_default_binding_mode(self.default_binding_mode)
692            .with_enable_data_validation(self.enable_data_validation);
693        if let Some(coerce) = self.coerce {
694            metadata = metadata.with_coerce(move |o, v| coerce(o, v));
695        }
696        (metadata, self.inherits, self.validate, self.notifying)
697    }
698}
699
700/// Records the accessor `$name` of `$owner` returning `$property`
701/// (`metadata::record_property_accessor`) with the `compiler-metadata`
702/// feature of this crate; expands to nothing without it.
703#[cfg(feature = "compiler-metadata")]
704#[doc(hidden)]
705#[macro_export]
706macro_rules! __record_property_accessor {
707    ($owner:ident, $vis:vis, $name:ident, $property:expr) => {
708        $crate::metadata::record_property_accessor(
709            <$owner as $crate::StaticType>::TYPE,
710            ::std::any::TypeId::of::<Self>(),
711            ::std::stringify!($name),
712            ::std::stringify!($vis),
713            $crate::Registrable::as_registered_property($property),
714        )
715    };
716}
717
718/// Records the accessor `$name` of `$owner` returning `$property` with the
719/// `compiler-metadata` feature of this crate; expands to nothing without it.
720#[cfg(not(feature = "compiler-metadata"))]
721#[doc(hidden)]
722#[macro_export]
723macro_rules! __record_property_accessor {
724    ($owner:ident, $vis:vis, $name:ident, $property:expr) => {};
725}
726
727/// Declares the accessor of a single property definition.
728///
729/// ```ignore
730/// impl Border {
731///     ferro_property!(pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
732///         FerroProperty::register::<Border, _>("Background", None)
733///     });
734/// }
735/// ```
736///
737/// The property is registered the first time the accessor is called on a
738/// thread. Property definitions, like the objects that use them, belong to
739/// the thread that created them.
740///
741/// On its own the macro registers nothing until the accessor is called, so a
742/// lookup by name can miss the property. Declare the properties of a type in
743/// a [`ferro_properties!`] block instead, which also registers them when the
744/// type is initialised.
745///
746/// Where an accessor cannot be written inside the block (it is generated by
747/// a local macro, or declared between other items), name the type whose
748/// initialisation it belongs to and list it in the block of that type:
749///
750/// ```ignore
751/// ferro_property!(for XYFocus; pub fn left_property() -> AttachedProperty<..> { .. });
752///
753/// ferro_properties! { impl XYFocus, also [XYFocus::left_property] {} }
754/// ```
755#[macro_export]
756macro_rules! ferro_property {
757    // An accessor that belongs to the static initialisation of `$owner`: the
758    // first call on a thread first initialises the owner (as reading a
759    // static field of a class runs its static initialisation), which
760    // registers the properties of the owner in declaration order, this one
761    // included.
762    (for $owner:ident; $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
763        $(#[$meta])*
764        #[inline]
765        $vis fn $name() -> &'static $ty {
766            $crate::ferro_thread_local! {
767                static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
768                    const { ::std::cell::Cell::new(::std::option::Option::None) };
769            }
770            match CELL.get() {
771                ::std::option::Option::Some(property) => property,
772                ::std::option::Option::None => {
773                    <$owner as $crate::StaticType>::TYPE.ensure_class_init();
774                    match CELL.get() {
775                        ::std::option::Option::Some(property) => property,
776                        // Reached while the owner is being initialised (this
777                        // is the registration in declaration order), or for
778                        // an owner whose runtime type has no initialisation.
779                        ::std::option::Option::None => {
780                            let property: &'static $ty = $body;
781                            CELL.set(::std::option::Option::Some(property));
782                            $crate::__record_property_accessor!($owner, $vis, $name, property);
783                            property
784                        }
785                    }
786                }
787            }
788        }
789    };
790
791    ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
792        $(#[$meta])*
793        #[inline]
794        $vis fn $name() -> &'static $ty {
795            $crate::ferro_thread_local! {
796                static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
797                    const { ::std::cell::Cell::new(::std::option::Option::None) };
798            }
799            match CELL.get() {
800                ::std::option::Option::Some(property) => property,
801                ::std::option::Option::None => {
802                    let property: &'static $ty = $body;
803                    CELL.set(::std::option::Option::Some(property));
804                    property
805                }
806            }
807        }
808    };
809}
810
811/// Declares the property definitions of a type: the accessors, as
812/// [`ferro_property!`] declares them one by one, and their registration as
813/// part of the static initialisation of the type.
814///
815/// ```ignore
816/// ferro_properties! {
817///     impl Border {
818///         /// Defines the `Background` property.
819///         pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
820///             FerroProperty::register::<Border, _>("Background", None)
821///         }
822///
823///         /// Defines the `Child` property (declared by another class).
824///         pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
825///             Decorator::child_property().add_owner::<Border>()
826///         }
827///     }
828/// }
829/// ```
830///
831/// All the properties are registered, in declaration order (the order of the
832/// static field initialisers of the managed class), when the type is
833/// initialised on a thread: when the first instance of the class or of a
834/// class deriving from it is created, when one of the accessors is first
835/// called, or when the property registry is asked about the type, whichever
836/// comes first. A lookup by name therefore finds every property, whether or
837/// not its accessor was called and whether or not an instance exists. After
838/// that an accessor is a thread-local read, exactly as with
839/// [`ferro_property!`].
840///
841/// The owner is a class ([`ferro_class!`](crate::ferro_class)) or a type
842/// declared with [`ferro_static_type!`](crate::ferro_static_type).
843///
844/// Existing `ferro_property!(..);` invocations can be wrapped in the block
845/// unchanged:
846///
847/// ```ignore
848/// ferro_properties! { impl Border {
849///     ferro_property!(pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
850///         FerroProperty::register::<Border, _>("Background", None)
851///     });
852/// } }
853/// ```
854///
855/// A type has one such block. Properties of the type that are declared
856/// elsewhere are named by it: `impl Owner, also [path, ..] { .. }` registers
857/// the properties of the block first and then calls the listed functions in
858/// order. A listed function is either
859///
860/// * the registration function of another block of accessors, declared with
861///   `ferro_properties! { impl Owner, fn name { .. } }` (an upstream partial
862///   class in a second file): `also [InputElement::register_gesture_properties]`;
863/// * or a single accessor declared with `ferro_property!(for Owner; ..)`
864///   (accessors generated by a local macro or interleaved with other items):
865///   `also [XYFocus::left_property, XYFocus::right_property]`.
866#[macro_export]
867macro_rules! ferro_properties {
868    (@accessors $owner:ident { $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )* }) => {
869        $(
870            $crate::ferro_property!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body);
871        )*
872    };
873
874    // The main block, wrapping `ferro_property!(..);` invocations. The
875    // accessors are declared through the macro name written at the call
876    // site, so the block works with the import the file already has.
877    (
878        impl $owner:ident $(, also [$($more:path),* $(,)?])? {
879            $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
880        }
881    ) => {
882        impl $owner {
883            $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
884
885            /// Registers the properties of the type in declaration order.
886            /// Called by the static initialisation of the type.
887            #[doc(hidden)]
888            pub fn __register_properties() {
889                $( let _ = <$owner>::$name(); )+
890                $($( $more(); )*)?
891            }
892        }
893    };
894
895    // A part of the properties of a type, wrapping `ferro_property!(..);`.
896    (
897        impl $owner:ident, fn $part:ident {
898            $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
899        }
900    ) => {
901        impl $owner {
902            $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
903
904            /// Registers a part of the properties of the type; named by its
905            /// main `ferro_properties!` block.
906            #[doc(hidden)]
907            pub fn $part() {
908                $( let _ = <$owner>::$name(); )+
909            }
910        }
911    };
912
913    // A part of the properties of a type.
914    (
915        impl $owner:ident, fn $part:ident {
916            $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
917        }
918    ) => {
919        impl $owner {
920            $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
921
922            /// Registers a part of the properties of the type; named by its
923            /// main `ferro_properties!` block.
924            #[doc(hidden)]
925            pub fn $part() {
926                $( let _ = <$owner>::$name(); )*
927            }
928        }
929    };
930
931    // The main block.
932    (
933        impl $owner:ident $(, also [$($more:path),* $(,)?])? {
934            $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
935        }
936    ) => {
937        impl $owner {
938            $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
939
940            /// Registers the properties of the type in declaration order.
941            /// Called by the static initialisation of the type.
942            #[doc(hidden)]
943            pub fn __register_properties() {
944                $( let _ = <$owner>::$name(); )*
945                $($( $more(); )*)?
946            }
947        }
948    };
949}