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