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}