Skip to main content

ferroui_controls/
items_source_view.rs

1// This source file is adapted from the WinUI project.
2// (https://github.com/microsoft/microsoft-ui-xaml)
3
4use crate::items_source::{unbox_item, IItemsList, ItemsChangedEventArgs, ItemsChangedHandler, ItemsSource};
5use crate::utils::{CollectionChangedEventManager, ICollectionChangedListener};
6use ferroui_base::utilities::HandlerList;
7use ferroui_base::{BoxedValue, PropertyValue};
8use std::cell::{Cell, RefCell};
9use std::marker::PhantomData;
10use std::ops::Deref;
11use std::rc::{Rc, Weak};
12
13ferroui_base::ferro_thread_local! {
14    static EMPTY: Rc<ItemsSourceView> = ItemsSourceView::new(ItemsSource::from_items([]));
15    // This is a sentinel value and must be unique.
16    static UNINITIALIZED_SOURCE: ItemsSource = ItemsSource::from_items([]);
17}
18
19/// Represents a standardized view of the supported interactions between an
20/// items collection and an items control.
21pub struct ItemsSourceView {
22    this: Weak<ItemsSourceView>,
23    source: RefCell<ItemsSource>,
24    collection_changed: HandlerList<ItemsChangedHandler>,
25    pre_collection_changed: HandlerList<ItemsChangedHandler>,
26    post_collection_changed: HandlerList<ItemsChangedHandler>,
27    listening: Cell<bool>,
28}
29
30/// A view is a reference: two views are equal when they are the same view.
31impl PartialEq for ItemsSourceView {
32    fn eq(&self, other: &Self) -> bool {
33        std::ptr::eq(self, other)
34    }
35}
36
37impl ItemsSourceView {
38    /// Gets an empty [`ItemsSourceView`].
39    pub fn empty() -> Rc<ItemsSourceView> {
40        EMPTY.with(Rc::clone)
41    }
42
43    /// Gets an instance representing an uninitialized source.
44    pub(crate) fn uninitialized_source() -> ItemsSource {
45        UNINITIALIZED_SOURCE.with(Clone::clone)
46    }
47
48    /// Initializes a new instance of the view for the specified data source.
49    // Deviation (DEVIATIONS.md, Items sources): upstream's constructor is `private protected`;
50    // WinUI's is public, and markup constructs a view (`ItemsSourceView(Object source)`).
51    pub fn new(source: ItemsSource) -> Rc<Self> {
52        Rc::new_cyclic(|this| Self {
53            this: this.clone(),
54            source: RefCell::new(source),
55            collection_changed: HandlerList::new(),
56            pre_collection_changed: HandlerList::new(),
57            post_collection_changed: HandlerList::new(),
58            listening: Cell::new(false),
59        })
60    }
61
62    /// Gets the number of items in the collection.
63    #[inline]
64    pub fn count(&self) -> usize {
65        self.source.borrow().count()
66    }
67
68    /// Gets a value that indicates whether the items source can provide a unique key for
69    /// each item: whether the source implements [`IKeyIndexMapping`](crate::IKeyIndexMapping)
70    /// (WinUI `ItemsSourceView.HasKeyIndexMapping`).
71    // Deviation (DEVIATIONS.md, Items sources): upstream keeps the member internal and always
72    // `false` ("preserved here for ItemsRepeater's usage"); here it is WinUI's.
73    pub fn has_key_index_mapping(&self) -> bool {
74        self.source().list().key_index_mapping().is_some()
75    }
76
77    /// Retrieves the unique key of the item at `index` from the items source
78    /// (WinUI `ItemsSourceView.KeyFromIndex`).
79    ///
80    /// Panics if the source does not implement [`IKeyIndexMapping`](crate::IKeyIndexMapping)
81    /// (WinUI throws `hresult_not_implemented`), or if `index` is not the index of an item.
82    // Deviation (DEVIATIONS.md, Items sources): upstream throws `NotImplementedException`
83    // whatever the source; here it is WinUI's, with the index checked before it is passed on.
84    pub fn key_from_index(&self, index: i32) -> String {
85        let source = self.source();
86        assert!(index >= 0 && (index as usize) < source.count(), "Argument index is invalid.");
87        source
88            .list()
89            .key_index_mapping()
90            .expect("The items source does not implement IKeyIndexMapping.")
91            .key_from_index(index)
92    }
93
94    /// Retrieves the index of the item that has the unique key `key`, or `-1` when no item
95    /// has it (WinUI `ItemsSourceView.IndexFromKey`).
96    ///
97    /// Panics if the source does not implement [`IKeyIndexMapping`](crate::IKeyIndexMapping)
98    /// (WinUI throws `hresult_not_implemented`).
99    pub fn index_from_key(&self, key: &str) -> i32 {
100        self.source()
101            .list()
102            .key_index_mapping()
103            .expect("The items source does not implement IKeyIndexMapping.")
104            .index_from_key(key)
105    }
106
107    /// Gets the source collection.
108    #[inline]
109    pub fn source(&self) -> ItemsSource {
110        self.source.borrow().clone()
111    }
112
113    pub(crate) fn try_get_initialized_source(&self) -> Option<ItemsSource> {
114        let source = self.source();
115        if source.ptr_eq(&Self::uninitialized_source()) {
116            None
117        } else {
118            Some(source)
119        }
120    }
121
122    /// Occurs when the collection has changed to indicate the reason for
123    /// the change and which items changed. Returns the token for
124    /// [`remove_collection_changed`](Self::remove_collection_changed).
125    pub fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
126        self.add_listener_if_necessary();
127        self.collection_changed.add(handler)
128    }
129
130    pub fn remove_collection_changed(&self, token: u64) {
131        self.collection_changed.remove(token);
132        self.remove_listener_if_necessary();
133    }
134
135    /// Occurs when a collection is about to notify its
136    /// [`collection changed`](Self::add_collection_changed) handlers.
137    pub(crate) fn add_pre_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
138        self.add_listener_if_necessary();
139        self.pre_collection_changed.add(handler)
140    }
141
142    pub(crate) fn remove_pre_collection_changed(&self, token: u64) {
143        self.pre_collection_changed.remove(token);
144        self.remove_listener_if_necessary();
145    }
146
147    /// Occurs when a collection has finished changing and all
148    /// [`collection changed`](Self::add_collection_changed) handlers have
149    /// been notified.
150    pub(crate) fn add_post_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
151        self.add_listener_if_necessary();
152        self.post_collection_changed.add(handler)
153    }
154
155    pub(crate) fn remove_post_collection_changed(&self, token: u64) {
156        self.post_collection_changed.remove(token);
157        self.remove_listener_if_necessary();
158    }
159
160    /// Retrieves the item at the specified index. Panics if out of range.
161    #[inline]
162    pub fn get_at(&self, index: usize) -> Option<BoxedValue> {
163        let source = self.source.borrow().clone();
164        source.get_at(index)
165    }
166
167    /// Whether the collection contains the item.
168    pub fn contains(&self, item: &Option<BoxedValue>) -> bool {
169        self.index_of(item) >= 0
170    }
171
172    /// The index of the item, or `-1`.
173    pub fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
174        let source = self.source.borrow().clone();
175        source.index_of(item)
176    }
177
178    /// Gets or creates an [`ItemsSourceView`] for the specified source.
179    ///
180    /// Returns [`empty`](Self::empty) if `items` is `None`, the view itself if `items`
181    /// is a view (`ItemsSourceView isv => isv`), otherwise a new view.
182    pub fn get_or_create(items: Option<&ItemsSource>) -> Rc<ItemsSourceView> {
183        match items {
184            None => Self::empty(),
185            Some(items) => items
186                .downcast_ref::<Self>()
187                .and_then(|view| view.this.upgrade())
188                .unwrap_or_else(|| Self::new(items.clone())),
189        }
190    }
191
192    /// Gets or creates a typed view for the specified source.
193    pub fn get_or_create_of<T: PropertyValue>(items: Option<&ItemsSource>) -> ItemsSourceViewOf<T> {
194        match items {
195            None => ItemsSourceViewOf::empty(),
196            Some(items) => ItemsSourceViewOf::new(items.clone()),
197        }
198    }
199
200    /// The items, enumerated by index over the live collection.
201    pub fn iter(&self) -> impl Iterator<Item = Option<BoxedValue>> {
202        let source = self.source();
203        let mut index = 0;
204        std::iter::from_fn(move || {
205            if index < source.count() {
206                index += 1;
207                Some(source.get_at(index - 1))
208            } else {
209                None
210            }
211        })
212    }
213
214    /// A copy of the items.
215    pub fn to_vec(&self) -> Vec<Option<BoxedValue>> {
216        self.source().to_vec()
217    }
218
219    pub(crate) fn raise_collection_changed(&self, e: &ItemsChangedEventArgs<'_>) {
220        Self::invoke(&self.pre_collection_changed, e);
221        Self::invoke(&self.collection_changed, e);
222        Self::invoke(&self.post_collection_changed, e);
223    }
224
225    pub(crate) fn set_source(&self, source: ItemsSource) {
226        let listener: Weak<dyn ICollectionChangedListener> = self.this.clone();
227
228        if self.listening.get() {
229            let old = self.source();
230            if old.is_notifying() {
231                CollectionChangedEventManager::remove_listener(&old, &listener);
232            }
233        }
234
235        *self.source.borrow_mut() = source.clone();
236
237        if self.listening.get() && source.is_notifying() {
238            CollectionChangedEventManager::add_listener(&source, listener);
239        }
240    }
241
242    fn add_listener_if_necessary(&self) {
243        if !self.listening.get() {
244            let source = self.source();
245            if source.is_notifying() {
246                CollectionChangedEventManager::add_listener(&source, self.this.clone());
247            }
248            self.listening.set(true);
249        }
250    }
251
252    fn remove_listener_if_necessary(&self) {
253        if self.listening.get() && self.collection_changed.is_empty() && self.post_collection_changed.is_empty() {
254            let source = self.source();
255            if source.is_notifying() {
256                let listener: Weak<dyn ICollectionChangedListener> = self.this.clone();
257                CollectionChangedEventManager::remove_listener(&source, &listener);
258            }
259            self.listening.set(false);
260        }
261    }
262
263    #[inline]
264    fn invoke(handlers: &HandlerList<ItemsChangedHandler>, e: &ItemsChangedEventArgs<'_>) {
265        if !handlers.is_empty() {
266            for (_, handler) in handlers.snapshot().iter() {
267                handler(e);
268            }
269        }
270    }
271}
272
273/// A view is a list of items itself (upstream `ItemsSourceView : IReadOnlyList<object?>, IList,
274/// INotifyCollectionChanged`): it can be the items source of a control, which then reads the
275/// items and the changes through the view, and [`ItemsSourceView::get_or_create`] returns it as
276/// it is. Its unique keys are those of its source.
277impl IItemsList for ItemsSourceView {
278    fn key_index_mapping(&self) -> Option<&dyn crate::IKeyIndexMapping> {
279        self.has_key_index_mapping().then_some(self as &dyn crate::IKeyIndexMapping)
280    }
281
282    fn count(&self) -> usize {
283        ItemsSourceView::count(self)
284    }
285
286    fn get_at(&self, index: usize) -> Option<BoxedValue> {
287        ItemsSourceView::get_at(self, index)
288    }
289
290    fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
291        ItemsSourceView::index_of(self, item)
292    }
293
294    fn is_notifying(&self) -> bool {
295        self.source().is_notifying()
296    }
297
298    fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
299        self.is_notifying().then(|| ItemsSourceView::add_collection_changed(self, handler))
300    }
301
302    fn remove_collection_changed(&self, token: u64) {
303        ItemsSourceView::remove_collection_changed(self, token);
304    }
305
306    fn as_any(&self) -> &dyn std::any::Any {
307        self
308    }
309}
310
311impl crate::IKeyIndexMapping for ItemsSourceView {
312    fn key_from_index(&self, index: i32) -> String {
313        ItemsSourceView::key_from_index(self, index)
314    }
315
316    fn index_from_key(&self, key: &str) -> i32 {
317        ItemsSourceView::index_from_key(self, key)
318    }
319}
320
321impl ICollectionChangedListener for ItemsSourceView {
322    fn pre_changed(&self, e: &ItemsChangedEventArgs<'_>) {
323        Self::invoke(&self.pre_collection_changed, e);
324    }
325
326    fn changed(&self, e: &ItemsChangedEventArgs<'_>) {
327        Self::invoke(&self.collection_changed, e);
328    }
329
330    fn post_changed(&self, e: &ItemsChangedEventArgs<'_>) {
331        Self::invoke(&self.post_collection_changed, e);
332    }
333}
334
335/// A typed [`ItemsSourceView`]: the items are read as `T`.
336///
337/// With `T` = `BoxedValue` the items are read untyped.
338pub struct ItemsSourceViewOf<T: PropertyValue> {
339    view: Rc<ItemsSourceView>,
340    marker: PhantomData<fn() -> T>,
341}
342
343impl<T: PropertyValue> Clone for ItemsSourceViewOf<T> {
344    fn clone(&self) -> Self {
345        Self { view: self.view.clone(), marker: PhantomData }
346    }
347}
348
349impl<T: PropertyValue> Deref for ItemsSourceViewOf<T> {
350    type Target = ItemsSourceView;
351
352    fn deref(&self) -> &ItemsSourceView {
353        &self.view
354    }
355}
356
357impl<T: PropertyValue> ItemsSourceViewOf<T> {
358    /// Gets an empty view.
359    pub fn empty() -> Self {
360        Self { view: ItemsSourceView::empty(), marker: PhantomData }
361    }
362
363    /// Initializes a new instance of the view for the specified data source.
364    pub fn new(source: ItemsSource) -> Self {
365        Self { view: ItemsSourceView::new(source), marker: PhantomData }
366    }
367
368    /// Reads an existing view as a typed view over the same source.
369    pub fn from_view(view: &ItemsSourceView) -> Self {
370        Self::new(view.source())
371    }
372
373    /// The untyped view.
374    pub fn view(&self) -> &Rc<ItemsSourceView> {
375        &self.view
376    }
377
378    /// Retrieves the item at the specified index: `None` for a null item.
379    /// Panics if the index is out of range or the item is not a `T`.
380    pub fn get_at(&self, index: usize) -> Option<T> {
381        let item = self.view.get_at(index);
382        if item.is_none() {
383            return None;
384        }
385        match unbox_item::<T>(&item) {
386            Some(value) => Some(value),
387            None => panic!("Unable to cast the item to type '{}'.", std::any::type_name::<T>()),
388        }
389    }
390
391    /// The items, enumerated by index over the live collection.
392    pub fn iter(&self) -> impl Iterator<Item = Option<T>> + '_ {
393        let mut index = 0;
394        std::iter::from_fn(move || {
395            if index < self.view.count() {
396                index += 1;
397                Some(self.get_at(index - 1))
398            } else {
399                None
400            }
401        })
402    }
403}