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 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}