Skip to main content

tui_lipan/widgets/tree/
types.rs

1//! Tree widget types.
2
3use crate::callback::{Callback, KeyHandler};
4use crate::core::event::KeyCode;
5use crate::core::event::KeyEvent;
6use crate::style::{Length, Style, StyleSlot};
7use crate::utils::gradient::ColorGradient;
8use crate::widgets::ScrollKeymap;
9use crate::widgets::{FocusAccordion, ListItem};
10use std::sync::Arc;
11
12/// A path to a tree node (index-based).
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14pub struct TreePath(pub(crate) Arc<[usize]>);
15
16impl TreePath {
17    /// Access the path segments.
18    pub fn segments(&self) -> &[usize] {
19        &self.0
20    }
21}
22
23impl From<Vec<usize>> for TreePath {
24    fn from(value: Vec<usize>) -> Self {
25        Self(value.into())
26    }
27}
28
29impl AsRef<[usize]> for TreePath {
30    fn as_ref(&self) -> &[usize] {
31        &self.0
32    }
33}
34
35/// Tree selection event.
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct TreeEvent {
38    /// Visible row index.
39    pub index: usize,
40    /// Path to the selected node.
41    pub path: TreePath,
42}
43
44/// Tree expand/collapse event.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct TreeToggleEvent {
47    /// Visible row index.
48    pub index: usize,
49    /// Path to the toggled node.
50    pub path: TreePath,
51    /// New expanded state.
52    pub expanded: bool,
53}
54
55/// Keyboard shortcuts for tree navigation.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub struct TreeKeymap(u8);
58
59impl TreeKeymap {
60    /// Disable key handling.
61    pub const NONE: Self = Self(0);
62    /// Arrow keys (Left/Right).
63    pub const ARROWS: Self = Self(1 << 0);
64    /// Vim-style h/l.
65    pub const VIM: Self = Self(1 << 1);
66    /// Toggle with Space.
67    pub const TOGGLE: Self = Self(1 << 2);
68    /// Default key set.
69    pub const DEFAULT: Self = Self(Self::ARROWS.0 | Self::VIM.0 | Self::TOGGLE.0);
70
71    /// Check if this keymap includes another set.
72    pub fn contains(self, other: Self) -> bool {
73        (self.0 & other.0) == other.0
74    }
75}
76
77impl std::ops::BitOr for TreeKeymap {
78    type Output = Self;
79
80    fn bitor(self, rhs: Self) -> Self {
81        Self(self.0 | rhs.0)
82    }
83}
84
85impl std::ops::BitOrAssign for TreeKeymap {
86    fn bitor_assign(&mut self, rhs: Self) {
87        self.0 |= rhs.0;
88    }
89}
90
91impl std::ops::BitAnd for TreeKeymap {
92    type Output = Self;
93
94    fn bitand(self, rhs: Self) -> Self {
95        Self(self.0 & rhs.0)
96    }
97}
98
99impl std::ops::BitAndAssign for TreeKeymap {
100    fn bitand_assign(&mut self, rhs: Self) {
101        self.0 &= rhs.0;
102    }
103}
104
105impl std::ops::Not for TreeKeymap {
106    type Output = Self;
107
108    fn not(self) -> Self {
109        Self(!self.0)
110    }
111}
112
113impl Default for TreeKeymap {
114    fn default() -> Self {
115        Self::DEFAULT
116    }
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub(crate) enum TreeAction {
121    Expand,
122    Collapse,
123    Toggle,
124}
125
126pub(crate) fn tree_action_from_key(key: &KeyEvent, keymap: TreeKeymap) -> Option<TreeAction> {
127    match key.code {
128        KeyCode::Left if keymap.contains(TreeKeymap::ARROWS) => Some(TreeAction::Collapse),
129        KeyCode::Right if keymap.contains(TreeKeymap::ARROWS) => Some(TreeAction::Expand),
130        KeyCode::Char('h') if keymap.contains(TreeKeymap::VIM) => Some(TreeAction::Collapse),
131        KeyCode::Char('l') if keymap.contains(TreeKeymap::VIM) => Some(TreeAction::Expand),
132        KeyCode::Char(' ') if keymap.contains(TreeKeymap::TOGGLE) => Some(TreeAction::Toggle),
133        _ => None,
134    }
135}
136
137/// A node in a tree.
138#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct TreeNode {
140    pub(crate) item: ListItem,
141    pub(crate) children: Vec<TreeNode>,
142    pub(crate) expanded: bool,
143    pub(crate) indent: u16,
144    pub(crate) leading_guide_fill_cells: u16,
145}
146
147impl TreeNode {
148    /// Create a new tree node.
149    pub fn new(item: impl Into<ListItem>) -> Self {
150        Self {
151            item: item.into(),
152            children: Vec::new(),
153            expanded: false,
154            indent: 2,
155            leading_guide_fill_cells: 0,
156        }
157    }
158
159    /// Add a child node.
160    pub fn child(mut self, child: TreeNode) -> Self {
161        self.children.push(child);
162        self
163    }
164
165    /// Replace children.
166    pub fn children(mut self, children: impl IntoIterator<Item = TreeNode>) -> Self {
167        self.children = children.into_iter().collect();
168        self
169    }
170
171    /// Set expanded state (initial).
172    pub fn expanded(mut self, expanded: bool) -> Self {
173        self.expanded = expanded;
174        self
175    }
176
177    /// Set indentation per level (default 2).
178    pub fn indent(mut self, indent: u16) -> Self {
179        self.indent = indent;
180        self
181    }
182
183    pub(crate) fn leading_guide_fill_cells(mut self, cells: u16) -> Self {
184        self.leading_guide_fill_cells = cells;
185        self
186    }
187}
188
189/// Style of indentation guides.
190#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
191pub enum IndentStyle {
192    /// No guides.
193    #[default]
194    None,
195    /// Vertical lines only (│).
196    Line,
197    /// Short branch connectors (├, └).
198    Short,
199    /// Short branch connectors with rounded terminal elbows (├, ╰).
200    ShortRounded,
201    /// Long branch connectors (├─, └─).
202    Long,
203    /// Long branch connectors with rounded terminal elbows (├─, ╰─).
204    LongRounded,
205}
206
207#[derive(Clone, PartialEq)]
208pub(crate) struct TreeProps {
209    pub root: TreeNode,
210    pub selected: Option<usize>,
211    pub force_scroll_to_selected: bool,
212    pub gap: u16,
213    pub icon_gap: u16,
214    pub show_icons: bool,
215    pub expanded_icon: Arc<str>,
216    pub collapsed_icon: Arc<str>,
217    pub leaf_icon: Option<Arc<str>>,
218    pub icon_style: Style,
219    pub width: Length,
220    pub height: Length,
221    pub style: Style,
222    pub hover_style: StyleSlot,
223    pub item_hover_style: StyleSlot,
224    pub selection_style: StyleSlot,
225    pub unfocused_selection_style: StyleSlot,
226    pub selection_symbol: Option<Arc<str>>,
227    pub selection_symbol_style: Option<Style>,
228    pub unfocused_selection_symbol_style: Option<Style>,
229    pub scrollbar: bool,
230    pub scrollbar_config: crate::style::ScrollbarConfig,
231    pub scroll_keys: ScrollKeymap,
232    pub scroll_wheel: bool,
233    pub show_scroll_indicators: bool,
234    pub scroll_indicator_style: Style,
235    pub empty_text: Option<Arc<str>>,
236    pub empty_text_style: Style,
237    pub activate_on_click: bool,
238    pub focusable: bool,
239    pub on_select: Option<Callback<TreeEvent>>,
240    pub on_activate: Option<Callback<TreeEvent>>,
241    pub on_toggle: Option<Callback<TreeToggleEvent>>,
242    pub keymap: TreeKeymap,
243    pub focus_policy: Option<FocusAccordion>,
244    pub indent_style: IndentStyle,
245    pub indent_guide_style: Style,
246    pub indent_gradient: Option<ColorGradient>,
247    pub(crate) solid_indent_connector_gap: bool,
248    pub selection_full_width: bool,
249    pub unselected_symbol: Option<Arc<str>>,
250    pub key_interceptor: Option<KeyHandler>,
251}