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