hojicha_core/
event.rs

1//! Event handling for keyboard, mouse, and other terminal events
2//!
3//! This module defines all the event types that your Hojicha application can receive.
4//! Events are the primary way your application interacts with the terminal and user input.
5//!
6//! ## Event Categories
7//!
8//! ### User Events
9//! Custom messages specific to your application:
10//! ```
11//! # use hojicha_core::event::Event;
12//! # #[derive(Debug, Clone, PartialEq)]
13//! enum MyMessage {
14//!     ButtonClicked,
15//!     DataLoaded(String),
16//! }
17//!
18//! let event = Event::User(MyMessage::ButtonClicked);
19//! ```
20//!
21//! ### Input Events
22//! Keyboard and mouse input from the user:
23//! ```
24//! # use hojicha_core::event::{Event, Key, KeyEvent, KeyModifiers};
25//! # type MyMessage = ();
26//! # let event: Event<MyMessage> = Event::Tick;
27//! match event {
28//!     Event::Key(key) if key.key == Key::Char('q') => {
29//!         // Handle quit
30//!     }
31//!     Event::Mouse(mouse) => {
32//!         // Handle mouse click/movement
33//!     }
34//!     _ => {}
35//! }
36//! ```
37//!
38//! ### System Events
39//! Terminal and application lifecycle events:
40//! - `Event::Resize` - Terminal was resized
41//! - `Event::Focus` / `Event::Blur` - Terminal gained/lost focus  
42//! - `Event::Suspend` / `Event::Resume` - App was suspended/resumed
43//! - `Event::Tick` - Periodic timer tick
44//! - `Event::Quit` - Application should exit
45
46use crossterm::event::KeyCode;
47pub use crossterm::event::{KeyModifiers, MouseButton, MouseEventKind};
48
49/// An event that can be received by the program
50#[derive(Debug, Clone, PartialEq)]
51pub enum Event<M> {
52    /// A keyboard event
53    Key(KeyEvent),
54    /// A mouse event  
55    Mouse(MouseEvent),
56    /// Terminal was resized
57    Resize {
58        /// New terminal width in columns
59        width: u16,
60        /// New terminal height in rows
61        height: u16,
62    },
63    /// A tick event (for animations, etc.)
64    Tick,
65    /// User-defined message
66    User(M),
67    /// Request to quit the program
68    Quit,
69    /// Terminal gained focus
70    Focus,
71    /// Terminal lost focus
72    Blur,
73    /// Program suspend request (Ctrl+Z)
74    Suspend,
75    /// Program resumed from suspend
76    Resume,
77    /// Bracketed paste event
78    Paste(String),
79    /// Internal event to trigger external process execution
80    #[doc(hidden)]
81    ExecProcess,
82}
83
84impl<M> Event<M> {
85    /// Check if this is a key event
86    #[inline(always)]
87    pub const fn is_key(&self) -> bool {
88        matches!(self, Event::Key(_))
89    }
90
91    /// Check if this is a specific key press
92    ///
93    /// # Example
94    /// ```no_run
95    /// # use hojicha_core::{Event, Key};
96    /// # let event: Event<()> = Event::Tick;
97    /// if event.is_key_press(Key::Enter) {
98    ///     // Handle enter key
99    /// }
100    /// ```
101    #[inline(always)]
102    pub fn is_key_press(&self, key: Key) -> bool {
103        matches!(self, Event::Key(k) if k.key == key)
104    }
105
106    /// Check if this is a specific key with modifiers
107    ///
108    /// # Example
109    /// ```no_run
110    /// # use hojicha_core::{Event, Key, KeyModifiers};
111    /// # let event: Event<()> = Event::Tick;
112    /// if event.is_key_with_modifiers(Key::Char('c'), KeyModifiers::CONTROL) {
113    ///     // Handle Ctrl+C
114    /// }
115    /// ```
116    pub fn is_key_with_modifiers(&self, key: Key, modifiers: KeyModifiers) -> bool {
117        matches!(self, Event::Key(k) if k.key == key && k.modifiers == modifiers)
118    }
119
120    /// Get the key event if this is a key event
121    #[inline(always)]
122    pub const fn as_key(&self) -> Option<&KeyEvent> {
123        match self {
124            Event::Key(k) => Some(k),
125            _ => None,
126        }
127    }
128
129    /// Check if this is a mouse event
130    #[inline(always)]
131    pub const fn is_mouse(&self) -> bool {
132        matches!(self, Event::Mouse(_))
133    }
134
135    /// Get the mouse event if this is a mouse event
136    #[inline(always)]
137    pub const fn as_mouse(&self) -> Option<&MouseEvent> {
138        match self {
139            Event::Mouse(m) => Some(m),
140            _ => None,
141        }
142    }
143
144    /// Check if this is a mouse click at any position
145    pub fn is_click(&self) -> bool {
146        matches!(self, Event::Mouse(m) if m.is_click())
147    }
148
149    /// Get click position if this is a click event
150    ///
151    /// # Example
152    /// ```no_run
153    /// # use hojicha_core::Event;
154    /// # let event: Event<()> = Event::Tick;
155    /// if let Some((x, y)) = event.as_click() {
156    ///     // Handle click at position (x, y)
157    /// }
158    /// ```
159    pub fn as_click(&self) -> Option<(u16, u16)> {
160        match self {
161            Event::Mouse(m) if m.is_click() => Some(m.position()),
162            _ => None,
163        }
164    }
165
166    /// Check if this is a resize event
167    pub fn is_resize(&self) -> bool {
168        matches!(self, Event::Resize { .. })
169    }
170
171    /// Get resize dimensions if this is a resize event
172    ///
173    /// # Example
174    /// ```no_run
175    /// # use hojicha_core::Event;
176    /// # let event: Event<()> = Event::Tick;
177    /// if let Some((width, height)) = event.as_resize() {
178    ///     // Handle resize to width x height
179    /// }
180    /// ```
181    pub fn as_resize(&self) -> Option<(u16, u16)> {
182        match self {
183            Event::Resize { width, height } => Some((*width, *height)),
184            _ => None,
185        }
186    }
187
188    /// Check if this is a user message
189    #[inline(always)]
190    pub const fn is_user(&self) -> bool {
191        matches!(self, Event::User(_))
192    }
193
194    /// Get the user message if this is a user event
195    #[inline(always)]
196    pub const fn as_user(&self) -> Option<&M> {
197        match self {
198            Event::User(msg) => Some(msg),
199            _ => None,
200        }
201    }
202
203    /// Take the user message if this is a user event
204    pub fn into_user(self) -> Option<M> {
205        match self {
206            Event::User(msg) => Some(msg),
207            _ => None,
208        }
209    }
210
211    /// Check if this is a quit event
212    #[inline(always)]
213    pub const fn is_quit(&self) -> bool {
214        matches!(self, Event::Quit)
215    }
216
217    /// Check if this is a tick event
218    #[inline(always)]
219    pub const fn is_tick(&self) -> bool {
220        matches!(self, Event::Tick)
221    }
222
223    /// Check if this is a paste event
224    pub fn is_paste(&self) -> bool {
225        matches!(self, Event::Paste(_))
226    }
227
228    /// Get pasted text if this is a paste event
229    pub fn as_paste(&self) -> Option<&str> {
230        match self {
231            Event::Paste(text) => Some(text.as_str()),
232            _ => None,
233        }
234    }
235
236    /// Check if this is a focus event
237    pub fn is_focus(&self) -> bool {
238        matches!(self, Event::Focus)
239    }
240
241    /// Check if this is a blur event
242    pub fn is_blur(&self) -> bool {
243        matches!(self, Event::Blur)
244    }
245
246    /// Check if this is a suspend event
247    pub fn is_suspend(&self) -> bool {
248        matches!(self, Event::Suspend)
249    }
250
251    /// Check if this is a resume event
252    pub fn is_resume(&self) -> bool {
253        matches!(self, Event::Resume)
254    }
255}
256
257/// Window size information
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct WindowSize {
260    /// Width in columns
261    pub width: u16,
262    /// Height in rows
263    pub height: u16,
264}
265
266/// A keyboard event
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub struct KeyEvent {
269    /// The key that was pressed
270    pub key: Key,
271    /// Key modifiers (Ctrl, Alt, Shift)
272    pub modifiers: KeyModifiers,
273}
274
275impl KeyEvent {
276    /// Create a new key event
277    pub fn new(key: Key, modifiers: KeyModifiers) -> Self {
278        Self { key, modifiers }
279    }
280
281    /// Check if this is a simple character key press
282    pub fn is_char(&self) -> bool {
283        matches!(self.key, Key::Char(_))
284    }
285
286    /// Get the character if this is a character key
287    pub fn char(&self) -> Option<char> {
288        match self.key {
289            Key::Char(c) => Some(c),
290            _ => None,
291        }
292    }
293
294    /// Check if this key event matches a specific key
295    ///
296    /// # Example
297    /// ```no_run
298    /// # use hojicha_core::{Key, KeyEvent, KeyModifiers};
299    /// # let key_event = KeyEvent { key: Key::Enter, modifiers: KeyModifiers::empty() };
300    /// if key_event.is(Key::Enter) {
301    ///     // Handle enter key
302    /// }
303    /// ```
304    pub fn is(&self, key: Key) -> bool {
305        self.key == key
306    }
307
308    /// Check if this key event matches a specific key with modifiers
309    ///
310    /// # Example
311    /// ```no_run
312    /// # use hojicha_core::{Key, KeyEvent, KeyModifiers};
313    /// # let key_event = KeyEvent { key: Key::Char('s'), modifiers: KeyModifiers::CONTROL };
314    /// if key_event.is_with_modifiers(Key::Char('s'), KeyModifiers::CONTROL) {
315    ///     // Handle Ctrl+S
316    /// }
317    /// ```
318    pub fn is_with_modifiers(&self, key: Key, modifiers: KeyModifiers) -> bool {
319        self.key == key && self.modifiers == modifiers
320    }
321
322    /// Check if control key is held
323    pub fn is_ctrl(&self) -> bool {
324        self.modifiers.contains(KeyModifiers::CONTROL)
325    }
326
327    /// Check if alt key is held
328    pub fn is_alt(&self) -> bool {
329        self.modifiers.contains(KeyModifiers::ALT)
330    }
331
332    /// Check if shift key is held
333    pub fn is_shift(&self) -> bool {
334        self.modifiers.contains(KeyModifiers::SHIFT)
335    }
336
337    /// Check if super/meta key is held
338    pub fn is_super(&self) -> bool {
339        self.modifiers.contains(KeyModifiers::SUPER)
340    }
341
342    /// Check if this is a navigation key (arrows, home, end, page up/down)
343    pub fn is_navigation(&self) -> bool {
344        matches!(
345            self.key,
346            Key::Up
347                | Key::Down
348                | Key::Left
349                | Key::Right
350                | Key::Home
351                | Key::End
352                | Key::PageUp
353                | Key::PageDown
354        )
355    }
356
357    /// Check if this is a function key (F1-F24)
358    pub fn is_function_key(&self) -> bool {
359        matches!(self.key, Key::F(_))
360    }
361
362    /// Check if this is a media control key
363    pub fn is_media_key(&self) -> bool {
364        matches!(
365            self.key,
366            Key::MediaPlay
367                | Key::MediaPause
368                | Key::MediaPlayPause
369                | Key::MediaStop
370                | Key::MediaNext
371                | Key::MediaPrevious
372                | Key::MediaFastForward
373                | Key::MediaRewind
374                | Key::MediaVolumeUp
375                | Key::MediaVolumeDown
376                | Key::MediaMute
377        )
378    }
379
380    /// Check if this key event has any modifiers
381    pub fn has_modifiers(&self) -> bool {
382        !self.modifiers.is_empty()
383    }
384
385    /// Check if this key event has no modifiers
386    pub fn no_modifiers(&self) -> bool {
387        self.modifiers.is_empty()
388    }
389}
390
391/// Modifier key types
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
393pub enum ModifierKey {
394    /// Shift key
395    Shift,
396    /// Control key
397    Control,
398    /// Alt/Option key
399    Alt,
400    /// Super/Windows/Command key
401    Super,
402    /// Meta key
403    Meta,
404    /// Hyper key
405    Hyper,
406}
407
408/// Represents a key on the keyboard
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
410pub enum Key {
411    /// A character key
412    Char(char),
413    /// Backspace key
414    Backspace,
415    /// Enter/Return key
416    Enter,
417    /// Left arrow
418    Left,
419    /// Right arrow
420    Right,
421    /// Up arrow
422    Up,
423    /// Down arrow
424    Down,
425    /// Home key
426    Home,
427    /// End key
428    End,
429    /// Page up
430    PageUp,
431    /// Page down
432    PageDown,
433    /// Tab key
434    Tab,
435    /// Delete key
436    Delete,
437    /// Insert key
438    Insert,
439    /// Escape key
440    Esc,
441    /// Function keys
442    F(u8),
443    /// Null key (usually Ctrl+@)
444    Null,
445    /// Caps Lock key
446    CapsLock,
447    /// Scroll Lock key
448    ScrollLock,
449    /// Num Lock key
450    NumLock,
451    /// Print Screen key
452    PrintScreen,
453    /// Pause/Break key
454    Pause,
455    /// Menu/Application key
456    Menu,
457    /// Keypad Begin (5 on keypad with NumLock off)
458    KeypadBegin,
459    /// Media Play
460    MediaPlay,
461    /// Media Pause
462    MediaPause,
463    /// Media Play/Pause toggle
464    MediaPlayPause,
465    /// Media Stop
466    MediaStop,
467    /// Media Next Track
468    MediaNext,
469    /// Media Previous Track
470    MediaPrevious,
471    /// Media Fast Forward
472    MediaFastForward,
473    /// Media Rewind
474    MediaRewind,
475    /// Media Volume Up
476    MediaVolumeUp,
477    /// Media Volume Down
478    MediaVolumeDown,
479    /// Media Mute
480    MediaMute,
481    /// Modifier key (Shift, Ctrl, Alt, Super/Meta)
482    Modifier(ModifierKey),
483}
484
485impl From<crossterm::event::KeyEvent> for KeyEvent {
486    fn from(event: crossterm::event::KeyEvent) -> Self {
487        let key = match event.code {
488            KeyCode::Char(c) => Key::Char(c),
489            KeyCode::Backspace => Key::Backspace,
490            KeyCode::Enter => Key::Enter,
491            KeyCode::Left => Key::Left,
492            KeyCode::Right => Key::Right,
493            KeyCode::Up => Key::Up,
494            KeyCode::Down => Key::Down,
495            KeyCode::Home => Key::Home,
496            KeyCode::End => Key::End,
497            KeyCode::PageUp => Key::PageUp,
498            KeyCode::PageDown => Key::PageDown,
499            KeyCode::Tab => Key::Tab,
500            KeyCode::BackTab => Key::Tab, // BackTab is Shift+Tab
501            KeyCode::Delete => Key::Delete,
502            KeyCode::Insert => Key::Insert,
503            KeyCode::Esc => Key::Esc,
504            KeyCode::F(n) => Key::F(n),
505            KeyCode::Null => Key::Null,
506            KeyCode::CapsLock => Key::CapsLock,
507            KeyCode::ScrollLock => Key::ScrollLock,
508            KeyCode::NumLock => Key::NumLock,
509            KeyCode::PrintScreen => Key::PrintScreen,
510            KeyCode::Pause => Key::Pause,
511            KeyCode::Menu => Key::Menu,
512            KeyCode::KeypadBegin => Key::KeypadBegin,
513            // Media keys - crossterm doesn't have all of these, so we handle what we can
514            KeyCode::Media(crossterm::event::MediaKeyCode::Play) => Key::MediaPlay,
515            KeyCode::Media(crossterm::event::MediaKeyCode::Pause) => Key::MediaPause,
516            KeyCode::Media(crossterm::event::MediaKeyCode::PlayPause) => Key::MediaPlayPause,
517            KeyCode::Media(crossterm::event::MediaKeyCode::Stop) => Key::MediaStop,
518            KeyCode::Media(crossterm::event::MediaKeyCode::FastForward) => Key::MediaFastForward,
519            KeyCode::Media(crossterm::event::MediaKeyCode::Rewind) => Key::MediaRewind,
520            KeyCode::Media(crossterm::event::MediaKeyCode::TrackNext) => Key::MediaNext,
521            KeyCode::Media(crossterm::event::MediaKeyCode::TrackPrevious) => Key::MediaPrevious,
522            KeyCode::Media(crossterm::event::MediaKeyCode::LowerVolume) => Key::MediaVolumeDown,
523            KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume) => Key::MediaVolumeUp,
524            KeyCode::Media(crossterm::event::MediaKeyCode::MuteVolume) => Key::MediaMute,
525            // Modifier keys - these are usually not sent as separate events but we can handle them
526            KeyCode::Modifier(
527                crossterm::event::ModifierKeyCode::LeftShift
528                | crossterm::event::ModifierKeyCode::RightShift,
529            ) => Key::Modifier(ModifierKey::Shift),
530            KeyCode::Modifier(
531                crossterm::event::ModifierKeyCode::LeftControl
532                | crossterm::event::ModifierKeyCode::RightControl,
533            ) => Key::Modifier(ModifierKey::Control),
534            KeyCode::Modifier(
535                crossterm::event::ModifierKeyCode::LeftAlt
536                | crossterm::event::ModifierKeyCode::RightAlt,
537            ) => Key::Modifier(ModifierKey::Alt),
538            KeyCode::Modifier(
539                crossterm::event::ModifierKeyCode::LeftSuper
540                | crossterm::event::ModifierKeyCode::RightSuper,
541            ) => Key::Modifier(ModifierKey::Super),
542            KeyCode::Modifier(
543                crossterm::event::ModifierKeyCode::LeftMeta
544                | crossterm::event::ModifierKeyCode::RightMeta,
545            ) => Key::Modifier(ModifierKey::Meta),
546            KeyCode::Modifier(
547                crossterm::event::ModifierKeyCode::LeftHyper
548                | crossterm::event::ModifierKeyCode::RightHyper,
549            ) => Key::Modifier(ModifierKey::Hyper),
550            _ => Key::Null, // Map unmapped keys to Null
551        };
552
553        Self {
554            key,
555            modifiers: event.modifiers,
556        }
557    }
558}
559
560/// A mouse event
561///
562/// # Example
563/// ```no_run
564/// # use hojicha_core::{Event, MouseEvent};
565/// # let event: Event<()> = Event::Tick;
566/// match event {
567///     Event::Mouse(mouse) => {
568///         if mouse.is_left_click() {
569///             println!("Left clicked at ({}, {})", mouse.column, mouse.row);
570///         } else if mouse.is_scroll_up() {
571///             println!("Scrolled up");
572///         }
573///     }
574///     _ => {}
575/// }
576/// ```
577#[derive(Debug, Clone, Copy, PartialEq)]
578pub struct MouseEvent {
579    /// The kind of mouse event
580    pub kind: MouseEventKind,
581    /// Column (x coordinate)
582    pub column: u16,
583    /// Row (y coordinate)
584    pub row: u16,
585    /// Key modifiers held during the event
586    pub modifiers: KeyModifiers,
587}
588
589impl MouseEvent {
590    /// Create a new mouse event
591    pub fn new(kind: MouseEventKind, column: u16, row: u16, modifiers: KeyModifiers) -> Self {
592        Self {
593            kind,
594            column,
595            row,
596            modifiers,
597        }
598    }
599
600    /// Check if this is a left button click (button down event)
601    pub fn is_left_click(&self) -> bool {
602        matches!(self.kind, MouseEventKind::Down(MouseButton::Left))
603    }
604
605    /// Check if this is a right button click (button down event)
606    pub fn is_right_click(&self) -> bool {
607        matches!(self.kind, MouseEventKind::Down(MouseButton::Right))
608    }
609
610    /// Check if this is a middle button click (button down event)
611    pub fn is_middle_click(&self) -> bool {
612        matches!(self.kind, MouseEventKind::Down(MouseButton::Middle))
613    }
614
615    /// Check if this is any button click (button down event)
616    pub fn is_click(&self) -> bool {
617        matches!(self.kind, MouseEventKind::Down(_))
618    }
619
620    /// Check if this is a button release event
621    pub fn is_release(&self) -> bool {
622        matches!(self.kind, MouseEventKind::Up(_))
623    }
624
625    /// Check if this is a drag event (mouse moved while button pressed)
626    pub fn is_drag(&self) -> bool {
627        matches!(self.kind, MouseEventKind::Drag(_))
628    }
629
630    /// Check if this is a left button drag
631    pub fn is_left_drag(&self) -> bool {
632        matches!(self.kind, MouseEventKind::Drag(MouseButton::Left))
633    }
634
635    /// Check if this is a right button drag
636    pub fn is_right_drag(&self) -> bool {
637        matches!(self.kind, MouseEventKind::Drag(MouseButton::Right))
638    }
639
640    /// Check if this is a middle button drag
641    pub fn is_middle_drag(&self) -> bool {
642        matches!(self.kind, MouseEventKind::Drag(MouseButton::Middle))
643    }
644
645    /// Check if this is a scroll up event
646    pub fn is_scroll_up(&self) -> bool {
647        matches!(self.kind, MouseEventKind::ScrollUp)
648    }
649
650    /// Check if this is a scroll down event
651    pub fn is_scroll_down(&self) -> bool {
652        matches!(self.kind, MouseEventKind::ScrollDown)
653    }
654
655    /// Check if this is a scroll left event
656    pub fn is_scroll_left(&self) -> bool {
657        matches!(self.kind, MouseEventKind::ScrollLeft)
658    }
659
660    /// Check if this is a scroll right event
661    pub fn is_scroll_right(&self) -> bool {
662        matches!(self.kind, MouseEventKind::ScrollRight)
663    }
664
665    /// Check if this is any scroll event
666    pub fn is_scroll(&self) -> bool {
667        matches!(
668            self.kind,
669            MouseEventKind::ScrollUp
670                | MouseEventKind::ScrollDown
671                | MouseEventKind::ScrollLeft
672                | MouseEventKind::ScrollRight
673        )
674    }
675
676    /// Check if this is a mouse move event (without button pressed)
677    pub fn is_move(&self) -> bool {
678        matches!(self.kind, MouseEventKind::Moved)
679    }
680
681    /// Get the button involved in this event, if any
682    pub fn button(&self) -> Option<MouseButton> {
683        match self.kind {
684            MouseEventKind::Down(btn) | MouseEventKind::Up(btn) | MouseEventKind::Drag(btn) => {
685                Some(btn)
686            }
687            _ => None,
688        }
689    }
690
691    /// Get the position as a tuple (column, row)
692    pub fn position(&self) -> (u16, u16) {
693        (self.column, self.row)
694    }
695
696    /// Check if the mouse event occurred within a rectangular area
697    ///
698    /// # Example
699    /// ```no_run
700    /// # use hojicha_core::event::{MouseEvent, MouseEventKind, MouseButton, KeyModifiers};
701    /// # let mouse_event = MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 15, row: 15, modifiers: KeyModifiers::empty() };
702    /// let (x, y, width, height) = (10, 10, 20, 10);
703    /// if mouse_event.is_within(x, y, width, height) {
704    ///     // Mouse event is within the rectangle
705    /// }
706    /// ```
707    pub fn is_within(&self, x: u16, y: u16, width: u16, height: u16) -> bool {
708        self.column >= x && self.column < x + width && self.row >= y && self.row < y + height
709    }
710
711    /// Check if the mouse event occurred at a specific position
712    pub fn is_at(&self, column: u16, row: u16) -> bool {
713        self.column == column && self.row == row
714    }
715
716    /// Check if a modifier key was held during the event
717    pub fn has_modifier(&self, modifier: KeyModifiers) -> bool {
718        self.modifiers.contains(modifier)
719    }
720
721    /// Check if control key is held
722    pub fn is_ctrl(&self) -> bool {
723        self.modifiers.contains(KeyModifiers::CONTROL)
724    }
725
726    /// Check if alt key is held
727    pub fn is_alt(&self) -> bool {
728        self.modifiers.contains(KeyModifiers::ALT)
729    }
730
731    /// Check if shift key is held
732    pub fn is_shift(&self) -> bool {
733        self.modifiers.contains(KeyModifiers::SHIFT)
734    }
735
736    /// Check if this mouse event has any modifiers
737    pub fn has_modifiers(&self) -> bool {
738        !self.modifiers.is_empty()
739    }
740
741    /// Check if this mouse event has no modifiers
742    pub fn no_modifiers(&self) -> bool {
743        self.modifiers.is_empty()
744    }
745}
746
747impl From<crossterm::event::MouseEvent> for MouseEvent {
748    fn from(event: crossterm::event::MouseEvent) -> Self {
749        Self {
750            kind: event.kind,
751            column: event.column,
752            row: event.row,
753            modifiers: event.modifiers,
754        }
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761    use proptest::prelude::*;
762
763    #[test]
764    fn test_key_event_creation() {
765        let event = KeyEvent::new(Key::Char('a'), KeyModifiers::empty());
766        assert_eq!(event.key, Key::Char('a'));
767        assert!(event.is_char());
768        assert_eq!(event.char(), Some('a'));
769    }
770
771    #[test]
772    fn test_key_event_modifiers() {
773        let event = KeyEvent::new(Key::Char('c'), KeyModifiers::CONTROL);
774        assert_eq!(event.key, Key::Char('c'));
775        assert_eq!(event.modifiers, KeyModifiers::CONTROL);
776    }
777
778    #[test]
779    fn test_non_char_keys() {
780        let event = KeyEvent::new(Key::Enter, KeyModifiers::empty());
781        assert!(!event.is_char());
782        assert_eq!(event.char(), None);
783    }
784
785    #[test]
786    fn test_window_size_creation() {
787        let size = WindowSize {
788            width: 80,
789            height: 24,
790        };
791        assert_eq!(size.width, 80);
792        assert_eq!(size.height, 24);
793    }
794
795    #[test]
796    fn test_mouse_event_creation() {
797        let event = MouseEvent {
798            kind: MouseEventKind::Down(MouseButton::Left),
799            column: 10,
800            row: 5,
801            modifiers: KeyModifiers::empty(),
802        };
803        assert_eq!(event.column, 10);
804        assert_eq!(event.row, 5);
805    }
806
807    #[test]
808    fn test_event_variants() {
809        let key_event = Event::<String>::Key(KeyEvent::new(Key::Char('a'), KeyModifiers::empty()));
810        let mouse_event = Event::<String>::Mouse(MouseEvent {
811            kind: MouseEventKind::Down(MouseButton::Left),
812            column: 0,
813            row: 0,
814            modifiers: KeyModifiers::empty(),
815        });
816        let resize_event = Event::<String>::Resize {
817            width: 80,
818            height: 24,
819        };
820        let tick_event = Event::<String>::Tick;
821        let user_event = Event::User("test".to_string());
822        let quit_event = Event::<String>::Quit;
823        let focus_event = Event::<String>::Focus;
824        let blur_event = Event::<String>::Blur;
825        let suspend_event = Event::<String>::Suspend;
826        let resume_event = Event::<String>::Resume;
827        let paste_event = Event::<String>::Paste("pasted text".to_string());
828
829        // Test using new helper methods
830        assert!(key_event.is_key());
831        assert!(key_event.as_key().is_some());
832
833        assert!(mouse_event.is_mouse());
834        assert!(mouse_event.as_mouse().is_some());
835
836        assert!(resize_event.is_resize());
837        assert_eq!(resize_event.as_resize(), Some((80, 24)));
838
839        assert!(tick_event.is_tick());
840
841        assert!(user_event.is_user());
842        assert_eq!(user_event.as_user(), Some(&"test".to_string()));
843
844        assert!(quit_event.is_quit());
845        assert!(focus_event.is_focus());
846        assert!(blur_event.is_blur());
847        assert!(suspend_event.is_suspend());
848        assert!(resume_event.is_resume());
849
850        assert!(paste_event.is_paste());
851        assert_eq!(paste_event.as_paste(), Some("pasted text"));
852    }
853
854    #[test]
855    fn test_key_variants() {
856        let keys = vec![
857            Key::Char('a'),
858            Key::Backspace,
859            Key::Enter,
860            Key::Left,
861            Key::Right,
862            Key::Up,
863            Key::Down,
864            Key::Home,
865            Key::End,
866            Key::PageUp,
867            Key::PageDown,
868            Key::Tab,
869            Key::Delete,
870            Key::Insert,
871            Key::Esc,
872            Key::F(1),
873            Key::Null,
874        ];
875
876        for key in keys {
877            let event = KeyEvent::new(key, KeyModifiers::empty());
878            assert_eq!(event.key, key);
879
880            // Test is_char and char methods
881            match key {
882                Key::Char(c) => {
883                    assert!(event.is_char());
884                    assert_eq!(event.char(), Some(c));
885                }
886                _ => {
887                    assert!(!event.is_char());
888                    assert_eq!(event.char(), None);
889                }
890            }
891        }
892    }
893
894    #[test]
895    fn test_crossterm_key_conversion() {
896        let crossterm_event = crossterm::event::KeyEvent::new(
897            KeyCode::Char('x'),
898            KeyModifiers::CONTROL | KeyModifiers::SHIFT,
899        );
900
901        let key_event: KeyEvent = crossterm_event.into();
902        assert_eq!(key_event.key, Key::Char('x'));
903        assert_eq!(
904            key_event.modifiers,
905            KeyModifiers::CONTROL | KeyModifiers::SHIFT
906        );
907    }
908
909    #[test]
910    fn test_enhanced_keys() {
911        use crossterm::event::{KeyCode, KeyModifiers};
912
913        // Test special keys
914        let caps_lock = crossterm::event::KeyEvent::new(KeyCode::CapsLock, KeyModifiers::empty());
915        let key_event = KeyEvent::from(caps_lock);
916        assert_eq!(key_event.key, Key::CapsLock);
917
918        let scroll_lock =
919            crossterm::event::KeyEvent::new(KeyCode::ScrollLock, KeyModifiers::empty());
920        let key_event = KeyEvent::from(scroll_lock);
921        assert_eq!(key_event.key, Key::ScrollLock);
922
923        let print_screen =
924            crossterm::event::KeyEvent::new(KeyCode::PrintScreen, KeyModifiers::empty());
925        let key_event = KeyEvent::from(print_screen);
926        assert_eq!(key_event.key, Key::PrintScreen);
927
928        // Test media keys
929        let play = crossterm::event::KeyEvent::new(
930            KeyCode::Media(crossterm::event::MediaKeyCode::Play),
931            KeyModifiers::empty(),
932        );
933        let key_event = KeyEvent::from(play);
934        assert_eq!(key_event.key, Key::MediaPlay);
935
936        let volume_up = crossterm::event::KeyEvent::new(
937            KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume),
938            KeyModifiers::empty(),
939        );
940        let key_event = KeyEvent::from(volume_up);
941        assert_eq!(key_event.key, Key::MediaVolumeUp);
942
943        // Test modifier keys
944        let left_shift = crossterm::event::KeyEvent::new(
945            KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftShift),
946            KeyModifiers::empty(),
947        );
948        let key_event = KeyEvent::from(left_shift);
949        assert_eq!(key_event.key, Key::Modifier(ModifierKey::Shift));
950
951        let right_alt = crossterm::event::KeyEvent::new(
952            KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightAlt),
953            KeyModifiers::empty(),
954        );
955        let key_event = KeyEvent::from(right_alt);
956        assert_eq!(key_event.key, Key::Modifier(ModifierKey::Alt));
957    }
958
959    #[test]
960    fn test_crossterm_key_conversion_all_keys() {
961        // Test all key code conversions
962        let test_cases = vec![
963            (KeyCode::Backspace, Key::Backspace),
964            (KeyCode::Enter, Key::Enter),
965            (KeyCode::Left, Key::Left),
966            (KeyCode::Right, Key::Right),
967            (KeyCode::Up, Key::Up),
968            (KeyCode::Down, Key::Down),
969            (KeyCode::Home, Key::Home),
970            (KeyCode::End, Key::End),
971            (KeyCode::PageUp, Key::PageUp),
972            (KeyCode::PageDown, Key::PageDown),
973            (KeyCode::Tab, Key::Tab),
974            (KeyCode::Delete, Key::Delete),
975            (KeyCode::Insert, Key::Insert),
976            (KeyCode::Esc, Key::Esc),
977            (KeyCode::F(1), Key::F(1)),
978            (KeyCode::F(12), Key::F(12)),
979            (KeyCode::Null, Key::Null),
980            (KeyCode::BackTab, Key::Tab), // BackTab is Shift+Tab
981        ];
982
983        for (crossterm_code, expected_key) in test_cases {
984            let crossterm_event =
985                crossterm::event::KeyEvent::new(crossterm_code, KeyModifiers::empty());
986            let key_event: KeyEvent = crossterm_event.into();
987            assert_eq!(key_event.key, expected_key);
988        }
989    }
990
991    #[test]
992    fn test_crossterm_mouse_conversion() {
993        let crossterm_event = crossterm::event::MouseEvent {
994            kind: MouseEventKind::Down(MouseButton::Right),
995            column: 42,
996            row: 24,
997            modifiers: KeyModifiers::ALT,
998        };
999
1000        let mouse_event: MouseEvent = crossterm_event.into();
1001        assert_eq!(mouse_event.kind, MouseEventKind::Down(MouseButton::Right));
1002        assert_eq!(mouse_event.column, 42);
1003        assert_eq!(mouse_event.row, 24);
1004        assert_eq!(mouse_event.modifiers, KeyModifiers::ALT);
1005    }
1006
1007    // Property-based tests
1008    proptest! {
1009        #[test]
1010        fn test_key_event_properties(
1011            c in any::<char>(),
1012            ctrl in any::<bool>(),
1013            alt in any::<bool>(),
1014            shift in any::<bool>()
1015        ) {
1016            let mut modifiers = KeyModifiers::empty();
1017            if ctrl { modifiers |= KeyModifiers::CONTROL; }
1018            if alt { modifiers |= KeyModifiers::ALT; }
1019            if shift { modifiers |= KeyModifiers::SHIFT; }
1020
1021            let event = KeyEvent::new(Key::Char(c), modifiers);
1022
1023            prop_assert_eq!(event.key, Key::Char(c));
1024            prop_assert_eq!(event.modifiers, modifiers);
1025            prop_assert!(event.is_char());
1026            prop_assert_eq!(event.char(), Some(c));
1027        }
1028
1029        #[test]
1030        fn test_window_size_properties(
1031            width in 1u16..1000u16,
1032            height in 1u16..1000u16
1033        ) {
1034            let size = WindowSize { width, height };
1035
1036            prop_assert_eq!(size.width, width);
1037            prop_assert_eq!(size.height, height);
1038
1039            // Test equality and cloning
1040            let size2 = size;
1041            prop_assert_eq!(size, size2);
1042        }
1043
1044        #[test]
1045        fn test_mouse_event_properties(
1046            column in 0u16..1000u16,
1047            row in 0u16..1000u16,
1048            button_idx in 0..3usize
1049        ) {
1050            let button = match button_idx {
1051                0 => MouseButton::Left,
1052                1 => MouseButton::Right,
1053                2 => MouseButton::Middle,
1054                _ => unreachable!(),
1055            };
1056
1057            let event = MouseEvent {
1058                kind: MouseEventKind::Down(button),
1059                column,
1060                row,
1061                modifiers: KeyModifiers::empty(),
1062            };
1063
1064            prop_assert_eq!(event.column, column);
1065            prop_assert_eq!(event.row, row);
1066            prop_assert_eq!(event.kind, MouseEventKind::Down(button));
1067        }
1068
1069        #[test]
1070        fn test_event_user_message_properties(
1071            message in ".*"
1072        ) {
1073            let event = Event::User(message.clone());
1074
1075            prop_assert!(event.is_user());
1076            prop_assert_eq!(event.as_user(), Some(&message));
1077        }
1078
1079        #[test]
1080        fn test_event_resize_properties(
1081            width in 1u16..1000u16,
1082            height in 1u16..1000u16
1083        ) {
1084            let event = Event::<String>::Resize { width, height };
1085
1086            prop_assert!(event.is_resize());
1087            prop_assert_eq!(event.as_resize(), Some((width, height)));
1088        }
1089
1090        #[test]
1091        fn test_event_paste_properties(
1092            paste_text in ".*"
1093        ) {
1094            let event = Event::<String>::Paste(paste_text.clone());
1095
1096            prop_assert!(event.is_paste());
1097            prop_assert_eq!(event.as_paste(), Some(paste_text.as_str()));
1098        }
1099
1100        #[test]
1101        fn test_function_key_properties(
1102            key_num in 1u8..25u8
1103        ) {
1104            let key = Key::F(key_num);
1105            let event = KeyEvent::new(key, KeyModifiers::empty());
1106
1107            prop_assert_eq!(event.key, Key::F(key_num));
1108            prop_assert!(!event.is_char());
1109            prop_assert_eq!(event.char(), None);
1110        }
1111
1112        #[test]
1113        fn test_key_modifier_combinations(
1114            ctrl in any::<bool>(),
1115            alt in any::<bool>(),
1116            shift in any::<bool>(),
1117            super_key in any::<bool>()
1118        ) {
1119            let mut modifiers = KeyModifiers::empty();
1120            if ctrl { modifiers |= KeyModifiers::CONTROL; }
1121            if alt { modifiers |= KeyModifiers::ALT; }
1122            if shift { modifiers |= KeyModifiers::SHIFT; }
1123            if super_key { modifiers |= KeyModifiers::SUPER; }
1124
1125            let event = KeyEvent::new(Key::Char('a'), modifiers);
1126
1127            prop_assert_eq!(event.modifiers.contains(KeyModifiers::CONTROL), ctrl);
1128            prop_assert_eq!(event.modifiers.contains(KeyModifiers::ALT), alt);
1129            prop_assert_eq!(event.modifiers.contains(KeyModifiers::SHIFT), shift);
1130            prop_assert_eq!(event.modifiers.contains(KeyModifiers::SUPER), super_key);
1131        }
1132    }
1133}