Skip to main content

cranpose_ui/
key_event.rs

1//! Keyboard input event types for Cranpose.
2//!
3//! This module provides platform-independent keyboard event types
4//! that are used to route keyboard input to focused components.
5
6use std::fmt;
7
8/// Type of keyboard event.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum KeyEventType {
11    /// Key was pressed down.
12    KeyDown,
13    /// Key was released.
14    KeyUp,
15}
16
17/// Modifier keys state.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub struct Modifiers {
20    /// Shift key is pressed.
21    pub shift: bool,
22    /// Control key is pressed (Cmd on macOS).
23    pub ctrl: bool,
24    /// Alt key is pressed (Option on macOS).
25    pub alt: bool,
26    /// Meta/Super key is pressed (Windows key, Cmd on macOS).
27    pub meta: bool,
28}
29
30impl Modifiers {
31    /// No modifiers pressed.
32    pub const NONE: Modifiers = Modifiers {
33        shift: false,
34        ctrl: false,
35        alt: false,
36        meta: false,
37    };
38
39    /// Returns true if any modifier is pressed.
40    pub fn any(&self) -> bool {
41        self.shift || self.ctrl || self.alt || self.meta
42    }
43
44    /// Returns true if Ctrl (or Cmd on macOS) is pressed.
45    pub fn command_or_ctrl(&self) -> bool {
46        #[cfg(target_os = "macos")]
47        {
48            self.meta
49        }
50        #[cfg(not(target_os = "macos"))]
51        {
52            self.ctrl
53        }
54    }
55}
56
57/// Physical key codes for keyboard input.
58///
59/// These represent physical keys on the keyboard, independent of
60/// the character they produce (which depends on keyboard layout).
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum KeyCode {
63    // Letters
64    A,
65    B,
66    C,
67    D,
68    E,
69    F,
70    G,
71    H,
72    I,
73    J,
74    K,
75    L,
76    M,
77    N,
78    O,
79    P,
80    Q,
81    R,
82    S,
83    T,
84    U,
85    V,
86    W,
87    X,
88    Y,
89    Z,
90
91    // Numbers
92    Digit0,
93    Digit1,
94    Digit2,
95    Digit3,
96    Digit4,
97    Digit5,
98    Digit6,
99    Digit7,
100    Digit8,
101    Digit9,
102
103    // Function keys
104    F1,
105    F2,
106    F3,
107    F4,
108    F5,
109    F6,
110    F7,
111    F8,
112    F9,
113    F10,
114    F11,
115    F12,
116
117    // Navigation
118    ArrowUp,
119    ArrowDown,
120    ArrowLeft,
121    ArrowRight,
122    Home,
123    End,
124    PageUp,
125    PageDown,
126
127    // Editing
128    Backspace,
129    Delete,
130    Enter,
131    Tab,
132    Space,
133    Escape,
134
135    // Modifiers (for completeness, usually detected via Modifiers struct)
136    ShiftLeft,
137    ShiftRight,
138    ControlLeft,
139    ControlRight,
140    AltLeft,
141    AltRight,
142    MetaLeft,
143    MetaRight,
144
145    // Punctuation and symbols
146    Minus,
147    Equal,
148    BracketLeft,
149    BracketRight,
150    Backslash,
151    Semicolon,
152    Quote,
153    Comma,
154    Period,
155    Slash,
156    Backquote,
157
158    /// Key not recognized or not mapped.
159    Unknown,
160}
161
162/// A keyboard input event.
163///
164/// Contains information about which key was pressed/released,
165/// the text it produces (if any), and modifier state.
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct KeyEvent {
168    /// The physical key that was pressed.
169    pub key_code: KeyCode,
170    /// The text produced by this key press (may be empty for non-character keys).
171    /// This accounts for keyboard layout and modifiers (e.g., Shift+A = "A").
172    pub text: String,
173    /// Current state of modifier keys.
174    pub modifiers: Modifiers,
175    /// Type of event (down or up).
176    pub event_type: KeyEventType,
177}
178
179impl KeyEvent {
180    /// Creates a new key event.
181    pub fn new(
182        key_code: KeyCode,
183        text: impl Into<String>,
184        modifiers: Modifiers,
185        event_type: KeyEventType,
186    ) -> Self {
187        Self {
188            key_code,
189            text: text.into(),
190            modifiers,
191            event_type,
192        }
193    }
194
195    /// Creates a key down event with the given key code and text.
196    pub fn key_down(key_code: KeyCode, text: impl Into<String>) -> Self {
197        Self::new(key_code, text, Modifiers::NONE, KeyEventType::KeyDown)
198    }
199
200    /// Creates a key down event with modifiers.
201    pub fn key_down_with_modifiers(
202        key_code: KeyCode,
203        text: impl Into<String>,
204        modifiers: Modifiers,
205    ) -> Self {
206        Self::new(key_code, text, modifiers, KeyEventType::KeyDown)
207    }
208
209    /// Returns true if this is a key down event.
210    pub fn is_key_down(&self) -> bool {
211        self.event_type == KeyEventType::KeyDown
212    }
213
214    /// Returns true if this key produces printable text.
215    pub fn has_text(&self) -> bool {
216        !self.text.is_empty()
217    }
218}
219
220impl fmt::Display for KeyEvent {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        write!(
223            f,
224            "KeyEvent({:?}, text=\"{}\", {:?})",
225            self.key_code, self.text, self.event_type
226        )
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn key_event_creation() {
236        let event = KeyEvent::key_down(KeyCode::A, "a");
237        assert_eq!(event.key_code, KeyCode::A);
238        assert_eq!(event.text, "a");
239        assert!(event.is_key_down());
240        assert!(event.has_text());
241    }
242
243    #[test]
244    fn key_event_with_modifiers() {
245        let modifiers = Modifiers {
246            shift: true,
247            ctrl: false,
248            alt: false,
249            meta: false,
250        };
251        let event = KeyEvent::key_down_with_modifiers(KeyCode::A, "A", modifiers);
252        assert_eq!(event.text, "A");
253        assert!(event.modifiers.shift);
254    }
255
256    #[test]
257    fn backspace_has_no_text() {
258        let event = KeyEvent::key_down(KeyCode::Backspace, "");
259        assert!(!event.has_text());
260    }
261
262    #[test]
263    fn modifiers_any() {
264        assert!(!Modifiers::NONE.any());
265        assert!(Modifiers {
266            shift: true,
267            ..Modifiers::NONE
268        }
269        .any());
270    }
271}