Skip to main content

cosmic/widget/table/model/
mod.rs

1pub mod category;
2pub mod entity;
3pub mod selection;
4
5use std::any::{Any, TypeId};
6use std::collections::{HashMap, VecDeque};
7
8use category::{ItemCategory, ItemInterface};
9use entity::EntityMut;
10use selection::Selectable;
11use slotmap::{SecondaryMap, SlotMap};
12
13slotmap::new_key_type! {
14    /// Unique key type for items in the table
15    pub struct Entity;
16}
17
18/// The portion of the model used only by the application.
19#[derive(Debug, Default)]
20pub(super) struct Storage(HashMap<TypeId, SecondaryMap<Entity, Box<dyn Any>>>);
21
22pub struct Model<SelectionMode: Default, Item: ItemInterface<Category>, Category: ItemCategory>
23where
24    Category: ItemCategory,
25{
26    pub(super) categories: Vec<Category>,
27
28    /// Stores the items
29    pub(super) items: SlotMap<Entity, Item>,
30
31    /// Whether the item is selected or not
32    pub(super) active: SecondaryMap<Entity, bool>,
33
34    /// Optional indents for the table items
35    pub(super) indents: SecondaryMap<Entity, u16>,
36
37    /// Order which the items will be displayed.
38    pub(super) order: VecDeque<Entity>,
39
40    /// Stores the current selection(s)
41    pub(super) selection: SelectionMode,
42
43    /// What category to sort by and whether it's ascending or not
44    pub(super) sort: Option<(Category, bool)>,
45
46    /// Application-managed data associated with each item
47    pub(super) storage: Storage,
48}
49
50impl<SelectionMode: Default, Item: ItemInterface<Category>, Category: ItemCategory>
51    Model<SelectionMode, Item, Category>
52where
53    Self: Selectable,
54{
55    pub fn new(categories: Vec<Category>) -> Self {
56        Self {
57            categories,
58            items: SlotMap::default(),
59            active: SecondaryMap::default(),
60            indents: SecondaryMap::default(),
61            order: VecDeque::new(),
62            selection: SelectionMode::default(),
63            sort: None,
64            storage: Storage::default(),
65        }
66    }
67
68    pub fn categories(&mut self, cats: Vec<Category>) {
69        self.categories = cats;
70    }
71
72    /// Activates the item in the model.
73    ///
74    /// ```ignore
75    /// model.activate(id);
76    /// ```
77    pub fn activate(&mut self, id: Entity) {
78        Selectable::activate(self, id);
79    }
80
81    /// Activates the item at the given position, returning true if it was activated.
82    pub fn activate_position(&mut self, position: u16) -> bool {
83        if let Some(entity) = self.entity_at(position) {
84            self.activate(entity);
85            return true;
86        }
87
88        false
89    }
90
91    /// Removes all items from the model.
92    ///
93    /// Any IDs held elsewhere by the application will no longer be usable with the map.
94    /// The generation is incremented on removal, so the stale IDs will return `None` for
95    /// any attempt to get values from the map.
96    ///
97    /// ```ignore
98    /// model.clear();
99    /// ```
100    pub fn clear(&mut self) {
101        for entity in self.order.clone() {
102            self.remove(entity);
103        }
104    }
105
106    /// Check if an item exists in the map.
107    ///
108    /// ```ignore
109    /// if model.contains_item(id) {
110    ///     println!("ID is still valid");
111    /// }
112    /// ```
113    pub fn contains_item(&self, id: Entity) -> bool {
114        self.items.contains_key(id)
115    }
116
117    /// Get an immutable reference to data associated with an item.
118    ///
119    /// ```ignore
120    /// if let Some(data) = model.data::<String>(id) {
121    ///     println!("found string on {:?}: {}", id, data);
122    /// }
123    /// ```
124    pub fn item(&self, id: Entity) -> Option<&Item> {
125        self.items.get(id)
126    }
127
128    /// Get a mutable reference to data associated with an item.
129    pub fn item_mut(&mut self, id: Entity) -> Option<&mut Item> {
130        self.items.get_mut(id)
131    }
132
133    /// Associates data with the item.
134    ///
135    /// There may only be one data component per Rust type.
136    ///
137    /// ```ignore
138    /// model.data_set::<String>(id, String::from("custom string"));
139    /// ```
140    pub fn item_set(&mut self, id: Entity, data: Item) {
141        if let Some(item) = self.items.get_mut(id) {
142            *item = data;
143        }
144    }
145
146    /// Get an immutable reference to data associated with an item.
147    ///
148    /// ```ignore
149    /// if let Some(data) = model.data::<String>(id) {
150    ///     println!("found string on {:?}: {}", id, data);
151    /// }
152    /// ```
153    pub fn data<Data: 'static>(&self, id: Entity) -> Option<&Data> {
154        self.storage
155            .0
156            .get(&TypeId::of::<Data>())
157            .and_then(|storage| storage.get(id))
158            .and_then(|data| data.downcast_ref())
159    }
160
161    /// Get a mutable reference to data associated with an item.
162    pub fn data_mut<Data: 'static>(&mut self, id: Entity) -> Option<&mut Data> {
163        self.storage
164            .0
165            .get_mut(&TypeId::of::<Data>())
166            .and_then(|storage| storage.get_mut(id))
167            .and_then(|data| data.downcast_mut())
168    }
169
170    /// Associates data with the item.
171    ///
172    /// There may only be one data component per Rust type.
173    ///
174    /// ```ignore
175    /// model.data_set::<String>(id, String::from("custom string"));
176    /// ```
177    pub fn data_set<Data: 'static>(&mut self, id: Entity, data: Data) {
178        if self.contains_item(id) {
179            self.storage
180                .0
181                .entry(TypeId::of::<Data>())
182                .or_default()
183                .insert(id, Box::new(data));
184        }
185    }
186
187    /// Removes a specific data type from the item.
188    ///
189    /// ```ignore
190    /// model.data.remove::<String>(id);
191    /// ```
192    pub fn data_remove<Data: 'static>(&mut self, id: Entity) {
193        self.storage
194            .0
195            .get_mut(&TypeId::of::<Data>())
196            .and_then(|storage| storage.remove(id));
197    }
198
199    /// Enable or disable an item.
200    ///
201    /// ```ignore
202    /// model.enable(id, true);
203    /// ```
204    pub fn enable(&mut self, id: Entity, enable: bool) {
205        if let Some(e) = self.active.get_mut(id) {
206            *e = enable;
207        }
208    }
209
210    /// Get the item that is located at a given position.
211    #[must_use]
212    pub fn entity_at(&mut self, position: u16) -> Option<Entity> {
213        self.order.get(position as usize).copied()
214    }
215
216    /// Inserts a new item in the model.
217    ///
218    /// ```ignore
219    /// let id = model.insert().text("Item A").icon("custom-icon").id();
220    /// ```
221    #[must_use]
222    pub fn insert(&mut self, item: Item) -> EntityMut<'_, SelectionMode, Item, Category> {
223        let id = self.items.insert(item);
224        self.order.push_back(id);
225        EntityMut { model: self, id }
226    }
227
228    /// Check if the given ID is the active ID.
229    #[must_use]
230    pub fn is_active(&self, id: Entity) -> bool {
231        <Self as Selectable>::is_active(self, id)
232    }
233
234    /// Check if the item is enabled.
235    ///
236    /// ```ignore
237    /// if model.is_enabled(id) {
238    ///     if let Some(text) = model.text(id) {
239    ///         println!("{text} is enabled");
240    ///     }
241    /// }
242    /// ```
243    #[must_use]
244    pub fn is_enabled(&self, id: Entity) -> bool {
245        self.active.get(id).is_some_and(|e| *e)
246    }
247
248    /// Iterates across items in the model in the order that they are displayed.
249    pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
250        self.order.iter().copied()
251    }
252
253    pub fn indent(&self, id: Entity) -> Option<u16> {
254        self.indents.get(id).copied()
255    }
256
257    pub fn indent_set(&mut self, id: Entity, indent: u16) -> Option<u16> {
258        if !self.contains_item(id) {
259            return None;
260        }
261
262        self.indents.insert(id, indent)
263    }
264
265    pub fn indent_remove(&mut self, id: Entity) -> Option<u16> {
266        self.indents.remove(id)
267    }
268
269    /// The position of the item in the model.
270    ///
271    /// ```ignore
272    /// if let Some(position) = model.position(id) {
273    ///     println!("found item at {}", position);
274    /// }
275    #[must_use]
276    pub fn position(&self, id: Entity) -> Option<u16> {
277        #[allow(clippy::cast_possible_truncation)]
278        self.order.iter().position(|k| *k == id).map(|v| v as u16)
279    }
280
281    /// Change the position of an item in the model.
282    ///
283    /// ```ignore
284    /// if let Some(new_position) = model.position_set(id, 0) {
285    ///     println!("placed item at {}", new_position);
286    /// }
287    /// ```
288    pub fn position_set(&mut self, id: Entity, position: u16) -> Option<usize> {
289        let index = self.position(id)?;
290
291        self.order.remove(index as usize);
292
293        let position = self.order.len().min(position as usize);
294
295        self.order.insert(position, id);
296        Some(position)
297    }
298
299    /// Swap the position of two items in the model.
300    ///
301    /// Returns false if the swap cannot be performed.
302    ///
303    /// ```ignore
304    /// if model.position_swap(first_id, second_id) {
305    ///     println!("positions swapped");
306    /// }
307    /// ```
308    pub fn position_swap(&mut self, first: Entity, second: Entity) -> bool {
309        let Some(first_index) = self.position(first) else {
310            return false;
311        };
312
313        let Some(second_index) = self.position(second) else {
314            return false;
315        };
316
317        self.order.swap(first_index as usize, second_index as usize);
318        true
319    }
320
321    /// Removes an item from the model.
322    ///
323    /// The generation of the slot for the ID will be incremented, so this ID will no
324    /// longer be usable with the map. Subsequent attempts to get values from the map
325    /// with this ID will return `None` and failed to assign values.
326    pub fn remove(&mut self, id: Entity) {
327        self.items.remove(id);
328        self.deactivate(id);
329
330        for storage in self.storage.0.values_mut() {
331            storage.remove(id);
332        }
333
334        if let Some(index) = self.position(id) {
335            self.order.remove(index as usize);
336        }
337    }
338
339    /// Get the sort data
340    pub fn get_sort(&self) -> Option<(Category, bool)> {
341        self.sort
342    }
343
344    /// Sorts items in the model, this should be called before it is drawn after all items have been added for the view
345    pub fn sort(&mut self, category: Category, ascending: bool) {
346        match self.sort {
347            Some((cat, asc)) if cat == category && asc == ascending => return,
348            Some((cat, _)) if cat == category => self.order.make_contiguous().reverse(),
349            _ => {
350                self.order.make_contiguous().sort_by(|entity_a, entity_b| {
351                    let cmp = self
352                        .items
353                        .get(*entity_a)
354                        .unwrap()
355                        .compare(self.items.get(*entity_b).unwrap(), category);
356                    if ascending { cmp } else { cmp.reverse() }
357                });
358            }
359        }
360        self.sort = Some((category, ascending));
361    }
362}