1use std::collections::{HashMap, HashSet};
20use std::hash::Hash;
21use std::ops::Range;
22use std::rc::Rc;
23
24use iced::futures::channel::mpsc;
25use iced::futures::{Stream, StreamExt, stream};
26use iced::keyboard::key::Named;
27use iced::keyboard::{Key, Modifiers};
28use iced::widget::{Space, column, container, mouse_area, row, text};
29use iced::{Alignment, Background, Border, Color, Element, Length, Subscription, Theme, mouse};
30
31use crate::data::ellipsis::ellipsis;
32use crate::data::interaction::{Look, RowMenu, pressable, row_menu, scope};
33use crate::data::rows::rows;
34use crate::feedback::badge::{self, badge};
35use crate::icon::{Glyph, opacity, themed};
36use crate::keys::{self, Chord, Keymap};
37use crate::overlay::context_menu;
38use crate::overlay::menu::{self, ICON_SIZE, ROW_PADDING, RowStatus};
39use crate::primitives::checkbox::{CheckState, checkbox};
40use crate::theme::{Tokens, fade, radius, space, text_size};
41
42pub const ROW_HEIGHT: f32 = 32.0;
44pub const INDENT: f32 = 20.0;
46pub const CHANNEL_CAPACITY: usize = 64;
48pub const BATCH_SIZE: usize = 32;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub enum Mode {
54 None,
56 #[default]
58 Single,
59 Multiple,
61 Checkbox,
64}
65
66impl Mode {
67 pub const ALL: [Mode; 4] = [Mode::None, Mode::Single, Mode::Multiple, Mode::Checkbox];
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum Trailing {
73 Badge(String),
75 Text(String),
77}
78
79#[derive(Debug, Clone, PartialEq)]
80enum Kids<Id> {
81 None,
82 Nodes(Vec<Node<Id>>),
83 Lazy,
84}
85
86#[derive(Debug, Clone, PartialEq)]
89pub struct Node<Id> {
90 pub id: Id,
91 pub label: String,
92 pub icon: Option<Glyph>,
93 pub open_icon: Option<Glyph>,
95 pub trailing: Option<Trailing>,
96 pub disabled: bool,
97 kids: Kids<Id>,
98}
99
100pub fn node<Id>(id: Id, label: impl Into<String>) -> Node<Id> {
102 Node {
103 id,
104 label: label.into(),
105 icon: None,
106 open_icon: None,
107 trailing: None,
108 disabled: false,
109 kids: Kids::None,
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct Loaded<Id> {
116 pub parent: Id,
117 pub children: Vec<Node<Id>>,
118}
119
120pub fn loaded<Id>(parent: Id, children: impl IntoIterator<Item = Node<Id>>) -> Loaded<Id> {
122 Loaded {
123 parent,
124 children: children.into_iter().collect(),
125 }
126}
127
128impl<Id> Node<Id> {
129 pub fn icon(mut self, glyph: Glyph) -> Self {
130 self.icon = Some(glyph);
131 self
132 }
133
134 pub fn icons(mut self, closed: Glyph, open: Glyph) -> Self {
136 self.icon = Some(closed);
137 self.open_icon = Some(open);
138 self
139 }
140
141 pub fn folder(self) -> Self {
143 self.icons(crate::lucide!(Folder), crate::lucide!(FolderOpen))
144 }
145
146 pub fn file(self) -> Self {
148 self.icon(crate::lucide!(File))
149 }
150
151 pub fn badge(mut self, label: impl Into<String>) -> Self {
153 self.trailing = Some(Trailing::Badge(label.into()));
154 self
155 }
156
157 pub fn trailing(mut self, label: impl Into<String>) -> Self {
159 self.trailing = Some(Trailing::Text(label.into()));
160 self
161 }
162
163 pub fn disabled(mut self, disabled: bool) -> Self {
166 self.disabled = disabled;
167 self
168 }
169
170 pub fn children(mut self, children: impl IntoIterator<Item = Node<Id>>) -> Self {
171 self.kids = Kids::Nodes(children.into_iter().collect());
172 self
173 }
174
175 pub fn lazy(mut self) -> Self {
177 self.kids = Kids::Lazy;
178 self
179 }
180}
181
182#[derive(Debug, Clone, PartialEq)]
184enum Children<Id> {
185 None,
186 Loaded(Vec<Id>),
187 Unloaded,
188 Loading,
189}
190
191#[derive(Debug, Clone)]
192struct Slot<Id> {
193 node: Node<Id>,
194 parent: Option<Id>,
195 children: Children<Id>,
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200enum Line<Id> {
201 Node {
202 id: Id,
203 depth: usize,
204 },
205 Loading {
207 depth: usize,
208 },
209}
210
211impl<Id: PartialEq> Line<Id> {
212 fn is(&self, id: &Id) -> bool {
213 matches!(self, Line::Node { id: line, .. } if line == id)
214 }
215}
216
217pub type Sender<Id> = mpsc::Sender<Loaded<Id>>;
220
221#[derive(Debug, Clone)]
223pub enum Event<Id> {
224 Ready(Sender<Id>),
226 Received(Vec<Loaded<Id>>),
228 Toggle(Id),
230 Expand(Id),
231 Collapse(Id),
232 Press(Id, Modifiers),
234 Activate(Id),
236 Check(Id, bool),
238 Next,
239 Previous,
240 First,
241 Last,
242 ExtendNext,
245 ExtendPrevious,
246 ExpandOrEnter,
249 CollapseOrLeave,
252 ExpandSiblings,
254 ToggleHighlighted,
256 ActivateHighlighted,
257 SelectAll,
259 Typeahead(char),
262 Focus(bool),
264}
265
266#[derive(Debug, Clone)]
268pub enum Output<Id> {
269 Ready(Sender<Id>),
270 Load(Vec<Id>),
273 Activated(Id),
275 Selected(Vec<Id>),
277 Checked(Vec<Id>),
279}
280
281#[derive(Debug, Clone)]
283pub struct State<Id> {
284 roots: Vec<Id>,
285 slots: HashMap<Id, Slot<Id>>,
286 mode: Mode,
287 expanded: HashSet<Id>,
288 selected: HashSet<Id>,
289 checked: HashSet<Id>,
290 partial: HashSet<Id>,
292 highlighted: Option<Id>,
293 anchor: Option<Id>,
295 focused: bool,
296}
297
298impl<Id: Copy + Eq + Hash> State<Id> {
299 pub fn new(roots: impl IntoIterator<Item = Node<Id>>) -> Self {
302 let mut state = Self {
303 roots: Vec::new(),
304 slots: HashMap::new(),
305 mode: Mode::default(),
306 expanded: HashSet::new(),
307 selected: HashSet::new(),
308 checked: HashSet::new(),
309 partial: HashSet::new(),
310 highlighted: None,
311 anchor: None,
312 focused: false,
313 };
314 state.roots = roots
315 .into_iter()
316 .map(|node| state.insert(node, None))
317 .collect();
318 state
319 }
320
321 pub fn with_mode(mut self, mode: Mode) -> Self {
322 self.mode = mode;
323 self
324 }
325
326 pub fn with_expanded(mut self, ids: impl IntoIterator<Item = Id>) -> Self {
329 for id in ids {
330 if self.is_loaded(id) && self.has_children(id) {
331 let _ = self.expanded.insert(id);
332 }
333 }
334 self
335 }
336
337 pub fn mode(&self) -> Mode {
338 self.mode
339 }
340
341 pub fn roots(&self) -> &[Id] {
342 &self.roots
343 }
344
345 pub fn node(&self, id: Id) -> Option<&Node<Id>> {
346 self.slots.get(&id).map(|slot| &slot.node)
347 }
348
349 pub fn parent(&self, id: Id) -> Option<Id> {
350 self.slots.get(&id).and_then(|slot| slot.parent)
351 }
352
353 pub fn children(&self, id: Id) -> &[Id] {
356 match self.slots.get(&id).map(|slot| &slot.children) {
357 Some(Children::Loaded(children)) => children,
358 _ => &[],
359 }
360 }
361
362 pub fn has_children(&self, id: Id) -> bool {
364 match self.slots.get(&id).map(|slot| &slot.children) {
365 Some(Children::Loaded(children)) => !children.is_empty(),
366 Some(Children::Unloaded | Children::Loading) => true,
367 Some(Children::None) | None => false,
368 }
369 }
370
371 pub fn is_loaded(&self, id: Id) -> bool {
373 matches!(
374 self.slots.get(&id).map(|slot| &slot.children),
375 Some(Children::None | Children::Loaded(_))
376 )
377 }
378
379 pub fn is_loading(&self, id: Id) -> bool {
380 matches!(
381 self.slots.get(&id).map(|slot| &slot.children),
382 Some(Children::Loading)
383 )
384 }
385
386 pub fn set_loading(&mut self, id: Id) {
389 if let Some(slot) = self.slots.get_mut(&id)
390 && !matches!(slot.children, Children::Loaded(_))
391 {
392 slot.children = Children::Loading;
393 }
394 }
395
396 pub fn is_expanded(&self, id: Id) -> bool {
397 self.expanded.contains(&id)
398 }
399
400 pub fn is_selected(&self, id: Id) -> bool {
401 self.selected.contains(&id)
402 }
403
404 pub fn is_focused(&self) -> bool {
405 self.focused
406 }
407
408 pub fn highlighted(&self) -> Option<Id> {
410 self.highlighted
411 }
412
413 pub fn selected(&self) -> Vec<Id> {
415 self.walk(false)
416 .into_iter()
417 .map(|(id, _)| id)
418 .filter(|id| self.selected.contains(id))
419 .collect()
420 }
421
422 pub fn checked(&self) -> Vec<Id> {
424 self.walk(false)
425 .into_iter()
426 .map(|(id, _)| id)
427 .filter(|id| self.checked.contains(id))
428 .collect()
429 }
430
431 pub fn check_state(&self, id: Id) -> CheckState {
434 if self.checked.contains(&id) {
435 CheckState::Checked
436 } else if self.partial.contains(&id) {
437 CheckState::Indeterminate
438 } else {
439 CheckState::Unchecked
440 }
441 }
442
443 pub fn visible(&self) -> Vec<Id> {
446 self.walk(true).into_iter().map(|(id, _)| id).collect()
447 }
448
449 pub fn len(&self) -> usize {
451 self.slots.len()
452 }
453
454 pub fn is_empty(&self) -> bool {
455 self.slots.is_empty()
456 }
457
458 pub fn update(&mut self, event: Event<Id>) -> Option<Output<Id>> {
460 match event {
461 Event::Ready(sender) => return Some(Output::Ready(sender)),
462 Event::Received(batch) => {
463 let mut changed = false;
464 for loaded in batch {
465 changed |= self.load(loaded);
466 }
467 return changed.then(|| Output::Checked(self.checked()));
468 }
469 Event::Toggle(id) if self.is_expanded(id) => self.collapse(id),
470 Event::Toggle(id) | Event::Expand(id) => return self.expand([id]),
471 Event::Collapse(id) => self.collapse(id),
472 Event::Press(id, modifiers) => return self.press(id, modifiers),
473 Event::Activate(id) if self.is_enabled(id) => {
474 self.highlighted = Some(id);
475 return Some(Output::Activated(id));
476 }
477 Event::Activate(_) => {}
478 Event::Check(id, checked) => return self.check(id, checked),
479 Event::Next => self.step(Step::Next),
480 Event::Previous => self.step(Step::Previous),
481 Event::First => self.step(Step::First),
482 Event::Last => self.step(Step::Last),
483 Event::ExtendNext => return self.extend(Step::Next),
484 Event::ExtendPrevious => return self.extend(Step::Previous),
485 Event::ExpandOrEnter => return self.expand_or_enter(),
486 Event::CollapseOrLeave => self.collapse_or_leave(),
487 Event::ExpandSiblings => return self.expand_siblings(),
488 Event::ToggleHighlighted => return self.toggle_highlighted(),
489 Event::ActivateHighlighted => {
490 return self.highlighted.map(Output::Activated);
491 }
492 Event::SelectAll => return self.select_all(),
493 Event::Typeahead(character) => self.typeahead(character),
494 Event::Focus(focused) => {
495 self.focused = focused;
496 if focused && self.highlighted.is_none() {
499 let nodes = self.navigable();
500 self.highlighted = nodes
501 .iter()
502 .copied()
503 .find(|id| self.selected.contains(id))
504 .or(nodes.first().copied());
505 }
506 }
507 }
508 None
509 }
510
511 pub fn key_event(&self, keymap: &Keymap<Action>, key: &keys::Event) -> Option<Event<Id>> {
515 let action = keymap.resolve_event(key).or_else(|| {
516 let symbol = matches!(&key.key, Key::Character(text) if !text.chars().any(char::is_alphanumeric));
517 (symbol && key.modifiers.shift())
518 .then(|| {
519 keymap.resolve(&key.key, key.modifiers.difference(Modifiers::SHIFT))
520 })
521 .flatten()
522 });
523 if let Some(action) = action {
524 return action.event(self);
525 }
526 typeahead(key).map(Event::Typeahead)
527 }
528
529 fn insert(&mut self, mut node: Node<Id>, parent: Option<Id>) -> Id {
530 let id = node.id;
531 let children = match std::mem::replace(&mut node.kids, Kids::None) {
532 Kids::None => Children::None,
533 Kids::Lazy => Children::Unloaded,
534 Kids::Nodes(nodes) => Children::Loaded(
535 nodes
536 .into_iter()
537 .map(|child| self.insert(child, Some(id)))
538 .collect(),
539 ),
540 };
541 let _ = self.slots.insert(
542 id,
543 Slot {
544 node,
545 parent,
546 children,
547 },
548 );
549 id
550 }
551
552 fn is_enabled(&self, id: Id) -> bool {
553 self.slots.get(&id).is_some_and(|slot| !slot.node.disabled)
554 }
555
556 fn walk(&self, visible: bool) -> Vec<(Id, usize)> {
559 let mut out = Vec::new();
560 let mut stack: Vec<(Id, usize)> = self.roots.iter().rev().map(|&id| (id, 0)).collect();
561 while let Some((id, depth)) = stack.pop() {
562 out.push((id, depth));
563 if visible && !self.expanded.contains(&id) {
564 continue;
565 }
566 stack.extend(
567 self.children(id)
568 .iter()
569 .rev()
570 .map(|&child| (child, depth + 1)),
571 );
572 }
573 out
574 }
575
576 fn lines(&self) -> Vec<Line<Id>> {
579 let mut lines = Vec::new();
580 for (id, depth) in self.walk(true) {
581 lines.push(Line::Node { id, depth });
582 if self.is_expanded(id) && self.is_loading(id) {
583 lines.push(Line::Loading { depth: depth + 1 });
584 }
585 }
586 lines
587 }
588
589 fn navigable(&self) -> Vec<Id> {
591 self.walk(true)
592 .into_iter()
593 .map(|(id, _)| id)
594 .filter(|&id| self.is_enabled(id))
595 .collect()
596 }
597
598 fn step(&mut self, step: Step) {
599 let nodes = self.navigable();
600 let current = self
601 .highlighted
602 .and_then(|id| nodes.iter().position(|&node| node == id));
603 let target = match (step, current) {
604 (Step::First, _) | (Step::Next, None) => nodes.first(),
605 (Step::Last, _) | (Step::Previous, None) => nodes.last(),
606 (Step::Next, Some(index)) => nodes.get(index + 1).or(nodes.get(index)),
607 (Step::Previous, Some(index)) => nodes.get(index.saturating_sub(1)),
608 };
609 if let Some(&target) = target {
610 self.highlighted = Some(target);
611 }
612 }
613
614 fn expand(&mut self, ids: impl IntoIterator<Item = Id>) -> Option<Output<Id>> {
615 let mut load = Vec::new();
616 for id in ids {
617 if !self.is_enabled(id) || !self.has_children(id) {
618 continue;
619 }
620 let _ = self.expanded.insert(id);
621 let Some(slot) = self.slots.get_mut(&id) else {
622 continue;
623 };
624 if slot.children == Children::Unloaded {
625 slot.children = Children::Loading;
626 load.push(id);
627 }
628 }
629 (!load.is_empty()).then_some(Output::Load(load))
630 }
631
632 fn collapse(&mut self, id: Id) {
633 if !self.expanded.remove(&id) {
634 return;
635 }
636 let hidden = self
638 .highlighted
639 .is_some_and(|highlighted| self.is_ancestor(id, highlighted));
640 if hidden {
641 self.highlighted = Some(id);
642 }
643 }
644
645 fn is_ancestor(&self, ancestor: Id, id: Id) -> bool {
646 let mut current = self.parent(id);
647 while let Some(parent) = current {
648 if parent == ancestor {
649 return true;
650 }
651 current = self.parent(parent);
652 }
653 false
654 }
655
656 fn expand_or_enter(&mut self) -> Option<Output<Id>> {
657 let id = self.highlighted?;
658 if !self.is_expanded(id) {
659 return self.expand([id]);
660 }
661 let first = self
662 .children(id)
663 .iter()
664 .copied()
665 .find(|&child| self.is_enabled(child));
666 if let Some(first) = first {
667 self.highlighted = Some(first);
668 }
669 None
670 }
671
672 fn collapse_or_leave(&mut self) {
673 let Some(id) = self.highlighted else {
674 return;
675 };
676 if self.is_expanded(id) {
677 self.collapse(id);
678 return;
679 }
680 if let Some(parent) = self.parent(id).filter(|&parent| self.is_enabled(parent)) {
681 self.highlighted = Some(parent);
682 }
683 }
684
685 fn expand_siblings(&mut self) -> Option<Output<Id>> {
686 let id = self.highlighted?;
687 let siblings = match self.parent(id) {
688 Some(parent) => self.children(parent).to_vec(),
689 None => self.roots.clone(),
690 };
691 self.expand(siblings)
692 }
693
694 fn selection_output(&self, before: &HashSet<Id>) -> Option<Output<Id>> {
695 (self.selected != *before).then(|| Output::Selected(self.selected()))
696 }
697
698 fn press(&mut self, id: Id, modifiers: Modifiers) -> Option<Output<Id>> {
699 if !self.is_enabled(id) {
700 return None;
701 }
702 self.highlighted = Some(id);
703 match self.mode {
704 Mode::None => None,
705 Mode::Single => {
706 let before = std::mem::replace(&mut self.selected, HashSet::from([id]));
707 self.anchor = Some(id);
708 self.selection_output(&before)
709 }
710 Mode::Checkbox => self.check(id, !self.checked.contains(&id)),
711 Mode::Multiple => {
712 let before = self.selected.clone();
713 let anchor = self
714 .anchor
715 .filter(|&anchor| self.navigable().contains(&anchor));
716 match (modifiers.shift(), modifiers.command(), anchor) {
717 (true, additive, Some(anchor)) => {
718 if !additive {
719 self.selected.clear();
720 }
721 self.selected.extend(self.range(anchor, id));
722 }
723 (_, true, _) => {
724 if !self.selected.remove(&id) {
725 let _ = self.selected.insert(id);
726 }
727 self.anchor = Some(id);
728 }
729 _ => {
730 self.selected = HashSet::from([id]);
731 self.anchor = Some(id);
732 }
733 }
734 self.selection_output(&before)
735 }
736 }
737 }
738
739 fn range(&self, a: Id, b: Id) -> Vec<Id> {
741 let nodes = self.navigable();
742 let (Some(start), Some(end)) = (
743 nodes.iter().position(|&id| id == a),
744 nodes.iter().position(|&id| id == b),
745 ) else {
746 return vec![b];
747 };
748 let span = if start <= end {
749 start..=end
750 } else {
751 end..=start
752 };
753 nodes.get(span).map(<[Id]>::to_vec).unwrap_or_default()
754 }
755
756 fn extend(&mut self, step: Step) -> Option<Output<Id>> {
757 if self.mode != Mode::Multiple {
758 self.step(step);
759 return None;
760 }
761 let anchor = self.anchor.or(self.highlighted);
762 self.step(step);
763 let (Some(anchor), Some(highlighted)) = (anchor, self.highlighted) else {
764 return None;
765 };
766 self.anchor = Some(anchor);
767 let before = self.selected.clone();
768 self.selected = self.range(anchor, highlighted).into_iter().collect();
769 self.selection_output(&before)
770 }
771
772 fn toggle_highlighted(&mut self) -> Option<Output<Id>> {
773 let id = self.highlighted?;
774 match self.mode {
775 Mode::None => None,
776 Mode::Checkbox => self.check(id, !self.checked.contains(&id)),
777 Mode::Single => self.press(id, Modifiers::empty()),
778 Mode::Multiple => self.press(id, Modifiers::COMMAND),
779 }
780 }
781
782 fn select_all(&mut self) -> Option<Output<Id>> {
783 if self.mode != Mode::Multiple {
784 return None;
785 }
786 let before = self.selected.clone();
787 self.selected = self.navigable().into_iter().collect();
788 self.selection_output(&before)
789 }
790
791 fn typeahead(&mut self, character: char) {
792 let wanted: String = character.to_lowercase().collect();
793 let nodes = self.navigable();
794 let start = self
795 .highlighted
796 .and_then(|id| nodes.iter().position(|&node| node == id))
797 .map_or(0, |index| index + 1);
798 let found = (0..nodes.len())
799 .filter_map(|offset| nodes.get((start + offset) % nodes.len()))
800 .copied()
801 .find(|&id| {
802 self.node(id)
803 .is_some_and(|node| node.label.to_lowercase().starts_with(&wanted))
804 });
805 if let Some(found) = found {
806 self.highlighted = Some(found);
807 }
808 }
809
810 fn check(&mut self, id: Id, checked: bool) -> Option<Output<Id>> {
811 if self.mode != Mode::Checkbox || !self.is_enabled(id) {
812 return None;
813 }
814 let before = self.checked.clone();
815 self.set_subtree(id, checked);
816 self.refresh(self.parent(id));
817 (self.checked != before).then(|| Output::Checked(self.checked()))
818 }
819
820 fn set_subtree(&mut self, id: Id, checked: bool) {
823 let mut stack = vec![id];
824 while let Some(current) = stack.pop() {
825 if current != id && !self.is_enabled(current) {
826 continue;
827 }
828 let _ = self.partial.remove(¤t);
829 if checked {
830 let _ = self.checked.insert(current);
831 } else {
832 let _ = self.checked.remove(¤t);
833 }
834 stack.extend(self.children(current).iter().copied());
835 }
836 }
837
838 fn refresh(&mut self, start: Option<Id>) {
841 let mut current = start;
842 while let Some(parent) = current {
843 let children: Vec<Id> = self
844 .children(parent)
845 .iter()
846 .copied()
847 .filter(|&child| self.is_enabled(child))
848 .collect();
849 if !children.is_empty() {
850 let all = children.iter().all(|child| self.checked.contains(child));
851 let some = children
852 .iter()
853 .any(|child| self.checked.contains(child) || self.partial.contains(child));
854 if all {
855 let _ = self.checked.insert(parent);
856 } else {
857 let _ = self.checked.remove(&parent);
858 }
859 if some && !all {
860 let _ = self.partial.insert(parent);
861 } else {
862 let _ = self.partial.remove(&parent);
863 }
864 } else {
865 let _ = self.partial.remove(&parent);
866 }
867 current = self.parent(parent);
868 }
869 }
870
871 fn load(&mut self, loaded: Loaded<Id>) -> bool {
874 let parent = loaded.parent;
875 if !self.slots.contains_key(&parent) {
876 return false;
877 }
878 let checked_before = self.checked.clone();
879 for child in self.children(parent).to_vec() {
880 self.remove(child);
881 }
882 let children: Vec<Id> = loaded
883 .children
884 .into_iter()
885 .map(|child| self.insert(child, Some(parent)))
886 .collect();
887 if let Some(slot) = self.slots.get_mut(&parent) {
888 slot.children = Children::Loaded(children.clone());
889 }
890 if children.is_empty() {
891 let _ = self.expanded.remove(&parent);
892 }
893 if self.mode != Mode::Checkbox {
894 return false;
895 }
896 if self.checked.contains(&parent) {
897 for child in children {
898 if self.is_enabled(child) {
899 self.set_subtree(child, true);
900 }
901 }
902 }
903 self.refresh(Some(parent));
904 self.checked != checked_before
905 }
906
907 fn remove(&mut self, id: Id) {
909 for child in self.children(id).to_vec() {
910 self.remove(child);
911 }
912 let _ = self.slots.remove(&id);
913 let _ = self.expanded.remove(&id);
914 let _ = self.selected.remove(&id);
915 let _ = self.checked.remove(&id);
916 let _ = self.partial.remove(&id);
917 if self.highlighted == Some(id) {
918 self.highlighted = None;
919 }
920 if self.anchor == Some(id) {
921 self.anchor = None;
922 }
923 }
924}
925
926#[derive(Debug, Clone, Copy, PartialEq, Eq)]
927enum Step {
928 Next,
929 Previous,
930 First,
931 Last,
932}
933
934fn typeahead(key: &keys::Event) -> Option<char> {
937 let modifiers = key.modifiers;
938 if modifiers.control() || modifiers.alt() || modifiers.logo() {
939 return None;
940 }
941 let Key::Character(text) = &key.key else {
942 return None;
943 };
944 let mut chars = text.chars();
945 let character = chars.next()?;
946 if chars.next().is_some() || !character.is_alphanumeric() {
947 return None;
948 }
949 Some(character)
950}
951
952#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
954pub enum Action {
955 Next,
956 Previous,
957 First,
958 Last,
959 Expand,
960 Collapse,
961 Activate,
962 Select,
963 ExpandSiblings,
964 ExtendNext,
965 ExtendPrevious,
966 SelectAll,
967}
968
969impl Action {
970 pub fn event<Id: Copy + Eq + Hash>(self, state: &State<Id>) -> Option<Event<Id>> {
973 let highlighted = state.highlighted().is_some();
974 let multiple = state.mode() == Mode::Multiple;
975 match self {
976 Action::Next => Some(Event::Next),
977 Action::Previous => Some(Event::Previous),
978 Action::First => Some(Event::First),
979 Action::Last => Some(Event::Last),
980 Action::Expand => highlighted.then_some(Event::ExpandOrEnter),
981 Action::Collapse => highlighted.then_some(Event::CollapseOrLeave),
982 Action::Activate => highlighted.then_some(Event::ActivateHighlighted),
983 Action::Select => {
984 (highlighted && state.mode() != Mode::None).then_some(Event::ToggleHighlighted)
985 }
986 Action::ExpandSiblings => highlighted.then_some(Event::ExpandSiblings),
987 Action::ExtendNext if multiple => Some(Event::ExtendNext),
988 Action::ExtendNext => Some(Event::Next),
989 Action::ExtendPrevious if multiple => Some(Event::ExtendPrevious),
990 Action::ExtendPrevious => Some(Event::Previous),
991 Action::SelectAll => multiple.then_some(Event::SelectAll),
992 }
993 }
994}
995
996impl keys::Action for Action {
997 const ALL: &'static [Self] = &[
998 Action::Next,
999 Action::Previous,
1000 Action::First,
1001 Action::Last,
1002 Action::Expand,
1003 Action::Collapse,
1004 Action::Activate,
1005 Action::Select,
1006 Action::ExpandSiblings,
1007 Action::ExtendNext,
1008 Action::ExtendPrevious,
1009 Action::SelectAll,
1010 ];
1011
1012 fn defaults() -> Keymap<Self> {
1013 default_keymap()
1014 }
1015
1016 fn name(self) -> &'static str {
1017 match self {
1018 Action::Next => "Next",
1019 Action::Previous => "Previous",
1020 Action::First => "First",
1021 Action::Last => "Last",
1022 Action::Expand => "Expand",
1023 Action::Collapse => "Collapse",
1024 Action::Activate => "Activate",
1025 Action::Select => "Select",
1026 Action::ExpandSiblings => "ExpandSiblings",
1027 Action::ExtendNext => "ExtendNext",
1028 Action::ExtendPrevious => "ExtendPrevious",
1029 Action::SelectAll => "SelectAll",
1030 }
1031 }
1032
1033 fn description(self) -> &'static str {
1034 match self {
1035 Action::Next => "Highlights the next visible node.",
1036 Action::Previous => "Highlights the previous visible node.",
1037 Action::First => "Highlights the first node.",
1038 Action::Last => "Highlights the last visible node.",
1039 Action::Expand => {
1040 "Expands the highlighted node, or moves to its first child when it is open."
1041 }
1042 Action::Collapse => {
1043 "Collapses the highlighted node, or moves to its parent when it is closed."
1044 }
1045 Action::Activate => "Activates the highlighted node.",
1046 Action::Select => "Selects, toggles or checks the highlighted node.",
1047 Action::ExpandSiblings => "Expands the highlighted node and all of its siblings.",
1048 Action::ExtendNext => "Moves down and extends the selection, in a multiple selection.",
1049 Action::ExtendPrevious => {
1050 "Moves up and extends the selection, in a multiple selection."
1051 }
1052 Action::SelectAll => "Selects every visible node, in a multiple selection.",
1053 }
1054 }
1055}
1056
1057pub fn default_keymap() -> Keymap<Action> {
1078 Keymap::new()
1079 .bind(Chord::named(Named::ArrowDown), Action::Next)
1080 .bind(Chord::named(Named::ArrowUp), Action::Previous)
1081 .bind(Chord::named(Named::Home), Action::First)
1082 .bind(Chord::named(Named::End), Action::Last)
1083 .bind(Chord::named(Named::ArrowRight), Action::Expand)
1084 .bind(Chord::named(Named::ArrowLeft), Action::Collapse)
1085 .bind(Chord::named(Named::Enter), Action::Activate)
1086 .bind(Chord::named(Named::Space), Action::Select)
1087 .bind(Chord::character('*'), Action::ExpandSiblings)
1088 .bind(Chord::named(Named::ArrowDown).shift(), Action::ExtendNext)
1089 .bind(Chord::named(Named::ArrowUp).shift(), Action::ExtendPrevious)
1090 .bind(Chord::character('a').command(), Action::SelectAll)
1091}
1092
1093pub fn subscription<Id: Send + 'static>() -> Subscription<Event<Id>> {
1096 Subscription::run(stream::<Id>)
1097}
1098
1099pub fn stream<Id: Send + 'static>() -> impl Stream<Item = Event<Id>> {
1101 let (sender, receiver) = mpsc::channel(CHANNEL_CAPACITY);
1102 stream::once(async move { Event::Ready(sender) })
1103 .chain(receiver.ready_chunks(BATCH_SIZE).map(Event::Received))
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1108pub struct Status {
1109 pub selected: bool,
1110 pub hovered: bool,
1111 pub highlighted: bool,
1113 pub disabled: bool,
1114}
1115
1116#[derive(Debug, Clone, Copy, PartialEq)]
1118pub struct RowStyle {
1119 pub background: Option<Color>,
1120 pub text: Color,
1121 pub icon: Color,
1122 pub ring: Option<Color>,
1124}
1125
1126pub fn row_style(tokens: &Tokens, status: Status) -> RowStyle {
1129 let menu_status = match (status.disabled, status.selected) {
1130 (true, _) => RowStatus::Disabled,
1131 (false, true) => RowStatus::Highlighted,
1132 (false, false) => RowStatus::Idle,
1133 };
1134 let base = menu::row_style(tokens, menu_status, false);
1135 let hover = (status.hovered && !status.disabled).then(|| fade(tokens.accent, 0.6));
1136 RowStyle {
1137 background: base.background.or(hover),
1138 text: base.text,
1139 icon: base.icon,
1140 ring: (status.highlighted && !status.disabled).then_some(tokens.ring),
1141 }
1142}
1143
1144pub fn guide_colour(tokens: &Tokens) -> Color {
1146 tokens.border
1147}
1148
1149type OnEvent<'a, Id, Message> = dyn Fn(Event<Id>) -> Message + 'a;
1150type BuildMenu<'a, Id, Message> =
1151 dyn FnOnce(Keymap<context_menu::Action>) -> RowMenu<'a, Id, Message> + 'a;
1152
1153pub struct Tree<'a, Id, Message> {
1155 state: &'a State<Id>,
1156 guides: bool,
1157 width: Length,
1158 height: Length,
1159 keymap: Keymap<Action>,
1160 menu: Option<Box<BuildMenu<'a, Id, Message>>>,
1161 menu_keymap: Keymap<context_menu::Action>,
1162 on_event: Option<Box<OnEvent<'a, Id, Message>>>,
1163}
1164
1165pub fn tree<Id, Message>(state: &State<Id>) -> Tree<'_, Id, Message> {
1168 Tree {
1169 state,
1170 guides: false,
1171 width: Length::Fill,
1172 height: Length::Shrink,
1173 keymap: default_keymap(),
1174 menu: None,
1175 menu_keymap: context_menu::default_keymap(),
1176 on_event: None,
1177 }
1178}
1179
1180impl<Id: std::fmt::Debug, Message> std::fmt::Debug for Tree<'_, Id, Message> {
1181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1182 f.debug_struct("Tree")
1183 .field("state", self.state)
1184 .field("guides", &self.guides)
1185 .field("width", &self.width)
1186 .field("height", &self.height)
1187 .field("keymap", &self.keymap)
1188 .finish_non_exhaustive()
1189 }
1190}
1191
1192impl<'a, Id, Message> Tree<'a, Id, Message> {
1193 pub fn guides(mut self, guides: bool) -> Self {
1195 self.guides = guides;
1196 self
1197 }
1198
1199 pub fn width(mut self, width: impl Into<Length>) -> Self {
1200 self.width = width.into();
1201 self
1202 }
1203
1204 pub fn height(mut self, height: impl Into<Length>) -> Self {
1207 self.height = height.into();
1208 self
1209 }
1210
1211 pub fn keymap(mut self, keymap: Keymap<Action>) -> Self {
1213 self.keymap = keymap;
1214 self
1215 }
1216
1217 pub fn on_event(mut self, on_event: impl Fn(Event<Id>) -> Message + 'a) -> Self {
1218 self.on_event = Some(Box::new(on_event));
1219 self
1220 }
1221
1222 pub fn context_menu<MenuId>(
1226 mut self,
1227 menu: &'a context_menu::State<MenuId, Id>,
1228 on_event: impl Fn(context_menu::Event<MenuId, Id>) -> Message + 'a,
1229 ) -> Self
1230 where
1231 MenuId: Copy + PartialEq + 'a,
1232 Id: Clone + PartialEq + 'a,
1233 Message: Clone + 'a,
1234 {
1235 let on_event: Rc<dyn Fn(context_menu::Event<MenuId, Id>) -> Message + 'a> =
1236 Rc::new(on_event);
1237 self.menu = Some(Box::new(move |keymap| row_menu(menu, keymap, on_event)));
1238 self
1239 }
1240
1241 pub fn context_menu_keymap(mut self, keymap: Keymap<context_menu::Action>) -> Self {
1243 self.menu_keymap = keymap;
1244 self
1245 }
1246
1247 pub fn is_enabled(&self) -> bool {
1248 self.on_event.is_some()
1249 }
1250}
1251
1252impl<'a, Id, Message> From<Tree<'a, Id, Message>> for Element<'a, Message>
1253where
1254 Id: Copy + Eq + Hash + 'a,
1255 Message: Clone + 'a,
1256{
1257 fn from(tree: Tree<'a, Id, Message>) -> Self {
1258 let Tree {
1259 state,
1260 guides,
1261 width,
1262 height,
1263 keymap,
1264 menu,
1265 menu_keymap,
1266 on_event,
1267 } = tree;
1268 let on_event: Option<Rc<OnEvent<'a, Id, Message>>> = on_event.map(Rc::from);
1269 let menu = menu
1270 .filter(|_| on_event.is_some())
1271 .map(|build| Rc::new(build(menu_keymap)));
1272 let lines = state.lines();
1273 let highlight = state
1274 .highlighted
1275 .and_then(|id| lines.iter().position(|line| line.is(&id)));
1276 let count = lines.len();
1277
1278 let build = {
1279 let on_event = on_event.clone();
1280 let menu = menu.clone();
1281 move |range: Range<usize>| -> Element<'a, Message> {
1282 let lines = lines.get(range).unwrap_or_default();
1283 column(lines.iter().map(|line| match *line {
1284 Line::Node { id, depth } => {
1285 let row = node_view(state, id, depth, guides, on_event.as_ref());
1286 match &menu {
1287 Some(menu) if state.is_enabled(id) => menu.wrap(id, row),
1288 _ => row,
1289 }
1290 }
1291 Line::Loading { depth } => loading_view(depth, guides),
1292 }))
1293 .into()
1294 }
1295 };
1296 let list = container(
1297 rows(count, ROW_HEIGHT, build)
1298 .highlight(highlight)
1299 .height(height),
1300 )
1301 .width(width);
1302
1303 let Some(on_event) = on_event else {
1304 return list.into();
1305 };
1306 let on_focus = on_event.clone();
1307 scope(list)
1308 .on_focus(move |focused| on_focus(Event::Focus(focused)))
1309 .on_key(move |key| {
1310 let opened = menu
1311 .as_ref()
1312 .zip(state.highlighted())
1313 .and_then(|(menu, target)| menu.key(key, target));
1314 opened.or_else(|| state.key_event(&keymap, key).map(&*on_event))
1315 })
1316 .into()
1317 }
1318}
1319
1320fn indent<'a, Message: 'a>(depth: usize, guides: bool) -> Element<'a, Message> {
1322 let levels =
1323 (0..depth).map(|_| {
1324 let guide: Element<'a, Message> =
1325 if guides {
1326 container(container(Space::new()).width(1).height(Length::Fill).style(
1327 |theme| container::Style {
1328 background: Some(Background::Color(guide_colour(&Tokens::of(theme)))),
1329 ..container::Style::default()
1330 },
1331 ))
1332 .padding(iced::Padding::ZERO.left(ICON_SIZE / 2.0))
1333 .into()
1334 } else {
1335 Space::new().into()
1336 };
1337 container(guide).width(INDENT).height(Length::Fill).into()
1338 });
1339 row(levels).height(Length::Fill).into()
1340}
1341
1342fn line_frame<'a, Message: 'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
1343 container(content)
1344 .padding(
1345 iced::Padding::ZERO
1346 .left(ROW_PADDING.left)
1347 .right(ROW_PADDING.right),
1348 )
1349 .width(Length::Fill)
1350 .height(ROW_HEIGHT)
1351 .into()
1352}
1353
1354fn loading_view<'a, Message: 'a>(depth: usize, guides: bool) -> Element<'a, Message> {
1355 let muted = |theme: &Theme| Tokens::of(theme).muted_foreground;
1356 line_frame(
1357 row![
1358 indent(depth, guides),
1359 Space::new().width(INDENT),
1360 row![
1361 themed(crate::lucide!(LoaderCircle), ICON_SIZE, 1.0, muted),
1362 text("Loading...")
1363 .size(text_size::SM)
1364 .wrapping(text::Wrapping::None)
1365 .style(move |theme| text::Style {
1366 color: Some(muted(theme)),
1367 }),
1368 ]
1369 .spacing(space::SM)
1370 .align_y(Alignment::Center)
1371 .height(Length::Fill),
1372 ]
1373 .height(Length::Fill),
1374 )
1375}
1376
1377fn node_view<'a, Id, Message>(
1378 state: &'a State<Id>,
1379 id: Id,
1380 depth: usize,
1381 guides: bool,
1382 on_event: Option<&Rc<OnEvent<'a, Id, Message>>>,
1383) -> Element<'a, Message>
1384where
1385 Id: Copy + Eq + Hash + 'a,
1386 Message: Clone + 'a,
1387{
1388 let Some(node) = state.node(id) else {
1389 return Space::new().into();
1390 };
1391 let on_event = on_event.filter(|_| !node.disabled);
1392 let enabled = on_event.is_some();
1393 let expanded = state.is_expanded(id);
1394 let status = Status {
1395 selected: state.mode != Mode::Checkbox && state.is_selected(id),
1396 hovered: false,
1397 highlighted: state.focused && state.highlighted == Some(id),
1398 disabled: !enabled,
1399 };
1400 let colours = move |theme: &Theme| row_style(&Tokens::of(theme), status);
1401
1402 let chevron: Element<'a, Message> = if state.has_children(id) {
1403 let glyph = if expanded {
1404 crate::lucide!(ChevronDown)
1405 } else {
1406 crate::lucide!(ChevronRight)
1407 };
1408 let icon = container(themed(glyph, ICON_SIZE, opacity(enabled), move |theme| {
1409 colours(theme).icon
1410 }))
1411 .width(INDENT)
1412 .height(Length::Fill)
1413 .center_y(Length::Fill);
1414 match on_event {
1415 Some(on_event) => mouse_area(icon)
1416 .on_press(on_event(Event::Toggle(id)))
1417 .interaction(mouse::Interaction::Pointer)
1418 .into(),
1419 None => icon.into(),
1420 }
1421 } else {
1422 Space::new().width(INDENT).into()
1423 };
1424
1425 let mut content = row![]
1426 .spacing(space::SM)
1427 .align_y(Alignment::Center)
1428 .height(Length::Fill);
1429 if state.mode == Mode::Checkbox {
1430 let check = on_event.map(|on_event| move |checked| on_event(Event::Check(id, checked)));
1431 content = content.push(checkbox(state.check_state(id)).on_toggle_maybe(check));
1432 }
1433 let glyph = if expanded {
1434 node.open_icon.or(node.icon)
1435 } else {
1436 node.icon
1437 };
1438 if let Some(glyph) = glyph {
1439 content = content.push(themed(glyph, ICON_SIZE, opacity(enabled), move |theme| {
1440 colours(theme).icon
1441 }));
1442 }
1443 content = content.push(ellipsis(node.label.as_str()).colour(move |theme| colours(theme).text));
1444 match &node.trailing {
1445 Some(Trailing::Badge(label)) => {
1446 content = content.push(badge(label.as_str()).variant(badge::Variant::Secondary));
1447 }
1448 Some(Trailing::Text(label)) => {
1449 content = content.push(
1450 text(label.as_str())
1451 .size(text_size::XS)
1452 .wrapping(text::Wrapping::None)
1453 .style(move |theme: &Theme| text::Style {
1454 color: Some(Tokens::of(theme).muted_foreground),
1455 }),
1456 );
1457 }
1458 None => {}
1459 }
1460
1461 let body = line_frame(row![indent(depth, guides), chevron, content].height(Length::Fill));
1462 let row = pressable(body, move |theme, hovered| {
1463 let style = row_style(&Tokens::of(theme), Status { hovered, ..status });
1464 Look {
1465 background: style.background,
1466 border: Border {
1467 color: style.ring.unwrap_or(Color::TRANSPARENT),
1468 width: if style.ring.is_some() { 1.0 } else { 0.0 },
1469 radius: radius::SM.into(),
1470 },
1471 divider: None,
1472 }
1473 });
1474 let Some(on_event) = on_event.cloned() else {
1475 return row.into();
1476 };
1477 let activate = on_event(Event::Activate(id));
1478 row.on_press(move |modifiers| on_event(Event::Press(id, modifiers)))
1479 .on_double_click(Some(activate))
1480 .into()
1481}
1482
1483#[cfg(test)]
1484mod tests {
1485 use super::*;
1486 use crate::theme::{dark, light};
1487
1488 fn sample() -> State<u32> {
1499 State::new([
1500 node(1, "src").folder().children([
1501 node(2, "main.rs").file(),
1502 node(3, "lib").folder().children([
1503 node(4, "mod.rs"),
1504 node(5, "old.rs").disabled(true),
1505 node(6, "util.rs"),
1506 ]),
1507 ]),
1508 node(7, "docs").folder().lazy(),
1509 node(8, "readme.md").file(),
1510 ])
1511 }
1512
1513 fn open() -> State<u32> {
1514 sample().with_expanded([1, 3])
1515 }
1516
1517 fn selected(output: Option<Output<u32>>) -> Vec<u32> {
1518 match output {
1519 Some(Output::Selected(ids)) => ids,
1520 other => panic!("expected a selection, got {other:?}"),
1521 }
1522 }
1523
1524 fn checked(output: Option<Output<u32>>) -> Vec<u32> {
1525 match output {
1526 Some(Output::Checked(ids)) => ids,
1527 other => panic!("expected checked nodes, got {other:?}"),
1528 }
1529 }
1530
1531 fn key(key: Key, modifiers: Modifiers) -> keys::Event {
1532 keys::Event { key, modifiers }
1533 }
1534
1535 #[test]
1536 fn builds_the_structure_collapsed() {
1537 let state = sample();
1538 assert_eq!(state.len(), 8);
1539 assert_eq!(state.roots(), &[1, 7, 8]);
1540 assert_eq!(state.children(1), &[2, 3]);
1541 assert_eq!(state.parent(4), Some(3));
1542 assert_eq!(state.parent(1), None);
1543 assert_eq!(state.visible(), vec![1, 7, 8]);
1544 assert!(state.has_children(7) && !state.is_loaded(7));
1545 assert!(!state.has_children(8) && state.is_loaded(8));
1546 assert_eq!(state.mode(), Mode::Single);
1547 assert!(State::<u8>::new([]).is_empty());
1548 }
1549
1550 #[test]
1551 fn with_expanded_skips_leaves_and_lazy_nodes() {
1552 let state = sample().with_expanded([1, 7, 8]);
1553 assert!(state.is_expanded(1));
1554 assert!(!state.is_expanded(7), "lazy nodes load only when asked");
1555 assert!(!state.is_expanded(8));
1556 assert_eq!(state.visible(), vec![1, 2, 3, 7, 8]);
1557 }
1558
1559 #[test]
1560 fn toggle_expands_and_collapses() {
1561 let mut state = sample();
1562 assert!(state.update(Event::Toggle(1)).is_none());
1563 assert_eq!(state.visible(), vec![1, 2, 3, 7, 8]);
1564 let _ = state.update(Event::Toggle(1));
1565 assert_eq!(state.visible(), vec![1, 7, 8]);
1566 let _ = state.update(Event::Expand(8));
1567 assert!(!state.is_expanded(8), "a leaf does not expand");
1568 }
1569
1570 #[test]
1571 fn collapsing_moves_a_hidden_highlight_to_the_parent() {
1572 let mut state = open();
1573 let _ = state.update(Event::Press(4, Modifiers::empty()));
1574 let _ = state.update(Event::Collapse(1));
1575 assert_eq!(state.highlighted(), Some(1));
1576 }
1577
1578 #[test]
1579 fn expanding_a_lazy_node_starts_loading() {
1580 let mut state = sample();
1581 let output = state.update(Event::Expand(7));
1582 assert!(matches!(output, Some(Output::Load(ids)) if ids == [7]));
1583 assert!(state.is_loading(7) && state.is_expanded(7));
1584 assert_eq!(
1585 state.lines(),
1586 vec![
1587 Line::Node { id: 1, depth: 0 },
1588 Line::Node { id: 7, depth: 0 },
1589 Line::Loading { depth: 1 },
1590 Line::Node { id: 8, depth: 0 },
1591 ]
1592 );
1593 assert!(state.update(Event::Expand(7)).is_none(), "already loading");
1594 }
1595
1596 #[test]
1597 fn loaded_children_replace_the_loading_line() {
1598 let mut state = sample();
1599 let _ = state.update(Event::Expand(7));
1600 let _ = state.update(Event::Received(vec![loaded(
1601 7,
1602 [node(70, "guide.md"), node(71, "api").lazy()],
1603 )]));
1604 assert!(!state.is_loading(7));
1605 assert_eq!(state.visible(), vec![1, 7, 70, 71, 8]);
1606 assert_eq!(state.parent(71), Some(7));
1607 assert!(state.has_children(71));
1608 }
1609
1610 #[test]
1611 fn reloading_replaces_old_children_and_forgets_them() {
1612 let mut state = open();
1613 let _ = state.update(Event::Press(4, Modifiers::empty()));
1614 let _ = state.update(Event::Received(vec![loaded(3, [node(9, "new.rs")])]));
1615 assert_eq!(state.children(3), &[9]);
1616 assert!(state.node(4).is_none());
1617 assert_eq!(state.highlighted(), None);
1618 assert!(state.selected().is_empty());
1619 }
1620
1621 #[test]
1622 fn loading_no_children_collapses_the_node() {
1623 let mut state = sample();
1624 let _ = state.update(Event::Expand(7));
1625 let _ = state.update(Event::Received(vec![loaded(7, [])]));
1626 assert!(!state.is_expanded(7) && !state.has_children(7));
1627 assert!(
1628 state
1629 .update(Event::Received(vec![loaded(99, [])]))
1630 .is_none()
1631 );
1632 }
1633
1634 #[test]
1635 fn set_loading_marks_unloaded_nodes_only() {
1636 let mut state = sample();
1637 state.set_loading(7);
1638 assert!(state.is_loading(7));
1639 state.set_loading(1);
1640 assert!(!state.is_loading(1), "loaded children stay");
1641 }
1642
1643 #[test]
1644 fn arrows_move_over_visible_enabled_nodes_without_wrapping() {
1645 let mut state = open();
1646 let _ = state.update(Event::Next);
1647 assert_eq!(state.highlighted(), Some(1));
1648 for _ in 0..4 {
1649 let _ = state.update(Event::Next);
1650 }
1651 assert_eq!(state.highlighted(), Some(6), "skips the disabled node");
1652 let _ = state.update(Event::Last);
1653 assert_eq!(state.highlighted(), Some(8));
1654 let _ = state.update(Event::Next);
1655 assert_eq!(state.highlighted(), Some(8), "no wrap");
1656 let _ = state.update(Event::First);
1657 let _ = state.update(Event::Previous);
1658 assert_eq!(state.highlighted(), Some(1));
1659
1660 let mut fresh = open();
1661 let _ = fresh.update(Event::Previous);
1662 assert_eq!(fresh.highlighted(), Some(8));
1663 }
1664
1665 #[test]
1666 fn right_expands_then_enters_and_left_collapses_then_leaves() {
1667 let mut state = sample();
1668 let _ = state.update(Event::First);
1669 let _ = state.update(Event::ExpandOrEnter);
1670 assert!(state.is_expanded(1));
1671 assert_eq!(state.highlighted(), Some(1));
1672 let _ = state.update(Event::ExpandOrEnter);
1673 assert_eq!(state.highlighted(), Some(2));
1674 let _ = state.update(Event::ExpandOrEnter);
1675 assert_eq!(state.highlighted(), Some(2), "a leaf goes nowhere");
1676 let _ = state.update(Event::CollapseOrLeave);
1677 assert_eq!(state.highlighted(), Some(1));
1678 let _ = state.update(Event::CollapseOrLeave);
1679 assert!(!state.is_expanded(1));
1680 let _ = state.update(Event::CollapseOrLeave);
1681 assert_eq!(state.highlighted(), Some(1), "a root has no parent");
1682 }
1683
1684 #[test]
1685 fn right_on_a_lazy_node_loads_it() {
1686 let mut state = sample();
1687 let _ = state.update(Event::Press(7, Modifiers::empty()));
1688 assert!(matches!(
1689 state.update(Event::ExpandOrEnter),
1690 Some(Output::Load(ids)) if ids == [7]
1691 ));
1692 }
1693
1694 #[test]
1695 fn star_expands_every_sibling_and_loads_lazy_ones() {
1696 let mut state = sample();
1697 let _ = state.update(Event::First);
1698 let output = state.update(Event::ExpandSiblings);
1699 assert!(matches!(output, Some(Output::Load(ids)) if ids == [7]));
1700 assert!(state.is_expanded(1) && state.is_expanded(7));
1701 assert!(!state.is_expanded(8));
1702 }
1703
1704 #[test]
1705 fn typeahead_cycles_through_matching_labels() {
1706 let mut state = open();
1707 let _ = state.update(Event::Typeahead('M'));
1708 assert_eq!(state.highlighted(), Some(2));
1709 let _ = state.update(Event::Typeahead('m'));
1710 assert_eq!(state.highlighted(), Some(4));
1711 let _ = state.update(Event::Typeahead('m'));
1712 assert_eq!(state.highlighted(), Some(2), "wraps round");
1713 let _ = state.update(Event::Typeahead('o'));
1714 assert_eq!(state.highlighted(), Some(2), "disabled old.rs is skipped");
1715 let _ = state.update(Event::Typeahead('z'));
1716 assert_eq!(state.highlighted(), Some(2));
1717 }
1718
1719 #[test]
1720 fn single_selection_follows_presses() {
1721 let mut state = open();
1722 assert_eq!(
1723 selected(state.update(Event::Press(2, Modifiers::empty()))),
1724 [2]
1725 );
1726 assert_eq!(
1727 selected(state.update(Event::Press(4, Modifiers::CTRL))),
1728 [4]
1729 );
1730 assert!(state.update(Event::Press(4, Modifiers::empty())).is_none());
1731 assert!(state.update(Event::Press(5, Modifiers::empty())).is_none());
1732 assert_eq!(state.selected(), [4], "disabled nodes are not selected");
1733 }
1734
1735 #[test]
1736 fn multiple_selection_with_command_and_shift() {
1737 let mut state = open().with_mode(Mode::Multiple);
1738 let _ = state.update(Event::Press(2, Modifiers::empty()));
1739 assert_eq!(
1740 selected(state.update(Event::Press(4, Modifiers::COMMAND))),
1741 [2, 4]
1742 );
1743 assert_eq!(
1744 selected(state.update(Event::Press(2, Modifiers::COMMAND))),
1745 [4]
1746 );
1747 assert_eq!(
1748 selected(state.update(Event::Press(7, Modifiers::SHIFT))),
1749 [2, 3, 4, 6, 7],
1750 "a range from the anchor, skipping the disabled node"
1751 );
1752 let _ = state.update(Event::Press(8, Modifiers::empty()));
1753 let _ = state.update(Event::Press(2, Modifiers::COMMAND));
1754 assert_eq!(
1755 selected(state.update(Event::Press(4, Modifiers::SHIFT | Modifiers::COMMAND))),
1756 [2, 3, 4, 8],
1757 "Ctrl+Shift adds the range"
1758 );
1759 assert_eq!(
1760 selected(state.update(Event::Press(3, Modifiers::empty()))),
1761 [3]
1762 );
1763 }
1764
1765 #[test]
1766 fn shift_arrows_extend_from_the_anchor() {
1767 let mut state = open().with_mode(Mode::Multiple);
1768 let _ = state.update(Event::Press(2, Modifiers::empty()));
1769 assert_eq!(selected(state.update(Event::ExtendNext)), [2, 3]);
1770 assert_eq!(selected(state.update(Event::ExtendNext)), [2, 3, 4]);
1771 assert_eq!(selected(state.update(Event::ExtendPrevious)), [2, 3]);
1772 let _ = state.update(Event::ExtendPrevious);
1773 assert_eq!(selected(state.update(Event::ExtendPrevious)), [1, 2]);
1774 }
1775
1776 #[test]
1777 fn extend_only_moves_outside_a_multiple_selection() {
1778 let mut state = open();
1779 let _ = state.update(Event::Press(2, Modifiers::empty()));
1780 assert!(state.update(Event::ExtendNext).is_none());
1781 assert_eq!(state.highlighted(), Some(3));
1782 assert_eq!(state.selected(), [2]);
1783 }
1784
1785 #[test]
1786 fn space_selects_by_mode() {
1787 let mut single = open();
1788 let _ = single.update(Event::First);
1789 assert_eq!(selected(single.update(Event::ToggleHighlighted)), [1]);
1790
1791 let mut multiple = open().with_mode(Mode::Multiple);
1792 let _ = multiple.update(Event::First);
1793 let _ = multiple.update(Event::ToggleHighlighted);
1794 let _ = multiple.update(Event::Next);
1795 assert_eq!(selected(multiple.update(Event::ToggleHighlighted)), [1, 2]);
1796 assert_eq!(selected(multiple.update(Event::ToggleHighlighted)), [1]);
1797
1798 let mut none = open().with_mode(Mode::None);
1799 let _ = none.update(Event::First);
1800 assert!(none.update(Event::ToggleHighlighted).is_none());
1801 assert!(none.update(Event::Press(2, Modifiers::empty())).is_none());
1802 assert_eq!(none.highlighted(), Some(2));
1803 }
1804
1805 #[test]
1806 fn select_all_takes_every_visible_enabled_node() {
1807 let mut state = open().with_mode(Mode::Multiple);
1808 assert_eq!(
1809 selected(state.update(Event::SelectAll)),
1810 [1, 2, 3, 4, 6, 7, 8]
1811 );
1812 assert!(open().update(Event::SelectAll).is_none());
1813 }
1814
1815 #[test]
1816 fn activation_reports_the_node() {
1817 let mut state = open();
1818 assert!(state.update(Event::ActivateHighlighted).is_none());
1819 assert!(matches!(
1820 state.update(Event::Activate(2)),
1821 Some(Output::Activated(2))
1822 ));
1823 assert_eq!(state.highlighted(), Some(2));
1824 assert!(matches!(
1825 state.update(Event::ActivateHighlighted),
1826 Some(Output::Activated(2))
1827 ));
1828 assert!(state.update(Event::Activate(5)).is_none());
1829 }
1830
1831 #[test]
1832 fn checking_a_parent_checks_its_enabled_children() {
1833 let mut state = open().with_mode(Mode::Checkbox);
1834 assert_eq!(checked(state.update(Event::Check(3, true))), [3, 4, 6]);
1835 assert_eq!(state.check_state(3), CheckState::Checked);
1836 assert_eq!(
1837 state.check_state(5),
1838 CheckState::Unchecked,
1839 "disabled stays"
1840 );
1841 assert_eq!(state.check_state(1), CheckState::Indeterminate);
1842
1843 assert_eq!(
1844 checked(state.update(Event::Check(2, true))),
1845 [1, 2, 3, 4, 6]
1846 );
1847 assert_eq!(state.check_state(1), CheckState::Checked);
1848
1849 assert_eq!(checked(state.update(Event::Check(4, false))), [2, 6]);
1850 assert_eq!(state.check_state(3), CheckState::Indeterminate);
1851 assert_eq!(state.check_state(1), CheckState::Indeterminate);
1852
1853 assert_eq!(
1854 checked(state.update(Event::Check(1, false))),
1855 Vec::<u32>::new()
1856 );
1857 assert_eq!(state.check_state(3), CheckState::Unchecked);
1858 }
1859
1860 #[test]
1861 fn an_indeterminate_parent_checks_everything_on_the_next_click() {
1862 let mut state = open().with_mode(Mode::Checkbox);
1863 let _ = state.update(Event::Check(4, true));
1864 assert_eq!(state.check_state(3), CheckState::Indeterminate);
1865 let next = state.check_state(3).toggled().is_checked();
1866 assert_eq!(checked(state.update(Event::Check(3, next))), [3, 4, 6]);
1867 }
1868
1869 #[test]
1870 fn presses_and_space_toggle_checks_in_checkbox_mode() {
1871 let mut state = open().with_mode(Mode::Checkbox);
1872 assert_eq!(
1873 checked(state.update(Event::Press(6, Modifiers::empty()))),
1874 [6]
1875 );
1876 assert_eq!(state.check_state(3), CheckState::Indeterminate);
1877 assert_eq!(
1878 checked(state.update(Event::ToggleHighlighted)),
1879 Vec::<u32>::new()
1880 );
1881 assert!(state.update(Event::Check(5, true)).is_none(), "disabled");
1882 assert!(state.selected().is_empty(), "checkbox mode never selects");
1883 assert!(
1884 open().update(Event::Check(2, true)).is_none(),
1885 "other modes"
1886 );
1887 }
1888
1889 #[test]
1890 fn children_loading_under_a_checked_parent_arrive_checked() {
1891 let mut state = sample().with_mode(Mode::Checkbox);
1892 let _ = state.update(Event::Check(7, true));
1893 let _ = state.update(Event::Expand(7));
1894 let output = state.update(Event::Received(vec![loaded(
1895 7,
1896 [node(70, "a"), node(71, "b").disabled(true)],
1897 )]));
1898 assert_eq!(checked(output), [7, 70]);
1899 assert_eq!(state.check_state(7), CheckState::Checked);
1900 }
1901
1902 #[test]
1903 fn replacing_partially_checked_children_refreshes_ancestors_and_reports_checks() {
1904 let mut state = State::new([node(1, "Root").children([
1905 node(2, "Folder").children([node(3, "Old"), node(4, "Other")]),
1906 node(5, "Sibling"),
1907 ])])
1908 .with_mode(Mode::Checkbox);
1909 let _ = state.update(Event::Check(3, true));
1910 assert_eq!(state.check_state(1), CheckState::Indeterminate);
1911 assert_eq!(state.check_state(2), CheckState::Indeterminate);
1912
1913 let output = state.update(Event::Received(vec![loaded(2, [node(6, "New")])]));
1914
1915 assert_eq!(state.check_state(1), CheckState::Unchecked);
1916 assert_eq!(state.check_state(2), CheckState::Unchecked);
1917 assert_eq!(checked(output), Vec::<u32>::new());
1918 }
1919
1920 #[test]
1921 fn replacing_partially_checked_children_with_an_empty_list_clears_the_partial_mark() {
1922 let mut state =
1923 State::new([node(1, "Folder").children([node(2, "Old"), node(3, "Other")])])
1924 .with_mode(Mode::Checkbox);
1925 let _ = state.update(Event::Check(2, true));
1926
1927 let output = state.update(Event::Received(vec![loaded(1, [])]));
1928
1929 assert_eq!(state.check_state(1), CheckState::Unchecked);
1930 assert_eq!(checked(output), Vec::<u32>::new());
1931 }
1932
1933 #[test]
1934 fn reloading_an_unchanged_checked_leaf_emits_no_checkbox_change() {
1935 let mut state = State::new([node(1, "Folder").lazy()]).with_mode(Mode::Checkbox);
1936 let _ = state.update(Event::Check(1, true));
1937 let output = state.update(Event::Received(vec![loaded(1, [])]));
1938
1939 assert_eq!(state.check_state(1), CheckState::Checked);
1940 assert!(output.is_none());
1941 }
1942
1943 #[test]
1944 fn disabled_nodes_ignore_everything() {
1945 let mut state = open();
1946 let _ = state.update(Event::Press(5, Modifiers::empty()));
1947 assert_eq!(state.highlighted(), None);
1948 let disabled = State::new([node(1, "a").disabled(true).children([node(2, "b")])]);
1949 let mut disabled = disabled;
1950 assert!(disabled.update(Event::Toggle(1)).is_none());
1951 assert!(!disabled.is_expanded(1));
1952 }
1953
1954 #[test]
1955 fn focus_is_tracked_and_lands_on_a_node() {
1956 let mut state = sample();
1957 let _ = state.update(Event::Focus(true));
1958 assert!(state.is_focused());
1959 assert_eq!(state.highlighted(), Some(1), "the first node");
1960 let _ = state.update(Event::Focus(false));
1961 assert!(!state.is_focused());
1962 assert_eq!(state.highlighted(), Some(1), "the highlight stays");
1963
1964 let mut selected = open();
1965 let _ = selected.update(Event::Press(6, Modifiers::empty()));
1966 let _ = selected.update(Event::Collapse(3));
1967 let _ = selected.update(Event::Focus(true));
1968 assert_eq!(
1969 selected.highlighted(),
1970 Some(3),
1971 "an existing highlight is kept"
1972 );
1973
1974 let mut fresh = open().with_mode(Mode::Multiple);
1975 let _ = fresh.update(Event::SelectAll);
1976 let _ = fresh.update(Event::Focus(true));
1977 assert_eq!(fresh.highlighted(), Some(1), "the first selected node");
1978 }
1979
1980 fn press(keymap: &Keymap<Action>, chord: &str) -> Option<Action> {
1981 let chord: Chord = chord.parse().unwrap();
1982 keymap.resolve(chord.key(), chord.modifiers())
1983 }
1984
1985 #[test]
1986 fn default_keymap_covers_every_action() {
1987 let keymap = default_keymap();
1988 assert_eq!(press(&keymap, "ArrowDown"), Some(Action::Next));
1989 assert_eq!(press(&keymap, "ArrowUp"), Some(Action::Previous));
1990 assert_eq!(press(&keymap, "Home"), Some(Action::First));
1991 assert_eq!(press(&keymap, "End"), Some(Action::Last));
1992 assert_eq!(press(&keymap, "ArrowRight"), Some(Action::Expand));
1993 assert_eq!(press(&keymap, "ArrowLeft"), Some(Action::Collapse));
1994 assert_eq!(press(&keymap, "Enter"), Some(Action::Activate));
1995 assert_eq!(press(&keymap, "Space"), Some(Action::Select));
1996 assert_eq!(press(&keymap, "*"), Some(Action::ExpandSiblings));
1997 assert_eq!(press(&keymap, "Shift+ArrowDown"), Some(Action::ExtendNext));
1998 assert_eq!(
1999 press(&keymap, "Shift+ArrowUp"),
2000 Some(Action::ExtendPrevious)
2001 );
2002 assert_eq!(press(&keymap, "Mod+A"), Some(Action::SelectAll));
2003 for action in <Action as keys::Action>::ALL {
2004 assert!(!keymap.chords(action).is_empty(), "{action:?}");
2005 assert!(keys::Action::description(*action).ends_with('.'));
2006 }
2007 assert_eq!(Keymap::<Action>::defaults(), keymap);
2008 }
2009
2010 #[test]
2011 fn keymap_overrides_and_unbinding() {
2012 let keymap = default_keymap()
2013 .unbind(&Chord::character('*'))
2014 .bind(Chord::character('l'), Action::Expand);
2015 let state = open();
2016 assert!(
2017 state
2018 .key_event(&keymap, &key(Key::Character("*".into()), Modifiers::SHIFT))
2019 .is_none()
2020 );
2021 let mut state = state;
2022 let _ = state.update(Event::First);
2023 assert!(matches!(
2024 state.key_event(
2025 &keymap,
2026 &key(Key::Character("l".into()), Modifiers::empty())
2027 ),
2028 Some(Event::ExpandOrEnter)
2029 ));
2030 assert!(default_keymap().clear().is_empty());
2031 }
2032
2033 #[test]
2034 fn key_events_use_the_keymap_then_typeahead() {
2035 let keymap = default_keymap();
2036 let mut state = open();
2037 let _ = state.update(Event::First);
2038 let star = key(Key::Character("*".into()), Modifiers::SHIFT);
2039 assert!(matches!(
2040 state.key_event(&keymap, &star),
2041 Some(Event::ExpandSiblings)
2042 ));
2043 let letter = key(Key::Character("r".into()), Modifiers::empty());
2044 assert!(matches!(
2045 state.key_event(&keymap, &letter),
2046 Some(Event::Typeahead('r'))
2047 ));
2048 let ctrl_letter = key(Key::Character("r".into()), Modifiers::CTRL);
2049 assert!(state.key_event(&keymap, &ctrl_letter).is_none());
2050 let shifted_letter = key(Key::Character("R".into()), Modifiers::SHIFT);
2051 assert!(matches!(
2052 state.key_event(&keymap, &shifted_letter),
2053 Some(Event::Typeahead('R'))
2054 ));
2055 }
2056
2057 #[test]
2058 fn actions_map_to_events_only_when_they_apply() {
2059 let mut state = open();
2060 assert!(Action::Next.event(&state).is_some());
2061 assert!(
2062 Action::Expand.event(&state).is_none(),
2063 "nothing highlighted"
2064 );
2065 assert!(Action::Activate.event(&state).is_none());
2066 assert!(
2067 Action::SelectAll.event(&state).is_none(),
2068 "single selection"
2069 );
2070 let _ = state.update(Event::First);
2071 assert!(matches!(
2072 Action::Expand.event(&state),
2073 Some(Event::ExpandOrEnter)
2074 ));
2075 assert!(matches!(
2076 Action::Collapse.event(&state),
2077 Some(Event::CollapseOrLeave)
2078 ));
2079 assert!(matches!(
2080 Action::Select.event(&state),
2081 Some(Event::ToggleHighlighted)
2082 ));
2083 assert!(matches!(
2084 Action::ExtendNext.event(&state),
2085 Some(Event::Next)
2086 ));
2087 assert!(matches!(
2088 Action::ExtendPrevious.event(&state),
2089 Some(Event::Previous)
2090 ));
2091
2092 let multiple = open().with_mode(Mode::Multiple);
2093 assert!(matches!(
2094 Action::SelectAll.event(&multiple),
2095 Some(Event::SelectAll)
2096 ));
2097 assert!(matches!(
2098 Action::ExtendNext.event(&multiple),
2099 Some(Event::ExtendNext)
2100 ));
2101
2102 let mut none = open().with_mode(Mode::None);
2103 let _ = none.update(Event::First);
2104 assert!(Action::Select.event(&none).is_none());
2105 }
2106
2107 #[test]
2108 fn stream_emits_ready_first_then_batches() {
2109 use iced::futures::SinkExt;
2110 use iced::futures::executor::block_on;
2111
2112 let mut events = Box::pin(stream::<u32>());
2113 let Some(Event::Ready(mut sender)) = block_on(events.next()) else {
2114 panic!("first event must be Ready");
2115 };
2116 block_on(async {
2117 for parent in [1, 2, 3] {
2118 sender.send(loaded(parent, [])).await.ok();
2119 }
2120 });
2121 let Some(Event::Received(batch)) = block_on(events.next()) else {
2122 panic!("expected a batch");
2123 };
2124 let parents: Vec<_> = batch.iter().map(|loaded| loaded.parent).collect();
2125 assert_eq!(parents, [1, 2, 3]);
2126 }
2127
2128 #[test]
2129 fn ready_hands_the_sender_back() {
2130 let (sender, _receiver) = mpsc::channel(1);
2131 assert!(matches!(
2132 sample().update(Event::Ready(sender)),
2133 Some(Output::Ready(_))
2134 ));
2135 }
2136
2137 #[test]
2138 fn rows_follow_the_menu_look_in_both_themes() {
2139 for theme in [light(), dark()] {
2140 let tokens = Tokens::of(&theme);
2141 let idle = row_style(&tokens, Status::default());
2142 assert_eq!(idle.background, None);
2143 assert_eq!(idle.text, tokens.foreground);
2144 assert_eq!(idle.ring, None);
2145
2146 let selected = row_style(
2147 &tokens,
2148 Status {
2149 selected: true,
2150 ..Status::default()
2151 },
2152 );
2153 assert_eq!(selected.background, Some(tokens.accent));
2154 assert_eq!(selected.text, tokens.accent_foreground);
2155
2156 let hovered = row_style(
2157 &tokens,
2158 Status {
2159 hovered: true,
2160 ..Status::default()
2161 },
2162 );
2163 assert!(
2164 hovered
2165 .background
2166 .is_some_and(|colour| colour.a < tokens.accent.a)
2167 );
2168
2169 let highlighted = row_style(
2170 &tokens,
2171 Status {
2172 highlighted: true,
2173 ..Status::default()
2174 },
2175 );
2176 assert_eq!(highlighted.ring, Some(tokens.ring));
2177
2178 let disabled = row_style(
2179 &tokens,
2180 Status {
2181 disabled: true,
2182 hovered: true,
2183 highlighted: true,
2184 selected: true,
2185 },
2186 );
2187 assert_eq!(disabled.background, None);
2188 assert_eq!(disabled.text, tokens.muted_foreground);
2189 assert_eq!(disabled.ring, None);
2190 assert_eq!(guide_colour(&tokens), tokens.border);
2191 }
2192 }
2193
2194 #[test]
2195 fn builder_defaults_and_options() {
2196 let state = sample();
2197 let view: Tree<'_, u32, ()> = tree(&state);
2198 assert!(!view.guides && !view.is_enabled());
2199 assert_eq!((view.width, view.height), (Length::Fill, Length::Shrink));
2200 assert_eq!(view.keymap, default_keymap());
2201 let view = view
2202 .guides(true)
2203 .width(240)
2204 .height(300)
2205 .keymap(Keymap::new())
2206 .on_event(|_| ());
2207 assert!(view.guides && view.is_enabled());
2208 assert_eq!(view.width, Length::Fixed(240.0));
2209 assert!(view.keymap.is_empty());
2210 }
2211
2212 #[test]
2213 fn node_builders() {
2214 let folder = node(1, "src").folder().badge("3");
2215 assert_eq!(folder.icon, Some(crate::lucide!(Folder)));
2216 assert_eq!(folder.open_icon, Some(crate::lucide!(FolderOpen)));
2217 assert_eq!(folder.trailing, Some(Trailing::Badge("3".into())));
2218 let file = node(2, "a.txt").file().trailing("4 KB").disabled(true);
2219 assert_eq!(file.icon, Some(crate::lucide!(File)));
2220 assert_eq!(file.trailing, Some(Trailing::Text("4 KB".into())));
2221 assert!(file.disabled);
2222 assert_eq!(Mode::ALL.len(), 4);
2223 }
2224}