Skip to main content

kozan_core/input/
modifiers.rs

1//! Modifier key state — tracks which modifier keys are held.
2//!
3//! Like Chrome's `WebInputEvent::modifiers_` — a u16 bitfield carried
4//! on every input event. Includes keyboard modifiers (Shift, Ctrl, Alt, Meta)
5//! and mouse button state (which buttons are currently pressed).
6//!
7//! # Why a bitfield?
8//!
9//! - 2 bytes vs 8+ bytes for a struct of bools
10//! - Matches Chrome's approach (`kShiftKey | kControlKey | ...`)
11//! - Cheap to copy, compare, combine with bitwise OR
12
13/// Modifier key and button state, carried on every input event.
14///
15/// Chrome equivalent: `WebInputEvent::modifiers_` bitfield.
16///
17/// ```ignore
18/// if event.modifiers.ctrl() && event.modifiers.shift() {
19///     // Ctrl+Shift is held
20/// }
21/// ```
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
23pub struct Modifiers(u16);
24
25// Bit positions — keyboard modifiers.
26const SHIFT: u16 = 1 << 0;
27const CTRL: u16 = 1 << 1;
28const ALT: u16 = 1 << 2;
29const META: u16 = 1 << 3; // Win key / Cmd key
30const CAPS_LOCK: u16 = 1 << 4;
31const NUM_LOCK: u16 = 1 << 5;
32
33// Bit positions — mouse button state (which buttons are currently held).
34// Chrome tracks these in the same bitfield as keyboard modifiers.
35const LEFT_BUTTON: u16 = 1 << 6;
36const RIGHT_BUTTON: u16 = 1 << 7;
37const MIDDLE_BUTTON: u16 = 1 << 8;
38
39// Bit positions — keyboard event flags.
40const IS_AUTO_REPEAT: u16 = 1 << 9;
41
42impl Modifiers {
43    /// Empty — no modifiers held.
44    pub const EMPTY: Self = Self(0);
45
46    /// Create from raw bits.
47    #[inline]
48    #[must_use]
49    pub const fn from_bits(bits: u16) -> Self {
50        Self(bits)
51    }
52
53    /// Get the raw bits.
54    #[inline]
55    #[must_use]
56    pub const fn bits(self) -> u16 {
57        self.0
58    }
59
60    // ---- Keyboard modifier queries ----
61
62    /// Shift key is held.
63    #[inline]
64    #[must_use]
65    pub const fn shift(self) -> bool {
66        self.0 & SHIFT != 0
67    }
68
69    /// Ctrl key is held (Control on Mac).
70    #[inline]
71    #[must_use]
72    pub const fn ctrl(self) -> bool {
73        self.0 & CTRL != 0
74    }
75
76    /// Alt key is held (Option on Mac).
77    #[inline]
78    #[must_use]
79    pub const fn alt(self) -> bool {
80        self.0 & ALT != 0
81    }
82
83    /// Meta key is held (Win on Windows, Cmd on Mac).
84    #[inline]
85    #[must_use]
86    pub const fn meta(self) -> bool {
87        self.0 & META != 0
88    }
89
90    /// Caps Lock is active.
91    #[inline]
92    #[must_use]
93    pub const fn caps_lock(self) -> bool {
94        self.0 & CAPS_LOCK != 0
95    }
96
97    /// Num Lock is active.
98    #[inline]
99    #[must_use]
100    pub const fn num_lock(self) -> bool {
101        self.0 & NUM_LOCK != 0
102    }
103
104    // ---- Mouse button state queries ----
105
106    /// Left mouse button is currently held.
107    #[inline]
108    #[must_use]
109    pub const fn left_button(self) -> bool {
110        self.0 & LEFT_BUTTON != 0
111    }
112
113    /// Right mouse button is currently held.
114    #[inline]
115    #[must_use]
116    pub const fn right_button(self) -> bool {
117        self.0 & RIGHT_BUTTON != 0
118    }
119
120    /// Middle mouse button is currently held.
121    #[inline]
122    #[must_use]
123    pub const fn middle_button(self) -> bool {
124        self.0 & MIDDLE_BUTTON != 0
125    }
126
127    // ---- Keyboard event flags ----
128
129    /// This is an auto-repeat key event (key held down).
130    #[inline]
131    #[must_use]
132    pub const fn is_auto_repeat(self) -> bool {
133        self.0 & IS_AUTO_REPEAT != 0
134    }
135
136    // ---- Builder methods (set individual bits) ----
137
138    #[inline]
139    #[must_use]
140    pub const fn with_shift(self) -> Self {
141        Self(self.0 | SHIFT)
142    }
143    #[inline]
144    #[must_use]
145    pub const fn with_ctrl(self) -> Self {
146        Self(self.0 | CTRL)
147    }
148    #[inline]
149    #[must_use]
150    pub const fn with_alt(self) -> Self {
151        Self(self.0 | ALT)
152    }
153    #[inline]
154    #[must_use]
155    pub const fn with_meta(self) -> Self {
156        Self(self.0 | META)
157    }
158    #[inline]
159    #[must_use]
160    pub const fn with_caps_lock(self) -> Self {
161        Self(self.0 | CAPS_LOCK)
162    }
163    #[inline]
164    #[must_use]
165    pub const fn with_num_lock(self) -> Self {
166        Self(self.0 | NUM_LOCK)
167    }
168    #[inline]
169    #[must_use]
170    pub const fn with_left_button(self) -> Self {
171        Self(self.0 | LEFT_BUTTON)
172    }
173    #[inline]
174    #[must_use]
175    pub const fn with_right_button(self) -> Self {
176        Self(self.0 | RIGHT_BUTTON)
177    }
178    #[inline]
179    #[must_use]
180    pub const fn with_middle_button(self) -> Self {
181        Self(self.0 | MIDDLE_BUTTON)
182    }
183    #[inline]
184    #[must_use]
185    pub const fn with_auto_repeat(self) -> Self {
186        Self(self.0 | IS_AUTO_REPEAT)
187    }
188
189    /// Combine two modifier sets.
190    #[inline]
191    #[must_use]
192    pub const fn union(self, other: Self) -> Self {
193        Self(self.0 | other.0)
194    }
195
196    /// Check if all bits in `other` are set in `self`.
197    #[inline]
198    #[must_use]
199    pub const fn contains(self, other: Self) -> bool {
200        self.0 & other.0 == other.0
201    }
202}
203
204impl std::ops::BitOr for Modifiers {
205    type Output = Self;
206    #[inline]
207    fn bitor(self, rhs: Self) -> Self {
208        Self(self.0 | rhs.0)
209    }
210}
211
212impl std::ops::BitOrAssign for Modifiers {
213    #[inline]
214    fn bitor_assign(&mut self, rhs: Self) {
215        self.0 |= rhs.0;
216    }
217}
218
219impl std::fmt::Display for Modifiers {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        let mut parts = Vec::new();
222        if self.ctrl() {
223            parts.push("Ctrl");
224        }
225        if self.shift() {
226            parts.push("Shift");
227        }
228        if self.alt() {
229            parts.push("Alt");
230        }
231        if self.meta() {
232            parts.push("Meta");
233        }
234        if parts.is_empty() {
235            write!(f, "None")
236        } else {
237            write!(f, "{}", parts.join("+"))
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn empty_has_no_modifiers() {
248        let m = Modifiers::EMPTY;
249        assert!(!m.shift());
250        assert!(!m.ctrl());
251        assert!(!m.alt());
252        assert!(!m.meta());
253        assert!(!m.left_button());
254        assert_eq!(m.bits(), 0);
255    }
256
257    #[test]
258    fn builder_methods() {
259        let m = Modifiers::EMPTY.with_ctrl().with_shift();
260        assert!(m.ctrl());
261        assert!(m.shift());
262        assert!(!m.alt());
263        assert!(!m.meta());
264    }
265
266    #[test]
267    fn bitor_combines() {
268        let a = Modifiers::EMPTY.with_ctrl();
269        let b = Modifiers::EMPTY.with_shift();
270        let c = a | b;
271        assert!(c.ctrl());
272        assert!(c.shift());
273    }
274
275    #[test]
276    fn contains_checks_subset() {
277        let m = Modifiers::EMPTY.with_ctrl().with_shift().with_alt();
278        let subset = Modifiers::EMPTY.with_ctrl().with_shift();
279        assert!(m.contains(subset));
280        assert!(!subset.contains(m));
281    }
282
283    #[test]
284    fn display_format() {
285        let m = Modifiers::EMPTY.with_ctrl().with_shift();
286        assert_eq!(format!("{}", m), "Ctrl+Shift");
287        assert_eq!(format!("{}", Modifiers::EMPTY), "None");
288    }
289
290    #[test]
291    fn mouse_button_state() {
292        let m = Modifiers::EMPTY.with_left_button().with_ctrl();
293        assert!(m.left_button());
294        assert!(m.ctrl());
295        assert!(!m.right_button());
296    }
297
298    #[test]
299    fn auto_repeat_flag() {
300        let m = Modifiers::EMPTY.with_auto_repeat();
301        assert!(m.is_auto_repeat());
302    }
303
304    #[test]
305    fn size_is_two_bytes() {
306        assert_eq!(std::mem::size_of::<Modifiers>(), 2);
307    }
308}