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}