fresh/view/controls/map_input/
input.rs1use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
4
5use super::{MapHit, MapLayout, MapState};
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum MapEvent {
10 EntryAdded(String),
12 EntryRemoved(usize),
14 EntryToggled(usize, bool),
16 FocusChanged(Option<usize>),
18 NewKeyChanged(String),
20}
21
22impl MapState {
23 pub fn handle_mouse(&mut self, event: MouseEvent, layout: &MapLayout) -> Option<MapEvent> {
33 if !self.is_enabled() {
34 return None;
35 }
36
37 if let MouseEventKind::Down(MouseButton::Left) = event.kind {
38 if let Some(hit) = layout.hit_test(event.column, event.row) {
39 match hit {
40 MapHit::ExpandArrow(index) => {
41 self.toggle_expand(index);
42 return Some(MapEvent::EntryToggled(index, self.is_expanded(index)));
43 }
44 MapHit::EntryKey(index) => {
45 self.focus_entry(index);
46 return Some(MapEvent::FocusChanged(Some(index)));
47 }
48 MapHit::RemoveButton(index) => {
49 self.remove_entry(index);
50 return Some(MapEvent::EntryRemoved(index));
51 }
52 MapHit::AddRow => {
53 self.focus_new_entry();
54 return Some(MapEvent::FocusChanged(None));
55 }
56 }
57 }
58 }
59 None
60 }
61
62 pub fn handle_key(&mut self, key: KeyEvent) -> Option<MapEvent> {
68 if !self.is_enabled() {
69 return None;
70 }
71
72 match key.code {
73 KeyCode::Enter => {
74 if self.focused_entry.is_none() && !self.new_key_text.is_empty() {
75 let key = self.new_key_text.clone();
76 self.add_entry_from_input();
77 Some(MapEvent::EntryAdded(key))
78 } else if let Some(index) = self.focused_entry {
79 self.toggle_expand(index);
81 Some(MapEvent::EntryToggled(index, self.is_expanded(index)))
82 } else {
83 None
84 }
85 }
86 KeyCode::Delete => {
87 if let Some(index) = self.focused_entry {
88 self.remove_entry(index);
89 Some(MapEvent::EntryRemoved(index))
90 } else {
91 None
92 }
93 }
94 KeyCode::Backspace => {
95 if self.focused_entry.is_none() && self.cursor > 0 {
96 self.backspace();
97 Some(MapEvent::NewKeyChanged(self.new_key_text.clone()))
98 } else {
99 None
100 }
101 }
102 KeyCode::Left => {
103 self.move_left();
104 None
105 }
106 KeyCode::Right => {
107 self.move_right();
108 None
109 }
110 KeyCode::Up => {
111 self.focus_prev();
112 Some(MapEvent::FocusChanged(self.focused_entry))
113 }
114 KeyCode::Down => {
115 self.focus_next();
116 Some(MapEvent::FocusChanged(self.focused_entry))
117 }
118 KeyCode::Char(' ') if self.focused_entry.is_some() => {
119 if let Some(index) = self.focused_entry {
121 self.toggle_expand(index);
122 Some(MapEvent::EntryToggled(index, self.is_expanded(index)))
123 } else {
124 None
125 }
126 }
127 KeyCode::Char(c) => {
128 if self.focused_entry.is_none() {
129 self.insert(c);
130 Some(MapEvent::NewKeyChanged(self.new_key_text.clone()))
131 } else {
132 None
133 }
134 }
135 _ => None,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crossterm::event::KeyModifiers;
144 use ratatui::layout::Rect;
145
146 use crate::view::controls::map_input::MapEntryLayout;
147
148 fn make_layout() -> MapLayout {
149 MapLayout {
150 full_area: Rect::new(0, 0, 50, 5),
151 entry_areas: vec![MapEntryLayout {
152 index: 0,
153 row_area: Rect::new(0, 1, 50, 1),
154 expand_area: Rect::new(2, 1, 1, 1),
155 key_area: Rect::new(4, 1, 10, 1),
156 remove_area: Rect::new(40, 1, 3, 1),
157 }],
158 add_row_area: Some(Rect::new(0, 2, 50, 1)),
159 }
160 }
161
162 fn mouse_down(x: u16, y: u16) -> MouseEvent {
163 MouseEvent {
164 kind: MouseEventKind::Down(MouseButton::Left),
165 column: x,
166 row: y,
167 modifiers: KeyModifiers::empty(),
168 }
169 }
170
171 #[test]
172 fn test_click_expand_arrow() {
173 let mut state = MapState::new("Test");
174 state.add_entry("key1".to_string(), serde_json::json!({"foo": "bar"}));
175 let layout = make_layout();
176
177 let result = state.handle_mouse(mouse_down(2, 1), &layout);
178 assert_eq!(result, Some(MapEvent::EntryToggled(0, true)));
179 assert!(state.is_expanded(0));
180 }
181
182 #[test]
183 fn test_click_remove_button() {
184 let mut state = MapState::new("Test");
185 state.add_entry("key1".to_string(), serde_json::json!({}));
186 let layout = make_layout();
187
188 let result = state.handle_mouse(mouse_down(40, 1), &layout);
189 assert_eq!(result, Some(MapEvent::EntryRemoved(0)));
190 assert!(state.entries.is_empty());
191 }
192
193 #[test]
194 fn test_click_add_row() {
195 let mut state = MapState::new("Test");
196 state.new_key_text = "newkey".to_string();
197 let layout = make_layout();
198
199 let result = state.handle_mouse(mouse_down(13, 2), &layout);
201 assert_eq!(result, Some(MapEvent::FocusChanged(None)));
202 assert!(state.focused_entry.is_none());
203 }
204
205 #[test]
206 fn test_keyboard_navigation() {
207 let mut state = MapState::new("Test");
208 state.add_entry("a".to_string(), serde_json::json!({}));
209 state.add_entry("b".to_string(), serde_json::json!({}));
210 state.focus_new_entry();
211
212 let up = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
213 let result = state.handle_key(up);
214 assert_eq!(result, Some(MapEvent::FocusChanged(Some(1))));
215
216 let result = state.handle_key(up);
217 assert_eq!(result, Some(MapEvent::FocusChanged(Some(0))));
218 }
219
220 #[test]
221 fn test_enter_adds_entry() {
222 let mut state = MapState::new("Test");
223 state.new_key_text = "newkey".to_string();
224
225 let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
226 let result = state.handle_key(enter);
227 assert_eq!(result, Some(MapEvent::EntryAdded("newkey".to_string())));
228 assert_eq!(state.entries.len(), 1);
229 }
230
231 #[test]
232 fn test_delete_removes_focused_entry() {
233 let mut state = MapState::new("Test");
234 state.add_entry("a".to_string(), serde_json::json!({}));
235 state.focus_entry(0);
236
237 let delete = KeyEvent::new(KeyCode::Delete, KeyModifiers::empty());
238 let result = state.handle_key(delete);
239 assert_eq!(result, Some(MapEvent::EntryRemoved(0)));
240 assert!(state.entries.is_empty());
241 }
242
243 #[test]
244 fn test_typing_in_new_key_field() {
245 let mut state = MapState::new("Test");
246 state.focus_new_entry();
247
248 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
249 let result = state.handle_key(key);
250 assert_eq!(result, Some(MapEvent::NewKeyChanged("a".to_string())));
251 assert_eq!(state.new_key_text, "a");
252 }
253
254 #[test]
255 fn test_space_toggles_expansion() {
256 let mut state = MapState::new("Test");
257 state.add_entry("key1".to_string(), serde_json::json!({"foo": "bar"}));
258 state.focus_entry(0);
259
260 let space = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty());
261 let result = state.handle_key(space);
262 assert_eq!(result, Some(MapEvent::EntryToggled(0, true)));
263 assert!(state.is_expanded(0));
264 }
265}