1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Handle keyboard and mouse events for TreeView.
use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
use ratatui::layout::Rect;
use crate::primitives::tree_view::tree_navigator::TreeNavigator;
use crate::primitives::tree_view::tree_view_state::TreeViewState;
use crate::primitives::tree_view::widget::TreeView;
use crate::primitives::widget_event::WidgetEvent;
impl<'a, T> TreeView<'a, T> {
/// Handle a keyboard event and return a WidgetEvent.
///
/// This method processes keyboard input for the tree view, including:
/// - Navigation keys (up/down/left/right)
/// - Expansion/collapse keys (h/l)
/// - Filter mode keys (/ to enter, Esc to exit, Enter to confirm)
///
/// When filter mode is active, all keystrokes are routed to the filter input
/// and the tree is automatically filtered.
///
/// # Arguments
///
/// * `key` - The keyboard event to handle
/// * `navigator` - The tree navigator for navigation operations
/// * `state` - The tree view state to modify
///
/// # Returns
///
/// A `WidgetEvent` indicating what action was taken.
///
/// # Example
///
/// ```rust
/// use ratatui_toolkit::tree_view::{TreeNode, TreeView, TreeNavigator, TreeViewState};
/// use crossterm::event::KeyCode;
///
/// let nodes = vec![TreeNode::new("Root")];
/// let mut tree = TreeView::new(nodes)
/// .render_fn(|data, state| {
/// ratatui::text::Line::from(*data)
/// });
/// let mut state = TreeViewState::new();
/// let navigator = TreeNavigator::new();
///
/// match tree.handle_key_event(crossterm::event::KeyEvent::new(KeyCode::Down), &navigator, &mut state) {
/// WidgetEvent::Selected { path } => println!("Selected: {:?}", path),
/// WidgetEvent::Scrolled { offset, direction } => println!("Scrolled"),
/// _ => {}
/// }
/// ```
pub fn handle_key_event(
&mut self,
key: KeyEvent,
navigator: &TreeNavigator,
state: &mut TreeViewState,
) -> WidgetEvent {
if state.filter_mode {
self.handle_filter_key(key, state)
} else {
navigator.handle_key_event(key, &self.nodes, state);
WidgetEvent::Selected {
path: state.selected_path.clone().unwrap_or_default(),
}
}
}
fn handle_filter_key(&mut self, key: KeyEvent, state: &mut TreeViewState) -> WidgetEvent {
match key.code {
KeyCode::Esc => {
state.clear_filter();
WidgetEvent::FilterModeExited {
path: state.selected_path.clone().unwrap_or_default(),
}
}
KeyCode::Enter => {
let path = state.selected_path.clone();
state.exit_filter_mode();
WidgetEvent::FilterModeExited {
path: path.unwrap_or_default(),
}
}
KeyCode::Backspace => {
state.backspace_filter();
WidgetEvent::FilterModeChanged {
active: true,
filter: state.filter.clone().unwrap_or_default(),
}
}
KeyCode::Char(c) => {
state.append_to_filter(c);
WidgetEvent::FilterModeChanged {
active: true,
filter: state.filter.clone().unwrap_or_default(),
}
}
_ => WidgetEvent::None,
}
}
/// Handle a mouse event and return a WidgetEvent.
///
/// This method processes mouse input for the tree view, including:
/// - Click to select items
/// - Scroll wheel to scroll
///
/// # Arguments
///
/// * `event` - The mouse event to handle
/// * `state` - The tree view state to modify
/// * `area` - The area occupied by the tree view (including borders)
///
/// # Returns
///
/// A `WidgetEvent` indicating what action was taken.
pub fn handle_mouse_event(
&mut self,
event: MouseEvent,
state: &mut TreeViewState,
area: Rect,
) -> WidgetEvent {
let inner_area = match self.block {
Some(ref block) => block.inner(area),
None => area,
};
if inner_area.height == 0 {
return WidgetEvent::None;
}
let y = event.row;
if y < inner_area.y || y >= inner_area.y + inner_area.height {
return WidgetEvent::None;
}
let row = (y - inner_area.y + state.offset as u16) as usize;
let items = self.flatten_tree(state);
if let Some((_, path)) = items.get(row) {
state.selected_path = Some(path.clone());
return WidgetEvent::Selected { path: path.clone() };
}
WidgetEvent::None
}
}