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