egui_selectable_table/
row_modification.rs

1use egui::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    /// # Parameters:
25    /// - `table`: A closure that takes a mutable reference to the rows and optionally returns a new row.
26    ///   If a row is returned, it will be added to the table.
27    ///
28    /// # Auto Reload:
29    /// - Use [`auto_reload`](#method.auto_reload) to automatically refresh the UI after a specified
30    ///   number of row modifications or additions.
31    ///
32    /// # Returns
33    /// * `Option<i64>` - The row id that is used internally for the new row
34    ///
35    /// # Example:
36    /// ```rust,ignore
37    /// let new_row_id = table.add_modify_row(|rows| {
38    ///     let my_row = rows.get_mut(row_id).unwrap();
39    ///     // modify your row as necessary
40    ///
41    ///     let new_row = MyRow {
42    ///         // Define your row values
43    ///     };
44    ///     Some(new_row) // Optionally add a new row
45    /// });
46    /// ```
47    pub fn add_modify_row<Fn>(&mut self, table: Fn) -> Option<i64>
48    where
49        Fn: FnOnce(&mut HashMap<i64, SelectableRow<Row, F>>) -> Option<Row>,
50    {
51        let new_row = table(&mut self.rows);
52
53        let mut to_return = None;
54
55        if let Some(row) = new_row {
56            let selected_columns = HashSet::new();
57            let new_row = SelectableRow {
58                row_data: row,
59                id: self.last_id_used,
60                selected_columns,
61            };
62            to_return = Some(self.last_id_used);
63            self.rows.insert(new_row.id, new_row);
64            self.last_id_used += 1;
65        }
66
67        let reload = self.auto_reload.increment_count();
68
69        if reload {
70            self.recreate_rows();
71        }
72        to_return
73    }
74
75    /// Modify only the rows currently displayed in the UI.
76    ///
77    /// This provides direct access to the currently formatted rows for lightweight updates.
78    ///
79    /// # Important:
80    /// - This does **not** require calling `recreate_rows` to reflect changes in the UI.
81    /// - **Do not delete rows** from inside this closure — doing so will **cause a panic** and break internal assumptions.
82    ///   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).
83    /// - Can be used alongside [`add_modify_row`](#method.add_modify_row) to show updated data immediately.
84    ///   When row recreation happens, the modified data will be preserved as long as it's updated via [`add_modify_row`](#method.add_modify_row).
85    /// - Does not contribute toward [`auto_reload`](#method.auto_reload) count.
86    ///
87    /// # Parameters:
88    /// - `table`: A closure that takes a mutable reference to the currently formatted rows and an index map.
89    ///
90    /// # Example:
91    /// ```rust,ignore
92    /// table.modify_shown_row(|formatted_rows, indexed_ids| {
93    ///     let row_id = 0;
94    ///     let target_index = indexed_ids.get(&row_id).unwrap();
95    ///     let row = formatted_rows.get_mut(*target_index).unwrap();
96    ///     // Safely modify row contents here
97    /// });
98    /// ```
99    pub fn modify_shown_row<Fn>(&mut self, mut rows: Fn)
100    where
101        Fn: FnMut(&mut Vec<SelectableRow<Row, F>>, &HashMap<i64, usize>),
102    {
103        rows(&mut self.formatted_rows, &self.indexed_ids);
104    }
105
106    /// Adds a new row to the bottom of the table without applying any sorting logic.
107    ///
108    /// This method inserts the row as-is at the end of the table, assigns it a unique ID, and
109    /// returns it as a `SelectableRow`. This does **not**
110    /// require calling [`recreate_rows`](#method.recreate_rows) for the row to appear in the UI.
111    ///
112    /// # Parameters:
113    /// - `row`: The data to insert into the table.
114    ///
115    /// # Returns:
116    /// - `SelectableRow<Row, F>`: The newly added row wrapped in a `SelectableRow`.
117    ///
118    /// # Example:
119    /// ```rust,ignore
120    /// let row = Row::new(vec![cell1, cell2, cell3]);
121    /// let added_row = table.add_unsorted_row(row);
122    /// ```
123    pub fn add_unsorted_row(&mut self, row: Row) -> SelectableRow<Row, F> {
124        let selected_columns = HashSet::new();
125        let new_row = SelectableRow {
126            row_data: row,
127            id: self.last_id_used,
128            selected_columns,
129        };
130
131        self.formatted_rows.push(new_row.clone());
132        self.indexed_ids
133            .insert(new_row.id, self.formatted_rows.len() - 1);
134        self.rows.insert(new_row.id, new_row.clone());
135        self.last_id_used += 1;
136        new_row
137    }
138
139    /// Sort the rows to the current sorting order and column and save them for later reuse
140    pub(crate) fn sort_rows(&mut self) {
141        let mut row_data: Vec<SelectableRow<Row, F>> =
142            self.rows.par_iter().map(|(_, v)| v.clone()).collect();
143
144        row_data.par_sort_by(|a, b| {
145            let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data);
146            match self.sort_order {
147                SortOrder::Ascending => ordering,
148                SortOrder::Descending => ordering.reverse(),
149            }
150        });
151
152        let indexed_data = row_data
153            .par_iter()
154            .enumerate()
155            .map(|(index, row)| (row.id, index))
156            .collect();
157
158        self.indexed_ids = indexed_data;
159        self.formatted_rows = row_data;
160    }
161}