Skip to main content

ftui_core/
event.rs

1#![forbid(unsafe_code)]
2
3//! Canonical input/event types.
4//!
5//! This module defines the standard event types used throughout ftui for
6//! input handling. All events derive `Clone`, `PartialEq`, and `Eq` for
7//! use in tests and pattern matching.
8//!
9//! # Design Notes
10//!
11//! - Mouse coordinates are 0-indexed (terminal is 1-indexed internally)
12//! - `KeyEventKind` defaults to `Press` when not available from the terminal
13//! - `Modifiers` use bitflags for easy combination
14//! - Clipboard events are optional and feature-gated in the future
15
16use bitflags::bitflags;
17use crossterm::event as cte;
18
19/// Canonical input event.
20///
21/// This enum represents all possible input events that ftui can receive
22/// from the terminal.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Event {
25    /// A keyboard event.
26    Key(KeyEvent),
27
28    /// A mouse event.
29    Mouse(MouseEvent),
30
31    /// Terminal was resized.
32    Resize {
33        /// New terminal width in columns.
34        width: u16,
35        /// New terminal height in rows.
36        height: u16,
37    },
38
39    /// Paste event (from bracketed paste mode).
40    Paste(PasteEvent),
41
42    /// Focus gained or lost.
43    ///
44    /// `true` = focus gained, `false` = focus lost.
45    Focus(bool),
46
47    /// Clipboard content received (optional, from OSC 52 response).
48    Clipboard(ClipboardEvent),
49
50    /// A tick event from the runtime.
51    ///
52    /// Fired when a scheduled tick interval elapses. Applications use this
53    /// for periodic updates (animations, polling, timers). The model's `update`
54    /// method receives the tick and can respond with state changes.
55    Tick,
56}
57
58impl Event {
59    /// Convert a Crossterm event into an ftui [`Event`].
60    #[must_use]
61    pub fn from_crossterm(event: cte::Event) -> Option<Self> {
62        map_crossterm_event_internal(event)
63    }
64}
65
66/// A keyboard event.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct KeyEvent {
69    /// The key code that was pressed.
70    pub code: KeyCode,
71
72    /// Modifier keys held during the event.
73    pub modifiers: Modifiers,
74
75    /// The type of key event (press, repeat, or release).
76    pub kind: KeyEventKind,
77}
78
79impl KeyEvent {
80    /// Create a new key event with default modifiers and Press kind.
81    #[must_use]
82    pub const fn new(code: KeyCode) -> Self {
83        Self {
84            code,
85            modifiers: Modifiers::NONE,
86            kind: KeyEventKind::Press,
87        }
88    }
89
90    /// Create a key event with modifiers.
91    #[must_use]
92    pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
93        self.modifiers = modifiers;
94        self
95    }
96
97    /// Create a key event with a specific kind.
98    #[must_use]
99    pub const fn with_kind(mut self, kind: KeyEventKind) -> Self {
100        self.kind = kind;
101        self
102    }
103
104    /// Check if this is a specific character key.
105    #[must_use]
106    pub fn is_char(&self, c: char) -> bool {
107        matches!(self.code, KeyCode::Char(ch) if ch == c)
108    }
109
110    /// Check if Ctrl modifier is held.
111    #[must_use]
112    pub const fn ctrl(&self) -> bool {
113        self.modifiers.contains(Modifiers::CTRL)
114    }
115
116    /// Check if Alt modifier is held.
117    #[must_use]
118    pub const fn alt(&self) -> bool {
119        self.modifiers.contains(Modifiers::ALT)
120    }
121
122    /// Check if Shift modifier is held.
123    #[must_use]
124    pub const fn shift(&self) -> bool {
125        self.modifiers.contains(Modifiers::SHIFT)
126    }
127
128    /// Check if Super/Meta/Cmd modifier is held.
129    #[must_use]
130    pub const fn super_key(&self) -> bool {
131        self.modifiers.contains(Modifiers::SUPER)
132    }
133}
134
135/// Key codes for keyboard events.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub enum KeyCode {
138    /// A regular character key.
139    Char(char),
140
141    /// Enter/Return key.
142    Enter,
143
144    /// Escape key.
145    Escape,
146
147    /// Backspace key.
148    Backspace,
149
150    /// Tab key.
151    Tab,
152
153    /// Shift+Tab (back-tab).
154    BackTab,
155
156    /// Delete key.
157    Delete,
158
159    /// Insert key.
160    Insert,
161
162    /// Home key.
163    Home,
164
165    /// End key.
166    End,
167
168    /// Page Up key.
169    PageUp,
170
171    /// Page Down key.
172    PageDown,
173
174    /// Up arrow key.
175    Up,
176
177    /// Down arrow key.
178    Down,
179
180    /// Left arrow key.
181    Left,
182
183    /// Right arrow key.
184    Right,
185
186    /// Function key (F1-F24).
187    F(u8),
188
189    /// Null character (Ctrl+Space or Ctrl+@).
190    Null,
191
192    /// Media key: Play/Pause.
193    MediaPlayPause,
194
195    /// Media key: Stop.
196    MediaStop,
197
198    /// Media key: Next track.
199    MediaNextTrack,
200
201    /// Media key: Previous track.
202    MediaPrevTrack,
203}
204
205/// The type of key event.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
207pub enum KeyEventKind {
208    /// Key was pressed (default when not distinguishable).
209    #[default]
210    Press,
211
212    /// Key is being held (repeat event).
213    Repeat,
214
215    /// Key was released.
216    Release,
217}
218
219bitflags! {
220    /// Modifier keys that can be held during a key event.
221    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222    pub struct Modifiers: u8 {
223        /// No modifiers.
224        const NONE  = 0b0000;
225        /// Shift key.
226        const SHIFT = 0b0001;
227        /// Alt/Option key.
228        const ALT   = 0b0010;
229        /// Control key.
230        const CTRL  = 0b0100;
231        /// Super/Meta/Command key.
232        const SUPER = 0b1000;
233    }
234}
235
236impl Default for Modifiers {
237    fn default() -> Self {
238        Self::NONE
239    }
240}
241
242/// A mouse event.
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub struct MouseEvent {
245    /// The type of mouse event.
246    pub kind: MouseEventKind,
247
248    /// X coordinate (0-indexed, leftmost column is 0).
249    pub x: u16,
250
251    /// Y coordinate (0-indexed, topmost row is 0).
252    pub y: u16,
253
254    /// Modifier keys held during the event.
255    pub modifiers: Modifiers,
256}
257
258impl MouseEvent {
259    /// Create a new mouse event.
260    #[must_use]
261    pub const fn new(kind: MouseEventKind, x: u16, y: u16) -> Self {
262        Self {
263            kind,
264            x,
265            y,
266            modifiers: Modifiers::NONE,
267        }
268    }
269
270    /// Create a mouse event with modifiers.
271    #[must_use]
272    pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
273        self.modifiers = modifiers;
274        self
275    }
276
277    /// Get the position as a tuple.
278    #[must_use]
279    pub const fn position(&self) -> (u16, u16) {
280        (self.x, self.y)
281    }
282}
283
284/// The type of mouse event.
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286pub enum MouseEventKind {
287    /// Mouse button pressed down.
288    Down(MouseButton),
289
290    /// Mouse button released.
291    Up(MouseButton),
292
293    /// Mouse dragged while button held.
294    Drag(MouseButton),
295
296    /// Mouse moved (no button pressed).
297    Moved,
298
299    /// Mouse wheel scrolled up.
300    ScrollUp,
301
302    /// Mouse wheel scrolled down.
303    ScrollDown,
304
305    /// Mouse wheel scrolled left (horizontal scroll).
306    ScrollLeft,
307
308    /// Mouse wheel scrolled right (horizontal scroll).
309    ScrollRight,
310}
311
312/// Mouse button identifiers.
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
314pub enum MouseButton {
315    /// Left mouse button.
316    Left,
317
318    /// Right mouse button.
319    Right,
320
321    /// Middle mouse button (scroll wheel click).
322    Middle,
323}
324
325/// A paste event from bracketed paste mode.
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub struct PasteEvent {
328    /// The pasted text content.
329    pub text: String,
330
331    /// True if this came from bracketed paste mode.
332    ///
333    /// When true, the text was received atomically and should be
334    /// treated as a single paste operation rather than individual
335    /// key presses.
336    pub bracketed: bool,
337}
338
339impl PasteEvent {
340    /// Create a new paste event.
341    #[must_use]
342    pub fn new(text: impl Into<String>, bracketed: bool) -> Self {
343        Self {
344            text: text.into(),
345            bracketed,
346        }
347    }
348
349    /// Create a bracketed paste event (the common case).
350    #[must_use]
351    pub fn bracketed(text: impl Into<String>) -> Self {
352        Self::new(text, true)
353    }
354}
355
356/// A clipboard event from OSC 52 response.
357///
358/// This is optional and may not be supported by all terminals.
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct ClipboardEvent {
361    /// The clipboard content (decoded from base64).
362    pub content: String,
363
364    /// The source of the clipboard content.
365    pub source: ClipboardSource,
366}
367
368impl ClipboardEvent {
369    /// Create a new clipboard event.
370    #[must_use]
371    pub fn new(content: impl Into<String>, source: ClipboardSource) -> Self {
372        Self {
373            content: content.into(),
374            source,
375        }
376    }
377}
378
379/// The source of clipboard content.
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
381pub enum ClipboardSource {
382    /// Clipboard content from OSC 52 protocol.
383    Osc52,
384
385    /// Unknown or unspecified source.
386    #[default]
387    Unknown,
388}
389
390fn map_crossterm_event_internal(event: cte::Event) -> Option<Event> {
391    match event {
392        cte::Event::Key(key) => map_key_event(key).map(Event::Key),
393        cte::Event::Mouse(mouse) => Some(Event::Mouse(map_mouse_event(mouse))),
394        cte::Event::Resize(width, height) => Some(Event::Resize { width, height }),
395        cte::Event::Paste(text) => Some(Event::Paste(PasteEvent::bracketed(text))),
396        cte::Event::FocusGained => Some(Event::Focus(true)),
397        cte::Event::FocusLost => Some(Event::Focus(false)),
398    }
399}
400
401fn map_key_event(event: cte::KeyEvent) -> Option<KeyEvent> {
402    let code = map_key_code(event.code)?;
403    let modifiers = map_modifiers(event.modifiers);
404    let kind = map_key_kind(event.kind);
405    Some(KeyEvent {
406        code,
407        modifiers,
408        kind,
409    })
410}
411
412fn map_key_kind(kind: cte::KeyEventKind) -> KeyEventKind {
413    match kind {
414        cte::KeyEventKind::Press => KeyEventKind::Press,
415        cte::KeyEventKind::Repeat => KeyEventKind::Repeat,
416        cte::KeyEventKind::Release => KeyEventKind::Release,
417    }
418}
419
420pub(crate) fn map_key_code(code: cte::KeyCode) -> Option<KeyCode> {
421    match code {
422        cte::KeyCode::Backspace => Some(KeyCode::Backspace),
423        cte::KeyCode::Enter => Some(KeyCode::Enter),
424        cte::KeyCode::Left => Some(KeyCode::Left),
425        cte::KeyCode::Right => Some(KeyCode::Right),
426        cte::KeyCode::Up => Some(KeyCode::Up),
427        cte::KeyCode::Down => Some(KeyCode::Down),
428        cte::KeyCode::Home => Some(KeyCode::Home),
429        cte::KeyCode::End => Some(KeyCode::End),
430        cte::KeyCode::PageUp => Some(KeyCode::PageUp),
431        cte::KeyCode::PageDown => Some(KeyCode::PageDown),
432        cte::KeyCode::Tab => Some(KeyCode::Tab),
433        cte::KeyCode::BackTab => Some(KeyCode::BackTab),
434        cte::KeyCode::Delete => Some(KeyCode::Delete),
435        cte::KeyCode::Insert => Some(KeyCode::Insert),
436        cte::KeyCode::F(n) => Some(KeyCode::F(n)),
437        cte::KeyCode::Char(c) => Some(KeyCode::Char(c)),
438        cte::KeyCode::Null => Some(KeyCode::Null),
439        cte::KeyCode::Esc => Some(KeyCode::Escape),
440        cte::KeyCode::Media(media) => map_media_key(media),
441        _ => None,
442    }
443}
444
445fn map_media_key(code: cte::MediaKeyCode) -> Option<KeyCode> {
446    match code {
447        cte::MediaKeyCode::Play | cte::MediaKeyCode::Pause | cte::MediaKeyCode::PlayPause => {
448            Some(KeyCode::MediaPlayPause)
449        }
450        cte::MediaKeyCode::Stop => Some(KeyCode::MediaStop),
451        cte::MediaKeyCode::TrackNext => Some(KeyCode::MediaNextTrack),
452        cte::MediaKeyCode::TrackPrevious => Some(KeyCode::MediaPrevTrack),
453        _ => None,
454    }
455}
456
457fn map_modifiers(modifiers: cte::KeyModifiers) -> Modifiers {
458    let mut mapped = Modifiers::NONE;
459    if modifiers.contains(cte::KeyModifiers::SHIFT) {
460        mapped |= Modifiers::SHIFT;
461    }
462    if modifiers.contains(cte::KeyModifiers::ALT) {
463        mapped |= Modifiers::ALT;
464    }
465    if modifiers.contains(cte::KeyModifiers::CONTROL) {
466        mapped |= Modifiers::CTRL;
467    }
468    if modifiers.contains(cte::KeyModifiers::SUPER)
469        || modifiers.contains(cte::KeyModifiers::HYPER)
470        || modifiers.contains(cte::KeyModifiers::META)
471    {
472        mapped |= Modifiers::SUPER;
473    }
474    mapped
475}
476
477fn map_mouse_event(event: cte::MouseEvent) -> MouseEvent {
478    let kind = match event.kind {
479        cte::MouseEventKind::Down(button) => MouseEventKind::Down(map_mouse_button(button)),
480        cte::MouseEventKind::Up(button) => MouseEventKind::Up(map_mouse_button(button)),
481        cte::MouseEventKind::Drag(button) => MouseEventKind::Drag(map_mouse_button(button)),
482        cte::MouseEventKind::Moved => MouseEventKind::Moved,
483        cte::MouseEventKind::ScrollUp => MouseEventKind::ScrollUp,
484        cte::MouseEventKind::ScrollDown => MouseEventKind::ScrollDown,
485        cte::MouseEventKind::ScrollLeft => MouseEventKind::ScrollLeft,
486        cte::MouseEventKind::ScrollRight => MouseEventKind::ScrollRight,
487    };
488
489    MouseEvent::new(kind, event.column, event.row).with_modifiers(map_modifiers(event.modifiers))
490}
491
492fn map_mouse_button(button: cte::MouseButton) -> MouseButton {
493    match button {
494        cte::MouseButton::Left => MouseButton::Left,
495        cte::MouseButton::Right => MouseButton::Right,
496        cte::MouseButton::Middle => MouseButton::Middle,
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use crossterm::event as ct_event;
504
505    #[test]
506    fn key_event_is_char() {
507        let event = KeyEvent::new(KeyCode::Char('q'));
508        assert!(event.is_char('q'));
509        assert!(!event.is_char('x'));
510    }
511
512    #[test]
513    fn key_event_modifiers() {
514        let event = KeyEvent::new(KeyCode::Char('c')).with_modifiers(Modifiers::CTRL);
515        assert!(event.ctrl());
516        assert!(!event.alt());
517        assert!(!event.shift());
518        assert!(!event.super_key());
519    }
520
521    #[test]
522    fn key_event_combined_modifiers() {
523        let event =
524            KeyEvent::new(KeyCode::Char('s')).with_modifiers(Modifiers::CTRL | Modifiers::SHIFT);
525        assert!(event.ctrl());
526        assert!(event.shift());
527        assert!(!event.alt());
528    }
529
530    #[test]
531    fn key_event_kind() {
532        let press = KeyEvent::new(KeyCode::Enter);
533        assert_eq!(press.kind, KeyEventKind::Press);
534
535        let release = press.with_kind(KeyEventKind::Release);
536        assert_eq!(release.kind, KeyEventKind::Release);
537    }
538
539    #[test]
540    fn mouse_event_position() {
541        let event = MouseEvent::new(MouseEventKind::Down(MouseButton::Left), 10, 20);
542        assert_eq!(event.position(), (10, 20));
543        assert_eq!(event.x, 10);
544        assert_eq!(event.y, 20);
545    }
546
547    #[test]
548    fn mouse_event_with_modifiers() {
549        let event = MouseEvent::new(MouseEventKind::Moved, 0, 0).with_modifiers(Modifiers::ALT);
550        assert_eq!(event.modifiers, Modifiers::ALT);
551    }
552
553    #[test]
554    fn paste_event_creation() {
555        let paste = PasteEvent::bracketed("hello world");
556        assert_eq!(paste.text, "hello world");
557        assert!(paste.bracketed);
558    }
559
560    #[test]
561    fn clipboard_event_creation() {
562        let clip = ClipboardEvent::new("copied text", ClipboardSource::Osc52);
563        assert_eq!(clip.content, "copied text");
564        assert_eq!(clip.source, ClipboardSource::Osc52);
565    }
566
567    #[test]
568    fn event_variants() {
569        // Test that all event variants can be created
570        let _key = Event::Key(KeyEvent::new(KeyCode::Char('a')));
571        let _mouse = Event::Mouse(MouseEvent::new(
572            MouseEventKind::Down(MouseButton::Left),
573            0,
574            0,
575        ));
576        let _resize = Event::Resize {
577            width: 80,
578            height: 24,
579        };
580        let _paste = Event::Paste(PasteEvent::bracketed("test"));
581        let _focus = Event::Focus(true);
582        let _clipboard = Event::Clipboard(ClipboardEvent::new("test", ClipboardSource::Unknown));
583        let _tick = Event::Tick;
584    }
585
586    #[test]
587    fn modifiers_default() {
588        assert_eq!(Modifiers::default(), Modifiers::NONE);
589    }
590
591    #[test]
592    fn key_event_kind_default() {
593        assert_eq!(KeyEventKind::default(), KeyEventKind::Press);
594    }
595
596    #[test]
597    fn clipboard_source_default() {
598        assert_eq!(ClipboardSource::default(), ClipboardSource::Unknown);
599    }
600
601    #[test]
602    fn function_keys() {
603        let f1 = KeyEvent::new(KeyCode::F(1));
604        let f12 = KeyEvent::new(KeyCode::F(12));
605        assert_eq!(f1.code, KeyCode::F(1));
606        assert_eq!(f12.code, KeyCode::F(12));
607    }
608
609    #[test]
610    fn event_is_clone_and_eq() {
611        let event = Event::Key(KeyEvent::new(KeyCode::Char('x')));
612        let cloned = event.clone();
613        assert_eq!(event, cloned);
614    }
615
616    // -- Crossterm mapping tests --
617
618    #[test]
619    fn map_modifiers_ctrl() {
620        let mapped = map_modifiers(ct_event::KeyModifiers::CONTROL);
621        assert!(mapped.contains(Modifiers::CTRL));
622        assert!(!mapped.contains(Modifiers::SHIFT));
623    }
624
625    #[test]
626    fn map_modifiers_alt() {
627        let mapped = map_modifiers(ct_event::KeyModifiers::ALT);
628        assert!(mapped.contains(Modifiers::ALT));
629    }
630
631    #[test]
632    fn map_modifiers_super_variants() {
633        let super_mapped = map_modifiers(ct_event::KeyModifiers::SUPER);
634        assert!(super_mapped.contains(Modifiers::SUPER));
635
636        let hyper_mapped = map_modifiers(ct_event::KeyModifiers::HYPER);
637        assert!(hyper_mapped.contains(Modifiers::SUPER));
638
639        let meta_mapped = map_modifiers(ct_event::KeyModifiers::META);
640        assert!(meta_mapped.contains(Modifiers::SUPER));
641    }
642
643    #[test]
644    fn map_modifiers_combined() {
645        let combined = ct_event::KeyModifiers::SHIFT | ct_event::KeyModifiers::CONTROL;
646        let mapped = map_modifiers(combined);
647        assert!(mapped.contains(Modifiers::SHIFT));
648        assert!(mapped.contains(Modifiers::CTRL));
649        assert!(!mapped.contains(Modifiers::ALT));
650    }
651
652    #[test]
653    fn map_mouse_button_all() {
654        assert_eq!(
655            map_mouse_button(ct_event::MouseButton::Left),
656            MouseButton::Left
657        );
658        assert_eq!(
659            map_mouse_button(ct_event::MouseButton::Right),
660            MouseButton::Right
661        );
662        assert_eq!(
663            map_mouse_button(ct_event::MouseButton::Middle),
664            MouseButton::Middle
665        );
666    }
667
668    #[test]
669    fn map_mouse_event_down() {
670        let ct_event = ct_event::MouseEvent {
671            kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
672            column: 10,
673            row: 5,
674            modifiers: ct_event::KeyModifiers::NONE,
675        };
676        let mapped = map_mouse_event(ct_event);
677        assert!(matches!(
678            mapped.kind,
679            MouseEventKind::Down(MouseButton::Left)
680        ));
681        assert_eq!(mapped.x, 10);
682        assert_eq!(mapped.y, 5);
683    }
684
685    #[test]
686    fn map_mouse_event_up() {
687        let ct_event = ct_event::MouseEvent {
688            kind: ct_event::MouseEventKind::Up(ct_event::MouseButton::Right),
689            column: 20,
690            row: 15,
691            modifiers: ct_event::KeyModifiers::NONE,
692        };
693        let mapped = map_mouse_event(ct_event);
694        assert!(matches!(
695            mapped.kind,
696            MouseEventKind::Up(MouseButton::Right)
697        ));
698        assert_eq!(mapped.x, 20);
699        assert_eq!(mapped.y, 15);
700    }
701
702    #[test]
703    fn map_mouse_event_drag() {
704        let ct_event = ct_event::MouseEvent {
705            kind: ct_event::MouseEventKind::Drag(ct_event::MouseButton::Middle),
706            column: 5,
707            row: 10,
708            modifiers: ct_event::KeyModifiers::NONE,
709        };
710        let mapped = map_mouse_event(ct_event);
711        assert!(matches!(
712            mapped.kind,
713            MouseEventKind::Drag(MouseButton::Middle)
714        ));
715    }
716
717    #[test]
718    fn map_mouse_event_moved() {
719        let ct_event = ct_event::MouseEvent {
720            kind: ct_event::MouseEventKind::Moved,
721            column: 0,
722            row: 0,
723            modifiers: ct_event::KeyModifiers::NONE,
724        };
725        let mapped = map_mouse_event(ct_event);
726        assert!(matches!(mapped.kind, MouseEventKind::Moved));
727    }
728
729    #[test]
730    fn map_mouse_event_scroll() {
731        let scroll_up = ct_event::MouseEvent {
732            kind: ct_event::MouseEventKind::ScrollUp,
733            column: 0,
734            row: 0,
735            modifiers: ct_event::KeyModifiers::NONE,
736        };
737        let scroll_down = ct_event::MouseEvent {
738            kind: ct_event::MouseEventKind::ScrollDown,
739            column: 0,
740            row: 0,
741            modifiers: ct_event::KeyModifiers::NONE,
742        };
743        let scroll_left = ct_event::MouseEvent {
744            kind: ct_event::MouseEventKind::ScrollLeft,
745            column: 0,
746            row: 0,
747            modifiers: ct_event::KeyModifiers::NONE,
748        };
749        let scroll_right = ct_event::MouseEvent {
750            kind: ct_event::MouseEventKind::ScrollRight,
751            column: 0,
752            row: 0,
753            modifiers: ct_event::KeyModifiers::NONE,
754        };
755
756        assert!(matches!(
757            map_mouse_event(scroll_up).kind,
758            MouseEventKind::ScrollUp
759        ));
760        assert!(matches!(
761            map_mouse_event(scroll_down).kind,
762            MouseEventKind::ScrollDown
763        ));
764        assert!(matches!(
765            map_mouse_event(scroll_left).kind,
766            MouseEventKind::ScrollLeft
767        ));
768        assert!(matches!(
769            map_mouse_event(scroll_right).kind,
770            MouseEventKind::ScrollRight
771        ));
772    }
773
774    #[test]
775    fn map_mouse_event_modifiers() {
776        let ct_event = ct_event::MouseEvent {
777            kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
778            column: 0,
779            row: 0,
780            modifiers: ct_event::KeyModifiers::SHIFT | ct_event::KeyModifiers::ALT,
781        };
782        let mapped = map_mouse_event(ct_event);
783        assert!(mapped.modifiers.contains(Modifiers::SHIFT));
784        assert!(mapped.modifiers.contains(Modifiers::ALT));
785    }
786
787    #[test]
788    fn map_key_event_char() {
789        let ct_event = ct_event::KeyEvent {
790            code: ct_event::KeyCode::Char('x'),
791            modifiers: ct_event::KeyModifiers::CONTROL,
792            kind: ct_event::KeyEventKind::Press,
793            state: ct_event::KeyEventState::NONE,
794        };
795        let mapped = map_key_event(ct_event).expect("should map");
796        assert_eq!(mapped.code, KeyCode::Char('x'));
797        assert!(mapped.modifiers.contains(Modifiers::CTRL));
798        assert_eq!(mapped.kind, KeyEventKind::Press);
799    }
800
801    #[test]
802    fn map_key_event_function_key() {
803        let ct_event = ct_event::KeyEvent {
804            code: ct_event::KeyCode::F(5),
805            modifiers: ct_event::KeyModifiers::NONE,
806            kind: ct_event::KeyEventKind::Press,
807            state: ct_event::KeyEventState::NONE,
808        };
809        let mapped = map_key_event(ct_event).expect("should map");
810        assert_eq!(mapped.code, KeyCode::F(5));
811    }
812
813    #[test]
814    fn map_crossterm_event_key() {
815        let ct_event = ct_event::Event::Key(ct_event::KeyEvent {
816            code: ct_event::KeyCode::Enter,
817            modifiers: ct_event::KeyModifiers::NONE,
818            kind: ct_event::KeyEventKind::Press,
819            state: ct_event::KeyEventState::NONE,
820        });
821        let mapped = map_crossterm_event_internal(ct_event).expect("should map");
822        assert!(matches!(mapped, Event::Key(_)));
823    }
824
825    #[test]
826    fn map_crossterm_event_mouse() {
827        let ct_event = ct_event::Event::Mouse(ct_event::MouseEvent {
828            kind: ct_event::MouseEventKind::Down(ct_event::MouseButton::Left),
829            column: 10,
830            row: 5,
831            modifiers: ct_event::KeyModifiers::NONE,
832        });
833        let mapped = map_crossterm_event_internal(ct_event).expect("should map");
834        assert!(matches!(mapped, Event::Mouse(_)));
835    }
836
837    #[test]
838    fn map_crossterm_event_resize() {
839        let ct_event = ct_event::Event::Resize(80, 24);
840        let mapped = map_crossterm_event_internal(ct_event).expect("should map");
841        assert!(matches!(
842            mapped,
843            Event::Resize {
844                width: 80,
845                height: 24
846            }
847        ));
848    }
849
850    #[test]
851    fn map_crossterm_event_paste() {
852        let ct_event = ct_event::Event::Paste("hello world".to_string());
853        let mapped = map_crossterm_event_internal(ct_event).expect("should map");
854        match mapped {
855            Event::Paste(paste) => assert_eq!(paste.text, "hello world"),
856            _ => panic!("expected Paste event"),
857        }
858    }
859
860    #[test]
861    fn map_crossterm_event_focus() {
862        let gained = ct_event::Event::FocusGained;
863        let lost = ct_event::Event::FocusLost;
864
865        assert!(matches!(
866            map_crossterm_event_internal(gained),
867            Some(Event::Focus(true))
868        ));
869        assert!(matches!(
870            map_crossterm_event_internal(lost),
871            Some(Event::Focus(false))
872        ));
873    }
874}