Skip to main content

vizia_core/views/
tree_view.rs

1use std::{
2    collections::{HashMap, HashSet},
3    hash::Hash,
4    ops::Deref,
5    rc::Rc,
6};
7
8use crate::{
9    icons::{ICON_CHEVRON_DOWN, ICON_CHEVRON_RIGHT},
10    prelude::*,
11};
12
13use super::tree_table::{TreeNodeRow, TreeTableRow};
14
15fn flatten_hierarchy_rows<U, Id>(
16    tree: &U,
17    root_ids: &dyn Fn(&U) -> Vec<Id>,
18    child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
19    is_visible: &dyn Fn(&U, &Id) -> bool,
20) -> Vec<TreeNodeRow<Id>>
21where
22    Id: Clone + Eq + Hash + 'static,
23{
24    fn visit<U, Id>(
25        tree: &U,
26        node_id: Id,
27        parent_id: Option<Id>,
28        child_ids: &dyn Fn(&U, &Id) -> Vec<Id>,
29        is_visible: &dyn Fn(&U, &Id) -> bool,
30        out: &mut Vec<TreeNodeRow<Id>>,
31    ) where
32        Id: Clone + Eq + Hash + 'static,
33    {
34        if !is_visible(tree, &node_id) {
35            return;
36        }
37
38        out.push(TreeNodeRow { id: node_id.clone(), parent_id });
39
40        for child_id in child_ids(tree, &node_id) {
41            visit(tree, child_id, Some(node_id.clone()), child_ids, is_visible, out);
42        }
43    }
44
45    let mut rows = Vec::new();
46    for root_id in root_ids(tree) {
47        visit(tree, root_id, None, child_ids, is_visible, &mut rows);
48    }
49
50    rows
51}
52
53fn flatten_visible_rows<T, V, Id>(
54    rows: &V,
55    row_id: &dyn Fn(&T) -> Id,
56    parent_id: &dyn Fn(&T) -> Option<Id>,
57    expanded_row_ids: &[Id],
58) -> Vec<TreeTableRow<T, Id>>
59where
60    V: Deref<Target = [T]>,
61    T: PartialEq + Clone + 'static,
62    Id: Clone + Eq + Hash + 'static,
63{
64    let mut rows_by_parent: HashMap<Option<Id>, Vec<T>> = HashMap::new();
65    for row in rows.deref().iter().cloned() {
66        rows_by_parent.entry(parent_id(&row)).or_default().push(row);
67    }
68
69    let expanded_set: HashSet<Id> = expanded_row_ids.iter().cloned().collect();
70    let mut visible_rows = Vec::new();
71
72    fn visit<T, Id>(
73        rows: &[T],
74        depth: usize,
75        rows_by_parent: &HashMap<Option<Id>, Vec<T>>,
76        expanded_set: &HashSet<Id>,
77        row_id: &dyn Fn(&T) -> Id,
78        out: &mut Vec<TreeTableRow<T, Id>>,
79    ) where
80        T: PartialEq + Clone + 'static,
81        Id: Clone + Eq + Hash + 'static,
82    {
83        for row in rows {
84            let id = row_id(row);
85            let child_rows =
86                rows_by_parent.get(&Some(id.clone())).map(Vec::as_slice).unwrap_or(&[]);
87            let has_children = !child_rows.is_empty();
88            let expanded = has_children && expanded_set.contains(&id);
89
90            out.push(TreeTableRow {
91                row: row.clone(),
92                id: id.clone(),
93                parent_id: None,
94                depth,
95                has_children,
96                expanded,
97            });
98
99            if expanded {
100                visit(child_rows, depth + 1, rows_by_parent, expanded_set, row_id, out);
101            }
102        }
103    }
104
105    let roots = rows_by_parent.get(&None).map(Vec::as_slice).unwrap_or(&[]);
106    visit(roots, 0, &rows_by_parent, &expanded_set, row_id, &mut visible_rows);
107
108    for visible_row in &mut visible_rows {
109        visible_row.parent_id = parent_id(&visible_row.row);
110    }
111
112    visible_rows
113}
114
115fn focused_visible_index<T, Id>(
116    visible_rows: &[TreeTableRow<T, Id>],
117    focused_row_id: Option<&Id>,
118    selected_row_ids: &[Id],
119) -> Option<usize>
120where
121    T: PartialEq + Clone + 'static,
122    Id: Clone + Eq + Hash + 'static,
123{
124    if let Some(focused_row_id) = focused_row_id {
125        if let Some(index) = visible_rows.iter().position(|row| &row.id == focused_row_id) {
126            return Some(index);
127        }
128    }
129
130    visible_rows
131        .iter()
132        .position(|row| selected_row_ids.iter().any(|selected_id| selected_id == &row.id))
133}
134
135enum TreeViewEvent<Id> {
136    SelectRow(usize),
137    FocusRow(Id),
138    SelectFocused,
139    ToggleCheckedFocused,
140    ExpandFocused,
141    CollapseFocused,
142    ToggleRow(Id, bool),
143}
144
145type TreeViewItemContent<T, Id> = dyn Fn(&mut Context, Memo<TreeTableRow<T, Id>>);
146type TreeViewTypeAheadText<T> = dyn Fn(&T) -> Option<String>;
147type TreeViewCheckedRow<T> = dyn Fn(&T) -> bool;
148
149pub struct TreeView<T, V, Id>
150where
151    V: Deref<Target = [T]> + Clone + 'static,
152    T: PartialEq + Clone + 'static,
153    Id: Eq + Hash + Clone + Send + Sync + 'static,
154{
155    rows: Signal<V>,
156    row_id: Rc<dyn Fn(&T) -> Id>,
157    parent_id: Rc<dyn Fn(&T) -> Option<Id>>,
158    selectable: Signal<Selectable>,
159    selection_follows_focus: Signal<bool>,
160    selected_row_ids: Signal<Vec<Id>>,
161    expanded_row_ids: Signal<Vec<Id>>,
162    focused_row_id: Signal<Option<Id>>,
163    list_entity: Signal<Entity>,
164    checked_row: Signal<Option<Rc<TreeViewCheckedRow<T>>>>,
165    type_ahead_text: Signal<Option<Rc<TreeViewTypeAheadText<T>>>>,
166    on_row_select: Option<Box<dyn Fn(&mut EventContext, Id)>>,
167    on_row_focus: Option<Box<dyn Fn(&mut EventContext, Id)>>,
168    on_row_check_toggle: Option<Box<dyn Fn(&mut EventContext, Id)>>,
169    on_row_toggle: Option<Box<dyn Fn(&mut EventContext, Id, bool)>>,
170}
171
172impl<T, V, Id> TreeView<T, V, Id>
173where
174    V: Deref<Target = [T]> + Clone + 'static,
175    T: PartialEq + Clone + 'static,
176    Id: Eq + Hash + Clone + Send + Sync + 'static,
177{
178    pub fn new<S, U, F>(
179        cx: &mut Context,
180        tree: S,
181        flatten_rows: F,
182        row_id: impl Fn(&T) -> Id + 'static,
183        parent_id: impl Fn(&T) -> Option<Id> + 'static,
184        item_content: impl Fn(&mut Context, Memo<TreeTableRow<T, Id>>) + 'static,
185    ) -> Handle<Self>
186    where
187        S: Res<U> + 'static,
188        U: Clone + 'static,
189        F: Fn(&U) -> V + 'static,
190    {
191        let tree_signal = tree.to_signal(cx);
192        let flatten_rows: Rc<dyn Fn(&U) -> V> = Rc::new(flatten_rows);
193        let row_signal = Signal::new(tree_signal.with(|tree| flatten_rows(tree)));
194        let row_id: Rc<dyn Fn(&T) -> Id> = Rc::new(row_id);
195        let parent_id: Rc<dyn Fn(&T) -> Option<Id>> = Rc::new(parent_id);
196        let item_content: Rc<TreeViewItemContent<T, Id>> = Rc::new(item_content);
197
198        let selectable = Signal::new(Selectable::None);
199        let selection_follows_focus = Signal::new(false);
200        let selected_row_ids = Signal::new(Vec::new());
201        let expanded_row_ids = Signal::new(Vec::new());
202        let focused_row_id = Signal::new(None);
203        let list_entity = Signal::new(Entity::null());
204        let checked_row = Signal::new(None);
205        let type_ahead_text = Signal::new(None);
206
207        let visible_rows = Memo::new({
208            let row_id = row_id.clone();
209            let parent_id = parent_id.clone();
210            move |_| {
211                row_signal.with(|rows| {
212                    expanded_row_ids.with(|expanded| {
213                        flatten_visible_rows(rows, &*row_id, &*parent_id, expanded)
214                    })
215                })
216            }
217        });
218
219        let selected_indices =
220            Memo::new(move |_| {
221                visible_rows.with(|rows| {
222                    selected_row_ids.with(|selected_ids| {
223                        rows.iter()
224                            .enumerate()
225                            .filter_map(|(index, row)| {
226                                if selected_ids.contains(&row.id) { Some(index) } else { None }
227                            })
228                            .collect::<Vec<usize>>()
229                    })
230                })
231            });
232
233        let focused_index = Memo::new(move |_| {
234            visible_rows.with(|rows| {
235                let focused_row_id = focused_row_id.get();
236                selected_row_ids.with(|selected_ids| {
237                    focused_visible_index(rows, focused_row_id.as_ref(), selected_ids)
238                })
239            })
240        });
241
242        let handle = Self {
243            rows: row_signal,
244            row_id,
245            parent_id,
246            selectable,
247            selection_follows_focus,
248            selected_row_ids,
249            expanded_row_ids,
250            focused_row_id,
251            list_entity,
252            checked_row,
253            type_ahead_text,
254            on_row_select: None,
255            on_row_focus: None,
256            on_row_check_toggle: None,
257            on_row_toggle: None,
258        }
259        .build(cx, move |cx| {
260            Keymap::from(vec![
261                (
262                    KeyChord::new(Modifiers::empty(), Code::Enter),
263                    KeymapEntry::new("Select Focused", |cx| {
264                        cx.emit(TreeViewEvent::<Id>::SelectFocused)
265                    }),
266                ),
267                (
268                    KeyChord::new(Modifiers::empty(), Code::Space),
269                    KeymapEntry::new("Toggle Checked Focused", |cx| {
270                        cx.emit(TreeViewEvent::<Id>::ToggleCheckedFocused)
271                    }),
272                ),
273                (
274                    KeyChord::new(Modifiers::empty(), Code::ArrowRight),
275                    KeymapEntry::new("Expand Selected", |cx| {
276                        cx.emit(TreeViewEvent::<Id>::ExpandFocused)
277                    }),
278                ),
279                (
280                    KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
281                    KeymapEntry::new("Collapse Focused", |cx| {
282                        cx.emit(TreeViewEvent::<Id>::CollapseFocused)
283                    }),
284                ),
285            ])
286            .build(cx);
287
288            let list = List::new_custom_items_with_selection(
289                cx,
290                visible_rows,
291                move |cx, row_index, row, is_selected| {
292                    let row: Memo<TreeTableRow<T, Id>> = Memo::new(move |_| row.get());
293                    let row_data = row.get();
294                    let has_children = row_data.has_children;
295                    let expanded = row_data.expanded;
296                    let row_id = row_data.id.clone();
297                    let checked_row_callback = checked_row.get();
298                    let indent = row_data.depth as f32 * 16.0;
299                    let item_content = item_content.clone();
300                    let row_for_size_of_set = row;
301                    let size_of_set = Memo::new(move |_| {
302                        let row_data = row_for_size_of_set.get();
303                        visible_rows.with(|rows| {
304                            rows.iter()
305                                .filter(|candidate| candidate.parent_id == row_data.parent_id)
306                                .count()
307                        })
308                    });
309                    let row_for_position = row;
310                    let position_in_set = Memo::new(move |_| {
311                        let row_data = row_for_position.get();
312                        visible_rows.with(|rows| {
313                            rows.iter()
314                                .filter(|candidate| candidate.parent_id == row_data.parent_id)
315                                .position(|candidate| candidate.id == row_data.id)
316                                .map(|position| position + 1)
317                                .unwrap_or(1)
318                        })
319                    });
320
321                    let is_checked = if let Some(checked_row_callback) = checked_row_callback {
322                        row.map(move |value| checked_row_callback(&value.row))
323                    } else {
324                        is_selected
325                    };
326
327                    let row_handle = HStack::new(cx, move |cx| {
328                        Element::new(cx)
329                            .class("tree-view-indent")
330                            .width(Pixels(indent))
331                            .height(Stretch(1.0))
332                            .pointer_events(PointerEvents::None);
333
334                        if has_children {
335                            let icon =
336                                if expanded { ICON_CHEVRON_DOWN } else { ICON_CHEVRON_RIGHT };
337                            Button::new(cx, move |cx| Svg::new(cx, icon).text_wrap(false))
338                                .variant(ButtonVariant::Text)
339                                .class("tree-view-disclosure")
340                                .navigable(false)
341                                .on_press({
342                                    let row_id = row_id.clone();
343                                    move |cx| {
344                                        cx.emit(TreeViewEvent::ToggleRow(
345                                            row_id.clone(),
346                                            !expanded,
347                                        ));
348                                    }
349                                });
350                        } else {
351                            Element::new(cx)
352                                .class("tree-view-disclosure-placeholder")
353                                .pointer_events(PointerEvents::None);
354                        }
355
356                        HStack::new(cx, move |cx| {
357                            item_content(cx, row);
358                        })
359                        .class("tree-view-row-content")
360                        .width(Stretch(1.0))
361                        .min_width(Auto)
362                        .height(Auto)
363                        .pointer_events(PointerEvents::None);
364                    })
365                    .class("tree-view-row")
366                    .toggle_class("odd", row_index % 2 == 1)
367                    .toggle_class("even", row_index % 2 == 0)
368                    .toggle_class("selected", is_selected)
369                    .toggle_class("expanded", row.map(|value| value.expanded))
370                    .toggle_class("collapsible", row.map(|value| value.has_children))
371                    .alignment(Alignment::Left)
372                    .height(Auto)
373                    .width(Stretch(1.0))
374                    .min_width(Auto)
375                    .role(Role::TreeItem)
376                    .accessibility_selected(is_selected)
377                    .checked(is_checked)
378                    .level(row.map(|value| value.depth + 1))
379                    .size_of_set(size_of_set)
380                    .position_in_set(position_in_set)
381                    .on_mouse_down(move |cx, button| {
382                        if button == MouseButton::Left && cx.hovered() == cx.current() {
383                            cx.emit(ListEvent::Select(row_index));
384                        }
385                    });
386
387                    if has_children {
388                        row_handle.expanded(row.map(|value| value.expanded))
389                    } else {
390                        row_handle
391                    }
392                },
393            )
394            .width(Stretch(1.0))
395            .height(Stretch(1.0))
396            .class("tree-view-body")
397            .selection(selected_indices)
398            .focused_index(focused_index)
399            .on_focus(move |cx, index| {
400                visible_rows.with(|rows| {
401                    if let Some(row) = rows.get(index) {
402                        focused_row_id.set(Some(row.id.clone()));
403                        cx.emit(TreeViewEvent::FocusRow(row.id.clone()));
404                    }
405                });
406            })
407            .selectable(selectable)
408            .selection_follows_focus(selection_follows_focus)
409            .space_selects_focused(false)
410            .type_ahead_text(move |_cx, index| {
411                let text_fn = type_ahead_text.get()?;
412                visible_rows.with(|rows| rows.get(index).and_then(|row| text_fn(&row.row)))
413            })
414            .on_select(move |cx, index| cx.emit(TreeViewEvent::<Id>::SelectRow(index)))
415            .role(Role::Tree);
416
417            list_entity.set(list.entity());
418        })
419        .navigable(false);
420
421        let flatten_rows_for_bind = flatten_rows.clone();
422        handle.bind(tree_signal, move |handle| {
423            let rows = tree_signal.with(|tree| flatten_rows_for_bind(tree));
424            handle.modify(|tree_view: &mut TreeView<T, V, Id>| tree_view.rows.set(rows));
425        })
426    }
427
428    pub fn from_rows<S>(
429        cx: &mut Context,
430        rows: S,
431        row_id: impl Fn(&T) -> Id + 'static,
432        parent_id: impl Fn(&T) -> Option<Id> + 'static,
433        item_content: impl Fn(&mut Context, Memo<TreeTableRow<T, Id>>) + 'static,
434    ) -> Handle<Self>
435    where
436        S: Res<V> + 'static,
437    {
438        Self::new(cx, rows, |rows: &V| rows.clone(), row_id, parent_id, item_content)
439    }
440
441    fn emit_toggle(&self, cx: &mut EventContext, row_id: Id, next_expanded: bool) {
442        if let Some(callback) = &self.on_row_toggle {
443            (callback)(cx, row_id, next_expanded);
444        }
445    }
446
447    fn emit_select(&self, cx: &mut EventContext, row_id: Id) {
448        if let Some(callback) = &self.on_row_select {
449            (callback)(cx, row_id);
450        }
451    }
452
453    fn emit_focus(&self, cx: &mut EventContext, row_id: Id) {
454        if let Some(callback) = &self.on_row_focus {
455            (callback)(cx, row_id);
456        }
457    }
458
459    fn emit_check_toggle(&self, cx: &mut EventContext, row_id: Id) {
460        if let Some(callback) = &self.on_row_check_toggle {
461            (callback)(cx, row_id);
462        }
463    }
464
465    fn visible_rows(&self) -> Vec<TreeTableRow<T, Id>> {
466        flatten_visible_rows(
467            &self.rows.get(),
468            &*self.row_id,
469            &*self.parent_id,
470            &self.expanded_row_ids.get(),
471        )
472    }
473
474    // Returns the currently focused row if it's visible, otherwise falls back to the first selected row that is visible.
475    fn focused_or_selected_visible_row(&self) -> Option<TreeTableRow<T, Id>> {
476        let visible_rows = self.visible_rows();
477
478        if let Some(focused_id) = self.focused_row_id.get() {
479            if let Some(row) = visible_rows.iter().find(|row| row.id == focused_id) {
480                return Some(row.clone());
481            }
482        }
483
484        let selected_id = self.selected_row_ids.get().first().cloned()?;
485        visible_rows.into_iter().find(|row| row.id == selected_id)
486    }
487
488    fn focus_row_id(&self, cx: &mut EventContext, row_id: Id) {
489        let visible_rows = self.visible_rows();
490        if let Some(index) = visible_rows.iter().position(|row| row.id == row_id) {
491            let list_entity = self.list_entity.get();
492            if list_entity != Entity::null() {
493                cx.emit_to(list_entity, ListEvent::Focus(index));
494                self.focused_row_id.set(Some(row_id));
495            }
496        }
497    }
498}
499
500impl<Id> TreeView<TreeNodeRow<Id>, Vec<TreeNodeRow<Id>>, Id>
501where
502    Id: Eq + Hash + Clone + Send + Sync + 'static,
503{
504    pub fn from_hierarchy<S, U>(
505        cx: &mut Context,
506        tree: S,
507        root_ids: impl Fn(&U) -> Vec<Id> + 'static,
508        child_ids: impl Fn(&U, &Id) -> Vec<Id> + 'static,
509        is_visible: impl Fn(&U, &Id) -> bool + 'static,
510        item_content: impl Fn(&mut Context, Memo<TreeTableRow<TreeNodeRow<Id>, Id>>) + 'static,
511    ) -> Handle<Self>
512    where
513        S: Res<U> + 'static,
514        U: Clone + 'static,
515    {
516        let root_ids: Rc<dyn Fn(&U) -> Vec<Id>> = Rc::new(root_ids);
517        let child_ids: Rc<dyn Fn(&U, &Id) -> Vec<Id>> = Rc::new(child_ids);
518        let is_visible: Rc<dyn Fn(&U, &Id) -> bool> = Rc::new(is_visible);
519
520        Self::new(
521            cx,
522            tree,
523            move |tree: &U| flatten_hierarchy_rows(tree, &*root_ids, &*child_ids, &*is_visible),
524            |row: &TreeNodeRow<Id>| row.id.clone(),
525            |row: &TreeNodeRow<Id>| row.parent_id.clone(),
526            item_content,
527        )
528    }
529}
530
531impl<T, V, Id> View for TreeView<T, V, Id>
532where
533    V: Deref<Target = [T]> + Clone + 'static,
534    T: PartialEq + Clone + 'static,
535    Id: Eq + Hash + Clone + Send + Sync + 'static,
536{
537    fn element(&self) -> Option<&'static str> {
538        Some("tree-view")
539    }
540
541    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
542        event.map(|tree_event: &TreeViewEvent<Id>, _| match tree_event {
543            TreeViewEvent::SelectRow(index) => {
544                let visible_rows = self.visible_rows();
545
546                if let Some(row) = visible_rows.get(*index) {
547                    self.emit_select(cx, row.id.clone());
548                }
549            }
550
551            TreeViewEvent::SelectFocused => {
552                if let Some(row) = self.focused_or_selected_visible_row() {
553                    self.emit_select(cx, row.id);
554                }
555            }
556
557            TreeViewEvent::FocusRow(row_id) => {
558                self.emit_focus(cx, row_id.clone());
559            }
560
561            TreeViewEvent::ToggleCheckedFocused => {
562                if let Some(row) = self.focused_or_selected_visible_row() {
563                    self.emit_check_toggle(cx, row.id);
564                }
565            }
566
567            TreeViewEvent::ExpandFocused => {
568                if let Some(row) = self.focused_or_selected_visible_row() {
569                    if row.has_children && !row.expanded {
570                        self.emit_toggle(cx, row.id, true);
571                    } else if row.has_children {
572                        let child_id = self
573                            .visible_rows()
574                            .into_iter()
575                            .find(|candidate| candidate.parent_id.as_ref() == Some(&row.id))
576                            .map(|candidate| candidate.id);
577
578                        if let Some(child_id) = child_id {
579                            self.focus_row_id(cx, child_id);
580                        }
581                    }
582                }
583            }
584
585            TreeViewEvent::CollapseFocused => {
586                if let Some(row) = self.focused_or_selected_visible_row() {
587                    if row.has_children && row.expanded {
588                        self.emit_toggle(cx, row.id, false);
589                    } else if let Some(parent_id) = row.parent_id {
590                        self.focus_row_id(cx, parent_id);
591                    }
592                }
593            }
594
595            TreeViewEvent::ToggleRow(row_id, next) => {
596                self.emit_toggle(cx, row_id.clone(), *next);
597            }
598        });
599    }
600}
601
602pub trait TreeViewModifiers<Id>: Sized
603where
604    Id: Eq + Hash + Clone + Send + Sync + 'static,
605{
606    fn selectable<U: Into<Selectable> + Clone + 'static>(
607        self,
608        selectable: impl Res<U> + 'static,
609    ) -> Self;
610
611    fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
612        self,
613        flag: impl Res<U> + 'static,
614    ) -> Self;
615
616    fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
617    where
618        R: Deref<Target = [Id]> + Clone + 'static;
619
620    fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
621    where
622        R: Deref<Target = [Id]> + Clone + 'static;
623
624    fn on_row_select<F>(self, callback: F) -> Self
625    where
626        F: 'static + Fn(&mut EventContext, Id);
627
628    fn on_row_focus<F>(self, callback: F) -> Self
629    where
630        F: 'static + Fn(&mut EventContext, Id);
631
632    fn on_row_check_toggle<F>(self, callback: F) -> Self
633    where
634        F: 'static + Fn(&mut EventContext, Id);
635
636    fn on_row_toggle<F>(self, callback: F) -> Self
637    where
638        F: 'static + Fn(&mut EventContext, Id, bool);
639}
640
641impl<T, V, Id> TreeViewModifiers<Id> for Handle<'_, TreeView<T, V, Id>>
642where
643    V: Deref<Target = [T]> + Clone + 'static,
644    T: PartialEq + Clone + 'static,
645    Id: Eq + Hash + Clone + Send + Sync + 'static,
646{
647    fn selectable<U: Into<Selectable> + Clone + 'static>(
648        self,
649        selectable: impl Res<U> + 'static,
650    ) -> Self {
651        let selectable = selectable.to_signal(self.cx);
652        self.bind(selectable, move |handle| {
653            let selectable = selectable.get().into();
654            handle
655                .modify(|tree_view: &mut TreeView<T, V, Id>| tree_view.selectable.set(selectable));
656        })
657    }
658
659    fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
660        self,
661        flag: impl Res<U> + 'static,
662    ) -> Self {
663        let flag = flag.to_signal(self.cx);
664        self.bind(flag, move |handle| {
665            let flag = flag.get().into();
666            handle.modify(|tree_view: &mut TreeView<T, V, Id>| {
667                tree_view.selection_follows_focus.set(flag)
668            });
669        })
670    }
671
672    fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
673    where
674        R: Deref<Target = [Id]> + Clone + 'static,
675    {
676        let selected_row_ids = selected_row_ids.to_signal(self.cx);
677        self.bind(selected_row_ids, move |handle| {
678            let ids = selected_row_ids.with(|ids| ids.deref().to_vec());
679            handle.modify(|tree_view: &mut TreeView<T, V, Id>| tree_view.selected_row_ids.set(ids));
680        })
681    }
682
683    fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
684    where
685        R: Deref<Target = [Id]> + Clone + 'static,
686    {
687        let expanded_row_ids = expanded_row_ids.to_signal(self.cx);
688        self.bind(expanded_row_ids, move |handle| {
689            let ids = expanded_row_ids.with(|ids| ids.deref().to_vec());
690            handle.modify(|tree_view: &mut TreeView<T, V, Id>| tree_view.expanded_row_ids.set(ids));
691        })
692    }
693
694    fn on_row_select<F>(self, callback: F) -> Self
695    where
696        F: 'static + Fn(&mut EventContext, Id),
697    {
698        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
699            tree_view.on_row_select = Some(Box::new(callback))
700        })
701    }
702
703    fn on_row_focus<F>(self, callback: F) -> Self
704    where
705        F: 'static + Fn(&mut EventContext, Id),
706    {
707        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
708            tree_view.on_row_focus = Some(Box::new(callback))
709        })
710    }
711
712    fn on_row_check_toggle<F>(self, callback: F) -> Self
713    where
714        F: 'static + Fn(&mut EventContext, Id),
715    {
716        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
717            tree_view.on_row_check_toggle = Some(Box::new(callback))
718        })
719    }
720
721    fn on_row_toggle<F>(self, callback: F) -> Self
722    where
723        F: 'static + Fn(&mut EventContext, Id, bool),
724    {
725        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
726            tree_view.on_row_toggle = Some(Box::new(callback))
727        })
728    }
729}
730
731impl<T, V, Id> Handle<'_, TreeView<T, V, Id>>
732where
733    V: Deref<Target = [T]> + Clone + 'static,
734    T: PartialEq + Clone + 'static,
735    Id: Eq + Hash + Clone + Send + Sync + 'static,
736{
737    pub fn type_ahead_text<F>(self, callback: F) -> Self
738    where
739        F: 'static + Fn(&T) -> Option<String>,
740    {
741        let callback: Rc<TreeViewTypeAheadText<T>> = Rc::new(callback);
742        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
743            tree_view.type_ahead_text.set(Some(callback.clone()))
744        })
745    }
746
747    pub fn checked_row<F>(self, callback: F) -> Self
748    where
749        F: 'static + Fn(&T) -> bool,
750    {
751        let callback: Rc<TreeViewCheckedRow<T>> = Rc::new(callback);
752        self.modify(|tree_view: &mut TreeView<T, V, Id>| {
753            tree_view.checked_row.set(Some(callback.clone()))
754        })
755    }
756}