1use std::collections::HashMap;
2
3use compact_str::CompactString;
4use roaring::RoaringBitmap;
5
6use super::{
7 error::TableError,
8 filter::Filter,
9 header::{ColResponse, ColumnState},
10 highlights::Highlights,
11 operations::TableProvider,
12};
13
14#[derive(Debug)]
15pub enum FilterUpdate {
16 Added(usize, Filter),
17 Removed(usize),
18 Modified(usize, Filter),
19}
20
21#[derive(Debug)]
22pub struct TableChanges {
23 pub filter_update: Option<FilterUpdate>,
24 pub sort_update: Option<usize>,
25 pub filter_state: Vec<(usize, Filter)>,
26 pub sort_state: Option<(usize, bool)>,
27}
28
29#[derive(Debug, Default)]
30pub struct TableState {
31 pub id: CompactString,
32 pub columns: Vec<ColumnState>,
33 pub highlights: Highlights,
34 pub highlights_changed: bool,
35 pub active_rows: Vec<usize>,
36 pub selected_rows: RoaringBitmap,
37 pub expanded_rows: RoaringBitmap,
38 pub last_clicked_visible_index: Option<usize>,
39 pub filter_matches: RoaringBitmap,
40 pub filter_cache_dirty: bool,
41 pub sorted_children_cache: HashMap<usize, Vec<usize>, ahash::RandomState>,
42}
43
44impl TableState {
45 #[must_use]
46 pub fn new(id: impl Into<CompactString>, row_count: usize) -> Self {
47 Self {
48 id: id.into(),
49 active_rows: (0..row_count).collect(),
50 filter_cache_dirty: true, ..Default::default()
52 }
53 }
54
55 #[must_use]
56 pub fn get_filter_state(&self) -> Vec<(usize, Filter)> {
57 self.columns
58 .iter()
59 .enumerate()
60 .filter_map(|(col_index, col)| {
61 if col.response.filtering.is_empty() {
62 None
63 } else {
64 Some((col_index, col.response.filtering.clone()))
65 }
66 })
67 .collect()
68 }
69
70 #[must_use]
71 pub fn get_sort_state(&self) -> Option<(usize, bool)> {
72 self.columns
73 .iter()
74 .enumerate()
75 .find_map(|(col_index, col)| col.sort_up.map(|sort_up| (col_index, sort_up)))
76 }
77
78 #[must_use]
79 pub fn counts_header(&self, row_len: usize) -> String {
80 use std::fmt::Write;
81 let mut counts = String::with_capacity(64);
82 let _ = write!(counts, "Total: {row_len}");
83
84 let active_rows_count = self.active_rows.len();
85 if active_rows_count != row_len {
86 let _ = write!(counts, ", {active_rows_count} passing filter");
87 }
88
89 let selected_rows_count = self.selected_rows.len();
90 if selected_rows_count != 0 {
91 let _ = write!(counts, ", {selected_rows_count} selected");
92 }
93
94 counts
95 }
96
97 pub fn process_responses(
98 &mut self,
99 provider: &dyn TableProvider,
100 responses: Vec<ColResponse>,
101 ) -> Result<(), TableError> {
102 let changes = self.collect_responses(responses);
103 let filter_update = changes.filter_update;
104 let sort_update = changes.sort_update;
105 let filter_state = changes.filter_state;
106 let sort_state = changes.sort_state;
107
108 if self.highlights_changed {
109 self.highlights_changed = false;
110 self.filter_cache_dirty = true;
111 self.sorted_children_cache.clear();
112
113 if !provider.is_tree() {
115 self.apply_all_filters(provider, &filter_state)?;
116 }
117
118 if let Some((sort_col, sort_up)) = sort_state {
119 provider.sort_active_rows(&mut self.active_rows, sort_col, sort_up)?;
120 }
121 return Ok(());
122 }
123
124 if let Some(update) = filter_update {
125 self.sorted_children_cache.clear();
126
127 if !provider.is_tree() {
129 match update {
130 FilterUpdate::Added(col, filter) => {
131 self.apply_incremental_filter(provider, col, &filter)?;
132 }
133 FilterUpdate::Removed(_) | FilterUpdate::Modified(_, _) => {
134 self.apply_all_filters(provider, &filter_state)?;
135 }
136 }
137 }
138
139 if let Some((sort_col, sort_up)) = sort_state {
140 provider.sort_active_rows(&mut self.active_rows, sort_col, sort_up)?;
141 }
142 } else if let Some(sort_col) = sort_update {
143 self.sorted_children_cache.clear();
144 if let Some((already_sorted_col, sort_up)) = sort_state {
145 if already_sorted_col == sort_col {
146 let column = self
147 .columns
148 .get_mut(sort_col)
149 .ok_or(TableError::CorruptedState)?;
150 column.sort_up = Some(!sort_up);
151 } else {
152 self.apply_new_sort(provider, sort_col)?;
153 }
154 } else {
155 self.apply_new_sort(provider, sort_col)?;
156 }
157 }
158
159 Ok(())
160 }
161
162 pub fn apply_new_sort(
163 &mut self,
164 provider: &dyn TableProvider,
165 sort_col: usize,
166 ) -> Result<(), TableError> {
167 for (i, column) in self.columns.iter_mut().enumerate() {
168 column.sort_up = if i == sort_col { Some(true) } else { None };
169 }
170 provider.sort_active_rows(&mut self.active_rows, sort_col, true)
171 }
172
173 pub fn apply_all_filters(
174 &mut self,
175 provider: &dyn TableProvider,
176 filters: &[(usize, Filter)],
177 ) -> Result<(), TableError> {
178 self.active_rows = provider.filter_rows(self, filters)?;
179 Ok(())
180 }
181
182 pub fn apply_incremental_filter(
183 &mut self,
184 provider: &dyn TableProvider,
185 filter_col: usize,
186 filter: &Filter,
187 ) -> Result<(), TableError> {
188 let mut active_mask = vec![false; provider.row_count()];
189 for &idx in &self.active_rows {
190 if idx < active_mask.len() {
191 active_mask[idx] = true;
192 }
193 }
194
195 let mut new_active = Vec::with_capacity(self.active_rows.len());
196 let mut row_idx = 0;
197
198 provider.for_all_rows(&mut |row| {
199 if row_idx < active_mask.len() && active_mask[row_idx] {
200 let highlight = self.highlights.get_usize(row_idx);
201 if let Some(cell) = row.get(filter_col)
202 && filter.matches(&cell.0, highlight)
203 {
204 new_active.push(row_idx);
205 }
206 }
207 row_idx += 1;
208 Ok(())
209 })?;
210
211 self.active_rows = new_active;
212 Ok(())
213 }
214
215 pub fn show_tree_cell(
218 &mut self,
219 ui: &mut egui::Ui,
220 row_index: usize,
221 hierarchy: crate::operations::RowHierarchy,
222 ) -> bool {
223 let mut changed = false;
224
225 #[allow(clippy::cast_precision_loss)]
226 let spacing = hierarchy.indent_level as f32 * 16.0;
227 if spacing > 0.0 {
228 ui.add_space(spacing);
229
230 let rect = ui.max_rect();
231 let painter = ui.painter();
232 let stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(65, 65, 65)); for i in 0..hierarchy.indent_level {
235 #[allow(clippy::cast_precision_loss)]
236 let x = (i as f32).mul_add(16.0, rect.min.x - spacing) + 8.0;
237
238 let dash_length = 2.0;
240 let gap_length = 2.0;
241 let step = dash_length + gap_length;
242 let total_height = rect.max.y - rect.min.y;
243 if total_height > 0.0 {
244 let num_steps = (total_height / step).ceil() as usize;
245 for step_idx in 0..num_steps {
246 #[allow(clippy::cast_precision_loss)]
247 let segment_y = (step_idx as f32).mul_add(step, rect.min.y);
248 let next_y = (segment_y + dash_length).min(rect.max.y);
249 painter.line_segment(
250 [egui::pos2(x, segment_y), egui::pos2(x, next_y)],
251 stroke,
252 );
253 }
254 }
255 }
256 }
257
258 if hierarchy.has_children {
259 let arrow = if hierarchy.is_expanded { "⏷" } else { "⏵" };
260
261 let arrow_color = if hierarchy.is_expanded {
262 ui.visuals().widgets.active.text_color()
263 } else {
264 ui.visuals().widgets.inactive.text_color()
265 };
266
267 let rich_arrow = egui::RichText::new(arrow).color(arrow_color);
268 if ui
269 .selectable_label(hierarchy.is_expanded, rich_arrow)
270 .clicked()
271 {
272 if hierarchy.is_expanded {
273 self.expanded_rows.remove(row_index as u32);
274 } else {
275 self.expanded_rows.insert(row_index as u32);
276 }
277
278 self.sorted_children_cache.remove(&row_index);
279 changed = true;
280 }
281 } else {
282 ui.add_space(16.0);
284 }
285
286 changed
287 }
288
289 pub fn handle_row_selection(&mut self, modifiers: egui::Modifiers, row_index: usize) {
290 let selected_rows = &mut self.selected_rows;
291 let active_rows = &self.active_rows;
292
293 let row_idx_u32 = row_index as u32;
294
295 if modifiers.command || modifiers.ctrl {
296 if selected_rows.contains(row_idx_u32) {
297 selected_rows.remove(row_idx_u32);
298 self.last_clicked_visible_index = None;
299 } else {
300 selected_rows.insert(row_idx_u32);
301 self.last_clicked_visible_index = active_rows.iter().position(|&r| r == row_index);
302 }
303 } else if modifiers.shift && self.last_clicked_visible_index.is_some() {
304 if let Some(anchor_visible_pos) = self.last_clicked_visible_index
305 && let Some(current_visible_pos) = active_rows.iter().position(|&r| r == row_index)
306 {
307 let start = anchor_visible_pos.min(current_visible_pos);
308 let end = anchor_visible_pos.max(current_visible_pos);
309 for visible_idx in start..=end {
310 if let Some(&actual_row_idx) = active_rows.get(visible_idx) {
311 selected_rows.insert(actual_row_idx as u32);
312 }
313 }
314 }
315 } else if selected_rows.len() == 1 && selected_rows.contains(row_idx_u32) {
316 selected_rows.clear();
317 self.last_clicked_visible_index = None;
318 } else {
319 selected_rows.clear();
320 selected_rows.insert(row_idx_u32);
321 self.last_clicked_visible_index = active_rows.iter().position(|&r| r == row_index);
322 }
323 }
324
325 pub fn rebuild_tree_filter_cache(&mut self, provider: &dyn TableProvider) {
327 let row_count = provider.row_count();
328
329 let is_empty = self.filter_matches.is_empty();
331 if self.filter_cache_dirty || is_empty {
332 self.sorted_children_cache.clear();
333 }
334
335 if !self.filter_cache_dirty && !is_empty {
336 return; }
338 self.filter_cache_dirty = false;
339 self.filter_matches.clear();
340
341 let active_filters = self.get_filter_state();
342 if active_filters.is_empty() {
343 self.filter_matches.insert_range(0..row_count as u32);
345 return;
346 }
347
348 for row_idx in (0..row_count).rev() {
350 let highlight = self.highlights.get_usize(row_idx);
351 let matches = provider.row_matches(self, row_idx, &active_filters, highlight);
352
353 if matches {
354 self.filter_matches.insert(row_idx as u32);
355 }
356
357 if self.filter_matches.contains(row_idx as u32)
359 && let Some(parent_idx) = provider.row_parent(row_idx)
360 {
361 self.filter_matches.insert(parent_idx as u32);
362 }
363 }
364 }
365
366 pub fn flatten_tree(&mut self, provider: &dyn TableProvider) {
368 self.rebuild_tree_filter_cache(provider);
369
370 let mut active = Vec::with_capacity(provider.row_count());
371 if provider.row_count() > 0 {
372 self.flatten_tree_impl(provider, 0, &mut active);
374 }
375 self.active_rows = active;
376 }
377
378 fn flatten_tree_impl(
379 &mut self,
380 provider: &dyn TableProvider,
381 row_idx: usize,
382 out: &mut Vec<usize>,
383 ) {
384 if !self.filter_matches.contains(row_idx as u32) {
386 return; }
388
389 out.push(row_idx);
390
391 let is_expanded = self.expanded_rows.contains(row_idx as u32);
392 if is_expanded {
393 let sorted_children = if let Some(cached) = self.sorted_children_cache.get(&row_idx) {
395 cached.clone()
396 } else {
397 let mut children = provider.row_children(row_idx);
398 let sort_state = self.get_sort_state();
399 if let Some((sort_col, sort_up)) = sort_state {
400 let _ = provider.sort_active_rows(&mut children, sort_col, sort_up);
401 }
402 self.sorted_children_cache.insert(row_idx, children.clone());
403 children
404 };
405
406 for child_idx in sorted_children {
407 self.flatten_tree_impl(provider, child_idx, out);
408 }
409 }
410 }
411}
412
413pub trait TableStateExt {
414 fn collect_responses(&mut self, responses: Vec<ColResponse>) -> TableChanges;
415}
416
417impl TableStateExt for TableState {
418 fn collect_responses(&mut self, responses: Vec<ColResponse>) -> TableChanges {
419 let mut filter_update = None;
420 let mut sort_update = None;
421 let mut filter_state = Vec::with_capacity(responses.len());
422
423 if self.columns.len() < responses.len() {
425 self.columns
426 .resize_with(responses.len(), ColumnState::default);
427 }
428
429 for (col_index, response) in responses.into_iter().enumerate() {
430 let col = &mut self.columns[col_index];
431 let old_active = !col.response.filtering.is_empty();
432 let new_active = !response.filtering.is_empty();
433
434 if !old_active && new_active {
435 filter_update = Some(FilterUpdate::Added(col_index, response.filtering.clone()));
436 self.filter_cache_dirty = true;
437 } else if old_active && !new_active {
438 filter_update = Some(FilterUpdate::Removed(col_index));
439 self.filter_cache_dirty = true;
440 } else if old_active && new_active {
441 let old = &col.response.filtering;
442 let new = &response.filtering;
443
444 if old.search.text() != new.search.text()
445 || old.search.options() != new.search.options()
446 || old.highlight != new.highlight
447 {
448 filter_update = Some(FilterUpdate::Modified(
449 col_index,
450 response.filtering.clone(),
451 ));
452 self.filter_cache_dirty = true;
453 }
454 }
455
456 if new_active {
457 filter_state.push((col_index, response.filtering.clone()));
458 }
459
460 col.response = response;
461 if col.response.to_sort {
462 col.response.to_sort = false;
463 sort_update = Some(col_index);
464 }
465 }
466
467 let sort_state = self.get_sort_state();
468
469 TableChanges {
470 filter_update,
471 sort_update,
472 filter_state,
473 sort_state,
474 }
475 }
476}