use crate::data::{BindingBase, BindingError, BindingExpressionBase, BindingPriority, BindingValue, BindingValueType};
use crate::property_store::{BindingSource, ValueStore};
use crate::reactive::{Disposable, IDisposable, IObservable};
use crate::threading::Dispatcher;
use crate::type_system::{__forwards_to_parent, cast_this, parent_vtable};
use crate::utilities::HandlerList;
use crate::{
BoxedValue, DirectPropertyBase, FerroProperty, FerroPropertyChangedEventArgs, FerroPropertyRegistry,
Nullable, ObjectType, PropertyValue, Ref, StaticType, StyledProperty, Subclassable, TypeInfo, Upcast, WeakRef,
};
use std::any::Any;
use std::cell::{Cell, OnceCell, RefCell};
use std::fmt;
use std::rc::{Rc, Weak};
use std::sync::Arc;
type PropertyChangedHandler = dyn Fn(&FerroPropertyChangedEventArgs<'_>);
#[derive(Clone, Copy)]
#[repr(C)]
pub struct FerroObjectVTable {
pub type_info: &'static TypeInfo,
pub constructed: fn(&FerroObject),
pub on_property_changed_core: fn(&FerroObject, &FerroPropertyChangedEventArgs<'_>),
pub on_property_changed: fn(&FerroObject, &FerroPropertyChangedEventArgs<'_>),
pub update_data_validation: fn(&FerroObject, &'static FerroProperty, BindingValueType, Option<&BindingError>),
}
#[repr(C)]
pub struct FerroObject {
this: OnceCell<Weak<dyn Any>>,
vtable: Cell<Option<&'static FerroObjectVTable>>,
values: ValueStore,
inheritance_parent: RefCell<Option<WeakRef<FerroObject>>>,
inheritance_children: RefCell<Vec<WeakRef<FerroObject>>>,
property_changed: HandlerList<PropertyChangedHandler>,
dispatcher: OnceCell<Arc<Dispatcher>>,
}
static FERRO_OBJECT_TYPE: TypeInfo = TypeInfo::new("FerroObject", None)
.with_module_path(module_path!())
.with_class_init(<FerroObject as ObjectType>::register_class)
.with_handles(|| [std::any::TypeId::of::<Ref<FerroObject>>(), std::any::TypeId::of::<Option<Ref<FerroObject>>>()])
.with_interfaces({
#[allow(unused_imports)]
use crate::ClassDefaults as _;
<FerroObject>::__INTERFACES
})
.with_markup({
#[allow(unused_imports)]
use crate::ClassDefaults as _;
<FerroObject>::__MARKUP
});
impl StaticType for FerroObject {
const TYPE: &'static TypeInfo = &FERRO_OBJECT_TYPE;
}
impl Upcast<FerroObject> for FerroObject {
#[inline]
fn upcast(&self) -> &FerroObject {
self
}
}
unsafe impl ObjectType for FerroObject {
type Parent = FerroObject;
type VTable = FerroObjectVTable;
fn vtable() -> &'static FerroObjectVTable {
static VTABLE: std::sync::OnceLock<FerroObjectVTable> = std::sync::OnceLock::new();
VTABLE.get_or_init(<FerroObject as Subclassable<FerroObject>>::build_vtable)
}
#[inline]
fn register_class() {
crate::ferro_thread_local! {
static REGISTERED: Cell<bool> = const { Cell::new(false) };
}
if !REGISTERED.replace(true) {
crate::type_system::__register_class::<FerroObject>();
}
}
}
pub trait FerroObjectImpl: ObjectType {
#[doc(hidden)]
const __OVERRIDES: Option<&'static [&'static str]> = None;
fn constructed(this: &Self) {
Self::parent_constructed(this)
}
fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed_core(this, change)
}
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change)
}
fn update_data_validation(
this: &Self,
property: &'static FerroProperty,
state: BindingValueType,
error: Option<&BindingError>,
) {
Self::parent_update_data_validation(this, property, state, error)
}
}
pub trait FerroObjectImplExt: FerroObjectImpl {
#[inline]
fn parent_constructed(this: &Self) {
(parent_vtable::<Self, FerroObjectVTable>().constructed)(this.upcast())
}
#[inline]
fn parent_on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
(parent_vtable::<Self, FerroObjectVTable>().on_property_changed_core)(this.upcast(), change)
}
#[inline]
fn parent_on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
(parent_vtable::<Self, FerroObjectVTable>().on_property_changed)(this.upcast(), change)
}
#[inline]
fn parent_update_data_validation(
this: &Self,
property: &'static FerroProperty,
state: BindingValueType,
error: Option<&BindingError>,
) {
(parent_vtable::<Self, FerroObjectVTable>().update_data_validation)(this.upcast(), property, state, error)
}
}
impl<T: FerroObjectImpl> FerroObjectImplExt for T {}
impl<T: FerroObjectImpl> Subclassable<T> for FerroObject {
fn build_vtable() -> FerroObjectVTable {
let mut table = FerroObjectVTable {
type_info: T::TYPE,
constructed: |this| T::constructed(unsafe { cast_this::<FerroObject, T>(this) }),
on_property_changed_core: |this, change| {
T::on_property_changed_core(unsafe { cast_this::<FerroObject, T>(this) }, change)
},
on_property_changed: |this, change| {
T::on_property_changed(unsafe { cast_this::<FerroObject, T>(this) }, change)
},
update_data_validation: |this, property, state, error| {
T::update_data_validation(unsafe { cast_this::<FerroObject, T>(this) }, property, state, error)
},
};
let forwards: [bool; 4] = const {
let overrides = <T as FerroObjectImpl>::__OVERRIDES;
[
__forwards_to_parent::<T, FerroObjectVTable>(overrides, "constructed"),
__forwards_to_parent::<T, FerroObjectVTable>(overrides, "on_property_changed_core"),
__forwards_to_parent::<T, FerroObjectVTable>(overrides, "on_property_changed"),
__forwards_to_parent::<T, FerroObjectVTable>(overrides, "update_data_validation"),
]
};
if forwards[0] {
table.constructed = parent_vtable::<T, FerroObjectVTable>().constructed;
}
if forwards[1] {
table.on_property_changed_core = parent_vtable::<T, FerroObjectVTable>().on_property_changed_core;
}
if forwards[2] {
table.on_property_changed = parent_vtable::<T, FerroObjectVTable>().on_property_changed;
}
if forwards[3] {
table.update_data_validation = parent_vtable::<T, FerroObjectVTable>().update_data_validation;
}
table
}
}
impl FerroObjectImpl for FerroObject {
fn constructed(_this: &Self) {}
fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
if change.is_effective_value_change() {
this.on_property_changed(change);
}
}
fn on_property_changed(_this: &Self, _change: &FerroPropertyChangedEventArgs<'_>) {}
fn update_data_validation(
_this: &Self,
_property: &'static FerroProperty,
_state: BindingValueType,
_error: Option<&BindingError>,
) {
}
}
impl FerroObject {
pub const TYPE: &'static TypeInfo = &FERRO_OBJECT_TYPE;
pub fn construct() -> FerroObject {
FerroObject {
this: OnceCell::new(),
vtable: Cell::new(None),
values: ValueStore::new(),
inheritance_parent: RefCell::new(None),
inheritance_children: RefCell::new(Vec::new()),
property_changed: HandlerList::new(),
dispatcher: OnceCell::new(),
}
}
#[inline]
pub fn dispatcher(&self) -> &Arc<Dispatcher> {
self.dispatcher.get_or_init(Dispatcher::current_dispatcher)
}
#[inline]
pub fn check_access(&self) -> bool {
self.dispatcher().check_access()
}
#[inline]
#[track_caller]
pub fn verify_access(&self) {
self.dispatcher().verify_access()
}
pub fn new() -> Ref<FerroObject> {
crate::instantiate(Self::construct())
}
#[doc(hidden)]
pub unsafe fn attach(&self, this: Weak<dyn Any>, vtable: *const ()) {
let _ = self.this.set(this);
self.vtable.set(Some(&*(vtable as *const FerroObjectVTable)));
}
#[inline]
fn vt(&self) -> &'static FerroObjectVTable {
self.vtable.get().expect("object is not instantiated")
}
#[doc(hidden)]
#[inline]
pub fn vtable_of<T: ObjectType>(this: &T) -> &'static T::VTable {
let object: &FerroObject = this.upcast();
let table: *const FerroObjectVTable = object.vt();
unsafe { &*(table as *const T::VTable) }
}
#[doc(hidden)]
#[inline]
pub fn ref_of<T: ObjectType>(this: &T) -> Ref<T> {
let object: &FerroObject = this.upcast();
let ptr = object
.this
.get()
.and_then(Weak::upgrade)
.expect("object is not instantiated or is being dropped");
Ref::from_rc(ptr)
}
#[inline]
pub fn to_ref(&self) -> Ref<FerroObject> {
Self::ref_of(self)
}
#[inline]
pub fn to_weak(&self) -> WeakRef<FerroObject> {
WeakRef::from_weak(self.this.get().expect("object is not instantiated").clone())
}
#[inline]
pub fn get_type(&self) -> &'static TypeInfo {
match self.vtable.get() {
Some(table) => table.type_info,
None => Self::TYPE,
}
}
#[inline]
pub fn is<T: ObjectType>(&self) -> bool {
T::TYPE.is_assignable_from(self.get_type())
}
#[inline]
pub fn downcast_ref<T: ObjectType>(&self) -> Option<&T> {
if self.is::<T>() {
Some(unsafe { cast_this::<FerroObject, T>(self) })
} else {
None
}
}
#[inline]
pub(crate) fn values(&self) -> &ValueStore {
&self.values
}
#[inline]
pub(crate) fn constructed(&self) {
crate::perf_count_virtual!("FerroObject::constructed");
(self.vt().constructed)(self)
}
#[inline]
pub(crate) fn on_property_changed_core(&self, change: &FerroPropertyChangedEventArgs<'_>) {
crate::perf_count_virtual!("FerroObject::on_property_changed_core");
(self.vt().on_property_changed_core)(self, change)
}
#[inline]
pub fn on_property_changed(&self, change: &FerroPropertyChangedEventArgs<'_>) {
crate::perf_count_virtual!("FerroObject::on_property_changed");
(self.vt().on_property_changed)(self, change)
}
#[inline]
pub(crate) fn on_update_data_validation(
&self,
property: &'static FerroProperty,
state: BindingValueType,
error: Option<BindingError>,
) {
crate::perf_count_virtual!("FerroObject::update_data_validation");
(self.vt().update_data_validation)(self, property, state, error.as_ref())
}
pub fn inheritance_parent(&self) -> Option<Ref<FerroObject>> {
self.inheritance_parent.borrow().as_ref().and_then(WeakRef::upgrade)
}
pub(crate) fn is_inheritance_parent(&self, parent: &FerroObject) -> bool {
self.inheritance_parent.borrow().as_ref().is_some_and(|weak| weak.points_at(parent))
}
pub fn set_inheritance_parent(&self, value: impl Into<Nullable<FerroObject>>) {
let value = value.into().0;
let value = value.as_ref();
let old = self.inheritance_parent();
let same = match (&old, value) {
(Some(a), Some(b)) => a.ptr_eq(b),
(None, None) => true,
_ => false,
};
if same {
return;
}
if let Some(old) = &old {
old.remove_inheritance_child(self);
}
*self.inheritance_parent.borrow_mut() = value.map(Ref::downgrade);
if let Some(new) = value {
let mut children = new.inheritance_children.borrow_mut();
if children.len() == children.capacity() {
children.retain(|c| c.upgrade().is_some());
}
children.push(self.to_weak());
}
self.values.set_inheritance_parent(self, value);
}
fn remove_inheritance_child(&self, child: &FerroObject) {
let child = child.to_ref();
self.inheritance_children.borrow_mut().retain(|c| !c.points_to(&child));
}
pub(crate) fn for_each_inheritance_child(&self, mut f: impl FnMut(&Ref<FerroObject>)) {
let count = self.inheritance_children.borrow().len();
for index in 0..count {
let child = match self.inheritance_children.borrow().get(index) {
Some(child) => child.upgrade(),
None => break,
};
if let Some(child) = child {
f(&child);
}
}
}
pub(crate) fn inheritance_children(&self) -> Vec<Ref<FerroObject>> {
let mut children = self.inheritance_children.borrow_mut();
if children.is_empty() {
return Vec::new();
}
let result: Vec<Ref<FerroObject>> = children.iter().filter_map(WeakRef::upgrade).collect();
if result.len() != children.len() {
children.retain(|c| c.upgrade().is_some());
}
result
}
pub fn clear_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) {
self.values.clear_value(self, property);
}
#[inline]
pub fn get_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> T {
self.values.get_value(self, property)
}
pub fn get_base_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>) -> Option<T> {
self.values.get_base_value(property)
}
pub fn is_animating(&self, property: &FerroProperty) -> bool {
self.values.is_animating(property)
}
pub fn is_set(&self, property: &FerroProperty) -> bool {
self.values.is_set(property)
}
#[inline]
pub fn set_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
match value_marker(&value) {
Some(ValueMarker::Unset) => self.clear_value(property),
Some(ValueMarker::DoNothing) => {}
None => {
self.values.set_value(self, property, value, BindingPriority::LocalValue);
}
}
}
pub fn set_value_with_priority<T: PropertyValue>(
&self,
property: &'static StyledProperty<T>,
value: T,
priority: BindingPriority,
) -> Option<Rc<dyn IDisposable>> {
Self::validate_priority(priority);
match value_marker(&value) {
Some(ValueMarker::Unset) => {
if priority == BindingPriority::LocalValue {
self.clear_value(property);
}
None
}
Some(ValueMarker::DoNothing) => None,
None => self.values.set_value(self, property, value, priority),
}
}
pub fn set_current_value<T: PropertyValue>(&self, property: &'static StyledProperty<T>, value: T) {
match value_marker(&value) {
Some(ValueMarker::Unset) => self.clear_value(property),
Some(ValueMarker::DoNothing) => {}
None => self.values.set_current_value(self, property, value),
}
}
pub fn bind_binding(
&self,
property: &'static FerroProperty,
binding: &dyn BindingBase,
) -> Rc<dyn BindingExpressionBase> {
self.bind_binding_with_anchor(property, binding, None)
}
pub fn bind_binding_with_anchor(
&self,
property: &'static FerroProperty,
binding: &dyn BindingBase,
anchor: Option<&Ref<FerroObject>>,
) -> Rc<dyn BindingExpressionBase> {
crate::perf_count!(BindingsInstanced);
let expression = binding.create_instance(self, Some(property), anchor);
self.values.add_binding_expression(self, property, expression)
}
pub fn indexer(&self, binding: &crate::data::IndexerDescriptor) -> crate::data::IndexerBinding {
let property = binding.property.expect("The IndexerDescriptor has no property.");
crate::data::IndexerBinding::new(self.to_ref(), property, binding.mode)
}
pub fn bind_indexer(
&self,
binding: &crate::data::IndexerDescriptor,
value: &dyn BindingBase,
) -> Rc<dyn BindingExpressionBase> {
let property = binding.property.expect("The IndexerDescriptor has no property.");
self.bind_binding(property, value)
}
pub(crate) fn get_expression(&self, property: &'static FerroProperty) -> Option<Rc<dyn BindingExpressionBase>> {
self.values.get_expression(property)
}
pub fn bind<T: PropertyValue>(
&self,
property: &'static StyledProperty<T>,
source: Rc<dyn IObservable<T>>,
priority: BindingPriority,
) -> Rc<dyn IDisposable> {
Self::validate_priority(priority);
self.values.add_binding(self, property, BindingSource::Typed(source), priority)
}
pub fn bind_value<T: PropertyValue>(
&self,
property: &'static StyledProperty<T>,
source: Rc<dyn IObservable<BindingValue<T>>>,
priority: BindingPriority,
) -> Rc<dyn IDisposable> {
Self::validate_priority(priority);
self.values.add_binding(self, property, BindingSource::Value(source), priority)
}
pub fn bind_untyped<T: PropertyValue>(
&self,
property: &'static StyledProperty<T>,
source: Rc<dyn IObservable<BoxedValue>>,
priority: BindingPriority,
) -> Rc<dyn IDisposable> {
Self::validate_priority(priority);
self.values.add_binding(self, property, BindingSource::Untyped(source), priority)
}
pub fn coerce_value(&self, property: &'static FerroProperty) {
self.values.coerce_value(self, property);
}
pub fn get_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) -> T {
let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
registered.invoke_getter(self)
}
pub fn set_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>, value: T) {
let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
registered.invoke_setter(self, BindingValue::new(value));
}
pub fn clear_direct_value<T: PropertyValue>(&self, property: &'static DirectPropertyBase<T>) {
let registered = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
let unset = registered.get_unset_value_for(self);
registered.invoke_setter(self, BindingValue::new(unset));
}
pub(crate) fn set_direct_value_unchecked<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
value: BindingValue<T>,
) {
let state = value.value_type();
let error = value.error().cloned();
match state {
BindingValueType::UNSET_VALUE | BindingValueType::BINDING_ERROR => {
let fallback =
if value.has_value() { value } else { value.with_value(property.get_unset_value_for(self)) };
property.invoke_setter(self, fallback);
}
BindingValueType::VALUE
| BindingValueType::BINDING_ERROR_WITH_FALLBACK
| BindingValueType::DATA_VALIDATION_ERROR
| BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK => property.invoke_setter(self, value),
_ => {}
}
if property.get_metadata_for(self).enable_data_validation() == Some(true) {
self.on_update_data_validation(property, state, error);
}
}
pub fn bind_direct<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
source: Rc<dyn IObservable<T>>,
) -> Rc<dyn IDisposable> {
let property = self.writable_direct(property);
self.values.add_direct_binding(self, property, BindingSource::Typed(source))
}
pub fn bind_direct_value<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
source: Rc<dyn IObservable<BindingValue<T>>>,
) -> Rc<dyn IDisposable> {
let property = self.writable_direct(property);
self.values.add_direct_binding(self, property, BindingSource::Value(source))
}
pub fn bind_direct_untyped<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
source: Rc<dyn IObservable<BoxedValue>>,
) -> Rc<dyn IDisposable> {
let property = self.writable_direct(property);
self.values.add_direct_binding(self, property, BindingSource::Untyped(source))
}
fn writable_direct<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
) -> &'static DirectPropertyBase<T> {
let property = FerroPropertyRegistry::instance().get_registered_direct_for(self, property);
assert!(!property.is_read_only(), "The property {} is readonly.", property.name());
property
}
pub fn set_and_raise<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
field: &RefCell<T>,
value: T,
) -> bool {
if *field.borrow() == value {
return false;
}
let old = field.replace(value.clone());
self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
true
}
pub fn set_and_raise_cell<T: PropertyValue + Copy>(
&self,
property: &'static DirectPropertyBase<T>,
field: &Cell<T>,
value: T,
) -> bool {
let old = field.get();
if old == value {
return false;
}
field.set(value);
self.raise_property_changed(property, Some(&old), &value, BindingPriority::LocalValue, true);
true
}
pub fn get_value_untyped(&self, property: &'static FerroProperty) -> BoxedValue {
property.routes().route_get_value(self)
}
pub fn set_value_untyped(
&self,
property: &'static FerroProperty,
value: &dyn Any,
priority: BindingPriority,
) -> Option<Rc<dyn IDisposable>> {
property.routes().route_set_value(self, value, priority)
}
pub fn set_current_value_untyped(&self, property: &'static FerroProperty, value: &dyn Any) {
property.routes().route_set_current_value(self, value)
}
pub fn clear_value_untyped(&self, property: &'static FerroProperty) {
property.routes().route_clear_value(self)
}
pub fn bind_property_untyped(
&self,
property: &'static FerroProperty,
source: Rc<dyn IObservable<BoxedValue>>,
priority: BindingPriority,
) -> Rc<dyn IDisposable> {
property.routes().route_bind(self, source, priority)
}
pub fn property_changed(
&self,
handler: impl Fn(&FerroPropertyChangedEventArgs<'_>) + 'static,
) -> Rc<dyn IDisposable> {
let token = self.property_changed.add(Rc::new(handler));
let weak = self.to_weak();
Disposable::create(move || {
if let Some(this) = weak.upgrade() {
this.property_changed.remove(token);
}
})
}
pub fn add_property_changed_handler(&self, handler: Rc<dyn Fn(&FerroPropertyChangedEventArgs<'_>)>) -> u64 {
self.property_changed.add(handler)
}
pub fn remove_property_changed_handler(&self, token: u64) -> bool {
self.property_changed.remove(token)
}
pub fn property_changed_subscriber_count(&self) -> usize {
self.property_changed.len()
}
pub fn raise_property_changed<T: PropertyValue>(
&self,
property: &'static FerroProperty,
old_value: Option<&T>,
new_value: &T,
priority: BindingPriority,
is_effective_value: bool,
) {
crate::perf_count!(PropertyChangesRaised);
let e = FerroPropertyChangedEventArgs::new(
self,
property,
old_value.map(|v| v as &dyn Any),
new_value,
priority,
is_effective_value,
);
self.on_property_changed_core(&e);
if is_effective_value {
crate::perf_count!(PropertyChangesEffective);
property.notify_changed(&e);
if !self.property_changed.is_empty() {
crate::perf_count!(PropertyChangesWithObjectListeners);
for (_, handler) in self.property_changed.snapshot().iter() {
handler(&e);
}
}
}
}
pub fn raise_direct_property_changed<T: PropertyValue>(
&self,
property: &'static DirectPropertyBase<T>,
old_value: &T,
new_value: &T,
) {
self.raise_property_changed(property, Some(old_value), new_value, BindingPriority::LocalValue, true);
}
fn validate_priority(priority: BindingPriority) {
if priority < BindingPriority::Animation || priority >= BindingPriority::Inherited {
panic!("Invalid priority {priority:?}");
}
}
}
impl fmt::Debug for FerroObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.get_type().name())
}
}
enum ValueMarker {
Unset,
DoNothing,
}
#[inline]
fn value_marker<T: 'static>(value: &T) -> Option<ValueMarker> {
let any: &dyn Any = value;
let boxed = match any.downcast_ref::<BoxedValue>() {
Some(boxed) => boxed,
None => any.downcast_ref::<Option<BoxedValue>>()?.as_ref()?,
};
if boxed.is::<crate::UnsetValueType>() {
Some(ValueMarker::Unset)
} else if boxed.is::<crate::DoNothingType>() {
Some(ValueMarker::DoNothing)
} else {
None
}
}