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 matcher used for fuzzy searching
250    #[cfg(feature = "fuzzy-matching")]
251    matcher: Matcher,
252    /// Whether to capture Ctrl+A for selecting all rows
253    no_ctrl_a_capture: bool,
254}
255
256impl<Row, F, Conf> SelectableTable<Row, F, Conf>
257where
258    Row: Clone + Send + Sync,
259    F: Eq
260        + Hash
261        + Clone
262        + Ord
263        + Send
264        + Sync
265        + Default
266        + ColumnOperations<Row, F, Conf>
267        + ColumnOrdering<Row>,
268    Conf: Default,
269{
270    /// Creates a new `SelectableTable` with the provided columns in a specified order.
271    ///
272    /// # Parameters:
273    /// - `columns`: A `Vec<F>` representing the columns. Columns must be passed in the correct order (e.g., 1 to 10).
274    ///
275    /// # Returns:
276    /// - A new instance of `SelectableTable`.
277    ///
278    /// # Example:
279    /// ```rust,ignore
280    /// let table = SelectableTable::new(vec![col1, col2, col3]);
281    /// ```
282    #[must_use]
283    pub fn new(columns: Vec<F>) -> Self {
284        let all_columns = columns.clone();
285        let mut column_number = HashMap::new();
286
287        for (index, col) in columns.into_iter().enumerate() {
288            column_number.insert(col, index);
289        }
290        Self {
291            all_columns,
292            column_number,
293            last_id_used: 0,
294            rows: HashMap::new(),
295            formatted_rows: Vec::new(),
296            sorted_by: F::default(),
297            sort_order: SortOrder::default(),
298            drag_started_on: None,
299            active_columns: HashSet::new(),
300            active_rows: HashSet::new(),
301            last_active_row: None,
302            last_active_column: None,
303            beyond_drag_point: false,
304            indexed_ids: HashMap::new(),
305            auto_scroll: AutoScroll::default(),
306            auto_reload: AutoReload::default(),
307            select_full_row: false,
308            horizontal_scroll: false,
309            config: Conf::default(),
310            add_serial_column: false,
311            row_height: 25.0,
312            #[cfg(feature = "fuzzy-matching")]
313            matcher: Matcher::default(),
314            no_ctrl_a_capture: false,
315        }
316    }
317
318    /// Updates the table's configuration with the given `conf`.
319    ///
320    /// # Parameters:
321    /// - `conf`: The new configuration of type `Conf`, which is user-defined and allows
322    ///   passing data to help with row/table modification.
323    ///
324    /// # Example:
325    /// ```rust,ignore
326    /// table.set_config(my_config);
327    /// ```
328    pub fn set_config(&mut self, conf: Conf) {
329        self.config = conf;
330    }
331
332    /// Sets a configuration in a builder-style pattern.
333    ///
334    /// # Parameters:
335    /// - `conf`: A configuration of type `Conf`. The user can pass any data to help with row creation or modification.
336    ///
337    /// # Returns:
338    /// - The updated `SelectableTable` with the new configuration applied.
339    ///
340    /// # Example:
341    /// ```rust,ignore
342    /// let table = SelectableTable::new(vec![col1, col2, col3]).config(my_config);
343    /// ```
344    #[must_use]
345    pub fn config(mut self, conf: Conf) -> Self {
346        self.config = conf;
347        self
348    }
349
350    /// Clears all rows from the table, including the displayed ones
351    ///
352    /// # Example:
353    /// ```rust,ignore
354    /// table.clear_all_rows();
355    /// ```
356    pub fn clear_all_rows(&mut self) {
357        self.rows.clear();
358        self.formatted_rows.clear();
359        self.active_rows.clear();
360        self.active_columns.clear();
361        self.last_id_used = 0;
362    }
363
364    /// Displays the UI for the table and uses the provided `TableBuilder` for creating the table UI.
365    ///
366    /// # Parameters:
367    /// - `ui`: The UI context where the table will be rendered.
368    /// - `table_builder`: A closure that receives and modifies the `TableBuilder`.
369    ///
370    /// # Example:
371    /// ```rust,ignore
372    /// table.show_ui(ui, |builder| builder.column(column1));
373    /// ```
374    pub fn show_ui<Fn>(&mut self, ui: &mut Ui, table_builder: Fn)
375    where
376        Fn: FnOnce(TableBuilder) -> TableBuilder,
377    {
378        let is_ctrl_pressed = ui.ctx().input(|i| i.modifiers.ctrl);
379        let key_a_pressed = ui.ctx().input(|i| i.key_pressed(Key::A));
380        let copy_initiated = ui.ctx().input(|i| i.events.contains(&Event::Copy));
381        let ctx = ui.ctx().clone();
382
383        if copy_initiated {
384            self.copy_selected_cells(ui);
385        }
386        if is_ctrl_pressed && key_a_pressed && !self.no_ctrl_a_capture {
387            self.select_all();
388        }
389
390        let pointer = ui.input(|i| i.pointer.hover_pos());
391        let max_rect = ui.max_rect();
392
393        if self.horizontal_scroll {
394            ScrollArea::horizontal().show(ui, |ui| {
395                let mut table = TableBuilder::new(ui);
396
397                if self.add_serial_column {
398                    table = table.column(Column::initial(25.0).clip(true));
399                }
400
401                table = table_builder(table);
402
403                if self.drag_started_on.is_some() {
404                    if let Some(offset) = self.auto_scroll.start_scroll(max_rect, pointer) {
405                        table = table.vertical_scroll_offset(offset);
406                        ctx.request_repaint();
407                    }
408                }
409
410                let output = table
411                    .header(20.0, |header| {
412                        self.build_head(header);
413                    })
414                    .body(|body| {
415                        body.rows(self.row_height, self.formatted_rows.len(), |row| {
416                            let index = row.index();
417                            self.build_body(row, index);
418                        });
419                    });
420                let scroll_offset = output.state.offset.y;
421                self.update_scroll_offset(scroll_offset);
422            });
423        } else {
424            let mut table = TableBuilder::new(ui);
425
426            if self.add_serial_column {
427                table = table.column(Column::initial(25.0).clip(true));
428            }
429
430            table = table_builder(table);
431
432            if self.drag_started_on.is_some() {
433                if let Some(offset) = self.auto_scroll.start_scroll(max_rect, pointer) {
434                    table = table.vertical_scroll_offset(offset);
435                    ctx.request_repaint();
436                }
437            }
438
439            let output = table
440                .header(20.0, |header| {
441                    self.build_head(header);
442                })
443                .body(|body| {
444                    body.rows(self.row_height, self.formatted_rows.len(), |row| {
445                        let index = row.index();
446                        self.build_body(row, index);
447                    });
448                });
449            let scroll_offset = output.state.offset.y;
450            self.update_scroll_offset(scroll_offset);
451        }
452    }
453
454    fn build_head(&mut self, mut header: TableRow) {
455        if self.add_serial_column {
456            header.col(|ui| {
457                ui.add_sized(ui.available_size(), Label::new(""));
458            });
459        }
460        for column_name in &self.all_columns.clone() {
461            header.col(|ui| {
462                let sort_order = if &self.sorted_by == column_name {
463                    Some(self.sort_order)
464                } else {
465                    None
466                };
467
468                let Some(resp) = column_name.create_header(ui, sort_order, self) else {
469                    return;
470                };
471
472                // Response click sense is not forced. So if a header should not be used
473                // for sorting, without click there won't be any actions.
474
475                if resp.clicked() {
476                    let is_selected = &self.sorted_by == column_name;
477                    if is_selected {
478                        self.change_sort_order();
479                    } else {
480                        self.change_sorted_by(column_name);
481                    }
482                    self.recreate_rows();
483                }
484            });
485        }
486    }
487
488    fn build_body(&mut self, mut row: TableRow, index: usize) {
489        let row_data = self.formatted_rows[index].clone();
490
491        if self.add_serial_column {
492            row.col(|ui| {
493                ui.add_sized(ui.available_size(), Label::new(format!("{}", index + 1)));
494            });
495        }
496        self.handle_table_body(row, &row_data);
497    }
498
499    /// Change the current sort order from ascending to descending and vice versa. Will unselect
500    /// all selected rows
501    fn change_sort_order(&mut self) {
502        self.unselect_all();
503        if matches!(self.sort_order, SortOrder::Ascending) {
504            self.sort_order = SortOrder::Descending;
505        } else {
506            self.sort_order = SortOrder::Ascending;
507        }
508    }
509
510    /// Change the column that is currently being used for sorting. Will unselect all rows
511    fn change_sorted_by(&mut self, sort_by: &F) {
512        self.unselect_all();
513        self.sorted_by = sort_by.clone();
514        self.sort_order = SortOrder::default();
515    }
516
517    /// Recreates the rows shown in the UI for the next frame load.
518    ///
519    /// # Important:
520    /// - Any direct modifications made using [`modify_shown_row`](#method.modify_shown_row)
521    ///   will be **cleared** when this is called.
522    ///   To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead.
523    ///
524    /// # Performance:
525    /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues.
526    /// - Consider calling after every X number of row updates, depending on update frequency,
527    ///   or use [`auto_reload`](#method.auto_reload) for automatic reload.
528    ///
529    /// # Example:
530    /// ```rust,ignore
531    /// table.recreate_rows();
532    /// ```
533    pub fn recreate_rows(&mut self) {
534        self.formatted_rows.clear();
535        self.active_rows.clear();
536        self.active_columns.clear();
537        self.sort_rows();
538    }
539
540    /// Recreates the rows shown in the UI for the next frame load.
541    ///
542    /// This function refreshes the internal row state by clearing and re-sorting the rows
543    /// similar to [`recreate_rows`](#method.recreate_rows), but it **preserves** the currently
544    /// selected rows and re-applies the active column selection to them.
545    ///
546    /// Useful when the UI needs to be refreshed without resetting user interaction state.
547    ///
548    /// # Important:
549    /// - Any direct modifications made to `formatted_rows` using [`modify_shown_row`](#method.modify_shown_row)
550    ///   will be **cleared** when this is called.
551    ///   To preserve changes, use [`add_modify_row`](#method.add_modify_row) to update row data instead.
552    ///
553    /// # Performance:
554    /// - Should be used sparingly for large datasets, as frequent calls can lead to performance issues.
555    /// - Consider calling after every X number of row updates, depending on update frequency,
556    ///   or use [`auto_reload`](#method.auto_reload) for automatic reload.
557    ///
558    /// # Example:
559    /// ```rust,ignore
560    /// table.recreate_rows_no_unselect();
561    /// ```
562    pub fn recreate_rows_no_unselect(&mut self) {
563        self.formatted_rows.clear();
564        self.sort_rows();
565
566        for row in &self.active_rows {
567            let Some(target_index) = self.indexed_ids.get(row) else {
568                continue;
569            };
570            self.formatted_rows[*target_index]
571                .selected_columns
572                .clone_from(&self.active_columns);
573        }
574    }
575
576    /// The first column that was passed by the user
577    fn first_column(&self) -> F {
578        self.all_columns[0].clone()
579    }
580
581    /// The last column that was passed by the user
582    fn last_column(&self) -> F {
583        self.all_columns[self.all_columns.len() - 1].clone()
584    }
585
586    /// Convert a number to a column value
587    fn column_to_num(&self, column: &F) -> usize {
588        *self
589            .column_number
590            .get(column)
591            .expect("Not in the column list")
592    }
593
594    /// Get the next column of the provided column
595    fn next_column(&self, column: &F) -> F {
596        let current_column_num = self.column_to_num(column);
597        if current_column_num == self.all_columns.len() - 1 {
598            self.all_columns[0].clone()
599        } else {
600            self.all_columns[current_column_num + 1].clone()
601        }
602    }
603
604    /// Get the previous column of the provided column
605    fn previous_column(&self, column: &F) -> F {
606        let current_column_num = self.column_to_num(column);
607        if current_column_num == 0 {
608            self.all_columns[self.all_columns.len() - 1].clone()
609        } else {
610            self.all_columns[current_column_num - 1].clone()
611        }
612    }
613
614    /// Builds the table's Body section
615    fn handle_table_body(&mut self, mut row: TableRow, row_data: &SelectableRow<Row, F>) {
616        for column_name in &self.all_columns.clone() {
617            row.col(|ui| {
618                let selected = row_data.selected_columns.contains(column_name);
619                let mut resp = column_name.create_table_row(ui, row_data, selected, self);
620
621                // Drag sense is forced otherwise there is no point of this library.
622                resp = resp.interact(Sense::drag());
623
624                if resp.drag_started() {
625                    // If CTRL is not pressed down and the mouse right click is not pressed, unselect all cells
626                    // Right click for context menu
627                    if !ui.ctx().input(|i| i.modifiers.ctrl)
628                        && !ui.ctx().input(|i| i.pointer.secondary_clicked())
629                    {
630                        self.unselect_all();
631                    }
632                    self.drag_started_on = Some((row_data.id, column_name.clone()));
633                }
634
635                let pointer_released = ui.input(|a| a.pointer.primary_released());
636
637                if pointer_released {
638                    self.last_active_row = None;
639                    self.last_active_column = None;
640                    self.drag_started_on = None;
641                    self.beyond_drag_point = false;
642                }
643
644                if resp.clicked() {
645                    // If CTRL is not pressed down and the mouse right click is not pressed, unselect all cells
646                    if !ui.ctx().input(|i| i.modifiers.ctrl)
647                        && !ui.ctx().input(|i| i.pointer.secondary_clicked())
648                    {
649                        self.unselect_all();
650                    }
651                    self.select_single_row_cell(row_data.id, column_name);
652                }
653
654                if ui.ui_contains_pointer() && self.drag_started_on.is_some() {
655                    if let Some(drag_start) = self.drag_started_on.as_ref() {
656                        // Only call drag either when not on the starting drag row/column or went beyond the
657                        // drag point at least once. Otherwise normal click would be considered as drag
658                        if drag_start.0 != row_data.id
659                            || &drag_start.1 != column_name
660                            || self.beyond_drag_point
661                        {
662                            let is_ctrl_pressed = ui.ctx().input(|i| i.modifiers.ctrl);
663                            self.select_dragged_row_cell(row_data.id, column_name, is_ctrl_pressed);
664                        }
665                    }
666                }
667            });
668        }
669    }
670
671    /// Returns the total number of rows currently being displayed in the UI.
672    ///
673    /// # Returns:
674    /// - `usize`: The number of rows that are formatted and ready for display.
675    pub const fn total_displayed_rows(&self) -> usize {
676        self.formatted_rows.len()
677    }
678
679    /// Returns the total number of rows in the table (both displayed and non-displayed).
680    ///
681    /// # Returns:
682    /// - `usize`: The total number of rows stored in the table, regardless of whether they are being displayed or not.
683    pub fn total_rows(&self) -> usize {
684        self.rows.len()
685    }
686
687    /// Provides a reference to the rows currently being displayed in the UI.
688    ///
689    /// # Returns:
690    /// - `&Vec<SelectableRow<Row, F>>`: A reference to the vector of formatted rows ready for display.
691    pub const fn get_displayed_rows(&self) -> &Vec<SelectableRow<Row, F>> {
692        &self.formatted_rows
693    }
694
695    /// Provides a reference to all rows in the table, regardless of whether they are displayed.
696    ///
697    /// # Returns:
698    /// - `&HashMap<i64, SelectableRow<Row, F>>`: A reference to the entire collection of rows in the table.
699    pub const fn get_all_rows(&self) -> &HashMap<i64, SelectableRow<Row, F>> {
700        &self.rows
701    }
702
703    /// Adds a serial column to the table.
704    ///
705    /// The serial column is automatically generated and displayed at the very left of the table.
706    /// It shows the row number (starting from 1) for each row.
707    ///
708    /// # Returns:
709    /// - `Self`: The modified table with the serial column enabled.
710    ///
711    /// # Example:
712    /// ```rust,ignore
713    /// let table = SelectableTable::new(vec![col1, col2, col3])
714    ///     .config(my_config).serial_column();
715    /// ```
716    #[must_use]
717    pub const fn serial_column(mut self) -> Self {
718        self.add_serial_column = true;
719        self
720    }
721
722    /// Add a horizontal scrollbar to the table
723    ///
724    /// # Returns:
725    /// - `Self`: The modified table with the serial column enabled.
726    ///
727    /// # Example:
728    /// ```rust,ignore
729    /// let table = SelectableTable::new(vec![col1, col2, col3])
730    ///     .horizontal_scroll();
731    /// ```
732    #[must_use]
733    pub const fn horizontal_scroll(mut self) -> Self {
734        self.horizontal_scroll = true;
735        self
736    }
737
738    /// Sets the height rows in the table.
739    ///
740    /// # Parameters:
741    /// - `height`: The desired height for each row in logical points.
742    ///
743    /// # Returns:
744    /// - `Self`: The modified table with the specified row height applied.
745    ///
746    /// # Example:
747    /// ```rust,ignore
748    /// let table = SelectableTable::new(vec![col1, col2, col3])
749    ///     .row_height(24.0);
750    /// ```
751    #[must_use]
752    pub const fn row_height(mut self, height: f32) -> Self {
753        self.row_height = height;
754        self
755    }
756
757    /// Disables Ctrl+A keyboard shortcut capturing for selecting all rows
758    ///
759    /// # Returns:
760    /// - `Self`: The modified table with Ctrl+A capturing disabled.
761    ///
762    /// # Example:
763    /// ```rust,ignore
764    /// let table = SelectableTable::new(columns)
765    ///     .no_ctrl_a_capture();
766    /// ```
767    #[must_use]
768    pub const fn no_ctrl_a_capture(mut self) -> Self {
769        self.no_ctrl_a_capture = true;
770        self
771    }
772
773    /// Enables or disables Ctrl+A keyboard shortcut capturing dynamically for selecting all rows.
774    ///
775    /// # Parameters:
776    /// - `status`: `true` to disable Ctrl+A capture, `false` to enable it.
777    ///
778    /// # Example:
779    /// ```rust,ignore
780    /// table.set_no_ctrl_a_capture(true); // Disable Ctrl+A capture
781    /// ```
782    pub const fn set_no_ctrl_a_capture(&mut self, status: bool) {
783        self.no_ctrl_a_capture = status;
784    }
785}