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    /// Sets the parent object that inherited property values are inherited
408    /// from.
409    pub fn set_inheritance_parent(&self, value: impl Into<Nullable<FerroObject>>) {
410        let value = value.into().0;
411        let value = value.as_ref();
412        let old = self.inheritance_parent();
413        let same = match (&old, value) {
414            (Some(a), Some(b)) => a.ptr_eq(b),
415            (None, None) => true,
416            _ => false,
417        };
418        if same {
419            return;
420        }
421        if let Some(old) = &old {
422            old.remove_inheritance_child(self);
423        }
424        *self.inheritance_parent.borrow_mut() = value.map(Ref::downgrade);
425        if let Some(new) = value {
426            let mut children = new.inheritance_children.borrow_mut();
427            if children.len() == children.capacity() {
428                // Before growing, forget children that were dropped without
429                // being detached (amortised constant time).
430                children.retain(|c| c.upgrade().is_some());
431            }
432            children.push(self.to_weak());
433        }
434        self.values.set_inheritance_parent(self, value);
435    }
436
437    fn remove_inheritance_child(&self, child: &FerroObject) {
438        let child = child.to_ref();
439        self.inheritance_children.borrow_mut().retain(|c| !c.points_to(&child));
440    }
441
442    /// Calls `f` for each inheritance child, by index over the count at the
443    /// start, as the original iterates `GetInheritanceChildren()`: no copy
444    /// of the children for every object a change propagates through. A child
445    /// that was dropped without being detached is skipped.
446    pub(crate) fn for_each_inheritance_child(&self, mut f: impl FnMut(&Ref<FerroObject>)) {
447        let count = self.inheritance_children.borrow().len();
448        for index in 0..count {
449            // Deviation (DEVIATIONS.md, Property system): upstream's
450            // `children[i]` throws past the end; here the list holds weak
451            // references and `inheritance_children` prunes dropped children,
452            // which can shorten it during the loop, so the loop stops instead.
453            let child = match self.inheritance_children.borrow().get(index) {
454                Some(child) => child.upgrade(),
455                None => break,
456            };
457            if let Some(child) = child {
458                f(&child);
459            }
460        }
461    }
462
463    pub(crate) fn inheritance_children(&self) -> Vec<Ref<FerroObject>> {
464        let mut children = self.inheritance_children.borrow_mut();
465        if children.is_empty() {
466            return Vec::new();
467        }
468        let result: Vec<Ref<FerroObject>> = children.iter().filter_map(WeakRef::upgrade).collect();
469        if result.len() != children.len() {
470            // Forget children that were dropped without being detached: a
471            // weak reference keeps the child's allocation alive.
472            children.retain(|c| c.upgrade().is_some());
473        }
474        result
475    }
476
477    // --- styled properties ------------------------------------------------
478
479    /// Clears a property's local value.
480    pub fn clear_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) {
481        self.values.clear_value(self, property);
482    }
483
484    /// Gets a property value.
485    #[inline]
486    pub fn get_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> T {
487        self.values.get_value(self, property)
488    }
489
490    /// Gets a property's base value: the value excluding animated values.
491    /// Returns `None` if the value comes from inheritance or the default.
492    pub fn get_base_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> Option<T> {
493        self.values.get_base_value(property)
494    }
495
496    /// Checks whether a property is animating.
497    pub fn is_animating(&self, property: &FerroProperty) -> bool {
498        self.values.is_animating(property)
499    }
500
501    /// Checks whether a styled property has a value assigned to it or a
502    /// binding targeting it.
503    pub fn is_set(&self, property: &FerroProperty) -> bool {
504        self.values.is_set(property)
505    }
506
507    /// Sets a property value with local value priority.
508    #[inline]
509    pub fn set_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
510        match value_marker(&value) {
511            Some(ValueMarker::Unset) => self.clear_value(property),
512            Some(ValueMarker::DoNothing) => {}
513            None => {
514                self.values.set_value(self, property, value, BindingPriority::LocalValue);
515            }
516        }
517    }
518
519    /// Sets a property value with the given priority. For priorities other
520    /// than local value, disposing the returned handle removes the value.
521    pub fn set_value_with_priority<T: PropertyValue>(
522        &self,
523        property: &'static StyledProperty<T>,
524        value: T,
525        priority: BindingPriority,
526    ) -> Option<Rc<dyn IDisposable>> {
527        Self::validate_priority(priority);
528        match value_marker(&value) {
529            Some(ValueMarker::Unset) => {
530                if priority == BindingPriority::LocalValue {
531                    self.clear_value(property);
532                }
533                None
534            }
535            Some(ValueMarker::DoNothing) => None,
536            None => self.values.set_value(self, property, value, priority),
537        }
538    }
539
540    /// Sets the value of a property without changing its value source.
541    ///
542    /// This is used by a component that programmatically sets the value of
543    /// one of its own properties without disabling an application's declared
544    /// use of the property: the effective value changes, but existing
545    /// bindings and styles continue to work. The new value has the
546    /// property's current priority.
547    pub fn set_current_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
548        match value_marker(&value) {
549            Some(ValueMarker::Unset) => self.clear_value(property),
550            Some(ValueMarker::DoNothing) => {}
551            None => self.values.set_current_value(self, property, value),
552        }
553    }
554
555    /// Binds a property to a binding. Returns the binding expression which
556    /// represents the binding instance on this object.
557    pub fn bind_binding(
558        &self,
559        property: &'static FerroProperty,
560        binding: &dyn BindingBase,
561    ) -> Rc<dyn BindingExpressionBase> {
562        self.bind_binding_with_anchor(property, binding, None)
563    }
564
565    /// Binds a property to a binding.
566    ///
567    /// Some bindings need context to locate their source: a data context,
568    /// named elements or resources. If this object is not an element, or is
569    /// not yet attached to the tree, `anchor` provides that context.
570    pub fn bind_binding_with_anchor(
571        &self,
572        property: &'static FerroProperty,
573        binding: &dyn BindingBase,
574        anchor: Option<&Ref<FerroObject>>,
575    ) -> Rc<dyn BindingExpressionBase> {
576        crate::perf_count!(BindingsInstanced);
577        let expression = binding.create_instance(self, Some(property), anchor);
578        self.values.add_binding_expression(self, property, expression)
579    }
580
581    /// Gets a binding to a property of this object: the indexer taking an
582    /// [`IndexerDescriptor`](crate::data::IndexerDescriptor)
583    /// (`source[!Property]` is `source.indexer(&Property.bind())`).
584    pub fn indexer(&self, binding: &crate::data::IndexerDescriptor) -> crate::data::IndexerBinding {
585        let property = binding.property.expect("The IndexerDescriptor has no property.");
586        crate::data::IndexerBinding::new(self.to_ref(), property, binding.mode)
587    }
588
589    /// Binds the property of an
590    /// [`IndexerDescriptor`](crate::data::IndexerDescriptor) to a binding:
591    /// the setter of the indexer (`target[!Property] = binding` is
592    /// `target.bind_indexer(&Property.bind(), &binding)`).
593    pub fn bind_indexer(
594        &self,
595        binding: &crate::data::IndexerDescriptor,
596        value: &dyn BindingBase,
597    ) -> Rc<dyn BindingExpressionBase> {
598        let property = binding.property.expect("The IndexerDescriptor has no property.");
599        self.bind_binding(property, value)
600    }
601
602    /// The binding expression that is currently active on the property, if
603    /// any.
604    pub(crate) fn get_expression(&self, property: &'static FerroProperty) -> Option<Rc<dyn BindingExpressionBase>> {
605        self.values.get_expression(property)
606    }
607
608    /// Binds a property to an observable.
609    pub fn bind<T: PropertyValue>(
610        &self,
611        property: &'static StyledProperty<T>,
612        source: Rc<dyn IObservable<T>>,
613        priority: BindingPriority,
614    ) -> Rc<dyn IDisposable> {
615        Self::validate_priority(priority);
616        self.values.add_binding(self, property, BindingSource::Typed(source), priority)
617    }
618
619    /// Binds a property to an observable of binding values.
620    pub fn bind_value<T: PropertyValue>(
621        &self,
622        property: &'static StyledProperty<T>,
623        source: Rc<dyn IObservable<BindingValue<T>>>,
624        priority: BindingPriority,
625    ) -> Rc<dyn IDisposable> {
626        Self::validate_priority(priority);
627        self.values.add_binding(self, property, BindingSource::Value(source), priority)
628    }
629
630    /// Binds a property to an observable of untyped values.
631    pub fn bind_untyped<T: PropertyValue>(
632        &self,
633        property: &'static StyledProperty<T>,
634        source: Rc<dyn IObservable<BoxedValue>>,
635        priority: BindingPriority,
636    ) -> Rc<dyn IDisposable> {
637        Self::validate_priority(priority);
638        self.values.add_binding(self, property, BindingSource::Untyped(source), priority)
639    }
640
641    /// Coerces the specified property.
642    pub fn coerce_value(&self, property: &'static FerroProperty) {
643        self.values.coerce_value(self, property);
644    }
645
646    // --- direct properties ------------------------------------------------
647
648    /// Gets a direct property value.
649    pub fn get_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) -> T {
650        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
651        registered.invoke_getter(self)
652    }
653
654    /// Sets a direct property value.
655    pub fn set_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>, value: T) {
656        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
657        registered.invoke_setter(self, BindingValue::new(value));
658    }
659
660    /// Resets a direct property to its unset value.
661    pub fn clear_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) {
662        let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
663        let unset = registered.get_unset_value_for(self);
664        registered.invoke_setter(self, BindingValue::new(unset));
665    }
666
667    pub(crate) fn set_direct_value_unchecked<T: PropertyValue>(
668        &self,
669        property: &'static DirectPropertyBase<T>,
670        value: BindingValue<T>,
671    ) {
672        let state = value.value_type();
673        let error = value.error().cloned();
674        match state {
675            BindingValueType::UNSET_VALUE | BindingValueType::BINDING_ERROR => {
676                let fallback =
677                    if value.has_value() { value } else { value.with_value(property.get_unset_value_for(self)) };
678                property.invoke_setter(self, fallback);
679            }
680            BindingValueType::VALUE
681            | BindingValueType::BINDING_ERROR_WITH_FALLBACK
682            | BindingValueType::DATA_VALIDATION_ERROR
683            | BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK => property.invoke_setter(self, value),
684            // Do nothing, and the flag combinations that no binding value has.
685            _ => {}
686        }
687        if property.get_metadata_for(self).enable_data_validation() == Some(true) {
688            self.on_update_data_validation(property, state, error);
689        }
690    }
691
692    /// Binds a direct property to an observable.
693    pub fn bind_direct<T: PropertyValue>(
694        &self,
695        property: &'static DirectPropertyBase<T>,
696        source: Rc<dyn IObservable<T>>,
697    ) -> Rc<dyn IDisposable> {
698        let property = self.writable_direct(property);
699        self.values.add_direct_binding(self, property, BindingSource::Typed(source))
700    }
701
702    /// Binds a direct property to an observable of binding values.
703    pub fn bind_direct_value<T: PropertyValue>(
704        &self,
705        property: &'static DirectPropertyBase<T>,
706        source: Rc<dyn IObservable<BindingValue<T>>>,
707    ) -> Rc<dyn IDisposable> {
708        let property = self.writable_direct(property);
709        self.values.add_direct_binding(self, property, BindingSource::Value(source))
710    }
711
712    /// Binds a direct property to an observable of untyped values.
713    pub fn bind_direct_untyped<T: PropertyValue>(
714        &self,
715        property: &'static DirectPropertyBase<T>,
716        source: Rc<dyn IObservable<BoxedValue>>,
717    ) -> Rc<dyn IDisposable> {
718        let property = self.writable_direct(property);
719        self.values.add_direct_binding(self, property, BindingSource::Untyped(source))
720    }
721
722    fn writable_direct<T: PropertyValue>(
723        &self,
724        property: &'static DirectPropertyBase<T>,
725    ) -> &'static DirectPropertyBase<T> {
726        let property = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
727        assert!(!property.is_read_only(), "The property {} is readonly.", property.name());
728        property
729    }
730
731    /// Sets the backing field of a direct property, raising a change
732    /// notification if the value changed. Returns true if it changed.
733    pub fn set_and_raise<T: PropertyValue>(
734        &self,
735        property: &'static DirectPropertyBase<T>,
736        field: &RefCell<T>,
737        value: T,
738    ) -> bool {
739        if *field.borrow() == value {
740            return false;
741        }
742        let old = field.replace(value.clone());
743        self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
744        true
745    }
746
747    /// [`set_and_raise`](Self::set_and_raise) for `Copy` values stored in a
748    /// [`Cell`].
749    pub fn set_and_raise_cell<T: PropertyValue + Copy>(
750        &self,
751        property: &'static DirectPropertyBase<T>,
752        field: &Cell<T>,
753        value: T,
754    ) -> bool {
755        let old = field.get();
756        if old == value {
757            return false;
758        }
759        field.set(value);
760        self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
761        true
762    }
763
764    // --- untyped access ---------------------------------------------------
765
766    /// Gets a property value as an untyped value.
767    pub fn get_value_untyped(&self, property: &'static FerroProperty) -> BoxedValue {
768        property.routes().route_get_value(self)
769    }
770
771    /// Sets a property from an untyped value, which must hold exactly the
772    /// property's value type or the unset marker.
773    pub fn set_value_untyped(
774        &self,
775        property: &'static FerroProperty,
776        value: &dyn Any,
777        priority: BindingPriority,
778    ) -> Option<Rc<dyn IDisposable>> {
779        property.routes().route_set_value(self, value, priority)
780    }
781
782    /// [`set_current_value`](Self::set_current_value) with an untyped value.
783    pub fn set_current_value_untyped(&self, property: &'static FerroProperty, value: &dyn Any) {
784        property.routes().route_set_current_value(self, value)
785    }
786
787    /// Clears a property's local value.
788    pub fn clear_value_untyped(&self, property: &'static FerroProperty) {
789        property.routes().route_clear_value(self)
790    }
791
792    /// Binds a property to an observable of untyped values.
793    pub fn bind_property_untyped(
794        &self,
795        property: &'static FerroProperty,
796        source: Rc<dyn IObservable<BoxedValue>>,
797        priority: BindingPriority,
798    ) -> Rc<dyn IDisposable> {
799        property.routes().route_bind(self, source, priority)
800    }
801
802    // --- change notification ----------------------------------------------
803
804    /// Subscribes to property changes on this object. Disposing the returned
805    /// handle unsubscribes.
806    pub fn property_changed(
807        &self,
808        handler: impl Fn(&FerroPropertyChangedEventArgs<'_>) + 'static,
809    ) -> Rc<dyn IDisposable> {
810        let token = self.property_changed.add(Rc::new(handler));
811        let weak = self.to_weak();
812        Disposable::create(move || {
813            if let Some(this) = weak.upgrade() {
814                this.property_changed.remove(token);
815            }
816        })
817    }
818
819    /// The number of subscribers to property changes on this object (a
820    /// diagnostics member, used by tests to verify that subscriptions are
821    /// released).
822    pub fn property_changed_subscriber_count(&self) -> usize {
823        self.property_changed.len()
824    }
825
826    /// Raises a property changed notification.
827    pub fn raise_property_changed<T: PropertyValue>(
828        &self,
829        property: &'static FerroProperty,
830        old_value: Option<&T>,
831        new_value: &T,
832        priority: BindingPriority,
833        is_effective_value: bool,
834    ) {
835        crate::perf_count!(PropertyChangesRaised);
836        let e = FerroPropertyChangedEventArgs::new(
837            self,
838            property,
839            old_value.map(|v| v as &dyn Any),
840            new_value,
841            priority,
842            is_effective_value,
843        );
844
845        self.on_property_changed_core(&e);
846
847        if is_effective_value {
848            crate::perf_count!(PropertyChangesEffective);
849            property.notify_changed(&e);
850            if !self.property_changed.is_empty() {
851                crate::perf_count!(PropertyChangesWithObjectListeners);
852                for (_, handler) in self.property_changed.snapshot().iter() {
853                    handler(&e);
854                }
855            }
856        }
857    }
858
859    /// Raises a property changed notification for a direct property.
860    pub fn raise_direct_property_changed<T: PropertyValue>(
861        &self,
862        property: &'static DirectPropertyBase<T>,
863        old_value: &T,
864        new_value: &T,
865    ) {
866        self.raise_property_changed(property, Some(old_value), new_value, BindingPriority::LocalValue, true);
867    }
868
869    fn validate_priority(priority: BindingPriority) {
870        if priority < BindingPriority::Animation || priority >= BindingPriority::Inherited {
871            panic!("Invalid priority {priority:?}");
872        }
873    }
874}
875
876impl fmt::Debug for FerroObject {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        f.write_str(self.get_type().name())
879    }
880}
881
882/// A marker held by a value of an untyped ("any value") property type.
883enum ValueMarker {
884    Unset,
885    DoNothing,
886}
887
888/// Detects the unset and do-nothing markers in a value of an untyped
889/// property type (`BoxedValue` or `Option<BoxedValue>`). For every other
890/// value type this folds to a constant `None`.
891#[inline]
892fn value_marker<T: 'static>(value: &T) -> Option<ValueMarker> {
893    let any: &dyn Any = value;
894    let boxed = match any.downcast_ref::<BoxedValue>() {
895        Some(boxed) => boxed,
896        None => any.downcast_ref::<Option<BoxedValue>>()?.as_ref()?,
897    };
898    if boxed.is::<crate::UnsetValueType>() {
899        Some(ValueMarker::Unset)
900    } else if boxed.is::<crate::DoNothingType>() {
901        Some(ValueMarker::DoNothing)
902    } else {
903        None
904    }
905}