blockworx_canvas2d/input/
chord.rs1use blockworx_paint::{Chord, Key, Modifiers};
4use web_sys::KeyboardEvent;
5
6#[must_use]
8pub fn chord_of(event: &KeyboardEvent) -> Option<Chord> {
9 let pressed = event.key();
10 let held = Held {
11 command: event.ctrl_key() || event.meta_key(),
14 shift: event.shift_key() && shift_is_a_modifier_of(&pressed),
15 };
16 Some(Chord {
17 modifiers: modifiers_of(held)?,
18 key: key_of(&pressed)?,
19 })
20}
21
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
24struct Held {
25 command: bool,
26 shift: bool,
27}
28
29fn shift_is_a_modifier_of(pressed: &str) -> bool {
32 pressed.chars().all(|c| c.is_ascii_alphabetic())
33}
34
35fn modifiers_of(held: Held) -> Option<Modifiers> {
39 match held {
40 Held {
41 command: false,
42 shift: false,
43 } => Some(Modifiers::None),
44 Held {
45 command: false,
46 shift: true,
47 } => None,
48 Held {
49 command: true,
50 shift: false,
51 } => Some(Modifiers::Command),
52 Held {
53 command: true,
54 shift: true,
55 } => Some(Modifiers::CommandShift),
56 }
57}
58
59fn key_of(pressed: &str) -> Option<Key> {
63 Some(match pressed {
64 "ArrowLeft" => Key::ArrowLeft,
65 "ArrowRight" => Key::ArrowRight,
66 "ArrowUp" => Key::ArrowUp,
67 "ArrowDown" => Key::ArrowDown,
68 _ => return character_key(pressed),
69 })
70}
71
72fn character_key(pressed: &str) -> Option<Key> {
73 let mut chars = pressed.chars();
74 let (only, rest) = (chars.next()?, chars.next());
75 if rest.is_some() {
76 return None;
77 }
78 Some(match only.to_ascii_lowercase() {
79 '0' => Key::Num0,
80 '1' => Key::Num1,
81 '2' => Key::Num2,
82 '3' => Key::Num3,
83 '4' => Key::Num4,
84 '5' => Key::Num5,
85 '6' => Key::Num6,
86 '7' => Key::Num7,
87 '8' => Key::Num8,
88 'b' => Key::B,
89 'e' => Key::E,
90 'i' => Key::I,
91 'k' => Key::K,
92 'm' => Key::M,
93 'n' => Key::N,
94 'p' => Key::P,
95 'r' => Key::R,
96 't' => Key::T,
97 'y' => Key::Y,
98 'z' => Key::Z,
99 '=' => Key::Equals,
100 '+' => Key::Plus,
101 '-' => Key::Minus,
102 _ => return None,
103 })
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn a_letter_binds_in_either_case() {
112 assert_eq!(key_of("b"), Some(Key::B));
113 assert_eq!(key_of("B"), Some(Key::B));
114 assert_eq!(key_of("T"), Some(Key::T));
115 }
116
117 #[test]
120 fn the_zoom_keys_are_their_own_keys() {
121 assert_eq!(key_of("="), Some(Key::Equals));
122 assert_eq!(key_of("+"), Some(Key::Plus));
123 assert_eq!(key_of("-"), Some(Key::Minus));
124 }
125
126 #[test]
129 fn a_named_key_is_not_read_as_its_first_letter() {
130 assert_eq!(key_of("Escape"), None);
131 assert_eq!(key_of("Enter"), None);
132 assert_eq!(key_of("Backspace"), None);
133 assert_eq!(key_of(""), None);
134 }
135
136 #[test]
137 fn the_arrows_are_read_by_their_names() {
138 assert_eq!(key_of("ArrowLeft"), Some(Key::ArrowLeft));
139 assert_eq!(key_of("ArrowRight"), Some(Key::ArrowRight));
140 assert_eq!(key_of("ArrowUp"), Some(Key::ArrowUp));
141 assert_eq!(key_of("ArrowDown"), Some(Key::ArrowDown));
142 }
143
144 #[test]
145 fn an_unbound_key_names_no_chord() {
146 assert_eq!(key_of("q"), None);
147 assert_eq!(key_of("9"), None);
148 }
149
150 #[test]
153 fn shift_is_a_modifier_of_a_letter_only() {
154 assert!(shift_is_a_modifier_of("z"));
155 assert!(shift_is_a_modifier_of("Z"));
156 assert!(!shift_is_a_modifier_of("+"));
157 assert!(!shift_is_a_modifier_of("2"));
158 let held = |command, shift| modifiers_of(Held { command, shift });
159 assert_eq!(held(true, true), Some(Modifiers::CommandShift));
160 assert_eq!(held(true, false), Some(Modifiers::Command));
161 assert_eq!(held(false, false), Some(Modifiers::None));
162 assert_eq!(held(false, true), None);
163 }
164}