spectrusty_utils/keyboard/
web_sys.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! Keyboard related functions to be used with [web-sys](https://crates.io/crates/web-sys).
9//!
10//! Requires "web-sys" feature to be enabled.
11use web_sys::KeyboardEvent;
12use spectrusty::peripherals::{ZXKeyboardMap,
13    joystick::{JoystickInterface, Directions},
14    serial::KeypadKeys
15};
16
17type ZXk = ZXKeyboardMap;
18
19/// Returns Spectrum keymap flags with a single bit set corresponding to the provided `key` code
20/// if the key matches one of the Spectrum's.
21///
22/// The alphanumeric keys, `ENTER` and `SPACE` are mapped as such.
23/// Left and right `SHIFT` is mapped as [CAPS SHIFT][ZXKeyboardMap::CS] and left and right `CTRL`
24/// is mapped as [SYMBOL SHIFT][ZXKeyboardMap::SS].
25///
26/// Otherwise returns an empty set.
27pub fn map_direct_key(key: &str) -> ZXKeyboardMap {
28    match key {
29        "Digit1" => ZXk::N1,
30        "Digit2" => ZXk::N2,
31        "Digit3" => ZXk::N3,
32        "Digit4" => ZXk::N4,
33        "Digit5" => ZXk::N5,
34        "Digit6" => ZXk::N6,
35        "Digit7" => ZXk::N7,
36        "Digit8" => ZXk::N8,
37        "Digit9" => ZXk::N9,
38        "Digit0" => ZXk::N0,
39        "KeyA"   => ZXk::A,
40        "KeyB"   => ZXk::B,
41        "KeyC"   => ZXk::C,
42        "KeyD"   => ZXk::D,
43        "KeyE"   => ZXk::E,
44        "KeyF"   => ZXk::F,
45        "KeyG"   => ZXk::G,
46        "KeyH"   => ZXk::H,
47        "KeyI"   => ZXk::I,
48        "KeyJ"   => ZXk::J,
49        "KeyK"   => ZXk::K,
50        "KeyL"   => ZXk::L,
51        "KeyM"   => ZXk::M,
52        "KeyN"   => ZXk::N,
53        "KeyO"   => ZXk::O,
54        "KeyP"   => ZXk::P,
55        "KeyQ"   => ZXk::Q,
56        "KeyR"   => ZXk::R,
57        "KeyS"   => ZXk::S,
58        "KeyT"   => ZXk::T,
59        "KeyU"   => ZXk::U,
60        "KeyV"   => ZXk::V,
61        "KeyW"   => ZXk::W,
62        "KeyX"   => ZXk::X,
63        "KeyY"   => ZXk::Y,
64        "KeyZ"   => ZXk::Z,
65        "ShiftLeft"|"ShiftRight" => ZXk::CS,
66        "ControlLeft"|"ControlRight" => ZXk::SS,
67        "Space" => ZXk::BR,
68        "Enter" => ZXk::EN,
69        _ => ZXk::empty()
70    }
71}
72
73/// Returns Spectrum keymap flags with some bits set corresponding to the provided `key` code
74/// if the provided key matches one or more of the Spectrum keys.
75///
76/// The second argument returned is `true` if the [ZXKeyboardMap::CS] should be removed from
77/// the updated keymap.
78///
79/// The key combination includes cursor keys (←→↑↓) and some non alphanumeric keys as shortcuts to
80/// corresponding Spectrum key combinations reachable via pressing `SYMBOL` or `CAPS` with another Spectrum key.
81///
82/// * `pressed` should be `true` if the `key` has been pressed down or `false` if it has been released.
83/// * `shift_down` should be `true` if one of the `SHIFT` key modifiers has been held down and `false` otherwise.
84pub fn map_combined_keys(key: &str, pressed: bool, shift_down: bool) -> (ZXKeyboardMap, bool) {
85    let mut removecs = false;
86    let zxk = match key {
87        "ArrowLeft" => ZXk::CS|ZXk::N5,
88        "ArrowDown" => ZXk::CS|ZXk::N6,
89        "ArrowUp" => ZXk::CS|ZXk::N7,
90        "ArrowRight" => ZXk::CS|ZXk::N8,
91
92        "CapsLock" => ZXk::CS|ZXk::N2,
93        "Backspace" => ZXk::CS|ZXk::N0,
94
95        "AltLeft"|"AltRight" => ZXk::CS|ZXk::SS,
96
97        "BracketLeft" => ZXk::SS|ZXk::N8,
98        "BracketRight" => ZXk::SS|ZXk::N9,
99        "Backquote" => ZXk::SS|ZXk::X,
100
101        "Minus" if pressed => if shift_down {
102            removecs = true;
103            ZXk::SS|ZXk::N0
104        }
105        else {
106            ZXk::SS|ZXk::J
107        },
108        "Minus" => ZXk::SS|ZXk::J|ZXk::N0,
109
110        "Equal" if pressed => if shift_down {
111            removecs = true;
112            ZXk::SS|ZXk::K
113        }
114        else {
115            ZXk::SS|ZXk::L
116        },
117        "Equal" => ZXk::SS|ZXk::K|ZXk::L,
118
119        "Comma" if pressed => if shift_down {
120            removecs = true;
121            ZXk::SS|ZXk::R
122        }
123        else {
124            ZXk::SS|ZXk::N
125        },
126        "Comma" => ZXk::SS|ZXk::R|ZXk::N,
127
128        "Period" if pressed => if shift_down {
129            removecs = true;
130            ZXk::SS|ZXk::T
131        }
132        else {
133            ZXk::SS|ZXk::M
134        },
135        "Period" => ZXk::SS|ZXk::T|ZXk::M,
136
137        "Quote" if pressed => if shift_down {
138            removecs = true;
139            ZXk::SS|ZXk::P
140        }
141        else {
142            ZXk::SS|ZXk::N7
143        },
144        "Quote" => ZXk::SS|ZXk::P|ZXk::N7,
145
146        "Slash" if pressed => if shift_down {
147            removecs = true;
148            ZXk::SS|ZXk::C
149        }
150        else {
151            ZXk::SS|ZXk::V
152        },
153        "Slash" => ZXk::SS|ZXk::C|ZXk::V,
154
155        "Semicolon" if pressed => if shift_down {
156            removecs = true;
157            ZXk::SS|ZXk::Z
158        }
159        else {
160            ZXk::SS|ZXk::O
161        },
162        "Semicolon" => ZXk::SS|ZXk::Z|ZXk::O,
163
164        k => map_direct_key(k)
165    };
166    (zxk, removecs)
167}
168
169/// Returns an updated Spectrum keymap state from a `key` down or up event.
170///
171/// * `cur` is the current keymap state.
172/// * `key` is the key code.
173/// * `pressed` should be `true` if the `key` has been pressed down and `false` if it has been released.
174/// * `shift_down` should be `true` if one of the `SHIFT` key modifiers has been held down and `false` otherwise.
175/// * `ctrl_down` should be `true` if one of the `CTRL` key modifiers has been held down and `false` otherwise.
176pub fn update_keymap(
177        mut cur: ZXKeyboardMap,
178        key: &str,
179        pressed: bool,
180        shift_down: bool,
181        ctrl_down: bool
182    ) -> ZXKeyboardMap
183{
184    let (chg, removecs) = map_combined_keys(key, pressed, shift_down);
185    if pressed {
186        cur.insert(chg);
187        if removecs {
188            cur.remove(ZXk::CS);
189        }
190    }
191    else {
192        cur.remove(chg);
193    }
194
195    if cur.is_empty() {
196        if shift_down {
197            cur.insert(ZXk::CS);
198        }
199        if ctrl_down {
200            cur.insert(ZXk::SS);
201        }
202    }
203    cur
204}
205
206/// Returns an updated Spectrum keymap state from a `key` down or up event.
207///
208/// * `cur` is the current keymap state.
209/// * `key` is the key code.
210/// * `pressed` should be `true` if the `key` has been pressed down and `false` if it has been released.
211/// * `modifier` is the `keymod` property from the `KeyDown` or `KeyUp` events.
212pub fn update_keymap_with_event(
213        cur: ZXKeyboardMap,
214        event: &KeyboardEvent,
215        pressed: bool,
216    ) -> ZXKeyboardMap
217{
218    let shift_down = event.shift_key();
219    let ctrl_down = event.ctrl_key();
220    let key = event.code();
221    update_keymap(cur, &key, pressed, shift_down, ctrl_down)
222}
223
224/// Returns a keypad's keymap flags with a single bit set corresponding to the provided `key` code
225/// if the key matches one of the Spectrum 128k keypad's.
226///
227/// The numeric keypad keys, `ENTER`, `PERIOD`, `+`, `-` are mapped as such.
228/// If `parens` is `true` the `/` key is mapped as [LPAREN] and `*` is mapped as [RPAREN].
229/// If `parens` is `false` keys `/` and `*` are mapped as such.
230///
231/// Otherwise returns an empty set.
232///
233/// [LPAREN]: KeypadKeys::LPAREN
234/// [RPAREN]: KeypadKeys::RPAREN
235pub fn map_keypad_key(key: &str, parens: bool) -> KeypadKeys {
236    match key {
237        "NumpadDivide" if parens => KeypadKeys::LPAREN,
238        "NumpadDivide" => KeypadKeys::DIVIDE,
239        "NumpadMultiply" if parens => KeypadKeys::RPAREN,
240        "NumpadMultiply" => KeypadKeys::MULTIPLY,
241        "NumpadSubtract" => KeypadKeys::MINUS,
242        "NumpadAdd" => KeypadKeys::PLUS,
243        "NumpadEnter" => KeypadKeys::ENTER,
244        "Numpad1" => KeypadKeys::N1,
245        "Numpad2" => KeypadKeys::N2,
246        "Numpad3" => KeypadKeys::N3,
247        "Numpad4" => KeypadKeys::N4,
248        "Numpad5" => KeypadKeys::N5,
249        "Numpad6" => KeypadKeys::N6,
250        "Numpad7" => KeypadKeys::N7,
251        "Numpad8" => KeypadKeys::N8,
252        "Numpad9" => KeypadKeys::N9,
253        "Numpad0" => KeypadKeys::N0,
254        "NumpadDecimal" => KeypadKeys::PERIOD,
255        _ => KeypadKeys::empty()
256    }
257}
258
259/// Returns an updated keypad's keymap state from a key down or up event.
260///
261/// * `cur` is the current Spectrum 128k keypad's keymap state.
262/// * `key` is the key code.
263/// * `pressed` should be `true` if the `key` has been pressed down or `false` if it has been released.
264/// * `parens` should be `true` if `/` and `*` keys should be mapped as [LPAREN] and [RPAREN], `false` otherwise.
265///
266/// [LPAREN]: KeypadKeys::LPAREN
267/// [RPAREN]: KeypadKeys::RPAREN
268pub fn update_keypad_keys(mut cur: KeypadKeys, key: &str, pressed: bool, parens: bool) -> KeypadKeys {
269    let chg = map_keypad_key(key, parens);
270    if !chg.is_empty() {
271        cur.set(chg, pressed);
272    }
273    cur
274}
275
276/// Returns an updated keypad keymap state from a key down or up event.
277///
278/// * `cur` is the current keymap state.
279/// * `key` is the key code.
280/// * `pressed` should be `true` if the `key` has been pressed down or `false` if it has been released.
281/// * `modifier` is the `keymod` property from the `KeyDown` or `KeyUp` events.
282#[inline]
283pub fn update_keypad_keys_with_event(cur: KeypadKeys, event: &KeyboardEvent, pressed: bool) -> KeypadKeys {
284    let parens = event.get_modifier_state("NumLock");
285    let key = event.code();
286    update_keypad_keys(cur, &key, pressed, parens)
287}
288
289/// Returns joystick direction flags with a single direction bit set if a `key` is one of ← → ↑ ↓ keys.
290pub fn map_key_to_direction(key: &str) -> Directions {
291    match key {
292        "ArrowUp"    => Directions::UP,
293        "ArrowRight" => Directions::RIGHT,
294        "ArrowDown"  => Directions::DOWN,
295        "ArrowLeft"  => Directions::LEFT,
296        _            => Directions::empty()
297    }
298}
299
300/// Updates the state of the joystick device via [JoystickInterface] from a key down or up event.
301///
302/// Returns `true` if the state of the joystick device was updated.
303/// Returns `false` if the `key` wasn't any of the ← → ↑ ↓ or `fire_key` keys or if `get_joy`
304/// returns `None`.
305///
306/// * `key` is the key code.
307/// * `pressed` indicates if the `key` was pressed (`true`) or released (`false`).
308/// * `fire_key` is the key code for the `FIRE` button.
309/// * `get_joy` should return a mutable reference to the [JoystickInterface] implementation instance
310///   if such instance is available.
311#[inline]
312pub fn update_joystick_from_key_event<'a, J, F>(
313            key: &str,
314            pressed: bool,
315            fire_key: &str,
316            get_joy: F
317        ) -> bool
318    where J: 'a + JoystickInterface + ?Sized,
319          F: FnOnce() -> Option<&'a mut J>
320{
321    super::update_joystick_from_key_event(key, pressed, fire_key, map_key_to_direction, get_joy)
322}