Skip to main content

ferroui_base/data/model/
i_bindable_indexer.rs

1use super::{CollectionChange, Event, INotifyCollectionChanged, INotifyPropertyChanged, ModelTypes};
2use crate::collections::{FerroDictionary, NotifyCollectionChangedEventArgs};
3use crate::data::core::{PropertyKind, Value, ValueType};
4use crate::data::BindingError;
5use crate::{BoxedValue, PropertyValue};
6use std::any::TypeId;
7use std::cell::{OnceCell, RefCell};
8use std::fmt::Display;
9use std::hash::Hash;
10use std::rc::Rc;
11
12/// The untyped form of an indexer (`this[..]`), for bindings with an indexer
13/// in their path (`Items[key]`, `Grid[1,2]`).
14///
15/// A model type declares its indexer with
16/// [`ModelTypeBuilder::indexer`](super::ModelTypeBuilder::indexer). The
17/// binding converts the textual arguments of the path to the declared
18/// parameter types before calling [`get`](Self::get) or [`set`](Self::set).
19///
20/// A binding re-reads the indexer when the object raises a collection change
21/// notification, or a property change notification for the indexer
22/// ([`INDEXER_NAME`](crate::data::core::INDEXER_NAME)).
23pub trait IBindableIndexer {
24    /// The types of the index parameters, in order.
25    fn index_parameter_types(&self) -> Vec<ValueType>;
26
27    /// The type of the values of the indexer.
28    fn value_type(&self) -> ValueType;
29
30    /// Whether the indexer has a setter.
31    fn can_set(&self) -> bool {
32        true
33    }
34
35    /// Reads the value at `arguments`, which hold exactly the declared
36    /// parameter types. An error is what the getter of a managed indexer
37    /// throws: a missing key, an index out of range.
38    fn get(&self, arguments: &[BoxedValue]) -> Result<Option<BoxedValue>, BindingError>;
39
40    /// Writes the value at `arguments`. Returns `Ok(false)` if the indexer
41    /// has no setter.
42    fn set(&self, arguments: &[BoxedValue], value: Option<&BoxedValue>) -> Result<bool, BindingError>;
43}
44
45/// Untyped access to an array: a fixed-size collection accessed with integer
46/// indexes, one per dimension.
47pub trait IBindableArray {
48    /// The number of dimensions.
49    fn rank(&self) -> usize;
50
51    /// The type of the elements.
52    fn element_type(&self) -> ValueType;
53
54    /// The element at `indexes`.
55    fn get_value(&self, indexes: &[i32]) -> Result<Option<BoxedValue>, BindingError>;
56
57    /// Replaces the element at `indexes`.
58    fn set_value(&self, indexes: &[i32], value: Option<&BoxedValue>) -> Result<(), BindingError>;
59}
60
61fn invalid_value(value: Option<&BoxedValue>, target: ValueType) -> BindingError {
62    BindingError::message(format!(
63        "Object of type '{}' cannot be converted to type '{}'.",
64        value.map_or("null", |v| (**v).type_name()),
65        target
66    ))
67}
68
69fn argument<'a, T: 'static>(arguments: &'a [BoxedValue], index: usize) -> Result<&'a T, BindingError> {
70    arguments.get(index).and_then(|a| a.downcast_ref::<T>()).ok_or_else(|| {
71        BindingError::message(format!(
72            "Object of type '{}' cannot be converted to type '{}'.",
73            arguments.get(index).map_or("null", |v| (**v).type_name()),
74            std::any::type_name::<T>()
75        ))
76    })
77}
78
79/// An array that bindings can index into: one or more dimensions of fixed
80/// lengths, stored in row-major order.
81///
82/// As a model object it has identity equality.
83pub struct BindableArray<T: PropertyValue> {
84    items: RefCell<Vec<T>>,
85    lengths: Vec<usize>,
86}
87
88impl<T: PropertyValue> BindableArray<T> {
89    /// Creates a one-dimensional array.
90    pub fn new(items: impl IntoIterator<Item = T>) -> Rc<Self> {
91        let items: Vec<T> = items.into_iter().collect();
92        let lengths = vec![items.len()];
93        Self::create(items, lengths)
94    }
95
96    /// Creates an array with the given length of each dimension, from its
97    /// elements in row-major order. Panics if the number of elements is not
98    /// the product of the lengths.
99    pub fn with_lengths(lengths: &[usize], items: impl IntoIterator<Item = T>) -> Rc<Self> {
100        let items: Vec<T> = items.into_iter().collect();
101        if lengths.is_empty() || lengths.iter().product::<usize>() != items.len() {
102            panic!("The number of elements does not match the lengths of the array.");
103        }
104        Self::create(items, lengths.to_vec())
105    }
106
107    fn create(items: Vec<T>, lengths: Vec<usize>) -> Rc<Self> {
108        if !ModelTypes::is_registered(TypeId::of::<Self>()) {
109            ModelTypes::register::<Self>(|b| b.array().indexer());
110        }
111        Rc::new(Self { items: RefCell::new(items), lengths })
112    }
113
114    /// The length of each dimension.
115    pub fn lengths(&self) -> &[usize] {
116        &self.lengths
117    }
118
119    /// The total number of elements.
120    pub fn len(&self) -> usize {
121        self.items.borrow().len()
122    }
123
124    pub fn is_empty(&self) -> bool {
125        self.len() == 0
126    }
127
128    /// The element at `indexes`. Panics if out of range.
129    pub fn get(&self, indexes: &[usize]) -> T {
130        let offset = self.offset(indexes).expect("Index was outside the bounds of the array.");
131        self.items.borrow()[offset].clone()
132    }
133
134    /// Replaces the element at `indexes`. Panics if out of range.
135    pub fn set(&self, indexes: &[usize], value: T) {
136        let offset = self.offset(indexes).expect("Index was outside the bounds of the array.");
137        self.items.borrow_mut()[offset] = value;
138    }
139
140    /// The elements in row-major order.
141    pub fn to_vec(&self) -> Vec<T> {
142        self.items.borrow().clone()
143    }
144
145    fn offset(&self, indexes: &[usize]) -> Option<usize> {
146        if indexes.len() != self.lengths.len() {
147            return None;
148        }
149        let mut offset = 0;
150        for (index, length) in indexes.iter().zip(&self.lengths) {
151            if index >= length {
152                return None;
153            }
154            offset = offset * length + index;
155        }
156        Some(offset)
157    }
158
159    fn checked_offset(&self, indexes: &[i32]) -> Result<usize, BindingError> {
160        if indexes.len() != self.lengths.len() {
161            return Err(BindingError::message("Indices length does not match the array rank."));
162        }
163        let indexes: Option<Vec<usize>> = indexes.iter().map(|i| usize::try_from(*i).ok()).collect();
164        indexes
165            .and_then(|i| self.offset(&i))
166            .ok_or_else(|| BindingError::message("Index was outside the bounds of the array."))
167    }
168}
169
170impl<T: PropertyValue> PartialEq for BindableArray<T> {
171    fn eq(&self, other: &Self) -> bool {
172        std::ptr::eq(self, other)
173    }
174}
175
176impl<T: PropertyValue> IBindableArray for BindableArray<T> {
177    fn rank(&self) -> usize {
178        self.lengths.len()
179    }
180
181    fn element_type(&self) -> ValueType {
182        ValueType::of::<T>()
183    }
184
185    fn get_value(&self, indexes: &[i32]) -> Result<Option<BoxedValue>, BindingError> {
186        let offset = self.checked_offset(indexes)?;
187        Ok(Some(Rc::new(self.items.borrow()[offset].clone())))
188    }
189
190    fn set_value(&self, indexes: &[i32], value: Option<&BoxedValue>) -> Result<(), BindingError> {
191        let offset = self.checked_offset(indexes)?;
192        let value = Value::<T>::from_untyped(value).ok_or_else(|| invalid_value(value, ValueType::of::<T>()))?;
193        self.items.borrow_mut()[offset] = value;
194        Ok(())
195    }
196}
197
198impl<T: PropertyValue> IBindableIndexer for BindableArray<T> {
199    fn index_parameter_types(&self) -> Vec<ValueType> {
200        vec![ValueType::of::<i32>(); self.lengths.len()]
201    }
202
203    fn value_type(&self) -> ValueType {
204        ValueType::of::<T>()
205    }
206
207    fn get(&self, arguments: &[BoxedValue]) -> Result<Option<BoxedValue>, BindingError> {
208        let indexes: Result<Vec<i32>, BindingError> =
209            (0..arguments.len()).map(|i| argument::<i32>(arguments, i).copied()).collect();
210        self.get_value(&indexes?)
211    }
212
213    fn set(&self, arguments: &[BoxedValue], value: Option<&BoxedValue>) -> Result<bool, BindingError> {
214        let indexes: Result<Vec<i32>, BindingError> =
215            (0..arguments.len()).map(|i| argument::<i32>(arguments, i).copied()).collect();
216        self.set_value(&indexes?, value).map(|()| true)
217    }
218}
219
220/// A notifying dictionary that bindings can index into (`Items[key]`): a
221/// [`FerroDictionary`] together with the untyped change event that binding
222/// indexers subscribe to.
223///
224/// As a model object it has identity equality.
225pub struct BindableDictionary<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> {
226    items: FerroDictionary<K, V>,
227    changed: Event<CollectionChange>,
228    forwarder: OnceCell<u64>,
229}
230
231impl<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> BindableDictionary<K, V> {
232    /// Creates a dictionary with the given entries.
233    pub fn new(entries: impl IntoIterator<Item = (K, V)>) -> Rc<Self> {
234        let this = Rc::new(Self {
235            items: FerroDictionary::from_dictionary(entries),
236            changed: Event::new(),
237            forwarder: OnceCell::new(),
238        });
239        let weak = Rc::downgrade(&this);
240        let token =
241            this.items.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, (K, V)>| {
242                if let Some(this) = weak.upgrade() {
243                    this.changed.raise(&CollectionChange {
244                        action: e.action,
245                        new_starting_index: e.new_starting_index,
246                        new_count: e.new_items.len(),
247                        old_starting_index: e.old_starting_index,
248                        old_count: e.old_items.len(),
249                    });
250                }
251            }));
252        let _ = this.forwarder.set(token);
253        if !ModelTypes::is_registered(TypeId::of::<Self>()) {
254            ModelTypes::register::<Self>(|b| b.indexer().notify_collection_changed().notify_property_changed());
255        }
256        this
257    }
258
259    /// The typed dictionary.
260    pub fn items(&self) -> &FerroDictionary<K, V> {
261        &self.items
262    }
263}
264
265impl<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> PartialEq for BindableDictionary<K, V> {
266    fn eq(&self, other: &Self) -> bool {
267        std::ptr::eq(self, other)
268    }
269}
270
271impl<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> INotifyCollectionChanged
272    for BindableDictionary<K, V>
273{
274    fn collection_changed(&self) -> &Event<CollectionChange> {
275        &self.changed
276    }
277}
278
279impl<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> INotifyPropertyChanged for BindableDictionary<K, V> {
280    fn property_changed(&self) -> &Event<str> {
281        self.items.property_changed()
282    }
283}
284
285impl<K: Eq + Hash + Clone + Display + 'static, V: PropertyValue> IBindableIndexer for BindableDictionary<K, V> {
286    fn index_parameter_types(&self) -> Vec<ValueType> {
287        vec![ValueType::of::<K>()]
288    }
289
290    fn value_type(&self) -> ValueType {
291        ValueType::of::<V>()
292    }
293
294    fn get(&self, arguments: &[BoxedValue]) -> Result<Option<BoxedValue>, BindingError> {
295        let key = argument::<K>(arguments, 0)?;
296        match self.items.try_get_value(key) {
297            Some(value) => Ok(Some(Rc::new(value))),
298            None => Err(BindingError::message(format!("The given key '{key}' was not present in the dictionary."))),
299        }
300    }
301
302    fn set(&self, arguments: &[BoxedValue], value: Option<&BoxedValue>) -> Result<bool, BindingError> {
303        let key = argument::<K>(arguments, 0)?;
304        let value = Value::<V>::from_untyped(value).ok_or_else(|| invalid_value(value, ValueType::of::<V>()))?;
305        self.items.set(key.clone(), value);
306        Ok(true)
307    }
308}