From 0571bebe147eb2251d41639837fbb71a2566c6c1 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Fri, 4 Jul 2025 16:40:47 +0600 Subject: [PATCH 1/6] Add public row selection --- src/auto_reload.rs | 4 +-- src/auto_scroll.rs | 4 +-- src/lib.rs | 12 +++----- src/row_selection.rs | 72 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/auto_reload.rs b/src/auto_reload.rs index 1975972..9d4f55d 100644 --- a/src/auto_reload.rs +++ b/src/auto_reload.rs @@ -10,7 +10,7 @@ pub struct AutoReload { impl AutoReload { /// Increase the current reload count and return bool based on if it is equal or above the count it is /// supposed to reload at - pub(crate) fn increment_count(&mut self) -> bool { + pub(crate) const fn increment_count(&mut self) -> bool { self.reload_count += 1; if let Some(count) = self.reload_after { let reload = self.reload_count >= count; @@ -77,7 +77,7 @@ where /// table.set_auto_reload(Some(1000)); // Reload after 1000 updates. /// table.set_auto_reload(None); // Disable auto-reloading. /// ``` - pub fn set_auto_reload(&mut self, count: Option) { + pub const fn set_auto_reload(&mut self, count: Option) { self.auto_reload.reload_after = count; self.auto_reload.reload_count = 0; } diff --git a/src/auto_scroll.rs b/src/auto_scroll.rs index ad9e0b2..47c145e 100644 --- a/src/auto_scroll.rs +++ b/src/auto_scroll.rs @@ -180,7 +180,7 @@ where + ColumnOrdering, Conf: Default, { - pub(crate) fn update_scroll_offset(&mut self, offset: f32) { + pub(crate) const fn update_scroll_offset(&mut self, offset: f32) { self.auto_scroll.scroll_offset = offset; } @@ -248,7 +248,7 @@ where /// let new_scroll_settings = AutoScroll::new(true).max_speed(60.0); /// table.update_auto_scroll(new_scroll_settings); // Update the auto-scroll settings during runtime /// ``` - pub fn update_auto_scroll(&mut self, scroll: AutoScroll) { + pub const fn update_auto_scroll(&mut self, scroll: AutoScroll) { self.auto_scroll = scroll; } } diff --git a/src/lib.rs b/src/lib.rs index 7ca9153..2e12662 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,8 +179,7 @@ where /// # Type Parameters /// * `Row` - The type representing each row in the table. /// * `F` - A type used to identify columns, often an enum or field type. -/// * `Conf` - Configuration type for additional table settings passed by the user. This is made available anytime -/// when creating or modifying rows +/// * `Conf` - Configuration type for additional table settings passed by the user. This is made available anytime when creating or modifying rows pub struct SelectableTable where Row: Clone + Send + Sync, @@ -387,7 +386,7 @@ where table = table.vertical_scroll_offset(offset); ctx.request_repaint(); } - }; + } let output = table .header(20.0, |header| { @@ -416,7 +415,7 @@ where table = table.vertical_scroll_offset(offset); ctx.request_repaint(); } - }; + } let output = table .header(20.0, |header| { @@ -604,8 +603,7 @@ where /// /// # Performance: /// - Should be used sparingly for large datasets as frequent calls can lead to performance issues. - /// - Consider calling after every X amount row updates, based on how frequently new rows are being - /// added or use [`auto_scroll`](#method.auto_scroll) for automatic reload. + /// - Consider calling after every X amount row updates, based on how frequently new rows are being added or use [`auto_scroll`](#method.auto_scroll) for automatic reload. /// /// # Example: /// ```rust,ignore @@ -717,7 +715,7 @@ where /// /// # Returns: /// - `usize`: The number of rows that are formatted and ready for display. - pub fn total_displayed_rows(&self) -> usize { + pub const fn total_displayed_rows(&self) -> usize { self.formatted_rows.len() } diff --git a/src/row_selection.rs b/src/row_selection.rs index d372033..2f03b10 100644 --- a/src/row_selection.rs +++ b/src/row_selection.rs @@ -1,5 +1,6 @@ use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; use egui::Ui; +use std::fmt::Write as _; use std::hash::Hash; use crate::{ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable}; @@ -24,27 +25,64 @@ where self.active_columns.insert(column_name.clone()); self.active_rows.insert(id); + // Should never panic, if it does, either a library issue or it was used incorrectly let target_index = self.indexed_ids.get(&id).expect("target_index not found"); + let target_row = self + .formatted_rows + .get_mut(*target_index) + .expect("target_row not found"); if self.select_full_row { self.active_columns.extend(self.all_columns.clone()); - self.formatted_rows - .get_mut(*target_index) - .expect("Row not found") - .selected_columns - .extend(self.all_columns.clone()); + target_row.selected_columns.extend(self.all_columns.clone()); } else { - self.formatted_rows - .get_mut(*target_index) - .expect("Row not found") - .selected_columns - .insert(column_name.clone()); + target_row.selected_columns.insert(column_name.clone()); } self.active_rows.insert(id); } + /// Marks a row as selected, optionally selecting a specific column within the row. + /// + /// This method updates the table's internal state to mark the row with the given `id` as active. + /// If a column is provided, only that column is marked as selected for the row. + /// If no column is provided, all columns in the row are marked as selected. + /// + /// This method is a lower-level API intended for manually controlling selection state. + /// For general usage, prefer using the built-in selection behaviors. + /// + /// # Parameters: + /// - `id`: The unique identifier of the row to mark as selected. + /// - `column`: An optional reference to the column to be marked as selected within the row. If `None`, all columns in the row are selected. + /// + /// # Example: + /// ```rust,ignore + /// table.mark_row_as_selected(42, Some(&"Name")); + /// table.mark_row_as_selected(43, None); // Selects all columns in row 43 + /// ``` + pub fn mark_row_as_selected(&mut self, id: i64, column: Option<&F>) { + let Some(target_index) = self.indexed_ids.get(&id) else { + return; + }; + + let Some(target_row) = self.formatted_rows.get_mut(*target_index) else { + return; + }; + + self.active_rows.insert(id); + + if let Some(column_name) = column { + self.active_columns.insert(column_name.clone()); + + target_row.selected_columns.insert(column_name.clone()); + } else { + self.active_columns.extend(self.all_columns.clone()); + + target_row.selected_columns.extend(self.all_columns.clone()); + } + } + pub(crate) fn select_dragged_row_cell( &mut self, id: i64, @@ -225,7 +263,7 @@ where .get(index) .expect("Current row not found"); - // if for example drag started on row 5 and ended on row 10 but missed drag on row 7 + // If for example drag started on row 5 and ended on row 10 but missed drag on row 7 // Mark the rows as selected till the drag start row is hit (if recursively going that way) let unselected_row = if (check_previous && index >= drag_start) || (!check_previous && index <= drag_start) @@ -404,7 +442,7 @@ where let mut column_max_length = HashMap::new(); - // Iter through all the rows and find the rows that have at least one column as selected + // Iter through all the rows and find the rows that have at least one column as selected. // Keep track of the biggest length of a value of a column // active rows cannot be used here because hashset does not maintain an order. // So itering will give the rows in a different order than what is shown in the ui @@ -432,7 +470,7 @@ where let mut to_copy = String::new(); - // Target is to ensure a fixed length after each column value of a row + // Target is to ensure a fixed length after each column value of a row. // If for example highest len is 10 but the current row's // column value is 5, we will add the column value and add 5 more space after that // to ensure alignment @@ -444,7 +482,8 @@ where && row.selected_columns.contains(&ongoing_column) { let column_text = ongoing_column.column_text(&row.row_data); - row_text += &format!( + let _ = write!( + row_text, "{: Date: Fri, 4 Jul 2025 18:05:37 +0600 Subject: [PATCH 2/6] Add api for getting total selected row and new unsorted row --- src/lib.rs | 133 +++++++--------------------------- src/row_modification.rs | 157 ++++++++++++++++++++++++++++++++++++++++ src/row_selection.rs | 14 ++++ 3 files changed, 196 insertions(+), 108 deletions(-) create mode 100644 src/row_modification.rs diff --git a/src/lib.rs b/src/lib.rs index 2e12662..f4e2795 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod auto_reload; mod auto_scroll; +mod row_modification; mod row_selection; use auto_reload::AutoReload; @@ -7,7 +8,6 @@ pub use auto_scroll::AutoScroll; use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; use egui::{Event, Key, Label, Response, ScrollArea, Sense, Ui}; use egui_extras::{Column, TableBuilder, TableRow}; -use rayon::prelude::*; use std::cmp::Ordering; use std::hash::Hash; @@ -233,9 +233,10 @@ where /// Additional Parameters passed by you, available when creating new rows or header. Can /// contain anything implementing the `Default` trait pub config: Conf, - /// Whether to add the row serial column to the table add_serial_column: bool, + /// The row height for the table, defaults to 25.0 + row_height: f32, } impl SelectableTable @@ -293,6 +294,7 @@ where horizontal_scroll: false, config: Conf::default(), add_serial_column: false, + row_height: 25.0, } } @@ -393,7 +395,7 @@ where self.build_head(header); }) .body(|body| { - body.rows(25.0, self.formatted_rows.len(), |row| { + body.rows(self.row_height, self.formatted_rows.len(), |row| { let index = row.index(); self.build_body(row, index); }); @@ -422,7 +424,7 @@ where self.build_head(header); }) .body(|body| { - body.rows(25.0, self.formatted_rows.len(), |row| { + body.rows(self.row_height, self.formatted_rows.len(), |row| { let index = row.index(); self.build_body(row, index); }); @@ -477,110 +479,6 @@ where self.handle_table_body(row, &row_data); } - /// Modify or add rows to the table. Changes are not immediately reflected in the UI. - /// You must call [`recreate_rows`](#method.recreate_rows) to apply these changes visually. - /// - /// # Parameters: - /// - `table`: A closure that takes a mutable reference to the rows and optionally returns a new row. - /// If a row is returned, it will be added to the table. - /// - /// # Auto Reload: - /// - Use [`auto_reload`](#method.auto_reload) to automatically refresh the UI after a specified - /// number of row modifications or additions. - /// - /// # Returns - /// * `Option` - The row id that is used internally for the new row - /// - /// # Example: - /// ```rust,ignore - /// let new_row_id = table.add_modify_row(|rows| { - /// let my_row = rows.get_mut(row_id).unwrap(); - /// // modify your row as necessary - /// - /// let new_row = MyRow { - /// // Define your row values - /// }; - /// Some(new_row) // Optionally add a new row - /// }); - /// ``` - pub fn add_modify_row(&mut self, table: Fn) -> Option - where - Fn: FnOnce(&mut HashMap>) -> Option, - { - let new_row = table(&mut self.rows); - - let mut to_return = None; - - if let Some(row) = new_row { - let selected_columns = HashSet::new(); - let new_row = SelectableRow { - row_data: row, - id: self.last_id_used, - selected_columns, - }; - to_return = Some(self.last_id_used); - self.rows.insert(new_row.id, new_row); - self.last_id_used += 1; - } - - let reload = self.auto_reload.increment_count(); - - if reload { - self.recreate_rows(); - } - to_return - } - - /// Modify only the rows currently displayed in the UI. - /// - /// # Important: - /// - This does not require calling `recreate_rows` to reflect changes. - /// - Should not be used when rows are frequently recreated, as data might be lost. - /// - Does not contribute toward `auto_reload` count. - /// - /// # Parameters: - /// - `table`: A closure that takes a mutable reference to the currently formatted rows and an index map. - /// - /// # Example: - /// ```rust,ignore - /// table.modify_shown_row(|formatted_rows, indexed_ids| { - /// let row_id = 0; - /// let target_index = indexed_ids.get(row_id).unwrap(); - /// let row = formatted_rows.get_mut(target_index).unwrap(); - /// /* modify rows */ - /// - /// }); - /// ``` - pub fn modify_shown_row(&mut self, mut rows: Fn) - where - Fn: FnMut(&mut Vec>, &HashMap), - { - rows(&mut self.formatted_rows, &self.indexed_ids); - } - - /// Sort the rows to the current sorting order and column and save them for later reuse - fn sort_rows(&mut self) { - let mut row_data: Vec> = - self.rows.par_iter().map(|(_, v)| v.clone()).collect(); - - row_data.par_sort_by(|a, b| { - let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data); - match self.sort_order { - SortOrder::Ascending => ordering, - SortOrder::Descending => ordering.reverse(), - } - }); - - let indexed_data = row_data - .par_iter() - .enumerate() - .map(|(index, row)| (row.id, index)) - .collect(); - - self.indexed_ids = indexed_data; - self.formatted_rows = row_data; - } - /// Change the current sort order from ascending to descending and vice versa. Will unselect /// all selected rows fn change_sort_order(&mut self) { @@ -777,4 +675,23 @@ where self.horizontal_scroll = true; self } + + /// Sets the height rows in the table. + /// + /// # Parameters: + /// - `height`: The desired height for each row in logical points. + /// + /// # Returns: + /// - `Self`: The modified table with the specified row height applied. + /// + /// # Example: + /// ```rust,ignore + /// let table = SelectableTable::new(vec![col1, col2, col3]) + /// .row_height(24.0); + /// ``` + #[must_use] + pub const fn row_height(mut self, height: f32) -> Self { + self.row_height = height; + self + } } diff --git a/src/row_modification.rs b/src/row_modification.rs new file mode 100644 index 0000000..be3f1cc --- /dev/null +++ b/src/row_modification.rs @@ -0,0 +1,157 @@ +use egui::ahash::{HashMap, HashSet, HashSetExt}; +use rayon::prelude::*; +use std::hash::Hash; + +use crate::{ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable, SortOrder}; + +impl SelectableTable +where + Row: Clone + Send + Sync, + F: Eq + + Hash + + Clone + + Ord + + Send + + Sync + + Default + + ColumnOperations + + ColumnOrdering, + Conf: Default, +{ + /// Modify or add rows to the table. Changes are not immediately reflected in the UI. + /// You must call [`recreate_rows`](#method.recreate_rows) to apply these changes visually. + /// + /// # Parameters: + /// - `table`: A closure that takes a mutable reference to the rows and optionally returns a new row. + /// If a row is returned, it will be added to the table. + /// + /// # Auto Reload: + /// - Use [`auto_reload`](#method.auto_reload) to automatically refresh the UI after a specified + /// number of row modifications or additions. + /// + /// # Returns + /// * `Option` - The row id that is used internally for the new row + /// + /// # Example: + /// ```rust,ignore + /// let new_row_id = table.add_modify_row(|rows| { + /// let my_row = rows.get_mut(row_id).unwrap(); + /// // modify your row as necessary + /// + /// let new_row = MyRow { + /// // Define your row values + /// }; + /// Some(new_row) // Optionally add a new row + /// }); + /// ``` + pub fn add_modify_row(&mut self, table: Fn) -> Option + where + Fn: FnOnce(&mut HashMap>) -> Option, + { + let new_row = table(&mut self.rows); + + let mut to_return = None; + + if let Some(row) = new_row { + let selected_columns = HashSet::new(); + let new_row = SelectableRow { + row_data: row, + id: self.last_id_used, + selected_columns, + }; + to_return = Some(self.last_id_used); + self.rows.insert(new_row.id, new_row); + self.last_id_used += 1; + } + + let reload = self.auto_reload.increment_count(); + + if reload { + self.recreate_rows(); + } + to_return + } + + /// Modify only the rows currently displayed in the UI. + /// + /// # Important: + /// - This does not require calling `recreate_rows` to reflect changes. + /// - Should not be used when rows are frequently recreated, as data might be lost. + /// - Does not contribute toward `auto_reload` count. + /// + /// # Parameters: + /// - `table`: A closure that takes a mutable reference to the currently formatted rows and an index map. + /// + /// # Example: + /// ```rust,ignore + /// table.modify_shown_row(|formatted_rows, indexed_ids| { + /// let row_id = 0; + /// let target_index = indexed_ids.get(row_id).unwrap(); + /// let row = formatted_rows.get_mut(target_index).unwrap(); + /// /* modify rows */ + /// + /// }); + /// ``` + pub fn modify_shown_row(&mut self, mut rows: Fn) + where + Fn: FnMut(&mut Vec>, &HashMap), + { + rows(&mut self.formatted_rows, &self.indexed_ids); + } + + /// Adds a new row to the bottom of the table without applying any sorting logic. + /// + /// This method inserts the row as-is at the end of the table, assigns it a unique ID, and + /// returns it as a `SelectableRow`. This does **not** + /// require calling `recreate_rows` for the row to appear in the UI. + /// + /// # Parameters: + /// - `row`: The data to insert into the table. + /// + /// # Returns: + /// - `SelectableRow`: The newly added row wrapped in a `SelectableRow`. + /// + /// # Example: + /// ```rust,ignore + /// let row = Row::new(vec![cell1, cell2, cell3]); + /// let added_row = table.add_unsorted_row(row); + /// ``` + pub fn add_unsorted_row(&mut self, row: Row) -> SelectableRow { + let selected_columns = HashSet::new(); + let new_row = SelectableRow { + row_data: row, + id: self.last_id_used, + selected_columns, + }; + + self.formatted_rows.push(new_row.clone()); + self.indexed_ids + .insert(new_row.id, self.formatted_rows.len() - 1); + self.rows.insert(new_row.id, new_row.clone()); + self.last_id_used += 1; + new_row + } + + /// Sort the rows to the current sorting order and column and save them for later reuse + pub(crate) fn sort_rows(&mut self) { + let mut row_data: Vec> = + self.rows.par_iter().map(|(_, v)| v.clone()).collect(); + + row_data.par_sort_by(|a, b| { + let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data); + match self.sort_order { + SortOrder::Ascending => ordering, + SortOrder::Descending => ordering.reverse(), + } + }); + + let indexed_data = row_data + .par_iter() + .enumerate() + .map(|(index, row)| (row.id, index)) + .collect(); + + self.indexed_ids = indexed_data; + self.formatted_rows = row_data; + } +} diff --git a/src/row_selection.rs b/src/row_selection.rs index 2f03b10..9988ae4 100644 --- a/src/row_selection.rs +++ b/src/row_selection.rs @@ -539,4 +539,18 @@ where pub const fn set_select_full_row(&mut self, status: bool) { self.select_full_row = status; } + + /// Returns the total number of currently selected rows. + /// + /// # Returns: + /// - `usize`: The number of selected rows. + /// + /// # Example: + /// ```rust,ignore + /// let selected_count = table.get_total_selected_rows(); + /// println!("{} rows selected", selected_count); + /// ``` + pub fn get_total_selected_rows(&mut self) -> usize { + self.active_rows.len() + } } From 28052c0e906419eb73c33bd8e90a06a6fd0dc11d Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Fri, 4 Jul 2025 18:14:45 +0600 Subject: [PATCH 3/6] Modify to multi-row selection --- src/row_selection.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/row_selection.rs b/src/row_selection.rs index 9988ae4..aa6b798 100644 --- a/src/row_selection.rs +++ b/src/row_selection.rs @@ -43,25 +43,25 @@ where self.active_rows.insert(id); } - /// Marks a row as selected, optionally selecting a specific column within the row. + /// Marks a row as selected, optionally selecting specific columns within the row. /// /// This method updates the table's internal state to mark the row with the given `id` as active. - /// If a column is provided, only that column is marked as selected for the row. - /// If no column is provided, all columns in the row are marked as selected. + /// If a list of columns is provided, only those columns are marked as selected for the row. + /// If no column list is provided, all columns in the row are marked as selected. /// /// This method is a lower-level API intended for manually controlling selection state. /// For general usage, prefer using the built-in selection behaviors. /// /// # Parameters: /// - `id`: The unique identifier of the row to mark as selected. - /// - `column`: An optional reference to the column to be marked as selected within the row. If `None`, all columns in the row are selected. + /// - `column`: An optional list of columns (`Vec`) to mark as selected within the row. If `None`, all columns are selected. /// /// # Example: /// ```rust,ignore - /// table.mark_row_as_selected(42, Some(&"Name")); + /// table.mark_row_as_selected(42, Some(vec!["Name", "Age"])); /// table.mark_row_as_selected(43, None); // Selects all columns in row 43 /// ``` - pub fn mark_row_as_selected(&mut self, id: i64, column: Option<&F>) { + pub fn mark_row_as_selected(&mut self, id: i64, column: Option>) { let Some(target_index) = self.indexed_ids.get(&id) else { return; }; @@ -72,10 +72,10 @@ where self.active_rows.insert(id); - if let Some(column_name) = column { - self.active_columns.insert(column_name.clone()); + if let Some(column_list) = column { + self.active_columns.extend(column_list.clone()); - target_row.selected_columns.insert(column_name.clone()); + target_row.selected_columns.extend(column_list); } else { self.active_columns.extend(self.all_columns.clone()); From 3ec6db28a09d89696344cd655712936564b09407 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Fri, 4 Jul 2025 19:32:42 +0600 Subject: [PATCH 4/6] Doc updates --- src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++-- src/row_modification.rs | 24 ++++++++++++--------- src/row_selection.rs | 4 ---- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4e2795..93ace6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -499,9 +499,15 @@ where /// Recreates the rows shown in the UI for the next frame load. /// + /// # Important: + /// - Any direct modifications made using [`modify_shown_row`](#method.modify_shown_row) + /// will be **cleared** when this is called. + /// To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead. + /// /// # Performance: - /// - Should be used sparingly for large datasets as frequent calls can lead to performance issues. - /// - Consider calling after every X amount row updates, based on how frequently new rows are being added or use [`auto_scroll`](#method.auto_scroll) for automatic reload. + /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues. + /// - Consider calling after every X number of row updates, depending on update frequency, + /// or use [`auto_reload`](#method.auto_reload) for automatic reload. /// /// # Example: /// ```rust,ignore @@ -514,6 +520,42 @@ where self.sort_rows(); } + /// Recreates the rows shown in the UI for the next frame load. + /// + /// This function refreshes the internal row state by clearing and re-sorting the rows + /// similar to [`recreate_rows`](#method.recreate_rows), but it **preserves** the currently + /// selected rows and re-applies the active column selection to them. + /// + /// Useful when the UI needs to be refreshed without resetting user interaction state. + /// + /// # Important: + /// - Any direct modifications made to `formatted_rows` using [`modify_shown_row`](#method.modify_shown_row) + /// will be **cleared** when this is called. + /// To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead. + /// + /// # Performance: + /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues. + /// - Consider calling after every X number of row updates, depending on update frequency, + /// or use [`auto_reload`](#method.auto_reload) for automatic reload. + /// + /// # Example: + /// ```rust,ignore + /// table.recreate_rows_no_unselect(); + /// ``` + pub fn recreate_rows_no_unselect(&mut self) { + self.formatted_rows.clear(); + self.sort_rows(); + + for row in &self.active_rows { + let Some(target_index) = self.indexed_ids.get(row) else { + continue; + }; + self.formatted_rows[*target_index] + .selected_columns + .clone_from(&self.active_columns); + } + } + /// The first column that was passed by the user fn first_column(&self) -> F { self.all_columns[0].clone() diff --git a/src/row_modification.rs b/src/row_modification.rs index be3f1cc..a8b031e 100644 --- a/src/row_modification.rs +++ b/src/row_modification.rs @@ -19,7 +19,7 @@ where Conf: Default, { /// Modify or add rows to the table. Changes are not immediately reflected in the UI. - /// You must call [`recreate_rows`](#method.recreate_rows) to apply these changes visually. + /// You must call [`recreate_rows`](#method.recreate_rows) or [`recreate_rows_no_unselect`](#method.recreate_rows_no_unselect) to apply these changes visually. /// /// # Parameters: /// - `table`: A closure that takes a mutable reference to the rows and optionally returns a new row. @@ -74,10 +74,15 @@ where /// Modify only the rows currently displayed in the UI. /// + /// This provides direct access to the currently formatted rows for lightweight updates. + /// /// # Important: - /// - This does not require calling `recreate_rows` to reflect changes. - /// - Should not be used when rows are frequently recreated, as data might be lost. - /// - Does not contribute toward `auto_reload` count. + /// - This does **not** require calling `recreate_rows` to reflect changes in the UI. + /// - **Do not delete rows** from inside this closure — doing so will **cause a panic** and break internal assumptions. + /// 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). + /// - Can be used alongside [`add_modify_row`](#method.add_modify_row) to show updated data immediately. + /// When row recreation happens, the modified data will be preserved as long as it's updated via [`add_modify_row`](#method.add_modify_row). + /// - Does not contribute toward [`auto_reload`](#method.auto_reload) count. /// /// # Parameters: /// - `table`: A closure that takes a mutable reference to the currently formatted rows and an index map. @@ -85,11 +90,10 @@ where /// # Example: /// ```rust,ignore /// table.modify_shown_row(|formatted_rows, indexed_ids| { - /// let row_id = 0; - /// let target_index = indexed_ids.get(row_id).unwrap(); - /// let row = formatted_rows.get_mut(target_index).unwrap(); - /// /* modify rows */ - /// + /// let row_id = 0; + /// let target_index = indexed_ids.get(&row_id).unwrap(); + /// let row = formatted_rows.get_mut(*target_index).unwrap(); + /// // Safely modify row contents here /// }); /// ``` pub fn modify_shown_row(&mut self, mut rows: Fn) @@ -103,7 +107,7 @@ where /// /// This method inserts the row as-is at the end of the table, assigns it a unique ID, and /// returns it as a `SelectableRow`. This does **not** - /// require calling `recreate_rows` for the row to appear in the UI. + /// require calling [`recreate_rows`](#method.recreate_rows) for the row to appear in the UI. /// /// # Parameters: /// - `row`: The data to insert into the table. diff --git a/src/row_selection.rs b/src/row_selection.rs index aa6b798..ba56ff2 100644 --- a/src/row_selection.rs +++ b/src/row_selection.rs @@ -45,13 +45,9 @@ where /// Marks a row as selected, optionally selecting specific columns within the row. /// - /// This method updates the table's internal state to mark the row with the given `id` as active. /// If a list of columns is provided, only those columns are marked as selected for the row. /// If no column list is provided, all columns in the row are marked as selected. /// - /// This method is a lower-level API intended for manually controlling selection state. - /// For general usage, prefer using the built-in selection behaviors. - /// /// # Parameters: /// - `id`: The unique identifier of the row to mark as selected. /// - `column`: An optional list of columns (`Vec`) to mark as selected within the row. If `None`, all columns are selected. From 3eefc108b14bbf343a625f58e2e0f6ec54ceecdb Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Fri, 4 Jul 2025 19:32:50 +0600 Subject: [PATCH 5/6] Update demo --- demo/src/app.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/demo/src/app.rs b/demo/src/app.rs index a81239d..76280aa 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -83,6 +83,20 @@ impl App for MainWindow { { self.table.set_select_full_row(self.select_entire_row); }; + ui.separator(); + if ui.button("Add unsorted row at the bottom").clicked() { + let new_row = TableRow { + field_1: self.row_num, + field_2: self.row_num as i64 * 10, + field_3: format!("I'm unsorted row with row num: {}", self.row_num), + field_4: format!("field 4 with row num: {}", self.row_num), + field_5: format!("field 5 with row num: {}", self.row_num), + field_6: format!("field 6 with row num: {}", self.row_num), + create_count: 0, + }; + self.row_num += 1; + self.table.add_unsorted_row(new_row); + } }); ui.separator(); ui.horizontal(|ui| { @@ -257,7 +271,7 @@ impl ColumnOperations for TableColumns { // reloaded. After there is no more row creation, auto reload is turned off and won't // reload until next manual intervention. While no more rows are being created, we are // modifying the rows directly that are being shown in the UI which is much less - // expensive and gets shown to the UI immediately + // expensive and gets shown to the UI immediately. // Continue to update the persistent row data to ensure once reload happens, the // previous count data is not lost table.add_modify_row(|table| { @@ -293,6 +307,10 @@ impl ColumnOperations for TableColumns { table.copy_selected_cells(ui); ui.close_menu(); } + if table.get_total_selected_rows() <= 1 && ui.button("Mark row as selected").clicked() { + ui.close_menu(); + table.mark_row_as_selected(row_id, None); + } }); resp } From fdf276d7407a74d1ce41165d262ff5817a746104 Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Fri, 4 Jul 2025 19:40:27 +0600 Subject: [PATCH 6/6] Update lib and egui version --- Cargo.toml | 6 +++--- demo/Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ad9f50..f5ffe30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui-selectable-table" -version = "0.2.0" +version = "0.2.1" edition = "2021" authors = ["TheRustyPickle "] readme = "README.md" @@ -15,8 +15,8 @@ license = "MIT" exclude = ["/demo", "/.github"] [dependencies] -egui = { version = "0.31.0", default-features = false, features = ["rayon"] } -egui_extras = { version = "0.31.0", default-features = false } +egui = { version = "0.31.1", default-features = false, features = ["rayon"] } +egui_extras = { version = "0.31.1", default-features = false } rayon = "1.10.0" [lints.rust] diff --git a/demo/Cargo.toml b/demo/Cargo.toml index 967edcd..47b3c7b 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -eframe = "0.31.0" -egui = "0.31.0" +eframe = "0.31.1" +egui = "0.31.1" egui-selectable-table = { path = ".." } egui_extras = "0.31.0" strum = "0.27.0"