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
493#[cfg(test)]
494mod tests {
495    use super::*;
496    use std::str::FromStr;
497
498    const FOO_VIEW_MODEL: &str = "foo";
499    const COUNTER_PROPERTY: &str = "counter";
500    const FLAG_PROPERTY: &str = "flag";
501
502    // view-model data type
503    struct Foo {
504        // can hold view-model value wrapper that implicitly notifies on mutation.
505        counter: ViewModelValue<usize>,
506        // or can hold raw notifiers to explicitly notify.
507        flag: bool,
508        flag_notifier: ViewModelNotifier,
509    }
510
511    impl Foo {
512        fn toggle(&mut self) {
513            self.flag = !self.flag;
514            self.flag_notifier.notify();
515        }
516    }
517
518    #[test]
519    fn test_view_model() {
520        let a = WidgetId::from_str("a:root/a").unwrap();
521        let b = WidgetId::from_str("b:root/b").unwrap();
522        let mut collection = ViewModelCollection::default();
523
524        // create new view-model and add it to collection.
525        // `produce` method allows to setup notifiers as we construct view-model.
526        let view_model = ViewModel::produce(|properties| Foo {
527            counter: ViewModelValue::new(0, properties.notifier(COUNTER_PROPERTY)),
528            flag: false,
529            flag_notifier: properties.notifier(FLAG_PROPERTY),
530        });
531        // handle to view-model data we can use to share around.
532        // it stays alive as long as its view-model object.
533        let handle = view_model.lazy::<Foo>().unwrap();
534        collection.insert(FOO_VIEW_MODEL.to_owned(), view_model);
535
536        // unbound properties won't trigger notification until we bind widgets to them.
537        assert!(!collection.consume_notified_common_root().is_valid());
538        handle.write().unwrap().toggle();
539        assert!(!collection.consume_notified_common_root().is_valid());
540        assert!(collection
541            .get_mut(FOO_VIEW_MODEL)
542            .unwrap()
543            .properties
544            .bindings(COUNTER_PROPERTY)
545            .unwrap()
546            .is_notified());
547        assert!(collection
548            .get_mut(FOO_VIEW_MODEL)
549            .unwrap()
550            .properties
551            .bindings(FLAG_PROPERTY)
552            .unwrap()
553            .is_notified());
554
555        // bind widget to properties.
556        // whenever property gets notified, its widgets will rebuild.
557        collection
558            .get_mut(FOO_VIEW_MODEL)
559            .unwrap()
560            .properties
561            .bindings(COUNTER_PROPERTY)
562            .unwrap()
563            .bind(a);
564        collection
565            .get_mut(FOO_VIEW_MODEL)
566            .unwrap()
567            .properties
568            .bindings(FLAG_PROPERTY)
569            .unwrap()
570            .bind(b);
571
572        // once we bind properties, notification will be triggered.
573        assert_eq!(
574            collection.consume_notified_common_root().path(),
575            Some("root")
576        );
577
578        // automatically notify on view-model value mutation.
579        *handle.write().unwrap().counter += 1;
580        assert_eq!(
581            collection.consume_notified_common_root().path(),
582            Some("root/a"),
583        );
584
585        // proxy notify via view-model method call.
586        handle.write().unwrap().toggle();
587        assert_eq!(
588            collection.consume_notified_common_root().path(),
589            Some("root/b"),
590        );
591
592        // rebuilding widgets tree will occur always from common root of notified widgets.
593        *handle.write().unwrap().counter += 1;
594        handle.write().unwrap().toggle();
595        assert_eq!(
596            collection.consume_notified_common_root().path(),
597            Some("root"),
598        );
599    }
600}