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