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}