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    /// Removes the metadata registered for the property on the specified
481    /// type.
482    pub fn unregister(&self, type_: &'static TypeInfo) {
483        self.routes().route_unregister(type_)
484    }
485
486    /// Checks whether `value` is valid for the property: of the property's
487    /// value type and accepted by its validation callback.
488    pub fn is_valid_value(&self, value: &dyn Any) -> bool {
489        self.routes().route_is_valid_value(value)
490    }
491
492    /// The property as a styled (or attached) property with value type `T`:
493    /// the typed view needed to read, set and bind it without boxing.
494    /// `None` for direct properties and when `T` is not the property's value
495    /// type.
496    pub fn as_styled<T: crate::PropertyValue>(&'static self) -> Option<&'static StyledProperty<T>> {
497        self.routes.get().and_then(|routes| routes.as_any().downcast_ref::<StyledProperty<T>>())
498    }
499
500    /// The marker value that represents an unset property value.
501    pub fn unset_value() -> BoxedValue {
502        crate::ferro_thread_local! {
503            static UNSET: BoxedValue = Rc::new(UnsetValueType);
504        }
505        UNSET.with(Rc::clone)
506    }
507
508    /// Registers a styled property with a default value.
509    pub fn register<TOwner: ObjectType, TValue: PropertyValue>(
510        name: &str,
511        default_value: TValue,
512    ) -> &'static StyledProperty<TValue> {
513        Self::register_with::<TOwner, TValue>(name, StyledPropertyOptions::new(default_value))
514    }
515
516    /// Registers a styled property.
517    pub fn register_with<TOwner: ObjectType, TValue: PropertyValue>(
518        name: &str,
519        options: StyledPropertyOptions<TValue>,
520    ) -> &'static StyledProperty<TValue> {
521        let assign_binding = options.assign_binding;
522        let (metadata, inherits, validate, notifying) = options.into_parts();
523        let result = StyledProperty::create(name, TOwner::TYPE, TOwner::TYPE, metadata, inherits, validate, notifying, false);
524        result.set_assign_binding(assign_binding);
525        FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
526        result
527    }
528
529    /// Registers an attached property with a default value.
530    ///
531    /// `TOwner` is the class that registers the property and `THost` the class
532    /// of objects the property can be set on.
533    pub fn register_attached<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
534        name: &str,
535        default_value: TValue,
536    ) -> &'static AttachedProperty<TValue> {
537        Self::register_attached_with::<TOwner, THost, TValue>(name, StyledPropertyOptions::new(default_value))
538    }
539
540    /// Registers an attached property.
541    pub fn register_attached_with<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
542        name: &str,
543        options: StyledPropertyOptions<TValue>,
544    ) -> &'static AttachedProperty<TValue> {
545        let assign_binding = options.assign_binding;
546        let (metadata, inherits, validate, notifying) = options.into_parts();
547        let result = AttachedProperty::create(name, TOwner::TYPE, THost::TYPE, metadata, inherits, validate, notifying);
548        result.set_assign_binding(assign_binding);
549        result.host_type.set(Some(THost::TYPE));
550        let registry = FerroPropertyRegistry::instance();
551        registry.register(TOwner::TYPE, result);
552        registry.register_attached(THost::TYPE, result);
553        result
554    }
555
556    /// Registers a direct property: a property whose value is stored in a
557    /// field of the owner and accessed through a getter and setter.
558    pub fn register_direct<TOwner: ObjectType, TValue: PropertyValue>(
559        name: &str,
560        getter: fn(&TOwner) -> TValue,
561        setter: Option<fn(&TOwner, TValue)>,
562        unset_value: TValue,
563    ) -> &'static DirectProperty<TOwner, TValue> {
564        Self::register_direct_with(name, getter, setter, DirectPropertyMetadata::new(Some(unset_value)))
565    }
566
567    /// Registers a direct property with explicit metadata.
568    pub fn register_direct_with<TOwner: ObjectType, TValue: PropertyValue>(
569        name: &str,
570        getter: fn(&TOwner) -> TValue,
571        setter: Option<fn(&TOwner, TValue)>,
572        metadata: DirectPropertyMetadata<TValue>,
573    ) -> &'static DirectProperty<TOwner, TValue> {
574        let result = DirectProperty::create(name, getter, setter, metadata);
575        FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
576        result
577    }
578}
579
580impl PartialEq for FerroProperty {
581    #[inline]
582    fn eq(&self, other: &Self) -> bool {
583        self.id == other.id
584    }
585}
586
587impl Eq for FerroProperty {}
588
589impl Hash for FerroProperty {
590    fn hash<H: Hasher>(&self, state: &mut H) {
591        self.id.hash(state)
592    }
593}
594
595impl fmt::Debug for FerroProperty {
596    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597        f.write_str(&self.name)
598    }
599}
600
601impl fmt::Display for FerroProperty {
602    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603        f.write_str(&self.name)
604    }
605}
606
607/// Options for registering a styled or attached property.
608pub struct StyledPropertyOptions<T> {
609    default_value: T,
610    inherits: bool,
611    default_binding_mode: BindingMode,
612    validate: Option<Box<dyn Fn(&T) -> bool>>,
613    coerce: Option<Rc<dyn Fn(&FerroObject, T) -> T>>,
614    enable_data_validation: bool,
615    assign_binding: bool,
616    notifying: Option<NotifyingCallback>,
617}
618
619impl<T: PropertyValue> StyledPropertyOptions<T> {
620    pub fn new(default_value: T) -> Self {
621        Self {
622            default_value,
623            inherits: false,
624            default_binding_mode: BindingMode::OneWay,
625            validate: None,
626            coerce: None,
627            enable_data_validation: false,
628            assign_binding: false,
629            notifying: None,
630        }
631    }
632
633    /// Whether the property inherits its value from the parent object.
634    pub fn inherits(mut self, inherits: bool) -> Self {
635        self.inherits = inherits;
636        self
637    }
638
639    /// The default binding mode for the property.
640    pub fn default_binding_mode(mut self, mode: BindingMode) -> Self {
641        self.default_binding_mode = mode;
642        self
643    }
644
645    /// A value validation callback. Setting a value for which it returns
646    /// false is rejected.
647    pub fn validate(mut self, validate: impl Fn(&T) -> bool + 'static) -> Self {
648        self.validate = Some(Box::new(validate));
649        self
650    }
651
652    /// A value coercion callback.
653    pub fn coerce(mut self, coerce: impl Fn(&FerroObject, T) -> T + 'static) -> Self {
654        self.coerce = Some(Rc::new(coerce));
655        self
656    }
657
658    /// Whether the property is interested in data validation.
659    pub fn enable_data_validation(mut self, enable: bool) -> Self {
660        self.enable_data_validation = enable;
661        self
662    }
663
664    /// Whether a binding given for the property in markup is assigned to the
665    /// property instead of bound to it; see [`FerroProperty::assign_binding`].
666    pub fn assign_binding(mut self, assign_binding: bool) -> Self {
667        self.assign_binding = assign_binding;
668        self
669    }
670
671    /// A callback invoked before and after change notifications are raised.
672    pub fn notifying(mut self, notifying: impl Fn(&FerroObject, bool) + 'static) -> Self {
673        self.notifying = Some(Box::new(notifying));
674        self
675    }
676
677    #[allow(clippy::type_complexity)]
678    fn into_parts(self) -> (StyledPropertyMetadata<T>, bool, Option<Box<dyn Fn(&T) -> bool>>, Option<NotifyingCallback>) {
679        let mut metadata = StyledPropertyMetadata::new(Some(self.default_value))
680            .with_default_binding_mode(self.default_binding_mode)
681            .with_enable_data_validation(self.enable_data_validation);
682        if let Some(coerce) = self.coerce {
683            metadata = metadata.with_coerce(move |o, v| coerce(o, v));
684        }
685        (metadata, self.inherits, self.validate, self.notifying)
686    }
687}
688
689/// Records the accessor `$name` of `$owner` returning `$property`
690/// (`metadata::record_property_accessor`) with the `compiler-metadata`
691/// feature of this crate; expands to nothing without it.
692#[cfg(feature = "compiler-metadata")]
693#[doc(hidden)]
694#[macro_export]
695macro_rules! __record_property_accessor {
696    ($owner:ident, $vis:vis, $name:ident, $property:expr) => {
697        $crate::metadata::record_property_accessor(
698            <$owner as $crate::StaticType>::TYPE,
699            ::std::any::TypeId::of::<Self>(),
700            ::std::stringify!($name),
701            ::std::stringify!($vis),
702            $crate::Registrable::as_registered_property($property),
703        )
704    };
705}
706
707/// Records the accessor `$name` of `$owner` returning `$property` with the
708/// `compiler-metadata` feature of this crate; expands to nothing without it.
709#[cfg(not(feature = "compiler-metadata"))]
710#[doc(hidden)]
711#[macro_export]
712macro_rules! __record_property_accessor {
713    ($owner:ident, $vis:vis, $name:ident, $property:expr) => {};
714}
715
716/// Declares the accessor of a single property definition.
717///
718/// ```ignore
719/// impl Border {
720///     ferro_property!(pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
721///         FerroProperty::register::<Border, _>("Background", None)
722///     });
723/// }
724/// ```
725///
726/// The property is registered the first time the accessor is called on a
727/// thread. Property definitions, like the objects that use them, belong to
728/// the thread that created them.
729///
730/// On its own the macro registers nothing until the accessor is called, so a
731/// lookup by name can miss the property. Declare the properties of a type in
732/// a [`ferro_properties!`] block instead, which also registers them when the
733/// type is initialised.
734///
735/// Where an accessor cannot be written inside the block (it is generated by
736/// a local macro, or declared between other items), name the type whose
737/// initialisation it belongs to and list it in the block of that type:
738///
739/// ```ignore
740/// ferro_property!(for XYFocus; pub fn left_property() -> AttachedProperty<..> { .. });
741///
742/// ferro_properties! { impl XYFocus, also [XYFocus::left_property] {} }
743/// ```
744#[macro_export]
745macro_rules! ferro_property {
746    // An accessor that belongs to the static initialisation of `$owner`: the
747    // first call on a thread first initialises the owner (as reading a
748    // static field of a class runs its static initialisation), which
749    // registers the properties of the owner in declaration order, this one
750    // included.
751    (for $owner:ident; $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
752        $(#[$meta])*
753        #[inline]
754        $vis fn $name() -> &'static $ty {
755            $crate::ferro_thread_local! {
756                static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
757                    const { ::std::cell::Cell::new(::std::option::Option::None) };
758            }
759            match CELL.get() {
760                ::std::option::Option::Some(property) => property,
761                ::std::option::Option::None => {
762                    <$owner as $crate::StaticType>::TYPE.ensure_class_init();
763                    match CELL.get() {
764                        ::std::option::Option::Some(property) => property,
765                        // Reached while the owner is being initialised (this
766                        // is the registration in declaration order), or for
767                        // an owner whose runtime type has no initialisation.
768                        ::std::option::Option::None => {
769                            let property: &'static $ty = $body;
770                            CELL.set(::std::option::Option::Some(property));
771                            $crate::__record_property_accessor!($owner, $vis, $name, property);
772                            property
773                        }
774                    }
775                }
776            }
777        }
778    };
779
780    ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
781        $(#[$meta])*
782        #[inline]
783        $vis fn $name() -> &'static $ty {
784            $crate::ferro_thread_local! {
785                static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
786                    const { ::std::cell::Cell::new(::std::option::Option::None) };
787            }
788            match CELL.get() {
789                ::std::option::Option::Some(property) => property,
790                ::std::option::Option::None => {
791                    let property: &'static $ty = $body;
792                    CELL.set(::std::option::Option::Some(property));
793                    property
794                }
795            }
796        }
797    };
798}
799
800/// Declares the property definitions of a type: the accessors, as
801/// [`ferro_property!`] declares them one by one, and their registration as
802/// part of the static initialisation of the type.
803///
804/// ```ignore
805/// ferro_properties! {
806///     impl Border {
807///         /// Defines the `Background` property.
808///         pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
809///             FerroProperty::register::<Border, _>("Background", None)
810///         }
811///
812///         /// Defines the `Child` property (declared by another class).
813///         pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
814///             Decorator::child_property().add_owner::<Border>()
815///         }
816///     }
817/// }
818/// ```
819///
820/// All the properties are registered, in declaration order (the order of the
821/// static field initialisers of the managed class), when the type is
822/// initialised on a thread: when the first instance of the class or of a
823/// class deriving from it is created, when one of the accessors is first
824/// called, or when the property registry is asked about the type, whichever
825/// comes first. A lookup by name therefore finds every property, whether or
826/// not its accessor was called and whether or not an instance exists. After
827/// that an accessor is a thread-local read, exactly as with
828/// [`ferro_property!`].
829///
830/// The owner is a class ([`ferro_class!`](crate::ferro_class)) or a type
831/// declared with [`ferro_static_type!`](crate::ferro_static_type).
832///
833/// Existing `ferro_property!(..);` invocations can be wrapped in the block
834/// unchanged:
835///
836/// ```ignore
837/// ferro_properties! { impl Border {
838///     ferro_property!(pub fn background_property() -> StyledProperty<Option<Ref<Brush>>> {
839///         FerroProperty::register::<Border, _>("Background", None)
840///     });
841/// } }
842/// ```
843///
844/// A type has one such block. Properties of the type that are declared
845/// elsewhere are named by it: `impl Owner, also [path, ..] { .. }` registers
846/// the properties of the block first and then calls the listed functions in
847/// order. A listed function is either
848///
849/// * the registration function of another block of accessors, declared with
850///   `ferro_properties! { impl Owner, fn name { .. } }` (an upstream partial
851///   class in a second file): `also [InputElement::register_gesture_properties]`;
852/// * or a single accessor declared with `ferro_property!(for Owner; ..)`
853///   (accessors generated by a local macro or interleaved with other items):
854///   `also [XYFocus::left_property, XYFocus::right_property]`.
855#[macro_export]
856macro_rules! ferro_properties {
857    (@accessors $owner:ident { $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )* }) => {
858        $(
859            $crate::ferro_property!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body);
860        )*
861    };
862
863    // The main block, wrapping `ferro_property!(..);` invocations. The
864    // accessors are declared through the macro name written at the call
865    // site, so the block works with the import the file already has.
866    (
867        impl $owner:ident $(, also [$($more:path),* $(,)?])? {
868            $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
869        }
870    ) => {
871        impl $owner {
872            $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
873
874            /// Registers the properties of the type in declaration order.
875            /// Called by the static initialisation of the type.
876            #[doc(hidden)]
877            pub fn __register_properties() {
878                $( let _ = <$owner>::$name(); )+
879                $($( $more(); )*)?
880            }
881        }
882    };
883
884    // A part of the properties of a type, wrapping `ferro_property!(..);`.
885    (
886        impl $owner:ident, fn $part:ident {
887            $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
888        }
889    ) => {
890        impl $owner {
891            $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
892
893            /// Registers a part of the properties of the type; named by its
894            /// main `ferro_properties!` block.
895            #[doc(hidden)]
896            pub fn $part() {
897                $( let _ = <$owner>::$name(); )+
898            }
899        }
900    };
901
902    // A part of the properties of a type.
903    (
904        impl $owner:ident, fn $part:ident {
905            $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
906        }
907    ) => {
908        impl $owner {
909            $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
910
911            /// Registers a part of the properties of the type; named by its
912            /// main `ferro_properties!` block.
913            #[doc(hidden)]
914            pub fn $part() {
915                $( let _ = <$owner>::$name(); )*
916            }
917        }
918    };
919
920    // The main block.
921    (
922        impl $owner:ident $(, also [$($more:path),* $(,)?])? {
923            $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
924        }
925    ) => {
926        impl $owner {
927            $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
928
929            /// Registers the properties of the type in declaration order.
930            /// Called by the static initialisation of the type.
931            #[doc(hidden)]
932            pub fn __register_properties() {
933                $( let _ = <$owner>::$name(); )*
934                $($( $more(); )*)?
935            }
936        }
937    };
938}