1use egui::{Color32, Margin, Response, Sense, Ui};
2
3use super::{
4 error::TableError,
5 filter::highlight::select_color,
6 header::{ColResponse, HeaderMenuAnchor, show_header_cell_contents},
7 layout::{CellInfo, HeaderCellInfo, PrefetchInfo, TableDelegate},
8 operations::{BorrowedRow, Row, TableProvider},
9 state::TableState,
10};
11
12pub type CustomCellCallback<'a> =
14 dyn FnMut(&mut Ui, &CellInfo, &dyn Row, Color32) -> Option<Response> + 'a;
15
16pub struct TableKitDelegate<'a> {
18 pub provider: &'a dyn TableProvider,
19 pub state: &'a mut TableState,
20 pub org_colors: &'a [[u8; 3]],
21 pub user_colors: &'a [[u8; 3]],
22 pub collected_responses: &'a mut Vec<ColResponse>,
23 pub halt_error: &'a mut Option<TableError>,
24 pub custom_cell_ui: Option<Box<CustomCellCallback<'a>>>,
25 pub item_clicked: &'a mut Option<usize>,
26 pub secondary_clicked: &'a mut Option<usize>,
27 pub is_new_pass: bool,
28
29 pub cell_padding: Margin,
31 pub highlight_entire_row: bool,
32 pub hovered_row: Option<u64>,
33 pub header_menu_anchor: HeaderMenuAnchor,
34 pub row_height: f32,
35 pub header_bg_color: Option<Color32>,
36
37 pub striped: bool,
38 pub striping_color: Option<Color32>,
39 pub hover_color: Option<Color32>,
40}
41
42impl<'a> TableKitDelegate<'a> {
43 pub fn new(
45 provider: &'a dyn TableProvider,
46 state: &'a mut TableState,
47 org_colors: &'a [[u8; 3]],
48 user_colors: &'a [[u8; 3]],
49 collected_responses: &'a mut Vec<ColResponse>,
50 halt_error: &'a mut Option<TableError>,
51 custom_cell_ui: Option<Box<CustomCellCallback<'a>>>,
52 item_clicked: &'a mut Option<usize>,
53 secondary_clicked: &'a mut Option<usize>,
54 ) -> Self {
55 Self {
56 provider,
57 state,
58 org_colors,
59 user_colors,
60 collected_responses,
61 halt_error,
62 custom_cell_ui,
63 item_clicked,
64 secondary_clicked,
65 is_new_pass: true,
66 cell_padding: Margin::symmetric(8, 0),
67 highlight_entire_row: true,
68 hovered_row: None,
69 header_menu_anchor: HeaderMenuAnchor::Cursor,
70 row_height: 16.0,
71 header_bg_color: None,
72 striped: false,
73 striping_color: None,
74 hover_color: None,
75 }
76 }
77}
78
79impl TableDelegate for TableKitDelegate<'_> {
80 fn default_row_height(&self) -> f32 {
81 self.row_height
82 }
83
84 fn prepare(&mut self, _info: &PrefetchInfo) {
85 if self.is_new_pass {
86 self.is_new_pass = false;
87 self.collected_responses.clear();
88 self.hovered_row = None;
89 }
90 }
91
92 fn header_cell_ui(&mut self, ui: &mut Ui, cell: &HeaderCellInfo) {
93 let col_idx = cell.col_range.start;
94 let title = self.provider.header(col_idx).unwrap_or_default();
95
96 let default_response = ColResponse::default();
97 let (previous_response, sort_up) = self
98 .state
99 .columns
100 .get(col_idx)
101 .map_or((&default_response, None), |col| {
102 (&col.response, col.sort_up)
103 });
104
105 let bg_color = self.header_bg_color.unwrap_or_else(|| {
107 if ui.visuals().dark_mode {
108 Color32::from_gray(38)
109 } else {
110 Color32::from_gray(230)
111 }
112 });
113 ui.visuals_mut().widgets.noninteractive.weak_bg_fill = bg_color;
114
115 ui.push_id(col_idx, |ui| {
117 match show_header_cell_contents(
118 ui,
119 title.as_ref(),
120 &sort_up,
121 previous_response,
122 self.org_colors,
123 self.user_colors,
124 self.header_menu_anchor,
125 ) {
126 Ok(response) => {
127 if col_idx >= self.collected_responses.len() {
128 self.collected_responses
129 .resize_with(col_idx + 1, ColResponse::default);
130 }
131 self.collected_responses[col_idx] = response;
132 }
133 Err(e) => {
134 *self.halt_error = Some(e);
135 }
136 }
137 });
138 }
139
140 fn cell_ui(&mut self, ui: &mut Ui, cell: &CellInfo) {
141 let current_visible_idx = cell.row_nr as usize;
142 let Some(&row_idx) = self.state.active_rows.get(current_visible_idx) else {
143 return;
144 };
145
146 let is_selected = self.state.selected_rows.contains(row_idx as u32);
147 let highlight = self.state.highlights.get_usize(row_idx);
148
149 let item_spacing = ui.spacing().item_spacing;
150 let cell_rect = ui.max_rect().expand2(0.5 * item_spacing);
151
152 let row_hovered = if self.highlight_entire_row {
154 cell.row_hovered
155 } else {
156 ui.rect_contains_pointer(cell_rect)
157 };
158
159 let highlight_rgb = if let Some(color_idx) = highlight {
161 select_color(color_idx, self.org_colors, self.user_colors).ok()
162 } else {
163 None
164 };
165
166 if is_selected {
168 ui.painter().rect_filled(
169 cell_rect,
170 egui::CornerRadius::ZERO,
171 ui.visuals().selection.bg_fill,
172 );
173 } else {
174 let base_color = if let Some(rgb) = highlight_rgb {
176 Some(Color32::from_rgb(rgb[0], rgb[1], rgb[2]))
177 } else if self.striped && cell.row_nr % 2 == 1 {
178 Some(
179 self.striping_color
180 .unwrap_or_else(|| ui.visuals().faint_bg_color),
181 )
182 } else {
183 None
184 };
185
186 if let Some(color) = base_color {
187 ui.painter()
188 .rect_filled(cell_rect, egui::CornerRadius::ZERO, color);
189 }
190
191 if row_hovered {
193 let hover_fill = self
194 .hover_color
195 .unwrap_or_else(|| ui.visuals().widgets.hovered.weak_bg_fill);
196
197 ui.painter()
198 .rect_filled(cell_rect, egui::CornerRadius::ZERO, hover_fill);
199 }
200 }
201
202 let is_tree_changed = if cell.col_nr == 0 && self.provider.is_tree() {
204 if let Some(hierarchy) = self.provider.row_hierarchy(self.state, row_idx) {
205 self.state.show_tree_cell(ui, row_idx, hierarchy)
206 } else {
207 false
208 }
209 } else {
210 false
211 };
212
213 if is_tree_changed {
214 ui.ctx().request_repaint();
215 }
216
217 let text_color = if is_selected {
219 ui.visuals().selection.stroke.color
220 } else {
221 ui.visuals().widgets.inactive.text_color()
222 };
223
224 let mut interact_rect = cell_rect;
226 if cell.col_nr == 0
227 && self.provider.is_tree()
228 && let Some(hierarchy) = self.provider.row_hierarchy(self.state, row_idx)
229 {
230 #[allow(clippy::cast_precision_loss)]
231 let indent_width = (hierarchy.indent_level as f32).mul_add(22.0, 22.0);
232 interact_rect.min.x = (interact_rect.min.x + indent_width).min(interact_rect.max.x);
233 }
234
235 let response = ui.interact(
237 interact_rect,
238 ui.id().with((cell.row_nr, cell.col_nr)),
239 Sense::click(),
240 );
241 if response.clicked() {
242 *self.item_clicked = Some(row_idx);
243 self.state
244 .handle_row_selection(ui.input(|i| i.modifiers), row_idx);
245 }
246 if response.secondary_clicked() {
247 *self.secondary_clicked = Some(row_idx);
248 }
249
250 let borrowed_row = BorrowedRow {
252 provider: self.provider,
253 row_index: row_idx,
254 };
255
256 let mut rendered = false;
257
258 let mut padding = self.cell_padding;
260 if cell.col_nr == 0 && self.provider.is_tree() {
261 padding.left = 0;
262 padding.right = 4;
263 }
264
265 egui::Frame::NONE.inner_margin(padding).show(ui, |ui| {
267 ui.push_id((cell.row_nr, cell.col_nr), |ui| {
268 let custom_renderer = self.custom_cell_ui.as_mut();
269
270 if let Some(renderer) = custom_renderer
271 && renderer(ui, cell, &borrowed_row as &dyn Row, text_color).is_some()
272 {
273 rendered = true;
274 }
275
276 if !rendered && let Some((val, _)) = borrowed_row.cell(cell.col_nr) {
277 ui.horizontal(|ui| {
278 ui.add(
279 egui::Label::new(egui::RichText::new(val.as_ref()).color(text_color))
280 .selectable(false)
281 .wrap_mode(if ui.is_sizing_pass() {
282 ui.wrap_mode()
283 } else {
284 egui::TextWrapMode::Truncate
285 }),
286 );
287 });
288 }
289 });
290 });
291 }
292}