Skip to main content

egui_selectable_table/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3mod auto_reload;
4mod auto_scroll;
5mod row_modification;
6mod row_selection;
7
8#[cfg(feature = "fuzzy-matching")]
9#[cfg_attr(docsrs, doc(cfg(feature = "fuzzy-matching")))]
10mod fuzzy_matcher;
11
12use auto_reload::AutoReload;
13pub use auto_scroll::AutoScroll;
14use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
15use egui::{Event, Key, Label, Response, ScrollArea, Sense, Ui};
16use egui_extras::{Column, TableBuilder, TableRow};
17use std::cmp::Ordering;
18use std::hash::Hash;
19
20#[cfg(feature = "fuzzy-matching")]
21use nucleo_matcher::Matcher;
22
23/// Enum representing the possible sort orders for table columns.
24#[derive(Default, Clone, Copy)]
25pub enum SortOrder {
26    /// Sorts in ascending order (e.g., A to Z or 1 to 10).
27    #[default]
28    Ascending,
29    /// Sorts in descending order (e.g., Z to A or 10 to 1).
30    Descending,
31}
32
33/// Trait for defining how to order rows based on a specific column.
34///
35/// This trait should be implemented by users to specify how rows should be
36/// compared for sorting purposes. The implementation can vary depending on
37/// the type of column. For instance, string comparisons or numeric comparisons
38/// can be handled differently depending on the column. Should only be implemented for Ascending
39/// ordering, in case of Descending, it is handled internally.
40///
41/// # Example
42/// Suppose you have a struct `MyRow` with fields like `user_id`, `name`, and `username`.
43/// You could implement this trait for each column to specify how rows should be compared.
44///
45/// ```rust,ignore
46/// impl ColumnOrdering<MyRow> for ColumnName {
47///     fn order_by(&self, row_1: &MyRow, row_2: &MyRow) -> Ordering {
48///         match self {
49///             ColumnName::UserID => row_1.user_id.cmp(&row_2.user_id),
50///             ColumnName::Name => row_1.name.cmp(&row_2.name),
51///             ColumnName::Username => row_1.username.cmp(&row_2.username),
52///         }
53///     }
54/// }
55/// ```
56pub trait ColumnOrdering<Row>
57where
58    Row: Clone + Send + Sync,
59{
60    /// Compare two rows and return the ordering result (`Ordering`).
61    ///
62    /// This function defines how to order two rows based on the specific column.
63    /// It returns `Ordering::Less`, `Ordering::Equal`, or `Ordering::Greater`
64    /// to indicate whether `row_1` should be placed before, after, or at the same
65    /// position as `row_2` when sorting. Should only be implemented for ascending ordering, in
66    /// case of Descending, it is handled internally.
67    ///
68    /// # Arguments
69    /// * `row_1` - The first row for comparison.
70    /// * `row_2` - The second row for comparison.
71    ///
72    /// # Returns
73    /// * `Ordering` - Indicates the relative order between the two rows.
74    fn order_by(&self, row_1: &Row, row_2: &Row) -> Ordering;
75}
76
77/// Trait for defining column-specific operations in a table UI.
78///
79/// This trait allows users to define how each column should behave within a table.
80/// This includes how headers should be displayed, how each row in the table should be rendered,
81/// and how to extract column-specific text.
82///
83/// # Type Parameters:
84/// * `Row` - The type representing each row in the table.
85/// * `F` - A type that identifies columns, usually an enum or a field type.
86/// * `Conf` - Configuration type for the table, useful for passing additional settings.
87///
88/// # Requirements:
89/// You must implement this trait to specify the behavior of each column within
90/// the context of your table UI.
91pub trait ColumnOperations<Row, F, Conf>
92where
93    Row: Clone + Send + Sync,
94    F: Eq
95        + Hash
96        + Clone
97        + Ord
98        + Send
99        + Sync
100        + Default
101        + ColumnOperations<Row, F, Conf>
102        + ColumnOrdering<Row>,
103    Conf: Default,
104{
105    /// Create the header UI for this column.
106    ///
107    /// This function is responsible for creating the visual representation of the column header.
108    /// The `sort_order` argument indicates whether the column is currently used for sorting and, if so, in which
109    /// direction (ascending or descending). You can customize the header appearance based on
110    /// this information, for example by adding icons or text. Return `None` for no header.
111    ///
112    /// # Arguments
113    /// * `ui` - A mutable reference to the UI context.
114    /// * `sort_order` - An optional `SortOrder` representing the current sort state of the column.
115    /// * `table` - A mutable reference to the `SelectableTable`, allowing you to interact with the table state.
116    ///
117    /// # Returns
118    /// * `Option<Response>` - An optional response representing interaction with the UI.
119    fn create_header(
120        &self,
121        ui: &mut Ui,
122        sort_order: Option<SortOrder>,
123        table: &mut SelectableTable<Row, F, Conf>,
124    ) -> Option<Response>;
125
126    /// Create the UI for a specific row in this column.
127    ///
128    /// This function is responsible for rendering the content of this column for a given row.
129    /// It should handle user interactions like clicking or selection as necessary. Mutable table
130    /// access is provided for modifyiing other rows as necessary.
131    ///
132    /// # Arguments
133    /// * `ui` - A mutable reference to the UI context.
134    /// * `row` - A reference to the current `SelectableRow` for this table.
135    /// * `column_selected` - A boolean indicating whether this column is selected.
136    /// * `table` - A mutable reference to the `SelectableTable` for modifying table data
137    ///
138    /// # Returns
139    /// * `Response` - The result of the UI interaction for this row.
140    fn create_table_row(
141        &self,
142        ui: &mut Ui,
143        row: &SelectableRow<Row, F>,
144        column_selected: bool,
145        table: &mut SelectableTable<Row, F, Conf>,
146    ) -> Response;
147
148    /// Extract the text representation of the column for the given row.
149    ///
150    /// This function should return the appropriate text representation of this column
151    /// for the given row. It can be used to display the data in a simplified form, such
152    /// as for debugging or plain text rendering.
153    ///
154    /// # Arguments
155    /// * `row` - A reference to the row from which to extract the column text.
156    ///
157    /// # Returns
158    /// * `String` - The text representation of this column for the row.
159    fn column_text(&self, row: &Row) -> String;
160}
161
162/// Represents a row in a table with selectable columns.
163///
164/// This struct is used to store the data of a row along with its unique identifier (`id`)
165/// and the set of selected columns for this row.
166///
167/// # Type Parameters:
168/// * `Row` - The type representing the data stored in each row.
169/// * `F` - The type used to identify each column, typically an enum or a type with unique values.
170///
171/// # Fields:
172/// * `row_data` - The actual data stored in the row.
173/// * `id` - A unique identifier for the row.
174/// * `selected_columns` - A set of columns that are selected in this row.
175#[derive(Clone)]
176pub struct SelectableRow<Row, F>
177where
178    Row: Clone + Send + Sync,
179    F: Eq + Hash + Clone + Ord + Send + Sync + Default,
180{
181    pub row_data: Row,
182    pub id: i64,
183    pub selected_columns: HashSet<F>,
184}
185
186/// A table structure that hold data for performing selection on drag, sorting, and displaying rows and more.
187///
188/// # Type Parameters
189/// * `Row` - The type representing each row in the table.
190/// * `F` - A type used to identify columns, often an enum or field type.
191/// * `Conf` - Configuration type for additional table settings passed by the user. This is made available anytime when creating or modifying rows
192pub struct SelectableTable<Row, F, Conf>
193where
194    Row: Clone + Send + Sync,
195    F: Eq
196        + Hash
197        + Clone
198        + Ord
199        + Send
200        + Sync
201        + Default
202        + ColumnOperations<Row, F, Conf>
203        + ColumnOrdering<Row>,
204    Conf: Default,
205{
206    /// List of all columns available in the table.
207    all_columns: Vec<F>,
208    /// Maps each column to its index in the table for quick lookup.
209    column_number: HashMap<F, usize>,
210    /// Stores all rows in the table, keyed by their unique ID.
211    rows: HashMap<i64, SelectableRow<Row, F>>,
212    /// The current set of formatted rows for display.
213    formatted_rows: Vec<SelectableRow<Row, F>>,
214    /// The column currently being used to sort the table.
215    sorted_by: F,
216    /// The current sort order (ascending or descending).
217    sort_order: SortOrder,
218    /// Tracks where a drag operation started in the table, if any.
219    drag_started_on: Option<(i64, F)>,
220    /// The columns that have at least 1 row with the column as selected
221    active_columns: HashSet<F>,
222    /// The rows that have at least 1 column as selected
223    active_rows: HashSet<i64>,
224    /// The last row where the pointer was
225    last_active_row: Option<i64>,
226    /// The last column where the pointer was
227    last_active_column: Option<F>,
228    /// Whether the pointer moved from the dragged point at least once
229    beyond_drag_point: bool,
230    /// Map of the row IDs to the indices of `formatted_rows`
231    indexed_ids: HashMap<i64, usize>,
232    /// The last ID that was used for a new row in the table.
233    last_id_used: i64,
234    /// Handles auto scroll operation when dragging
235    auto_scroll: AutoScroll,
236    /// Handles auto recreating the displayed rows with the latest data
237    auto_reload: AutoReload,
238    /// Whether to select the entire row when dragging and selecting instead of a single cell
239    select_full_row: bool,
240    /// Whether to add a horizontal scrollbar
241    horizontal_scroll: bool,
242    /// Additional Parameters passed by you, available when creating new rows or header. Can
243    /// contain anything implementing the `Default` trait
244    pub config: Conf,
245    /// Whether to add the row serial column to the table
246    add_serial_column: bool,
247    /// The row height for the table, defaults to 25.0
248    row_height: f32,
249    /// The header height for the table, defaults to 20.0
250    header_height: f32,
251
252    /// The matcher used for fuzzy searching
253    #[cfg(feature = "fuzzy-matching")]
254    matcher: Matcher,
255    /// Whether to capture Ctrl+A for selecting all rows
256    no_ctrl_a_capture: bool,
257}
258
259impl<Row, F, Conf> SelectableTable<Row, F, Conf>
260where
261    Row: Clone + Send + Sync,
262    F: Eq
263        + Hash
264        + Clone
265        + Ord
266        + Send
267        + Sync
268        + Default
269        + ColumnOperations<Row, F, Conf>
270        + ColumnOrdering<Row>,
271    Conf: Default,
272{
273    /// Creates a new `SelectableTable` with the provided columns in a specified order.
274    ///
275    /// # Parameters:
276    /// - `columns`: A `Vec<F>` representing the columns. Columns must be passed in the correct order (e.g., 1 to 10).
277    ///
278    /// # Returns:
279    /// - A new instance of `SelectableTable`.
280    ///
281    /// # Example:
282    /// ```rust,ignore
283    /// let table = SelectableTable::new(vec![col1, col2, col3]);
284    /// ```
285    #[must_use]
286    pub fn new(columns: Vec<F>) -> Self {
287        let all_columns = columns.clone();
288        let mut column_number = HashMap::new();
289
290        for (index, col) in columns.into_iter().enumerate() {
291            column_number.insert(col, index);
292        }
293        Self {
294            all_columns,
295            column_number,
296            last_id_used: 0,
297            rows: HashMap::new(),
298            formatted_rows: Vec::new(),
299            sorted_by: F::default(),
300            sort_order: SortOrder::default(),
301            drag_started_on: None,
302            active_columns: HashSet::new(),
303            active_rows: HashSet::new(),
304            last_active_row: None,
305            last_active_column: None,
306            beyond_drag_point: false,
307            indexed_ids: HashMap::new(),
308            auto_scroll: AutoScroll::default(),
309            auto_reload: AutoReload::default(),
310            select_full_row: false,
311            horizontal_scroll: false,
312            config: Conf::default(),
313            add_serial_column: false,
314            row_height: 25.0,
315            header_height: 20.0,
316            #[cfg(feature = "fuzzy-matching")]
317            matcher: Matcher::default(),
318            no_ctrl_a_capture: false,
319        }
320    }
321
322    /// Updates the table's configuration with the given `conf`.
323    ///
324    /// # Parameters:
325    /// - `conf`: The new configuration of type `Conf`, which is user-defined and allows
326    ///   passing data to help with row/table modification.
327    ///
328    /// # Example:
329    /// ```rust,ignore
330    /// table.set_config(my_config);
331    /// ```
332    pub fn set_config(&mut self, conf: Conf) {
333        self.config = conf;
334    }
335
336    /// Sets a configuration in a builder-style pattern.
337    ///
338    /// # Parameters:
339    /// - `conf`: A configuration of type `Conf`. The user can pass any data to help with row creation or modification.
340    ///
341    /// # Returns:
342    /// - The updated `SelectableTable` with the new configuration applied.
343    ///
344    /// # Example:
345    /// ```rust,ignore
346    /// let table = SelectableTable::new(vec![col1, col2, col3]).config(my_config);
347    /// ```
348    #[must_use]
349    pub fn config(mut self, conf: Conf) -> Self {
350        self.config = conf;
351        self
352    }
353
354    /// Clears all rows from the table, including the displayed ones. No need for any additional
355    /// call to update the UI.
356    ///
357    /// # Example:
358    /// ```rust,ignore
359    /// table.clear_all_rows();
360    /// ```
361    pub fn clear_all_rows(&mut self) {
362        self.rows.clear();
363        self.formatted_rows.clear();
364        self.active_rows.clear();
365        self.active_columns.clear();
366        self.last_id_used = 0;
367    }
368
369    /// Displays the UI for the table and uses the provided `TableBuilder` for creating the table UI.
370    ///
371    /// # Parameters:
372    /// - `ui`: The UI context where the table will be rendered.
373    /// - A closure that provides and allows modification of `TableBuilder`. Build your own table
374    ///   and return it.
375    ///
376    /// # Example:
377    /// ```rust,ignore
378    /// table.show_ui(ui, |builder| builder.column(column1));
379    /// ```
380    pub fn show_ui<Fn>(&mut self, ui: &mut Ui, table_builder: Fn)
381    where
382        Fn: FnOnce(TableBuilder) -> TableBuilder,
383    {
384        let is_ctrl_pressed = ui.ctx().input(|i| i.modifiers.ctrl);
385        let key_a_pressed = ui.ctx().input(|i| i.key_pressed(Key::A));
386        let copy_initiated = ui.ctx().input(|i| i.events.contains(&Event::Copy));
387        let ctx = ui.ctx().clone();
388
389        if copy_initiated {
390            self.copy_selected_cells(ui);
391        }
392        if is_ctrl_pressed && key_a_pressed && !self.no_ctrl_a_capture {
393            self.select_all();
394        }
395
396        let pointer = ui.input(|i| i.pointer.hover_pos());
397        let max_rect = ui.max_rect();
398
399        if self.horizontal_scroll {
400            ScrollArea::horizontal().show(ui, |ui| {
401                let mut table = TableBuilder::new(ui);
402
403                if self.add_serial_column {
404                    table = table.column(Column::initial(25.0).clip(true));
405                }
406
407                table = table_builder(table);
408
409                if self.drag_started_on.is_some()
410                    && let Some(offset) = self.auto_scroll.start_scroll(max_rect, pointer)
411                {
412                    table = table.vertical_scroll_offset(offset);
413                    ctx.request_repaint();
414                }
415
416                let output = table
417                    .header(self.header_height, |header| {
418                        self.build_head(header);
419                    })
420                    .body(|body| {
421                        body.rows(self.row_height, self.formatted_rows.len(), |row| {
422                            let index = row.index();
423                            self.build_body(row, index);
424                        });
425                    });
426                let scroll_offset = output.state.offset.y;
427                self.update_scroll_offset(scroll_offset);
428            });
429        } else {
430            let mut table = TableBuilder::new(ui);
431
432            if self.add_serial_column {
433                table = table.column(Column::initial(25.0).clip(true));
434            }
435
436            table = table_builder(table);
437
438            if self.drag_started_on.is_some()
439                && let Some(offset) = self.auto_scroll.start_scroll(max_rect, pointer)
440            {
441                table = table.vertical_scroll_offset(offset);
442                ctx.request_repaint();
443            }
444
445            let output = table
446                .header(self.header_height, |header| {
447                    self.build_head(header);
448                })
449                .body(|body| {
450                    body.rows(self.row_height, self.formatted_rows.len(), |row| {
451                        let index = row.index();
452                        self.build_body(row, index);
453                    });
454                });
455            let scroll_offset = output.state.offset.y;
456            self.update_scroll_offset(scroll_offset);
457        }
458    }
459
460    fn build_head(&mut self, mut header: TableRow) {
461        if self.add_serial_column {
462            header.col(|ui| {
463                ui.add_sized(ui.available_size(), Label::new(""));
464            });
465        }
466        for column_name in &self.all_columns.clone() {
467            header.col(|ui| {
468                let sort_order = if &self.sorted_by == column_name {
469                    Some(self.sort_order)
470                } else {
471                    None
472                };
473
474                let Some(resp) = column_name.create_header(ui, sort_order, self) else {
475                    return;
476                };
477
478                // Response click sense is not forced. So if a header should not be used
479                // for sorting, without click there won't be any actions.
480
481                if resp.clicked() {
482                    let is_selected = &self.sorted_by == column_name;
483                    if is_selected {
484                        self.change_sort_order();
485                    } else {
486                        self.change_sorted_by(column_name);
487                    }
488                    self.recreate_rows();
489                }
490            });
491        }
492    }
493
494    fn build_body(&mut self, mut row: TableRow, index: usize) {
495        let row_data = self.formatted_rows[index].clone();
496
497        if self.add_serial_column {
498            row.col(|ui| {
499                ui.add_sized(ui.available_size(), Label::new(format!("{}", index + 1)));
500            });
501        }
502        self.handle_table_body(row, &row_data);
503    }
504
505    /// Change the current sort order from ascending to descending and vice versa. Will unselect
506    /// all selected rows
507    fn change_sort_order(&mut self) {
508        self.unselect_all();
509        if matches!(self.sort_order, SortOrder::Ascending) {
510            self.sort_order = SortOrder::Descending;
511        } else {
512            self.sort_order = SortOrder::Ascending;
513        }
514    }
515
516    /// Change the column that is currently being used for sorting. Will unselect all rows
517    fn change_sorted_by(&mut self, sort_by: &F) {
518        self.unselect_all();
519        self.sorted_by = sort_by.clone();
520        self.sort_order = SortOrder::default();
521    }
522
523    /// Recreates the rows shown in the UI for the next frame load.
524    ///
525    /// # Important:
526    /// - Any direct modifications made using [`modify_shown_row`](#method.modify_shown_row)
527    ///   will be **cleared** when this is called.
528    ///   To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead.
529    ///
530    /// # Performance:
531    /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues.
532    /// - Consider calling after every X number of row updates, depending on update frequency,
533    ///   or use [`auto_reload`](#method.auto_reload) for automatic reload.
534    ///
535    /// # Example:
536    /// ```rust,ignore
537    /// table.recreate_rows();
538    /// ```
539    pub fn recreate_rows(&mut self) {
540        self.formatted_rows.clear();
541        self.active_rows.clear();
542        self.active_columns.clear();
543        self.sort_rows();
544    }
545
546    /// Recreates the rows shown in the UI for the next frame load.
547    ///
548    /// This function refreshes the internal row state by clearing and re-sorting the rows
549    /// similar to [`recreate_rows`](#method.recreate_rows), but it **preserves** the currently
550    /// selected rows and re-applies the active column selection to them.
551    ///
552    /// Useful when the UI needs to be refreshed without resetting user interaction state.
553    ///
554    /// # Important:
555    /// - Any direct modifications made to `formatted_rows` using [`modify_shown_row`](#method.modify_shown_row)
556    ///   will be **cleared** when this is called.
557    ///   To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead.
558    ///
559    /// # Performance:
560    /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues.
561    /// - Consider calling after every X number of row updates, depending on update frequency,
562    ///   or use [`auto_reload`](#method.auto_reload) for automatic reload.
563    ///
564    /// # Example:
565    /// ```rust,ignore
566    /// table.recreate_rows_no_unselect();
567    /// ```
568    pub fn recreate_rows_no_unselect(&mut self) {
569        self.formatted_rows.clear();
570        self.sort_rows();
571
572        for row in &self.active_rows {
573            let Some(target_index) = self.indexed_ids.get(row) else {
574                continue;
575            };
576            self.formatted_rows[*target_index]
577                .selected_columns
578                .clone_from(&self.active_columns);
579        }
580    }
581
582    /// The first column that was passed by the user
583    fn first_column(&self) -> F {
584        self.all_columns[0].clone()
585    }
586
587    /// The last column that was passed by the user
588    fn last_column(&self) -> F {
589        self.all_columns[self.all_columns.len() - 1].clone()
590    }
591
592    /// Convert a number to a column value
593    fn column_to_num(&self, column: &F) -> usize {
594        *self
595            .column_number
596            .get(column)
597            .expect("Not in the column list")
598    }
599
600    /// Get the next column of the provided column
601    fn next_column(&self, column: &F) -> F {
602        let current_column_num = self.column_to_num(column);
603        if current_column_num == self.all_columns.len() - 1 {
604            self.all_columns[0].clone()
605        } else {
606            self.all_columns[current_column_num + 1].clone()
607        }
608    }
609
610    /// Get the previous column of the provided column
611    fn previous_column(&self, column: &F) -> F {
612        let current_column_num = self.column_to_num(column);
613        if current_column_num == 0 {
614            self.all_columns[self.all_columns.len() - 1].clone()
615        } else {
616            self.all_columns[current_column_num - 1].clone()
617        }
618    }
619
620    /// Builds the table's Body section
621    fn handle_table_body(&mut self, mut row: TableRow, row_data: &SelectableRow<Row, F>) {
622        for column_name in &self.all_columns.clone() {
623            row.col(|ui| {
624                let selected = row_data.selected_columns.contains(column_name);
625                let mut resp = column_name.create_table_row(ui, row_data, selected, self);
626
627                // Drag sense is forced otherwise there is no point of this library.
628                resp = resp.interact(Sense::drag());
629
630                if resp.drag_started() {
631                    // If CTRL is not pressed down and the mouse right click is not pressed, unselect all cells
632                    // Right click for context menu
633                    if !ui.ctx().input(|i| i.modifiers.ctrl)
634                        && !ui.ctx().input(|i| i.pointer.secondary_clicked())
635                    {
636                        self.unselect_all();
637                    }
638                    self.drag_started_on = Some((row_data.id, column_name.clone()));
639                }
640
641                let pointer_released = ui.input(|a| a.pointer.primary_released());
642
643                if pointer_released {
644                    self.last_active_row = None;
645                    self.last_active_column = None;
646                    self.drag_started_on = None;
647                    self.beyond_drag_point = false;
648                }
649
650                if resp.clicked() {
651                    // If CTRL is not pressed down and the mouse right click is not pressed, unselect all cells
652                    if !ui.ctx().input(|i| i.modifiers.ctrl)
653                        && !ui.ctx().input(|i| i.pointer.secondary_clicked())
654                    {
655                        self.unselect_all();
656                    }
657                    self.select_single_row_cell(row_data.id, column_name);
658                }
659
660                if ui.ui_contains_pointer()
661                    && self.drag_started_on.is_some()
662                    && let Some(drag_start) = self.drag_started_on.as_ref()
663                {
664                    // Only call drag either when not on the starting drag row/column or went beyond the
665                    // drag point at least once. Otherwise normal click would be considered as drag
666                    if drag_start.0 != row_data.id
667                        || &drag_start.1 != column_name
668                        || self.beyond_drag_point
669                    {
670                        let is_ctrl_pressed = ui.ctx().input(|i| i.modifiers.ctrl);
671                        self.select_dragged_row_cell(row_data.id, column_name, is_ctrl_pressed);
672                    }
673                }
674            });
675        }
676    }
677
678    /// Returns the total number of rows currently being displayed in the UI.
679    ///
680    /// # Returns:
681    /// - `usize`: The number of rows that are formatted and ready for display.
682    pub const fn total_displayed_rows(&self) -> usize {
683        self.formatted_rows.len()
684    }
685
686    /// Returns the total number of rows in the table (both displayed and non-displayed).
687    ///
688    /// # Returns:
689    /// - `usize`: The total number of rows stored in the table, regardless of whether they are being displayed or not.
690    pub fn total_rows(&self) -> usize {
691        self.rows.len()
692    }
693
694    /// Provides a reference to the rows currently being displayed in the UI.
695    ///
696    /// # Returns:
697    /// - `&Vec<SelectableRow<Row, F>>`: A reference to the vector of formatted rows ready for display.
698    pub const fn get_displayed_rows(&self) -> &Vec<SelectableRow<Row, F>> {
699        &self.formatted_rows
700    }
701
702    /// Provides a reference to all rows in the table, regardless of whether they are displayed.
703    ///
704    /// # Returns:
705    /// - `&HashMap<i64, SelectableRow<Row, F>>`: A reference to the entire collection of rows in the table.
706    pub const fn get_all_rows(&self) -> &HashMap<i64, SelectableRow<Row, F>> {
707        &self.rows
708    }
709
710    /// Adds a serial column UI to the table.
711    ///
712    /// The serial column UI is automatically generated and displayed at the very left of the table.
713    /// It shows the row number (starting from 1) for each row.
714    ///
715    /// # Returns:
716    /// - `Self`: The modified table with the serial column enabled.
717    ///
718    /// # Example:
719    /// ```rust,ignore
720    /// let table = SelectableTable::new(vec![col1, col2, col3])
721    ///     .config(my_config).serial_column();
722    /// ```
723    #[must_use]
724    pub const fn serial_column(mut self) -> Self {
725        self.add_serial_column = true;
726        self
727    }
728
729    /// Add a horizontal scrollbar to the table
730    ///
731    /// # Returns:
732    /// - `Self`: The modified table with the horizontal scrollbar enabled.
733    ///
734    /// # Example:
735    /// ```rust,ignore
736    /// let table = SelectableTable::new(vec![col1, col2, col3])
737    ///     .horizontal_scroll();
738    /// ```
739    #[must_use]
740    pub const fn horizontal_scroll(mut self) -> Self {
741        self.horizontal_scroll = true;
742        self
743    }
744
745    /// Sets the height of the rows in the table. Defaults to 25.0.
746    ///
747    /// # Parameters:
748    /// - `height`: The desired height for each row in logical points.
749    ///
750    /// # Returns:
751    /// - `Self`: The modified table with the specified row height applied.
752    ///
753    /// # Example:
754    /// ```rust,ignore
755    /// let table = SelectableTable::new(vec![col1, col2, col3])
756    ///     .row_height(24.0);
757    /// ```
758    #[must_use]
759    pub const fn row_height(mut self, height: f32) -> Self {
760        self.row_height = height;
761        self
762    }
763
764    /// Sets the height of the header in the table. Defaults to 20.0.
765    ///
766    /// # Parameters:
767    /// - `height`: The desired height for the header
768    ///
769    /// # Returns:
770    /// - `Self`: The modified table with the specified header height applied.
771    ///
772    /// # Example:
773    /// ```rust,ignore
774    /// let table = SelectableTable::new(vec![col1, col2, col3])
775    ///     .header_height(24.0);
776    /// ```
777    #[must_use]
778    pub const fn header_height(mut self, height: f32) -> Self {
779        self.header_height = height;
780        self
781    }
782
783    /// Disables Ctrl+A keyboard shortcut capturing for selecting all rows
784    ///
785    /// # Returns:
786    /// - `Self`: The modified table with Ctrl+A capturing disabled.
787    ///
788    /// # Example:
789    /// ```rust,ignore
790    /// let table = SelectableTable::new(columns)
791    ///     .no_ctrl_a_capture();
792    /// ```
793    #[must_use]
794    pub const fn no_ctrl_a_capture(mut self) -> Self {
795        self.no_ctrl_a_capture = true;
796        self
797    }
798
799    /// Enables or disables Ctrl+A keyboard shortcut capturing dynamically for selecting all rows.
800    ///
801    /// # Parameters:
802    /// - `status`: `true` to disable Ctrl+A capture, `false` to enable it.
803    ///
804    /// # Example:
805    /// ```rust,ignore
806    /// table.set_no_ctrl_a_capture(true); // Disable Ctrl+A capture
807    /// ```
808    pub const fn set_no_ctrl_a_capture(&mut self, status: bool) {
809        self.no_ctrl_a_capture = status;
810    }
811}