Skip to main content

ferroui_controls/
item_collection.rs

1use crate::items_source::{ItemsChangedEventArgs, ItemsSource};
2use crate::ItemsSourceView;
3use ferroui_base::collections::{FerroList, ResetBehavior};
4use ferroui_base::utilities::HandlerList;
5use ferroui_base::BoxedValue;
6use std::cell::{Cell, RefCell};
7use std::ops::Deref;
8use std::rc::Rc;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11enum Mode {
12    Items,
13    ItemsSource,
14}
15
16type DefaultCollection = FerroList<Option<BoxedValue>>;
17
18/// Holds the list of items that constitute the content of an
19/// `ItemsControl`.
20///
21/// The value is a shared handle: clones refer to the same collection, and
22/// handles compare by identity.
23#[derive(Clone)]
24pub struct ItemCollection(Rc<ItemCollectionData>);
25
26struct ItemCollectionData {
27    view: Rc<ItemsSourceView>,
28    mode: Cell<Mode>,
29    /// The writable collection behind the view while in items mode.
30    writable: RefCell<Option<Rc<DefaultCollection>>>,
31    source_changed: HandlerList<dyn Fn()>,
32}
33
34impl PartialEq for ItemCollection {
35    fn eq(&self, other: &Self) -> bool {
36        Rc::ptr_eq(&self.0, &other.0)
37    }
38}
39
40impl std::fmt::Debug for ItemCollection {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "ItemCollection({})", self.0.view.count())
43    }
44}
45
46impl Deref for ItemCollection {
47    type Target = ItemsSourceView;
48
49    fn deref(&self) -> &ItemsSourceView {
50        &self.0.view
51    }
52}
53
54impl ItemCollection {
55    pub(crate) fn new() -> Self {
56        Self(Rc::new(ItemCollectionData {
57            view: ItemsSourceView::new(ItemsSourceView::uninitialized_source()),
58            mode: Cell::new(Mode::Items),
59            writable: RefCell::new(None),
60            source_changed: HandlerList::new(),
61        }))
62    }
63
64    /// The read-only view of the collection.
65    pub fn view(&self) -> &Rc<ItemsSourceView> {
66        &self.0.view
67    }
68
69    /// Replaces the item at the specified index.
70    ///
71    /// Panics if the collection is in items source mode.
72    pub fn set(&self, index: usize, value: Option<BoxedValue>) {
73        self.writable_source().set(index, value)
74    }
75
76    /// Whether the collection is in items source mode.
77    pub fn is_read_only(&self) -> bool {
78        self.0.mode.get() == Mode::ItemsSource
79    }
80
81    /// Raised when the source of the collection has been replaced.
82    pub(crate) fn add_source_changed(&self, handler: Rc<dyn Fn()>) -> u64 {
83        self.0.source_changed.add(handler)
84    }
85
86    /// Adds an item to the items control.
87    ///
88    /// Returns the position into which the new element was inserted.
89    /// Panics if the collection is in items source mode.
90    pub fn add(&self, value: Option<BoxedValue>) -> i32 {
91        let source = self.writable_source();
92        source.add(value);
93        source.count() as i32 - 1
94    }
95
96    /// Clears the collection and releases the references on all items
97    /// currently in the collection.
98    ///
99    /// Panics if the collection is in items source mode.
100    pub fn clear(&self) {
101        self.writable_source().clear()
102    }
103
104    /// Inserts an element into the collection at the specified index.
105    ///
106    /// Panics if the collection is in items source mode.
107    pub fn insert(&self, index: usize, value: Option<BoxedValue>) {
108        self.writable_source().insert(index, value)
109    }
110
111    /// Removes the item at the specified index of the collection or view.
112    ///
113    /// Panics if the collection is in items source mode.
114    pub fn remove_at(&self, index: usize) {
115        self.writable_source().remove_at(index)
116    }
117
118    /// Removes the specified item reference from the collection or view.
119    /// Returns true if the item was removed.
120    ///
121    /// Panics if the collection is in items source mode.
122    pub fn remove(&self, value: &Option<BoxedValue>) -> bool {
123        let source = self.writable_source();
124        let index = self.0.view.index_of(value);
125        if index >= 0 {
126            source.remove_at(index as usize);
127            true
128        } else {
129            false
130        }
131    }
132
133    fn writable_source(&self) -> Rc<DefaultCollection> {
134        if self.is_read_only() {
135            Self::throw_is_items_source();
136        }
137        if self.0.view.try_get_initialized_source().is_none() {
138            self.set_source_default();
139        }
140        self.0.writable.borrow().clone().expect("the collection is in items mode")
141    }
142
143    pub(crate) fn set_items_source(&self, value: Option<ItemsSource>) {
144        if self.0.mode.get() != Mode::ItemsSource && self.0.view.count() > 0 {
145            panic!("Items collection must be empty before using ItemsSource.");
146        }
147
148        match value {
149            Some(value) => {
150                self.0.mode.set(Mode::ItemsSource);
151                *self.0.writable.borrow_mut() = None;
152                self.set_source(value);
153            }
154            None => {
155                self.0.mode.set(Mode::Items);
156                self.set_source_default();
157            }
158        }
159    }
160
161    fn set_source_default(&self) {
162        let collection = Self::create_default_collection();
163        *self.0.writable.borrow_mut() = Some(collection.clone());
164        self.set_source(collection.into());
165    }
166
167    fn set_source(&self, source: ItemsSource) {
168        let old_source = self.0.view.source();
169
170        self.0.view.set_source(source.clone());
171
172        // The notifications carry the sources themselves, as windows: no
173        // item is read here. The removed items are those of the old source,
174        // which the view no longer holds.
175        if old_source.count() > 0 {
176            self.0.view.raise_collection_changed(&ItemsChangedEventArgs::remove(&old_source, 0));
177        }
178        if source.count() > 0 {
179            self.0.view.raise_collection_changed(&ItemsChangedEventArgs::add(&source, 0));
180        }
181        if !self.0.source_changed.is_empty() {
182            for (_, handler) in self.0.source_changed.snapshot().iter() {
183                handler();
184            }
185        }
186    }
187
188    fn create_default_collection() -> Rc<DefaultCollection> {
189        let collection = DefaultCollection::new();
190        collection.set_reset_behavior(ResetBehavior::Remove);
191        Rc::new(collection)
192    }
193
194    fn throw_is_items_source() -> ! {
195        panic!(
196            "Operation is not valid while ItemsSource is in use.\
197             Access and modify elements with ItemsControl.ItemsSource instead."
198        );
199    }
200}