Skip to main content

iced_cube/data/
tree.rs

1//! Hierarchical items that expand and collapse, such as folders and files.
2//!
3//! [`State`] holds the nodes keyed by id, which are expanded, and the
4//! selection. Its [`update`](State::update) handles expanding, pointer and
5//! keyboard selection in the chosen [`Mode`], checkboxes with tri-state
6//! parents, and children that load later. The view only builds the rows of
7//! expanded nodes that are in view, so a tree of thousands of nodes stays
8//! quick.
9//!
10//! A node made with [`Node::lazy`] has children that are not loaded yet.
11//! Expanding it marks it loading and returns [`Output::Load`]; the app
12//! fetches the children and sends them back through the channel that
13//! [`subscription`] owns, as [`Loaded`] batches.
14//!
15//! The tree resolves its keys through a [`Keymap`] of [`Action`]s while it
16//! has focus, which it takes when pressed; see [`default_keymap`]. Letters
17//! and digits jump to the next node starting with them.
18
19use 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
42/// Height of every row, as in a menu.
43pub const ROW_HEIGHT: f32 = 32.0;
44/// Width of one level of indentation, which is also the chevron's slot.
45pub const INDENT: f32 = 20.0;
46/// How many loaded batches a producer can send before it has to wait.
47pub const CHANNEL_CAPACITY: usize = 64;
48/// The most batches delivered in one [`Event::Received`].
49pub const BATCH_SIZE: usize = 32;
50
51/// How nodes are selected.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub enum Mode {
54    /// Nodes can be highlighted and activated, but not selected.
55    None,
56    /// One node at a time.
57    #[default]
58    Single,
59    /// Ctrl or Cmd click toggles a node, Shift click selects a range.
60    Multiple,
61    /// Every node has a checkbox. Checking a parent checks its children,
62    /// and a parent shows whether all, some or none of them are checked.
63    Checkbox,
64}
65
66impl Mode {
67    pub const ALL: [Mode; 4] = [Mode::None, Mode::Single, Mode::Multiple, Mode::Checkbox];
68}
69
70/// What sits at the end of a row.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum Trailing {
73    /// A small secondary badge, such as a count.
74    Badge(String),
75    /// Muted text, such as a size or a date.
76    Text(String),
77}
78
79#[derive(Debug, Clone, PartialEq)]
80enum Kids<Id> {
81    None,
82    Nodes(Vec<Node<Id>>),
83    Lazy,
84}
85
86/// One node: its label and look. Its children are given with
87/// [`children`](Node::children), or later with [`Node::lazy`].
88#[derive(Debug, Clone, PartialEq)]
89pub struct Node<Id> {
90    pub id: Id,
91    pub label: String,
92    pub icon: Option<Glyph>,
93    /// Shown in place of `icon` while the node is expanded.
94    pub open_icon: Option<Glyph>,
95    pub trailing: Option<Trailing>,
96    pub disabled: bool,
97    kids: Kids<Id>,
98}
99
100/// Creates a leaf node.
101pub 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/// Children that arrived for a lazy node.
114#[derive(Debug, Clone, PartialEq)]
115pub struct Loaded<Id> {
116    pub parent: Id,
117    pub children: Vec<Node<Id>>,
118}
119
120/// The children of `parent`, to send through the channel.
121pub 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    /// Icons for the collapsed and the expanded node.
135    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    /// A closed folder icon that opens while expanded.
142    pub fn folder(self) -> Self {
143        self.icons(crate::lucide!(Folder), crate::lucide!(FolderOpen))
144    }
145
146    /// A file icon.
147    pub fn file(self) -> Self {
148        self.icon(crate::lucide!(File))
149    }
150
151    /// A small badge at the end of the row.
152    pub fn badge(mut self, label: impl Into<String>) -> Self {
153        self.trailing = Some(Trailing::Badge(label.into()));
154        self
155    }
156
157    /// Muted text at the end of the row.
158    pub fn trailing(mut self, label: impl Into<String>) -> Self {
159        self.trailing = Some(Trailing::Text(label.into()));
160        self
161    }
162
163    /// A disabled node cannot be highlighted, selected, checked, activated
164    /// or expanded.
165    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    /// Children that load when the node is first expanded.
176    pub fn lazy(mut self) -> Self {
177        self.kids = Kids::Lazy;
178        self
179    }
180}
181
182/// Where a node's children are.
183#[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/// One line of the rendered tree.
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200enum Line<Id> {
201    Node {
202        id: Id,
203        depth: usize,
204    },
205    /// Children on their way for the node above.
206    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
217/// The sending half of the channel lazy children arrive on. Clone it for
218/// each producer.
219pub type Sender<Id> = mpsc::Sender<Loaded<Id>>;
220
221/// Everything that changes a tree.
222#[derive(Debug, Clone)]
223pub enum Event<Id> {
224    /// The channel is open. Keep the sender and hand clones to producers.
225    Ready(Sender<Id>),
226    /// Children from the channel, oldest first.
227    Received(Vec<Loaded<Id>>),
228    /// Expands or collapses a node, as its chevron does.
229    Toggle(Id),
230    Expand(Id),
231    Collapse(Id),
232    /// The pointer pressed a row with these modifiers held.
233    Press(Id, Modifiers),
234    /// A row was double clicked.
235    Activate(Id),
236    /// A checkbox changed, in [`Mode::Checkbox`].
237    Check(Id, bool),
238    Next,
239    Previous,
240    First,
241    Last,
242    /// Moves the highlight and extends the selection to it, in
243    /// [`Mode::Multiple`].
244    ExtendNext,
245    ExtendPrevious,
246    /// Expands the highlighted node, or moves to its first child when it
247    /// is already expanded.
248    ExpandOrEnter,
249    /// Collapses the highlighted node, or moves to its parent when it is
250    /// already collapsed.
251    CollapseOrLeave,
252    /// Expands the highlighted node and every sibling that has children.
253    ExpandSiblings,
254    /// Selects, toggles or checks the highlighted node, by [`Mode`].
255    ToggleHighlighted,
256    ActivateHighlighted,
257    /// Selects every visible node, in [`Mode::Multiple`].
258    SelectAll,
259    /// Highlights the next visible node whose label starts with this
260    /// character.
261    Typeahead(char),
262    /// The tree gained or lost focus. The widget sends this itself.
263    Focus(bool),
264}
265
266/// What the app may need to act on after an [`Event`].
267#[derive(Debug, Clone)]
268pub enum Output<Id> {
269    Ready(Sender<Id>),
270    /// These lazy nodes were expanded and now show as loading. Fetch their
271    /// children and send them through the channel.
272    Load(Vec<Id>),
273    /// A node was activated with Enter or a double click.
274    Activated(Id),
275    /// The selection changed. Holds every selected node, in tree order.
276    Selected(Vec<Id>),
277    /// Checkboxes changed. Holds every checked node, in tree order.
278    Checked(Vec<Id>),
279}
280
281/// The nodes, their structure and what is expanded, selected and checked.
282#[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    /// Parents with some, but not all, of their children checked.
291    partial: HashSet<Id>,
292    highlighted: Option<Id>,
293    /// Where a Shift click or Shift arrow range starts.
294    anchor: Option<Id>,
295    focused: bool,
296}
297
298impl<Id: Copy + Eq + Hash> State<Id> {
299    /// A tree in [`Mode::Single`] with every node collapsed. Ids must be
300    /// unique across the whole tree.
301    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    /// Starts with these nodes expanded. Lazy nodes stay collapsed until
327    /// the user expands them, so nothing loads unasked.
328    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    /// The loaded children of a node, empty for a leaf or a node whose
354    /// children have not arrived.
355    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    /// Whether a node has children, or may have once they load.
363    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    /// Whether a node's children are known: a leaf, or loaded.
372    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    /// Marks a node as loading, such as while the app refreshes it. Its
387    /// children stay until new ones arrive.
388    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    /// The node the keyboard acts on.
409    pub fn highlighted(&self) -> Option<Id> {
410        self.highlighted
411    }
412
413    /// The selected nodes, in tree order.
414    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    /// The checked nodes, parents included, in tree order.
423    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    /// Whether a node's checkbox is checked, unchecked, or indeterminate
432    /// because only some of its children are checked.
433    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    /// The nodes on screen when scrolled through: roots and the children
444    /// of expanded nodes, in tree order.
445    pub fn visible(&self) -> Vec<Id> {
446        self.walk(true).into_iter().map(|(id, _)| id).collect()
447    }
448
449    /// How many nodes the tree holds.
450    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    /// Applies an event and returns what the app may need to act on.
459    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                // Focus lands on the first selected node, or the first node,
497                // so the keyboard has somewhere to start.
498                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    /// Turns a key press into an event: first through `keymap`, then as
512    /// typeahead for a letter or digit. A symbol typed with Shift, such as
513    /// `*`, also matches its chord without Shift.
514    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    /// Nodes in tree order with their depth: every loaded node, or only
557    /// those under expanded parents.
558    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    /// The rendered lines: visible nodes, and a loading line under each
577    /// expanded node whose children are on their way.
578    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    /// Visible nodes that can take the highlight.
590    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        // A highlight inside the collapsed branch moves up to it.
637        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    /// Enabled visible nodes from `a` to `b`, in either order.
740    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    /// Checks or unchecks a node and its loaded descendants, leaving
821    /// disabled descendants as they are.
822    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(&current);
829            if checked {
830                let _ = self.checked.insert(current);
831            } else {
832                let _ = self.checked.remove(&current);
833            }
834            stack.extend(self.children(current).iter().copied());
835        }
836    }
837
838    /// Recomputes `start` and each node above it from its enabled children:
839    /// checked when all are, indeterminate when some are.
840    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    /// Attaches children that arrived, replacing any the node had. Returns
872    /// whether any checkbox changed.
873    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    /// Forgets a node and everything under it.
908    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
934/// The character a key press types into the typeahead: a letter or digit
935/// pressed without Ctrl, Alt or the logo key.
936fn 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/// What a tree keyboard shortcut does.
953#[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    /// The [`Event`] this action sends to `state`, or `None` when it does
971    /// nothing, so the key is left for others.
972    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
1057/// The default tree shortcuts:
1058///
1059/// | Keys | Action |
1060/// | --- | --- |
1061/// | `ArrowDown` | [`Action::Next`] |
1062/// | `ArrowUp` | [`Action::Previous`] |
1063/// | `Home` | [`Action::First`] |
1064/// | `End` | [`Action::Last`] |
1065/// | `ArrowRight` | [`Action::Expand`] |
1066/// | `ArrowLeft` | [`Action::Collapse`] |
1067/// | `Enter` | [`Action::Activate`] |
1068/// | `Space` | [`Action::Select`] |
1069/// | `*` | [`Action::ExpandSiblings`] |
1070/// | `Shift+ArrowDown` | [`Action::ExtendNext`] |
1071/// | `Shift+ArrowUp` | [`Action::ExtendPrevious`] |
1072/// | `Mod+A` (Ctrl, or Cmd on macOS) | [`Action::SelectAll`] |
1073///
1074/// Letters and digits jump to the next node starting with them. The tree
1075/// resolves these itself, only while it has focus, so they never clash
1076/// with other components. Pass a changed keymap with [`Tree::keymap`].
1077pub 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
1093/// Owns the channel lazy children arrive on. Emits [`Event::Ready`] first,
1094/// then batches.
1095pub fn subscription<Id: Send + 'static>() -> Subscription<Event<Id>> {
1096    Subscription::run(stream::<Id>)
1097}
1098
1099/// The stream behind [`subscription`], for driving it without a runtime.
1100pub 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/// How a row is shown.
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1108pub struct Status {
1109    pub selected: bool,
1110    pub hovered: bool,
1111    /// Carries the keyboard highlight while the tree has focus.
1112    pub highlighted: bool,
1113    pub disabled: bool,
1114}
1115
1116/// The colours of a row.
1117#[derive(Debug, Clone, Copy, PartialEq)]
1118pub struct RowStyle {
1119    pub background: Option<Color>,
1120    pub text: Color,
1121    pub icon: Color,
1122    /// The ring around the row carrying the keyboard highlight.
1123    pub ring: Option<Color>,
1124}
1125
1126/// Resolves a row's colours from the shared menu rows: a selected row is
1127/// drawn as a highlighted menu row, and a hovered one with a lighter tint.
1128pub 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
1144/// The colour of the indentation guides.
1145pub 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
1153/// A tree builder. Convert it into an [`Element`] to render.
1154pub 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
1165/// Renders `state`. Without [`on_event`](Tree::on_event) every row renders
1166/// disabled.
1167pub 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    /// Draws a vertical line at each level of indentation.
1194    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    /// A fixed or filling height scrolls the rows. Defaults to shrinking to
1205    /// fit them.
1206    pub fn height(mut self, height: impl Into<Length>) -> Self {
1207        self.height = height.into();
1208        self
1209    }
1210
1211    /// Replaces the [`default_keymap`] the focused tree resolves keys with.
1212    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    /// Gives every node the context menu of `menu`, keyed by node id. It
1223    /// opens on a right-click or a long press, and on the highlighted node
1224    /// with its opening chord while the tree has focus.
1225    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    /// Replaces the context menu's default keymap.
1242    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
1320/// The indentation for `depth` levels, with a guide at each when asked.
1321fn 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    /// ```text
1489    /// 1 src
1490    ///   2 main.rs
1491    ///   3 lib
1492    ///     4 mod.rs
1493    ///     5 old.rs (disabled)
1494    ///     6 util.rs
1495    /// 7 docs (lazy)
1496    /// 8 readme.md
1497    /// ```
1498    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}