1use accesskit::SortDirection as AccessSortDirection;
2use std::{
3 collections::{HashMap, HashSet},
4 hash::Hash,
5 marker::PhantomData,
6 ops::Deref,
7 rc::Rc,
8 sync::Arc,
9};
10
11use crate::prelude::*;
12
13use super::{
14 Cell, TableSelectionMode, TableSortCycle, TableSortDirection, TableSortState, TreeNodeRow,
15 TreeTableColumn, TreeTableEvent, TreeTableFirstCellEvent, TreeTableRow,
16 table::next_sort_direction, table::sort_direction_for_column,
17};
18
19#[derive(Clone, PartialEq)]
20enum TableFocus<Id, K>
21where
22 Id: Eq + Hash + Clone + Send + Sync + 'static,
23 K: Clone + PartialEq + Send + Sync + 'static,
24{
25 Row(Id),
26 Cell(Id, K),
27}
28
29fn flatten_hierarchy_rows<U, Id>(
30 tree: &U,
31 root_ids: &dyn Fn(&U) -> Vec<Id>,
32 child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
33 is_visible: &dyn Fn(&U, &Id) -> bool,
34) -> Vec<TreeNodeRow<Id>>
35where
36 Id: Clone + Eq + Hash + 'static,
37{
38 fn visit<U, Id>(
39 tree: &U,
40 node_id: Id,
41 parent_id: Option<Id>,
42 child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
43 is_visible: &dyn Fn(&U, &Id) -> bool,
44 out: &mut Vec<TreeNodeRow<Id>>,
45 ) where
46 Id: Clone + Eq + Hash + 'static,
47 {
48 if !is_visible(tree, &node_id) {
49 return;
50 }
51
52 out.push(TreeNodeRow { id: node_id.clone(), parent_id });
53
54 for child_id in child_ids(tree, &node_id) {
55 visit(tree, child_id, Some(node_id.clone()), child_ids, is_visible, out);
56 }
57 }
58
59 let mut rows = Vec::new();
60 for root_id in root_ids(tree) {
61 visit(tree, root_id, None, child_ids, is_visible, &mut rows);
62 }
63
64 rows
65}
66
67fn flatten_visible_rows<T, V, Id>(
68 rows: &V,
69 row_id: &dyn Fn(&T) -> Id,
70 parent_id: &dyn Fn(&T) -> Option<Id>,
71 expanded_row_ids: &[Id],
72) -> Vec<TreeTableRow<T, Id>>
73where
74 V: Deref<Target = [T]>,
75 T: PartialEq + Clone + 'static,
76 Id: Clone + Eq + Hash + 'static,
77{
78 let mut rows_by_parent: HashMap<Option<Id>, Vec<T>> = HashMap::new();
79 for row in rows.deref().iter().cloned() {
80 rows_by_parent.entry(parent_id(&row)).or_default().push(row);
81 }
82
83 let expanded_set: HashSet<Id> = expanded_row_ids.iter().cloned().collect();
84 let mut visible_rows = Vec::new();
85
86 fn visit<T, Id>(
87 rows: &[T],
88 depth: usize,
89 rows_by_parent: &HashMap<Option<Id>, Vec<T>>,
90 expanded_set: &HashSet<Id>,
91 row_id: &dyn Fn(&T) -> Id,
92 out: &mut Vec<TreeTableRow<T, Id>>,
93 ) where
94 T: PartialEq + Clone + 'static,
95 Id: Clone + Eq + Hash + 'static,
96 {
97 for row in rows {
98 let id = row_id(row);
99 let child_rows =
100 rows_by_parent.get(&Some(id.clone())).map(Vec::as_slice).unwrap_or(&[]);
101 let has_children = !child_rows.is_empty();
102 let expanded = has_children && expanded_set.contains(&id);
103
104 out.push(TreeTableRow {
105 row: row.clone(),
106 id: id.clone(),
107 parent_id: None,
108 depth,
109 has_children,
110 expanded,
111 });
112
113 if expanded {
114 visit(child_rows, depth + 1, rows_by_parent, expanded_set, row_id, out);
115 }
116 }
117 }
118
119 let roots = rows_by_parent.get(&None).map(Vec::as_slice).unwrap_or(&[]);
120 visit(roots, 0, &rows_by_parent, &expanded_set, row_id, &mut visible_rows);
121
122 for visible_row in &mut visible_rows {
123 visible_row.parent_id = parent_id(&visible_row.row);
124 }
125
126 visible_rows
127}
128
129pub struct VirtualTreeTable<T, V, Id, H, K = String>
130where
131 V: Deref<Target = [T]> + Clone + 'static,
132 T: PartialEq + Clone + 'static,
133 Id: Eq + Hash + Clone + Send + Sync + 'static,
134 H: View,
135 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
136{
137 rows: Signal<V>,
138 visible_rows: Memo<Vec<TreeTableRow<T, Id>>>,
139 _header: PhantomData<H>,
140 row_id: Rc<dyn Fn(&T) -> Id>,
141 parent_id: Rc<dyn Fn(&T) -> Option<Id>>,
142 sort_state: Signal<Option<TableSortState<K>>>,
143 sort_cycle: Signal<TableSortCycle>,
144 resizable_columns: Signal<bool>,
145 selectable: Signal<Selectable>,
146 selection_follows_focus: Signal<bool>,
147 selection: Signal<HashSet<Cell<Id, K>>>,
148 selection_mode: Signal<TableSelectionMode>,
149 expanded_row_ids: Signal<Vec<Id>>,
150 focused: Signal<Option<TableFocus<Id, K>>>,
151 treegrid_label: Signal<Option<String>>,
152 on_sort: Option<Arc<dyn Fn(&mut EventContext, K, TableSortDirection) + Send + Sync>>,
153 on_select: Option<Box<dyn Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync>>,
154 on_row_toggle: Option<Box<dyn Fn(&mut EventContext, Id, bool)>>,
155 columns: Memo<Vec<K>>,
156}
157
158impl<T, V, Id, H, K> VirtualTreeTable<T, V, Id, H, K>
159where
160 V: Deref<Target = [T]> + Clone + 'static,
161 T: PartialEq + Clone + 'static,
162 Id: Eq + Hash + Clone + Send + Sync + 'static,
163 H: Clone + View,
164 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
165{
166 pub fn new<S, U, C, R, F>(
167 cx: &mut Context,
168 tree: S,
169 columns: C,
170 item_height: f32,
171 flatten_rows: F,
172 row_id: impl Fn(&T) -> Id + 'static,
173 parent_id: impl Fn(&T) -> Option<Id> + 'static,
174 ) -> Handle<Self>
175 where
176 S: Res<U> + 'static,
177 U: Clone + 'static,
178 C: Res<R> + 'static,
179 R: Deref<Target = [TreeTableColumn<T, Id, H, K>]> + Clone + 'static,
180 F: Fn(&U) -> V + 'static,
181 {
182 let tree_signal = tree.to_signal(cx);
183 let flatten_rows: Rc<dyn Fn(&U) -> V> = Rc::new(flatten_rows);
184 let row_signal = Signal::new(tree_signal.with(|tree| flatten_rows(tree)));
185 let column_signal = columns.to_signal(cx);
186 let row_id: Rc<dyn Fn(&T) -> Id> = Rc::new(row_id);
187 let parent_id: Rc<dyn Fn(&T) -> Option<Id>> = Rc::new(parent_id);
188
189 let sort_state = Signal::new(None);
190 let sort_cycle = Signal::new(TableSortCycle::BiState);
191 let resizable_columns = Signal::new(false);
192 let selectable = Signal::new(Selectable::None);
193 let selection_follows_focus = Signal::new(false);
194 let selection = Signal::new(HashSet::new());
195 let selection_mode = Signal::new(TableSelectionMode::Cell);
196 let expanded_row_ids = Signal::new(Vec::new());
197 let focused = Signal::new(None);
198 let treegrid_label = Signal::new(None::<String>);
199
200 let visible_rows = Memo::new({
201 let row_id = row_id.clone();
202 let parent_id = parent_id.clone();
203 move |_| {
204 row_signal.with(|rows| {
205 expanded_row_ids.with(|expanded| {
206 flatten_visible_rows(rows, &*row_id, &*parent_id, expanded)
207 })
208 })
209 }
210 });
211
212 let focused_index = Memo::new(move |_| {
213 visible_rows.with(|rows| {
214 let focused_row_id =
215 focused.with(|focused: &Option<TableFocus<Id, K>>| match focused {
216 Some(TableFocus::Row(id)) => Some(id.clone()),
217 Some(TableFocus::Cell(_id, _)) => None,
218 None => None,
219 });
220
221 rows.iter().position(|row| Some(&row.id) == focused_row_id.as_ref())
222 })
223 });
224
225 let columns = Memo::new(move |_| {
226 column_signal.with(|columns| {
227 columns
228 .deref()
229 .iter()
230 .filter(|column| !column.hidden.get())
231 .map(|column| column.key.clone())
232 .collect::<Vec<_>>()
233 })
234 });
235
236 let column_layout = Memo::new(move |_| {
237 column_signal.with(|columns| {
238 columns
239 .deref()
240 .iter()
241 .map(|column| (column.key.clone(), column.hidden.get()))
242 .collect::<Vec<_>>()
243 })
244 });
245
246 let handle = Self {
247 rows: row_signal,
248 visible_rows,
249 _header: PhantomData,
250 row_id,
251 parent_id,
252 sort_state,
253 sort_cycle,
254 resizable_columns,
255 selectable,
256 selection_follows_focus,
257 selection,
258 selection_mode,
259 expanded_row_ids,
260 focused,
261 treegrid_label,
262 on_sort: None,
263 on_select: None,
264 on_row_toggle: None,
265 columns,
266 }
267 .build(cx, move |cx| {
268 Keymap::from(vec![
269 (
270 KeyChord::new(Modifiers::empty(), Code::ArrowRight),
271 KeymapEntry::new("Focus Right", |cx| {
272 cx.emit(TreeTableEvent::<K, Id>::FocusRight)
273 }),
274 ),
275 (
276 KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
277 KeymapEntry::new("Focus Left", |cx| {
278 cx.emit(TreeTableEvent::<K, Id>::FocusLeft)
279 }),
280 ),
281 (
282 KeyChord::new(Modifiers::empty(), Code::ArrowUp),
283 KeymapEntry::new("Focus Up", |cx| cx.emit(TreeTableEvent::<K, Id>::FocusUp)),
284 ),
285 (
286 KeyChord::new(Modifiers::empty(), Code::ArrowDown),
287 KeymapEntry::new("Focus Down", |cx| {
288 cx.emit(TreeTableEvent::<K, Id>::FocusDown)
289 }),
290 ),
291 (
292 KeyChord::new(Modifiers::empty(), Code::Home),
293 KeymapEntry::new("Focus Home", |cx| {
294 cx.emit(TreeTableEvent::<K, Id>::FocusHome)
295 }),
296 ),
297 (
298 KeyChord::new(Modifiers::empty(), Code::End),
299 KeymapEntry::new("Focus End", |cx| cx.emit(TreeTableEvent::<K, Id>::FocusEnd)),
300 ),
301 (
302 KeyChord::new(Modifiers::empty(), Code::PageUp),
303 KeymapEntry::new("Focus Page Up", |cx| {
304 cx.emit(TreeTableEvent::<K, Id>::PageUp)
305 }),
306 ),
307 (
308 KeyChord::new(Modifiers::empty(), Code::PageDown),
309 KeymapEntry::new("Focus Page Down", |cx| {
310 cx.emit(TreeTableEvent::<K, Id>::PageDown)
311 }),
312 ),
313 (
314 KeyChord::new(Modifiers::CTRL, Code::Home),
315 KeymapEntry::new("Focus Control Home", |cx| {
316 cx.emit(TreeTableEvent::<K, Id>::CtrlHome)
317 }),
318 ),
319 (
320 KeyChord::new(Modifiers::CTRL, Code::End),
321 KeymapEntry::new("Focus Control End", |cx| {
322 cx.emit(TreeTableEvent::<K, Id>::CtrlEnd)
323 }),
324 ),
325 (
326 KeyChord::new(Modifiers::empty(), Code::Enter),
327 KeymapEntry::new("Select Focused", |cx| {
328 cx.emit(TreeTableEvent::<K, Id>::SelectFocused)
329 }),
330 ),
331 ])
332 .build(cx);
333
334 Binding::new(cx, column_layout, move |cx| {
335 let visible_columns = column_signal.with(|columns| {
336 columns
337 .deref()
338 .iter()
339 .filter(|column| !column.hidden.get())
340 .cloned()
341 .collect::<Vec<_>>()
342 });
343
344 let last_header_index = visible_columns.len().saturating_sub(1);
345 let header_columns = Rc::new(visible_columns);
346
347 HStack::new(cx, move |cx| {
348 for (column_index, column) in header_columns.iter().cloned().enumerate() {
349 let width_signal = column.width;
350 let sort_state = sort_state;
351 let resizable_columns = resizable_columns;
352 let min_width = column.min_width;
353 let sortable = column.sortable;
354 let resizable = column.resizable;
355 let is_last_column = column_index == last_header_index;
356 let header_content = column.header_content.clone();
357 let column_key = column.key.clone();
358 let sort_direction = sort_state.map({
359 let column_key = column_key.clone();
360 move |state| sort_direction_for_column(state.as_ref(), &column_key)
361 });
362
363 if is_last_column {
364 let header = header_content(cx, sort_direction);
365 let on_press_column_key = column_key.clone();
366 header
367 .class("table-header-cell")
368 .role(Role::ColumnHeader)
369 .sort_direction(sort_direction.map(|direction| match direction {
370 TableSortDirection::Ascending => {
371 Some(AccessSortDirection::Ascending)
372 }
373 TableSortDirection::Descending => {
374 Some(AccessSortDirection::Descending)
375 }
376 TableSortDirection::None => None,
377 }))
378 .toggle_class("sortable", sortable)
379 .toggle_class("not-sortable", sortable.map(|value| !*value))
380 .toggle_class("resizable", false)
381 .toggle_class("not-resizable", true)
382 .width(Stretch(1.0))
383 .min_width(Auto)
384 .on_press(move |cx| {
385 cx.emit(TreeTableEvent::<K, Id>::SelectColumn(
386 on_press_column_key.clone(),
387 ));
388 });
389 } else {
390 Resizable::new(
391 cx,
392 width_signal,
393 ResizeStackDirection::Right,
394 move |_cx, new_size| {
395 if resizable_columns.get() && resizable.get() {
396 width_signal.set(Pixels(new_size.max(min_width.get())));
397 }
398 },
399 move |cx| {
400 let header = header_content(cx, sort_direction);
401 let on_press_column_key = column_key.clone();
402 header.on_press(move |cx| {
403 cx.emit(TreeTableEvent::<K, Id>::SelectColumn(
404 on_press_column_key.clone(),
405 ));
406 });
407 },
408 )
409 .class("table-header-cell")
410 .role(Role::ColumnHeader)
411 .sort_direction(sort_direction.map(|direction| match direction {
412 TableSortDirection::Ascending => {
413 Some(AccessSortDirection::Ascending)
414 }
415 TableSortDirection::Descending => {
416 Some(AccessSortDirection::Descending)
417 }
418 TableSortDirection::None => None,
419 }))
420 .toggle_class("sortable", sortable)
421 .toggle_class("not-sortable", sortable.map(|value| !*value))
422 .toggle_class(
423 "resizable",
424 resizable_columns.map(move |enabled| *enabled && resizable.get()),
425 )
426 .toggle_class(
427 "not-resizable",
428 resizable_columns.map(move |enabled| !*enabled || !resizable.get()),
429 )
430 .width(width_signal)
431 .min_width(min_width.map(|value| Pixels(*value)));
432 }
433 }
434 })
435 .class("table-header-row")
436 .height(Auto)
437 .width(Stretch(1.0))
438 .min_width(Auto);
439
440 VirtualList::new_custom_items_with_selection(
441 cx,
442 visible_rows,
443 item_height,
444 move |cx, row_index, row, _selected| {
445 let row: Memo<TreeTableRow<T, Id>> = Memo::new(move |_| row.get());
446 let mut row_handle =
447 HStack::new(cx, |cx| {
448 column_signal.with(|columns| {
449 let visible_columns = columns
450 .deref()
451 .iter()
452 .filter(|column| !column.hidden.get())
453 .collect::<Vec<_>>();
454
455 for (column_index, column) in visible_columns.iter().enumerate()
456 {
457 let width_signal = column.width;
458 let min_width = column.min_width;
459 let cell_content = column.cell_content.clone();
460 let is_last_column =
461 column_index + 1 == visible_columns.len();
462
463 let col_key = column.key.clone();
464 let cell_row_id = row.map(|value| value.id.clone());
465 let cell_col_key = col_key.clone();
466
467 let col_key_for_focus = col_key.clone();
468 let row_id_for_focus = row.map(|value| value.id.clone());
469 let cell_is_focused =
470 focused.map(move |focused| match focused {
471 Some(TableFocus::Cell(id, col)) => {
472 *col == col_key_for_focus
473 && *id == row_id_for_focus.get()
474 }
475 _ => false,
476 });
477
478 let col_key_for_select = col_key.clone();
479 let row_id_for_select = row.map(|value| value.id.clone());
480 let cell_is_selected = selection.map(move |selection| {
481 selection.contains(&Cell(
482 row_id_for_select.get(),
483 col_key_for_select.clone(),
484 ))
485 });
486
487 let cell_col_key_1 = cell_col_key.clone();
488 let cell_col_key_2 = cell_col_key.clone();
489
490 if is_last_column {
491 VStack::new(cx, move |cx| {
492 cell_content(cx, row);
493 })
494 .class("table-cell")
495 .role(Role::GridCell)
496 .toggle_class("selected", cell_is_selected)
497 .selected(cell_is_selected)
498 .focusable(true)
499 .navigable(false)
500 .focused_with_visibility(cell_is_focused, true)
501 .width(Stretch(1.0))
502 .min_width(Auto)
503 .height(Stretch(1.0))
504 .min_height(Auto)
505 .on_press(move |cx| {
506 cx.emit(TreeTableEvent::<K, Id>::SelectCell(
507 cell_row_id.get(),
508 cell_col_key_1.clone(),
509 ));
510 });
511 } else {
512 VStack::new(cx, move |cx| {
513 cell_content(cx, row);
514 })
515 .class("table-cell")
516 .role(Role::GridCell)
517 .toggle_class("selected", cell_is_selected)
518 .selected(cell_is_selected)
519 .focusable(true)
520 .navigable(false)
521 .focused_with_visibility(cell_is_focused, true)
522 .width(width_signal)
523 .min_width(min_width.map(|value| Pixels(*value)))
524 .height(Stretch(1.0))
525 .min_height(Auto)
526 .on_press(move |cx| {
527 cx.emit(TreeTableEvent::<K, Id>::SelectCell(
528 cell_row_id.get(),
529 cell_col_key_2.clone(),
530 ));
531 });
532 }
533 }
534 });
535 })
536 .class("table-row")
537 .toggle_class("odd", row_index % 2 == 1)
538 .toggle_class("even", row_index % 2 == 0)
539 .toggle_class("expanded", row.map(|value| value.expanded))
540 .toggle_class("collapsible", row.map(|value| value.has_children))
541 .alignment(Alignment::Left)
542 .height(Auto)
543 .width(Stretch(1.0))
544 .min_width(Auto)
545 .role(Role::Row)
546 .level(row.map(|value| value.depth + 1))
547 .on_press(move |cx| cx.emit(ListEvent::Select(row_index)));
548
549 if row.get().has_children {
550 row_handle = row_handle.expanded(row.map(|value| value.expanded));
551 }
552
553 row_handle
554 },
555 )
556 .width(Stretch(1.0))
557 .min_width(Auto)
558 .height(Stretch(1.0))
559 .min_height(Auto)
560 .class("table-body")
561 .class("tree-table-body")
562 .focused_index(focused_index)
563 .on_focus(move |_cx, index| {
564 visible_rows.with(|rows| {
565 if let Some(row) = rows.get(index) {
566 focused.set(Some(TableFocus::Row(row.id.clone())));
567 }
568 });
569 })
570 .on_build(|cx| {
571 cx.emit_to(
572 cx.current(),
573 KeymapEvent::RemoveAction(
574 KeyChord::new(Modifiers::empty(), Code::ArrowDown),
575 "Focus Next",
576 ),
577 );
578
579 cx.emit_to(
580 cx.current(),
581 KeymapEvent::RemoveAction(
582 KeyChord::new(Modifiers::empty(), Code::ArrowUp),
583 "Focus Previous",
584 ),
585 );
586 });
587 });
588 })
589 .class("table")
590 .class("virtual-table")
591 .class("tree-table")
592 .class("virtual-tree-table")
593 .navigable(true)
594 .name(treegrid_label.map(|label| label.clone().unwrap_or_else(|| "Tree table".to_string())))
595 .multiselectable(selectable.map(|mode| *mode == Selectable::Multi))
596 .role(Role::TreeGrid);
597
598 let flatten_rows_for_bind = flatten_rows.clone();
599 handle.bind(tree_signal, move |handle| {
600 let rows = tree_signal.with(|tree| flatten_rows_for_bind(tree));
601 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| table.rows.set(rows));
602 })
603 }
604
605 pub fn from_rows<S, C, R>(
606 cx: &mut Context,
607 rows: S,
608 columns: C,
609 item_height: f32,
610 row_id: impl Fn(&T) -> Id + 'static,
611 parent_id: impl Fn(&T) -> Option<Id> + 'static,
612 ) -> Handle<Self>
613 where
614 S: Res<V> + 'static,
615 C: Res<R> + 'static,
616 R: Deref<Target = [TreeTableColumn<T, Id, H, K>]> + Clone + 'static,
617 {
618 Self::new(cx, rows, columns, item_height, |rows: &V| rows.clone(), row_id, parent_id)
619 }
620
621 fn emit_toggle(&self, cx: &mut EventContext, row_id: Id, next_expanded: bool) {
622 if let Some(callback) = &self.on_row_toggle {
623 (callback)(cx, row_id, next_expanded);
624 }
625 }
626
627 fn focused_visible_row(&self) -> Option<TreeTableRow<T, Id>> {
628 let focused_id = self.focused.get().map(|focused| match focused {
629 TableFocus::Row(id) => id.clone(),
630 TableFocus::Cell(id, _) => id.clone(),
631 })?;
632
633 self.visible_rows.with(|rows| rows.clone()).into_iter().find(|row| row.id == focused_id)
634 }
635
636 fn focus_row_id(&self, row_id: Id) {
637 let next_focus = match self.focused.get() {
638 Some(TableFocus::Cell(_, column_key)) => TableFocus::Cell(row_id, column_key),
639 _ => TableFocus::Row(row_id),
640 };
641
642 self.focused.set(Some(next_focus));
643 }
644}
645
646impl<Id, H, K> VirtualTreeTable<TreeNodeRow<Id>, Vec<TreeNodeRow<Id>>, Id, H, K>
647where
648 Id: Eq + Hash + Clone + Send + Sync + 'static,
649 H: Clone + View,
650 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
651{
652 pub fn from_hierarchy<S, U, C, R>(
660 cx: &mut Context,
661 tree: S,
662 columns: C,
663 item_height: f32,
664 root_ids: impl Fn(&U) -> Vec<Id> + 'static,
665 child_ids: impl Fn(&U, &Id) -> Vec<Id> + 'static,
666 is_visible: impl Fn(&U, &Id) -> bool + 'static,
667 ) -> Handle<Self>
668 where
669 S: Res<U> + 'static,
670 U: Clone + 'static,
671 C: Res<R> + 'static,
672 R: Deref<Target = [TreeTableColumn<TreeNodeRow<Id>, Id, H, K>]> + Clone + 'static,
673 {
674 let root_ids: Rc<dyn Fn(&U) -> Vec<Id>> = Rc::new(root_ids);
675 let child_ids: Rc<dyn Fn(&U, &Id) -> Vec<Id>> = Rc::new(child_ids);
676 let is_visible: Rc<dyn Fn(&U, &Id) -> bool> = Rc::new(is_visible);
677
678 Self::new(
679 cx,
680 tree,
681 columns,
682 item_height,
683 move |tree: &U| flatten_hierarchy_rows(tree, &*root_ids, &*child_ids, &*is_visible),
684 |row: &TreeNodeRow<Id>| row.id.clone(),
685 |row: &TreeNodeRow<Id>| row.parent_id.clone(),
686 )
687 }
688}
689
690impl<T, V, Id, H, K> View for VirtualTreeTable<T, V, Id, H, K>
691where
692 V: Deref<Target = [T]> + Clone + 'static,
693 T: PartialEq + Clone + 'static,
694 Id: Eq + Hash + Clone + Send + Sync + 'static,
695 H: Clone + View,
696 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
697{
698 fn element(&self) -> Option<&'static str> {
699 Some("virtual-tree-table")
700 }
701
702 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
703 event.map(|table_event: &TreeTableEvent<K, Id>, _| match table_event {
704 TreeTableEvent::RequestSort(key, direction) => {
705 if let Some(callback) = &self.on_sort {
706 (callback)(cx, key.clone(), *direction);
707 }
708 }
709
710 TreeTableEvent::SelectColumn(column_key) => {
711 let visible_rows = flatten_visible_rows(
712 &self.rows.get(),
713 &*self.row_id,
714 &*self.parent_id,
715 &self.expanded_row_ids.get(),
716 );
717
718 let column_cells = HashSet::from_iter(
719 visible_rows.iter().map(|row| Cell(row.id.clone(), column_key.clone())),
720 );
721
722 let current_selection = self.selection.get();
723 let next_selection =
724 if current_selection == column_cells { HashSet::new() } else { column_cells };
725
726 self.selection.set(next_selection.clone());
727
728 if let Some(callback) = &self.on_select {
729 (callback)(cx, next_selection);
730 }
731 }
732
733 TreeTableEvent::SelectRow(index) => {
734 if self.selectable.get() == Selectable::None {
735 return;
736 }
737
738 let visible_rows = flatten_visible_rows(
739 &self.rows.get(),
740 &*self.row_id,
741 &*self.parent_id,
742 &self.expanded_row_ids.get(),
743 );
744
745 if let Some(row) = visible_rows.get(*index) {
746 let cols = self.columns.get();
747 self.selection.set(HashSet::from_iter(
748 cols.iter().map(|col| Cell(row.id.clone(), col.clone())),
749 ));
750
751 if let Some(callback) = &self.on_select {
752 (callback)(cx, self.selection.get());
753 }
754 }
755 }
756
757 TreeTableEvent::SelectCell(row_id, column_key) => {
758 let selectable = self.selectable.get();
759 if selectable == Selectable::None {
760 return;
761 }
762
763 let selection_mode = self.selection_mode.get();
764
765 let next_selection = match selection_mode {
766 TableSelectionMode::Cell => {
767 let cell = Cell(row_id.clone(), column_key.clone());
768 if selectable == Selectable::Single {
769 let current = self.selection.get();
770 if current.contains(&cell) {
771 HashSet::new()
772 } else {
773 HashSet::from([cell])
774 }
775 } else {
776 let current = self.selection.get();
777 let mut next = current;
778 if next.contains(&cell) {
779 next.remove(&cell);
780 } else {
781 next.insert(cell);
782 }
783 next
784 }
785 }
786 TableSelectionMode::Row => {
787 let cols = self.columns.get();
788 let row_cells: HashSet<Cell<Id, K>> = HashSet::from_iter(
789 cols.iter().map(|col| Cell(row_id.clone(), col.clone())),
790 );
791
792 if selectable == Selectable::Single {
793 let current = self.selection.get();
794 if current == row_cells { HashSet::new() } else { row_cells }
795 } else {
796 let mut next = self.selection.get();
797 let row_selected = row_cells.iter().all(|c| next.contains(c));
798 if row_selected {
799 for c in &row_cells {
800 next.remove(c);
801 }
802 } else {
803 next.extend(row_cells);
804 }
805 next
806 }
807 }
808 };
809
810 self.selection.set(next_selection.clone());
811
812 if let Some(callback) = &self.on_select {
813 (callback)(cx, next_selection);
814 }
815 }
816
817 TreeTableEvent::SelectFocused => self.focused.with(|focused| match focused {
818 Some(TableFocus::Row(id)) => {
819 if self.selectable.get() == Selectable::None {
820 return;
821 }
822
823 let cols = self.columns.get();
824 self.selection.set(HashSet::from_iter(
825 cols.iter().map(|col| Cell(id.clone(), col.clone())),
826 ));
827
828 if let Some(callback) = &self.on_select {
829 (callback)(cx, self.selection.get());
830 }
831 }
832 Some(TableFocus::Cell(id, col)) => {
833 self.selection.set(HashSet::from([Cell(id.clone(), col.clone())]));
834
835 if let Some(callback) = &self.on_select {
836 (callback)(cx, self.selection.get());
837 }
838 }
839 None => {}
840 }),
841
842 TreeTableEvent::ExpandSelected => {
843 if let Some(row) = self.focused_visible_row() {
844 if row.has_children && !row.expanded {
845 self.emit_toggle(cx, row.id, true);
846 } else if row.has_children {
847 let child_id = self.visible_rows.with(|rows| {
848 rows.iter()
849 .find(|candidate| candidate.parent_id.as_ref() == Some(&row.id))
850 .map(|candidate| candidate.id.clone())
851 });
852
853 if let Some(child_id) = child_id {
854 self.focus_row_id(child_id);
855 }
856 }
857 }
858 }
859
860 TreeTableEvent::FocusRight => {
861 let next_focus = match self.focused.get() {
862 Some(TableFocus::Row(id)) => {
863 if let Some(row) = self.focused_visible_row() {
864 if row.has_children && !row.expanded {
865 cx.emit(TreeTableEvent::<K, Id>::ExpandSelected);
866 return;
867 }
868 }
869
870 let first_column_key =
871 self.columns.with(|columns| columns.first().cloned()).unwrap();
872 Some(TableFocus::Cell(id.clone(), first_column_key))
873 }
874 Some(TableFocus::Cell(id, col)) => {
875 let next_col = self.columns.with(|columns| {
876 columns
877 .iter()
878 .position(|column| column == &col)
879 .and_then(|index| index.checked_add(1))
880 .and_then(|index| columns.get(index))
881 .cloned()
882 });
883
884 next_col.map(|next_col| TableFocus::Cell(id.clone(), next_col))
885 }
886 None => None,
887 };
888
889 if next_focus.is_some() {
890 self.focused.set(next_focus);
891 }
892 }
893
894 TreeTableEvent::FocusLeft => {
895 let next_focus = match self.focused.get() {
896 Some(TableFocus::Row(_)) => {
897 if let Some(row) = self.focused_visible_row() {
898 if row.has_children && row.expanded {
899 self.emit_toggle(cx, row.id, false);
900 }
901 }
902
903 None
904 }
905 Some(TableFocus::Cell(id, col)) => {
906 let prev_col = self.columns.with(|columns| {
907 columns
908 .iter()
909 .position(|column| column == &col)
910 .and_then(|index| index.checked_sub(1))
911 .and_then(|index| columns.get(index))
912 .cloned()
913 });
914
915 prev_col
916 .map(|prev_col| TableFocus::Cell(id.clone(), prev_col))
917 .or_else(|| Some(TableFocus::Row(id.clone())))
918 }
919 None => None,
920 };
921
922 if next_focus.is_some() {
923 self.focused.set(next_focus);
924 }
925 }
926
927 TreeTableEvent::FocusHome => {
928 let next_focus = self.focused.with(|focused| match focused {
929 Some(TableFocus::Row(_)) => self
930 .visible_rows
931 .with(|rows| rows.first().map(|row| TableFocus::Row(row.id.clone()))),
932 Some(TableFocus::Cell(_, col)) => self.visible_rows.with(|rows| {
933 rows.first().map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
934 }),
935 None => None,
936 });
937
938 if next_focus.is_some() {
939 self.focused.set(next_focus);
940 }
941 }
942
943 TreeTableEvent::FocusEnd => {
944 let next_focus = self.focused.with(|focused| match focused {
945 Some(TableFocus::Row(_)) => self
946 .visible_rows
947 .with(|rows| rows.last().map(|row| TableFocus::Row(row.id.clone()))),
948 Some(TableFocus::Cell(_, col)) => self.visible_rows.with(|rows| {
949 rows.last().map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
950 }),
951 None => None,
952 });
953
954 if next_focus.is_some() {
955 self.focused.set(next_focus);
956 }
957 }
958
959 TreeTableEvent::PageUp => {
960 let page_size = self.visible_rows.with(|rows| rows.len().saturating_div(2).max(1));
961 let next_focus = self.focused.with(|focused| {
962 self.visible_rows.with(|rows| {
963 let current_index = focused.as_ref().and_then(|focused| match focused {
964 TableFocus::Row(id) | TableFocus::Cell(id, _) => {
965 rows.iter().position(|row| row.id == id.clone())
966 }
967 });
968
969 current_index.and_then(|index| {
970 let next_index = index.saturating_sub(page_size);
971 rows.get(next_index).map(|row| match focused.as_ref() {
972 Some(TableFocus::Cell(_, col)) => {
973 TableFocus::Cell(row.id.clone(), col.clone())
974 }
975 _ => TableFocus::Row(row.id.clone()),
976 })
977 })
978 })
979 });
980
981 if next_focus.is_some() {
982 self.focused.set(next_focus);
983 }
984 }
985
986 TreeTableEvent::PageDown => {
987 let page_size = self.visible_rows.with(|rows| rows.len().saturating_div(2).max(1));
988 let next_focus = self.focused.with(|focused| {
989 self.visible_rows.with(|rows| {
990 let current_index = focused.as_ref().and_then(|focused| match focused {
991 TableFocus::Row(id) | TableFocus::Cell(id, _) => {
992 rows.iter().position(|row| row.id == id.clone())
993 }
994 });
995
996 current_index.and_then(|index| {
997 let next_index = (index + page_size).min(rows.len().saturating_sub(1));
998 rows.get(next_index).map(|row| match focused.as_ref() {
999 Some(TableFocus::Cell(_, col)) => {
1000 TableFocus::Cell(row.id.clone(), col.clone())
1001 }
1002 _ => TableFocus::Row(row.id.clone()),
1003 })
1004 })
1005 })
1006 });
1007
1008 if next_focus.is_some() {
1009 self.focused.set(next_focus);
1010 }
1011 }
1012
1013 TreeTableEvent::CtrlHome => {
1014 let next_focus = self.focused.with(|focused| {
1015 self.visible_rows.with(|rows| {
1016 rows.first().map(|row| match focused {
1017 Some(TableFocus::Cell(_, col)) => {
1018 TableFocus::Cell(row.id.clone(), col.clone())
1019 }
1020 _ => TableFocus::Row(row.id.clone()),
1021 })
1022 })
1023 });
1024
1025 if next_focus.is_some() {
1026 self.focused.set(next_focus);
1027 }
1028 }
1029
1030 TreeTableEvent::CtrlEnd => {
1031 let next_focus = self.focused.with(|focused| {
1032 self.visible_rows.with(|rows| {
1033 rows.last().map(|row| match focused {
1034 Some(TableFocus::Cell(_, col)) => {
1035 TableFocus::Cell(row.id.clone(), col.clone())
1036 }
1037 _ => TableFocus::Row(row.id.clone()),
1038 })
1039 })
1040 });
1041
1042 if next_focus.is_some() {
1043 self.focused.set(next_focus);
1044 }
1045 }
1046
1047 TreeTableEvent::FocusUp => {
1048 let next_focus = self.focused.with(|focused| match focused {
1049 Some(TableFocus::Row(id)) => self.visible_rows.with(|rows| {
1050 rows.iter()
1051 .take_while(|row| row.id != *id)
1052 .last()
1053 .map(|row| TableFocus::Row(row.id.clone()))
1054 }),
1055 Some(TableFocus::Cell(id, col)) => self.visible_rows.with(|rows| {
1056 rows.iter()
1057 .take_while(|row| row.id != *id)
1058 .last()
1059 .map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1060 }),
1061 None => None,
1062 });
1063
1064 if next_focus.is_some() {
1065 self.focused.set(next_focus);
1066 }
1067 }
1068
1069 TreeTableEvent::FocusDown => {
1070 let next_focus = self.focused.with(|focused| match focused {
1071 Some(TableFocus::Row(id)) => self.visible_rows.with(|rows| {
1072 rows.iter()
1073 .skip_while(|row| row.id != *id)
1074 .nth(1)
1075 .map(|row| TableFocus::Row(row.id.clone()))
1076 }),
1077 Some(TableFocus::Cell(id, col)) => self.visible_rows.with(|rows| {
1078 rows.iter()
1079 .skip_while(|row| row.id != *id)
1080 .nth(1)
1081 .map(|row| TableFocus::Cell(row.id.clone(), col.clone()))
1082 }),
1083 None => None,
1084 });
1085
1086 if next_focus.is_some() {
1087 self.focused.set(next_focus);
1088 }
1089 }
1090 });
1091
1092 event.map(|cell_event: &TreeTableFirstCellEvent<Id>, _| {
1093 let TreeTableFirstCellEvent::Toggle(id, next) = cell_event;
1094 self.emit_toggle(cx, id.clone(), *next);
1095 });
1096
1097 event.map(|table_event: &TableEvent<K>, _| match table_event {
1098 TableEvent::ToggleSort(col) => {
1099 let visible_rows = flatten_visible_rows(
1100 &self.rows.get(),
1101 &*self.row_id,
1102 &*self.parent_id,
1103 &self.expanded_row_ids.get(),
1104 );
1105
1106 let column_cells = HashSet::from_iter(
1107 visible_rows.iter().map(|row| Cell(row.id.clone(), col.clone())),
1108 );
1109
1110 let current_selection = self.selection.get();
1111 let next_selection =
1112 if current_selection == column_cells { HashSet::new() } else { column_cells };
1113
1114 self.selection.set(next_selection.clone());
1115
1116 if let Some(callback) = &self.on_select {
1117 (callback)(cx, next_selection);
1118 }
1119
1120 if let Some(callback) = &self.on_sort {
1121 let current_direction =
1122 sort_direction_for_column(self.sort_state.get().as_ref(), col);
1123 let next_direction =
1124 next_sort_direction(self.sort_cycle.get(), current_direction);
1125 (callback)(cx, col.clone(), next_direction);
1126 }
1127 }
1128
1129 _ => {}
1130 });
1131 }
1132}
1133
1134pub trait VirtualTreeTableModifiers<Id, K = String>: Sized
1135where
1136 Id: Eq + Hash + Clone + Send + Sync + 'static,
1137 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
1138{
1139 fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self;
1140
1141 fn resizable_columns<U: Into<bool> + Clone + 'static>(
1142 self,
1143 flag: impl Res<U> + 'static,
1144 ) -> Self;
1145
1146 fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
1147 self,
1148 cycle: impl Res<U> + 'static,
1149 ) -> Self;
1150
1151 fn selectable<U: Into<Selectable> + Clone + 'static>(
1152 self,
1153 selectable: impl Res<U> + 'static,
1154 ) -> Self;
1155
1156 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
1157 self,
1158 flag: impl Res<U> + 'static,
1159 ) -> Self;
1160
1161 fn selection_mode<U: Into<TableSelectionMode> + Clone + 'static>(
1162 self,
1163 mode: impl Res<U> + 'static,
1164 ) -> Self;
1165
1166 fn treegrid_label<U: Into<Option<String>> + Clone + 'static>(
1167 self,
1168 label: impl Res<U> + 'static,
1169 ) -> Self;
1170
1171 fn selection<R>(self, selection: impl Res<R> + 'static) -> Self
1172 where
1173 R: Deref<Target = [Cell<Id, K>]> + Clone + 'static;
1174
1175 fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
1176 where
1177 R: Deref<Target = [Id]> + Clone + 'static;
1178
1179 fn on_sort<F>(self, callback: F) -> Self
1180 where
1181 F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync;
1182
1183 fn on_select<F>(self, callback: F) -> Self
1184 where
1185 F: 'static + Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync;
1186
1187 fn on_row_toggle<F>(self, callback: F) -> Self
1188 where
1189 F: 'static + Fn(&mut EventContext, Id, bool);
1190}
1191
1192impl<T, V, Id, H, K> VirtualTreeTableModifiers<Id, K>
1193 for Handle<'_, VirtualTreeTable<T, V, Id, H, K>>
1194where
1195 V: Deref<Target = [T]> + Clone + 'static,
1196 T: PartialEq + Clone + 'static,
1197 Id: Eq + Hash + Clone + Send + Sync + 'static,
1198 H: Clone + View,
1199 K: Eq + Hash + Clone + PartialEq + Send + Sync + 'static,
1200{
1201 fn sort_state(self, sort_state: impl Res<Option<TableSortState<K>>> + 'static) -> Self {
1202 let sort_state = sort_state.to_signal(self.cx);
1203 self.bind(sort_state, move |handle| {
1204 let sort_state = sort_state.get();
1205 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1206 table.sort_state.set(sort_state)
1207 });
1208 })
1209 }
1210
1211 fn resizable_columns<U: Into<bool> + Clone + 'static>(
1212 self,
1213 flag: impl Res<U> + 'static,
1214 ) -> Self {
1215 let flag = flag.to_signal(self.cx);
1216 self.bind(flag, move |handle| {
1217 let flag = flag.get().into();
1218 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1219 table.resizable_columns.set(flag)
1220 });
1221 })
1222 }
1223
1224 fn sort_cycle<U: Into<TableSortCycle> + Clone + 'static>(
1225 self,
1226 cycle: impl Res<U> + 'static,
1227 ) -> Self {
1228 let cycle = cycle.to_signal(self.cx);
1229 self.bind(cycle, move |handle| {
1230 let cycle = cycle.get().into();
1231 handle
1232 .modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| table.sort_cycle.set(cycle));
1233 })
1234 }
1235
1236 fn selectable<U: Into<Selectable> + Clone + 'static>(
1237 self,
1238 selectable: impl Res<U> + 'static,
1239 ) -> Self {
1240 let selectable = selectable.to_signal(self.cx);
1241 self.bind(selectable, move |handle| {
1242 let selectable = selectable.get().into();
1243 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1244 table.selectable.set(selectable)
1245 });
1246 })
1247 }
1248
1249 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
1250 self,
1251 flag: impl Res<U> + 'static,
1252 ) -> Self {
1253 let flag = flag.to_signal(self.cx);
1254 self.bind(flag, move |handle| {
1255 let flag = flag.get().into();
1256 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1257 table.selection_follows_focus.set(flag)
1258 });
1259 })
1260 }
1261
1262 fn selection_mode<U: Into<TableSelectionMode> + Clone + 'static>(
1263 self,
1264 mode: impl Res<U> + 'static,
1265 ) -> Self {
1266 let mode = mode.to_signal(self.cx);
1267 self.bind(mode, move |handle| {
1268 let mode = mode.get().into();
1269 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1270 table.selection_mode.set(mode)
1271 });
1272 })
1273 }
1274
1275 fn treegrid_label<U: Into<Option<String>> + Clone + 'static>(
1276 self,
1277 label: impl Res<U> + 'static,
1278 ) -> Self {
1279 let label = label.to_signal(self.cx);
1280 self.bind(label, move |handle| {
1281 let label = label.get().into();
1282 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1283 table.treegrid_label.set(label)
1284 });
1285 })
1286 }
1287
1288 fn selection<R>(self, selection: impl Res<R> + 'static) -> Self
1289 where
1290 R: Deref<Target = [Cell<Id, K>]> + Clone + 'static,
1291 {
1292 let selection = selection.to_signal(self.cx);
1293 self.bind(selection, move |handle| {
1294 let ids = selection.with(|ids| ids.deref().to_vec());
1295 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1296 table.selection.set(HashSet::from_iter(ids))
1297 });
1298 })
1299 }
1300
1301 fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
1302 where
1303 R: Deref<Target = [Id]> + Clone + 'static,
1304 {
1305 let expanded_row_ids = expanded_row_ids.to_signal(self.cx);
1306 self.bind(expanded_row_ids, move |handle| {
1307 let ids = expanded_row_ids.with(|ids| ids.deref().to_vec());
1308 handle.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1309 table.expanded_row_ids.set(ids)
1310 });
1311 })
1312 }
1313
1314 fn on_sort<F>(self, callback: F) -> Self
1315 where
1316 F: 'static + Fn(&mut EventContext, K, TableSortDirection) + Send + Sync,
1317 {
1318 self.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1319 table.on_sort = Some(Arc::new(callback))
1320 })
1321 }
1322
1323 fn on_select<F>(self, callback: F) -> Self
1324 where
1325 F: 'static + Fn(&mut EventContext, HashSet<Cell<Id, K>>) + Send + Sync,
1326 {
1327 self.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1328 table.on_select = Some(Box::new(callback))
1329 })
1330 }
1331
1332 fn on_row_toggle<F>(self, callback: F) -> Self
1333 where
1334 F: 'static + Fn(&mut EventContext, Id, bool),
1335 {
1336 self.modify(|table: &mut VirtualTreeTable<T, V, Id, H, K>| {
1337 table.on_row_toggle = Some(Box::new(callback))
1338 })
1339 }
1340}