Skip to main content

ferroui_base/
ferro_object.rs

1use crate::data::{BindingBase, BindingError, BindingExpressionBase, BindingPriority, BindingValue, BindingValueType};
2use crate::property_store::{BindingSource, ValueStore};
3use crate::reactive::{Disposable, IDisposable, IObservable};
4use crate::threading::Dispatcher;
5use crate::type_system::{__forwards_to_parent, cast_this, parent_vtable};
6use crate::utilities::HandlerList;
7use crate::{
8    BoxedValue, DirectPropertyBase, FerroProperty, FerroPropertyChangedEventArgs, FerroPropertyRegistry,
9    Nullable, ObjectType, PropertyValue, Ref, StaticType, StyledProperty, Subclassable, TypeInfo, Upcast, WeakRef,
10};
11use std::any::Any;
12use std::cell::{Cell, OnceCell, RefCell};
13use std::fmt;
14use std::rc::{Rc, Weak};
15use std::sync::Arc;
16
17type PropertyChangedHandler = dyn Fn(&FerroPropertyChangedEventArgs<'_>);
18
19/// The virtual table of [`FerroObject`].
20#[derive(Clone, Copy)]
21#[repr(C)]
22pub struct FerroObjectVTable {
23    pub type_info: &'static TypeInfo,
24    pub constructed: fn(&FerroObject),
25    pub on_property_changed_core: fn(&FerroObject, &FerroPropertyChangedEventArgs<'_>),
26    pub on_property_changed: fn(&FerroObject, &FerroPropertyChangedEventArgs<'_>),
27    pub update_data_validation: fn(&FerroObject, &'static FerroProperty, BindingValueType, Option<&BindingError>),
28}
29
30/// An object with [`FerroProperty`] support: the root of the class hierarchy.
31#[repr(C)]
32pub struct FerroObject {
33    this: OnceCell<Weak<dyn Any>>,
34    vtable: Cell<Option<&'static FerroObjectVTable>>,
35    values: ValueStore,
36    inheritance_parent: RefCell<Option<WeakRef<FerroObject>>>,
37    inheritance_children: RefCell<Vec<WeakRef<FerroObject>>>,
38    property_changed: HandlerList<PropertyChangedHandler>,
39    /// The dispatcher of the thread that owns this object; resolved the first
40    /// time it is asked for, so that constructing an object costs nothing.
41    dispatcher: OnceCell<Arc<Dispatcher>>,
42}
43
44static FERRO_OBJECT_TYPE: TypeInfo = TypeInfo::new("FerroObject", None)
45    .with_module_path(module_path!())
46    .with_class_init(<FerroObject as ObjectType>::register_class)
47    .with_handles(|| [std::any::TypeId::of::<Ref<FerroObject>>(), std::any::TypeId::of::<Option<Ref<FerroObject>>>()])
48    .with_interfaces({
49        #[allow(unused_imports)]
50        use crate::ClassDefaults as _;
51        <FerroObject>::__INTERFACES
52    })
53    .with_markup({
54        #[allow(unused_imports)]
55        use crate::ClassDefaults as _;
56        <FerroObject>::__MARKUP
57    });
58
59impl StaticType for FerroObject {
60    const TYPE: &'static TypeInfo = &FERRO_OBJECT_TYPE;
61}
62
63impl Upcast<FerroObject> for FerroObject {
64    #[inline]
65    fn upcast(&self) -> &FerroObject {
66        self
67    }
68}
69
70// SAFETY: the root class has no base; its table is the root table.
71unsafe impl ObjectType for FerroObject {
72    type Parent = FerroObject;
73    type VTable = FerroObjectVTable;
74
75    fn vtable() -> &'static FerroObjectVTable {
76        static VTABLE: std::sync::OnceLock<FerroObjectVTable> = std::sync::OnceLock::new();
77        VTABLE.get_or_init(<FerroObject as Subclassable<FerroObject>>::build_vtable)
78    }
79
80    #[inline]
81    fn register_class() {
82        crate::ferro_thread_local! {
83            static REGISTERED: Cell<bool> = const { Cell::new(false) };
84        }
85        if !REGISTERED.replace(true) {
86            crate::type_system::__register_class::<FerroObject>();
87        }
88    }
89}
90
91/// The overridable members of [`FerroObject`].
92pub trait FerroObjectImpl: ObjectType {
93    /// The names of the members this implementation overrides, where it
94    /// states them: `None` for an implementation written as a plain `impl`.
95    /// Set by [`ferro_impl_classes!`](crate::ferro_impl_classes) and
96    /// [`ferro_overrides!`](crate::ferro_overrides), never by hand: the
97    /// table of the class is built from it.
98    #[doc(hidden)]
99    const __OVERRIDES: Option<&'static [&'static str]> = None;
100
101    /// Called once, right after the object has been allocated. This is where
102    /// constructor logic that needs a reference to the object runs. Overrides
103    /// must call `parent_constructed` first.
104    fn constructed(this: &Self) {
105        Self::parent_constructed(this)
106    }
107
108    /// Called for every property change on the object, including changes of
109    /// non-effective (base) values. Overriding this is rarely needed; override
110    /// [`on_property_changed`](Self::on_property_changed) instead.
111    fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
112        Self::parent_on_property_changed_core(this, change)
113    }
114
115    /// Called when the effective value of a property changes on the object.
116    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
117        Self::parent_on_property_changed(this, change)
118    }
119
120    /// Called to update the validation state for properties for which data
121    /// validation is enabled.
122    fn update_data_validation(
123        this: &Self,
124        property: &'static FerroProperty,
125        state: BindingValueType,
126        error: Option<&BindingError>,
127    ) {
128        Self::parent_update_data_validation(this, property, state, error)
129    }
130}
131
132/// Access to the base implementations of the [`FerroObjectImpl`] members.
133pub trait FerroObjectImplExt: FerroObjectImpl {
134    #[inline]
135    fn parent_constructed(this: &Self) {
136        (parent_vtable::<Self, FerroObjectVTable>().constructed)(this.upcast())
137    }
138
139    #[inline]
140    fn parent_on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
141        (parent_vtable::<Self, FerroObjectVTable>().on_property_changed_core)(this.upcast(), change)
142    }
143
144    #[inline]
145    fn parent_on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
146        (parent_vtable::<Self, FerroObjectVTable>().on_property_changed)(this.upcast(), change)
147    }
148
149    #[inline]
150    fn parent_update_data_validation(
151        this: &Self,
152        property: &'static FerroProperty,
153        state: BindingValueType,
154        error: Option<&BindingError>,
155    ) {
156        (parent_vtable::<Self, FerroObjectVTable>().update_data_validation)(this.upcast(), property, state, error)
157    }
158}
159
160impl<T: FerroObjectImpl> FerroObjectImplExt for T {}
161
162impl<T: FerroObjectImpl> Subclassable<T> for FerroObject {
163    fn build_vtable() -> FerroObjectVTable {
164        // SAFETY (all casts): the table is only attached to objects whose
165        // most-derived class is `T`.
166        let mut table = FerroObjectVTable {
167            type_info: T::TYPE,
168            constructed: |this| T::constructed(unsafe { cast_this::<FerroObject, T>(this) }),
169            on_property_changed_core: |this, change| {
170                T::on_property_changed_core(unsafe { cast_this::<FerroObject, T>(this) }, change)
171            },
172            on_property_changed: |this, change| {
173                T::on_property_changed(unsafe { cast_this::<FerroObject, T>(this) }, change)
174            },
175            update_data_validation: |this, property, state, error| {
176                T::update_data_validation(unsafe { cast_this::<FerroObject, T>(this) }, property, state, error)
177            },
178        };
179
180        // A member `T` states it does not override is the default, which
181        // calls the same slot of the parent table and does nothing else: the
182        // slot itself is taken, so that a call reaches the implementation
183        // without a forwarding function of every class in between. A change
184        // of a property goes through two of these members.
185        let forwards: [bool; 4] = const {
186            let overrides = <T as FerroObjectImpl>::__OVERRIDES;
187            [
188                __forwards_to_parent::<T, FerroObjectVTable>(overrides, "constructed"),
189                __forwards_to_parent::<T, FerroObjectVTable>(overrides, "on_property_changed_core"),
190                __forwards_to_parent::<T, FerroObjectVTable>(overrides, "on_property_changed"),
191                __forwards_to_parent::<T, FerroObjectVTable>(overrides, "update_data_validation"),
192            ]
193        };
194        if forwards[0] {
195            table.constructed = parent_vtable::<T, FerroObjectVTable>().constructed;
196        }
197        if forwards[1] {
198            table.on_property_changed_core = parent_vtable::<T, FerroObjectVTable>().on_property_changed_core;
199        }
200        if forwards[2] {
201            table.on_property_changed = parent_vtable::<T, FerroObjectVTable>().on_property_changed;
202        }
203        if forwards[3] {
204            table.update_data_validation = parent_vtable::<T, FerroObjectVTable>().update_data_validation;
205        }
206        table
207    }
208}
209
210impl FerroObjectImpl for FerroObject {
211    fn constructed(_this: &Self) {}
212
213    fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
214        if change.is_effective_value_change() {
215            this.on_property_changed(change);
216        }
217    }
218
219    fn on_property_changed(_this: &Self, _change: &FerroPropertyChangedEventArgs<'_>) {}
220
221    fn update_data_validation(
222        _this: &Self,
223        _property: &'static FerroProperty,
224        _state: BindingValueType,
225        _error: Option<&BindingError>,
226    ) {
227    }
228}
229
230impl FerroObject {
231    /// The runtime type of this class.
232    pub const TYPE: &'static TypeInfo = &FERRO_OBJECT_TYPE;
233
234    /// Creates the class data. Use from the `construct` function of a derived
235    /// class; the object becomes usable once passed to
236    /// [`instantiate`](crate::instantiate).
237    pub fn construct() -> FerroObject {
238        FerroObject {
239            this: OnceCell::new(),
240            vtable: Cell::new(None),
241            values: ValueStore::new(),
242            inheritance_parent: RefCell::new(None),
243            inheritance_children: RefCell::new(Vec::new()),
244            property_changed: HandlerList::new(),
245            dispatcher: OnceCell::new(),
246        }
247    }
248
249    /// Returns the [`Dispatcher`] that this object is associated with: the
250    /// dispatcher of the thread that owns the object.
251    #[inline]
252    pub fn dispatcher(&self) -> &Arc<Dispatcher> {
253        // Objects cannot leave their thread, so the dispatcher of the calling
254        // thread is the dispatcher of the object.
255        self.dispatcher.get_or_init(Dispatcher::current_dispatcher)
256    }
257
258    /// Returns whether the calling thread is the thread that owns this
259    /// object.
260    #[inline]
261    pub fn check_access(&self) -> bool {
262        self.dispatcher().check_access()
263    }
264
265    /// Checks that the calling thread is the thread that owns this object.
266    ///
267    /// # Panics
268    /// Panics when it is not.
269    #[inline]
270    #[track_caller]
271    pub fn verify_access(&self) {
272        self.dispatcher().verify_access()
273    }
274
275    /// Creates a plain object.
276    pub fn new() -> Ref<FerroObject> {
277        crate::instantiate(Self::construct())
278    }
279
280    /// Attaches the self reference and the virtual table.
281    ///
282    /// # Safety
283    ///
284    /// `vtable` must point to the `'static` virtual table of the most-derived
285    /// class of the object that `self` is the root part of, and `this` must
286    /// be a weak reference to that object.
287    #[doc(hidden)]
288    pub unsafe fn attach(&self, this: Weak<dyn Any>, vtable: *const ()) {
289        let _ = self.this.set(this);
290        self.vtable.set(Some(&*(vtable as *const FerroObjectVTable)));
291    }
292
293    #[inline]
294    fn vt(&self) -> &'static FerroObjectVTable {
295        self.vtable.get().expect("object is not instantiated")
296    }
297
298    /// Returns the virtual table of the object viewed as class `T`'s table.
299    #[doc(hidden)]
300    #[inline]
301    pub fn vtable_of<T: ObjectType>(this: &T) -> &'static T::VTable {
302        let object: &FerroObject = this.upcast();
303        let table: *const FerroObjectVTable = object.vt();
304        // SAFETY: `this` is the `T` part of a live object, so the object's
305        // most-derived class derives from `T` and its table starts with
306        // `T::VTable` (`ObjectType` invariant).
307        unsafe { &*(table as *const T::VTable) }
308    }
309
310    /// Returns a strong reference to the object that `this` is part of.
311    #[doc(hidden)]
312    #[inline]
313    pub fn ref_of<T: ObjectType>(this: &T) -> Ref<T> {
314        let object: &FerroObject = this.upcast();
315        let ptr = object
316            .this
317            .get()
318            .and_then(Weak::upgrade)
319            .expect("object is not instantiated or is being dropped");
320        // `this` is the `T` part of the object behind `ptr`.
321        Ref::from_rc(ptr)
322    }
323
324    /// A strong reference to this object.
325    #[inline]
326    pub fn to_ref(&self) -> Ref<FerroObject> {
327        Self::ref_of(self)
328    }
329
330    /// A weak reference to this object.
331    #[inline]
332    pub fn to_weak(&self) -> WeakRef<FerroObject> {
333        WeakRef::from_weak(self.this.get().expect("object is not instantiated").clone())
334    }
335
336    /// The runtime type of the object.
337    #[inline]
338    pub fn get_type(&self) -> &'static TypeInfo {
339        match self.vtable.get() {
340            Some(table) => table.type_info,
341            None => Self::TYPE,
342        }
343    }
344
345    /// Whether the object is of class `T` or a class derived from it.
346    #[inline]
347    pub fn is<T: ObjectType>(&self) -> bool {
348        T::TYPE.is_assignable_from(self.get_type())
349    }
350
351    /// Returns the object viewed as class `T` if it is one.
352    #[inline]
353    pub fn downcast_ref<T: ObjectType>(&self) -> Option<&T> {
354        if self.is::<T>() {
355            // SAFETY: the runtime type derives from `T`, so `self` is the root
356            // part of an object that has `T` at offset zero.
357            Some(unsafe { cast_this::<FerroObject, T>(self) })
358        } else {
359            None
360        }
361    }
362
363    #[inline]
364    pub(crate) fn values(&self) -> &ValueStore {
365        &self.values
366    }
367
368    // --- virtual dispatch -------------------------------------------------
369
370    #[inline]
371    pub(crate) fn constructed(&self) {
372        crate::perf_count_virtual!("FerroObject::constructed");
373        (self.vt().constructed)(self)
374    }
375
376    #[inline]
377    pub(crate) fn on_property_changed_core(&self, change: &FerroPropertyChangedEventArgs<'_>) {
378        crate::perf_count_virtual!("FerroObject::on_property_changed_core");
379        (self.vt().on_property_changed_core)(self, change)
380    }
381
382    /// Invokes the `on_property_changed` virtual member.
383    #[inline]
384    pub fn on_property_changed(&self, change: &FerroPropertyChangedEventArgs<'_>) {
385        crate::perf_count_virtual!("FerroObject::on_property_changed");
386        (self.vt().on_property_changed)(self, change)
387    }
388
389    #[inline]
390    pub(crate) fn on_update_data_validation(
391        &self,
392        property: &'static FerroProperty,
393        state: BindingValueType,
394        error: Option<BindingError>,
395    ) {
396        crate::perf_count_virtual!("FerroObject::update_data_validation");
397        (self.vt().update_data_validation)(self, property, state, error.as_ref())
398    }
399
400    // --- inheritance ------------------------------------------------------
401
402    /// The parent object that inherited property values are inherited from.
403    pub fn inheritance_parent(&self) -> Option<Ref<FerroObject>> {
404        self.inheritance_parent.borrow().as_ref().and_then(WeakRef::upgrade)
405    }
406
407    /// Whether `parent` is the inheritance parent of the object (`InheritanceParent == parent`),
408    /// without taking a handle to it.
409    pub(crate) fn is_inheritance_parent(&self, parent: &FerroObject) -> bool {
410        self.inheritance_parent.borrow().as_ref().is_some_and(|weak| weak.points_at(parent))
411    }
412
413    /// Sets the parent object that inherited property values are inherited
414    /// from.
415    pub fn set_inheritance_parent(&self, value: impl Into<Nullable<FerroObject>>) {
416        let value = value.into().0;
417        let value = value.as_ref();
418        let old = self.inheritance_parent();
419        let same = match (&old, value) {
420            (Some(a), Some(b)) => a.ptr_eq(b),
421            (None, None) => true,
422            _ => false,
423        };
424        if same {
425            return;
426        }
427        if let Some(old) = &old {
428            old.remove_inheritance_child(self);
429        }
430        *self.inheritance_parent.borrow_mut() = value.map(Ref::downgrade);
431        if let Some(new) = value {
432            let mut children = new.inheritance_children.borrow_mut();
433            if children.len() == children.capacity() {
434                // Before growing, forget children that were dropped without
435                // being detached (amortised constant time).
436                children.retain(|c| c.upgrade().is_some());
437            }
438            children.push(self.to_weak());
439        }
440        self.values.set_inheritance_parent(self, value);
441    }
442
443    fn remove_inheritance_child(&self, child: &FerroObject) {
444        let child = child.to_ref();
445        self.inheritance_children.borrow_mut().retain(|c| !c.points_to(&child));
446    }
447
448    /// Calls `f` for each inheritance child, by index over the count at the
449    /// start, as the original iterates `GetInheritanceChildren()`: no copy
450    /// of the children for every object a change propagates through. A child
451    /// that was dropped without being detached is skipped.
452    pub(crate) fn for_each_inheritance_child(&self, mut f: impl FnMut(&Ref<FerroObject>)) {
453        let count = self.inheritance_children.borrow().len();
454        for index in 0..count {
455            // Deviation (DEVIATIONS.md, Property system): upstream's
456            // `children[i]` throws past the end; here the list holds weak
457            // references and `inheritance_children` prunes dropped children,
458            // which can shorten it during the loop, so the loop stops instead.
459            let child = match self.inheritance_children.borrow().get(index) {
460                Some(child) => child.upgrade(),
461                None => break,
462            };
463            if let Some(child) = child {
464                f(&child);
465            }
466        }
467    }
468
469    pub(crate) fn inheritance_children(&self) -> Vec<Ref<FerroObject>> {
470        let mut children = self.inheritance_children.borrow_mut();
471        if children.is_empty() {
472            return Vec::new();
473        }
474        let result: Vec<Ref<FerroObject>> = children.iter().filter_map(WeakRef::upgrade).collect();
475        if result.len() != children.len() {
476            // Forget children that were dropped without being detached: a
477            // weak reference keeps the child's allocation alive.
478            children.retain(|c| c.upgrade().is_some());
479        }
480        result
481    }
482
483    // --- styled properties ------------------------------------------------
484
485    /// Starts tracking explicit local/current writes to this property and returns
486    /// its version. Equal-value writes count; style evaluation does not.
487    /// Tracking lives with this object and is available only in development builds.
488    #[cfg(all(debug_assertions, feature = "hot-reload"))]
489    pub fn local_write_version(&self, property: &FerroProperty) -> u64 {
490        self.values.local_write_version(property)
491    }
492
493    /// Tracks explicit local assignments and binding-slot changes. Unlike
494    /// `local_write_version`, SetCurrentValue (for example two-way user input)
495    /// does not transfer assignment ownership.
496    #[cfg(all(debug_assertions, feature = "hot-reload"))]
497    pub fn local_assignment_version(&self, property: &FerroProperty) -> u64 {
498        self.values.local_assignment_version(property)
499    }
500
501    /// Clears a property's local value.
502    pub fn clear_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) {
503        self.values.clear_value(self, property);
504    }
505
506    /// Gets a property value.
507    #[inline]
508    pub fn get_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> T {
509        self.values.get_value(self, property)
510    }
511
512    /// Gets a property's base value: the value excluding animated values.
513    /// Returns `None` if the value comes from inheritance or the default.
514    pub fn get_base_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> Option<T> {
515        self.values.get_base_value(property)
516    }
517
518    /// Checks whether a property is animating.
519    pub fn is_animating(&self, property: &FerroProperty) -> bool {
520        self.values.is_animating(property)
521    }
522
523    /// Checks whether a styled property has a value assigned to it or a
524    /// binding targeting it.
525    pub fn is_set(&self, property: &FerroProperty) -> bool {
526        self.values.is_set(property)
527    }
528
529    /// Whether a local-value binding targets this property, including direct
530    /// properties and observable bindings. Unlike `is_set`, this does not rely
531    /// on a styled property's effective-value entry. It does not inspect bindings
532    /// at style/template priorities.
533    pub fn has_local_value_binding(&self, property: &FerroProperty) -> bool {
534        self.values.has_local_value_binding(property)
535    }
536
537    /// Sets a property value with local value priority.
538    #[inline]
539    pub fn set_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
540        match value_marker(&value) {
541            Some(ValueMarker::Unset) => self.clear_value(property),
542            Some(ValueMarker::DoNothing) => {}
543            None => {
544                self.values.set_value(self, property, value, BindingPriority::LocalValue);
545            }
546        }
547    }
548
549    /// Sets a property value with the given priority. For priorities other
550    /// than local value, disposing the returned handle removes the value.
551    pub fn set_value_with_priority<T: PropertyValue>(
552        &self,
553        property: &'static StyledProperty<T>,
554        value: T,
555        priority: BindingPriority,
556    ) -> Option<Rc<dyn IDisposable>> {
557        Self::validate_priority(priority);
558        match value_marker(&value) {
559            Some(ValueMarker::Unset) => {
560                if priority == BindingPriority::LocalValue {
561                    self.clear_value(property);
562                }
563                None
564            }
565            Some(ValueMarker::DoNothing) => None,
566            None => self.values.set_value(self, property, value, priority),
567        }
568    }
569
570    /// Sets the value of a property without changing its value source.
571    ///
572    /// This is used by a component that programmatically sets the value of
573    /// one of its own properties without disabling an application's declared
574    /// use of the property: the effective value changes, but existing
575    /// bindings and styles continue to work. The new value has the
576    /// property's current priority.
577    pub fn set_current_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
578        match value_marker(&value) {
579            Some(ValueMarker::Unset) => self.clear_value(property),
580            Some(ValueMarker::DoNothing) => {}
581            None => self.values.set_current_value(self, property, value),
582        }
583    }
584
585    /// Binds a property to a binding. Returns the binding expression which
586    /// represents the binding instance on this object.
587    pub fn bind_binding(
588        &self,
589        property: &'static FerroProperty,
590        binding: &dyn BindingBase,
591    ) -> Rc<dyn BindingExpressionBase> {
592        self.bind_binding_with_anchor(property, binding, None)
593    }
594
595    /// Binds a property to a binding.
596    ///
597    /// Some bindings need context to locate their source: a data context,
598    /// named elements or resources. If this object is not an element, or is
599    /// not yet attached to the tree, `anchor` provides that context.
600    pub fn bind_binding_with_anchor(
601        &self,
602        property: &'static FerroProperty,
603        binding: &dyn BindingBase,
604        anchor: Option<&Ref<FerroObject>>,
605    ) -> Rc<dyn BindingExpressionBase> {
606        crate::perf_count!(BindingsInstanced);
607        let expression = binding.create_instance(self, Some(property), anchor);
608        self.values.add_binding_expression(self, property, expression)
609    }
610
611    /// Binds a property to a binding at the given priority, whatever the
612    /// default priority of the binding is. Disposing the returned expression
613    /// removes the binding.
614    ///
615    /// An addition (no upstream counterpart): the values of WinUI's visual
616    /// states are bound at [`BindingPriority::Animation`].
617    pub fn bind_binding_with_priority(
618        &self,
619        property: &'static FerroProperty,
620        binding: &dyn BindingBase,
621        priority: BindingPriority,
622        anchor: Option<&Ref<FerroObject>>,
623    ) -> Rc<dyn BindingExpressionBase> {
624        Self::validate_priority(priority);
625        crate::perf_count!(BindingsInstanced);
626        let expression = binding.create_instance(self, Some(property), anchor);
627        self.values.add_binding_expression_with_priority(self, property, expression, priority)
628    }
629
630    /// Gets a binding to a property of this object: the indexer taking an
631    /// [`IndexerDescriptor`](crate::data::IndexerDescriptor)
632    /// (`source[!Property]` is `source.indexer(&Property.bind())`).
633    pub fn indexer(&self, binding: &crate::data::IndexerDescriptor) -> crate::data::IndexerBinding {
634        let property = binding.property.expect("The IndexerDescriptor has no property.");
635        crate::data::IndexerBinding::new(self.to_ref(), property, binding.mode)
636    }
637
638    /// Binds the property of an
639    /// [`IndexerDescriptor`](crate::data::IndexerDescriptor) to a binding:
640    /// the setter of the indexer (`target[!Property] = binding` is
641    /// `target.bind_indexer(&Property.bind(), &binding)`).
642    pub fn bind_indexer(
643        &self,
644        binding: &crate::data::IndexerDescriptor,
645        value: &dyn BindingBase,
646    ) -> Rc<dyn BindingExpressionBase> {
647        let property = binding.property.expect("The IndexerDescriptor has no property.");
648        self.bind_binding(property, value)
649    }
650
651    /// The binding expression that is currently active on the property, if
652    /// any.
653    pub(crate) fn get_expression(&self, property: &'static FerroProperty) -> Option<Rc<dyn BindingExpressionBase>> {
654        self.values.get_expression(property)
655    }
656
657    /// Binds a property to an observable.
658    pub fn bind<T: PropertyValue>(
659        &self,
660        property: &'static StyledProperty<T>,
661        source: Rc<dyn IObservable<T>>,
662        priority: BindingPriority,
663    ) -> Rc<dyn IDisposable> {
664        Self::validate_priority(priority);
665        self.values.add_binding(self, property, BindingSource::Typed(source), priority)
666    }
667
668    /// Binds a property to an observable of binding values.
669    pub fn bind_value<T: PropertyValue>(
670        &self,
671        property: &'static StyledProperty<T>,
672        source: Rc<dyn IObservable<BindingValue<T>>>,
673        priority: BindingPriority,
674    ) -> Rc<dyn IDisposable> {
675        Self::validate_priority(priority);
676        self.values.add_binding(self, property, BindingSource::Value(source), priority)
677    }
678
679    /// Binds a property to an observable of untyped values.
680    pub fn bind_untyped<T: PropertyValue>(
681        &self,
682        property: &'static StyledProperty<T>,
683        source: Rc<dyn IObservable<BoxedValue>>,
684        priority: BindingPriority,
685    ) -> Rc<dyn IDisposable> {
686        Self::validate_priority(priority);
687        self.values.add_binding(self, property, BindingSource::Untyped(source), priority)
688    }
689
690    /// Coerces the specified property.
691    pub fn coerce_value(&self, property: &'static FerroProperty) {
692        self.values.coerce_value(self, property);
693    }
694
695    // --- direct properties ------------------------------------------------
696
697    /// Gets a direct property value.
698    pub fn get_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) -> T {
699        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
700        registered.invoke_getter(self)
701    }
702
703    /// Sets a direct property value.
704    pub fn set_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>, value: T) {
705        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
706        registered.invoke_setter(self, BindingValue::new(value));
707    }
708
709    /// Resets a direct property to its unset value.
710    pub fn clear_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) {
711        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
712        let unset = registered.get_unset_value_for(self);
713        registered.invoke_setter(self, BindingValue::new(unset));
714    }
715
716    pub(crate) fn set_direct_value_unchecked<T: PropertyValue>(
717        &self,
718        property: &'static DirectPropertyBase<T>,
719        value: BindingValue<T>,
720    ) {
721        let state = value.value_type();
722        let error = value.error().cloned();
723        match state {
724            BindingValueType::UNSET_VALUE | BindingValueType::BINDING_ERROR => {
725                let fallback =
726                    if value.has_value() { value } else { value.with_value(property.get_unset_value_for(self)) };
727                property.invoke_setter(self, fallback);
728            }
729            BindingValueType::VALUE
730            | BindingValueType::BINDING_ERROR_WITH_FALLBACK
731            | BindingValueType::DATA_VALIDATION_ERROR
732            | BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK => property.invoke_setter(self, value),
733            // Do nothing, and the flag combinations that no binding value has.
734            _ => {}
735        }
736        if property.get_metadata_for(self).enable_data_validation() == Some(true) {
737            self.on_update_data_validation(property, state, error);
738        }
739    }
740
741    /// Binds a direct property to an observable.
742    pub fn bind_direct<T: PropertyValue>(
743        &self,
744        property: &'static DirectPropertyBase<T>,
745        source: Rc<dyn IObservable<T>>,
746    ) -> Rc<dyn IDisposable> {
747        let property = self.writable_direct(property);
748        self.values.add_direct_binding(self, property, BindingSource::Typed(source))
749    }
750
751    /// Binds a direct property to an observable of binding values.
752    pub fn bind_direct_value<T: PropertyValue>(
753        &self,
754        property: &'static DirectPropertyBase<T>,
755        source: Rc<dyn IObservable<BindingValue<T>>>,
756    ) -> Rc<dyn IDisposable> {
757        let property = self.writable_direct(property);
758        self.values.add_direct_binding(self, property, BindingSource::Value(source))
759    }
760
761    /// Binds a direct property to an observable of untyped values.
762    pub fn bind_direct_untyped<T: PropertyValue>(
763        &self,
764        property: &'static DirectPropertyBase<T>,
765        source: Rc<dyn IObservable<BoxedValue>>,
766    ) -> Rc<dyn IDisposable> {
767        let property = self.writable_direct(property);
768        self.values.add_direct_binding(self, property, BindingSource::Untyped(source))
769    }
770
771    fn writable_direct<T: PropertyValue>(
772        &self,
773        property: &'static DirectPropertyBase<T>,
774    ) -> &'static DirectPropertyBase<T> {
775        let property = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
776        assert!(!property.is_read_only(), "The property {} is readonly.", property.name());
777        property
778    }
779
780    /// Sets the backing field of a direct property, raising a change
781    /// notification if the value changed. Returns true if it changed.
782    pub fn set_and_raise<T: PropertyValue>(
783        &self,
784        property: &'static DirectPropertyBase<T>,
785        field: &RefCell<T>,
786        value: T,
787    ) -> bool {
788        if *field.borrow() == value {
789            return false;
790        }
791        let old = field.replace(value.clone());
792        self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
793        true
794    }
795
796    /// [`set_and_raise`](Self::set_and_raise) for `Copy` values stored in a
797    /// [`Cell`].
798    pub fn set_and_raise_cell<T: PropertyValue + Copy>(
799        &self,
800        property: &'static DirectPropertyBase<T>,
801        field: &Cell<T>,
802        value: T,
803    ) -> bool {
804        let old = field.get();
805        if old == value {
806            return false;
807        }
808        field.set(value);
809        self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
810        true
811    }
812
813    // --- untyped access ---------------------------------------------------
814
815    /// Gets a property value as an untyped value.
816    pub fn get_value_untyped(&self, property: &'static FerroProperty) -> BoxedValue {
817        property.routes().route_get_value(self)
818    }
819
820    /// Sets a property from an untyped value, which must hold exactly the
821    /// property's value type or the unset marker.
822    pub fn set_value_untyped(
823        &self,
824        property: &'static FerroProperty,
825        value: &dyn Any,
826        priority: BindingPriority,
827    ) -> Option<Rc<dyn IDisposable>> {
828        property.routes().route_set_value(self, value, priority)
829    }
830
831    /// [`set_current_value`](Self::set_current_value) with an untyped value.
832    pub fn set_current_value_untyped(&self, property: &'static FerroProperty, value: &dyn Any) {
833        property.routes().route_set_current_value(self, value)
834    }
835
836    /// Clears a property's local value.
837    pub fn clear_value_untyped(&self, property: &'static FerroProperty) {
838        property.routes().route_clear_value(self)
839    }
840
841    /// Binds a property to an observable of untyped values.
842    pub fn bind_property_untyped(
843        &self,
844        property: &'static FerroProperty,
845        source: Rc<dyn IObservable<BoxedValue>>,
846        priority: BindingPriority,
847    ) -> Rc<dyn IDisposable> {
848        property.routes().route_bind(self, source, priority)
849    }
850
851    // --- change notification ----------------------------------------------
852
853    /// Subscribes to property changes on this object. Disposing the returned
854    /// handle unsubscribes.
855    pub fn property_changed(
856        &self,
857        handler: impl Fn(&FerroPropertyChangedEventArgs<'_>) + 'static,
858    ) -> Rc<dyn IDisposable> {
859        let token = self.property_changed.add(Rc::new(handler));
860        let weak = self.to_weak();
861        Disposable::create(move || {
862            if let Some(this) = weak.upgrade() {
863                this.property_changed.remove(token);
864            }
865        })
866    }
867
868    /// Subscribes `handler` to property changes on this object and returns
869    /// the token that [`remove_property_changed_handler`](Self::remove_property_changed_handler)
870    /// takes: [`property_changed`](Self::property_changed) for a subscriber
871    /// that keeps the object and the token itself, so that no handle is made.
872    pub fn add_property_changed_handler(&self, handler: Rc<dyn Fn(&FerroPropertyChangedEventArgs<'_>)>) -> u64 {
873        self.property_changed.add(handler)
874    }
875
876    /// Ends a subscription of [`add_property_changed_handler`](Self::add_property_changed_handler).
877    pub fn remove_property_changed_handler(&self, token: u64) -> bool {
878        self.property_changed.remove(token)
879    }
880
881    /// The number of subscribers to property changes on this object (a
882    /// diagnostics member, used by tests to verify that subscriptions are
883    /// released).
884    pub fn property_changed_subscriber_count(&self) -> usize {
885        self.property_changed.len()
886    }
887
888    /// Raises a property changed notification.
889    pub fn raise_property_changed<T: PropertyValue>(
890        &self,
891        property: &'static FerroProperty,
892        old_value: Option<&T>,
893        new_value: &T,
894        priority: BindingPriority,
895        is_effective_value: bool,
896    ) {
897        let e = FerroPropertyChangedEventArgs::new(
898            self,
899            property,
900            old_value.map(|v| v as &dyn Any),
901            new_value,
902            priority,
903            is_effective_value,
904        );
905        self.notify_property_changed(&e);
906    }
907
908    pub(crate) fn raise_inherited_property_changed<T: PropertyValue>(
909        &self,
910        property: &'static FerroProperty,
911        old_value: &T,
912        new_value: &T,
913        priority: BindingPriority,
914    ) {
915        let e = FerroPropertyChangedEventArgs::new(
916            self, property, Some(old_value), new_value, priority, true,
917        ).from_inheritance();
918        self.notify_property_changed(&e);
919    }
920
921    fn notify_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
922        crate::perf_count!(PropertyChangesRaised);
923        self.on_property_changed_core(e);
924
925        if e.is_effective_value_change() {
926            crate::perf_count!(PropertyChangesEffective);
927            e.property().notify_changed(e);
928            if !self.property_changed.is_empty() {
929                crate::perf_count!(PropertyChangesWithObjectListeners);
930                for (_, handler) in self.property_changed.snapshot().iter() {
931                    handler(e);
932                }
933            }
934        }
935    }
936
937    /// Raises a property changed notification for a direct property.
938    pub fn raise_direct_property_changed<T: PropertyValue>(
939        &self,
940        property: &'static DirectPropertyBase<T>,
941        old_value: &T,
942        new_value: &T,
943    ) {
944        self.raise_property_changed(property, Some(old_value), new_value, BindingPriority::LocalValue, true);
945    }
946
947    fn validate_priority(priority: BindingPriority) {
948        if priority < BindingPriority::Animation || priority >= BindingPriority::Inherited {
949            panic!("Invalid priority {priority:?}");
950        }
951    }
952}
953
954impl fmt::Debug for FerroObject {
955    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
956        f.write_str(self.get_type().name())
957    }
958}
959
960/// A marker held by a value of an untyped ("any value") property type.
961enum ValueMarker {
962    Unset,
963    DoNothing,
964}
965
966/// Detects the unset and do-nothing markers in a value of an untyped
967/// property type (`BoxedValue` or `Option<BoxedValue>`). For every other
968/// value type this folds to a constant `None`.
969#[inline]
970fn value_marker<T: 'static>(value: &T) -> Option<ValueMarker> {
971    let any: &dyn Any = value;
972    let boxed = match any.downcast_ref::<BoxedValue>() {
973        Some(boxed) => boxed,
974        None => any.downcast_ref::<Option<BoxedValue>>()?.as_ref()?,
975    };
976    if boxed.is::<crate::UnsetValueType>() {
977        Some(ValueMarker::Unset)
978    } else if boxed.is::<crate::DoNothingType>() {
979        Some(ValueMarker::DoNothing)
980    } else {
981        None
982    }
983}