raui_core/
view_model.rs

1use crate::widget::{WidgetId, WidgetIdCommon};
2use intuicio_data::{
3    lifetime::{ValueReadAccess, ValueWriteAccess},
4    managed::DynamicManaged,
5    managed::{Managed, ManagedLazy, ManagedRef, ManagedRefMut},
6};
7use std::{
8    collections::{HashMap, HashSet},
9    ops::{Deref, DerefMut},
10};
11
12pub struct ViewModelBindings {
13    widgets: HashSet<WidgetId>,
14    common_root: WidgetIdCommon,
15    notify: bool,
16}
17
18impl Default for ViewModelBindings {
19    fn default() -> Self {
20        Self {
21            widgets: Default::default(),
22            common_root: Default::default(),
23            notify: true,
24        }
25    }
26}
27
28impl ViewModelBindings {
29    pub fn bind(&mut self, id: WidgetId) {
30        self.widgets.insert(id);
31        self.rebuild_common_root();
32    }
33
34    pub fn unbind(&mut self, id: &WidgetId) {
35        self.widgets.remove(id);
36        self.rebuild_common_root();
37    }
38
39    pub fn clear(&mut self) {
40        self.widgets.clear();
41        self.common_root = Default::default();
42    }
43
44    pub fn is_empty(&self) -> bool {
45        self.widgets.is_empty()
46    }
47
48    pub fn is_bound(&self, id: &WidgetId) -> bool {
49        self.widgets.contains(id)
50    }
51
52    pub fn widgets(&self) -> impl Iterator<Item = &WidgetId> {
53        self.widgets.iter()
54    }
55
56    pub fn common_root(&self) -> &WidgetIdCommon {
57        &self.common_root
58    }
59
60    pub fn notify(&mut self) {
61        self.notify = true;
62    }
63
64    pub fn is_notified(&self) -> bool {
65        self.notify
66    }
67
68    pub fn consume_notification(&mut self) -> bool {
69        !self.widgets.is_empty() && std::mem::take(&mut self.notify)
70    }
71
72    fn rebuild_common_root(&mut self) {
73        self.common_root = WidgetIdCommon::from_iter(self.widgets.iter());
74    }
75}
76
77#[derive(Default)]
78pub struct ViewModelProperties {
79    inner: HashMap<String, Managed<ViewModelBindings>>,
80}
81
82impl ViewModelProperties {
83    pub fn unbind_all(&mut self, id: &WidgetId) {
84        for bindings in self.inner.values_mut() {
85            if let Some(mut bindings) = bindings.write() {
86                bindings.unbind(id);
87            }
88        }
89    }
90
91    pub fn remove(&mut self, id: &str) {
92        self.inner.remove(id);
93    }
94
95    pub fn clear(&mut self) {
96        self.inner.clear();
97    }
98
99    pub fn is_empty(&self) -> bool {
100        self.inner.is_empty()
101    }
102
103    pub fn has(&self, id: &str) -> bool {
104        self.inner.contains_key(id)
105    }
106
107    pub fn remove_empty_bindings(&mut self) {
108        let to_remove = self
109            .inner
110            .iter()
111            .filter_map(|(key, bindings)| {
112                if let Some(bindings) = bindings.read() {
113                    if bindings.is_empty() {
114                        return Some(key.to_owned());
115                    }
116                }
117                None
118            })
119            .collect::<Vec<_>>();
120        for key in to_remove {
121            self.inner.remove(&key);
122        }
123    }
124
125    pub fn bindings(&mut self, id: impl ToString) -> Option<ValueWriteAccess<ViewModelBindings>> {
126        self.inner.entry(id.to_string()).or_default().write()
127    }
128
129    pub fn notifier(&mut self, id: impl ToString) -> ViewModelNotifier {
130        ViewModelNotifier {
131            inner: self.inner.entry(id.to_string()).or_default().lazy(),
132        }
133    }
134
135    pub fn consume_notification(&mut self) -> bool {
136        self.inner.values_mut().any(|bindings| {
137            bindings
138                .write()
139                .map(|mut bindings| bindings.consume_notification())
140                .unwrap_or_default()
141        })
142    }
143
144    pub fn consume_notified_common_root(&mut self) -> WidgetIdCommon {
145        let mut result = WidgetIdCommon::default();
146        for bindings in self.inner.values_mut() {
147            if let Some(mut bindings) = bindings.write() {
148                if bindings.consume_notification() {
149                    let root = bindings.common_root();
150                    result.include_other(root);
151                }
152            }
153        }
154        result
155    }
156}
157
158#[derive(Clone)]
159pub struct ViewModelNotifier {
160    inner: ManagedLazy<ViewModelBindings>,
161}
162
163impl ViewModelNotifier {
164    pub fn notify(&mut self) -> bool {
165        if let Some(mut bindings) = self.inner.write() {
166            bindings.notify();
167            true
168        } else {
169            false
170        }
171    }
172}
173
174pub struct ViewModel {
175    object: DynamicManaged,
176    pub properties: ViewModelProperties,
177}
178
179impl ViewModel {
180    pub fn new<T: 'static>(object: T, properties: ViewModelProperties) -> Self {
181        Self {
182            object: DynamicManaged::new(object).ok().unwrap(),
183            properties,
184        }
185    }
186
187    pub fn new_object<T: 'static>(object: T) -> Self {
188        Self::new(object, Default::default())
189    }
190
191    pub fn produce<T: 'static>(producer: impl FnOnce(&mut ViewModelProperties) -> T) -> Self {
192        let mut properties = Default::default();
193        let object = DynamicManaged::new(producer(&mut properties)).ok().unwrap();
194        Self { object, properties }
195    }
196
197    pub fn borrow<T: 'static>(&self) -> Option<ManagedRef<T>> {
198        self.object
199            .borrow()
200            .and_then(|object| object.into_typed::<T>().ok())
201    }
202
203    pub fn borrow_mut<T: 'static>(&mut self) -> Option<ManagedRefMut<T>> {
204        self.object
205            .borrow_mut()
206            .and_then(|object| object.into_typed::<T>().ok())
207    }
208
209    pub fn lazy<T: 'static>(&self) -> Option<ManagedLazy<T>> {
210        self.object.lazy().into_typed::<T>().ok()
211    }
212
213    pub fn read<T: 'static>(&self) -> Option<ValueReadAccess<T>> {
214        self.object.read::<T>()
215    }
216
217    pub fn write<T: 'static>(&mut self) -> Option<ValueWriteAccess<T>> {
218        self.object.write::<T>()
219    }
220
221    pub fn write_notified<T: 'static>(&mut self) -> Option<ViewModelObject<T>> {
222        if let Some(access) = self.object.write::<T>() {
223            Some(ViewModelObject {
224                access,
225                notifier: self.properties.notifier(""),
226            })
227        } else {
228            None
229        }
230    }
231}
232
233#[derive(Default)]
234pub struct ViewModelCollection {
235    named: HashMap<String, ViewModel>,
236    widgets: HashMap<WidgetId, HashMap<String, ViewModel>>,
237}
238
239impl ViewModelCollection {
240    pub fn unbind_all(&mut self, id: &WidgetId) {
241        for view_model in self.named.values_mut() {
242            view_model.properties.unbind_all(id);
243        }
244        for view_model in self.widgets.values_mut() {
245            for view_model in view_model.values_mut() {
246                view_model.properties.unbind_all(id);
247            }
248        }
249    }
250
251    pub fn remove_empty_bindings(&mut self) {
252        for view_model in self.named.values_mut() {
253            view_model.properties.remove_empty_bindings();
254        }
255        for view_model in self.widgets.values_mut() {
256            for view_model in view_model.values_mut() {
257                view_model.properties.remove_empty_bindings();
258            }
259        }
260    }
261
262    pub fn consume_notification(&mut self) -> bool {
263        let mut result = false;
264        for view_model in self.named.values_mut() {
265            result = result || view_model.properties.consume_notification();
266        }
267        for view_model in self.widgets.values_mut() {
268            for view_model in view_model.values_mut() {
269                result = result || view_model.properties.consume_notification();
270            }
271        }
272        result
273    }
274
275    pub fn consume_notified_common_root(&mut self) -> WidgetIdCommon {
276        let mut result = WidgetIdCommon::default();
277        for view_model in self.named.values_mut() {
278            result.include_other(&view_model.properties.consume_notified_common_root());
279        }
280        for view_model in self.widgets.values_mut() {
281            for view_model in view_model.values_mut() {
282                result.include_other(&view_model.properties.consume_notified_common_root());
283            }
284        }
285        result
286    }
287
288    pub fn remove_widget_view_models(&mut self, id: &WidgetId) {
289        self.widgets.remove(id);
290    }
291}
292
293impl Deref for ViewModelCollection {
294    type Target = HashMap<String, ViewModel>;
295
296    fn deref(&self) -> &Self::Target {
297        &self.named
298    }
299}
300
301impl DerefMut for ViewModelCollection {
302    fn deref_mut(&mut self) -> &mut Self::Target {
303        &mut self.named
304    }
305}
306
307pub struct ViewModelCollectionView<'a> {
308    id: &'a WidgetId,
309    collection: &'a mut ViewModelCollection,
310}
311
312impl<'a> ViewModelCollectionView<'a> {
313    pub fn new(id: &'a WidgetId, collection: &'a mut ViewModelCollection) -> Self {
314        Self { id, collection }
315    }
316
317    pub fn id(&self) -> &WidgetId {
318        self.id
319    }
320
321    pub fn collection(&'a self) -> &'a ViewModelCollection {
322        self.collection
323    }
324
325    pub fn collection_mut(&'a mut self) -> &'a mut ViewModelCollection {
326        self.collection
327    }
328
329    pub fn bindings(
330        &mut self,
331        view_model: &str,
332        property: impl ToString,
333    ) -> Option<ValueWriteAccess<ViewModelBindings>> {
334        self.collection
335            .get_mut(view_model)?
336            .properties
337            .bindings(property)
338    }
339
340    pub fn view_model(&self, name: &str) -> Option<&ViewModel> {
341        self.collection.get(name)
342    }
343
344    pub fn view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {
345        self.collection.get_mut(name)
346    }
347
348    pub fn widget_register(&mut self, name: impl ToString, view_model: ViewModel) {
349        self.collection
350            .widgets
351            .entry(self.id.to_owned())
352            .or_default()
353            .insert(name.to_string(), view_model);
354    }
355
356    pub fn widget_unregister(&mut self, name: &str) -> Option<ViewModel> {
357        let view_models = self.collection.widgets.get_mut(self.id)?;
358        let result = view_models.remove(name)?;
359        if view_models.is_empty() {
360            self.collection.widgets.remove(self.id);
361        }
362        Some(result)
363    }
364
365    pub fn widget_bindings(
366        &mut self,
367        view_model: &str,
368        property: impl ToString,
369    ) -> Option<ValueWriteAccess<ViewModelBindings>> {
370        self.collection
371            .widgets
372            .get_mut(self.id)?
373            .get_mut(view_model)?
374            .properties
375            .bindings(property)
376    }
377
378    pub fn widget_view_model(&self, name: &str) -> Option<&ViewModel> {
379        self.collection.widgets.get(self.id)?.get(name)
380    }
381
382    pub fn widget_view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {
383        self.collection.widgets.get_mut(self.id)?.get_mut(name)
384    }
385
386    pub fn hierarchy_view_model(&self, name: &str) -> Option<&ViewModel> {
387        self.collection
388            .widgets
389            .iter()
390            .filter_map(|(id, view_models)| {
391                id.distance_to(self.id).ok().and_then(|distance| {
392                    if distance <= 0 {
393                        Some((distance, view_models.get(name)?))
394                    } else {
395                        None
396                    }
397                })
398            })
399            .min_by(|(a, _), (b, _)| a.cmp(b))
400            .map(|(_, view_model)| view_model)
401    }
402
403    pub fn hierarchy_view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {
404        self.collection
405            .widgets
406            .iter_mut()
407            .filter_map(|(id, view_models)| {
408                id.distance_to(self.id).ok().and_then(|distance| {
409                    if distance <= 0 {
410                        Some((distance, view_models.get_mut(name)?))
411                    } else {
412                        None
413                    }
414                })
415            })
416            .min_by(|(a, _), (b, _)| a.cmp(b))
417            .map(|(_, view_model)| view_model)
418    }
419}
420
421pub struct ViewModelObject<'a, T> {
422    access: ValueWriteAccess<'a, T>,
423    notifier: ViewModelNotifier,
424}
425
426impl<T> ViewModelObject<'_, T> {
427    pub fn set_unique_notify(&mut self, value: T)
428    where
429        T: PartialEq,
430    {
431        if *self.access != value {
432            *self.access = value;
433            self.notifier.notify();
434        }
435    }
436}
437
438impl<T> Deref for ViewModelObject<'_, T> {
439    type Target = T;
440
441    fn deref(&self) -> &Self::Target {
442        &self.access
443    }
444}
445
446impl<T> DerefMut for ViewModelObject<'_, T> {
447    fn deref_mut(&mut self) -> &mut Self::Target {
448        self.notifier.notify();
449        &mut self.access
450    }
451}
452
453pub struct ViewModelValue<T> {
454    value: T,
455    notifier: ViewModelNotifier,
456}
457
458impl<T> ViewModelValue<T> {
459    pub fn new(value: T, notifier: ViewModelNotifier) -> Self {
460        Self { value, notifier }
461    }
462
463    pub fn consume(self) -> T {
464        self.value
465    }
466
467    pub fn set_unique_notify(&mut self, value: T)
468    where
469        T: PartialEq,
470    {
471        if self.value != value {
472            self.value = value;
473            self.notifier.notify();
474        }
475    }
476}
477
478impl<T> Deref for ViewModelValue<T> {
479    type Target = T;
480
481    fn deref(&self) -> &Self::Target {
482        &self.value
483    }
484}
485
486impl<T> DerefMut for ViewModelValue<T> {
487    fn deref_mut(&mut self) -> &mut Self::Target {
488        self.notifier.notify();
489        &mut self.value
490    }
491}
492
493impl<T> std::fmt::Display for ViewModelValue<T>
494where
495    T: std::fmt::Display,
496{
497    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
498        write!(f, "{}", self.value)
499    }
500}
501
502impl<T> std::fmt::Debug for ViewModelValue<T>
503where
504    T: std::fmt::Debug,
505{
506    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507        f.debug_struct("ViewModelValue")
508            .field("value", &self.value)
509            .finish()
510    }
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use std::str::FromStr;
517
518    const FOO_VIEW_MODEL: &str = "foo";
519    const COUNTER_PROPERTY: &str = "counter";
520    const FLAG_PROPERTY: &str = "flag";
521
522    // view-model data type
523    struct Foo {
524        // can hold view-model value wrapper that implicitly notifies on mutation.
525        counter: ViewModelValue<usize>,
526        // or can hold raw notifiers to explicitly notify.
527        flag: bool,
528        flag_notifier: ViewModelNotifier,
529    }
530
531    impl Foo {
532        fn toggle(&mut self) {
533            self.flag = !self.flag;
534            self.flag_notifier.notify();
535        }
536    }
537
538    #[test]
539    fn test_view_model() {
540        let a = WidgetId::from_str("a:root/a").unwrap();
541        let b = WidgetId::from_str("b:root/b").unwrap();
542        let mut collection = ViewModelCollection::default();
543
544        // create new view-model and add it to collection.
545        // `produce` method allows to setup notifiers as we construct view-model.
546        let view_model = ViewModel::produce(|properties| Foo {
547            counter: ViewModelValue::new(0, properties.notifier(COUNTER_PROPERTY)),
548            flag: false,
549            flag_notifier: properties.notifier(FLAG_PROPERTY),
550        });
551        // handle to view-model data we can use to share around.
552        // it stays alive as long as its view-model object.
553        let handle = view_model.lazy::<Foo>().unwrap();
554        collection.insert(FOO_VIEW_MODEL.to_owned(), view_model);
555
556        // unbound properties won't trigger notification until we bind widgets to them.
557        assert!(!collection.consume_notified_common_root().is_valid());
558        handle.write().unwrap().toggle();
559        assert!(!collection.consume_notified_common_root().is_valid());
560        assert!(
561            collection
562                .get_mut(FOO_VIEW_MODEL)
563                .unwrap()
564                .properties
565                .bindings(COUNTER_PROPERTY)
566                .unwrap()
567                .is_notified()
568        );
569        assert!(
570            collection
571                .get_mut(FOO_VIEW_MODEL)
572                .unwrap()
573                .properties
574                .bindings(FLAG_PROPERTY)
575                .unwrap()
576                .is_notified()
577        );
578
579        // bind widget to properties.
580        // whenever property gets notified, its widgets will rebuild.
581        collection
582            .get_mut(FOO_VIEW_MODEL)
583            .unwrap()
584            .properties
585            .bindings(COUNTER_PROPERTY)
586            .unwrap()
587            .bind(a);
588        collection
589            .get_mut(FOO_VIEW_MODEL)
590            .unwrap()
591            .properties
592            .bindings(FLAG_PROPERTY)
593            .unwrap()
594            .bind(b);
595
596        // once we bind properties, notification will be triggered.
597        assert_eq!(
598            collection.consume_notified_common_root().path(),
599            Some("root")
600        );
601
602        // automatically notify on view-model value mutation.
603        *handle.write().unwrap().counter += 1;
604        assert_eq!(
605            collection.consume_notified_common_root().path(),
606            Some("root/a"),
607        );
608
609        // proxy notify via view-model method call.
610        handle.write().unwrap().toggle();
611        assert_eq!(
612            collection.consume_notified_common_root().path(),
613            Some("root/b"),
614        );
615
616        // rebuilding widgets tree will occur always from common root of notified widgets.
617        *handle.write().unwrap().counter += 1;
618        handle.write().unwrap().toggle();
619        assert_eq!(
620            collection.consume_notified_common_root().path(),
621            Some("root"),
622        );
623    }
624}