diff --git a/capi/chewing-internal/src/bopomofo.rs b/capi/chewing-internal/src/bopomofo.rs index 144ce9979..69098a695 100644 --- a/capi/chewing-internal/src/bopomofo.rs +++ b/capi/chewing-internal/src/bopomofo.rs @@ -1,11 +1,8 @@ use std::{ffi::CString, slice}; use chewing::editor::{ - keymap::{ - IdentityKeymap, KeyCode, KeyCodeFromQwerty, Keymap, RemappingKeymap, CARPALX, DVORAK, - QWERTY, - }, - layout::{ + keyboard::{Dvorak, KeyCode, KeyboardLayout, Modifiers, Qgmlwy, Qwerty}, + syllable::{ DaiChien26, Et, Et26, GinYieh, Hsu, Ibm, KeyBehavior, KeyboardLayoutCompat, Pinyin, Standard, }, @@ -21,7 +18,7 @@ use super::{ #[repr(C)] pub struct SyllableEditorWithKeymap { kb_type: KeyboardLayoutCompat, - keymap: Box, + keyboard: Box, editor: Box, } @@ -33,67 +30,67 @@ pub extern "C" fn NewPhoneticEditor( match kb_type { KB::Default => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Standard::new()), }), KB::Hsu => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Hsu::new()), }), KB::Ibm => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Ibm::new()), }), KB::GinYieh => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(GinYieh::new()), }), KB::Et => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Et::new()), }), KB::Et26 => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Et26::new()), }), KB::Dvorak => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(RemappingKeymap::new(DVORAK, QWERTY)), + keyboard: Box::new(Dvorak), editor: Box::new(Standard::new()), }), KB::DvorakHsu => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(RemappingKeymap::new(DVORAK, QWERTY)), + keyboard: Box::new(Dvorak), editor: Box::new(Hsu::new()), }), KB::DachenCp26 => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(DaiChien26::new()), }), KB::HanyuPinyin => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Pinyin::hanyu()), }), KB::ThlPinyin => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Pinyin::thl()), }), KB::Mps2Pinyin => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(IdentityKeymap::new(QWERTY)), + keyboard: Box::new(Qwerty), editor: Box::new(Pinyin::mps2()), }), KB::Carpalx => Box::new(SyllableEditorWithKeymap { kb_type, - keymap: Box::new(RemappingKeymap::new(CARPALX, QWERTY)), + keyboard: Box::new(Qgmlwy), editor: Box::new(Standard::new()), }), } @@ -114,17 +111,15 @@ pub extern "C" fn BopomofoPhoInput(pgdata: &mut ChewingData, key: i32) -> KeyBeh } let editor_keymap = pgdata.bopomofo_data.editor_with_keymap.as_mut(); - let key_code = match (key as u8).as_key_code() { - Some(key_code) => key_code, - None => return KeyBehavior::KeyError, - }; - let key_event = editor_keymap.keymap.map_key(key_code); + let key_event = editor_keymap + .keyboard + .map_ascii(key as u8, Modifiers::default()); let result = editor_keymap.editor.key_press(key_event); let key_buf = editor_keymap.editor.read(); if result == KeyBehavior::Commit { if key_buf.is_empty() { - return if key_code == KeyCode::Space { + return if key_event.code == KeyCode::Space { KeyBehavior::KeyError } else { KeyBehavior::NoWord diff --git a/src/editor.rs b/src/editor.rs index bc0cce361..23a4259b1 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,13 +2,13 @@ pub mod composition_editor; mod estimate; -pub mod keymap; -pub mod layout; +pub mod keyboard; +pub mod syllable; use std::{fmt::Debug, rc::Rc}; pub use estimate::{EstimateError, SqliteUserFreqEstimate, UserFreqEstimate}; -pub use layout::SyllableEditor; +pub use syllable::SyllableEditor; use tracing::warn; use crate::{ @@ -19,8 +19,8 @@ use crate::{ use self::{ composition_editor::CompositionEditor, - keymap::KeyEvent, - layout::{KeyBehavior, Standard}, + keyboard::KeyEvent, + syllable::{KeyBehavior, Standard}, }; /// Indicates the state change of the editor. @@ -210,11 +210,12 @@ impl ChewingEditorState for Entering { _ => (EditorKeyBehavior::Bell, &EnteringSyllable), }, LanguageMode::English => { - let char_ = evt.code.to_char(); match editor.character_form { - CharacterForm::Halfwidth => editor.composition.push(Symbol::Char(char_)), + CharacterForm::Halfwidth => { + editor.composition.push(Symbol::Char(evt.unicode)) + } CharacterForm::Fullwidth => { - let char_ = full_width_symbol_input(char_).unwrap(); + let char_ = full_width_symbol_input(evt.unicode).unwrap(); editor.composition.push(Symbol::Char(char_)); } } @@ -404,33 +405,33 @@ mod tests { use std::{collections::HashMap, rc::Rc}; use crate::{ - conversion::ChewingConversionEngine, dictionary::Dictionary, editor::EditorKeyBehavior, - syl, zhuyin::Bopomofo, + conversion::ChewingConversionEngine, + dictionary::Dictionary, + editor::{keyboard::Modifiers, EditorKeyBehavior}, + syl, + zhuyin::Bopomofo, }; use super::{ - keymap::{KeyCode, KeyEvent, KeyIndex}, + keyboard::{KeyCode, KeyboardLayout, Qwerty}, BasicEditor, Editor, EditorKeyEvent, }; #[test] fn editing_mode_input_bopomofo() { + let keyboard = Qwerty; let dict: Rc = Rc::new(HashMap::new()); let conversion_engine = ChewingConversionEngine::new(dict.clone()); let mut editor = Editor::new(conversion_engine, dict); - let key_behavior = editor.key_press(EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K32, - code: KeyCode::H, - })); + let ev = keyboard.map_keycode(KeyCode::H, Modifiers::default()); + let key_behavior = editor.key_press(EditorKeyEvent::Default(ev)); assert_eq!(EditorKeyBehavior::Absorb, key_behavior); assert_eq!(syl![Bopomofo::C], editor.syllable_buffer()); - let key_behavior = editor.key_press(EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K34, - code: KeyCode::K, - })); + let ev = keyboard.map_keycode(KeyCode::K, Modifiers::default()); + let key_behavior = editor.key_press(EditorKeyEvent::Default(ev)); assert_eq!(EditorKeyBehavior::Absorb, key_behavior); assert_eq!(syl![Bopomofo::C, Bopomofo::E], editor.syllable_buffer()); @@ -438,6 +439,7 @@ mod tests { #[test] fn editing_mode_input_bopomofo_commit() { + let keyboard = Qwerty; let dict: Rc = Rc::new(HashMap::from([( vec![crate::syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]], vec![("冊", 100).into()], @@ -446,22 +448,13 @@ mod tests { let conversion_engine = ChewingConversionEngine::new(dict.clone()); let mut editor = Editor::new(conversion_engine, dict); - let keys = [ - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K32, - code: KeyCode::H, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K34, - code: KeyCode::K, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K4, - code: KeyCode::N4, - }), - ]; - - let key_behaviors: Vec<_> = keys.iter().map(|&key| editor.key_press(key)).collect(); + let keys = [KeyCode::H, KeyCode::K, KeyCode::N4]; + let key_behaviors: Vec<_> = keys + .into_iter() + .map(|key| keyboard.map_keycode(key, Modifiers::default())) + .map(|ev| EditorKeyEvent::Default(ev)) + .map(|key| editor.key_press(key)) + .collect(); assert_eq!( vec![ @@ -477,6 +470,7 @@ mod tests { #[test] fn editing_mode_input_chinese_to_english_mode() { + let keyboard = Qwerty; let dict: Rc = Rc::new(HashMap::from([( vec![crate::syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]], vec![("冊", 100).into()], @@ -486,23 +480,11 @@ mod tests { let mut editor = Editor::new(conversion_engine, dict); let keys = [ - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K32, - code: KeyCode::H, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K34, - code: KeyCode::K, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K4, - code: KeyCode::N4, - }), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::H, Default::default())), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::K, Default::default())), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::N4, Default::default())), EditorKeyEvent::CapsLock, - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K39, - code: KeyCode::Z, - }), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::Z, Default::default())), ]; let key_behaviors: Vec<_> = keys.iter().map(|&key| editor.key_press(key)).collect(); @@ -523,6 +505,7 @@ mod tests { #[test] fn editing_mode_input_english_to_chinese_mode() { + let keyboard = Qwerty; let dict: Rc = Rc::new(HashMap::from([( vec![crate::syl![Bopomofo::C, Bopomofo::E, Bopomofo::TONE4]], vec![("冊", 100).into()], @@ -533,23 +516,11 @@ mod tests { let keys = [ EditorKeyEvent::CapsLock, - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K39, - code: KeyCode::X, - }), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::X, Default::default())), EditorKeyEvent::CapsLock, - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K32, - code: KeyCode::H, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K34, - code: KeyCode::K, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K4, - code: KeyCode::N4, - }), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::H, Default::default())), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::K, Default::default())), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::N4, Default::default())), ]; let key_behaviors: Vec<_> = keys.iter().map(|&key| editor.key_press(key)).collect(); @@ -574,20 +545,16 @@ mod tests { #[test] fn editing_mode_input_full_shape_symbol() { + let keyboard = Qwerty; let dictionary = Rc::new(HashMap::new()); let conversion_engine = ChewingConversionEngine::new(dictionary.clone()); let mut editor = Editor::new(conversion_engine, dictionary); editor.switch_character_form(); + let keys = [ EditorKeyEvent::CapsLock, - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K10, - code: KeyCode::N0, - }), - EditorKeyEvent::Default(KeyEvent { - index: KeyIndex::K11, - code: KeyCode::Minus, - }), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::N0, Default::default())), + EditorKeyEvent::Default(keyboard.map_keycode(KeyCode::Minus, Default::default())), ]; let key_behaviors: Vec<_> = keys.iter().map(|&key| editor.key_press(key)).collect(); diff --git a/src/editor/keyboard.rs b/src/editor/keyboard.rs new file mode 100644 index 000000000..cfa9f1eef --- /dev/null +++ b/src/editor/keyboard.rs @@ -0,0 +1,139 @@ +//! Keyboard layout conversion +//! +//! People usually practice Zhuyin input method independently from practicing +//! English typing, they acquire different muscle memory. This module provides APIs +//! to map different English layouts to layout independent key indexes that can be +//! used to drive the phonetic conversion engines. + +mod dvorak; +mod qgmlwy; +mod qwerty; + +pub use dvorak::Dvorak; +pub use qgmlwy::Qgmlwy; +pub use qwerty::Qwerty; + +/// The set of modifier keys you have on a keyboard. +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Modifiers { + /// Any shift key is down + pub shift: bool, + /// Any control key is down + pub ctrl: bool, + /// The caps lock toggle is on + pub capslock: bool, +} + +fn generic_map_keycode( + keycode_index: &[KeyCode; 49], + unicode_map: &[char; 49], + shift_map: &[char; 49], + keycode: KeyCode, + modifiers: Modifiers, +) -> KeyEvent { + let index = keycode_index + .iter() + .position(|key| *key == keycode) + .expect("invalid keycode"); + let unicode = if modifiers.capslock || modifiers.shift { + shift_map[index] + } else { + unicode_map[index] + }; + KeyEvent { + index: INDEX_MAP[index], + code: keycode, + unicode, + modifiers, + } +} + +/// Describe a Keyboard Layout +pub trait KeyboardLayout { + /// Map the keycode to a key event according to the keyboard layout + fn map_keycode(&self, keycode: KeyCode, modifiers: Modifiers) -> KeyEvent; + /// Map the ascii to keycode then to a key event + fn map_ascii(&self, ascii: u8, modifiers: Modifiers) -> KeyEvent { + let keycode = KEYCODE_MAP + .iter() + .find(|item| item.0 == ascii) + .map_or(Unknown, |item| item.1); + self.map_keycode(keycode, modifiers) + } +} + +/// Layout independent key index +/// +/// TODO: refactor this to not use enum? +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[rustfmt::skip] +pub enum KeyIndex { + K0 = 0, +// 1 2 3 4 5 6 7 8 9 0 - = \ ` + K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14, +// Q W E R T Y U I O P [ ] + K15, K16, K17, K18, K19, K20, K21, K22, K23, K24, K25, K26, +// A S D F G H J K L ; ' + K27, K28, K29, K30, K31, K32, K33, K34, K35, K36, K37, +// Z X C V B N M , . / SPC + K38, K39, K40, K41, K42, K43, K44, K45, K46, K47, K48 +} + +#[rustfmt::skip] +static INDEX_MAP: [KeyIndex; 49] = [ + K0, + K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14, + K15, K16, K17, K18, K19, K20, K21, K22, K23, K24, K25, K26, + K27, K28, K29, K30, K31, K32, K33, K34, K35, K36, K37, + K38, K39, K40, K41, K42, K43, K44, K45, K46, K47, K48 +]; + +/// USB HID KeyCodes +/// +/// TODO: refactor this to not use enum? +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[rustfmt::skip] +pub enum KeyCode { + Unknown = 0, + N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, + Q, W, E, R, T, Y, U, I, O, P, LBracket, RBracket, + A, S, D, F, G, H, J, K, L, SColon, Quote, + Z, X, C, V, B, N, M, Comma, Dot, Slash, Space +} + +use KeyCode::*; +use KeyIndex::*; + +/// Key processed by a keymap +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct KeyEvent { + /// TODO: doc + pub index: KeyIndex, + /// TODO: doc + pub code: KeyCode, + /// TODO: doc + pub unicode: char, + /// TODO: doc + pub modifiers: Modifiers, +} + +macro_rules! keycode_map { + ($($k:expr => $v:expr),* $(,)?) => {{ + [$(($k, $v),)*] + }}; +} + +#[rustfmt::skip] +static KEYCODE_MAP: [(u8, KeyCode); 48] = keycode_map! { + b'1' => N1, b'2' => N2, b'3' => N3, b'4' => N4, b'5' => N5, + b'6' => N6, b'7' => N7, b'8' => N8, b'9' => N9, b'0' => N0, + b'-' => Minus, b'=' => Equal, b'\\' => BSlash, b'`' => Grave, + b'q' => Q, b'w' => W, b'e' => E, b'r' => R, b't' => T, b'y' => Y, + b'u' => U, b'i' => I, b'o' => O, b'p' => P, b'[' => LBracket, b']' => RBracket, + b'a' => A, b's' => S, b'd' => D, b'f' => F, b'g' => G, b'h' => H, + b'j' => J, b'k' => K, b'l' => L, b';' => SColon, b'\'' => Quote, + b'z' => Z, b'x' => X, b'c' => C, b'v' => V, b'b' => B, b'n' => N, + b'm' => M, b',' => Comma, b'.' => Dot, b'/' => Slash, b' ' => Space +}; diff --git a/src/editor/keyboard/dvorak.rs b/src/editor/keyboard/dvorak.rs new file mode 100644 index 000000000..eb1cd68d9 --- /dev/null +++ b/src/editor/keyboard/dvorak.rs @@ -0,0 +1,42 @@ +use super::{ + generic_map_keycode, + KeyCode::{self, *}, + KeyEvent, KeyboardLayout, Modifiers, +}; + +/// A Dvorak keyboard. +#[derive(Debug)] +pub struct Dvorak; + +#[rustfmt::skip] +static KEYCODE_INDEX: [KeyCode; 49] = [ + Unknown, + N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, LBracket, RBracket, BSlash, Grave, + Quote, Comma, Dot, P, Y, F, G, C, R, L, Slash, Equal, + A, O, E, U, I, D, H, T, N, S, Minus, + SColon, Q, J, K, X, B, M, W, V, Z, Space +]; + +#[rustfmt::skip] +static UNICODE_MAP: [char; 49] = [ + '�', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '[', ']', '\\', '`', + '\'', ',', '.', 'p', 'y', 'f', 'g', 'c', 'r', 'l', '/', '=', + 'a', 'o', 'e', 'u', 'i', 'd', 'h', 't', 'n', 's', '-', + ';', 'q', 'j', 'k', 'x', 'b', 'm', 'w', 'v', 'z', ' ' +]; + +#[rustfmt::skip] +static SHIFT_MAP: [char; 49] = [ + '�', + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}', '|', '~', + '"', '<', '>', 'P', 'Y', 'F', 'G', 'C', 'R', 'L', '?', '+', + 'A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S', '_', + ':', 'Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z', ' ' +]; + +impl KeyboardLayout for Dvorak { + fn map_keycode(&self, keycode: KeyCode, modifiers: Modifiers) -> KeyEvent { + generic_map_keycode(&KEYCODE_INDEX, &UNICODE_MAP, &SHIFT_MAP, keycode, modifiers) + } +} diff --git a/src/editor/keyboard/qgmlwy.rs b/src/editor/keyboard/qgmlwy.rs new file mode 100644 index 000000000..1bd607b3f --- /dev/null +++ b/src/editor/keyboard/qgmlwy.rs @@ -0,0 +1,42 @@ +use super::{ + generic_map_keycode, + KeyCode::{self, *}, + KeyEvent, KeyboardLayout, Modifiers, +}; + +/// A CARPALX keyboard variant (QGMLWY). +#[derive(Debug)] +pub struct Qgmlwy; + +#[rustfmt::skip] +static KEYCODE_INDEX: [KeyCode; 49] = [ + Unknown, + N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, + Q, G, M, L, W, Y, F, U, B, SColon, LBracket, RBracket, + D, S, T, N, R, I, A, E, O, H, Quote, + Z, X, C, V, J, K, P, Comma, Dot, Slash, Space +]; + +#[rustfmt::skip] +static UNICODE_MAP: [char; 49] = [ + '�', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\\', '`', + 'q', 'g', 'm', 'l', 'w', 'y', 'f', 'u', 'b', ';', '[', ']', + 'd', 's', 't', 'n', 'r', 'i', 'a', 'e', 'o', 'h', '\'', + 'z', 'x', 'c', 'v', 'j', 'k', 'p', ',', '.', '/', ' ' +]; + +#[rustfmt::skip] +static SHIFT_MAP: [char; 49] = [ + '�', + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '|', '~', + 'Q', 'G', 'M', 'L', 'W', 'Y', 'F', 'U', 'B', ':', '{', '}', + 'D', 'S', 'T', 'N', 'R', 'I', 'A', 'E', 'O', 'H', '"', + 'Z', 'X', 'C', 'V', 'J', 'K', 'P', '<', '>', '?', ' ' +]; + +impl KeyboardLayout for Qgmlwy { + fn map_keycode(&self, keycode: KeyCode, modifiers: Modifiers) -> KeyEvent { + generic_map_keycode(&KEYCODE_INDEX, &UNICODE_MAP, &SHIFT_MAP, keycode, modifiers) + } +} diff --git a/src/editor/keyboard/qwerty.rs b/src/editor/keyboard/qwerty.rs new file mode 100644 index 000000000..b527680c2 --- /dev/null +++ b/src/editor/keyboard/qwerty.rs @@ -0,0 +1,42 @@ +use super::{ + generic_map_keycode, + KeyCode::{self, *}, + KeyEvent, KeyboardLayout, Modifiers, +}; + +/// A standard keyboard. +#[derive(Debug)] +pub struct Qwerty; + +#[rustfmt::skip] +static KEYCODE_INDEX: [KeyCode; 49] = [ + Unknown, + N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, + Q, W, E, R, T, Y, U, I, O, P, LBracket, RBracket, + A, S, D, F, G, H, J, K, L, SColon, Quote, + Z, X, C, V, B, N, M, Comma, Dot, Slash, Space +]; + +#[rustfmt::skip] +static UNICODE_MAP: [char; 49] = [ + '�', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\\', '`', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' ' +]; + +#[rustfmt::skip] +static SHIFT_MAP: [char; 49] = [ + '�', + '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '|', '~', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', + 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', + 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' ' +]; + +impl KeyboardLayout for Qwerty { + fn map_keycode(&self, keycode: KeyCode, modifiers: Modifiers) -> KeyEvent { + generic_map_keycode(&KEYCODE_INDEX, &UNICODE_MAP, &SHIFT_MAP, keycode, modifiers) + } +} diff --git a/src/editor/keymap.rs b/src/editor/keymap.rs deleted file mode 100644 index f91a4939d..000000000 --- a/src/editor/keymap.rs +++ /dev/null @@ -1,236 +0,0 @@ -//! Keyboard layout conversion -//! -//! People usually practice Zhuyin input method independently from practicing -//! English typing, they acquire different muscle memory. This module provides APIs -//! to map different English layouts to layout independent key indexes that can be -//! used to drive the phonetic conversion engines. - -/// Layout independent key index -/// -/// TODO: refactor this to not use enum? -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq)] -#[rustfmt::skip] -pub enum KeyIndex { - K0 = 0, -// 1 2 3 4 5 6 7 8 9 0 - = \ ` - K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14, -// Q W E R T Y U I O P [ ] - K15, K16, K17, K18, K19, K20, K21, K22, K23, K24, K25, K26, -// A S D F G H J K L ; ' - K27, K28, K29, K30, K31, K32, K33, K34, K35, K36, K37, -// Z X C V B N M , . / SPC - K38, K39, K40, K41, K42, K43, K44, K45, K46, K47, K48 -} - -/// USB HID KeyCodes -/// -/// TODO: refactor this to not use enum? -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq)] -#[rustfmt::skip] -pub enum KeyCode { - Unknown = 0, - N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, - Q, W, E, R, T, Y, U, I, O, P, LBracket, RBracket, - A, S, D, F, G, H, J, K, L, SColon, Quote, - Z, X, C, V, B, N, M, Comma, Dot, Slash, Space -} - -impl KeyCode { - /// Return the unicode char corresponding the key code - pub fn to_char(&self) -> char { - match self { - Unknown => '�', - N1 => '1', - N2 => '2', - N3 => '3', - N4 => '4', - N5 => '5', - N6 => '6', - N7 => '7', - N8 => '8', - N9 => '9', - N0 => '0', - Minus => '-', - Equal => '=', - BSlash => '/', - Grave => '`', - Q => 'q', - W => 'w', - E => 'e', - R => 'r', - T => 't', - Y => 'y', - U => 'u', - I => 'i', - O => 'o', - P => 'p', - LBracket => '[', - RBracket => ']', - A => 'a', - S => 's', - D => 'd', - F => 'f', - G => 'g', - H => 'h', - J => 'j', - K => 'k', - L => 'l', - SColon => ':', - Quote => '\'', - Z => 'z', - X => 'x', - C => 'c', - V => 'v', - B => 'b', - N => 'n', - M => 'm', - Comma => ',', - Dot => '.', - Slash => '\\', - Space => ' ', - } - } -} - -/// Key processed by a keymap -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct KeyEvent { - /// TODO: doc - pub index: KeyIndex, - /// TODO: doc - pub code: KeyCode, -} - -/// TODO: doc -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Layout { - name: &'static str, - map: [KeyCode; 48], -} - -/// TODO: doc -pub trait Keymap { - /// TODO: doc - fn map_key(&self, input: KeyCode) -> KeyEvent; -} - -/// TODO: doc -#[derive(Debug)] -pub struct IdentityKeymap { - inner: RemappingKeymap, -} - -impl IdentityKeymap { - /// TODO: doc - pub fn new(source: Layout) -> IdentityKeymap { - IdentityKeymap { - inner: RemappingKeymap::new(source, source), - } - } -} - -impl Keymap for IdentityKeymap { - fn map_key(&self, input: KeyCode) -> KeyEvent { - self.inner.map_key(input) - } -} - -/// TODO: doc -#[derive(Debug)] -pub struct RemappingKeymap { - source: Layout, - target: Layout, -} - -impl RemappingKeymap { - /// TODO: doc - pub fn new(source: Layout, target: Layout) -> RemappingKeymap { - RemappingKeymap { source, target } - } -} - -impl Keymap for RemappingKeymap { - fn map_key(&self, input: KeyCode) -> KeyEvent { - let position = self - .source - .map - .iter() - .position(|&key| input == key) - .expect("invalid keycode"); - let index = BLANK[position]; - let code = self.target.map[position]; - KeyEvent { index, code } - } -} - -#[rustfmt::skip] -const QWERTY_INDEX: [u8; 48] = [ - b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'0', b'-', b'=', b'\\', b'`', - b'q', b'w', b'e', b'r', b't', b'y', b'u', b'i', b'o', b'p', b'[', b']', - b'a', b's', b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', b'\'', - b'z', b'x', b'c', b'v', b'b', b'n', b'm', b',', b'.', b'/', b' ' -]; - -/// TODO: doc -pub trait KeyCodeFromQwerty { - /// TODO: doc - fn as_key_code(&self) -> Option; -} - -impl KeyCodeFromQwerty for u8 { - fn as_key_code(&self) -> Option { - let position = QWERTY_INDEX.iter().position(|key| key == self); - position.map(|pos| QWERTY.map[pos]) - } -} - -use std::fmt::Debug; - -use KeyCode::*; -use KeyIndex::*; - -#[rustfmt::skip] -const BLANK: [KeyIndex; 48] = [ - K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, K12, K13, K14, - K15, K16, K17, K18, K19, K20, K21, K22, K23, K24, K25, K26, - K27, K28, K29, K30, K31, K32, K33, K34, K35, K36, K37, - K38, K39, K40, K41, K42, K43, K44, K45, K46, K47, K48 -]; - -/// TODO: doc -#[rustfmt::skip] -pub const QWERTY: Layout = Layout { - name: "QWERTY", - map: [ - N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, - Q, W, E, R, T, Y, U, I, O, P, LBracket, RBracket, - A, S, D, F, G, H, J, K, L, SColon, Quote, - Z, X, C, V, B, N, M, Comma, Dot, Slash, Space - ], -}; - -/// TODO: doc -#[rustfmt::skip] -pub const DVORAK: Layout = Layout { - name: "DVORAK", - map: [ - N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, LBracket, RBracket, BSlash, Grave, - Quote, Comma, Dot, P, Y, F, G, C, R, L, Slash, Equal, - A, O, E, U, I, D, H, T, N, S, Minus, - SColon, Q, J, K, X, B, M, W, V, Z, Space - ], -}; - -/// TODO: doc -#[rustfmt::skip] -pub const CARPALX: Layout = Layout { - name: "CARPALX (QGMLWY)", - map: [ - N1, N2, N3, N4, N5, N6, N7, N8, N9, N0, Minus, Equal, BSlash, Grave, - Q, G, M, L, W, Y, F, U, B, SColon, LBracket, RBracket, - D, S, T, N, R, I, A, E, O, H, Quote, - Z, X, C, V, J, K, P, Comma, Dot, Slash, Space - ], -}; diff --git a/src/editor/layout.rs b/src/editor/syllable.rs similarity index 98% rename from src/editor/layout.rs rename to src/editor/syllable.rs index 42ccc48d4..cd0f0f7a4 100644 --- a/src/editor/layout.rs +++ b/src/editor/syllable.rs @@ -30,7 +30,7 @@ pub use self::{ standard::Standard, }; -use super::keymap::KeyEvent; +use super::keyboard::KeyEvent; mod dc26; mod et; diff --git a/src/editor/layout/dc26.rs b/src/editor/syllable/dc26.rs similarity index 99% rename from src/editor/layout/dc26.rs rename to src/editor/syllable/dc26.rs index 2336077c3..33fa5bc4b 100644 --- a/src/editor/layout/dc26.rs +++ b/src/editor/syllable/dc26.rs @@ -1,7 +1,7 @@ //! Dai Chien CP26 use crate::{ - editor::keymap::{KeyEvent, KeyIndex}, + editor::keyboard::{KeyEvent, KeyIndex}, zhuyin::{Bopomofo, Syllable}, }; diff --git a/src/editor/layout/et.rs b/src/editor/syllable/et.rs similarity index 92% rename from src/editor/layout/et.rs rename to src/editor/syllable/et.rs index a7406d724..e5008ab1f 100644 --- a/src/editor/layout/et.rs +++ b/src/editor/syllable/et.rs @@ -3,7 +3,7 @@ //! Another commonly used keyboard layout on older IBM PC. use crate::{ - editor::keymap::{KeyEvent, KeyIndex}, + editor::keyboard::{KeyEvent, KeyIndex}, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; @@ -121,8 +121,8 @@ impl SyllableEditor for Et { #[cfg(test)] mod test { use crate::editor::{ - keymap::{IdentityKeymap, KeyCode, Keymap, QWERTY}, - layout::{KeyBehavior, SyllableEditor}, + keyboard::{KeyCode, KeyboardLayout, Modifiers, Qwerty}, + syllable::{KeyBehavior, SyllableEditor}, }; use super::Et; @@ -130,8 +130,8 @@ mod test { #[test] fn space() { let mut editor = Et::new(); - let keymap = IdentityKeymap::new(QWERTY); - let behavior = editor.key_press(keymap.map_key(KeyCode::Space)); + let keyboard = Qwerty; + let behavior = editor.key_press(keyboard.map_keycode(KeyCode::Space, Modifiers::default())); assert_eq!(KeyBehavior::KeyError, behavior); } } diff --git a/src/editor/layout/et26.rs b/src/editor/syllable/et26.rs similarity index 99% rename from src/editor/layout/et26.rs rename to src/editor/syllable/et26.rs index 44c9c3da4..dfe9041d1 100644 --- a/src/editor/layout/et26.rs +++ b/src/editor/syllable/et26.rs @@ -1,7 +1,7 @@ //! ET26 (倚天26鍵) use crate::{ - editor::keymap::{KeyCode, KeyEvent}, + editor::keyboard::{KeyCode, KeyEvent}, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; diff --git a/src/editor/layout/ginyieh.rs b/src/editor/syllable/ginyieh.rs similarity index 92% rename from src/editor/layout/ginyieh.rs rename to src/editor/syllable/ginyieh.rs index 508d6cfaf..14fb3abfe 100644 --- a/src/editor/layout/ginyieh.rs +++ b/src/editor/syllable/ginyieh.rs @@ -3,7 +3,7 @@ //! Another commonly used keyboard layout on older IBM PC. use crate::{ - editor::keymap::{KeyEvent, KeyIndex}, + editor::keyboard::{KeyEvent, KeyIndex}, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; @@ -120,8 +120,8 @@ impl SyllableEditor for GinYieh { #[cfg(test)] mod test { use crate::editor::{ - keymap::{IdentityKeymap, KeyCode, Keymap, QWERTY}, - layout::{KeyBehavior, SyllableEditor}, + keyboard::{KeyCode, KeyboardLayout, Modifiers, Qwerty}, + syllable::{KeyBehavior, SyllableEditor}, }; use super::GinYieh; @@ -129,8 +129,8 @@ mod test { #[test] fn space() { let mut editor = GinYieh::new(); - let keymap = IdentityKeymap::new(QWERTY); - let behavior = editor.key_press(keymap.map_key(KeyCode::Space)); + let keyboard = Qwerty; + let behavior = editor.key_press(keyboard.map_keycode(KeyCode::Space, Modifiers::default())); assert_eq!(KeyBehavior::KeyError, behavior); } } diff --git a/src/editor/layout/hsu.rs b/src/editor/syllable/hsu.rs similarity index 92% rename from src/editor/layout/hsu.rs rename to src/editor/syllable/hsu.rs index 9394bea7f..f78b8d1cf 100644 --- a/src/editor/layout/hsu.rs +++ b/src/editor/syllable/hsu.rs @@ -1,7 +1,7 @@ //! Hsu keyboard layout use crate::{ - editor::keymap::KeyCode, + editor::keyboard::KeyCode, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; @@ -249,8 +249,8 @@ mod test { use crate::{ editor::{ - keymap::{IdentityKeymap, KeyCode, Keymap, QWERTY}, - layout::SyllableEditor, + keyboard::{KeyCode, KeyboardLayout, Modifiers, Qwerty}, + syllable::SyllableEditor, }, zhuyin::Bopomofo, }; @@ -260,11 +260,11 @@ mod test { #[test] fn cen() { let mut hsu = Hsu::new(); - let keymap = IdentityKeymap::new(QWERTY); - hsu.key_press(keymap.map_key(KeyCode::C)); - hsu.key_press(keymap.map_key(KeyCode::E)); - hsu.key_press(keymap.map_key(KeyCode::N)); - hsu.key_press(keymap.map_key(KeyCode::Space)); + let keyboard = Qwerty; + hsu.key_press(keyboard.map_keycode(KeyCode::C, Modifiers::default())); + hsu.key_press(keyboard.map_keycode(KeyCode::E, Modifiers::default())); + hsu.key_press(keyboard.map_keycode(KeyCode::N, Modifiers::default())); + hsu.key_press(keyboard.map_keycode(KeyCode::Space, Modifiers::default())); let result = hsu.read(); assert_eq!(result.initial(), Some(Bopomofo::X)); assert_eq!(result.medial(), Some(Bopomofo::I)); @@ -274,9 +274,9 @@ mod test { #[test] fn convert_n_to_en() { let mut hsu = Hsu::new(); - let keymap = IdentityKeymap::new(QWERTY); - hsu.key_press(keymap.map_key(KeyCode::N)); - hsu.key_press(keymap.map_key(KeyCode::F)); + let keyboard = Qwerty; + hsu.key_press(keyboard.map_keycode(KeyCode::N, Modifiers::default())); + hsu.key_press(keyboard.map_keycode(KeyCode::F, Modifiers::default())); let result = hsu.read(); assert_eq!(result.rime(), Some(Bopomofo::EN)); } diff --git a/src/editor/layout/ibm.rs b/src/editor/syllable/ibm.rs similarity index 92% rename from src/editor/layout/ibm.rs rename to src/editor/syllable/ibm.rs index ada1a0037..17144e72b 100644 --- a/src/editor/layout/ibm.rs +++ b/src/editor/syllable/ibm.rs @@ -3,7 +3,7 @@ //! Another commonly used keyboard layout on older IBM PC. use crate::{ - editor::keymap::{KeyEvent, KeyIndex}, + editor::keyboard::{KeyEvent, KeyIndex}, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; @@ -121,8 +121,8 @@ impl SyllableEditor for Ibm { #[cfg(test)] mod test { use crate::editor::{ - keymap::{IdentityKeymap, KeyCode, Keymap, QWERTY}, - layout::{KeyBehavior, SyllableEditor}, + keyboard::{KeyCode, KeyboardLayout, Modifiers, Qwerty}, + syllable::{KeyBehavior, SyllableEditor}, }; use super::Ibm; @@ -130,8 +130,8 @@ mod test { #[test] fn space() { let mut editor = Ibm::new(); - let keymap = IdentityKeymap::new(QWERTY); - let behavior = editor.key_press(keymap.map_key(KeyCode::Space)); + let keyboard = Qwerty; + let behavior = editor.key_press(keyboard.map_keycode(KeyCode::Space, Modifiers::default())); assert_eq!(KeyBehavior::KeyError, behavior); } } diff --git a/src/editor/layout/pinyin.rs b/src/editor/syllable/pinyin.rs similarity index 99% rename from src/editor/layout/pinyin.rs rename to src/editor/syllable/pinyin.rs index 335e7d28d..b6eab5aae 100644 --- a/src/editor/layout/pinyin.rs +++ b/src/editor/syllable/pinyin.rs @@ -1,7 +1,7 @@ //! Pinyin use crate::{ - editor::keymap::{KeyCode, KeyEvent}, + editor::keyboard::{KeyCode, KeyEvent}, zhuyin::{Bopomofo, Syllable}, }; diff --git a/src/editor/layout/standard.rs b/src/editor/syllable/standard.rs similarity index 92% rename from src/editor/layout/standard.rs rename to src/editor/syllable/standard.rs index 06c15bbdc..afe0a2654 100644 --- a/src/editor/layout/standard.rs +++ b/src/editor/syllable/standard.rs @@ -4,7 +4,7 @@ //! all platforms and the most commonly used one. use crate::{ - editor::keymap::{KeyEvent, KeyIndex}, + editor::keyboard::{KeyEvent, KeyIndex}, zhuyin::{Bopomofo, BopomofoKind, Syllable}, }; @@ -122,8 +122,8 @@ impl SyllableEditor for Standard { #[cfg(test)] mod test { use crate::editor::{ - keymap::{IdentityKeymap, KeyCode, Keymap, QWERTY}, - layout::{KeyBehavior, SyllableEditor}, + keyboard::{KeyCode, KeyboardLayout, Modifiers, Qwerty}, + syllable::{KeyBehavior, SyllableEditor}, }; use super::Standard; @@ -131,8 +131,8 @@ mod test { #[test] fn space() { let mut editor = Standard::new(); - let keymap = IdentityKeymap::new(QWERTY); - let behavior = editor.key_press(keymap.map_key(KeyCode::Space)); + let keyboard = Qwerty; + let behavior = editor.key_press(keyboard.map_keycode(KeyCode::Space, Modifiers::default())); assert_eq!(KeyBehavior::KeyError, behavior); } }