Skip to main content

egui_table_kit/
table.rs

1//! High-level table runner to completely encapsulate `egui_table` and Delegate boilerplate.
2
3use crate::{
4    error::TableError,
5    operations::{Row, TableProvider},
6    state::TableState,
7};
8
9pub struct TableKit<'a> {
10    id: String,
11    provider: &'a dyn TableProvider,
12    state: &'a mut TableState,
13    row_height: f32,
14    org_colors: &'a [[u8; 3]],
15    user_colors: &'a [[u8; 3]],
16    scroll_to_row: Option<(u64, egui::Align)>,
17    striped: bool,
18    striping_color: Option<egui::Color32>,
19    hover_color: Option<egui::Color32>,
20    max_height: Option<f32>,
21    max_rows: Option<u64>,
22    columns: Option<Vec<crate::layout::Column>>,
23    auto_size_mode: crate::layout::AutoSizeMode,
24}
25
26impl<'a> TableKit<'a> {
27    pub fn new(
28        id: impl Into<String>,
29        provider: &'a dyn TableProvider,
30        state: &'a mut TableState,
31    ) -> Self {
32        Self {
33            id: id.into(),
34            provider,
35            state,
36            row_height: 16.0,
37            org_colors: &[],
38            user_colors: &[],
39            scroll_to_row: None,
40            striped: false,
41            striping_color: None,
42            hover_color: None,
43            max_height: Some(400.0), // Default limit fallback
44            max_rows: None,
45            columns: None,
46            auto_size_mode: crate::layout::AutoSizeMode::OnParentResize,
47        }
48    }
49
50    #[must_use]
51    pub fn with_columns(mut self, columns: Vec<crate::layout::Column>) -> Self {
52        self.columns = Some(columns);
53        self
54    }
55
56    #[must_use]
57    pub const fn with_row_height(mut self, height: f32) -> Self {
58        self.row_height = height;
59        self
60    }
61
62    #[must_use]
63    pub const fn with_max_height(mut self, max_height: Option<f32>) -> Self {
64        self.max_height = max_height;
65        self
66    }
67
68    #[must_use]
69    pub const fn with_colors(mut self, org: &'a [[u8; 3]], user: &'a [[u8; 3]]) -> Self {
70        self.org_colors = org;
71        self.user_colors = user;
72        self
73    }
74
75    #[must_use]
76    pub const fn with_auto_size_mode(mut self, mode: crate::layout::AutoSizeMode) -> Self {
77        self.auto_size_mode = mode;
78        self
79    }
80
81    #[must_use]
82    pub const fn with_max_rows(mut self, max_rows: u64) -> Self {
83        self.max_rows = Some(max_rows);
84        self.max_height = None; // Prioritize explicit row limit over pixel defaults
85        self
86    }
87
88    /// Set an optional row to scroll to during the next rendering pass.
89    #[must_use]
90    pub const fn with_scroll_to_row(mut self, row_nr: u64, align: egui::Align) -> Self {
91        self.scroll_to_row = Some((row_nr, align));
92        self
93    }
94
95    /// Enable or disable alternating row background colors.
96    #[must_use]
97    pub const fn with_striped(mut self, striped: bool) -> Self {
98        self.striped = striped;
99        self
100    }
101
102    /// Provide a custom background color for alternating striped rows.
103    /// If none is provided, it falls back to `ui.visuals().faint_bg_color`.
104    #[must_use]
105    pub const fn with_striping_color(mut self, color: egui::Color32) -> Self {
106        self.striping_color = Some(color);
107        self
108    }
109
110    /// Set an optional custom background overlay color for hovered rows.
111    #[must_use]
112    pub const fn with_hover_color(mut self, color: egui::Color32) -> Self {
113        self.hover_color = Some(color);
114        self
115    }
116
117    pub fn show<F>(self, ui: &mut egui::Ui, custom_cell_ui: F) -> Result<egui::Response, TableError>
118    where
119        F: FnMut(
120                &mut egui::Ui,
121                &crate::layout::CellInfo,
122                &dyn Row,
123                egui::Color32,
124            ) -> Option<egui::Response>
125            + 'a,
126    {
127        // Refresh filter/sorting view when dirty
128        let _ = self.state.refresh_view(self.provider);
129
130        // Prioritize custom pre-configured layout columns over fallback defaults
131        let columns = self.columns.unwrap_or_else(|| {
132            (0..self.provider.column_count())
133                .map(|_| {
134                    crate::layout::Column::new(120.0)
135                        .range(15.0..=f32::INFINITY)
136                        .resizable(true)
137                })
138                .collect::<Vec<_>>()
139        });
140
141        let mut table = crate::layout::Table::new()
142            .id_salt(&self.id)
143            .num_rows(self.state.active_rows.len() as u64)
144            .columns(columns)
145            .auto_size_mode(self.auto_size_mode) // <--- Forward auto-size mode
146            .headers([crate::layout::HeaderRow::new(self.row_height)]);
147
148        // Apply scroll-to-row instruction to the table builder
149        if let Some((row_nr, align)) = self.scroll_to_row {
150            table = table.scroll_to_row(row_nr, Some(align));
151        }
152
153        if let Some(max_r) = self.max_rows {
154            table = table.max_rows(max_r);
155        } else if let Some(max_h) = self.max_height {
156            table = table.max_height(max_h);
157        }
158
159        let mut collected_responses = Vec::new();
160        let mut halt_error = None;
161
162        let response = {
163            let mut item_clicked = None;
164            let mut secondary_clicked = None;
165
166            let mut delegate = crate::delegate::TableKitDelegate::new(
167                self.provider,
168                self.state,
169                self.org_colors,
170                self.user_colors,
171                &mut collected_responses,
172                &mut halt_error,
173                Some(Box::new(custom_cell_ui)),
174                &mut item_clicked,
175                &mut secondary_clicked,
176            );
177            delegate.row_height = self.row_height;
178            delegate.striped = self.striped;
179            delegate.striping_color = self.striping_color;
180            delegate.hover_color = self.hover_color;
181
182            table.show(ui, &mut delegate)
183        };
184
185        if let Some(err) = halt_error {
186            return Err(err);
187        }
188
189        self.state
190            .process_responses(self.provider, collected_responses)?;
191
192        Ok(response)
193    }
194}