cosmic/widget/table/model/
mod.rs

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