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, 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
30impl ItemsSourceView {
31    /// Gets an empty [`ItemsSourceView`].
32    pub fn empty() -> Rc<ItemsSourceView> {
33        EMPTY.with(Rc::clone)
34    }
35
36    /// Gets an instance representing an uninitialized source.
37    pub(crate) fn uninitialized_source() -> ItemsSource {
38        UNINITIALIZED_SOURCE.with(Clone::clone)
39    }
40
41    /// Initializes a new instance of the view for the specified data source.
42    pub(crate) fn new(source: ItemsSource) -> Rc<Self> {
43        Rc::new_cyclic(|this| Self {
44            this: this.clone(),
45            source: RefCell::new(source),
46            collection_changed: HandlerList::new(),
47            pre_collection_changed: HandlerList::new(),
48            post_collection_changed: HandlerList::new(),
49            listening: Cell::new(false),
50        })
51    }
52
53    /// Gets the number of items in the collection.
54    #[inline]
55    pub fn count(&self) -> usize {
56        self.source.borrow().count()
57    }
58
59    /// Gets the source collection.
60    #[inline]
61    pub fn source(&self) -> ItemsSource {
62        self.source.borrow().clone()
63    }
64
65    pub(crate) fn try_get_initialized_source(&self) -> Option<ItemsSource> {
66        let source = self.source();
67        if source.ptr_eq(&Self::uninitialized_source()) {
68            None
69        } else {
70            Some(source)
71        }
72    }
73
74    /// Occurs when the collection has changed to indicate the reason for
75    /// the change and which items changed. Returns the token for
76    /// [`remove_collection_changed`](Self::remove_collection_changed).
77    pub fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
78        self.add_listener_if_necessary();
79        self.collection_changed.add(handler)
80    }
81
82    pub fn remove_collection_changed(&self, token: u64) {
83        self.collection_changed.remove(token);
84        self.remove_listener_if_necessary();
85    }
86
87    /// Occurs when a collection is about to notify its
88    /// [`collection changed`](Self::add_collection_changed) handlers.
89    pub(crate) fn add_pre_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
90        self.add_listener_if_necessary();
91        self.pre_collection_changed.add(handler)
92    }
93
94    pub(crate) fn remove_pre_collection_changed(&self, token: u64) {
95        self.pre_collection_changed.remove(token);
96        self.remove_listener_if_necessary();
97    }
98
99    /// Occurs when a collection has finished changing and all
100    /// [`collection changed`](Self::add_collection_changed) handlers have
101    /// been notified.
102    pub(crate) fn add_post_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> u64 {
103        self.add_listener_if_necessary();
104        self.post_collection_changed.add(handler)
105    }
106
107    pub(crate) fn remove_post_collection_changed(&self, token: u64) {
108        self.post_collection_changed.remove(token);
109        self.remove_listener_if_necessary();
110    }
111
112    /// Retrieves the item at the specified index. Panics if out of range.
113    #[inline]
114    pub fn get_at(&self, index: usize) -> Option<BoxedValue> {
115        let source = self.source.borrow().clone();
116        source.get_at(index)
117    }
118
119    /// Whether the collection contains the item.
120    pub fn contains(&self, item: &Option<BoxedValue>) -> bool {
121        self.index_of(item) >= 0
122    }
123
124    /// The index of the item, or `-1`.
125    pub fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
126        let source = self.source.borrow().clone();
127        source.index_of(item)
128    }
129
130    /// Gets or creates an [`ItemsSourceView`] for the specified source.
131    ///
132    /// Returns [`empty`](Self::empty) if `items` is `None`, otherwise a new
133    /// view. (A caller that already holds a view uses it as is.)
134    pub fn get_or_create(items: Option<&ItemsSource>) -> Rc<ItemsSourceView> {
135        match items {
136            None => Self::empty(),
137            Some(items) => Self::new(items.clone()),
138        }
139    }
140
141    /// Gets or creates a typed view for the specified source.
142    pub fn get_or_create_of<T: PropertyValue>(items: Option<&ItemsSource>) -> ItemsSourceViewOf<T> {
143        match items {
144            None => ItemsSourceViewOf::empty(),
145            Some(items) => ItemsSourceViewOf::new(items.clone()),
146        }
147    }
148
149    /// The items, enumerated by index over the live collection.
150    pub fn iter(&self) -> impl Iterator<Item = Option<BoxedValue>> {
151        let source = self.source();
152        let mut index = 0;
153        std::iter::from_fn(move || {
154            if index < source.count() {
155                index += 1;
156                Some(source.get_at(index - 1))
157            } else {
158                None
159            }
160        })
161    }
162
163    /// A copy of the items.
164    pub fn to_vec(&self) -> Vec<Option<BoxedValue>> {
165        self.source().to_vec()
166    }
167
168    pub(crate) fn raise_collection_changed(&self, e: &ItemsChangedEventArgs<'_>) {
169        Self::invoke(&self.pre_collection_changed, e);
170        Self::invoke(&self.collection_changed, e);
171        Self::invoke(&self.post_collection_changed, e);
172    }
173
174    pub(crate) fn set_source(&self, source: ItemsSource) {
175        let listener: Weak<dyn ICollectionChangedListener> = self.this.clone();
176
177        if self.listening.get() {
178            let old = self.source();
179            if old.is_notifying() {
180                CollectionChangedEventManager::remove_listener(&old, &listener);
181            }
182        }
183
184        *self.source.borrow_mut() = source.clone();
185
186        if self.listening.get() && source.is_notifying() {
187            CollectionChangedEventManager::add_listener(&source, listener);
188        }
189    }
190
191    fn add_listener_if_necessary(&self) {
192        if !self.listening.get() {
193            let source = self.source();
194            if source.is_notifying() {
195                CollectionChangedEventManager::add_listener(&source, self.this.clone());
196            }
197            self.listening.set(true);
198        }
199    }
200
201    fn remove_listener_if_necessary(&self) {
202        if self.listening.get() && self.collection_changed.is_empty() && self.post_collection_changed.is_empty() {
203            let source = self.source();
204            if source.is_notifying() {
205                let listener: Weak<dyn ICollectionChangedListener> = self.this.clone();
206                CollectionChangedEventManager::remove_listener(&source, &listener);
207            }
208            self.listening.set(false);
209        }
210    }
211
212    #[inline]
213    fn invoke(handlers: &HandlerList<ItemsChangedHandler>, e: &ItemsChangedEventArgs<'_>) {
214        if !handlers.is_empty() {
215            for (_, handler) in handlers.snapshot().iter() {
216                handler(e);
217            }
218        }
219    }
220}
221
222impl ICollectionChangedListener for ItemsSourceView {
223    fn pre_changed(&self, e: &ItemsChangedEventArgs<'_>) {
224        Self::invoke(&self.pre_collection_changed, e);
225    }
226
227    fn changed(&self, e: &ItemsChangedEventArgs<'_>) {
228        Self::invoke(&self.collection_changed, e);
229    }
230
231    fn post_changed(&self, e: &ItemsChangedEventArgs<'_>) {
232        Self::invoke(&self.post_collection_changed, e);
233    }
234}
235
236/// A typed [`ItemsSourceView`]: the items are read as `T`.
237///
238/// With `T` = `BoxedValue` the items are read untyped.
239pub struct ItemsSourceViewOf<T: PropertyValue> {
240    view: Rc<ItemsSourceView>,
241    marker: PhantomData<fn() -> T>,
242}
243
244impl<T: PropertyValue> Clone for ItemsSourceViewOf<T> {
245    fn clone(&self) -> Self {
246        Self { view: self.view.clone(), marker: PhantomData }
247    }
248}
249
250impl<T: PropertyValue> Deref for ItemsSourceViewOf<T> {
251    type Target = ItemsSourceView;
252
253    fn deref(&self) -> &ItemsSourceView {
254        &self.view
255    }
256}
257
258impl<T: PropertyValue> ItemsSourceViewOf<T> {
259    /// Gets an empty view.
260    pub fn empty() -> Self {
261        Self { view: ItemsSourceView::empty(), marker: PhantomData }
262    }
263
264    /// Initializes a new instance of the view for the specified data source.
265    pub(crate) fn new(source: ItemsSource) -> Self {
266        Self { view: ItemsSourceView::new(source), marker: PhantomData }
267    }
268
269    /// Reads an existing view as a typed view over the same source.
270    pub fn from_view(view: &ItemsSourceView) -> Self {
271        Self::new(view.source())
272    }
273
274    /// The untyped view.
275    pub fn view(&self) -> &Rc<ItemsSourceView> {
276        &self.view
277    }
278
279    /// Retrieves the item at the specified index: `None` for a null item.
280    /// Panics if the index is out of range or the item is not a `T`.
281    pub fn get_at(&self, index: usize) -> Option<T> {
282        let item = self.view.get_at(index);
283        if item.is_none() {
284            return None;
285        }
286        match unbox_item::<T>(&item) {
287            Some(value) => Some(value),
288            None => panic!("Unable to cast the item to type '{}'.", std::any::type_name::<T>()),
289        }
290    }
291
292    /// The items, enumerated by index over the live collection.
293    pub fn iter(&self) -> impl Iterator<Item = Option<T>> + '_ {
294        let mut index = 0;
295        std::iter::from_fn(move || {
296            if index < self.view.count() {
297                index += 1;
298                Some(self.get_at(index - 1))
299            } else {
300                None
301            }
302        })
303    }
304}