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 is a key up event.
215    pub fn is_key_up(&self) -> bool {
216        self.event_type == KeyEventType::KeyUp
217    }
218
219    /// Returns true if this key produces printable text.
220    pub fn has_text(&self) -> bool {
221        !self.text.is_empty()
222    }
223}
224
225impl fmt::Display for KeyEvent {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(
228            f,
229            "KeyEvent({:?}, text=\"{}\", {:?})",
230            self.key_code, self.text, self.event_type
231        )
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn key_event_creation() {
241        let event = KeyEvent::key_down(KeyCode::A, "a");
242        assert_eq!(event.key_code, KeyCode::A);
243        assert_eq!(event.text, "a");
244        assert!(event.is_key_down());
245        assert!(event.has_text());
246    }
247
248    #[test]
249    fn key_event_with_modifiers() {
250        let modifiers = Modifiers {
251            shift: true,
252            ctrl: false,
253            alt: false,
254            meta: false,
255        };
256        let event = KeyEvent::key_down_with_modifiers(KeyCode::A, "A", modifiers);
257        assert_eq!(event.text, "A");
258        assert!(event.modifiers.shift);
259    }
260
261    #[test]
262    fn backspace_has_no_text() {
263        let event = KeyEvent::key_down(KeyCode::Backspace, "");
264        assert!(!event.has_text());
265    }
266
267    #[test]
268    fn modifiers_any() {
269        assert!(!Modifiers::NONE.any());
270        assert!(Modifiers {
271            shift: true,
272            ..Modifiers::NONE
273        }
274        .any());
275    }
276}