use super::INotifyPropertyChanged;
use crate::IDisposable;
use std::any::Any;
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
type Observe<S> = Rc<dyn Fn(&S, Rc<dyn Fn()>) -> Rc<dyn IDisposable>>;
pub struct PropertyPath<S, V> {
names: Rc<[String]>,
get: Rc<dyn Fn(&S) -> V>,
observe: Observe<S>,
steps: Rc<[PathStep]>,
}
impl<S, V> Clone for PropertyPath<S, V> {
fn clone(&self) -> Self {
Self { names: self.names.clone(), get: self.get.clone(), observe: self.observe.clone(), steps: self.steps.clone() }
}
}
#[derive(Clone, Default)]
pub struct PropertyOwner {
pub object: Option<Rc<dyn Any>>,
pub notify: Option<Rc<dyn INotifyPropertyChanged>>,
}
impl PropertyOwner {
pub fn same_object(&self, other: &PropertyOwner) -> bool {
match (&self.object, &other.object) {
(Some(first), Some(second)) => Rc::ptr_eq(first, second),
(None, None) => match (&self.notify, &other.notify) {
(Some(first), Some(second)) => Rc::ptr_eq(first, second),
(None, None) => true,
_ => false,
},
_ => false,
}
}
}
impl fmt::Debug for PropertyOwner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PropertyOwner")
.field("object", &self.object.as_ref().map(|object| Rc::as_ptr(object) as *const ()))
.field("notify", &self.notify.is_some())
.finish()
}
}
#[derive(Clone)]
pub struct PathStep {
pub name: String,
pub get: Rc<dyn Fn(&dyn Any) -> Box<dyn Any>>,
pub owner: Rc<dyn Fn(&dyn Any) -> PropertyOwner>,
}
impl fmt::Debug for PathStep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PathStep").field("name", &self.name).finish_non_exhaustive()
}
}
fn step_input<'a, T: 'static>(value: &'a dyn Any, name: &str) -> &'a T {
match value.downcast_ref::<T>() {
Some(value) => value,
None => panic!(
"the step {name} of a property path was given a value of another type than {}",
std::any::type_name::<T>()
),
}
}
fn owner_of<M: INotifyPropertyChanged + 'static>(object: &Rc<M>) -> PropertyOwner {
PropertyOwner { object: Some(object.clone() as Rc<dyn Any>), notify: Some(object.clone() as Rc<dyn INotifyPropertyChanged>) }
}
fn observe_property<M: INotifyPropertyChanged + 'static>(source: &Rc<M>, watched: String, changed: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
let token = source.property_changed().add(Rc::new(move |property: &str| {
if property.is_empty() || property == watched {
changed();
}
}));
let source = source.clone();
on_dispose(move || {
source.property_changed().remove(token);
})
}
struct OnDispose(RefCell<Option<Box<dyn FnOnce()>>>);
impl IDisposable for OnDispose {
fn dispose(&self) {
let end = self.0.borrow_mut().take();
if let Some(end) = end {
end();
}
}
}
fn on_dispose(end: impl FnOnce() + 'static) -> Rc<dyn IDisposable> {
Rc::new(OnDispose(RefCell::new(Some(Box::new(end)))))
}
pub fn property_name_of(getter: &str) -> String {
getter
.split('_')
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
None => String::new(),
}
})
.collect()
}
impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Rc<M>, V> {
pub fn property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
let name: String = name.into();
let watched = name.clone();
let get: Rc<dyn Fn(&Rc<M>) -> V> = Rc::new(get);
let step_get = get.clone();
let step_name = name.clone();
let owner_name = name.clone();
let step = PathStep {
name: name.clone(),
get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<Rc<M>>(source, &step_name))) as Box<dyn Any>),
owner: Rc::new(move |source: &dyn Any| owner_of(step_input::<Rc<M>>(source, &owner_name))),
};
Self {
names: Rc::from(vec![name]),
get,
observe: Rc::new(move |source: &Rc<M>, changed: Rc<dyn Fn()>| observe_property(source, watched.clone(), changed)),
steps: Rc::from(vec![step]),
}
}
}
impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Option<Rc<M>>, Option<V>> {
pub fn optional_property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
let name: String = name.into();
let watched = name.clone();
let get: Rc<dyn Fn(&Rc<M>) -> V> = Rc::new(get);
let path_get = get.clone();
let step_name = name.clone();
let owner_name = name.clone();
let step = PathStep {
name: name.clone(),
get: Rc::new(move |source: &dyn Any| {
Box::new(step_input::<Option<Rc<M>>>(source, &step_name).as_ref().map(|object| get(object))) as Box<dyn Any>
}),
owner: Rc::new(move |source: &dyn Any| match step_input::<Option<Rc<M>>>(source, &owner_name) {
Some(object) => owner_of(object),
None => PropertyOwner::default(),
}),
};
Self {
names: Rc::from(vec![name]),
get: Rc::new(move |source: &Option<Rc<M>>| source.as_ref().map(|object| path_get(object))),
observe: Rc::new(move |source: &Option<Rc<M>>, changed: Rc<dyn Fn()>| match source {
Some(object) => observe_property(object, watched.clone(), changed),
None => on_dispose(|| {}),
}),
steps: Rc::from(vec![step]),
}
}
}
impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Option<Rc<M>>, Option<V>> {
pub fn optional_property_and_then(name: impl Into<String>, get: impl Fn(&Rc<M>) -> Option<V> + 'static) -> Self {
let name: String = name.into();
let watched = name.clone();
let get: Rc<dyn Fn(&Rc<M>) -> Option<V>> = Rc::new(get);
let path_get = get.clone();
let step_name = name.clone();
let owner_name = name.clone();
let step = PathStep {
name: name.clone(),
get: Rc::new(move |source: &dyn Any| {
Box::new(step_input::<Option<Rc<M>>>(source, &step_name).as_ref().and_then(|object| get(object))) as Box<dyn Any>
}),
owner: Rc::new(move |source: &dyn Any| match step_input::<Option<Rc<M>>>(source, &owner_name) {
Some(object) => owner_of(object),
None => PropertyOwner::default(),
}),
};
Self {
names: Rc::from(vec![name]),
get: Rc::new(move |source: &Option<Rc<M>>| source.as_ref().and_then(|object| path_get(object))),
observe: Rc::new(move |source: &Option<Rc<M>>, changed: Rc<dyn Fn()>| match source {
Some(object) => observe_property(object, watched.clone(), changed),
None => on_dispose(|| {}),
}),
steps: Rc::from(vec![step]),
}
}
}
impl<M: 'static, V: 'static> PropertyPath<Rc<M>, V> {
pub fn object_property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
Self::owned_property(
name,
get,
|source: &Rc<M>| PropertyOwner { object: Some(source.clone() as Rc<dyn Any>), notify: None },
|_: &Rc<M>, _: Rc<dyn Fn()>| on_dispose(|| {}),
)
}
}
impl<S: 'static, V: 'static> PropertyPath<S, V> {
pub fn owned_property(
name: impl Into<String>,
get: impl Fn(&S) -> V + 'static,
owner: impl Fn(&S) -> PropertyOwner + 'static,
observe: impl Fn(&S, Rc<dyn Fn()>) -> Rc<dyn IDisposable> + 'static,
) -> Self {
let name: String = name.into();
let get: Rc<dyn Fn(&S) -> V> = Rc::new(get);
let owner: Rc<dyn Fn(&S) -> PropertyOwner> = Rc::new(owner);
let step_get = get.clone();
let step_name = name.clone();
let owner_name = name.clone();
let step = PathStep {
name: name.clone(),
get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<S>(source, &step_name))) as Box<dyn Any>),
owner: Rc::new(move |source: &dyn Any| owner(step_input::<S>(source, &owner_name))),
};
Self { names: Rc::from(vec![name]), get, observe: Rc::new(observe), steps: Rc::from(vec![step]) }
}
pub fn value(name: impl Into<String>, get: impl Fn(&S) -> V + 'static) -> Self {
let name: String = name.into();
let get: Rc<dyn Fn(&S) -> V> = Rc::new(get);
let step_get = get.clone();
let step_name = name.clone();
let step = PathStep {
name: name.clone(),
get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<S>(source, &step_name))) as Box<dyn Any>),
owner: Rc::new(|_: &dyn Any| PropertyOwner::default()),
};
Self {
names: Rc::from(vec![name]),
get,
observe: Rc::new(|_: &S, _: Rc<dyn Fn()>| on_dispose(|| {})),
steps: Rc::from(vec![step]),
}
}
pub fn steps(&self) -> &[PathStep] {
&self.steps
}
pub fn names(&self) -> &[String] {
&self.names
}
pub fn path(&self) -> String {
self.names.join(".")
}
pub fn get(&self, source: &S) -> V {
(self.get)(source)
}
pub fn getter(&self) -> Rc<dyn Fn(&S) -> V> {
self.get.clone()
}
pub fn observe(&self, source: &S, changed: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
(self.observe)(source, changed)
}
pub fn then<W: 'static>(self, next: PropertyPath<V, W>) -> PropertyPath<S, W>
where
S: Clone,
V: Clone,
{
let names: Vec<String> = self.names.iter().chain(next.names.iter()).cloned().collect();
let steps: Vec<PathStep> = self.steps.iter().chain(next.steps.iter()).cloned().collect();
let (first_get, next_get) = (self.get.clone(), next.get.clone());
let (first, second) = (self, next);
PropertyPath {
names: Rc::from(names),
steps: Rc::from(steps),
get: Rc::new(move |source: &S| next_get(&first_get(source))),
observe: Rc::new(move |source: &S, changed: Rc<dyn Fn()>| {
let inner: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(Some(second.observe(&first.get(source), changed.clone()))));
let (source_for_change, inner_for_change, second_for_change, first_get) = (source.clone(), inner.clone(), second.clone(), first.get.clone());
let outer = first.observe(
source,
Rc::new(move || {
let next = second_for_change.observe(&first_get(&source_for_change), changed.clone());
let previous = inner_for_change.borrow_mut().replace(next);
if let Some(previous) = previous {
previous.dispose();
}
changed();
}),
);
on_dispose(move || {
outer.dispose();
let inner = inner.borrow_mut().take();
if let Some(inner) = inner {
inner.dispose();
}
})
}),
}
}
}
impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Rc<M>> {
pub fn then_property<W: 'static>(self, name: impl Into<String>, get: impl Fn(&Rc<M>) -> W + 'static) -> PropertyPath<S, W> {
self.then(PropertyPath::property(name, get))
}
}
impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Option<Rc<M>>> {
pub fn then_optional_property<W: 'static>(self, name: impl Into<String>, get: impl Fn(&Rc<M>) -> W + 'static) -> PropertyPath<S, Option<W>> {
self.then(PropertyPath::optional_property(name, get))
}
}
impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Option<Rc<M>>> {
pub fn then_optional_property_and_then<W: 'static>(self, name: impl Into<String>, get: impl Fn(&Rc<M>) -> Option<W> + 'static) -> PropertyPath<S, Option<W>> {
self.then(PropertyPath::optional_property_and_then(name, get))
}
}
#[macro_export]
macro_rules! prop {
(object $type_:ty => $first:ident $(as $name:literal)?) => {
$crate::component_model::PropertyPath::<::std::rc::Rc<$type_>, _>::object_property(
$crate::prop!(@name $first $($name)?),
|source: &::std::rc::Rc<$type_>| source.$first(),
)
};
($type_:ty => $first:ident $(as $name:literal)? $(. $rest:ident $(as $rest_name:literal)?)*) => {
$crate::component_model::PropertyPath::<::std::rc::Rc<$type_>, _>::property(
$crate::prop!(@name $first $($name)?),
|source: &::std::rc::Rc<$type_>| source.$first(),
)
$(.then_property(
$crate::prop!(@name $rest $($rest_name)?),
|source| source.$rest(),
))*
};
(@name $getter:ident $name:literal) => { $name };
(@name $getter:ident) => { $crate::component_model::property_name_of(::std::stringify!($getter)) };
}