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 VirtualTreeViewEvent<Id> {
136 SelectRow(usize),
137 FocusRow(Id),
138 SelectFocused,
139 ToggleCheckedFocused,
140 ExpandFocused,
141 CollapseFocused,
142 ToggleRow(Id, bool),
143}
144
145type VirtualTreeViewItemContent<T, Id> = dyn Fn(&mut Context, Memo<TreeTableRow<T, Id>>);
146type VirtualTreeViewTypeAheadText<T> = dyn Fn(&T) -> Option<String>;
147type VirtualTreeViewCheckedRow<T> = dyn Fn(&T) -> bool;
148
149pub struct VirtualTreeView<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<VirtualTreeViewCheckedRow<T>>>>,
165 type_ahead_text: Signal<Option<Rc<VirtualTreeViewTypeAheadText<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> VirtualTreeView<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 item_height: f32,
182 flatten_rows: F,
183 row_id: impl Fn(&T) -> Id + 'static,
184 parent_id: impl Fn(&T) -> Option<Id> + 'static,
185 item_content: impl Fn(&mut Context, Memo<TreeTableRow<T, Id>>) + 'static,
186 ) -> Handle<Self>
187 where
188 S: Res<U> + 'static,
189 U: Clone + 'static,
190 F: Fn(&U) -> V + 'static,
191 {
192 let tree_signal = tree.to_signal(cx);
193 let flatten_rows: Rc<dyn Fn(&U) -> V> = Rc::new(flatten_rows);
194 let row_signal = Signal::new(tree_signal.with(|tree| flatten_rows(tree)));
195 let row_id: Rc<dyn Fn(&T) -> Id> = Rc::new(row_id);
196 let parent_id: Rc<dyn Fn(&T) -> Option<Id>> = Rc::new(parent_id);
197 let item_content: Rc<VirtualTreeViewItemContent<T, Id>> = Rc::new(item_content);
198 let item_content_signal = Signal::new(item_content.clone());
199
200 let selectable = Signal::new(Selectable::None);
201 let selection_follows_focus = Signal::new(false);
202 let selected_row_ids = Signal::new(Vec::new());
203 let expanded_row_ids = Signal::new(Vec::new());
204 let focused_row_id = Signal::new(None);
205 let list_entity = Signal::new(Entity::null());
206 let checked_row = Signal::new(None);
207 let type_ahead_text = Signal::new(None);
208
209 let visible_rows = Memo::new({
210 let row_id = row_id.clone();
211 let parent_id = parent_id.clone();
212 move |_| {
213 row_signal.with(|rows| {
214 expanded_row_ids.with(|expanded| {
215 flatten_visible_rows(rows, &*row_id, &*parent_id, expanded)
216 })
217 })
218 }
219 });
220
221 let selected_indices =
222 Memo::new(move |_| {
223 visible_rows.with(|rows| {
224 selected_row_ids.with(|selected_ids| {
225 rows.iter()
226 .enumerate()
227 .filter_map(|(index, row)| {
228 if selected_ids.contains(&row.id) { Some(index) } else { None }
229 })
230 .collect::<Vec<usize>>()
231 })
232 })
233 });
234
235 let focused_index = Memo::new(move |_| {
236 visible_rows.with(|rows| {
237 let focused_row_id = focused_row_id.get();
238 selected_row_ids.with(|selected_ids| {
239 focused_visible_index(rows, focused_row_id.as_ref(), selected_ids)
240 })
241 })
242 });
243
244 let handle = Self {
245 rows: row_signal,
246 row_id,
247 parent_id,
248 selectable,
249 selection_follows_focus,
250 selected_row_ids,
251 expanded_row_ids,
252 focused_row_id,
253 list_entity,
254 checked_row,
255 type_ahead_text,
256 on_row_select: None,
257 on_row_focus: None,
258 on_row_check_toggle: None,
259 on_row_toggle: None,
260 }
261 .build(cx, move |cx| {
262 Keymap::from(vec![
263 (
264 KeyChord::new(Modifiers::empty(), Code::Enter),
265 KeymapEntry::new("Select Focused", |cx| {
266 cx.emit(VirtualTreeViewEvent::<Id>::SelectFocused)
267 }),
268 ),
269 (
270 KeyChord::new(Modifiers::empty(), Code::Space),
271 KeymapEntry::new("Toggle Checked Focused", |cx| {
272 cx.emit(VirtualTreeViewEvent::<Id>::ToggleCheckedFocused)
273 }),
274 ),
275 (
276 KeyChord::new(Modifiers::empty(), Code::ArrowRight),
277 KeymapEntry::new("Expand Focused", |cx| {
278 cx.emit(VirtualTreeViewEvent::<Id>::ExpandFocused)
279 }),
280 ),
281 (
282 KeyChord::new(Modifiers::empty(), Code::ArrowLeft),
283 KeymapEntry::new("Collapse Focused", |cx| {
284 cx.emit(VirtualTreeViewEvent::<Id>::CollapseFocused)
285 }),
286 ),
287 ])
288 .build(cx);
289
290 let list = VirtualList::new_custom_items_with_selection(
291 cx,
292 visible_rows,
293 item_height,
294 move |cx, row_index, row, is_selected| {
295 let row: Memo<TreeTableRow<T, Id>> = Memo::new(move |_| row.get());
296 let row_data = row.get();
297 let has_children = row_data.has_children;
298 let expanded = row_data.expanded;
299 let row_id = row_data.id.clone();
300 let checked_row_callback = checked_row.get();
301 let indent = row_data.depth as f32 * 16.0;
302 let item_content = item_content_signal.get();
303 let row_for_size_of_set = row;
304 let size_of_set = Memo::new(move |_| {
305 let row_data = row_for_size_of_set.get();
306 visible_rows.with(|rows| {
307 rows.iter()
308 .filter(|candidate| candidate.parent_id == row_data.parent_id)
309 .count()
310 })
311 });
312 let row_for_position = row;
313 let position_in_set = Memo::new(move |_| {
314 let row_data = row_for_position.get();
315 visible_rows.with(|rows| {
316 rows.iter()
317 .filter(|candidate| candidate.parent_id == row_data.parent_id)
318 .position(|candidate| candidate.id == row_data.id)
319 .map(|position| position + 1)
320 .unwrap_or(1)
321 })
322 });
323
324 let is_checked = if let Some(checked_row_callback) = checked_row_callback {
325 row.map(move |value| checked_row_callback(&value.row))
326 } else {
327 is_selected
328 };
329
330 let row_handle = HStack::new(cx, move |cx| {
331 Element::new(cx)
332 .class("tree-view-indent")
333 .width(Pixels(indent))
334 .height(Stretch(1.0))
335 .pointer_events(PointerEvents::None);
336
337 if has_children {
338 let icon =
339 if expanded { ICON_CHEVRON_DOWN } else { ICON_CHEVRON_RIGHT };
340 Button::new(cx, move |cx| Svg::new(cx, icon).text_wrap(false))
341 .variant(ButtonVariant::Text)
342 .class("tree-view-disclosure")
343 .navigable(false)
344 .on_press({
345 let row_id = row_id.clone();
346 move |cx| {
347 cx.emit(VirtualTreeViewEvent::ToggleRow(
348 row_id.clone(),
349 !expanded,
350 ));
351 }
352 });
353 } else {
354 Element::new(cx)
355 .class("tree-view-disclosure-placeholder")
356 .pointer_events(PointerEvents::None);
357 }
358
359 HStack::new(cx, move |cx| {
360 item_content(cx, row);
361 })
362 .class("tree-view-row-content")
363 .width(Stretch(1.0))
364 .min_width(Auto)
365 .height(Auto)
366 .pointer_events(PointerEvents::None);
367 })
368 .class("tree-view-row")
369 .toggle_class("odd", row_index % 2 == 1)
370 .toggle_class("even", row_index % 2 == 0)
371 .toggle_class("selected", is_selected)
372 .toggle_class("expanded", row.map(|value| value.expanded))
373 .toggle_class("collapsible", row.map(|value| value.has_children))
374 .alignment(Alignment::Left)
375 .height(Auto)
376 .width(Stretch(1.0))
377 .min_width(Auto)
378 .role(Role::TreeItem)
379 .accessibility_selected(is_selected)
380 .checked(is_checked)
381 .level(row.map(|value| value.depth + 1))
382 .size_of_set(size_of_set)
383 .position_in_set(position_in_set)
384 .on_mouse_down(move |cx, button| {
385 if button == MouseButton::Left && cx.hovered() == cx.current() {
386 cx.emit(ListEvent::Select(row_index));
387 }
388 });
389
390 if has_children {
391 row_handle.expanded(row.map(|value| value.expanded))
392 } else {
393 row_handle
394 }
395 },
396 )
397 .width(Stretch(1.0))
398 .height(Stretch(1.0))
399 .class("tree-view-body")
400 .selection(selected_indices)
401 .focused_index(focused_index)
402 .on_focus(move |cx, index| {
403 visible_rows.with(|rows| {
404 if let Some(row) = rows.get(index) {
405 focused_row_id.set(Some(row.id.clone()));
406 cx.emit(VirtualTreeViewEvent::FocusRow(row.id.clone()));
407 }
408 });
409 })
410 .selectable(selectable)
411 .selection_follows_focus(selection_follows_focus)
412 .space_selects_focused(false)
413 .type_ahead_text(move |_cx, index| {
414 let text_fn = type_ahead_text.get()?;
415 visible_rows.with(|rows| rows.get(index).and_then(|row| text_fn(&row.row)))
416 })
417 .on_select(move |cx, index| cx.emit(VirtualTreeViewEvent::<Id>::SelectRow(index)))
418 .role(Role::Tree);
419
420 list_entity.set(list.entity());
421 })
422 .navigable(false);
423
424 let flatten_rows_for_bind = flatten_rows.clone();
425 handle.bind(tree_signal, move |handle| {
426 let rows = tree_signal.with(|tree| flatten_rows_for_bind(tree));
427 handle.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| tree_view.rows.set(rows));
428 })
429 }
430
431 pub fn from_rows<S>(
432 cx: &mut Context,
433 rows: S,
434 item_height: f32,
435 row_id: impl Fn(&T) -> Id + 'static,
436 parent_id: impl Fn(&T) -> Option<Id> + 'static,
437 item_content: impl Fn(&mut Context, Memo<TreeTableRow<T, Id>>) + 'static,
438 ) -> Handle<Self>
439 where
440 S: Res<V> + 'static,
441 {
442 Self::new(cx, rows, item_height, |rows: &V| rows.clone(), row_id, parent_id, item_content)
443 }
444
445 fn emit_toggle(&self, cx: &mut EventContext, row_id: Id, next_expanded: bool) {
446 if let Some(callback) = &self.on_row_toggle {
447 (callback)(cx, row_id, next_expanded);
448 }
449 }
450
451 fn emit_select(&self, cx: &mut EventContext, row_id: Id) {
452 if let Some(callback) = &self.on_row_select {
453 (callback)(cx, row_id);
454 }
455 }
456
457 fn emit_focus(&self, cx: &mut EventContext, row_id: Id) {
458 if let Some(callback) = &self.on_row_focus {
459 (callback)(cx, row_id);
460 }
461 }
462
463 fn emit_check_toggle(&self, cx: &mut EventContext, row_id: Id) {
464 if let Some(callback) = &self.on_row_check_toggle {
465 (callback)(cx, row_id);
466 }
467 }
468
469 fn visible_rows(&self) -> Vec<TreeTableRow<T, Id>> {
470 flatten_visible_rows(
471 &self.rows.get(),
472 &*self.row_id,
473 &*self.parent_id,
474 &self.expanded_row_ids.get(),
475 )
476 }
477
478 fn focused_or_selected_visible_row(&self) -> Option<TreeTableRow<T, Id>> {
479 let visible_rows = self.visible_rows();
480
481 if let Some(focused_id) = self.focused_row_id.get() {
482 if let Some(row) = visible_rows.iter().find(|row| row.id == focused_id) {
483 return Some(row.clone());
484 }
485 }
486
487 let selected_id = self.selected_row_ids.get().first().cloned()?;
488 visible_rows.into_iter().find(|row| row.id == selected_id)
489 }
490
491 fn focus_row_id(&self, cx: &mut EventContext, row_id: Id) {
492 let visible_rows = self.visible_rows();
493 if let Some(index) = visible_rows.iter().position(|row| row.id == row_id) {
494 let list_entity = self.list_entity.get();
495 if list_entity != Entity::null() {
496 cx.emit_to(list_entity, ListEvent::Focus(index));
497 self.focused_row_id.set(Some(row_id));
498 }
499 }
500 }
501}
502
503impl<Id> VirtualTreeView<TreeNodeRow<Id>, Vec<TreeNodeRow<Id>>, Id>
504where
505 Id: Eq + Hash + Clone + Send + Sync + 'static,
506{
507 pub fn from_hierarchy<S, U>(
508 cx: &mut Context,
509 tree: S,
510 item_height: f32,
511 root_ids: impl Fn(&U) -> Vec<Id> + 'static,
512 child_ids: impl Fn(&U, &Id) -> Vec<Id> + 'static,
513 is_visible: impl Fn(&U, &Id) -> bool + 'static,
514 item_content: impl Fn(&mut Context, Memo<TreeTableRow<TreeNodeRow<Id>, Id>>) + 'static,
515 ) -> Handle<Self>
516 where
517 S: Res<U> + 'static,
518 U: Clone + 'static,
519 {
520 let root_ids: Rc<dyn Fn(&U) -> Vec<Id>> = Rc::new(root_ids);
521 let child_ids: Rc<dyn Fn(&U, &Id) -> Vec<Id>> = Rc::new(child_ids);
522 let is_visible: Rc<dyn Fn(&U, &Id) -> bool> = Rc::new(is_visible);
523
524 Self::new(
525 cx,
526 tree,
527 item_height,
528 move |tree: &U| flatten_hierarchy_rows(tree, &*root_ids, &*child_ids, &*is_visible),
529 |row: &TreeNodeRow<Id>| row.id.clone(),
530 |row: &TreeNodeRow<Id>| row.parent_id.clone(),
531 item_content,
532 )
533 }
534}
535
536impl<T, V, Id> View for VirtualTreeView<T, V, Id>
537where
538 V: Deref<Target = [T]> + Clone + 'static,
539 T: PartialEq + Clone + 'static,
540 Id: Eq + Hash + Clone + Send + Sync + 'static,
541{
542 fn element(&self) -> Option<&'static str> {
543 Some("virtual-tree-view")
544 }
545
546 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
547 event.map(|tree_event: &VirtualTreeViewEvent<Id>, _| match tree_event {
548 VirtualTreeViewEvent::SelectRow(index) => {
549 let visible_rows = self.visible_rows();
550
551 if let Some(row) = visible_rows.get(*index) {
552 self.emit_select(cx, row.id.clone());
553 }
554 }
555
556 VirtualTreeViewEvent::SelectFocused => {
557 if let Some(row) = self.focused_or_selected_visible_row() {
558 self.emit_select(cx, row.id);
559 }
560 }
561
562 VirtualTreeViewEvent::FocusRow(row_id) => {
563 self.emit_focus(cx, row_id.clone());
564 }
565
566 VirtualTreeViewEvent::ToggleCheckedFocused => {
567 if let Some(row) = self.focused_or_selected_visible_row() {
568 self.emit_check_toggle(cx, row.id);
569 }
570 }
571
572 VirtualTreeViewEvent::ExpandFocused => {
573 if let Some(row) = self.focused_or_selected_visible_row() {
574 if row.has_children && !row.expanded {
575 self.emit_toggle(cx, row.id, true);
576 } else if row.has_children {
577 let child_id = self
578 .visible_rows()
579 .into_iter()
580 .find(|candidate| candidate.parent_id.as_ref() == Some(&row.id))
581 .map(|candidate| candidate.id);
582
583 if let Some(child_id) = child_id {
584 self.focus_row_id(cx, child_id);
585 }
586 }
587 }
588 }
589
590 VirtualTreeViewEvent::CollapseFocused => {
591 if let Some(row) = self.focused_or_selected_visible_row() {
592 if row.has_children && row.expanded {
593 self.emit_toggle(cx, row.id, false);
594 } else if let Some(parent_id) = row.parent_id {
595 self.focus_row_id(cx, parent_id);
596 }
597 }
598 }
599
600 VirtualTreeViewEvent::ToggleRow(row_id, next) => {
601 self.emit_toggle(cx, row_id.clone(), *next);
602 }
603 });
604 }
605}
606
607pub trait VirtualTreeViewModifiers<Id>: Sized
608where
609 Id: Eq + Hash + Clone + Send + Sync + 'static,
610{
611 fn selectable<U: Into<Selectable> + Clone + 'static>(
612 self,
613 selectable: impl Res<U> + 'static,
614 ) -> Self;
615
616 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
617 self,
618 flag: impl Res<U> + 'static,
619 ) -> Self;
620
621 fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
622 where
623 R: Deref<Target = [Id]> + Clone + 'static;
624
625 fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
626 where
627 R: Deref<Target = [Id]> + Clone + 'static;
628
629 fn on_row_select<F>(self, callback: F) -> Self
630 where
631 F: 'static + Fn(&mut EventContext, Id);
632
633 fn on_row_focus<F>(self, callback: F) -> Self
634 where
635 F: 'static + Fn(&mut EventContext, Id);
636
637 fn on_row_check_toggle<F>(self, callback: F) -> Self
638 where
639 F: 'static + Fn(&mut EventContext, Id);
640
641 fn on_row_toggle<F>(self, callback: F) -> Self
642 where
643 F: 'static + Fn(&mut EventContext, Id, bool);
644}
645
646impl<T, V, Id> VirtualTreeViewModifiers<Id> for Handle<'_, VirtualTreeView<T, V, Id>>
647where
648 V: Deref<Target = [T]> + Clone + 'static,
649 T: PartialEq + Clone + 'static,
650 Id: Eq + Hash + Clone + Send + Sync + 'static,
651{
652 fn selectable<U: Into<Selectable> + Clone + 'static>(
653 self,
654 selectable: impl Res<U> + 'static,
655 ) -> Self {
656 let selectable = selectable.to_signal(self.cx);
657 self.bind(selectable, move |handle| {
658 let selectable = selectable.get().into();
659 handle.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
660 tree_view.selectable.set(selectable)
661 });
662 })
663 }
664
665 fn selection_follows_focus<U: Into<bool> + Clone + 'static>(
666 self,
667 flag: impl Res<U> + 'static,
668 ) -> Self {
669 let flag = flag.to_signal(self.cx);
670 self.bind(flag, move |handle| {
671 let flag = flag.get().into();
672 handle.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
673 tree_view.selection_follows_focus.set(flag)
674 });
675 })
676 }
677
678 fn selected_row_ids<R>(self, selected_row_ids: impl Res<R> + 'static) -> Self
679 where
680 R: Deref<Target = [Id]> + Clone + 'static,
681 {
682 let selected_row_ids = selected_row_ids.to_signal(self.cx);
683 self.bind(selected_row_ids, move |handle| {
684 let ids = selected_row_ids.with(|ids| ids.deref().to_vec());
685 handle.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
686 tree_view.selected_row_ids.set(ids)
687 });
688 })
689 }
690
691 fn expanded_row_ids<R>(self, expanded_row_ids: impl Res<R> + 'static) -> Self
692 where
693 R: Deref<Target = [Id]> + Clone + 'static,
694 {
695 let expanded_row_ids = expanded_row_ids.to_signal(self.cx);
696 self.bind(expanded_row_ids, move |handle| {
697 let ids = expanded_row_ids.with(|ids| ids.deref().to_vec());
698 handle.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
699 tree_view.expanded_row_ids.set(ids)
700 });
701 })
702 }
703
704 fn on_row_select<F>(self, callback: F) -> Self
705 where
706 F: 'static + Fn(&mut EventContext, Id),
707 {
708 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
709 tree_view.on_row_select = Some(Box::new(callback))
710 })
711 }
712
713 fn on_row_focus<F>(self, callback: F) -> Self
714 where
715 F: 'static + Fn(&mut EventContext, Id),
716 {
717 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
718 tree_view.on_row_focus = Some(Box::new(callback))
719 })
720 }
721
722 fn on_row_check_toggle<F>(self, callback: F) -> Self
723 where
724 F: 'static + Fn(&mut EventContext, Id),
725 {
726 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
727 tree_view.on_row_check_toggle = Some(Box::new(callback))
728 })
729 }
730
731 fn on_row_toggle<F>(self, callback: F) -> Self
732 where
733 F: 'static + Fn(&mut EventContext, Id, bool),
734 {
735 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
736 tree_view.on_row_toggle = Some(Box::new(callback))
737 })
738 }
739}
740
741impl<T, V, Id> Handle<'_, VirtualTreeView<T, V, Id>>
742where
743 V: Deref<Target = [T]> + Clone + 'static,
744 T: PartialEq + Clone + 'static,
745 Id: Eq + Hash + Clone + Send + Sync + 'static,
746{
747 pub fn type_ahead_text<F>(self, callback: F) -> Self
748 where
749 F: 'static + Fn(&T) -> Option<String>,
750 {
751 let callback: Rc<VirtualTreeViewTypeAheadText<T>> = Rc::new(callback);
752 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
753 tree_view.type_ahead_text.set(Some(callback.clone()))
754 })
755 }
756
757 pub fn checked_row<F>(self, callback: F) -> Self
758 where
759 F: 'static + Fn(&T) -> bool,
760 {
761 let callback: Rc<VirtualTreeViewCheckedRow<T>> = Rc::new(callback);
762 self.modify(|tree_view: &mut VirtualTreeView<T, V, Id>| {
763 tree_view.checked_row.set(Some(callback.clone()))
764 })
765 }
766}
767
768#[cfg(test)]
769mod tests {
770 use super::*;
771
772 fn make_row(id: i32) -> TreeTableRow<i32, i32> {
773 TreeTableRow {
774 row: id,
775 id,
776 parent_id: None,
777 depth: 0,
778 has_children: false,
779 expanded: false,
780 }
781 }
782
783 #[test]
784 fn focused_visible_index_prefers_focused_row() {
785 let visible_rows = vec![make_row(1), make_row(2), make_row(3)];
786
787 assert_eq!(focused_visible_index(&visible_rows, Some(&2), &[1, 3]), Some(1));
788 }
789
790 #[test]
791 fn focused_visible_index_falls_back_to_first_visible_selected_row() {
792 let visible_rows = vec![make_row(4), make_row(5), make_row(6)];
793
794 assert_eq!(focused_visible_index(&visible_rows, Some(&9), &[6, 4]), Some(0));
795 }
796
797 #[test]
798 fn focused_visible_index_returns_none_when_nothing_is_visible() {
799 let visible_rows = vec![make_row(7), make_row(8)];
800
801 assert_eq!(focused_visible_index(&visible_rows, None, &[9]), None);
802 }
803}