Skip to main content

egui_selectable_table/
row_modification.rs

1use ahash::{HashMap, HashSet, HashSetExt};
2use rayon::prelude::*;
3use std::hash::Hash;
4
5use crate::{ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable, SortOrder};
6
7impl<Row, F, Conf> SelectableTable<Row, F, Conf>
8where
9    Row: Clone + Send + Sync,
10    F: Eq
11        + Hash
12        + Clone
13        + Ord
14        + Send
15        + Sync
16        + Default
17        + ColumnOperations<Row, F, Conf>
18        + ColumnOrdering<Row>,
19    Conf: Default,
20{
21    /// Modify or add rows to the table. Changes are not immediately reflected in the UI.
22    /// You must call [`recreate_rows`](#method.recreate_rows) or [`recreate_rows_no_unselect`](#method.recreate_rows_no_unselect) to apply these changes visually.
23    ///
24    /// If modifying existing and visible row, consider using [`modify_shown_row`](#method.modify_shown_row)
25    /// alongside modifying through this function which will effectively mirror [`recreate_rows`](#method.recreate_rows)
26    /// without doing an expensive call.
27    ///
28    /// Bulk additions of rows is possible through this function with a single call to
29    /// [`add_modify_row`](#method.add_modify_row) at the end.
30    ///
31    /// # Parameters:
32    /// - A closure that provides a mutable reference to the rows and optionally returns a new row.
33    ///   If a row is returned, it will be added to the table.
34    ///
35    /// # Auto Reload:
36    /// - Use [`auto_reload`](#method.auto_reload) to automatically refresh the UI after a specified
37    ///   number of row modifications or additions.
38    ///
39    /// # Returns
40    /// * `Option<i64>` - The row id that is used internally for the new row
41    ///
42    /// # Example:
43    /// ```rust,ignore
44    /// let new_row_id = table.add_modify_row(|rows| {
45    ///     let my_row = rows.get_mut(row_id).unwrap();
46    ///     // modify your row as necessary
47    ///
48    ///     let new_row = MyRow {
49    ///         // Define your row values
50    ///     };
51    ///     Some(new_row) // Optionally add a new row
52    /// });
53    /// ```
54    pub fn add_modify_row<Fn>(&mut self, table: Fn) -> Option<i64>
55    where
56        Fn: FnOnce(&mut HashMap<i64, SelectableRow<Row, F>>) -> Option<Row>,
57    {
58        let new_row = table(&mut self.rows);
59
60        let mut to_return = None;
61
62        if let Some(row) = new_row {
63            let selected_columns = HashSet::new();
64            let new_row = SelectableRow {
65                row_data: row,
66                id: self.last_id_used,
67                selected_columns,
68            };
69            to_return = Some(self.last_id_used);
70            self.rows.insert(new_row.id, new_row);
71            self.last_id_used += 1;
72        }
73
74        let reload = self.auto_reload.increment_count();
75
76        if reload {
77            self.recreate_rows();
78        }
79        to_return
80    }
81
82    /// Modify a single row by its ID
83    ///
84    /// Changes go to the source-of-truth row storage. Call [`recreate_rows`](SelectableTable::recreate_rows)
85    /// afterwards to reflect the change in the UI, or rely on `auto_reload`.
86    ///
87    /// # Example:
88    /// ```rust,ignore
89    /// table.update_row(42, |row| { row.name = "new name".into(); });
90    /// ```
91    pub fn update_row(&mut self, id: i64, f: impl FnOnce(&mut Row)) {
92        self.add_modify_row(|rows| {
93            if let Some(row) = rows.get_mut(&id) {
94                f(&mut row.row_data);
95            }
96            None
97        });
98    }
99
100    /// Modify only the rows currently displayed in the UI.
101    ///
102    /// This provides direct access to the currently formatted rows for lightweight updates.
103    ///
104    /// # Important:
105    /// - This does **not** require calling [`recreate_rows`](SelectableTable::recreate_rows) to reflect changes in the UI.
106    /// - **Do not delete rows** from inside this closure.
107    /// - Changes **evaporate** on the next [`recreate_rows`](SelectableTable::recreate_rows) call.
108    ///   To persist changes, also call [`update_row`](Self::update_row) or [`add_modify_row`](Self::add_modify_row).
109    /// - Does not contribute toward `auto_reload` count.
110    ///
111    /// # When to use:
112    /// - When you need a modification visible immediately without an expensive recreate.
113    ///
114    /// # Example:
115    /// ```rust,ignore
116    /// table.patch_visible_row(|formatted_rows, indexed_ids| {
117    ///     let row_id = 0;
118    ///     let target_index = indexed_ids.get(&row_id).unwrap();
119    ///     let row = formatted_rows.get_mut(*target_index).unwrap();
120    ///     row.row_data.name = "updated".into();
121    /// });
122    /// ```
123    pub fn patch_visible_row<Fn>(&mut self, mut f: Fn)
124    where
125        Fn: FnMut(&mut Vec<SelectableRow<Row, F>>, &HashMap<i64, usize>),
126    {
127        f(&mut self.formatted_rows, &self.indexed_ids);
128    }
129
130    /// Modify only the rows currently displayed in the UI.
131    ///
132    /// This provides direct access to the currently formatted rows for lightweight updates.
133    ///
134    /// # Important:
135    /// - This does **not** require calling [`recreate_rows`](#method.recreate_rows) to reflect changes in the UI.
136    /// - **Do not delete rows** from inside this closure — doing so will **cause a panic** and break internal assumptions.
137    ///   To safely delete a row, use [`add_modify_row`](#method.add_modify_row) and then call [`recreate_rows`](#method.recreate_rows) or [`recreate_rows_no_unselect`](#method.recreate_rows_no_unselect).
138    /// - Can be used to update a row and show it immediately without having to do an expensive [`recreate_rows`](#method.recreate_rows).
139    ///   You can then immediately call [`add_modify_row`](#method.add_modify_row) to do the same
140    ///   update so later calls to [`recreate_rows`](#method.recreate_rows) or
141    ///   [`recreate_rows_no_unselect`](#method.recreate_rows_no_unselect) does not revert the changes.
142    /// - Does not contribute toward [`auto_reload`](#method.auto_reload) count.
143    ///
144    /// # Parameters:
145    /// - A closure that provides a mutable reference to the currently formatted rows and an index map.
146    ///
147    /// # When to use:
148    /// - Use when you need to modify a data that is currently displayed without having to call
149    ///   [`recreate_rows`](#method.recreate_rows).
150    /// - When you need to make a temporary change to the data displayed in the UI. To persist any
151    ///   changes, use [`add_modify_row`](#method.add_modify_row).
152    ///
153    /// # How to use:
154    /// 1. Get the row ID you want to modify.
155    /// 2. Use the index map to get the index of the row in the formatted rows.
156    /// 3. Use the index to get a mutable reference to the row.
157    /// 4. Safely modify the row contents.
158    /// 5. In the next frame load, the modified data is visible immediately without any additional
159    ///    calls.
160    ///
161    /// # Example:
162    /// ```rust,ignore
163    /// table.modify_shown_row(|formatted_rows, indexed_ids| {
164    ///     let row_id = 0;
165    ///     let target_index = indexed_ids.get(&row_id).unwrap();
166    ///     let row = formatted_rows.get_mut(*target_index).unwrap();
167    ///     // Safely modify row contents here
168    /// });
169    /// ```
170    #[deprecated = "use `patch_visible_row` instead"]
171    pub fn modify_shown_row<Fn>(&mut self, mut rows: Fn)
172    where
173        Fn: FnMut(&mut Vec<SelectableRow<Row, F>>, &HashMap<i64, usize>),
174    {
175        rows(&mut self.formatted_rows, &self.indexed_ids);
176    }
177
178    /// Adds a new row to the bottom of the table without applying any sorting logic.
179    ///
180    /// This method inserts the row as-is at the end of the table, assigns it a unique ID, and
181    /// returns it as a `SelectableRow`. This does **not**
182    /// require calling [`recreate_rows`](#method.recreate_rows) for the row to appear in the UI.
183    ///
184    /// # Important:
185    /// - This method does not contribute toward [`auto_reload`](#method.auto_reload) count.
186    /// - Later calls to [`recreate_rows`](#method.recreate_rows) or
187    ///   [`recreate_rows_no_unselect`](#method.recreate_rows_no_unselect) will sort the row again.
188    ///
189    /// # Parameters:
190    /// - `row`: The data to insert into the table.
191    ///
192    /// # Returns:
193    /// - `SelectableRow<Row, F>`: The newly added row wrapped in a `SelectableRow`.
194    ///
195    /// # Example:
196    /// ```rust,ignore
197    /// let row = Row::new(vec![cell1, cell2, cell3]);
198    /// let added_row = table.add_unsorted_row(row);
199    /// ```
200    pub fn add_unsorted_row(&mut self, row: Row) -> SelectableRow<Row, F> {
201        let selected_columns = HashSet::new();
202        let new_row = SelectableRow {
203            row_data: row,
204            id: self.last_id_used,
205            selected_columns,
206        };
207
208        self.formatted_rows.push(new_row.clone());
209        self.indexed_ids
210            .insert(new_row.id, self.formatted_rows.len() - 1);
211        self.rows.insert(new_row.id, new_row.clone());
212        self.last_id_used += 1;
213        new_row
214    }
215
216    /// Sort the rows to the current sorting order and column and save them for later reuse
217    pub(crate) fn sort_rows(&mut self) {
218        let mut row_data: Vec<SelectableRow<Row, F>> =
219            self.rows.par_iter().map(|(_, v)| v.clone()).collect();
220
221        row_data.par_sort_by(|a, b| {
222            let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data);
223            match self.sort_order {
224                SortOrder::Ascending => ordering,
225                SortOrder::Descending => ordering.reverse(),
226            }
227        });
228
229        let indexed_data = row_data
230            .par_iter()
231            .enumerate()
232            .map(|(index, row)| (row.id, index))
233            .collect();
234
235        self.indexed_ids = indexed_data;
236        self.formatted_rows = row_data;
237    }
238}