Skip to main content

ferroui_base/data/model/
i_notify_collection_changed.rs

1use super::Event;
2use crate::collections::{FerroList, NotifyCollectionChangedEventArgs};
3pub use ferroui_system::collections::specialized::{CollectionChange, INotifyCollectionChanged};
4use crate::data::core::ValueType;
5use crate::{BoxedValue, PropertyValue};
6use std::cell::OnceCell;
7use std::rc::Rc;
8
9/// Untyped indexed access to a list, for bindings with an indexer in their
10/// path (`Items[2]`).
11pub trait IBindableList {
12    /// The number of items.
13    fn count(&self) -> usize;
14
15    /// The type of the items.
16    fn item_type(&self) -> ValueType {
17        ValueType::object()
18    }
19
20    /// The item at `index` as an untyped value, or `None` if out of range.
21    fn get_item(&self, index: usize) -> Option<BoxedValue>;
22
23    /// Replaces the item at `index`. Returns false if the index is out of
24    /// range, the list is read-only or the value is of the wrong type.
25    fn set_item(&self, index: usize, value: &BoxedValue) -> bool;
26
27    /// The change notifications of the list, if it is a notifying list.
28    fn as_notify_collection_changed(&self) -> Option<&dyn INotifyCollectionChanged>;
29}
30
31/// A notifying list that bindings can index into: a [`FerroList`] together
32/// with the untyped change event that binding indexers subscribe to.
33///
34/// As a model object it has identity equality.
35pub struct BindableList<T: PropertyValue> {
36    items: FerroList<T>,
37    changed: Event<CollectionChange>,
38    forwarder: OnceCell<u64>,
39}
40
41impl<T: PropertyValue> BindableList<T> {
42    /// Creates a list with the given items.
43    pub fn new(items: impl IntoIterator<Item = T>) -> Rc<Self> {
44        let this = Rc::new(Self { items: FerroList::from_items(items), changed: Event::new(), forwarder: OnceCell::new() });
45        let weak = Rc::downgrade(&this);
46        let token = this.items.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, T>| {
47            if let Some(this) = weak.upgrade() {
48                this.changed.raise(&CollectionChange {
49                    action: e.action,
50                    new_starting_index: e.new_starting_index,
51                    new_count: e.new_items.len(),
52                    old_starting_index: e.old_starting_index,
53                    old_count: e.old_items.len(),
54                });
55            }
56        }));
57        let _ = this.forwarder.set(token);
58        super::ModelTypes::register_list::<Self>();
59        this
60    }
61
62    /// The typed list.
63    pub fn items(&self) -> &FerroList<T> {
64        &self.items
65    }
66}
67
68impl<T: PropertyValue> PartialEq for BindableList<T> {
69    fn eq(&self, other: &Self) -> bool {
70        std::ptr::eq(self, other)
71    }
72}
73
74impl<T: PropertyValue> INotifyCollectionChanged for BindableList<T> {
75    fn collection_changed(&self) -> &Event<CollectionChange> {
76        &self.changed
77    }
78}
79
80impl<T: PropertyValue> IBindableList for BindableList<T> {
81    fn count(&self) -> usize {
82        self.items.count()
83    }
84
85    fn item_type(&self) -> ValueType {
86        ValueType::of::<T>()
87    }
88
89    fn get_item(&self, index: usize) -> Option<BoxedValue> {
90        self.items.try_get(index).map(|v| Rc::new(v) as BoxedValue)
91    }
92
93    fn set_item(&self, index: usize, value: &BoxedValue) -> bool {
94        match value.downcast_ref::<T>() {
95            Some(v) if index < self.items.count() => {
96                self.items.set(index, v.clone());
97                true
98            }
99            _ => false,
100        }
101    }
102
103    fn as_notify_collection_changed(&self) -> Option<&dyn INotifyCollectionChanged> {
104        Some(self)
105    }
106}