Skip to main content

photon_ui/
events.rs

1use crossterm::event::{
2    KeyCode,
3    KeyEvent,
4    KeyEventKind,
5    KeyModifiers,
6};
7
8/// Unified input event type used by the framework.
9///
10/// Wraps crossterm events into a simpler enum that components consume via
11/// [`Component::handle_input`](crate::Component::handle_input).
12#[derive(Debug, Clone, PartialEq)]
13pub enum Event {
14    /// A keyboard event.
15    Key(KeyEvent),
16    /// A mouse event (click, scroll, drag).
17    Mouse(crossterm::event::MouseEvent),
18    /// Terminal resized to the given dimensions `(cols, rows)`.
19    Resize(u16, u16),
20    /// Bracketed paste payload.
21    Paste(String),
22    /// Terminal gained focus.
23    FocusGained,
24    /// Terminal lost focus.
25    FocusLost,
26}
27
28/// A portable representation of a key press with modifier state.
29///
30/// Unlike raw crossterm [`KeyEvent`], this type
31/// is `Hash` and `Eq`, making it suitable for use in maps such as
32/// [`KeybindingsManager`](crate::keybindings::KeybindingsManager).
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum Key {
35    /// A printable character with modifiers.
36    Char(char, Modifiers),
37    /// A special key code with modifiers.
38    Code(KeyCode, Modifiers),
39}
40
41/// Modifier keys held during an event.
42#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct Modifiers {
44    /// Shift was held.
45    pub shift: bool,
46    /// Control was held.
47    pub ctrl: bool,
48    /// Alt / Option was held.
49    pub alt: bool,
50}
51
52impl Modifiers {
53    /// No modifiers.
54    pub fn none() -> Self {
55        Self::default()
56    }
57
58    /// Control only.
59    pub fn ctrl() -> Self {
60        Self {
61            ctrl: true,
62            ..Default::default()
63        }
64    }
65
66    /// Shift only.
67    pub fn shift() -> Self {
68        Self {
69            shift: true,
70            ..Default::default()
71        }
72    }
73
74    /// Alt only.
75    pub fn alt() -> Self {
76        Self {
77            alt: true,
78            ..Default::default()
79        }
80    }
81
82    /// Control + Shift.
83    pub fn ctrl_shift() -> Self {
84        Self {
85            ctrl: true,
86            shift: true,
87            ..Default::default()
88        }
89    }
90}
91
92impl Key {
93    /// Enter / Return with no modifiers.
94    pub fn enter() -> Self {
95        Key::Code(KeyCode::Enter, Modifiers::none())
96    }
97
98    /// Left arrow with no modifiers.
99    pub fn left() -> Self {
100        Key::Code(KeyCode::Left, Modifiers::none())
101    }
102
103    /// Right arrow with no modifiers.
104    pub fn right() -> Self {
105        Key::Code(KeyCode::Right, Modifiers::none())
106    }
107
108    /// Up arrow with no modifiers.
109    pub fn up() -> Self {
110        Key::Code(KeyCode::Up, Modifiers::none())
111    }
112
113    /// Down arrow with no modifiers.
114    pub fn down() -> Self {
115        Key::Code(KeyCode::Down, Modifiers::none())
116    }
117
118    /// Backspace with no modifiers.
119    pub fn backspace() -> Self {
120        Key::Code(KeyCode::Backspace, Modifiers::none())
121    }
122
123    /// Delete with no modifiers.
124    pub fn delete() -> Self {
125        Key::Code(KeyCode::Delete, Modifiers::none())
126    }
127
128    /// Home with no modifiers.
129    pub fn home() -> Self {
130        Key::Code(KeyCode::Home, Modifiers::none())
131    }
132
133    /// End with no modifiers.
134    pub fn end() -> Self {
135        Key::Code(KeyCode::End, Modifiers::none())
136    }
137
138    /// Tab with no modifiers.
139    pub fn tab() -> Self {
140        Key::Code(KeyCode::Tab, Modifiers::none())
141    }
142
143    /// Escape with no modifiers.
144    pub fn esc() -> Self {
145        Key::Code(KeyCode::Esc, Modifiers::none())
146    }
147
148    /// Character with Control held.
149    pub fn ctrl(c: char) -> Self {
150        Key::Char(c, Modifiers::ctrl())
151    }
152
153    /// Character with Control + Shift held.
154    pub fn ctrl_shift(c: char) -> Self {
155        Key::Char(c, Modifiers::ctrl_shift())
156    }
157
158    /// Character with Alt held.
159    pub fn alt(c: char) -> Self {
160        Key::Char(c, Modifiers::alt())
161    }
162
163    /// Character with no modifiers.
164    pub fn char(c: char) -> Self {
165        Key::Char(c, Modifiers::none())
166    }
167}
168
169/// Check whether an [`Event`] matches a specific [`Key`].
170///
171/// Returns `false` for non-key events and for `Release` key events
172/// (only `Press` and `Repeat` are matched).
173///
174/// # Example
175///
176/// ```
177/// use crossterm::event::{
178///     KeyCode,
179///     KeyEvent,
180///     KeyModifiers,
181/// };
182/// use photon_ui::{
183///     Event,
184///     Key,
185///     matches_key,
186/// };
187///
188/// let event = Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL));
189/// assert!(matches_key(&event, &Key::ctrl('c')));
190/// ```
191pub fn matches_key(event: &Event, key: &Key) -> bool {
192    let Event::Key(key_event) = event else {
193        return false;
194    };
195    if key_event.kind == KeyEventKind::Release {
196        return false;
197    }
198    let km = key_event.modifiers;
199    let mods = Modifiers {
200        shift: km.contains(KeyModifiers::SHIFT),
201        ctrl: km.contains(KeyModifiers::CONTROL),
202        alt: km.contains(KeyModifiers::ALT),
203    };
204    match key {
205        | Key::Char(expected, expected_mods) => {
206            if let KeyCode::Char(c) = key_event.code {
207                c == *expected && mods == *expected_mods
208            } else {
209                false
210            }
211        },
212        | Key::Code(expected, expected_mods) => {
213            key_event.code == *expected && mods == *expected_mods
214        },
215    }
216}