1use crate::buffer::ScreenBuffer;
7use crate::cell::Cell;
8use crate::event::{Event, KeyCode, KeyEvent};
9use crate::geometry::Rect;
10use crate::segment::Segment;
11use crate::style::Style;
12use crate::text::truncate_to_display_width;
13use unicode_width::UnicodeWidthStr;
14
15use super::{BorderStyle, EventResult, InteractiveWidget, Widget};
16
17#[derive(Clone, Debug)]
19pub struct TreeNode<T> {
20 pub data: T,
22 pub children: Vec<TreeNode<T>>,
24 pub expanded: bool,
26 pub is_leaf: bool,
28}
29
30impl<T> TreeNode<T> {
31 pub fn new(data: T) -> Self {
33 Self {
34 data,
35 children: Vec::new(),
36 expanded: false,
37 is_leaf: true,
38 }
39 }
40
41 pub fn branch(data: T) -> Self {
43 Self {
44 data,
45 children: Vec::new(),
46 expanded: false,
47 is_leaf: false,
48 }
49 }
50
51 pub fn with_child(mut self, child: TreeNode<T>) -> Self {
53 self.is_leaf = false;
54 self.children.push(child);
55 self
56 }
57
58 pub fn with_children(mut self, children: Vec<TreeNode<T>>) -> Self {
60 if !children.is_empty() {
61 self.is_leaf = false;
62 }
63 self.children = children;
64 self
65 }
66}
67
68struct VisibleNode {
70 depth: usize,
72 path: Vec<usize>,
74 expanded: bool,
76 is_leaf: bool,
78}
79
80type NodeRenderFn<T> = Box<dyn Fn(&T, usize, bool, bool) -> Vec<Segment>>;
82
83type LazyLoadFn<T> = Option<Box<dyn Fn(&T) -> Vec<TreeNode<T>>>>;
85
86pub struct Tree<T> {
91 roots: Vec<TreeNode<T>>,
93 selected: usize,
95 scroll_offset: usize,
97 render_fn: NodeRenderFn<T>,
99 node_style: Style,
101 selected_style: Style,
103 border: BorderStyle,
105 lazy_load_fn: LazyLoadFn<T>,
107}
108
109impl<T> Tree<T> {
110 pub fn new(roots: Vec<TreeNode<T>>) -> Self {
112 Self {
113 roots,
114 selected: 0,
115 scroll_offset: 0,
116 render_fn: Box::new(|_, _, _, _| vec![Segment::new("???")]),
117 node_style: Style::default(),
118 selected_style: Style::default().reverse(true),
119 border: BorderStyle::None,
120 lazy_load_fn: None,
121 }
122 }
123
124 #[must_use]
128 pub fn with_render_fn<F>(mut self, f: F) -> Self
129 where
130 F: Fn(&T, usize, bool, bool) -> Vec<Segment> + 'static,
131 {
132 self.render_fn = Box::new(f);
133 self
134 }
135
136 #[must_use]
138 pub fn with_node_style(mut self, style: Style) -> Self {
139 self.node_style = style;
140 self
141 }
142
143 #[must_use]
145 pub fn with_selected_style(mut self, style: Style) -> Self {
146 self.selected_style = style;
147 self
148 }
149
150 #[must_use]
152 pub fn with_border(mut self, border: BorderStyle) -> Self {
153 self.border = border;
154 self
155 }
156
157 #[must_use]
159 pub fn with_lazy_load<F>(mut self, f: F) -> Self
160 where
161 F: Fn(&T) -> Vec<TreeNode<T>> + 'static,
162 {
163 self.lazy_load_fn = Some(Box::new(f));
164 self
165 }
166
167 pub fn roots(&self) -> &[TreeNode<T>] {
169 &self.roots
170 }
171
172 pub fn selected(&self) -> usize {
174 self.selected
175 }
176
177 pub fn scroll_offset(&self) -> usize {
179 self.scroll_offset
180 }
181
182 fn build_visible(&self) -> Vec<VisibleNode> {
184 let mut result = Vec::new();
185 for (idx, root) in self.roots.iter().enumerate() {
186 self.collect_visible(root, 0, vec![idx], &mut result);
187 }
188 result
189 }
190
191 fn collect_visible(
193 &self,
194 node: &TreeNode<T>,
195 depth: usize,
196 path: Vec<usize>,
197 result: &mut Vec<VisibleNode>,
198 ) {
199 result.push(VisibleNode {
200 depth,
201 path: path.clone(),
202 expanded: node.expanded,
203 is_leaf: node.is_leaf,
204 });
205
206 if node.expanded {
207 for (child_idx, child) in node.children.iter().enumerate() {
208 let mut child_path = path.clone();
209 child_path.push(child_idx);
210 self.collect_visible(child, depth + 1, child_path, result);
211 }
212 }
213 }
214
215 fn node_at_path_mut(&mut self, path: &[usize]) -> Option<&mut TreeNode<T>> {
217 if path.is_empty() {
218 return None;
219 }
220 let mut current = self.roots.get_mut(path[0])?;
221 for &idx in &path[1..] {
222 current = current.children.get_mut(idx)?;
223 }
224 Some(current)
225 }
226
227 fn node_at_path(&self, path: &[usize]) -> Option<&TreeNode<T>> {
229 if path.is_empty() {
230 return None;
231 }
232 let mut current = self.roots.get(path[0])?;
233 for &idx in &path[1..] {
234 current = current.children.get(idx)?;
235 }
236 Some(current)
237 }
238
239 pub fn toggle_selected(&mut self) {
241 let visible = self.build_visible();
242 if let Some(vnode) = visible.get(self.selected) {
243 let path = vnode.path.clone();
244 if let Some(node) = self.node_at_path_mut(&path)
245 && !node.is_leaf
246 {
247 node.expanded = !node.expanded;
248 }
249 }
250 }
251
252 pub fn expand_selected(&mut self) {
254 let visible = self.build_visible();
255 if let Some(vnode) = visible.get(self.selected) {
256 let path = vnode.path.clone();
257 let is_leaf = vnode.is_leaf;
258
259 if is_leaf {
260 return;
261 }
262
263 if let Some(ref lazy_fn) = self.lazy_load_fn
265 && let Some(node) = self.node_at_path(&path)
266 && node.children.is_empty()
267 && !node.is_leaf
268 {
269 let new_children = lazy_fn(&node.data);
270 if let Some(node_mut) = self.node_at_path_mut(&path) {
271 node_mut.children = new_children;
272 }
273 }
274
275 if let Some(node) = self.node_at_path_mut(&path) {
276 node.expanded = true;
277 }
278 }
279 }
280
281 pub fn collapse_selected(&mut self) {
283 let visible = self.build_visible();
284 if let Some(vnode) = visible.get(self.selected) {
285 let path = vnode.path.clone();
286
287 if let Some(node) = self.node_at_path_mut(&path)
288 && node.expanded
289 {
290 node.expanded = false;
291 return;
292 }
293
294 if path.len() > 1 {
296 let parent_path = &path[..path.len() - 1];
297 for (idx, v) in visible.iter().enumerate() {
299 if v.path == parent_path {
300 self.selected = idx;
301 break;
302 }
303 }
304 }
305 }
306 }
307
308 pub fn selected_node(&self) -> Option<&TreeNode<T>> {
310 let visible = self.build_visible();
311 visible
312 .get(self.selected)
313 .and_then(|v| self.node_at_path(&v.path))
314 }
315
316 pub fn visible_count(&self) -> usize {
318 self.build_visible().len()
319 }
320
321 fn ensure_selected_visible(&mut self, visible_height: usize) {
323 if visible_height == 0 {
324 return;
325 }
326 if self.selected < self.scroll_offset {
327 self.scroll_offset = self.selected;
328 }
329 if self.selected >= self.scroll_offset + visible_height {
330 self.scroll_offset = self
331 .selected
332 .saturating_sub(visible_height.saturating_sub(1));
333 }
334 }
335}
336
337impl<T> Widget for Tree<T> {
338 fn render(&self, area: Rect, buf: &mut ScreenBuffer) {
339 if area.size.width == 0 || area.size.height == 0 {
340 return;
341 }
342
343 super::border::render_border(area, self.border, self.node_style.clone(), buf);
344
345 let inner = super::border::inner_area(area, self.border);
346 if inner.size.width == 0 || inner.size.height == 0 {
347 return;
348 }
349
350 let height = inner.size.height as usize;
351 let width = inner.size.width as usize;
352 let visible = self.build_visible();
353 let count = visible.len();
354
355 let max_offset = count.saturating_sub(height.max(1));
356 let scroll = self.scroll_offset.min(max_offset);
357 let visible_end = (scroll + height).min(count);
358
359 for (row, vis_idx) in (scroll..visible_end).enumerate() {
360 let y = inner.position.y + row as u16;
361 if let Some(vnode) = visible.get(vis_idx) {
362 let is_selected = vis_idx == self.selected;
363 let style = if is_selected {
364 &self.selected_style
365 } else {
366 &self.node_style
367 };
368
369 if is_selected {
371 for col in 0..inner.size.width {
372 buf.set(inner.position.x + col, y, Cell::new(" ", style.clone()));
373 }
374 }
375
376 let indent = vnode.depth * 2;
378
379 let indicator = if vnode.is_leaf {
381 " "
382 } else if vnode.expanded {
383 "\u{25bc}" } else {
385 "\u{25b6}" };
387
388 let mut col: u16 = 0;
390
391 for _ in 0..indent {
393 if col as usize >= width {
394 break;
395 }
396 buf.set(inner.position.x + col, y, Cell::new(" ", style.clone()));
397 col += 1;
398 }
399
400 if (col as usize) < width {
402 buf.set(
403 inner.position.x + col,
404 y,
405 Cell::new(indicator, style.clone()),
406 );
407 col += 1;
408 }
409
410 if (col as usize) < width {
412 buf.set(inner.position.x + col, y, Cell::new(" ", style.clone()));
413 col += 1;
414 }
415
416 if let Some(node) = self.node_at_path(&vnode.path) {
418 let segments =
419 (self.render_fn)(&node.data, vnode.depth, vnode.expanded, vnode.is_leaf);
420 for segment in &segments {
421 if col as usize >= width {
422 break;
423 }
424 let remaining = width.saturating_sub(col as usize);
425 let truncated = truncate_to_display_width(&segment.text, remaining);
426 for ch in truncated.chars() {
427 let char_w =
428 UnicodeWidthStr::width(ch.encode_utf8(&mut [0; 4]) as &str);
429 if col as usize + char_w > width {
430 break;
431 }
432 buf.set(
433 inner.position.x + col,
434 y,
435 Cell::new(ch.to_string(), style.clone()),
436 );
437 col += char_w as u16;
438 }
439 }
440 }
441 }
442 }
443 }
444}
445
446impl<T> InteractiveWidget for Tree<T> {
447 fn handle_event(&mut self, event: &Event) -> EventResult {
448 let Event::Key(KeyEvent { code, .. }) = event else {
449 return EventResult::Ignored;
450 };
451
452 let count = self.visible_count();
453
454 match code {
455 KeyCode::Up => {
456 if self.selected > 0 {
457 self.selected -= 1;
458 self.ensure_selected_visible(20);
459 }
460 EventResult::Consumed
461 }
462 KeyCode::Down => {
463 if count > 0 && self.selected < count.saturating_sub(1) {
464 self.selected += 1;
465 self.ensure_selected_visible(20);
466 }
467 EventResult::Consumed
468 }
469 KeyCode::Right => {
470 self.expand_selected();
471 EventResult::Consumed
472 }
473 KeyCode::Left => {
474 self.collapse_selected();
475 EventResult::Consumed
476 }
477 KeyCode::Enter => {
478 self.toggle_selected();
479 EventResult::Consumed
480 }
481 KeyCode::PageUp => {
482 let page = 20;
483 self.selected = self.selected.saturating_sub(page);
484 self.ensure_selected_visible(20);
485 EventResult::Consumed
486 }
487 KeyCode::PageDown => {
488 let page = 20;
489 if count > 0 {
490 self.selected = (self.selected + page).min(count.saturating_sub(1));
491 self.ensure_selected_visible(20);
492 }
493 EventResult::Consumed
494 }
495 KeyCode::Home => {
496 self.selected = 0;
497 self.scroll_offset = 0;
498 EventResult::Consumed
499 }
500 KeyCode::End => {
501 if count > 0 {
502 self.selected = count.saturating_sub(1);
503 self.ensure_selected_visible(20);
504 }
505 EventResult::Consumed
506 }
507 _ => EventResult::Ignored,
508 }
509 }
510}
511
512#[cfg(test)]
513#[allow(clippy::unwrap_used)]
514mod tests {
515 use super::*;
516 use crate::geometry::Size;
517
518 fn make_render_fn() -> impl Fn(&String, usize, bool, bool) -> Vec<Segment> {
519 |data: &String, _depth, _expanded, _is_leaf| vec![Segment::new(data)]
520 }
521
522 fn make_test_tree() -> Tree<String> {
523 let tree = TreeNode::branch("root".into())
524 .with_child(TreeNode::new("leaf1".into()))
525 .with_child(
526 TreeNode::branch("branch1".into())
527 .with_child(TreeNode::new("child1".into()))
528 .with_child(TreeNode::new("child2".into())),
529 )
530 .with_child(TreeNode::new("leaf2".into()));
531
532 Tree::new(vec![tree]).with_render_fn(make_render_fn())
533 }
534
535 #[test]
536 fn create_tree_with_nodes() {
537 let tree = make_test_tree();
538 assert_eq!(tree.roots().len(), 1);
539 }
540
541 #[test]
542 fn render_collapsed_tree_only_roots() {
543 let tree = make_test_tree();
544 assert_eq!(tree.visible_count(), 1);
546
547 let mut buf = ScreenBuffer::new(Size::new(30, 10));
548 tree.render(Rect::new(0, 0, 30, 10), &mut buf);
549
550 assert_eq!(buf.get(0, 0).map(|c| c.grapheme.as_str()), Some("\u{25b6}"));
552 }
553
554 #[test]
555 fn expand_node_children_visible() {
556 let mut tree = make_test_tree();
557 tree.toggle_selected();
559
560 assert_eq!(tree.visible_count(), 4);
562 }
563
564 #[test]
565 fn collapse_node_hides_children() {
566 let mut tree = make_test_tree();
567 tree.toggle_selected(); assert_eq!(tree.visible_count(), 4);
569 tree.toggle_selected(); assert_eq!(tree.visible_count(), 1);
571 }
572
573 #[test]
574 fn navigate_visible_nodes() {
575 let mut tree = make_test_tree();
576 tree.toggle_selected(); let down = Event::Key(KeyEvent {
579 code: KeyCode::Down,
580 modifiers: crate::event::Modifiers::NONE,
581 });
582 let up = Event::Key(KeyEvent {
583 code: KeyCode::Up,
584 modifiers: crate::event::Modifiers::NONE,
585 });
586
587 assert_eq!(tree.selected(), 0);
588 tree.handle_event(&down);
589 assert_eq!(tree.selected(), 1); tree.handle_event(&down);
591 assert_eq!(tree.selected(), 2); tree.handle_event(&up);
593 assert_eq!(tree.selected(), 1);
594 }
595
596 #[test]
597 fn right_key_expands() {
598 let mut tree = make_test_tree();
599 let right = Event::Key(KeyEvent {
600 code: KeyCode::Right,
601 modifiers: crate::event::Modifiers::NONE,
602 });
603
604 tree.handle_event(&right); assert_eq!(tree.visible_count(), 4);
606 }
607
608 #[test]
609 fn left_key_collapses() {
610 let mut tree = make_test_tree();
611 tree.toggle_selected(); let left = Event::Key(KeyEvent {
613 code: KeyCode::Left,
614 modifiers: crate::event::Modifiers::NONE,
615 });
616
617 tree.handle_event(&left); assert_eq!(tree.visible_count(), 1);
619 }
620
621 #[test]
622 fn enter_toggles() {
623 let mut tree = make_test_tree();
624 let enter = Event::Key(KeyEvent {
625 code: KeyCode::Enter,
626 modifiers: crate::event::Modifiers::NONE,
627 });
628
629 tree.handle_event(&enter); assert_eq!(tree.visible_count(), 4);
631 tree.handle_event(&enter); assert_eq!(tree.visible_count(), 1);
633 }
634
635 #[test]
636 fn lazy_load_on_expand() {
637 use std::cell::RefCell;
638 use std::rc::Rc;
639
640 let load_count = Rc::new(RefCell::new(0));
641 let captured = Rc::clone(&load_count);
642
643 let root = TreeNode::branch("root".into());
644 let mut tree = Tree::new(vec![root])
645 .with_render_fn(make_render_fn())
646 .with_lazy_load(move |_data: &String| {
647 *captured.borrow_mut() += 1;
648 vec![
649 TreeNode::new("loaded1".into()),
650 TreeNode::new("loaded2".into()),
651 ]
652 });
653
654 tree.expand_selected();
655 assert_eq!(*load_count.borrow(), 1);
656 assert_eq!(tree.visible_count(), 3);
658 }
659
660 #[test]
661 fn selected_node_retrieval() {
662 let mut tree = make_test_tree();
663 tree.toggle_selected(); tree.selected = 1;
667 match tree.selected_node() {
668 Some(node) => assert_eq!(node.data, "leaf1"),
669 None => unreachable!("should have selected node"),
670 }
671 }
672
673 #[test]
674 fn utf8_safe_node_labels() {
675 let node = TreeNode::new("你好".into());
676 let tree = Tree::new(vec![node]).with_render_fn(make_render_fn());
677
678 let mut buf = ScreenBuffer::new(Size::new(10, 1));
679 tree.render(Rect::new(0, 0, 10, 1), &mut buf);
680
681 assert_eq!(buf.get(2, 0).map(|c| c.grapheme.as_str()), Some("你"));
684 }
685
686 #[test]
687 fn empty_tree() {
688 let tree: Tree<String> = Tree::new(vec![]).with_render_fn(make_render_fn());
689 assert_eq!(tree.visible_count(), 0);
690 assert!(tree.selected_node().is_none());
691
692 let mut buf = ScreenBuffer::new(Size::new(20, 5));
694 tree.render(Rect::new(0, 0, 20, 5), &mut buf);
695 }
696
697 #[test]
698 fn deep_tree_multiple_levels() {
699 let deep = TreeNode::branch("L0".into()).with_child(
700 TreeNode::branch("L1".into())
701 .with_child(TreeNode::branch("L2".into()).with_child(TreeNode::new("L3".into()))),
702 );
703
704 let mut tree = Tree::new(vec![deep]).with_render_fn(make_render_fn());
705
706 tree.expand_selected(); let down = Event::Key(KeyEvent {
709 code: KeyCode::Down,
710 modifiers: crate::event::Modifiers::NONE,
711 });
712 tree.handle_event(&down); tree.expand_selected(); tree.handle_event(&down); tree.handle_event(&down); tree.selected = 2; tree.expand_selected(); assert_eq!(tree.visible_count(), 4);
721 }
722
723 #[test]
724 fn mixed_expanded_collapsed() {
725 let mut tree = make_test_tree();
726 tree.toggle_selected(); tree.selected = 2;
729 tree.toggle_selected(); assert_eq!(tree.visible_count(), 6);
733
734 tree.toggle_selected();
736 assert_eq!(tree.visible_count(), 4);
738 }
739
740 #[test]
741 fn render_with_border() {
742 let tree = make_test_tree();
743 let tree = Tree {
744 border: BorderStyle::Single,
745 ..tree
746 };
747
748 let mut buf = ScreenBuffer::new(Size::new(20, 10));
749 tree.render(Rect::new(0, 0, 20, 10), &mut buf);
750
751 assert_eq!(buf.get(0, 0).map(|c| c.grapheme.as_str()), Some("\u{250c}"));
752 }
753}