oxedyne/fe2o3/fe2o3_tui/src/lib_tui/event.rs
18.1 KiB, 9 runs
created by r1870400018:1191, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use crate::lib_tui::{ |
| 2 | action::{ |
| 3 | Action, |
| 4 | ActionData, |
| 5 | }, |
| 6 | text::{ |
| 7 | edit::EditorMode, |
| 8 | typ::TextViewType, |
| 9 | }, |
| 10 | window::{ |
| 11 | WindowManager, |
| 12 | WindowMode, |
| 13 | }, |
| 14 | }; |
| 15 | |
| 16 | use oxedyne_fe2o3_core::prelude::*; |
| 17 | |
| 18 | use crossterm::{ |
| 19 | event::{ |
| 20 | KeyCode, |
| 21 | KeyEvent, |
| 22 | KeyModifiers, |
| 23 | }, |
| 24 | }; |
| 25 | |
| 26 | |
| 27 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 28 | pub enum AppFlow { |
| 29 | Quit, |
| 30 | Continue, |
| 31 | CursorOnly, |
| 32 | FocalWindowRender, |
| 33 | FullScreenRender, |
| 34 | } |
| 35 | |
| 36 | #[derive(Clone, Debug, Default)] |
| 37 | pub struct KeyState { |
| 38 | buf: String, |
| 39 | } |
| 40 | |
| 41 | impl KeyState { |
| 42 | |
| 43 | pub fn buffer(&self) -> &String { |
| 44 | &self.buf |
| 45 | } |
| 46 | |
| 47 | pub fn clear(&mut self) { |
| 48 | self.buf = String::new(); |
| 49 | } |
| 50 | |
| 51 | pub fn is_empty(&self) -> bool { |
| 52 | self.buf.len() == 0 |
| 53 | } |
| 54 | |
| 55 | pub fn push(&mut self, c: char) { |
| 56 | self.buf.push(c); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | impl WindowManager<'_> { |
| 61 | |
| 62 | pub fn native_key_handler( |
| 63 | &mut self, |
| 64 | key: KeyEvent, |
| 65 | data: Option<ActionData>, |
| 66 | ) |
| 67 | -> Outcome<AppFlow> |
| 68 | { |
| 69 | match key { |
| 70 | // ┌─────────────────────────────┐ |
| 71 | // │ ALL MODES │ |
| 72 | // └─────────────────────────────┘ |
| 73 | // Ctrl-C: Exit app. |
| 74 | KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, .. } => { |
| 75 | return self.act(&Action::ExitApp, data); |
| 76 | } |
| 77 | // Esc: Exiting modes. |
| 78 | KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::NONE, .. } => { |
| 79 | match self.mode { |
| 80 | WindowMode::Interaction => { |
| 81 | // If the user is in editor navigation mode, Esc drops them out of editing |
| 82 | // and back to window navigation mode. If the user is in another editor |
| 83 | // mode, they are dropped back to editor navigation mode. |
| 84 | let result = self.get_focal_window_text_box_mut(); |
| 85 | if let Some(tbox) = res!(result) { |
| 86 | match &mut tbox.tview.vtyp { |
| 87 | TextViewType::Editable(editor) => { |
| 88 | match editor.mode { |
| 89 | EditorMode::Navigation => { |
| 90 | res!(self.set_mode_update_menu(WindowMode::Navigation, None)); |
| 91 | } |
| 92 | _ => { |
| 93 | editor.mode = EditorMode::Navigation; |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | _ => { |
| 98 | res!(self.set_mode_update_menu(WindowMode::Navigation, None)); |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | self.key_state.clear(); |
| 103 | } |
| 104 | WindowMode::WindowManagement => { |
| 105 | return self.act(&Action::ReturnToWindowNavigationMode, data); |
| 106 | } |
| 107 | WindowMode::BorderManagement => { |
| 108 | return self.act(&Action::ReturnToWindowManagementMode, data); |
| 109 | } |
| 110 | _ => {} |
| 111 | } |
| 112 | } |
| 113 | // Shift + PageUp: Move menu selection up one line in current tab of menu window. |
| 114 | KeyEvent { code: KeyCode::PageUp, modifiers: KeyModifiers::SHIFT, .. } => { |
| 115 | return self.act(&Action::MoveCurrentMenuFocusUp, data); |
| 116 | } |
| 117 | // Shift + PageDown: Move menu selection down one line in current tab of menu window. |
| 118 | KeyEvent { code: KeyCode::PageDown, modifiers: KeyModifiers::SHIFT, .. } => { |
| 119 | return self.act(&Action::MoveCurrentMenuFocusDown, data); |
| 120 | } |
| 121 | // Home: Perform currently highlighted menu action. |
| 122 | KeyEvent { code: KeyCode::Home, modifiers: KeyModifiers::NONE, .. } => { |
| 123 | return self.act(&Action::ExecuteCurrentMenuAction, data); |
| 124 | } |
| 125 | // ┌─────────────────────────────┐ |
| 126 | // │ NAVIGATION MODE │ |
| 127 | // └─────────────────────────────┘ |
| 128 | // Tab: Rotate the focus amongst windows. |
| 129 | KeyEvent { code: KeyCode::Tab, modifiers: KeyModifiers::NONE, .. } |
| 130 | if self.mode == WindowMode::Navigation => |
| 131 | { |
| 132 | return self.act(&Action::MoveToNextWindow, data); |
| 133 | } |
| 134 | // Enter: |
| 135 | // - Transition from window navigation to interaction mode (enter the window). |
| 136 | // - If the key buffer is not empty, try and jump straight into the appropriate window. |
| 137 | KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, .. } |
| 138 | if self.mode == WindowMode::Navigation => |
| 139 | { |
| 140 | return self.act(&Action::EnterWindow, data); |
| 141 | } |
| 142 | // Collect keys into the key buffer, to form a label which we can navigate to. |
| 143 | KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, .. } |
| 144 | if self.mode == WindowMode::Navigation |
| 145 | && c != ' ' => |
| 146 | { |
| 147 | self.key_state.push(c); |
| 148 | } |
| 149 | // Space: Transition from window navigation to management mode. |
| 150 | KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, .. } |
| 151 | if self.mode == WindowMode::Navigation => |
| 152 | { |
| 153 | match c { |
| 154 | ' ' => { |
| 155 | return self.act(&Action::EnterWindowManagementMode, data); |
| 156 | } |
| 157 | _ => {} |
| 158 | } |
| 159 | } |
| 160 | // ┌─────────────────────────────┐ |
| 161 | // │ WINDOW MANAGEMENT MODE │ |
| 162 | // └─────────────────────────────┘ |
| 163 | // Space: Transition to border management mode. |
| 164 | KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, .. } |
| 165 | if self.mode == WindowMode::WindowManagement => |
| 166 | { |
| 167 | match c { |
| 168 | ' ' => { |
| 169 | return self.act(&Action::EnterBorderManagementMode, data); |
| 170 | } |
| 171 | _ => {} |
| 172 | } |
| 173 | } |
| 174 | // Up: Translate window in window management mode. |
| 175 | KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, .. } |
| 176 | if self.mode == WindowMode::WindowManagement => |
| 177 | { |
| 178 | return self.act(&Action::SlideWindowUp, data); |
| 179 | } |
| 180 | // Down: Translate window in window management mode. |
| 181 | KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, .. } |
| 182 | if self.mode == WindowMode::WindowManagement => |
| 183 | { |
| 184 | return self.act(&Action::SlideWindowDown, data); |
| 185 | } |
| 186 | // Right: Translate window in window management mode. |
| 187 | KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::NONE, .. } |
| 188 | if self.mode == WindowMode::WindowManagement => |
| 189 | { |
| 190 | return self.act(&Action::SlideWindowRight, data); |
| 191 | } |
| 192 | // Left: Translate window in window management mode. |
| 193 | KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE, .. } |
| 194 | if self.mode == WindowMode::WindowManagement => |
| 195 | { |
| 196 | return self.act(&Action::SlideWindowLeft, data); |
| 197 | } |
| 198 | // ┌─────────────────────────────┐ |
| 199 | // │ BORDER MANAGEMENT MODE │ |
| 200 | // └─────────────────────────────┘ |
| 201 | // Space: Select next border. |
| 202 | KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, .. } |
| 203 | if self.mode == WindowMode::BorderManagement => |
| 204 | { |
| 205 | match c { |
| 206 | ' ' => { |
| 207 | return self.act(&Action::MoveToNextBorder, data); |
| 208 | } |
| 209 | _ => {} |
| 210 | } |
| 211 | } |
| 212 | // Up: Resize in vertical direction in border management mode. |
| 213 | KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, .. } |
| 214 | if self.mode == WindowMode::BorderManagement => |
| 215 | { |
| 216 | return self.act(&Action::DragBorderUp, data); |
| 217 | } |
| 218 | // Down: Resize in vertical direction in border management mode. |
| 219 | KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, .. } |
| 220 | if self.mode == WindowMode::BorderManagement => |
| 221 | { |
| 222 | return self.act(&Action::DragBorderDown, data); |
| 223 | } |
| 224 | // Right: Resize in horizontal direction in border management mode. |
| 225 | KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::NONE, .. } |
| 226 | if self.mode == WindowMode::BorderManagement => |
| 227 | { |
| 228 | return self.act(&Action::DragBorderRight, data); |
| 229 | } |
| 230 | // Left: Resize in horizontal direction in border management mode. |
| 231 | KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE, .. } |
| 232 | if self.mode == WindowMode::BorderManagement => |
| 233 | { |
| 234 | return self.act(&Action::DragBorderLeft, data); |
| 235 | } |
| 236 | // ┌─────────────────────────────┐ |
| 237 | // │ INTERACTION MODE │ |
| 238 | // └─────────────────────────────┘ |
| 239 | // Up: Move cursor up one line, if possible. |
| 240 | KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, .. } |
| 241 | if self.mode == WindowMode::Interaction => |
| 242 | { |
| 243 | return self.act(&Action::MoveCursorUp, data); |
| 244 | } |
| 245 | // Down: Move cursor down one line, if possible. |
| 246 | KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, .. } |
| 247 | if self.mode == WindowMode::Interaction => |
| 248 | { |
| 249 | return self.act(&Action::MoveCursorDown, data); |
| 250 | } |
| 251 | // Right: Move one character to the right, if possible. |
| 252 | KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::NONE, .. } |
| 253 | if self.mode == WindowMode::Interaction => |
| 254 | { |
| 255 | return self.act(&Action::MoveCursorRight, data); |
| 256 | } |
| 257 | // Left: Move one character to the left, if possible. |
| 258 | KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE, .. } |
| 259 | if self.mode == WindowMode::Interaction => |
| 260 | { |
| 261 | return self.act(&Action::MoveCursorLeft, data); |
| 262 | } |
| 263 | // Shift Up: Pan text view up, if possible. |
| 264 | KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::SHIFT, .. } |
| 265 | if self.mode == WindowMode::Interaction => |
| 266 | { |
| 267 | return self.act(&Action::PanTextViewUp, data); |
| 268 | } |
| 269 | // Shift Down: Pan text view down, if possible. |
| 270 | KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::SHIFT, .. } |
| 271 | if self.mode == WindowMode::Interaction => |
| 272 | { |
| 273 | return self.act(&Action::PanTextViewDown, data); |
| 274 | } |
| 275 | // Shift Right: Pan text view right, if possible. |
| 276 | KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::SHIFT, .. } |
| 277 | if self.mode == WindowMode::Interaction => |
| 278 | { |
| 279 | return self.act(&Action::PanTextViewRight, data); |
| 280 | } |
| 281 | // Shift Left: Pan text view left, if possible. |
| 282 | KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::SHIFT, .. } |
| 283 | if self.mode == WindowMode::Interaction => |
| 284 | { |
| 285 | return self.act(&Action::PanTextViewLeft, data); |
| 286 | } |
| 287 | // Char: |
| 288 | // - Transition from intra-window navigation to other editor modes. |
| 289 | // - Character insertion in insert or replace editor modes. |
| 290 | KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT, .. } |
| 291 | if self.mode == WindowMode::Interaction => |
| 292 | { |
| 293 | let result = self.get_focal_window_mut(); |
| 294 | let win = res!(result); |
| 295 | if let Some(tbox) = win.get_focal_text_box_mut() { |
| 296 | if let Some(editor) = tbox.tview.vtyp.get_editor_mut() { |
| 297 | match editor.mode { |
| 298 | EditorMode::Navigation => { |
| 299 | match c { |
| 300 | 'i' => editor.mode = EditorMode::Insert, |
| 301 | 'r' => editor.mode = EditorMode::Replace, |
| 302 | 'm' => editor.mode = EditorMode::Modify, |
| 303 | _ => {} |
| 304 | } |
| 305 | } |
| 306 | EditorMode::Insert | EditorMode::Replace => { |
| 307 | let result = tbox.tview.atext.get_text_lines_mut(); |
| 308 | let mut text_lines = res!(result); |
| 309 | text_lines.add_char( |
| 310 | &mut editor.nav.cursor, |
| 311 | c, |
| 312 | editor.mode == EditorMode::Replace, |
| 313 | ); |
| 314 | } |
| 315 | _ => {} |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | // Shift + Tab doesn't work reliably. |
| 321 | // - Rotate the focus amongst tabs. |
| 322 | //KeyEvent { code: KeyCode::BackTab, modifiers: KeyModifiers::NONE, .. } |
| 323 | // if self.mode == WindowMode::Interaction => |
| 324 | //{ |
| 325 | // debug!("Shift + Tab"); |
| 326 | // let win = res!(self.get_focal_window_mut()); |
| 327 | // if let Some(tbox) = win.get_focal_text_box() { |
| 328 | // match &tbox.tview.vtyp { |
| 329 | // TextViewType::Editable(editor) => match editor.mode { |
| 330 | // EditorMode::Navigation => { |
| 331 | // win.state.text_boxes.next_focus(); |
| 332 | // } |
| 333 | // _ => {} |
| 334 | // } |
| 335 | // _ => { |
| 336 | // win.state.text_boxes.next_focus(); |
| 337 | // } |
| 338 | // } |
| 339 | // } |
| 340 | //} |
| 341 | // Tab: |
| 342 | // - If navigating: rotate highlighting of line focus ranges in text. |
| 343 | // - If editing: insert tab characters (defined via configuration) into current text. |
| 344 | KeyEvent { code: KeyCode::Tab, modifiers: KeyModifiers::NONE, .. } |
| 345 | if self.mode == WindowMode::Interaction => |
| 346 | { |
| 347 | return self.act(&Action::MoveToNextTab, data); |
| 348 | } |
| 349 | // Backspace: Delete character to the left of the cursors, dragging the remainder of |
| 350 | // the line from the right. |
| 351 | KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::NONE, .. } |
| 352 | if self.mode == WindowMode::Interaction => |
| 353 | { |
| 354 | let result = self.get_focal_window_text_box_mut(); |
| 355 | if let Some(tbox) = res!(result) { |
| 356 | if let Some(editor) = tbox.tview.vtyp.get_editor_mut() { |
| 357 | match editor.mode { |
| 358 | EditorMode::Insert | EditorMode::Replace | EditorMode::Modify => { |
| 359 | let result = tbox.tview.atext.get_text_lines_mut(); |
| 360 | let mut text_lines = res!(result); |
| 361 | text_lines.backspace(&mut editor.nav.cursor); |
| 362 | } |
| 363 | _ => {} |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | // Delete: Delete character under the cursor, dragging the remainder of the line from |
| 369 | // the right. |
| 370 | KeyEvent { code: KeyCode::Delete, modifiers: KeyModifiers::NONE, .. } |
| 371 | if self.mode == WindowMode::Interaction => |
| 372 | { |
| 373 | let result = self.get_focal_window_text_box_mut(); |
| 374 | if let Some(tbox) = res!(result) { |
| 375 | if let Some(editor) = tbox.tview.vtyp.get_editor_mut() { |
| 376 | match editor.mode { |
| 377 | EditorMode::Insert | EditorMode::Replace | EditorMode::Modify => { |
| 378 | let result = tbox.tview.atext.get_text_lines_mut(); |
| 379 | let mut text_lines = res!(result); |
| 380 | text_lines.delete_char(&mut editor.nav.cursor); |
| 381 | } |
| 382 | _ => {} |
| 383 | } |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | // Enter: Insert a new line. |
| 388 | KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, .. } |
| 389 | if self.mode == WindowMode::Interaction => |
| 390 | { |
| 391 | let result = self.get_focal_window_text_box_mut(); |
| 392 | if let Some(tbox) = res!(result) { |
| 393 | if let Some(editor) = tbox.tview.vtyp.get_editor_mut() { |
| 394 | match editor.mode { |
| 395 | EditorMode::Insert | EditorMode::Replace | EditorMode::Modify => { |
| 396 | let result = tbox.tview.atext.get_text_lines_mut(); |
| 397 | let mut text_lines = res!(result); |
| 398 | text_lines.enter_new_line(&mut editor.nav.cursor); |
| 399 | } |
| 400 | _ => {} |
| 401 | } |
| 402 | } |
| 403 | } |
| 404 | } |
| 405 | _ => {} |
| 406 | } |
| 407 | Ok(AppFlow::FullScreenRender) |
| 408 | } |
| 409 | } |