Skip to main content

shuyi_app/
keys.rs

1//! Translation from macroquad key codes to `shuyi-ui`'s hardware-agnostic
2//! [`Key`], collecting a frame's keyboard events as [`RawInputEvent`]s, and
3//! hearing what the window's focus is made from (`WindowHeard`).
4
5use macroquad::miniquad::{EventHandler, KeyMods, MouseButton};
6use macroquad::prelude::{
7    KeyCode, get_char_pressed, get_keys_pressed, get_keys_released, is_key_down,
8};
9use shuyi_ui::RawInputEvent;
10use shuyi_ui::input::{Chord, Key};
11
12use crate::focus::Heard;
13
14/// The game key for a macroquad key code; `None` for keys the game ignores
15/// (bare modifiers, which only form `Shift+` chords, and rare keys).
16///
17/// On the web, miniquad 0.4 reports neither `'` nor `/`, and reports the
18/// `` ` `` key as `'` (its JS key table); they work on native.
19pub fn to_key(code: KeyCode) -> Option<Key> {
20    Some(match code {
21        KeyCode::A => Key::A,
22        KeyCode::B => Key::B,
23        KeyCode::C => Key::C,
24        KeyCode::D => Key::D,
25        KeyCode::E => Key::E,
26        KeyCode::F => Key::F,
27        KeyCode::G => Key::G,
28        KeyCode::H => Key::H,
29        KeyCode::I => Key::I,
30        KeyCode::J => Key::J,
31        KeyCode::K => Key::K,
32        KeyCode::L => Key::L,
33        KeyCode::M => Key::M,
34        KeyCode::N => Key::N,
35        KeyCode::O => Key::O,
36        KeyCode::P => Key::P,
37        KeyCode::Q => Key::Q,
38        KeyCode::R => Key::R,
39        KeyCode::S => Key::S,
40        KeyCode::T => Key::T,
41        KeyCode::U => Key::U,
42        KeyCode::V => Key::V,
43        KeyCode::W => Key::W,
44        KeyCode::X => Key::X,
45        KeyCode::Y => Key::Y,
46        KeyCode::Z => Key::Z,
47        KeyCode::Key0 => Key::Digit0,
48        KeyCode::Key1 => Key::Digit1,
49        KeyCode::Key2 => Key::Digit2,
50        KeyCode::Key3 => Key::Digit3,
51        KeyCode::Key4 => Key::Digit4,
52        KeyCode::Key5 => Key::Digit5,
53        KeyCode::Key6 => Key::Digit6,
54        KeyCode::Key7 => Key::Digit7,
55        KeyCode::Key8 => Key::Digit8,
56        KeyCode::Key9 => Key::Digit9,
57        KeyCode::Semicolon => Key::Semicolon,
58        KeyCode::Up => Key::Up,
59        KeyCode::Down => Key::Down,
60        KeyCode::Left => Key::Left,
61        KeyCode::Right => Key::Right,
62        KeyCode::Enter | KeyCode::KpEnter => Key::Enter,
63        KeyCode::Escape => Key::Escape,
64        KeyCode::Space => Key::Space,
65        KeyCode::Tab => Key::Tab,
66        KeyCode::Backspace => Key::Backspace,
67        KeyCode::F1 => Key::F1,
68        KeyCode::F2 => Key::F2,
69        KeyCode::F3 => Key::F3,
70        KeyCode::F4 => Key::F4,
71        KeyCode::F5 => Key::F5,
72        KeyCode::F6 => Key::F6,
73        KeyCode::F7 => Key::F7,
74        KeyCode::F8 => Key::F8,
75        KeyCode::F9 => Key::F9,
76        KeyCode::F10 => Key::F10,
77        KeyCode::F11 => Key::F11,
78        KeyCode::F12 => Key::F12,
79        KeyCode::Comma => Key::Comma,
80        KeyCode::Period => Key::Period,
81        KeyCode::Slash => Key::Slash,
82        KeyCode::Apostrophe => Key::Apostrophe,
83        KeyCode::LeftBracket => Key::LeftBracket,
84        KeyCode::RightBracket => Key::RightBracket,
85        KeyCode::Backslash => Key::Backslash,
86        KeyCode::Minus => Key::Minus,
87        KeyCode::Equal => Key::Equal,
88        KeyCode::GraveAccent => Key::Backquote,
89        KeyCode::Insert => Key::Insert,
90        KeyCode::Delete => Key::Delete,
91        KeyCode::Home => Key::Home,
92        KeyCode::End => Key::End,
93        KeyCode::PageUp => Key::PageUp,
94        KeyCode::PageDown => Key::PageDown,
95        KeyCode::Kp0 => Key::Kp0,
96        KeyCode::Kp1 => Key::Kp1,
97        KeyCode::Kp2 => Key::Kp2,
98        KeyCode::Kp3 => Key::Kp3,
99        KeyCode::Kp4 => Key::Kp4,
100        KeyCode::Kp5 => Key::Kp5,
101        KeyCode::Kp6 => Key::Kp6,
102        KeyCode::Kp7 => Key::Kp7,
103        KeyCode::Kp8 => Key::Kp8,
104        KeyCode::Kp9 => Key::Kp9,
105        KeyCode::KpAdd => Key::KpAdd,
106        KeyCode::KpSubtract => Key::KpSubtract,
107        KeyCode::KpMultiply => Key::KpMultiply,
108        KeyCode::KpDivide => Key::KpDivide,
109        KeyCode::KpDecimal => Key::KpDecimal,
110        _ => return None,
111    })
112}
113
114/// Whether the window's fullscreen shortcut was pressed this frame: `F11`,
115/// or `Alt+Enter`. Like the window's close button these belong to the
116/// platform, not to the game's keymap (ticket 0902).
117pub fn fullscreen_shortcut() -> bool {
118    let alt = is_key_down(KeyCode::LeftAlt) || is_key_down(KeyCode::RightAlt);
119    get_keys_pressed().into_iter().any(|code| {
120        code == KeyCode::F11 || (alt && matches!(code, KeyCode::Enter | KeyCode::KpEnter))
121    })
122}
123
124/// This frame's key events, in the order they happened ([`ordered`]), then
125/// the characters typed (for text boxes, with the keyboard's layout).
126pub fn poll() -> Vec<RawInputEvent> {
127    let shift = is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift);
128    let keys = |codes: Vec<KeyCode>| -> Vec<(KeyCode, Key)> {
129        codes
130            .into_iter()
131            .filter_map(|code| Some((code, to_key(code)?)))
132            .collect()
133    };
134    let released = keys(get_keys_released().into_iter().collect());
135    let pressed = keys(get_keys_pressed().into_iter().collect());
136    let down = |key: Key| {
137        pressed
138            .iter()
139            .any(|&(code, k)| k == key && is_key_down(code))
140    };
141    let just = |list: &[(KeyCode, Key)]| list.iter().map(|&(_, key)| key).collect::<Vec<_>>();
142    let mut events = ordered(&just(&released), &just(&pressed), down, shift);
143    events.extend(std::iter::from_fn(get_char_pressed).map(RawInputEvent::Text));
144    events
145}
146
147/// A frame's key events in the order they happened, from what the window
148/// says of the frame: the keys `released` and `pressed` in it, and whether
149/// a key is `down` at its end. Releases come first (a key let go, then
150/// another pressed), then presses (with `shift`). A key both pressed and
151/// released in one frame was tapped if it is up at the end (pressed, then
152/// let go: its release comes after its press), or let go and pressed again
153/// if it is down; so a quick tap never reads as a key held down, which
154/// would repeat its action until the next release.
155pub fn ordered(
156    released: &[Key],
157    pressed: &[Key],
158    down: impl Fn(Key) -> bool,
159    shift: bool,
160) -> Vec<RawInputEvent> {
161    let tapped = |key: Key| released.contains(&key) && pressed.contains(&key) && !down(key);
162    let ups = released
163        .iter()
164        .filter(|&&key| !tapped(key))
165        .map(|&key| RawInputEvent::Up(key));
166    let downs = pressed
167        .iter()
168        .map(|&key| RawInputEvent::Down(Chord { key, shift }));
169    let taps = pressed
170        .iter()
171        .filter(|&&key| tapped(key))
172        .map(|&key| RawInputEvent::Up(key));
173    ups.chain(downs).chain(taps).collect()
174}
175
176/// What macroquad's input queue replays into for the window's focus
177/// ([`crate::focus`]): its minimizes, restores and presses, in order, and
178/// nothing else. A press counts whatever its key or button.
179#[derive(Debug, Default)]
180pub(crate) struct WindowHeard(pub(crate) Vec<Heard>);
181
182impl EventHandler for WindowHeard {
183    fn update(&mut self) {}
184
185    fn draw(&mut self) {}
186
187    fn key_down_event(&mut self, _: KeyCode, _: KeyMods, _: bool) {
188        self.0.push(Heard::Pressed);
189    }
190
191    fn mouse_button_down_event(&mut self, _: MouseButton, _: f32, _: f32) {
192        self.0.push(Heard::Pressed);
193    }
194
195    fn window_minimized_event(&mut self) {
196        self.0.push(Heard::Minimized);
197    }
198
199    fn window_restored_event(&mut self) {
200        self.0.push(Heard::Restored);
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    /// A key code `app` reads as `key`. Exhaustive, so a new [`Key`]
209    /// without a key code doesn't compile.
210    fn code_for(key: Key) -> KeyCode {
211        match key {
212            Key::A => KeyCode::A,
213            Key::B => KeyCode::B,
214            Key::C => KeyCode::C,
215            Key::D => KeyCode::D,
216            Key::E => KeyCode::E,
217            Key::F => KeyCode::F,
218            Key::G => KeyCode::G,
219            Key::H => KeyCode::H,
220            Key::I => KeyCode::I,
221            Key::J => KeyCode::J,
222            Key::K => KeyCode::K,
223            Key::L => KeyCode::L,
224            Key::M => KeyCode::M,
225            Key::N => KeyCode::N,
226            Key::O => KeyCode::O,
227            Key::P => KeyCode::P,
228            Key::Q => KeyCode::Q,
229            Key::R => KeyCode::R,
230            Key::S => KeyCode::S,
231            Key::T => KeyCode::T,
232            Key::U => KeyCode::U,
233            Key::V => KeyCode::V,
234            Key::W => KeyCode::W,
235            Key::X => KeyCode::X,
236            Key::Y => KeyCode::Y,
237            Key::Z => KeyCode::Z,
238            Key::Digit0 => KeyCode::Key0,
239            Key::Digit1 => KeyCode::Key1,
240            Key::Digit2 => KeyCode::Key2,
241            Key::Digit3 => KeyCode::Key3,
242            Key::Digit4 => KeyCode::Key4,
243            Key::Digit5 => KeyCode::Key5,
244            Key::Digit6 => KeyCode::Key6,
245            Key::Digit7 => KeyCode::Key7,
246            Key::Digit8 => KeyCode::Key8,
247            Key::Digit9 => KeyCode::Key9,
248            Key::Semicolon => KeyCode::Semicolon,
249            Key::Up => KeyCode::Up,
250            Key::Down => KeyCode::Down,
251            Key::Left => KeyCode::Left,
252            Key::Right => KeyCode::Right,
253            Key::Enter => KeyCode::Enter,
254            Key::Escape => KeyCode::Escape,
255            Key::Space => KeyCode::Space,
256            Key::Tab => KeyCode::Tab,
257            Key::Backspace => KeyCode::Backspace,
258            Key::F1 => KeyCode::F1,
259            Key::F2 => KeyCode::F2,
260            Key::F3 => KeyCode::F3,
261            Key::F4 => KeyCode::F4,
262            Key::F5 => KeyCode::F5,
263            Key::F6 => KeyCode::F6,
264            Key::F7 => KeyCode::F7,
265            Key::F8 => KeyCode::F8,
266            Key::F9 => KeyCode::F9,
267            Key::F10 => KeyCode::F10,
268            Key::F11 => KeyCode::F11,
269            Key::F12 => KeyCode::F12,
270            Key::Comma => KeyCode::Comma,
271            Key::Period => KeyCode::Period,
272            Key::Slash => KeyCode::Slash,
273            Key::Apostrophe => KeyCode::Apostrophe,
274            Key::LeftBracket => KeyCode::LeftBracket,
275            Key::RightBracket => KeyCode::RightBracket,
276            Key::Backslash => KeyCode::Backslash,
277            Key::Minus => KeyCode::Minus,
278            Key::Equal => KeyCode::Equal,
279            Key::Backquote => KeyCode::GraveAccent,
280            Key::Insert => KeyCode::Insert,
281            Key::Delete => KeyCode::Delete,
282            Key::Home => KeyCode::Home,
283            Key::End => KeyCode::End,
284            Key::PageUp => KeyCode::PageUp,
285            Key::PageDown => KeyCode::PageDown,
286            Key::Kp0 => KeyCode::Kp0,
287            Key::Kp1 => KeyCode::Kp1,
288            Key::Kp2 => KeyCode::Kp2,
289            Key::Kp3 => KeyCode::Kp3,
290            Key::Kp4 => KeyCode::Kp4,
291            Key::Kp5 => KeyCode::Kp5,
292            Key::Kp6 => KeyCode::Kp6,
293            Key::Kp7 => KeyCode::Kp7,
294            Key::Kp8 => KeyCode::Kp8,
295            Key::Kp9 => KeyCode::Kp9,
296            Key::KpAdd => KeyCode::KpAdd,
297            Key::KpSubtract => KeyCode::KpSubtract,
298            Key::KpMultiply => KeyCode::KpMultiply,
299            Key::KpDivide => KeyCode::KpDivide,
300            Key::KpDecimal => KeyCode::KpDecimal,
301        }
302    }
303
304    #[test]
305    fn a_frames_key_events_come_in_the_order_they_happened() {
306        let none = |_: Key| false;
307        let down = |c: Chord| RawInputEvent::Down(c);
308        let up = RawInputEvent::Up;
309        let plain = Chord::plain;
310        // A key let go and another pressed: the release first.
311        assert_eq!(
312            ordered(&[Key::A], &[Key::B], |k| k == Key::B, false),
313            [up(Key::A), down(plain(Key::B))]
314        );
315        // Tapped within the frame: pressed, then let go.
316        assert_eq!(
317            ordered(&[Key::Right], &[Key::Right], none, false),
318            [down(plain(Key::Right)), up(Key::Right)]
319        );
320        // Let go and pressed again: down at the end.
321        assert_eq!(
322            ordered(&[Key::Right], &[Key::Right], |k| k == Key::Right, false),
323            [up(Key::Right), down(plain(Key::Right))]
324        );
325        // Shift goes with the presses.
326        assert_eq!(
327            ordered(&[], &[Key::E, Key::F], none, true),
328            [down(Chord::shifted(Key::E)), down(Chord::shifted(Key::F))]
329        );
330        let found = ordered(&[], &[], none, false);
331        assert!(found.is_empty(), "{found:?}");
332    }
333
334    #[test]
335    fn every_key_is_mapped_from_a_key_code() {
336        for &key in Key::ALL {
337            assert_eq!(to_key(code_for(key)), Some(key), "{key}");
338        }
339    }
340
341    #[test]
342    fn every_game_key_has_a_key_code() {
343        let codes = [
344            KeyCode::A,
345            KeyCode::Z,
346            KeyCode::Key0,
347            KeyCode::Key9,
348            KeyCode::Semicolon,
349            KeyCode::Up,
350            KeyCode::Right,
351            KeyCode::Enter,
352            KeyCode::Escape,
353            KeyCode::Space,
354            KeyCode::Tab,
355            KeyCode::Backspace,
356            KeyCode::F1,
357            KeyCode::F12,
358        ];
359        let keys: Vec<String> = codes
360            .into_iter()
361            .filter_map(to_key)
362            .map(|k| k.to_string())
363            .collect();
364        assert_eq!(
365            keys,
366            [
367                "a",
368                "z",
369                "0",
370                "9",
371                ";",
372                "Up",
373                "Right",
374                "Enter",
375                "Escape",
376                "Space",
377                "Tab",
378                "Backspace",
379                "F1",
380                "F12"
381            ]
382        );
383        assert_eq!(to_key(KeyCode::KpEnter), Some(Key::Enter));
384    }
385
386    #[test]
387    fn other_key_codes_are_ignored() {
388        for code in [
389            KeyCode::LeftShift,
390            KeyCode::RightShift,
391            KeyCode::LeftControl,
392            KeyCode::LeftAlt,
393            KeyCode::LeftSuper,
394            KeyCode::CapsLock,
395            KeyCode::KpEqual,
396            KeyCode::F13,
397        ] {
398            assert_eq!(to_key(code), None);
399        }
400    }
401}