Another project
1use core::fmt;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(
6 Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
7)]
8#[serde(transparent)]
9pub struct ModifierMask(u8);
10
11impl ModifierMask {
12 pub const NONE: Self = Self(0);
13 pub const CTRL: Self = Self(1 << 0);
14 pub const SHIFT: Self = Self(1 << 1);
15 pub const ALT: Self = Self(1 << 2);
16 pub const META: Self = Self(1 << 3);
17
18 #[must_use]
19 pub const fn contains(self, other: Self) -> bool {
20 (self.0 & other.0) == other.0
21 }
22
23 #[must_use]
24 pub const fn union(self, other: Self) -> Self {
25 Self(self.0 | other.0)
26 }
27}
28
29impl core::ops::BitOr for ModifierMask {
30 type Output = Self;
31 fn bitor(self, rhs: Self) -> Self {
32 self.union(rhs)
33 }
34}
35
36impl fmt::Display for ModifierMask {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 for (flag, name) in [
39 (Self::CTRL, "Ctrl"),
40 (Self::SHIFT, "Shift"),
41 (Self::ALT, "Alt"),
42 (Self::META, "Meta"),
43 ] {
44 if self.contains(flag) {
45 f.write_str(name)?;
46 f.write_str("+")?;
47 }
48 }
49 Ok(())
50 }
51}
52
53#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
54pub enum NamedKey {
55 Tab,
56 Enter,
57 Escape,
58 Backspace,
59 Delete,
60 Space,
61 ArrowUp,
62 ArrowDown,
63 ArrowLeft,
64 ArrowRight,
65 Home,
66 End,
67 PageUp,
68 PageDown,
69 F2,
70}
71
72impl NamedKey {
73 #[must_use]
74 pub const fn label(self) -> &'static str {
75 match self {
76 Self::Tab => "Tab",
77 Self::Enter => "Enter",
78 Self::Escape => "Esc",
79 Self::Backspace => "Backspace",
80 Self::Delete => "Delete",
81 Self::Space => "Space",
82 Self::ArrowUp => "Up",
83 Self::ArrowDown => "Down",
84 Self::ArrowLeft => "Left",
85 Self::ArrowRight => "Right",
86 Self::Home => "Home",
87 Self::End => "End",
88 Self::PageUp => "PageUp",
89 Self::PageDown => "PageDown",
90 Self::F2 => "F2",
91 }
92 }
93}
94
95impl fmt::Display for NamedKey {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(self.label())
98 }
99}
100
101#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
102#[serde(transparent)]
103pub struct KeyChar(char);
104
105impl KeyChar {
106 #[must_use]
107 pub fn from_char(c: char) -> Self {
108 Self(c.to_lowercase().next().unwrap_or(c))
109 }
110
111 #[must_use]
112 pub const fn from_ascii(c: char) -> Self {
113 Self(c.to_ascii_lowercase())
114 }
115
116 #[must_use]
117 pub const fn get(self) -> char {
118 self.0
119 }
120}
121
122#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
123pub enum KeyCode {
124 Named(NamedKey),
125 Char(KeyChar),
126}
127
128impl fmt::Display for KeyCode {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::Named(named) => fmt::Display::fmt(named, f),
132 Self::Char(c) => f.write_str(&c.get().to_uppercase().collect::<String>()),
133 }
134 }
135}
136
137#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
138pub struct KeyEvent {
139 pub code: KeyCode,
140 pub modifiers: ModifierMask,
141}
142
143impl KeyEvent {
144 #[must_use]
145 pub const fn new(code: KeyCode, modifiers: ModifierMask) -> Self {
146 Self { code, modifiers }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::{KeyChar, ModifierMask};
153
154 #[test]
155 fn modifier_contains_self() {
156 let m = ModifierMask::CTRL | ModifierMask::SHIFT;
157 assert!(m.contains(ModifierMask::CTRL));
158 assert!(m.contains(ModifierMask::SHIFT));
159 assert!(!m.contains(ModifierMask::ALT));
160 }
161
162 #[test]
163 fn key_char_normalizes_to_lower() {
164 assert_eq!(KeyChar::from_char('A').get(), 'a');
165 assert_eq!(KeyChar::from_char('z').get(), 'z');
166 assert_eq!(KeyChar::from_char('É').get(), 'é');
167 }
168}