1use super::INotifyPropertyChanged;
16use crate::IDisposable;
17use std::any::Any;
18use std::cell::RefCell;
19use std::fmt;
20use std::rc::Rc;
21
22type Observe<S> = Rc<dyn Fn(&S, Rc<dyn Fn()>) -> Rc<dyn IDisposable>>;
25
26pub struct PropertyPath<S, V> {
28 names: Rc<[String]>,
29 get: Rc<dyn Fn(&S) -> V>,
30 observe: Observe<S>,
31 steps: Rc<[PathStep]>,
32}
33
34impl<S, V> Clone for PropertyPath<S, V> {
35 fn clone(&self) -> Self {
36 Self { names: self.names.clone(), get: self.get.clone(), observe: self.observe.clone(), steps: self.steps.clone() }
37 }
38}
39
40#[derive(Clone, Default)]
48pub struct PropertyOwner {
49 pub object: Option<Rc<dyn Any>>,
51 pub notify: Option<Rc<dyn INotifyPropertyChanged>>,
53}
54
55impl PropertyOwner {
56 pub fn same_object(&self, other: &PropertyOwner) -> bool {
58 match (&self.object, &other.object) {
59 (Some(first), Some(second)) => Rc::ptr_eq(first, second),
60 (None, None) => match (&self.notify, &other.notify) {
61 (Some(first), Some(second)) => Rc::ptr_eq(first, second),
62 (None, None) => true,
63 _ => false,
64 },
65 _ => false,
66 }
67 }
68}
69
70impl fmt::Debug for PropertyOwner {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.debug_struct("PropertyOwner")
73 .field("object", &self.object.as_ref().map(|object| Rc::as_ptr(object) as *const ()))
74 .field("notify", &self.notify.is_some())
75 .finish()
76 }
77}
78
79#[derive(Clone)]
85pub struct PathStep {
86 pub name: String,
88 pub get: Rc<dyn Fn(&dyn Any) -> Box<dyn Any>>,
90 pub owner: Rc<dyn Fn(&dyn Any) -> PropertyOwner>,
92}
93
94impl fmt::Debug for PathStep {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.debug_struct("PathStep").field("name", &self.name).finish_non_exhaustive()
97 }
98}
99
100fn step_input<'a, T: 'static>(value: &'a dyn Any, name: &str) -> &'a T {
103 match value.downcast_ref::<T>() {
104 Some(value) => value,
105 None => panic!(
106 "the step {name} of a property path was given a value of another type than {}",
107 std::any::type_name::<T>()
108 ),
109 }
110}
111
112fn owner_of<M: INotifyPropertyChanged + 'static>(object: &Rc<M>) -> PropertyOwner {
114 PropertyOwner { object: Some(object.clone() as Rc<dyn Any>), notify: Some(object.clone() as Rc<dyn INotifyPropertyChanged>) }
115}
116
117fn observe_property<M: INotifyPropertyChanged + 'static>(source: &Rc<M>, watched: String, changed: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
120 let token = source.property_changed().add(Rc::new(move |property: &str| {
121 if property.is_empty() || property == watched {
122 changed();
123 }
124 }));
125 let source = source.clone();
126 on_dispose(move || {
127 source.property_changed().remove(token);
128 })
129}
130
131struct OnDispose(RefCell<Option<Box<dyn FnOnce()>>>);
133
134impl IDisposable for OnDispose {
135 fn dispose(&self) {
136 let end = self.0.borrow_mut().take();
137 if let Some(end) = end {
138 end();
139 }
140 }
141}
142
143fn on_dispose(end: impl FnOnce() + 'static) -> Rc<dyn IDisposable> {
144 Rc::new(OnDispose(RefCell::new(Some(Box::new(end)))))
145}
146
147pub fn property_name_of(getter: &str) -> String {
150 getter
151 .split('_')
152 .filter(|part| !part.is_empty())
153 .map(|part| {
154 let mut chars = part.chars();
155 match chars.next() {
156 Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
157 None => String::new(),
158 }
159 })
160 .collect()
161}
162
163impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Rc<M>, V> {
164 pub fn property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
169 let name: String = name.into();
170 let watched = name.clone();
171 let get: Rc<dyn Fn(&Rc<M>) -> V> = Rc::new(get);
172 let step_get = get.clone();
173 let step_name = name.clone();
174 let owner_name = name.clone();
175 let step = PathStep {
176 name: name.clone(),
177 get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<Rc<M>>(source, &step_name))) as Box<dyn Any>),
178 owner: Rc::new(move |source: &dyn Any| owner_of(step_input::<Rc<M>>(source, &owner_name))),
179 };
180 Self {
181 names: Rc::from(vec![name]),
182 get,
183 observe: Rc::new(move |source: &Rc<M>, changed: Rc<dyn Fn()>| observe_property(source, watched.clone(), changed)),
184 steps: Rc::from(vec![step]),
185 }
186 }
187}
188
189impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Option<Rc<M>>, Option<V>> {
190 pub fn optional_property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
196 let name: String = name.into();
197 let watched = name.clone();
198 let get: Rc<dyn Fn(&Rc<M>) -> V> = Rc::new(get);
199 let path_get = get.clone();
200 let step_name = name.clone();
201 let owner_name = name.clone();
202 let step = PathStep {
203 name: name.clone(),
204 get: Rc::new(move |source: &dyn Any| {
205 Box::new(step_input::<Option<Rc<M>>>(source, &step_name).as_ref().map(|object| get(object))) as Box<dyn Any>
206 }),
207 owner: Rc::new(move |source: &dyn Any| match step_input::<Option<Rc<M>>>(source, &owner_name) {
208 Some(object) => owner_of(object),
209 None => PropertyOwner::default(),
210 }),
211 };
212 Self {
213 names: Rc::from(vec![name]),
214 get: Rc::new(move |source: &Option<Rc<M>>| source.as_ref().map(|object| path_get(object))),
215 observe: Rc::new(move |source: &Option<Rc<M>>, changed: Rc<dyn Fn()>| match source {
216 Some(object) => observe_property(object, watched.clone(), changed),
217 None => on_dispose(|| {}),
218 }),
219 steps: Rc::from(vec![step]),
220 }
221 }
222}
223
224impl<M: INotifyPropertyChanged + 'static, V: 'static> PropertyPath<Option<Rc<M>>, Option<V>> {
225 pub fn optional_property_and_then(name: impl Into<String>, get: impl Fn(&Rc<M>) -> Option<V> + 'static) -> Self {
230 let name: String = name.into();
231 let watched = name.clone();
232 let get: Rc<dyn Fn(&Rc<M>) -> Option<V>> = Rc::new(get);
233 let path_get = get.clone();
234 let step_name = name.clone();
235 let owner_name = name.clone();
236 let step = PathStep {
237 name: name.clone(),
238 get: Rc::new(move |source: &dyn Any| {
239 Box::new(step_input::<Option<Rc<M>>>(source, &step_name).as_ref().and_then(|object| get(object))) as Box<dyn Any>
240 }),
241 owner: Rc::new(move |source: &dyn Any| match step_input::<Option<Rc<M>>>(source, &owner_name) {
242 Some(object) => owner_of(object),
243 None => PropertyOwner::default(),
244 }),
245 };
246 Self {
247 names: Rc::from(vec![name]),
248 get: Rc::new(move |source: &Option<Rc<M>>| source.as_ref().and_then(|object| path_get(object))),
249 observe: Rc::new(move |source: &Option<Rc<M>>, changed: Rc<dyn Fn()>| match source {
250 Some(object) => observe_property(object, watched.clone(), changed),
251 None => on_dispose(|| {}),
252 }),
253 steps: Rc::from(vec![step]),
254 }
255 }
256}
257
258impl<M: 'static, V: 'static> PropertyPath<Rc<M>, V> {
259 pub fn object_property(name: impl Into<String>, get: impl Fn(&Rc<M>) -> V + 'static) -> Self {
267 Self::owned_property(
268 name,
269 get,
270 |source: &Rc<M>| PropertyOwner { object: Some(source.clone() as Rc<dyn Any>), notify: None },
271 |_: &Rc<M>, _: Rc<dyn Fn()>| on_dispose(|| {}),
272 )
273 }
274}
275
276impl<S: 'static, V: 'static> PropertyPath<S, V> {
277 pub fn owned_property(
285 name: impl Into<String>,
286 get: impl Fn(&S) -> V + 'static,
287 owner: impl Fn(&S) -> PropertyOwner + 'static,
288 observe: impl Fn(&S, Rc<dyn Fn()>) -> Rc<dyn IDisposable> + 'static,
289 ) -> Self {
290 let name: String = name.into();
291 let get: Rc<dyn Fn(&S) -> V> = Rc::new(get);
292 let owner: Rc<dyn Fn(&S) -> PropertyOwner> = Rc::new(owner);
293 let step_get = get.clone();
294 let step_name = name.clone();
295 let owner_name = name.clone();
296 let step = PathStep {
297 name: name.clone(),
298 get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<S>(source, &step_name))) as Box<dyn Any>),
299 owner: Rc::new(move |source: &dyn Any| owner(step_input::<S>(source, &owner_name))),
300 };
301 Self { names: Rc::from(vec![name]), get, observe: Rc::new(observe), steps: Rc::from(vec![step]) }
302 }
303
304 pub fn value(name: impl Into<String>, get: impl Fn(&S) -> V + 'static) -> Self {
310 let name: String = name.into();
311 let get: Rc<dyn Fn(&S) -> V> = Rc::new(get);
312 let step_get = get.clone();
313 let step_name = name.clone();
314 let step = PathStep {
315 name: name.clone(),
316 get: Rc::new(move |source: &dyn Any| Box::new(step_get(step_input::<S>(source, &step_name))) as Box<dyn Any>),
317 owner: Rc::new(|_: &dyn Any| PropertyOwner::default()),
318 };
319 Self {
320 names: Rc::from(vec![name]),
321 get,
322 observe: Rc::new(|_: &S, _: Rc<dyn Fn()>| on_dispose(|| {})),
323 steps: Rc::from(vec![step]),
324 }
325 }
326
327 pub fn steps(&self) -> &[PathStep] {
329 &self.steps
330 }
331
332 pub fn names(&self) -> &[String] {
334 &self.names
335 }
336
337 pub fn path(&self) -> String {
339 self.names.join(".")
340 }
341
342 pub fn get(&self, source: &S) -> V {
344 (self.get)(source)
345 }
346
347 pub fn getter(&self) -> Rc<dyn Fn(&S) -> V> {
349 self.get.clone()
350 }
351
352 pub fn observe(&self, source: &S, changed: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
355 (self.observe)(source, changed)
356 }
357
358 pub fn then<W: 'static>(self, next: PropertyPath<V, W>) -> PropertyPath<S, W>
361 where
362 S: Clone,
363 V: Clone,
364 {
365 let names: Vec<String> = self.names.iter().chain(next.names.iter()).cloned().collect();
366 let steps: Vec<PathStep> = self.steps.iter().chain(next.steps.iter()).cloned().collect();
367 let (first_get, next_get) = (self.get.clone(), next.get.clone());
368 let (first, second) = (self, next);
369 PropertyPath {
370 names: Rc::from(names),
371 steps: Rc::from(steps),
372 get: Rc::new(move |source: &S| next_get(&first_get(source))),
373 observe: Rc::new(move |source: &S, changed: Rc<dyn Fn()>| {
374 let inner: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(Some(second.observe(&first.get(source), changed.clone()))));
375 let (source_for_change, inner_for_change, second_for_change, first_get) = (source.clone(), inner.clone(), second.clone(), first.get.clone());
376 let outer = first.observe(
377 source,
378 Rc::new(move || {
379 let next = second_for_change.observe(&first_get(&source_for_change), changed.clone());
380 let previous = inner_for_change.borrow_mut().replace(next);
381 if let Some(previous) = previous {
382 previous.dispose();
383 }
384 changed();
385 }),
386 );
387 on_dispose(move || {
388 outer.dispose();
389 let inner = inner.borrow_mut().take();
390 if let Some(inner) = inner {
391 inner.dispose();
392 }
393 })
394 }),
395 }
396 }
397}
398
399impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Rc<M>> {
400 pub fn then_property<W: 'static>(self, name: impl Into<String>, get: impl Fn(&Rc<M>) -> W + 'static) -> PropertyPath<S, W> {
403 self.then(PropertyPath::property(name, get))
404 }
405}
406
407impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Option<Rc<M>>> {
408 pub fn then_optional_property<W: 'static>(self, name: impl Into<String>, get: impl Fn(&Rc<M>) -> W + 'static) -> PropertyPath<S, Option<W>> {
411 self.then(PropertyPath::optional_property(name, get))
412 }
413}
414
415impl<S: Clone + 'static, M: INotifyPropertyChanged + 'static> PropertyPath<S, Option<Rc<M>>> {
416 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>> {
420 self.then(PropertyPath::optional_property_and_then(name, get))
421 }
422}
423
424#[macro_export]
454macro_rules! prop {
455 (object $type_:ty => $first:ident $(as $name:literal)?) => {
456 $crate::component_model::PropertyPath::<::std::rc::Rc<$type_>, _>::object_property(
457 $crate::prop!(@name $first $($name)?),
458 |source: &::std::rc::Rc<$type_>| source.$first(),
459 )
460 };
461 ($type_:ty => $first:ident $(as $name:literal)? $(. $rest:ident $(as $rest_name:literal)?)*) => {
462 $crate::component_model::PropertyPath::<::std::rc::Rc<$type_>, _>::property(
463 $crate::prop!(@name $first $($name)?),
464 |source: &::std::rc::Rc<$type_>| source.$first(),
465 )
466 $(.then_property(
467 $crate::prop!(@name $rest $($rest_name)?),
468 |source| source.$rest(),
469 ))*
470 };
471 (@name $getter:ident $name:literal) => { $name };
472 (@name $getter:ident) => { $crate::component_model::property_name_of(::std::stringify!($getter)) };
473}