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, ringed, 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            let menu_target = menu.as_ref().and_then(|menu| menu.target());
1282            move |range: Range<usize>| -> Element<'a, Message> {
1283                let lines = lines.get(range).unwrap_or_default();
1284                column(lines.iter().map(|line| match *line {
1285                    Line::Node { id, depth } => {
1286                        let ringed = ringed(
1287                            menu_target.as_ref(),
1288                            &id,
1289                            state.focused && state.highlighted == Some(id),
1290                        );
1291                        let row = node_view(state, id, depth, guides, ringed, on_event.as_ref());
1292                        match &menu {
1293                            Some(menu) if state.is_enabled(id) => menu.wrap(id, row),
1294                            _ => row,
1295                        }
1296                    }
1297                    Line::Loading { depth } => loading_view(depth, guides),
1298                }))
1299                .into()
1300            }
1301        };
1302        let list = container(
1303            rows(count, ROW_HEIGHT, build)
1304                .highlight(highlight)
1305                .height(height),
1306        )
1307        .width(width);
1308
1309        let Some(on_event) = on_event else {
1310            return list.into();
1311        };
1312        let on_focus = on_event.clone();
1313        scope(list)
1314            .on_focus(move |focused| on_focus(Event::Focus(focused)))
1315            .on_key(move |key| {
1316                let opened = menu
1317                    .as_ref()
1318                    .zip(state.highlighted())
1319                    .and_then(|(menu, target)| menu.key(key, target));
1320                opened.or_else(|| state.key_event(&keymap, key).map(&*on_event))
1321            })
1322            .into()
1323    }
1324}
1325
1326/// The indentation for `depth` levels, with a guide at each when asked.
1327fn indent<'a, Message: 'a>(depth: usize, guides: bool) -> Element<'a, Message> {
1328    let levels =
1329        (0..depth).map(|_| {
1330            let guide: Element<'a, Message> =
1331                if guides {
1332                    container(container(Space::new()).width(1).height(Length::Fill).style(
1333                        |theme| container::Style {
1334                            background: Some(Background::Color(guide_colour(&Tokens::of(theme)))),
1335                            ..container::Style::default()
1336                        },
1337                    ))
1338                    .padding(iced::Padding::ZERO.left(ICON_SIZE / 2.0))
1339                    .into()
1340                } else {
1341                    Space::new().into()
1342                };
1343            container(guide).width(INDENT).height(Length::Fill).into()
1344        });
1345    row(levels).height(Length::Fill).into()
1346}
1347
1348fn line_frame<'a, Message: 'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
1349    container(content)
1350        .padding(
1351            iced::Padding::ZERO
1352                .left(ROW_PADDING.left)
1353                .right(ROW_PADDING.right),
1354        )
1355        .width(Length::Fill)
1356        .height(ROW_HEIGHT)
1357        .into()
1358}
1359
1360fn loading_view<'a, Message: 'a>(depth: usize, guides: bool) -> Element<'a, Message> {
1361    let muted = |theme: &Theme| Tokens::of(theme).muted_foreground;
1362    line_frame(
1363        row![
1364            indent(depth, guides),
1365            Space::new().width(INDENT),
1366            row![
1367                themed(crate::lucide!(LoaderCircle), ICON_SIZE, 1.0, muted),
1368                text("Loading...")
1369                    .size(text_size::SM)
1370                    .wrapping(text::Wrapping::None)
1371                    .style(move |theme| text::Style {
1372                        color: Some(muted(theme)),
1373                    }),
1374            ]
1375            .spacing(space::SM)
1376            .align_y(Alignment::Center)
1377            .height(Length::Fill),
1378        ]
1379        .height(Length::Fill),
1380    )
1381}
1382
1383fn node_view<'a, Id, Message>(
1384    state: &'a State<Id>,
1385    id: Id,
1386    depth: usize,
1387    guides: bool,
1388    ringed: bool,
1389    on_event: Option<&Rc<OnEvent<'a, Id, Message>>>,
1390) -> Element<'a, Message>
1391where
1392    Id: Copy + Eq + Hash + 'a,
1393    Message: Clone + 'a,
1394{
1395    let Some(node) = state.node(id) else {
1396        return Space::new().into();
1397    };
1398    let on_event = on_event.filter(|_| !node.disabled);
1399    let enabled = on_event.is_some();
1400    let expanded = state.is_expanded(id);
1401    let status = Status {
1402        selected: state.mode != Mode::Checkbox && state.is_selected(id),
1403        hovered: false,
1404        highlighted: ringed,
1405        disabled: !enabled,
1406    };
1407    let colours = move |theme: &Theme| row_style(&Tokens::of(theme), status);
1408
1409    let chevron: Element<'a, Message> = if state.has_children(id) {
1410        let glyph = if expanded {
1411            crate::lucide!(ChevronDown)
1412        } else {
1413            crate::lucide!(ChevronRight)
1414        };
1415        let icon = container(themed(glyph, ICON_SIZE, opacity(enabled), move |theme| {
1416            colours(theme).icon
1417        }))
1418        .width(INDENT)
1419        .height(Length::Fill)
1420        .center_y(Length::Fill);
1421        match on_event {
1422            Some(on_event) => mouse_area(icon)
1423                .on_press(on_event(Event::Toggle(id)))
1424                .interaction(mouse::Interaction::Pointer)
1425                .into(),
1426            None => icon.into(),
1427        }
1428    } else {
1429        Space::new().width(INDENT).into()
1430    };
1431
1432    let mut content = row![]
1433        .spacing(space::SM)
1434        .align_y(Alignment::Center)
1435        .height(Length::Fill);
1436    if state.mode == Mode::Checkbox {
1437        let check = on_event.map(|on_event| move |checked| on_event(Event::Check(id, checked)));
1438        content = content.push(checkbox(state.check_state(id)).on_toggle_maybe(check));
1439    }
1440    let glyph = if expanded {
1441        node.open_icon.or(node.icon)
1442    } else {
1443        node.icon
1444    };
1445    if let Some(glyph) = glyph {
1446        content = content.push(themed(glyph, ICON_SIZE, opacity(enabled), move |theme| {
1447            colours(theme).icon
1448        }));
1449    }
1450    content = content.push(ellipsis(node.label.as_str()).colour(move |theme| colours(theme).text));
1451    match &node.trailing {
1452        Some(Trailing::Badge(label)) => {
1453            content = content.push(badge(label.as_str()).variant(badge::Variant::Secondary));
1454        }
1455        Some(Trailing::Text(label)) => {
1456            content = content.push(
1457                text(label.as_str())
1458                    .size(text_size::XS)
1459                    .wrapping(text::Wrapping::None)
1460                    .style(move |theme: &Theme| text::Style {
1461                        color: Some(Tokens::of(theme).muted_foreground),
1462                    }),
1463            );
1464        }
1465        None => {}
1466    }
1467
1468    let body = line_frame(row![indent(depth, guides), chevron, content].height(Length::Fill));
1469    let row = pressable(body, move |theme, hovered| {
1470        let style = row_style(&Tokens::of(theme), Status { hovered, ..status });
1471        Look {
1472            background: style.background,
1473            border: Border {
1474                color: style.ring.unwrap_or(Color::TRANSPARENT),
1475                width: if style.ring.is_some() { 1.0 } else { 0.0 },
1476                radius: radius::SM.into(),
1477            },
1478            divider: None,
1479        }
1480    });
1481    let Some(on_event) = on_event.cloned() else {
1482        return row.into();
1483    };
1484    let activate = on_event(Event::Activate(id));
1485    row.on_press(move |modifiers| on_event(Event::Press(id, modifiers)))
1486        .on_double_click(Some(activate))
1487        .into()
1488}
1489
1490#[cfg(test)]
1491mod tests {
1492    use super::*;
1493    use crate::theme::{dark, light};
1494
1495    /// ```text
1496    /// 1 src
1497    ///   2 main.rs
1498    ///   3 lib
1499    ///     4 mod.rs
1500    ///     5 old.rs (disabled)
1501    ///     6 util.rs
1502    /// 7 docs (lazy)
1503    /// 8 readme.md
1504    /// ```
1505    fn sample() -> State<u32> {
1506        State::new([
1507            node(1, "src").folder().children([
1508                node(2, "main.rs").file(),
1509                node(3, "lib").folder().children([
1510                    node(4, "mod.rs"),
1511                    node(5, "old.rs").disabled(true),
1512                    node(6, "util.rs"),
1513                ]),
1514            ]),
1515            node(7, "docs").folder().lazy(),
1516            node(8, "readme.md").file(),
1517        ])
1518    }
1519
1520    fn open() -> State<u32> {
1521        sample().with_expanded([1, 3])
1522    }
1523
1524    fn selected(output: Option<Output<u32>>) -> Vec<u32> {
1525        match output {
1526            Some(Output::Selected(ids)) => ids,
1527            other => panic!("expected a selection, got {other:?}"),
1528        }
1529    }
1530
1531    fn checked(output: Option<Output<u32>>) -> Vec<u32> {
1532        match output {
1533            Some(Output::Checked(ids)) => ids,
1534            other => panic!("expected checked nodes, got {other:?}"),
1535        }
1536    }
1537
1538    fn key(key: Key, modifiers: Modifiers) -> keys::Event {
1539        keys::Event { key, modifiers }
1540    }
1541
1542    #[test]
1543    fn builds_the_structure_collapsed() {
1544        let state = sample();
1545        assert_eq!(state.len(), 8);
1546        assert_eq!(state.roots(), &[1, 7, 8]);
1547        assert_eq!(state.children(1), &[2, 3]);
1548        assert_eq!(state.parent(4), Some(3));
1549        assert_eq!(state.parent(1), None);
1550        assert_eq!(state.visible(), vec![1, 7, 8]);
1551        assert!(state.has_children(7) && !state.is_loaded(7));
1552        assert!(!state.has_children(8) && state.is_loaded(8));
1553        assert_eq!(state.mode(), Mode::Single);
1554        assert!(State::<u8>::new([]).is_empty());
1555    }
1556
1557    #[test]
1558    fn with_expanded_skips_leaves_and_lazy_nodes() {
1559        let state = sample().with_expanded([1, 7, 8]);
1560        assert!(state.is_expanded(1));
1561        assert!(!state.is_expanded(7), "lazy nodes load only when asked");
1562        assert!(!state.is_expanded(8));
1563        assert_eq!(state.visible(), vec![1, 2, 3, 7, 8]);
1564    }
1565
1566    #[test]
1567    fn toggle_expands_and_collapses() {
1568        let mut state = sample();
1569        assert!(state.update(Event::Toggle(1)).is_none());
1570        assert_eq!(state.visible(), vec![1, 2, 3, 7, 8]);
1571        let _ = state.update(Event::Toggle(1));
1572        assert_eq!(state.visible(), vec![1, 7, 8]);
1573        let _ = state.update(Event::Expand(8));
1574        assert!(!state.is_expanded(8), "a leaf does not expand");
1575    }
1576
1577    #[test]
1578    fn collapsing_moves_a_hidden_highlight_to_the_parent() {
1579        let mut state = open();
1580        let _ = state.update(Event::Press(4, Modifiers::empty()));
1581        let _ = state.update(Event::Collapse(1));
1582        assert_eq!(state.highlighted(), Some(1));
1583    }
1584
1585    #[test]
1586    fn expanding_a_lazy_node_starts_loading() {
1587        let mut state = sample();
1588        let output = state.update(Event::Expand(7));
1589        assert!(matches!(output, Some(Output::Load(ids)) if ids == [7]));
1590        assert!(state.is_loading(7) && state.is_expanded(7));
1591        assert_eq!(
1592            state.lines(),
1593            vec![
1594                Line::Node { id: 1, depth: 0 },
1595                Line::Node { id: 7, depth: 0 },
1596                Line::Loading { depth: 1 },
1597                Line::Node { id: 8, depth: 0 },
1598            ]
1599        );
1600        assert!(state.update(Event::Expand(7)).is_none(), "already loading");
1601    }
1602
1603    #[test]
1604    fn loaded_children_replace_the_loading_line() {
1605        let mut state = sample();
1606        let _ = state.update(Event::Expand(7));
1607        let _ = state.update(Event::Received(vec![loaded(
1608            7,
1609            [node(70, "guide.md"), node(71, "api").lazy()],
1610        )]));
1611        assert!(!state.is_loading(7));
1612        assert_eq!(state.visible(), vec![1, 7, 70, 71, 8]);
1613        assert_eq!(state.parent(71), Some(7));
1614        assert!(state.has_children(71));
1615    }
1616
1617    #[test]
1618    fn reloading_replaces_old_children_and_forgets_them() {
1619        let mut state = open();
1620        let _ = state.update(Event::Press(4, Modifiers::empty()));
1621        let _ = state.update(Event::Received(vec![loaded(3, [node(9, "new.rs")])]));
1622        assert_eq!(state.children(3), &[9]);
1623        assert!(state.node(4).is_none());
1624        assert_eq!(state.highlighted(), None);
1625        assert!(state.selected().is_empty());
1626    }
1627
1628    #[test]
1629    fn loading_no_children_collapses_the_node() {
1630        let mut state = sample();
1631        let _ = state.update(Event::Expand(7));
1632        let _ = state.update(Event::Received(vec![loaded(7, [])]));
1633        assert!(!state.is_expanded(7) && !state.has_children(7));
1634        assert!(
1635            state
1636                .update(Event::Received(vec![loaded(99, [])]))
1637                .is_none()
1638        );
1639    }
1640
1641    #[test]
1642    fn set_loading_marks_unloaded_nodes_only() {
1643        let mut state = sample();
1644        state.set_loading(7);
1645        assert!(state.is_loading(7));
1646        state.set_loading(1);
1647        assert!(!state.is_loading(1), "loaded children stay");
1648    }
1649
1650    #[test]
1651    fn arrows_move_over_visible_enabled_nodes_without_wrapping() {
1652        let mut state = open();
1653        let _ = state.update(Event::Next);
1654        assert_eq!(state.highlighted(), Some(1));
1655        for _ in 0..4 {
1656            let _ = state.update(Event::Next);
1657        }
1658        assert_eq!(state.highlighted(), Some(6), "skips the disabled node");
1659        let _ = state.update(Event::Last);
1660        assert_eq!(state.highlighted(), Some(8));
1661        let _ = state.update(Event::Next);
1662        assert_eq!(state.highlighted(), Some(8), "no wrap");
1663        let _ = state.update(Event::First);
1664        let _ = state.update(Event::Previous);
1665        assert_eq!(state.highlighted(), Some(1));
1666
1667        let mut fresh = open();
1668        let _ = fresh.update(Event::Previous);
1669        assert_eq!(fresh.highlighted(), Some(8));
1670    }
1671
1672    #[test]
1673    fn right_expands_then_enters_and_left_collapses_then_leaves() {
1674        let mut state = sample();
1675        let _ = state.update(Event::First);
1676        let _ = state.update(Event::ExpandOrEnter);
1677        assert!(state.is_expanded(1));
1678        assert_eq!(state.highlighted(), Some(1));
1679        let _ = state.update(Event::ExpandOrEnter);
1680        assert_eq!(state.highlighted(), Some(2));
1681        let _ = state.update(Event::ExpandOrEnter);
1682        assert_eq!(state.highlighted(), Some(2), "a leaf goes nowhere");
1683        let _ = state.update(Event::CollapseOrLeave);
1684        assert_eq!(state.highlighted(), Some(1));
1685        let _ = state.update(Event::CollapseOrLeave);
1686        assert!(!state.is_expanded(1));
1687        let _ = state.update(Event::CollapseOrLeave);
1688        assert_eq!(state.highlighted(), Some(1), "a root has no parent");
1689    }
1690
1691    #[test]
1692    fn right_on_a_lazy_node_loads_it() {
1693        let mut state = sample();
1694        let _ = state.update(Event::Press(7, Modifiers::empty()));
1695        assert!(matches!(
1696            state.update(Event::ExpandOrEnter),
1697            Some(Output::Load(ids)) if ids == [7]
1698        ));
1699    }
1700
1701    #[test]
1702    fn star_expands_every_sibling_and_loads_lazy_ones() {
1703        let mut state = sample();
1704        let _ = state.update(Event::First);
1705        let output = state.update(Event::ExpandSiblings);
1706        assert!(matches!(output, Some(Output::Load(ids)) if ids == [7]));
1707        assert!(state.is_expanded(1) && state.is_expanded(7));
1708        assert!(!state.is_expanded(8));
1709    }
1710
1711    #[test]
1712    fn typeahead_cycles_through_matching_labels() {
1713        let mut state = open();
1714        let _ = state.update(Event::Typeahead('M'));
1715        assert_eq!(state.highlighted(), Some(2));
1716        let _ = state.update(Event::Typeahead('m'));
1717        assert_eq!(state.highlighted(), Some(4));
1718        let _ = state.update(Event::Typeahead('m'));
1719        assert_eq!(state.highlighted(), Some(2), "wraps round");
1720        let _ = state.update(Event::Typeahead('o'));
1721        assert_eq!(state.highlighted(), Some(2), "disabled old.rs is skipped");
1722        let _ = state.update(Event::Typeahead('z'));
1723        assert_eq!(state.highlighted(), Some(2));
1724    }
1725
1726    #[test]
1727    fn single_selection_follows_presses() {
1728        let mut state = open();
1729        assert_eq!(
1730            selected(state.update(Event::Press(2, Modifiers::empty()))),
1731            [2]
1732        );
1733        assert_eq!(
1734            selected(state.update(Event::Press(4, Modifiers::CTRL))),
1735            [4]
1736        );
1737        assert!(state.update(Event::Press(4, Modifiers::empty())).is_none());
1738        assert!(state.update(Event::Press(5, Modifiers::empty())).is_none());
1739        assert_eq!(state.selected(), [4], "disabled nodes are not selected");
1740    }
1741
1742    #[test]
1743    fn multiple_selection_with_command_and_shift() {
1744        let mut state = open().with_mode(Mode::Multiple);
1745        let _ = state.update(Event::Press(2, Modifiers::empty()));
1746        assert_eq!(
1747            selected(state.update(Event::Press(4, Modifiers::COMMAND))),
1748            [2, 4]
1749        );
1750        assert_eq!(
1751            selected(state.update(Event::Press(2, Modifiers::COMMAND))),
1752            [4]
1753        );
1754        assert_eq!(
1755            selected(state.update(Event::Press(7, Modifiers::SHIFT))),
1756            [2, 3, 4, 6, 7],
1757            "a range from the anchor, skipping the disabled node"
1758        );
1759        let _ = state.update(Event::Press(8, Modifiers::empty()));
1760        let _ = state.update(Event::Press(2, Modifiers::COMMAND));
1761        assert_eq!(
1762            selected(state.update(Event::Press(4, Modifiers::SHIFT | Modifiers::COMMAND))),
1763            [2, 3, 4, 8],
1764            "Ctrl+Shift adds the range"
1765        );
1766        assert_eq!(
1767            selected(state.update(Event::Press(3, Modifiers::empty()))),
1768            [3]
1769        );
1770    }
1771
1772    #[test]
1773    fn shift_arrows_extend_from_the_anchor() {
1774        let mut state = open().with_mode(Mode::Multiple);
1775        let _ = state.update(Event::Press(2, Modifiers::empty()));
1776        assert_eq!(selected(state.update(Event::ExtendNext)), [2, 3]);
1777        assert_eq!(selected(state.update(Event::ExtendNext)), [2, 3, 4]);
1778        assert_eq!(selected(state.update(Event::ExtendPrevious)), [2, 3]);
1779        let _ = state.update(Event::ExtendPrevious);
1780        assert_eq!(selected(state.update(Event::ExtendPrevious)), [1, 2]);
1781    }
1782
1783    #[test]
1784    fn extend_only_moves_outside_a_multiple_selection() {
1785        let mut state = open();
1786        let _ = state.update(Event::Press(2, Modifiers::empty()));
1787        assert!(state.update(Event::ExtendNext).is_none());
1788        assert_eq!(state.highlighted(), Some(3));
1789        assert_eq!(state.selected(), [2]);
1790    }
1791
1792    #[test]
1793    fn space_selects_by_mode() {
1794        let mut single = open();
1795        let _ = single.update(Event::First);
1796        assert_eq!(selected(single.update(Event::ToggleHighlighted)), [1]);
1797
1798        let mut multiple = open().with_mode(Mode::Multiple);
1799        let _ = multiple.update(Event::First);
1800        let _ = multiple.update(Event::ToggleHighlighted);
1801        let _ = multiple.update(Event::Next);
1802        assert_eq!(selected(multiple.update(Event::ToggleHighlighted)), [1, 2]);
1803        assert_eq!(selected(multiple.update(Event::ToggleHighlighted)), [1]);
1804
1805        let mut none = open().with_mode(Mode::None);
1806        let _ = none.update(Event::First);
1807        assert!(none.update(Event::ToggleHighlighted).is_none());
1808        assert!(none.update(Event::Press(2, Modifiers::empty())).is_none());
1809        assert_eq!(none.highlighted(), Some(2));
1810    }
1811
1812    #[test]
1813    fn select_all_takes_every_visible_enabled_node() {
1814        let mut state = open().with_mode(Mode::Multiple);
1815        assert_eq!(
1816            selected(state.update(Event::SelectAll)),
1817            [1, 2, 3, 4, 6, 7, 8]
1818        );
1819        assert!(open().update(Event::SelectAll).is_none());
1820    }
1821
1822    #[test]
1823    fn activation_reports_the_node() {
1824        let mut state = open();
1825        assert!(state.update(Event::ActivateHighlighted).is_none());
1826        assert!(matches!(
1827            state.update(Event::Activate(2)),
1828            Some(Output::Activated(2))
1829        ));
1830        assert_eq!(state.highlighted(), Some(2));
1831        assert!(matches!(
1832            state.update(Event::ActivateHighlighted),
1833            Some(Output::Activated(2))
1834        ));
1835        assert!(state.update(Event::Activate(5)).is_none());
1836    }
1837
1838    #[test]
1839    fn checking_a_parent_checks_its_enabled_children() {
1840        let mut state = open().with_mode(Mode::Checkbox);
1841        assert_eq!(checked(state.update(Event::Check(3, true))), [3, 4, 6]);
1842        assert_eq!(state.check_state(3), CheckState::Checked);
1843        assert_eq!(
1844            state.check_state(5),
1845            CheckState::Unchecked,
1846            "disabled stays"
1847        );
1848        assert_eq!(state.check_state(1), CheckState::Indeterminate);
1849
1850        assert_eq!(
1851            checked(state.update(Event::Check(2, true))),
1852            [1, 2, 3, 4, 6]
1853        );
1854        assert_eq!(state.check_state(1), CheckState::Checked);
1855
1856        assert_eq!(checked(state.update(Event::Check(4, false))), [2, 6]);
1857        assert_eq!(state.check_state(3), CheckState::Indeterminate);
1858        assert_eq!(state.check_state(1), CheckState::Indeterminate);
1859
1860        assert_eq!(
1861            checked(state.update(Event::Check(1, false))),
1862            Vec::<u32>::new()
1863        );
1864        assert_eq!(state.check_state(3), CheckState::Unchecked);
1865    }
1866
1867    #[test]
1868    fn an_indeterminate_parent_checks_everything_on_the_next_click() {
1869        let mut state = open().with_mode(Mode::Checkbox);
1870        let _ = state.update(Event::Check(4, true));
1871        assert_eq!(state.check_state(3), CheckState::Indeterminate);
1872        let next = state.check_state(3).toggled().is_checked();
1873        assert_eq!(checked(state.update(Event::Check(3, next))), [3, 4, 6]);
1874    }
1875
1876    #[test]
1877    fn presses_and_space_toggle_checks_in_checkbox_mode() {
1878        let mut state = open().with_mode(Mode::Checkbox);
1879        assert_eq!(
1880            checked(state.update(Event::Press(6, Modifiers::empty()))),
1881            [6]
1882        );
1883        assert_eq!(state.check_state(3), CheckState::Indeterminate);
1884        assert_eq!(
1885            checked(state.update(Event::ToggleHighlighted)),
1886            Vec::<u32>::new()
1887        );
1888        assert!(state.update(Event::Check(5, true)).is_none(), "disabled");
1889        assert!(state.selected().is_empty(), "checkbox mode never selects");
1890        assert!(
1891            open().update(Event::Check(2, true)).is_none(),
1892            "other modes"
1893        );
1894    }
1895
1896    #[test]
1897    fn children_loading_under_a_checked_parent_arrive_checked() {
1898        let mut state = sample().with_mode(Mode::Checkbox);
1899        let _ = state.update(Event::Check(7, true));
1900        let _ = state.update(Event::Expand(7));
1901        let output = state.update(Event::Received(vec![loaded(
1902            7,
1903            [node(70, "a"), node(71, "b").disabled(true)],
1904        )]));
1905        assert_eq!(checked(output), [7, 70]);
1906        assert_eq!(state.check_state(7), CheckState::Checked);
1907    }
1908
1909    #[test]
1910    fn replacing_partially_checked_children_refreshes_ancestors_and_reports_checks() {
1911        let mut state = State::new([node(1, "Root").children([
1912            node(2, "Folder").children([node(3, "Old"), node(4, "Other")]),
1913            node(5, "Sibling"),
1914        ])])
1915        .with_mode(Mode::Checkbox);
1916        let _ = state.update(Event::Check(3, true));
1917        assert_eq!(state.check_state(1), CheckState::Indeterminate);
1918        assert_eq!(state.check_state(2), CheckState::Indeterminate);
1919
1920        let output = state.update(Event::Received(vec![loaded(2, [node(6, "New")])]));
1921
1922        assert_eq!(state.check_state(1), CheckState::Unchecked);
1923        assert_eq!(state.check_state(2), CheckState::Unchecked);
1924        assert_eq!(checked(output), Vec::<u32>::new());
1925    }
1926
1927    #[test]
1928    fn replacing_partially_checked_children_with_an_empty_list_clears_the_partial_mark() {
1929        let mut state =
1930            State::new([node(1, "Folder").children([node(2, "Old"), node(3, "Other")])])
1931                .with_mode(Mode::Checkbox);
1932        let _ = state.update(Event::Check(2, true));
1933
1934        let output = state.update(Event::Received(vec![loaded(1, [])]));
1935
1936        assert_eq!(state.check_state(1), CheckState::Unchecked);
1937        assert_eq!(checked(output), Vec::<u32>::new());
1938    }
1939
1940    #[test]
1941    fn reloading_an_unchanged_checked_leaf_emits_no_checkbox_change() {
1942        let mut state = State::new([node(1, "Folder").lazy()]).with_mode(Mode::Checkbox);
1943        let _ = state.update(Event::Check(1, true));
1944        let output = state.update(Event::Received(vec![loaded(1, [])]));
1945
1946        assert_eq!(state.check_state(1), CheckState::Checked);
1947        assert!(output.is_none());
1948    }
1949
1950    #[test]
1951    fn disabled_nodes_ignore_everything() {
1952        let mut state = open();
1953        let _ = state.update(Event::Press(5, Modifiers::empty()));
1954        assert_eq!(state.highlighted(), None);
1955        let disabled = State::new([node(1, "a").disabled(true).children([node(2, "b")])]);
1956        let mut disabled = disabled;
1957        assert!(disabled.update(Event::Toggle(1)).is_none());
1958        assert!(!disabled.is_expanded(1));
1959    }
1960
1961    #[test]
1962    fn focus_is_tracked_and_lands_on_a_node() {
1963        let mut state = sample();
1964        let _ = state.update(Event::Focus(true));
1965        assert!(state.is_focused());
1966        assert_eq!(state.highlighted(), Some(1), "the first node");
1967        let _ = state.update(Event::Focus(false));
1968        assert!(!state.is_focused());
1969        assert_eq!(state.highlighted(), Some(1), "the highlight stays");
1970
1971        let mut selected = open();
1972        let _ = selected.update(Event::Press(6, Modifiers::empty()));
1973        let _ = selected.update(Event::Collapse(3));
1974        let _ = selected.update(Event::Focus(true));
1975        assert_eq!(
1976            selected.highlighted(),
1977            Some(3),
1978            "an existing highlight is kept"
1979        );
1980
1981        let mut fresh = open().with_mode(Mode::Multiple);
1982        let _ = fresh.update(Event::SelectAll);
1983        let _ = fresh.update(Event::Focus(true));
1984        assert_eq!(fresh.highlighted(), Some(1), "the first selected node");
1985    }
1986
1987    fn press(keymap: &Keymap<Action>, chord: &str) -> Option<Action> {
1988        let chord: Chord = chord.parse().unwrap();
1989        keymap.resolve(chord.key(), chord.modifiers())
1990    }
1991
1992    #[test]
1993    fn default_keymap_covers_every_action() {
1994        let keymap = default_keymap();
1995        assert_eq!(press(&keymap, "ArrowDown"), Some(Action::Next));
1996        assert_eq!(press(&keymap, "ArrowUp"), Some(Action::Previous));
1997        assert_eq!(press(&keymap, "Home"), Some(Action::First));
1998        assert_eq!(press(&keymap, "End"), Some(Action::Last));
1999        assert_eq!(press(&keymap, "ArrowRight"), Some(Action::Expand));
2000        assert_eq!(press(&keymap, "ArrowLeft"), Some(Action::Collapse));
2001        assert_eq!(press(&keymap, "Enter"), Some(Action::Activate));
2002        assert_eq!(press(&keymap, "Space"), Some(Action::Select));
2003        assert_eq!(press(&keymap, "*"), Some(Action::ExpandSiblings));
2004        assert_eq!(press(&keymap, "Shift+ArrowDown"), Some(Action::ExtendNext));
2005        assert_eq!(
2006            press(&keymap, "Shift+ArrowUp"),
2007            Some(Action::ExtendPrevious)
2008        );
2009        assert_eq!(press(&keymap, "Mod+A"), Some(Action::SelectAll));
2010        for action in <Action as keys::Action>::ALL {
2011            assert!(!keymap.chords(action).is_empty(), "{action:?}");
2012            assert!(keys::Action::description(*action).ends_with('.'));
2013        }
2014        assert_eq!(Keymap::<Action>::defaults(), keymap);
2015    }
2016
2017    #[test]
2018    fn keymap_overrides_and_unbinding() {
2019        let keymap = default_keymap()
2020            .unbind(&Chord::character('*'))
2021            .bind(Chord::character('l'), Action::Expand);
2022        let state = open();
2023        assert!(
2024            state
2025                .key_event(&keymap, &key(Key::Character("*".into()), Modifiers::SHIFT))
2026                .is_none()
2027        );
2028        let mut state = state;
2029        let _ = state.update(Event::First);
2030        assert!(matches!(
2031            state.key_event(
2032                &keymap,
2033                &key(Key::Character("l".into()), Modifiers::empty())
2034            ),
2035            Some(Event::ExpandOrEnter)
2036        ));
2037        assert!(default_keymap().clear().is_empty());
2038    }
2039
2040    #[test]
2041    fn key_events_use_the_keymap_then_typeahead() {
2042        let keymap = default_keymap();
2043        let mut state = open();
2044        let _ = state.update(Event::First);
2045        let star = key(Key::Character("*".into()), Modifiers::SHIFT);
2046        assert!(matches!(
2047            state.key_event(&keymap, &star),
2048            Some(Event::ExpandSiblings)
2049        ));
2050        let letter = key(Key::Character("r".into()), Modifiers::empty());
2051        assert!(matches!(
2052            state.key_event(&keymap, &letter),
2053            Some(Event::Typeahead('r'))
2054        ));
2055        let ctrl_letter = key(Key::Character("r".into()), Modifiers::CTRL);
2056        assert!(state.key_event(&keymap, &ctrl_letter).is_none());
2057        let shifted_letter = key(Key::Character("R".into()), Modifiers::SHIFT);
2058        assert!(matches!(
2059            state.key_event(&keymap, &shifted_letter),
2060            Some(Event::Typeahead('R'))
2061        ));
2062    }
2063
2064    #[test]
2065    fn actions_map_to_events_only_when_they_apply() {
2066        let mut state = open();
2067        assert!(Action::Next.event(&state).is_some());
2068        assert!(
2069            Action::Expand.event(&state).is_none(),
2070            "nothing highlighted"
2071        );
2072        assert!(Action::Activate.event(&state).is_none());
2073        assert!(
2074            Action::SelectAll.event(&state).is_none(),
2075            "single selection"
2076        );
2077        let _ = state.update(Event::First);
2078        assert!(matches!(
2079            Action::Expand.event(&state),
2080            Some(Event::ExpandOrEnter)
2081        ));
2082        assert!(matches!(
2083            Action::Collapse.event(&state),
2084            Some(Event::CollapseOrLeave)
2085        ));
2086        assert!(matches!(
2087            Action::Select.event(&state),
2088            Some(Event::ToggleHighlighted)
2089        ));
2090        assert!(matches!(
2091            Action::ExtendNext.event(&state),
2092            Some(Event::Next)
2093        ));
2094        assert!(matches!(
2095            Action::ExtendPrevious.event(&state),
2096            Some(Event::Previous)
2097        ));
2098
2099        let multiple = open().with_mode(Mode::Multiple);
2100        assert!(matches!(
2101            Action::SelectAll.event(&multiple),
2102            Some(Event::SelectAll)
2103        ));
2104        assert!(matches!(
2105            Action::ExtendNext.event(&multiple),
2106            Some(Event::ExtendNext)
2107        ));
2108
2109        let mut none = open().with_mode(Mode::None);
2110        let _ = none.update(Event::First);
2111        assert!(Action::Select.event(&none).is_none());
2112    }
2113
2114    #[test]
2115    fn stream_emits_ready_first_then_batches() {
2116        use iced::futures::SinkExt;
2117        use iced::futures::executor::block_on;
2118
2119        let mut events = Box::pin(stream::<u32>());
2120        let Some(Event::Ready(mut sender)) = block_on(events.next()) else {
2121            panic!("first event must be Ready");
2122        };
2123        block_on(async {
2124            for parent in [1, 2, 3] {
2125                sender.send(loaded(parent, [])).await.ok();
2126            }
2127        });
2128        let Some(Event::Received(batch)) = block_on(events.next()) else {
2129            panic!("expected a batch");
2130        };
2131        let parents: Vec<_> = batch.iter().map(|loaded| loaded.parent).collect();
2132        assert_eq!(parents, [1, 2, 3]);
2133    }
2134
2135    #[test]
2136    fn ready_hands_the_sender_back() {
2137        let (sender, _receiver) = mpsc::channel(1);
2138        assert!(matches!(
2139            sample().update(Event::Ready(sender)),
2140            Some(Output::Ready(_))
2141        ));
2142    }
2143
2144    #[test]
2145    fn rows_follow_the_menu_look_in_both_themes() {
2146        for theme in [light(), dark()] {
2147            let tokens = Tokens::of(&theme);
2148            let idle = row_style(&tokens, Status::default());
2149            assert_eq!(idle.background, None);
2150            assert_eq!(idle.text, tokens.foreground);
2151            assert_eq!(idle.ring, None);
2152
2153            let selected = row_style(
2154                &tokens,
2155                Status {
2156                    selected: true,
2157                    ..Status::default()
2158                },
2159            );
2160            assert_eq!(selected.background, Some(tokens.accent));
2161            assert_eq!(selected.text, tokens.accent_foreground);
2162
2163            let hovered = row_style(
2164                &tokens,
2165                Status {
2166                    hovered: true,
2167                    ..Status::default()
2168                },
2169            );
2170            assert!(
2171                hovered
2172                    .background
2173                    .is_some_and(|colour| colour.a < tokens.accent.a)
2174            );
2175
2176            let highlighted = row_style(
2177                &tokens,
2178                Status {
2179                    highlighted: true,
2180                    ..Status::default()
2181                },
2182            );
2183            assert_eq!(highlighted.ring, Some(tokens.ring));
2184
2185            let disabled = row_style(
2186                &tokens,
2187                Status {
2188                    disabled: true,
2189                    hovered: true,
2190                    highlighted: true,
2191                    selected: true,
2192                },
2193            );
2194            assert_eq!(disabled.background, None);
2195            assert_eq!(disabled.text, tokens.muted_foreground);
2196            assert_eq!(disabled.ring, None);
2197            assert_eq!(guide_colour(&tokens), tokens.border);
2198        }
2199    }
2200
2201    #[test]
2202    fn builder_defaults_and_options() {
2203        let state = sample();
2204        let view: Tree<'_, u32, ()> = tree(&state);
2205        assert!(!view.guides && !view.is_enabled());
2206        assert_eq!((view.width, view.height), (Length::Fill, Length::Shrink));
2207        assert_eq!(view.keymap, default_keymap());
2208        let view = view
2209            .guides(true)
2210            .width(240)
2211            .height(300)
2212            .keymap(Keymap::new())
2213            .on_event(|_| ());
2214        assert!(view.guides && view.is_enabled());
2215        assert_eq!(view.width, Length::Fixed(240.0));
2216        assert!(view.keymap.is_empty());
2217    }
2218
2219    #[test]
2220    fn node_builders() {
2221        let folder = node(1, "src").folder().badge("3");
2222        assert_eq!(folder.icon, Some(crate::lucide!(Folder)));
2223        assert_eq!(folder.open_icon, Some(crate::lucide!(FolderOpen)));
2224        assert_eq!(folder.trailing, Some(Trailing::Badge("3".into())));
2225        let file = node(2, "a.txt").file().trailing("4 KB").disabled(true);
2226        assert_eq!(file.icon, Some(crate::lucide!(File)));
2227        assert_eq!(file.trailing, Some(Trailing::Text("4 KB".into())));
2228        assert!(file.disabled);
2229        assert_eq!(Mode::ALL.len(), 4);
2230    }
2231}