use super::{
IBindableArray, IBindableIndexer, IBindableList, ICommand, INotifyCollectionChanged, INotifyDataErrorInfo,
INotifyPropertyChanged,
};
use crate::data::core::value_type_id;
use crate::data::core::{ClrPropertyInfo, IPropertyInfo, PropertyKind, ValueType, ValueTypes};
use crate::data::BindingError;
use crate::{AnyValue, BoxedValue};
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
pub struct ModelMethod {
pub(crate) name: Box<str>,
pub(crate) execute: Rc<dyn Fn(&dyn AnyValue, Option<&BoxedValue>)>,
pub(crate) can_execute: Option<Rc<dyn Fn(&dyn AnyValue, Option<&BoxedValue>) -> bool>>,
pub(crate) depends_on: Vec<Box<str>>,
}
impl ModelMethod {
pub fn name(&self) -> &str {
&self.name
}
}
pub struct ModelType {
value_type: ValueType,
properties: Vec<Rc<dyn IPropertyInfo>>,
methods: Vec<Rc<ModelMethod>>,
as_inpc: Option<fn(&dyn AnyValue) -> Option<&dyn INotifyPropertyChanged>>,
as_indei: Option<fn(&dyn AnyValue) -> Option<&dyn INotifyDataErrorInfo>>,
as_list: Option<fn(&dyn AnyValue) -> Option<&dyn IBindableList>>,
as_indexer: Option<fn(&dyn AnyValue) -> Option<&dyn IBindableIndexer>>,
as_array: Option<fn(&dyn AnyValue) -> Option<&dyn IBindableArray>>,
as_incc: Option<fn(&dyn AnyValue) -> Option<&dyn INotifyCollectionChanged>>,
}
impl ModelType {
pub fn value_type(&self) -> ValueType {
self.value_type
}
pub fn properties(&self) -> &[Rc<dyn IPropertyInfo>] {
&self.properties
}
pub fn find_property(&self, name: &str) -> Option<Rc<dyn IPropertyInfo>> {
self.properties.iter().find(|p| p.name() == name).cloned()
}
pub fn find_method(&self, name: &str) -> Option<Rc<ModelMethod>> {
self.methods.iter().find(|m| &*m.name == name).cloned()
}
pub fn as_notify_property_changed<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn INotifyPropertyChanged> {
self.as_inpc.and_then(|f| f(value))
}
pub fn as_notify_data_error_info<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn INotifyDataErrorInfo> {
self.as_indei.and_then(|f| f(value))
}
pub fn as_list<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn IBindableList> {
self.as_list.and_then(|f| f(value))
}
pub fn as_indexer<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn IBindableIndexer> {
self.as_indexer.and_then(|f| f(value))
}
pub fn as_array<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn IBindableArray> {
self.as_array.and_then(|f| f(value))
}
pub fn as_notify_collection_changed<'a>(&self, value: &'a dyn AnyValue) -> Option<&'a dyn INotifyCollectionChanged> {
match self.as_incc {
Some(f) => f(value),
None => self.as_list(value).and_then(|l| l.as_notify_collection_changed()),
}
}
}
pub struct ModelTypeBuilder<T: PartialEq + 'static> {
model: ModelType,
_marker: std::marker::PhantomData<fn(&T)>,
}
impl<T: PartialEq + 'static> ModelTypeBuilder<T> {
fn new() -> Self {
Self {
model: ModelType {
value_type: ValueType::of::<T>(),
properties: Vec::new(),
methods: Vec::new(),
as_inpc: None,
as_indei: None,
as_list: None,
as_indexer: None,
as_array: None,
as_incc: None,
},
_marker: std::marker::PhantomData,
}
}
pub fn notify_property_changed(mut self) -> Self
where
T: INotifyPropertyChanged,
{
fn cast<T: INotifyPropertyChanged + 'static>(v: &dyn AnyValue) -> Option<&dyn INotifyPropertyChanged> {
v.downcast_ref::<T>().map(|v| v as &dyn INotifyPropertyChanged)
}
self.model.as_inpc = Some(cast::<T>);
self
}
pub fn notify_data_error_info(mut self) -> Self
where
T: INotifyDataErrorInfo,
{
fn cast<T: INotifyDataErrorInfo + 'static>(v: &dyn AnyValue) -> Option<&dyn INotifyDataErrorInfo> {
v.downcast_ref::<T>().map(|v| v as &dyn INotifyDataErrorInfo)
}
self.model.as_indei = Some(cast::<T>);
self
}
pub fn list(mut self) -> Self
where
T: IBindableList,
{
fn cast<T: IBindableList + 'static>(v: &dyn AnyValue) -> Option<&dyn IBindableList> {
v.downcast_ref::<T>().map(|v| v as &dyn IBindableList)
}
self.model.as_list = Some(cast::<T>);
self
}
pub fn indexer(mut self) -> Self
where
T: IBindableIndexer,
{
fn cast<T: IBindableIndexer + 'static>(v: &dyn AnyValue) -> Option<&dyn IBindableIndexer> {
v.downcast_ref::<T>().map(|v| v as &dyn IBindableIndexer)
}
self.model.as_indexer = Some(cast::<T>);
self
}
pub fn array(mut self) -> Self
where
T: IBindableArray,
{
fn cast<T: IBindableArray + 'static>(v: &dyn AnyValue) -> Option<&dyn IBindableArray> {
v.downcast_ref::<T>().map(|v| v as &dyn IBindableArray)
}
self.model.as_array = Some(cast::<T>);
self
}
pub fn notify_collection_changed(mut self) -> Self
where
T: INotifyCollectionChanged,
{
fn cast<T: INotifyCollectionChanged + 'static>(v: &dyn AnyValue) -> Option<&dyn INotifyCollectionChanged> {
v.downcast_ref::<T>().map(|v| v as &dyn INotifyCollectionChanged)
}
self.model.as_incc = Some(cast::<T>);
self
}
pub fn command(self) -> Self
where
T: ICommand,
{
ValueTypes::register_boxed_conversion::<T, Rc<dyn ICommand>>(|v| {
let any: Rc<dyn std::any::Any> = v.clone();
any.downcast::<T>().ok().map(|c| c as Rc<dyn ICommand>)
});
self
}
pub fn property_info(mut self, property: Rc<dyn IPropertyInfo>) -> Self {
self.model.properties.push(property);
self
}
pub fn read_only<K: PropertyKind>(self, name: &str, get: impl Fn(&T) -> K::Typed + 'static) -> Self {
self.property_info(Rc::new(ClrPropertyInfo::read_only::<T, K>(name, get)))
}
pub fn property<K: PropertyKind>(
self,
name: &str,
get: impl Fn(&T) -> K::Typed + 'static,
set: impl Fn(&T, K::Typed) + 'static,
) -> Self {
self.property_info(Rc::new(ClrPropertyInfo::read_write::<T, K>(name, get, set)))
}
pub fn validated_property<K: PropertyKind>(
self,
name: &str,
get: impl Fn(&T) -> K::Typed + 'static,
set: impl Fn(&T, K::Typed) -> Result<(), BindingError> + 'static,
) -> Self {
self.property_info(Rc::new(ClrPropertyInfo::read_write_validated::<T, K>(name, get, set)))
}
pub fn method(mut self, name: &str, execute: impl Fn(&T, Option<&BoxedValue>) + 'static) -> Self {
self.model.methods.push(Rc::new(ModelMethod {
name: name.into(),
execute: Rc::new(move |o, p| {
if let Some(o) = o.downcast_ref::<T>() {
execute(o, p)
}
}),
can_execute: None,
depends_on: Vec::new(),
}));
self
}
pub fn method_with_can_execute(
mut self,
name: &str,
execute: impl Fn(&T, Option<&BoxedValue>) + 'static,
can_execute: impl Fn(&T, Option<&BoxedValue>) -> bool + 'static,
depends_on: &[&str],
) -> Self {
self.model.methods.push(Rc::new(ModelMethod {
name: name.into(),
execute: Rc::new(move |o, p| {
if let Some(o) = o.downcast_ref::<T>() {
execute(o, p)
}
}),
can_execute: Some(Rc::new(move |o, p| o.downcast_ref::<T>().is_some_and(|o| can_execute(o, p)))),
depends_on: depends_on.iter().map(|s| (*s).into()).collect(),
}));
self
}
}
crate::ferro_thread_local! {
static MODELS: RefCell<HashMap<TypeId, Rc<ModelType>>> = RefCell::new(HashMap::new());
}
pub trait Model: PartialEq + Sized + 'static {
fn describe(builder: ModelTypeBuilder<Self>) -> ModelTypeBuilder<Self>;
fn register() {
if !ModelTypes::is_registered(TypeId::of::<Self>()) {
ModelTypes::register::<Self>(Self::describe);
}
}
fn new_model(value: Self) -> Rc<Self> {
Self::register();
Rc::new(value)
}
}
pub struct ModelTypes;
impl ModelTypes {
pub fn register<T: PartialEq + 'static>(describe: impl FnOnce(ModelTypeBuilder<T>) -> ModelTypeBuilder<T>) {
ValueTypes::register_reference::<T>();
let model = Rc::new(describe(ModelTypeBuilder::new()).model);
MODELS.with(|m| m.borrow_mut().insert(TypeId::of::<T>(), model));
}
pub(crate) fn register_list<T: IBindableList + PartialEq + 'static>() {
if !Self::is_registered(TypeId::of::<T>()) {
Self::register::<T>(|b| b.list());
}
}
pub fn register_notifying_collection<T: INotifyCollectionChanged + PartialEq + 'static>() {
if !Self::is_registered(TypeId::of::<T>()) {
let model = Rc::new(ModelTypeBuilder::<T>::new().notify_collection_changed().model);
MODELS.with(|m| m.borrow_mut().insert(TypeId::of::<T>(), model));
}
}
pub fn is_registered(id: TypeId) -> bool {
MODELS.with(|m| m.borrow().contains_key(&id))
}
pub fn find(value: &dyn AnyValue) -> Option<Rc<ModelType>> {
let id = value_type_id(&*value);
MODELS.try_with(|m| m.borrow().get(&id).cloned()).ok().flatten()
}
}
#[macro_export]
macro_rules! ferro_model {
($ty:ty, $describe:expr) => {
impl ::std::cmp::PartialEq for $ty {
#[inline]
fn eq(&self, other: &Self) -> bool {
::std::ptr::eq(self, other)
}
}
impl $crate::data::model::Model for $ty {
fn describe(
builder: $crate::data::model::ModelTypeBuilder<Self>,
) -> $crate::data::model::ModelTypeBuilder<Self> {
let describe: fn(
$crate::data::model::ModelTypeBuilder<Self>,
) -> $crate::data::model::ModelTypeBuilder<Self> = $describe;
describe(builder)
}
}
};
}