From 873fd03c1d7b56a50c5c214c255e2ef998634dd0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 12 Aug 2025 16:58:10 +0200 Subject: [PATCH] Move keyboard definitions into their own file --- src/bin/edit/draw_editor.rs | 16 +- src/bin/edit/draw_filepicker.rs | 20 +- src/bin/edit/draw_menubar.rs | 42 ++--- src/bin/edit/draw_statusbar.rs | 4 +- src/bin/edit/main.rs | 28 ++- src/input.rs | 317 ++++++++++++++------------------ src/kbd.rs | 114 ++++++++++++ src/lib.rs | 1 + src/tui.rs | 198 ++++++++++---------- 9 files changed, 401 insertions(+), 339 deletions(-) create mode 100644 src/kbd.rs diff --git a/src/bin/edit/draw_editor.rs b/src/bin/edit/draw_editor.rs index 94f7dbf..b013625 100644 --- a/src/bin/edit/draw_editor.rs +++ b/src/bin/edit/draw_editor.rs @@ -6,7 +6,7 @@ use std::num::ParseIntError; use edit::framebuffer::IndexedColor; use edit::helpers::*; use edit::icu; -use edit::input::{kbmod, vk}; +use edit::kbd::*; use edit::tui::*; use crate::localization::*; @@ -68,7 +68,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) { ctx.attr_background_rgba(ctx.indexed(IndexedColor::White)); ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::Black)); { - if ctx.contains_focus() && ctx.consume_shortcut(vk::ESCAPE) { + if ctx.contains_focus() && ctx.consume_shortcut(VK_ESCAPE) { state.wants_search.kind = StateSearchKind::Hidden; } @@ -90,7 +90,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) { if focus == StateSearchKind::Search { ctx.steal_focus(); } - if ctx.is_focused() && ctx.consume_shortcut(vk::RETURN) { + if ctx.is_focused() && ctx.consume_shortcut(VK_RETURN) { action = Some(SearchAction::Search); } } @@ -105,9 +105,9 @@ fn draw_search(ctx: &mut Context, state: &mut State) { ctx.steal_focus(); } if ctx.is_focused() { - if ctx.consume_shortcut(vk::RETURN) { + if ctx.consume_shortcut(VK_RETURN) { action = Some(SearchAction::Replace); - } else if ctx.consume_shortcut(kbmod::CTRL_ALT | vk::RETURN) { + } else if ctx.consume_shortcut(MOD_CTRL | MOD_ALT | VK_RETURN) { action = Some(SearchAction::ReplaceAll); } } @@ -271,9 +271,9 @@ pub fn draw_handle_wants_close(ctx: &mut Context, state: &mut State) { // Handle accelerator shortcuts if contains_focus { - if ctx.consume_shortcut(vk::S) { + if ctx.consume_shortcut(VK_S) { action = Action::Save; - } else if ctx.consume_shortcut(vk::N) { + } else if ctx.consume_shortcut(VK_N) { action = Action::Discard; } } @@ -319,7 +319,7 @@ pub fn draw_goto_menu(ctx: &mut Context, state: &mut State) { ctx.attr_intrinsic_size(Size { width: 24, height: 1 }); ctx.steal_focus(); - if ctx.consume_shortcut(vk::RETURN) { + if ctx.consume_shortcut(VK_RETURN) { match validate_goto_point(&state.goto_target) { Ok(point) => { let mut buf = doc.buffer.borrow_mut(); diff --git a/src/bin/edit/draw_filepicker.rs b/src/bin/edit/draw_filepicker.rs index 3fae635..9ecd632 100644 --- a/src/bin/edit/draw_filepicker.rs +++ b/src/bin/edit/draw_filepicker.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use edit::arena::scratch_arena; use edit::framebuffer::IndexedColor; use edit::helpers::*; -use edit::input::{kbmod, vk}; +use edit::kbd::*; use edit::tui::*; use edit::{icu, path}; @@ -75,9 +75,9 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { if !state.file_picker_autocomplete.is_empty() { let bg = ctx.indexed_alpha(IndexedColor::Background, 3, 4); let fg = ctx.contrasted(bg); - let focus_list_beg = ctx.is_focused() && ctx.consume_shortcut(vk::DOWN); - let focus_list_end = ctx.is_focused() && ctx.consume_shortcut(vk::UP); - let mut autocomplete_done = ctx.consume_shortcut(vk::ESCAPE); + let focus_list_beg = ctx.is_focused() && ctx.consume_shortcut(VK_DOWN); + let focus_list_end = ctx.is_focused() && ctx.consume_shortcut(VK_UP); + let mut autocomplete_done = ctx.consume_shortcut(VK_ESCAPE); ctx.list_begin("suggestions"); ctx.attr_float(FloatSpec { @@ -105,8 +105,8 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { if (is_first && focus_list_beg) || (is_last && focus_list_end) { ctx.list_item_steal_focus(); } else if ctx.is_focused() - && ((is_first && ctx.consume_shortcut(vk::UP)) - || (is_last && ctx.consume_shortcut(vk::DOWN))) + && ((is_first && ctx.consume_shortcut(VK_UP)) + || (is_last && ctx.consume_shortcut(VK_DOWN))) { ctx.toss_focus_up(); } @@ -126,7 +126,7 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { } } - if ctx.is_focused() && ctx.consume_shortcut(vk::RETURN) { + if ctx.is_focused() && ctx.consume_shortcut(VK_RETURN) { activated = true; } } @@ -170,7 +170,7 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { ctx.scrollarea_end(); if contains_focus - && (ctx.consume_shortcut(vk::BACK) || ctx.consume_shortcut(kbmod::ALT | vk::UP)) + && (ctx.consume_shortcut(VK_BACK) || ctx.consume_shortcut(MOD_ALT | VK_UP)) { state.file_picker_pending_name = "..".into(); activated = true; @@ -224,8 +224,8 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) { ctx.table_end(); if contains_focus { - save |= ctx.consume_shortcut(vk::Y); - if ctx.consume_shortcut(vk::N) { + save |= ctx.consume_shortcut(VK_Y); + if ctx.consume_shortcut(VK_N) { state.file_picker_overwrite_warning = None; } } diff --git a/src/bin/edit/draw_menubar.rs b/src/bin/edit/draw_menubar.rs index 9fe8b7c..92f621d 100644 --- a/src/bin/edit/draw_menubar.rs +++ b/src/bin/edit/draw_menubar.rs @@ -3,7 +3,7 @@ use edit::arena_format; use edit::helpers::*; -use edit::input::{kbmod, vk}; +use edit::kbd::*; use edit::tui::*; use crate::localization::*; @@ -19,7 +19,7 @@ pub fn draw_menubar(ctx: &mut Context, state: &mut State) { if ctx.menubar_menu_begin(loc(LocId::File), 'F') { draw_menu_file(ctx, state); } - if !contains_focus && ctx.consume_shortcut(vk::F10) { + if !contains_focus && ctx.consume_shortcut(VK_F10) { ctx.steal_focus(); } if state.documents.active().is_some() { @@ -38,24 +38,24 @@ pub fn draw_menubar(ctx: &mut Context, state: &mut State) { } fn draw_menu_file(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_button(loc(LocId::FileNew), 'N', kbmod::CTRL | vk::N) { + if ctx.menubar_menu_button(loc(LocId::FileNew), 'N', MOD_CTRL | VK_N) { draw_add_untitled_document(ctx, state); } - if ctx.menubar_menu_button(loc(LocId::FileOpen), 'O', kbmod::CTRL | vk::O) { + if ctx.menubar_menu_button(loc(LocId::FileOpen), 'O', MOD_CTRL | VK_O) { state.wants_file_picker = StateFilePicker::Open; } if state.documents.active().is_some() { - if ctx.menubar_menu_button(loc(LocId::FileSave), 'S', kbmod::CTRL | vk::S) { + if ctx.menubar_menu_button(loc(LocId::FileSave), 'S', MOD_CTRL | VK_S) { state.wants_save = true; } - if ctx.menubar_menu_button(loc(LocId::FileSaveAs), 'A', vk::NULL) { + if ctx.menubar_menu_button(loc(LocId::FileSaveAs), 'A', VK_NULL) { state.wants_file_picker = StateFilePicker::SaveAs; } - if ctx.menubar_menu_button(loc(LocId::FileClose), 'C', kbmod::CTRL | vk::W) { + if ctx.menubar_menu_button(loc(LocId::FileClose), 'C', MOD_CTRL | VK_W) { state.wants_close = true; } } - if ctx.menubar_menu_button(loc(LocId::FileExit), 'X', kbmod::CTRL | vk::Q) { + if ctx.menubar_menu_button(loc(LocId::FileExit), 'X', MOD_CTRL | VK_Q) { state.wants_exit = true; } ctx.menubar_menu_end(); @@ -65,37 +65,37 @@ fn draw_menu_edit(ctx: &mut Context, state: &mut State) { let doc = state.documents.active().unwrap(); let mut tb = doc.buffer.borrow_mut(); - if ctx.menubar_menu_button(loc(LocId::EditUndo), 'U', kbmod::CTRL | vk::Z) { + if ctx.menubar_menu_button(loc(LocId::EditUndo), 'U', MOD_CTRL | VK_Z) { tb.undo(); ctx.needs_rerender(); } - if ctx.menubar_menu_button(loc(LocId::EditRedo), 'R', kbmod::CTRL | vk::Y) { + if ctx.menubar_menu_button(loc(LocId::EditRedo), 'R', MOD_CTRL | VK_Y) { tb.redo(); ctx.needs_rerender(); } - if ctx.menubar_menu_button(loc(LocId::EditCut), 'T', kbmod::CTRL | vk::X) { + if ctx.menubar_menu_button(loc(LocId::EditCut), 'T', MOD_CTRL | VK_X) { tb.cut(ctx.clipboard_mut()); ctx.needs_rerender(); } - if ctx.menubar_menu_button(loc(LocId::EditCopy), 'C', kbmod::CTRL | vk::C) { + if ctx.menubar_menu_button(loc(LocId::EditCopy), 'C', MOD_CTRL | VK_C) { tb.copy(ctx.clipboard_mut()); ctx.needs_rerender(); } - if ctx.menubar_menu_button(loc(LocId::EditPaste), 'P', kbmod::CTRL | vk::V) { + if ctx.menubar_menu_button(loc(LocId::EditPaste), 'P', MOD_CTRL | VK_V) { tb.paste(ctx.clipboard_ref()); ctx.needs_rerender(); } if state.wants_search.kind != StateSearchKind::Disabled { - if ctx.menubar_menu_button(loc(LocId::EditFind), 'F', kbmod::CTRL | vk::F) { + if ctx.menubar_menu_button(loc(LocId::EditFind), 'F', MOD_CTRL | VK_F) { state.wants_search.kind = StateSearchKind::Search; state.wants_search.focus = true; } - if ctx.menubar_menu_button(loc(LocId::EditReplace), 'L', kbmod::CTRL | vk::R) { + if ctx.menubar_menu_button(loc(LocId::EditReplace), 'L', MOD_CTRL | VK_R) { state.wants_search.kind = StateSearchKind::Replace; state.wants_search.focus = true; } } - if ctx.menubar_menu_button(loc(LocId::EditSelectAll), 'A', kbmod::CTRL | vk::A) { + if ctx.menubar_menu_button(loc(LocId::EditSelectAll), 'A', MOD_CTRL | VK_A) { tb.select_all(); ctx.needs_rerender(); } @@ -108,16 +108,16 @@ fn draw_menu_view(ctx: &mut Context, state: &mut State) { let word_wrap = tb.is_word_wrap_enabled(); // All values on the statusbar are currently document specific. - if ctx.menubar_menu_button(loc(LocId::ViewFocusStatusbar), 'S', vk::NULL) { + if ctx.menubar_menu_button(loc(LocId::ViewFocusStatusbar), 'S', VK_NULL) { state.wants_statusbar_focus = true; } - if ctx.menubar_menu_button(loc(LocId::ViewGoToFile), 'F', kbmod::CTRL | vk::P) { + if ctx.menubar_menu_button(loc(LocId::ViewGoToFile), 'F', MOD_CTRL | VK_P) { state.wants_go_to_file = true; } - if ctx.menubar_menu_button(loc(LocId::FileGoto), 'G', kbmod::CTRL | vk::G) { + if ctx.menubar_menu_button(loc(LocId::FileGoto), 'G', MOD_CTRL | VK_G) { state.wants_goto = true; } - if ctx.menubar_menu_checkbox(loc(LocId::ViewWordWrap), 'W', kbmod::ALT | vk::Z, word_wrap) { + if ctx.menubar_menu_checkbox(loc(LocId::ViewWordWrap), 'W', MOD_ALT | VK_Z, word_wrap) { tb.set_word_wrap(!word_wrap); ctx.needs_rerender(); } @@ -127,7 +127,7 @@ fn draw_menu_view(ctx: &mut Context, state: &mut State) { } fn draw_menu_help(ctx: &mut Context, state: &mut State) { - if ctx.menubar_menu_button(loc(LocId::HelpAbout), 'A', vk::NULL) { + if ctx.menubar_menu_button(loc(LocId::HelpAbout), 'A', VK_NULL) { state.wants_about = true; } ctx.menubar_menu_end(); diff --git a/src/bin/edit/draw_statusbar.rs b/src/bin/edit/draw_statusbar.rs index f7a631a..e5cc8cc 100644 --- a/src/bin/edit/draw_statusbar.rs +++ b/src/bin/edit/draw_statusbar.rs @@ -5,7 +5,7 @@ use edit::arena::scratch_arena; use edit::framebuffer::{Attributes, IndexedColor}; use edit::fuzzy::score_fuzzy; use edit::helpers::*; -use edit::input::vk; +use edit::kbd::*; use edit::tui::*; use edit::{arena_format, icu}; @@ -97,7 +97,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) { ctx.attr_padding(Rect::two(0, 1)); ctx.table_set_cell_gap(Size { width: 1, height: 0 }); { - if ctx.contains_focus() && ctx.consume_shortcut(vk::RETURN) { + if ctx.contains_focus() && ctx.consume_shortcut(VK_RETURN) { ctx.toss_focus_up(); } diff --git a/src/bin/edit/main.rs b/src/bin/edit/main.rs index 5f310d6..ea3ed54 100644 --- a/src/bin/edit/main.rs +++ b/src/bin/edit/main.rs @@ -25,11 +25,11 @@ use draw_statusbar::*; use edit::arena::{self, Arena, ArenaString, scratch_arena}; use edit::framebuffer::{self, IndexedColor}; use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size}; -use edit::input::{self, kbmod, vk}; +use edit::kbd::*; use edit::oklab::oklab_blend; use edit::tui::*; use edit::vt::{self, Token}; -use edit::{apperr, arena_format, base64, path, sys, unicode}; +use edit::{apperr, arena_format, base64, input, path, sys, unicode}; use localization::*; use state::*; @@ -322,31 +322,29 @@ fn draw(ctx: &mut Context, state: &mut State) { if let Some(key) = ctx.keyboard_input() { // Shortcuts that are not handled as part of the textarea, etc. - if key == kbmod::CTRL | vk::N { + if key == MOD_CTRL | VK_N { draw_add_untitled_document(ctx, state); - } else if key == kbmod::CTRL | vk::O { + } else if key == MOD_CTRL | VK_O { state.wants_file_picker = StateFilePicker::Open; - } else if key == kbmod::CTRL | vk::S { + } else if key == MOD_CTRL | VK_S { state.wants_save = true; - } else if key == kbmod::CTRL_SHIFT | vk::S { + } else if key == MOD_CTRL | MOD_SHIFT | VK_S { state.wants_file_picker = StateFilePicker::SaveAs; - } else if key == kbmod::CTRL | vk::W { + } else if key == MOD_CTRL | VK_W { state.wants_close = true; - } else if key == kbmod::CTRL | vk::P { + } else if key == MOD_CTRL | VK_P { state.wants_go_to_file = true; - } else if key == kbmod::CTRL | vk::Q { + } else if key == MOD_CTRL | VK_Q { state.wants_exit = true; - } else if key == kbmod::CTRL | vk::G { + } else if key == MOD_CTRL | VK_G { state.wants_goto = true; - } else if key == kbmod::CTRL | vk::F && state.wants_search.kind != StateSearchKind::Disabled - { + } else if key == MOD_CTRL | VK_F && state.wants_search.kind != StateSearchKind::Disabled { state.wants_search.kind = StateSearchKind::Search; state.wants_search.focus = true; - } else if key == kbmod::CTRL | vk::R && state.wants_search.kind != StateSearchKind::Disabled - { + } else if key == MOD_CTRL | VK_R && state.wants_search.kind != StateSearchKind::Disabled { state.wants_search.kind = StateSearchKind::Replace; state.wants_search.focus = true; - } else if key == vk::F3 { + } else if key == VK_F3 { search_execute(ctx, state, SearchAction::Search); } else { return; diff --git a/src/input.rs b/src/input.rs index 8808e14..ac187e8 100644 --- a/src/input.rs +++ b/src/input.rs @@ -6,14 +6,16 @@ //! In the future this allows us to take apart the application and //! support input schemes that aren't VT, such as UEFI, or GUI. -use std::mem; +use std::ops::{BitOr, BitOrAssign}; +use std::{fmt, mem}; use crate::helpers::{CoordType, Point, Size}; +use crate::kbd::*; use crate::vt; /// Represents a key/modifier combination. /// -/// TODO: Is this a good idea? I did it to allow typing `kbmod::CTRL | vk::A`. +/// TODO: Is this a good idea? I did it to allow typing `MOD_CTRL | VK_A`. /// The reason it's an awkward u32 and not a struct is to hopefully make ABIs easier later. /// Of course you could just translate on the ABI boundary, but my hope is that this /// design lets me realize some restrictions early on that I can't foresee yet. @@ -32,7 +34,7 @@ impl InputKey { } else if ch >= 'a' && ch <= 'z' { Some(Self(ch as u32 & !0x20)) // Shift a-z to A-Z } else if ch >= 'A' && ch <= 'Z' { - Some(Self(kbmod::SHIFT.0 | ch as u32)) + Some(Self(MOD_SHIFT.0 | ch as u32)) } else { None } @@ -59,13 +61,62 @@ impl InputKey { } } +impl fmt::Debug for InputKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self.0 { + 0x00 => "NULL", + 0x08 => "BACK", + 0x09 => "TAB", + 0x0D => "RETURN", + 0x1B => "ESCAPE", + 0x20 => "SPACE", + 0x21 => "PRIOR", + 0x22 => "NEXT", + + 0x23 => "END", + 0x24 => "HOME", + + 0x25 => "LEFT", + 0x26 => "UP", + 0x27 => "RIGHT", + 0x28 => "DOWN", + + 0x2D => "INSERT", + 0x2E => "DELETE", + + 0x6A => "MULTIPLY", + 0x6B => "ADD", + 0x6C => "SEPARATOR", + 0x6D => "SUBTRACT", + 0x6E => "DECIMAL", + 0x6F => "DIVIDE", + _ => { + return { + if matches!(self.0, 0x30..=0x39 | 0x41..=0x5A) { + // 0-9, A-Z + write!(f, "{}", char::from_u32(self.0).unwrap_or('\0')) + } else if matches!(self.0, 0x70..=0x87) { + // F1-F24 + write!(f, "F{}", self.0 - 0x70 + 1) + } else if matches!(self.0, 0x60..=0x69) { + // NUMPAD0-NUMPAD9 + write!(f, "NUMPAD{}", self.0 - 0x60) + } else { + write!(f, "VK_{:02X}", self.0) + } + }; + } + }) + } +} + /// A keyboard modifier. Ctrl/Alt/Shift. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq)] pub struct InputKeyMod(u32); impl InputKeyMod { - const fn new(v: u32) -> Self { + pub(crate) const fn new(v: u32) -> Self { Self(v) } @@ -74,7 +125,7 @@ impl InputKeyMod { } } -impl std::ops::BitOr for InputKey { +impl BitOr for InputKeyMod { type Output = Self; fn bitor(self, rhs: InputKeyMod) -> Self { @@ -82,7 +133,15 @@ impl std::ops::BitOr for InputKey { } } -impl std::ops::BitOr for InputKeyMod { +impl BitOr for InputKey { + type Output = Self; + + fn bitor(self, rhs: InputKeyMod) -> Self { + Self(self.0 | rhs.0) + } +} + +impl BitOr for InputKeyMod { type Output = InputKey; fn bitor(self, rhs: InputKey) -> InputKey { @@ -90,137 +149,30 @@ impl std::ops::BitOr for InputKeyMod { } } -impl std::ops::BitOrAssign for InputKeyMod { +impl BitOrAssign for InputKeyMod { fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0; } } -/// Keyboard keys. -/// -/// The codes defined here match the VK_* constants on Windows. -/// It's a convenient way to handle keyboard input, even on other platforms. -pub mod vk { - use super::InputKey; - - pub const NULL: InputKey = InputKey::new('\0' as u32); - pub const BACK: InputKey = InputKey::new(0x08); - pub const TAB: InputKey = InputKey::new('\t' as u32); - pub const RETURN: InputKey = InputKey::new('\r' as u32); - pub const ESCAPE: InputKey = InputKey::new(0x1B); - pub const SPACE: InputKey = InputKey::new(' ' as u32); - pub const PRIOR: InputKey = InputKey::new(0x21); - pub const NEXT: InputKey = InputKey::new(0x22); - - pub const END: InputKey = InputKey::new(0x23); - pub const HOME: InputKey = InputKey::new(0x24); - - pub const LEFT: InputKey = InputKey::new(0x25); - pub const UP: InputKey = InputKey::new(0x26); - pub const RIGHT: InputKey = InputKey::new(0x27); - pub const DOWN: InputKey = InputKey::new(0x28); - - pub const INSERT: InputKey = InputKey::new(0x2D); - pub const DELETE: InputKey = InputKey::new(0x2E); - - pub const N0: InputKey = InputKey::new('0' as u32); - pub const N1: InputKey = InputKey::new('1' as u32); - pub const N2: InputKey = InputKey::new('2' as u32); - pub const N3: InputKey = InputKey::new('3' as u32); - pub const N4: InputKey = InputKey::new('4' as u32); - pub const N5: InputKey = InputKey::new('5' as u32); - pub const N6: InputKey = InputKey::new('6' as u32); - pub const N7: InputKey = InputKey::new('7' as u32); - pub const N8: InputKey = InputKey::new('8' as u32); - pub const N9: InputKey = InputKey::new('9' as u32); - - pub const A: InputKey = InputKey::new('A' as u32); - pub const B: InputKey = InputKey::new('B' as u32); - pub const C: InputKey = InputKey::new('C' as u32); - pub const D: InputKey = InputKey::new('D' as u32); - pub const E: InputKey = InputKey::new('E' as u32); - pub const F: InputKey = InputKey::new('F' as u32); - pub const G: InputKey = InputKey::new('G' as u32); - pub const H: InputKey = InputKey::new('H' as u32); - pub const I: InputKey = InputKey::new('I' as u32); - pub const J: InputKey = InputKey::new('J' as u32); - pub const K: InputKey = InputKey::new('K' as u32); - pub const L: InputKey = InputKey::new('L' as u32); - pub const M: InputKey = InputKey::new('M' as u32); - pub const N: InputKey = InputKey::new('N' as u32); - pub const O: InputKey = InputKey::new('O' as u32); - pub const P: InputKey = InputKey::new('P' as u32); - pub const Q: InputKey = InputKey::new('Q' as u32); - pub const R: InputKey = InputKey::new('R' as u32); - pub const S: InputKey = InputKey::new('S' as u32); - pub const T: InputKey = InputKey::new('T' as u32); - pub const U: InputKey = InputKey::new('U' as u32); - pub const V: InputKey = InputKey::new('V' as u32); - pub const W: InputKey = InputKey::new('W' as u32); - pub const X: InputKey = InputKey::new('X' as u32); - pub const Y: InputKey = InputKey::new('Y' as u32); - pub const Z: InputKey = InputKey::new('Z' as u32); - - pub const NUMPAD0: InputKey = InputKey::new(0x60); - pub const NUMPAD1: InputKey = InputKey::new(0x61); - pub const NUMPAD2: InputKey = InputKey::new(0x62); - pub const NUMPAD3: InputKey = InputKey::new(0x63); - pub const NUMPAD4: InputKey = InputKey::new(0x64); - pub const NUMPAD5: InputKey = InputKey::new(0x65); - pub const NUMPAD6: InputKey = InputKey::new(0x66); - pub const NUMPAD7: InputKey = InputKey::new(0x67); - pub const NUMPAD8: InputKey = InputKey::new(0x68); - pub const NUMPAD9: InputKey = InputKey::new(0x69); - pub const MULTIPLY: InputKey = InputKey::new(0x6A); - pub const ADD: InputKey = InputKey::new(0x6B); - pub const SEPARATOR: InputKey = InputKey::new(0x6C); - pub const SUBTRACT: InputKey = InputKey::new(0x6D); - pub const DECIMAL: InputKey = InputKey::new(0x6E); - pub const DIVIDE: InputKey = InputKey::new(0x6F); - - pub const F1: InputKey = InputKey::new(0x70); - pub const F2: InputKey = InputKey::new(0x71); - pub const F3: InputKey = InputKey::new(0x72); - pub const F4: InputKey = InputKey::new(0x73); - pub const F5: InputKey = InputKey::new(0x74); - pub const F6: InputKey = InputKey::new(0x75); - pub const F7: InputKey = InputKey::new(0x76); - pub const F8: InputKey = InputKey::new(0x77); - pub const F9: InputKey = InputKey::new(0x78); - pub const F10: InputKey = InputKey::new(0x79); - pub const F11: InputKey = InputKey::new(0x7A); - pub const F12: InputKey = InputKey::new(0x7B); - pub const F13: InputKey = InputKey::new(0x7C); - pub const F14: InputKey = InputKey::new(0x7D); - pub const F15: InputKey = InputKey::new(0x7E); - pub const F16: InputKey = InputKey::new(0x7F); - pub const F17: InputKey = InputKey::new(0x80); - pub const F18: InputKey = InputKey::new(0x81); - pub const F19: InputKey = InputKey::new(0x82); - pub const F20: InputKey = InputKey::new(0x83); - pub const F21: InputKey = InputKey::new(0x84); - pub const F22: InputKey = InputKey::new(0x85); - pub const F23: InputKey = InputKey::new(0x86); - pub const F24: InputKey = InputKey::new(0x87); -} - -/// Keyboard modifiers. -pub mod kbmod { - use super::InputKeyMod; - - pub const NONE: InputKeyMod = InputKeyMod::new(0x00000000); - pub const CTRL: InputKeyMod = InputKeyMod::new(0x01000000); - pub const ALT: InputKeyMod = InputKeyMod::new(0x02000000); - pub const SHIFT: InputKeyMod = InputKeyMod::new(0x04000000); - - pub const CTRL_ALT: InputKeyMod = InputKeyMod::new(0x03000000); - pub const CTRL_SHIFT: InputKeyMod = InputKeyMod::new(0x05000000); - pub const ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x06000000); - pub const CTRL_ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x07000000); +impl fmt::Debug for InputKeyMod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut sep = ""; + for (modifier, name) in [(MOD_CTRL, "CTRL"), (MOD_ALT, "ALT"), (MOD_SHIFT, "SHIFT")] { + if self.contains(modifier) { + write!(f, "{}{}", sep, name)?; + sep = " | "; + } + } + if sep.is_empty() { + f.write_str("NONE")?; + } + Ok(()) + } } /// Mouse input state. Up/Down, Left/Right, etc. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum InputMouseState { #[default] None, @@ -236,7 +188,7 @@ pub enum InputMouseState { } /// Mouse input. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct InputMouse { /// The state of the mouse.Up/Down, Left/Right, etc. pub state: InputMouseState, @@ -316,14 +268,14 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { } const KEYPAD_LUT: [u8; 8] = [ - vk::UP.value() as u8, // A - vk::DOWN.value() as u8, // B - vk::RIGHT.value() as u8, // C - vk::LEFT.value() as u8, // D - 0, // E - vk::END.value() as u8, // F - 0, // G - vk::HOME.value() as u8, // H + VK_UP.value() as u8, // A + VK_DOWN.value() as u8, // B + VK_RIGHT.value() as u8, // C + VK_LEFT.value() as u8, // D + 0, // E + VK_END.value() as u8, // F + 0, // G + VK_HOME.value() as u8, // H ]; match self.stream.next()? { @@ -332,24 +284,24 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { } vt::Token::Ctrl(ch) => match ch { '\0' | '\t' | '\r' => return Some(Input::Keyboard(InputKey::new(ch as u32))), - '\n' => return Some(Input::Keyboard(kbmod::CTRL | vk::RETURN)), + '\n' => return Some(Input::Keyboard(MOD_CTRL | VK_RETURN)), ..='\x1a' => { // Shift control code to A-Z let key = ch as u32 | 0x40; - return Some(Input::Keyboard(kbmod::CTRL | InputKey::new(key))); + return Some(Input::Keyboard(MOD_CTRL | InputKey::new(key))); } - '\x7f' => return Some(Input::Keyboard(vk::BACK)), + '\x7f' => return Some(Input::Keyboard(VK_BACK)), _ => {} }, vt::Token::Esc(ch) => { match ch { - '\0' => return Some(Input::Keyboard(vk::ESCAPE)), - '\n' => return Some(Input::Keyboard(kbmod::CTRL_ALT | vk::RETURN)), + '\0' => return Some(Input::Keyboard(VK_ESCAPE)), + '\n' => return Some(Input::Keyboard(MOD_CTRL | MOD_ALT | VK_RETURN)), ' '..='~' => { let ch = ch as u32; let key = ch & !0x20; // Shift a-z to A-Z let modifiers = - if (ch & 0x20) != 0 { kbmod::ALT } else { kbmod::ALT_SHIFT }; + if (ch & 0x20) != 0 { MOD_ALT } else { MOD_ALT | MOD_SHIFT }; return Some(Input::Keyboard(modifiers | InputKey::new(key))); } _ => {} @@ -363,7 +315,7 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { } } 'P'..='S' => { - let key = vk::F1.value() + ch as u32 - 'P' as u32; + let key = VK_F1.value() + ch as u32 - 'P' as u32; return Some(Input::Keyboard(InputKey::new(key))); } _ => {} @@ -378,16 +330,16 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { )); } } - 'Z' => return Some(Input::Keyboard(kbmod::SHIFT | vk::TAB)), + 'Z' => return Some(Input::Keyboard(MOD_SHIFT | VK_TAB)), '~' => { const LUT: [u8; 35] = [ 0, - vk::HOME.value() as u8, // 1 - vk::INSERT.value() as u8, // 2 - vk::DELETE.value() as u8, // 3 - vk::END.value() as u8, // 4 - vk::PRIOR.value() as u8, // 5 - vk::NEXT.value() as u8, // 6 + VK_HOME.value() as u8, // 1 + VK_INSERT.value() as u8, // 2 + VK_DELETE.value() as u8, // 3 + VK_END.value() as u8, // 4 + VK_PRIOR.value() as u8, // 5 + VK_NEXT.value() as u8, // 6 0, 0, 0, @@ -396,26 +348,26 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { 0, 0, 0, - vk::F5.value() as u8, // 15 + VK_F5.value() as u8, // 15 0, - vk::F6.value() as u8, // 17 - vk::F7.value() as u8, // 18 - vk::F8.value() as u8, // 19 - vk::F9.value() as u8, // 20 - vk::F10.value() as u8, // 21 + VK_F6.value() as u8, // 17 + VK_F7.value() as u8, // 18 + VK_F8.value() as u8, // 19 + VK_F9.value() as u8, // 20 + VK_F10.value() as u8, // 21 0, - vk::F11.value() as u8, // 23 - vk::F12.value() as u8, // 24 - vk::F13.value() as u8, // 25 - vk::F14.value() as u8, // 26 + VK_F11.value() as u8, // 23 + VK_F12.value() as u8, // 24 + VK_F13.value() as u8, // 25 + VK_F14.value() as u8, // 26 0, - vk::F15.value() as u8, // 28 - vk::F16.value() as u8, // 29 + VK_F15.value() as u8, // 28 + VK_F16.value() as u8, // 29 0, - vk::F17.value() as u8, // 31 - vk::F18.value() as u8, // 32 - vk::F19.value() as u8, // 33 - vk::F20.value() as u8, // 34 + VK_F17.value() as u8, // 31 + VK_F18.value() as u8, // 32 + VK_F19.value() as u8, // 33 + VK_F20.value() as u8, // 34 ]; const LUT_LEN: u16 = LUT.len() as u16; @@ -436,7 +388,7 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { let btn = csi.params[0]; let mut mouse = InputMouse { state: InputMouseState::None, - modifiers: kbmod::NONE, + modifiers: MOD_NONE, position: Default::default(), scroll: Default::default(), }; @@ -455,13 +407,10 @@ impl<'input> Iterator for Stream<'_, '_, 'input> { mouse.state = STATES[(btn as usize) & 0x03]; } - mouse.modifiers = kbmod::NONE; - mouse.modifiers |= - if (btn & 0x04) != 0 { kbmod::SHIFT } else { kbmod::NONE }; - mouse.modifiers |= - if (btn & 0x08) != 0 { kbmod::ALT } else { kbmod::NONE }; - mouse.modifiers |= - if (btn & 0x10f) != 0 { kbmod::CTRL } else { kbmod::NONE }; + mouse.modifiers = MOD_NONE; + mouse.modifiers |= if (btn & 0x04) != 0 { MOD_SHIFT } else { MOD_NONE }; + mouse.modifiers |= if (btn & 0x08) != 0 { MOD_ALT } else { MOD_NONE }; + mouse.modifiers |= if (btn & 0x10) != 0 { MOD_CTRL } else { MOD_NONE }; mouse.position.x = csi.params[1] as CoordType - 1; mouse.position.y = csi.params[2] as CoordType - 1; @@ -552,10 +501,10 @@ impl<'input> Stream<'_, '_, 'input> { _ => InputMouseState::None, }; let modifiers = match modifier { - 4 => kbmod::SHIFT, - 8 => kbmod::ALT, - 16 => kbmod::CTRL, - _ => kbmod::NONE, + 4 => MOD_SHIFT, + 8 => MOD_ALT, + 16 => MOD_CTRL, + _ => MOD_NONE, }; self.parser.x10_mouse_want = false; @@ -570,16 +519,16 @@ impl<'input> Stream<'_, '_, 'input> { } fn parse_modifiers(csi: &vt::Csi) -> InputKeyMod { - let mut modifiers = kbmod::NONE; + let mut modifiers = MOD_NONE; let p1 = csi.params[1].saturating_sub(1); if (p1 & 0x01) != 0 { - modifiers |= kbmod::SHIFT; + modifiers |= MOD_SHIFT; } if (p1 & 0x02) != 0 { - modifiers |= kbmod::ALT; + modifiers |= MOD_ALT; } if (p1 & 0x04) != 0 { - modifiers |= kbmod::CTRL; + modifiers |= MOD_CTRL; } modifiers } diff --git a/src/kbd.rs b/src/kbd.rs new file mode 100644 index 0000000..c0fbcd3 --- /dev/null +++ b/src/kbd.rs @@ -0,0 +1,114 @@ +//! The VK_* codes defined here match the VK_* constants on Windows. +//! It's a convenient way to handle keyboard input, even on other platforms. + +use crate::input::{InputKey, InputKeyMod}; + +pub const VK_NULL: InputKey = InputKey::new('\0' as u32); +pub const VK_BACK: InputKey = InputKey::new(0x08); +pub const VK_TAB: InputKey = InputKey::new('\t' as u32); +pub const VK_RETURN: InputKey = InputKey::new('\r' as u32); +pub const VK_ESCAPE: InputKey = InputKey::new(0x1B); +pub const VK_SPACE: InputKey = InputKey::new(' ' as u32); +pub const VK_PRIOR: InputKey = InputKey::new(0x21); +pub const VK_NEXT: InputKey = InputKey::new(0x22); + +pub const VK_END: InputKey = InputKey::new(0x23); +pub const VK_HOME: InputKey = InputKey::new(0x24); + +pub const VK_LEFT: InputKey = InputKey::new(0x25); +pub const VK_UP: InputKey = InputKey::new(0x26); +pub const VK_RIGHT: InputKey = InputKey::new(0x27); +pub const VK_DOWN: InputKey = InputKey::new(0x28); + +pub const VK_INSERT: InputKey = InputKey::new(0x2D); +pub const VK_DELETE: InputKey = InputKey::new(0x2E); + +pub const VK_0: InputKey = InputKey::new('0' as u32); +pub const VK_1: InputKey = InputKey::new('1' as u32); +pub const VK_2: InputKey = InputKey::new('2' as u32); +pub const VK_3: InputKey = InputKey::new('3' as u32); +pub const VK_4: InputKey = InputKey::new('4' as u32); +pub const VK_5: InputKey = InputKey::new('5' as u32); +pub const VK_6: InputKey = InputKey::new('6' as u32); +pub const VK_7: InputKey = InputKey::new('7' as u32); +pub const VK_8: InputKey = InputKey::new('8' as u32); +pub const VK_9: InputKey = InputKey::new('9' as u32); + +pub const VK_A: InputKey = InputKey::new('A' as u32); +pub const VK_B: InputKey = InputKey::new('B' as u32); +pub const VK_C: InputKey = InputKey::new('C' as u32); +pub const VK_D: InputKey = InputKey::new('D' as u32); +pub const VK_E: InputKey = InputKey::new('E' as u32); +pub const VK_F: InputKey = InputKey::new('F' as u32); +pub const VK_G: InputKey = InputKey::new('G' as u32); +pub const VK_H: InputKey = InputKey::new('H' as u32); +pub const VK_I: InputKey = InputKey::new('I' as u32); +pub const VK_J: InputKey = InputKey::new('J' as u32); +pub const VK_K: InputKey = InputKey::new('K' as u32); +pub const VK_L: InputKey = InputKey::new('L' as u32); +pub const VK_M: InputKey = InputKey::new('M' as u32); +pub const VK_N: InputKey = InputKey::new('N' as u32); +pub const VK_O: InputKey = InputKey::new('O' as u32); +pub const VK_P: InputKey = InputKey::new('P' as u32); +pub const VK_Q: InputKey = InputKey::new('Q' as u32); +pub const VK_R: InputKey = InputKey::new('R' as u32); +pub const VK_S: InputKey = InputKey::new('S' as u32); +pub const VK_T: InputKey = InputKey::new('T' as u32); +pub const VK_U: InputKey = InputKey::new('U' as u32); +pub const VK_V: InputKey = InputKey::new('V' as u32); +pub const VK_W: InputKey = InputKey::new('W' as u32); +pub const VK_X: InputKey = InputKey::new('X' as u32); +pub const VK_Y: InputKey = InputKey::new('Y' as u32); +pub const VK_Z: InputKey = InputKey::new('Z' as u32); + +pub const VK_NUMPAD0: InputKey = InputKey::new(0x60); +pub const VK_NUMPAD1: InputKey = InputKey::new(0x61); +pub const VK_NUMPAD2: InputKey = InputKey::new(0x62); +pub const VK_NUMPAD3: InputKey = InputKey::new(0x63); +pub const VK_NUMPAD4: InputKey = InputKey::new(0x64); +pub const VK_NUMPAD5: InputKey = InputKey::new(0x65); +pub const VK_NUMPAD6: InputKey = InputKey::new(0x66); +pub const VK_NUMPAD7: InputKey = InputKey::new(0x67); +pub const VK_NUMPAD8: InputKey = InputKey::new(0x68); +pub const VK_NUMPAD9: InputKey = InputKey::new(0x69); +pub const VK_MULTIPLY: InputKey = InputKey::new(0x6A); +pub const VK_ADD: InputKey = InputKey::new(0x6B); +pub const VK_SEPARATOR: InputKey = InputKey::new(0x6C); +pub const VK_SUBTRACT: InputKey = InputKey::new(0x6D); +pub const VK_DECIMAL: InputKey = InputKey::new(0x6E); +pub const VK_DIVIDE: InputKey = InputKey::new(0x6F); + +pub const VK_F1: InputKey = InputKey::new(0x70); +pub const VK_F2: InputKey = InputKey::new(0x71); +pub const VK_F3: InputKey = InputKey::new(0x72); +pub const VK_F4: InputKey = InputKey::new(0x73); +pub const VK_F5: InputKey = InputKey::new(0x74); +pub const VK_F6: InputKey = InputKey::new(0x75); +pub const VK_F7: InputKey = InputKey::new(0x76); +pub const VK_F8: InputKey = InputKey::new(0x77); +pub const VK_F9: InputKey = InputKey::new(0x78); +pub const VK_F10: InputKey = InputKey::new(0x79); +pub const VK_F11: InputKey = InputKey::new(0x7A); +pub const VK_F12: InputKey = InputKey::new(0x7B); +pub const VK_F13: InputKey = InputKey::new(0x7C); +pub const VK_F14: InputKey = InputKey::new(0x7D); +pub const VK_F15: InputKey = InputKey::new(0x7E); +pub const VK_F16: InputKey = InputKey::new(0x7F); +pub const VK_F17: InputKey = InputKey::new(0x80); +pub const VK_F18: InputKey = InputKey::new(0x81); +pub const VK_F19: InputKey = InputKey::new(0x82); +pub const VK_F20: InputKey = InputKey::new(0x83); +pub const VK_F21: InputKey = InputKey::new(0x84); +pub const VK_F22: InputKey = InputKey::new(0x85); +pub const VK_F23: InputKey = InputKey::new(0x86); +pub const VK_F24: InputKey = InputKey::new(0x87); + +pub const MOD_NONE: InputKeyMod = InputKeyMod::new(0x00000000); +pub const MOD_CTRL: InputKeyMod = InputKeyMod::new(0x01000000); +pub const MOD_ALT: InputKeyMod = InputKeyMod::new(0x02000000); +pub const MOD_SHIFT: InputKeyMod = InputKeyMod::new(0x04000000); + +pub const MOD_CTRL_ALT: InputKeyMod = InputKeyMod::new(0x03000000); +pub const MOD_CTRL_SHIFT: InputKeyMod = InputKeyMod::new(0x05000000); +pub const MOD_ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x06000000); +pub const MOD_CTRL_ALT_SHIFT: InputKeyMod = InputKeyMod::new(0x07000000); diff --git a/src/lib.rs b/src/lib.rs index 4a150da..5688332 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ pub mod hash; pub mod helpers; pub mod icu; pub mod input; +pub mod kbd; pub mod oklab; pub mod path; pub mod simd; diff --git a/src/tui.rs b/src/tui.rs index 0c53662..5bd8e26 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -157,13 +157,13 @@ use crate::document::WriteableDocument; use crate::framebuffer::{Attributes, Framebuffer, INDEXED_COLORS_COUNT, IndexedColor}; use crate::hash::*; use crate::helpers::*; -use crate::input::{InputKeyMod, kbmod, vk}; +use crate::input::InputKeyMod; +use crate::kbd::*; use crate::{apperr, arena_format, input, simd, unicode}; const ROOT_ID: u64 = 0x14057B7EF767814F; // Knuth's MMIX constant -const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT); -const KBMOD_FOR_WORD_NAV: InputKeyMod = - if cfg!(target_os = "macos") { kbmod::ALT } else { kbmod::CTRL }; +const SHIFT_TAB: InputKey = VK_TAB.with_modifiers(MOD_SHIFT); +const KBMOD_FOR_WORD_NAV: InputKeyMod = if cfg!(target_os = "macos") { MOD_ALT } else { MOD_CTRL }; type Input<'input> = input::Input<'input>; type InputKey = input::InputKey; @@ -520,7 +520,7 @@ impl Tui { let now = std::time::Instant::now(); let mut input_text = None; let mut input_keyboard = None; - let mut input_mouse_modifiers = kbmod::NONE; + let mut input_mouse_modifiers = MOD_NONE; let mut input_mouse_click = 0; let mut input_scroll_delta = Point { x: 0, y: 0 }; // `input_consumed` should be `true` if we're in the settling phase which is indicated by @@ -556,7 +556,7 @@ impl Tui { let clipboard = self.clipboard_mut(); clipboard.write(paste); clipboard.mark_as_synchronized(); - input_keyboard = Some(kbmod::CTRL | vk::V); + input_keyboard = Some(MOD_CTRL | VK_V); } Some(Input::Keyboard(keyboard)) => { input_keyboard = Some(keyboard); @@ -743,7 +743,7 @@ impl Tui { let mut focus_path_pop_min = 0; // If the user pressed Escape, we move the focus to a parent node. - if !ctx.input_consumed && ctx.consume_shortcut(vk::ESCAPE) { + if !ctx.input_consumed && ctx.consume_shortcut(VK_ESCAPE) { focus_path_pop_min = 1; } @@ -1463,11 +1463,11 @@ impl<'a> Context<'a, '_> { let Some(input) = self.input_keyboard else { return; }; - if !matches!(input, SHIFT_TAB | vk::TAB) { + if !matches!(input, SHIFT_TAB | VK_TAB) { return; } - let forward = input == vk::TAB; + let forward = input == VK_TAB; let mut focused_start = focused; let mut focused_next = focused; @@ -1749,7 +1749,7 @@ impl<'a> Context<'a, '_> { // Consume the input unconditionally, so that the root (the "main window") // doesn't accidentally receive any input via `consume_shortcut()`. if self.contains_focus() { - let exit = !self.input_consumed && self.input_keyboard == Some(vk::ESCAPE); + let exit = !self.input_consumed && self.input_keyboard == Some(VK_ESCAPE); self.set_input_consumed_unchecked(); exit } else { @@ -1820,7 +1820,7 @@ impl<'a> Context<'a, '_> { } fn table_end_row(&mut self) { - self.table_move_focus(vk::LEFT, vk::RIGHT); + self.table_move_focus(VK_LEFT, VK_RIGHT); } /// Ends the current table block. @@ -1835,7 +1835,7 @@ impl<'a> Context<'a, '_> { } self.block_end(); // table - self.table_move_focus(vk::UP, vk::DOWN); + self.table_move_focus(VK_UP, VK_DOWN); } fn table_move_focus(&mut self, prev_key: InputKey, next_key: InputKey) { @@ -2031,8 +2031,8 @@ impl<'a> Context<'a, '_> { fn button_activated(&mut self) -> bool { if !self.input_consumed && ((self.input_mouse_click != 0 && self.contains_mouse_down()) - || self.input_keyboard == Some(vk::RETURN) - || self.input_keyboard == Some(vk::SPACE)) + || self.input_keyboard == Some(VK_RETURN) + || self.input_keyboard == Some(VK_SPACE)) && self.is_focused() { self.set_input_consumed(); @@ -2267,7 +2267,7 @@ impl<'a> Context<'a, '_> { 2 => tb.select_word(), _ => match self.tui.mouse_state { InputMouseState::Left => { - if self.input_mouse_modifiers.contains(kbmod::SHIFT) { + if self.input_mouse_modifiers.contains(MOD_SHIFT) { // TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click. tb.selection_update_visual(pos); } else { @@ -2321,29 +2321,29 @@ impl<'a> Context<'a, '_> { make_cursor_visible = true; match key { - vk::BACK => { - let granularity = if modifiers == kbmod::CTRL { + VK_BACK => { + let granularity = if modifiers == MOD_CTRL { CursorMovement::Word } else { CursorMovement::Grapheme }; tb.delete(granularity, -1); } - vk::TAB => { + VK_TAB => { if single_line { // If this is just a simple input field, don't consume Tab (= early return). return false; } - tb.indent_change(if modifiers == kbmod::SHIFT { -1 } else { 1 }); + tb.indent_change(if modifiers == MOD_SHIFT { -1 } else { 1 }); } - vk::RETURN => { + VK_RETURN => { if single_line { // If this is just a simple input field, don't consume Enter (= early return). return false; } write = b"\n"; } - vk::ESCAPE => { + VK_ESCAPE => { // If there was a selection, clear it and show the cursor (= fallthrough). if !tb.clear_selection() { if single_line { @@ -2357,7 +2357,7 @@ impl<'a> Context<'a, '_> { make_cursor_visible = false; } } - vk::PRIOR => { + VK_PRIOR => { let height = node_prev.inner.height() - 1; // If the cursor was already on the first line, @@ -2366,7 +2366,7 @@ impl<'a> Context<'a, '_> { tc.preferred_column = 0; } - if modifiers == kbmod::SHIFT { + if modifiers == MOD_SHIFT { tb.selection_update_visual(Point { x: tc.preferred_column, y: tb.cursor_visual_pos().y - height, @@ -2378,7 +2378,7 @@ impl<'a> Context<'a, '_> { }); } } - vk::NEXT => { + VK_NEXT => { let height = node_prev.inner.height() - 1; // If the cursor was already on the last line, @@ -2387,7 +2387,7 @@ impl<'a> Context<'a, '_> { tc.preferred_column = CoordType::MAX; } - if modifiers == kbmod::SHIFT { + if modifiers == MOD_SHIFT { tb.selection_update_visual(Point { x: tc.preferred_column, y: tb.cursor_visual_pos().y + height, @@ -2403,28 +2403,28 @@ impl<'a> Context<'a, '_> { tc.preferred_column = tb.cursor_visual_pos().x; } } - vk::END => { + VK_END => { let logical_before = tb.cursor_logical_pos(); - let destination = if modifiers.contains(kbmod::CTRL) { + let destination = if modifiers.contains(MOD_CTRL) { Point::MAX } else { Point { x: CoordType::MAX, y: tb.cursor_visual_pos().y } }; - if modifiers.contains(kbmod::SHIFT) { + if modifiers.contains(MOD_SHIFT) { tb.selection_update_visual(destination); } else { tb.cursor_move_to_visual(destination); } - if !modifiers.contains(kbmod::CTRL) { + if !modifiers.contains(MOD_CTRL) { let logical_after = tb.cursor_logical_pos(); // If word-wrap is enabled and the user presses End the first time, // it moves to the start of the visual line. The second time they // press it, it moves to the start of the logical line. if tb.is_word_wrap_enabled() && logical_after == logical_before { - if modifiers == kbmod::SHIFT { + if modifiers == MOD_SHIFT { tb.selection_update_logical(Point { x: CoordType::MAX, y: tb.cursor_logical_pos().y, @@ -2438,28 +2438,28 @@ impl<'a> Context<'a, '_> { } } } - vk::HOME => { + VK_HOME => { let logical_before = tb.cursor_logical_pos(); - let destination = if modifiers.contains(kbmod::CTRL) { + let destination = if modifiers.contains(MOD_CTRL) { Default::default() } else { Point { x: 0, y: tb.cursor_visual_pos().y } }; - if modifiers.contains(kbmod::SHIFT) { + if modifiers.contains(MOD_SHIFT) { tb.selection_update_visual(destination); } else { tb.cursor_move_to_visual(destination); } - if !modifiers.contains(kbmod::CTRL) { + if !modifiers.contains(MOD_CTRL) { let mut logical_after = tb.cursor_logical_pos(); // If word-wrap is enabled and the user presses Home the first time, // it moves to the start of the visual line. The second time they // press it, it moves to the start of the logical line. if tb.is_word_wrap_enabled() && logical_after == logical_before { - if modifiers == kbmod::SHIFT { + if modifiers == MOD_SHIFT { tb.selection_update_logical(Point { x: 0, y: tb.cursor_logical_pos().y, @@ -2483,7 +2483,7 @@ impl<'a> Context<'a, '_> { && let indent_end = tb.indent_end_logical_pos() && (logical_before > indent_end || logical_before.x == 0) { - if modifiers == kbmod::SHIFT { + if modifiers == MOD_SHIFT { tb.selection_update_logical(indent_end); } else { tb.cursor_move_to_logical(indent_end); @@ -2491,13 +2491,13 @@ impl<'a> Context<'a, '_> { } } } - vk::LEFT => { + VK_LEFT => { let granularity = if modifiers.contains(KBMOD_FOR_WORD_NAV) { CursorMovement::Word } else { CursorMovement::Grapheme }; - if modifiers.contains(kbmod::SHIFT) { + if modifiers.contains(MOD_SHIFT) { tb.selection_update_delta(granularity, -1); } else if let Some((beg, _)) = tb.selection_range() { unsafe { tb.set_cursor(beg) }; @@ -2505,12 +2505,12 @@ impl<'a> Context<'a, '_> { tb.cursor_move_delta(granularity, -1); } } - vk::UP => { + VK_UP => { if single_line { return false; } match modifiers { - kbmod::NONE => { + MOD_NONE => { let mut x = tc.preferred_column; let mut y = tb.cursor_visual_pos().y - 1; @@ -2530,11 +2530,11 @@ impl<'a> Context<'a, '_> { tb.cursor_move_to_visual(Point { x, y }); } - kbmod::CTRL => { + MOD_CTRL => { tc.scroll_offset.y -= 1; make_cursor_visible = false; } - kbmod::SHIFT => { + MOD_SHIFT => { // If the cursor was already on the first line, // move it to the start of the buffer. if tb.cursor_visual_pos().y == 0 { @@ -2546,20 +2546,20 @@ impl<'a> Context<'a, '_> { y: tb.cursor_visual_pos().y - 1, }); } - kbmod::ALT => tb.move_selected_lines(MoveLineDirection::Up), - kbmod::CTRL_ALT => { + MOD_ALT => tb.move_selected_lines(MoveLineDirection::Up), + MOD_CTRL_ALT => { // TODO: Add cursor above } _ => return false, } } - vk::RIGHT => { + VK_RIGHT => { let granularity = if modifiers.contains(KBMOD_FOR_WORD_NAV) { CursorMovement::Word } else { CursorMovement::Grapheme }; - if modifiers.contains(kbmod::SHIFT) { + if modifiers.contains(MOD_SHIFT) { tb.selection_update_delta(granularity, 1); } else if let Some((_, end)) = tb.selection_range() { unsafe { tb.set_cursor(end) }; @@ -2567,12 +2567,12 @@ impl<'a> Context<'a, '_> { tb.cursor_move_delta(granularity, 1); } } - vk::DOWN => { + VK_DOWN => { if single_line { return false; } match modifiers { - kbmod::NONE => { + MOD_NONE => { let mut x = tc.preferred_column; let mut y = tb.cursor_visual_pos().y + 1; @@ -2597,11 +2597,11 @@ impl<'a> Context<'a, '_> { tc.preferred_column = tb.cursor_visual_pos().x; } } - kbmod::CTRL => { + MOD_CTRL => { tc.scroll_offset.y += 1; make_cursor_visible = false; } - kbmod::SHIFT => { + MOD_SHIFT => { // If the cursor was already on the last line, // move it to the end of the buffer. if tb.cursor_visual_pos().y >= tb.visual_line_count() - 1 { @@ -2617,77 +2617,77 @@ impl<'a> Context<'a, '_> { tc.preferred_column = tb.cursor_visual_pos().x; } } - kbmod::ALT => tb.move_selected_lines(MoveLineDirection::Down), - kbmod::CTRL_ALT => { + MOD_ALT => tb.move_selected_lines(MoveLineDirection::Down), + MOD_CTRL_ALT => { // TODO: Add cursor above } _ => return false, } } - vk::INSERT => match modifiers { - kbmod::SHIFT => tb.paste(self.clipboard_ref()), - kbmod::CTRL => tb.copy(self.clipboard_mut()), + VK_INSERT => match modifiers { + MOD_SHIFT => tb.paste(self.clipboard_ref()), + MOD_CTRL => tb.copy(self.clipboard_mut()), _ => tb.set_overtype(!tb.is_overtype()), }, - vk::DELETE => match modifiers { - kbmod::SHIFT => tb.cut(self.clipboard_mut()), - kbmod::CTRL => tb.delete(CursorMovement::Word, 1), + VK_DELETE => match modifiers { + MOD_SHIFT => tb.cut(self.clipboard_mut()), + MOD_CTRL => tb.delete(CursorMovement::Word, 1), _ => tb.delete(CursorMovement::Grapheme, 1), }, - vk::A => match modifiers { - kbmod::CTRL => tb.select_all(), + VK_A => match modifiers { + MOD_CTRL => tb.select_all(), _ => return false, }, - vk::B => match modifiers { - kbmod::ALT if cfg!(target_os = "macos") => { + VK_B => match modifiers { + MOD_ALT if cfg!(target_os = "macos") => { // On macOS, terminals commonly emit the Emacs style // Alt+B (ESC b) sequence for Alt+Left. tb.cursor_move_delta(CursorMovement::Word, -1); } _ => return false, }, - vk::F => match modifiers { - kbmod::ALT if cfg!(target_os = "macos") => { + VK_F => match modifiers { + MOD_ALT if cfg!(target_os = "macos") => { // On macOS, terminals commonly emit the Emacs style // Alt+F (ESC f) sequence for Alt+Right. tb.cursor_move_delta(CursorMovement::Word, 1); } _ => return false, }, - vk::H => match modifiers { - kbmod::CTRL => tb.delete(CursorMovement::Word, -1), + VK_H => match modifiers { + MOD_CTRL => tb.delete(CursorMovement::Word, -1), _ => return false, }, - vk::L => match modifiers { - kbmod::CTRL => tb.select_line(), + VK_L => match modifiers { + MOD_CTRL => tb.select_line(), _ => return false, }, - vk::X => match modifiers { - kbmod::CTRL => tb.cut(self.clipboard_mut()), + VK_X => match modifiers { + MOD_CTRL => tb.cut(self.clipboard_mut()), _ => return false, }, - vk::C => match modifiers { - kbmod::CTRL => tb.copy(self.clipboard_mut()), + VK_C => match modifiers { + MOD_CTRL => tb.copy(self.clipboard_mut()), _ => return false, }, - vk::V => match modifiers { - kbmod::CTRL => tb.paste(self.clipboard_ref()), + VK_V => match modifiers { + MOD_CTRL => tb.paste(self.clipboard_ref()), _ => return false, }, - vk::Y => match modifiers { - kbmod::CTRL => tb.redo(), + VK_Y => match modifiers { + MOD_CTRL => tb.redo(), _ => return false, }, - vk::Z => match modifiers { - kbmod::CTRL => tb.undo(), - kbmod::CTRL_SHIFT => tb.redo(), - kbmod::ALT => tb.set_word_wrap(!tb.is_word_wrap_enabled()), + VK_Z => match modifiers { + MOD_CTRL => tb.undo(), + MOD_CTRL_SHIFT => tb.redo(), + MOD_ALT => tb.set_word_wrap(!tb.is_word_wrap_enabled()), _ => return false, }, _ => return false, } - change_preferred_column = !matches!(key, vk::PRIOR | vk::NEXT | vk::UP | vk::DOWN); + change_preferred_column = !matches!(key, VK_PRIOR | VK_NEXT | VK_UP | VK_DOWN); } else { return false; } @@ -2863,10 +2863,10 @@ impl<'a> Context<'a, '_> { && let Some(key) = self.input_keyboard { match key { - vk::PRIOR => sc.scroll_offset.y -= prev_container.inner_clipped.height(), - vk::NEXT => sc.scroll_offset.y += prev_container.inner_clipped.height(), - vk::END => sc.scroll_offset.y = CoordType::MAX, - vk::HOME => sc.scroll_offset.y = 0, + VK_PRIOR => sc.scroll_offset.y -= prev_container.inner_clipped.height(), + VK_NEXT => sc.scroll_offset.y += prev_container.inner_clipped.height(), + VK_END => sc.scroll_offset.y = CoordType::MAX, + VK_HOME => sc.scroll_offset.y = 0, _ => return, } self.set_input_consumed(); @@ -2956,7 +2956,7 @@ impl<'a> Context<'a, '_> { let entered = focused && selected_before && !self.input_consumed - && matches!(self.input_keyboard, Some(vk::RETURN)); + && matches!(self.input_keyboard, Some(VK_RETURN)); let activated = clicked || entered; if activated { self.set_input_consumed(); @@ -3020,7 +3020,7 @@ impl<'a> Context<'a, '_> { let mut consumed = true; match key { - vk::PRIOR => { + VK_PRIOR => { selected_next = selected_now; for _ in 0..prev_container.borrow().inner_clipped.height() - 1 { let node = selected_next.borrow(); @@ -3030,7 +3030,7 @@ impl<'a> Context<'a, '_> { }; } } - vk::NEXT => { + VK_NEXT => { selected_next = selected_now; for _ in 0..prev_container.borrow().inner_clipped.height() - 1 { let node = selected_next.borrow(); @@ -3040,13 +3040,13 @@ impl<'a> Context<'a, '_> { }; } } - vk::END => { + VK_END => { selected_next = list.children.last.unwrap_or(selected_next); } - vk::HOME => { + VK_HOME => { selected_next = list.children.first.unwrap_or(selected_next); } - vk::UP => { + VK_UP => { selected_next = selected_now .borrow() .siblings @@ -3054,7 +3054,7 @@ impl<'a> Context<'a, '_> { .or(list.children.last) .unwrap_or(selected_next); } - vk::DOWN => { + VK_DOWN => { selected_next = selected_now .borrow() .siblings @@ -3122,7 +3122,7 @@ impl<'a> Context<'a, '_> { let contains_focus = self.contains_focus(); let keyboard_focus = accelerator != '\0' && !contains_focus - && self.consume_shortcut(kbmod::ALT | InputKey::new(accelerator as u32)); + && self.consume_shortcut(MOD_ALT | InputKey::new(accelerator as u32)); if contains_focus || keyboard_focus { self.attr_background_rgba(self.tui.floater_default_bg); @@ -3212,15 +3212,15 @@ impl<'a> Context<'a, '_> { if !self.input_consumed && let Some(key) = self.input_keyboard - && matches!(key, vk::ESCAPE | vk::UP | vk::DOWN) + && matches!(key, VK_ESCAPE | VK_UP | VK_DOWN) { - if matches!(key, vk::UP | vk::DOWN) { + if matches!(key, VK_UP | VK_DOWN) { // If the focus is on the menubar, and the user presses up/down, // focus the first/last item of the flyout respectively. let ln = self.tree.last_node.borrow(); if self.tui.is_node_focused(ln.parent.map_or(0, |n| n.borrow().id)) { let selected_next = - if key == vk::UP { ln.children.last } else { ln.children.first }; + if key == VK_UP { ln.children.last } else { ln.children.first }; if let Some(selected_next) = selected_next { self.steal_focus_for(selected_next); self.set_input_consumed(); @@ -3303,15 +3303,15 @@ impl<'a> Context<'a, '_> { let shortcut_letter = shortcut.value() as u8 as char; if shortcut_letter.is_ascii_uppercase() { let mut shortcut_text = ArenaString::new_in(self.arena()); - if shortcut.modifiers_contains(kbmod::CTRL) { + if shortcut.modifiers_contains(MOD_CTRL) { shortcut_text.push_str(self.tui.modifier_translations.ctrl); shortcut_text.push('+'); } - if shortcut.modifiers_contains(kbmod::ALT) { + if shortcut.modifiers_contains(MOD_ALT) { shortcut_text.push_str(self.tui.modifier_translations.alt); shortcut_text.push('+'); } - if shortcut.modifiers_contains(kbmod::SHIFT) { + if shortcut.modifiers_contains(MOD_SHIFT) { shortcut_text.push_str(self.tui.modifier_translations.shift); shortcut_text.push('+'); }