Skip to content

Commit

Permalink
Add several more methods relating event ergonomics
Browse files Browse the repository at this point in the history
- add is_key_release() and is_key_repeat() checks
- add as_key_event()
- rename as_key_press() to as_key_press_event()
- add as_key_repeat_event()
- add as_key_release_event()
- add as_mouse_event()
- add as_paste_event()
- more tests
- update event-match and key-display examples
  • Loading branch information
joshka committed Dec 26, 2024
1 parent 5dea800 commit 5ee4931
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 70 deletions.
4 changes: 2 additions & 2 deletions examples/event-match-modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};

fn match_event(event: Event) {
if let Some(key) = event.as_key_press() {
if let Some(key) = event.as_key_press_event() {
match key {
KeyEvent {
modifiers: KeyModifiers::CONTROL,
Expand Down Expand Up @@ -33,7 +33,7 @@ fn match_event(event: Event) {
KeyEvent {
code, modifiers, ..
} => {
if *modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) {
if modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) {
println!("Alt + Shift {:?}", code);
} else {
println!("({:?}) with key: {:?}", modifiers, code)
Expand Down
29 changes: 13 additions & 16 deletions examples/key-display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
use std::io;

use crossterm::event::{KeyEventKind, KeyModifiers};
use crossterm::event::KeyModifiers;
use crossterm::{
event::{read, Event, KeyCode},
event::{read, KeyCode},
terminal::{disable_raw_mode, enable_raw_mode},
};

Expand All @@ -29,20 +29,17 @@ fn main() -> io::Result<()> {
}

fn print_events() -> io::Result<()> {
loop {
let event = read()?;
match event {
Event::Key(event) if event.kind == KeyEventKind::Press => {
print!("Key pressed: ");
if event.modifiers != KeyModifiers::NONE {
print!("{}+", event.modifiers);
}
println!("{}\r", event.code);
if event.code == KeyCode::Esc {
break;
}
}
_ => {}
while let Ok(event) = read() {
let Some(event) = event.as_key_press_event() else {
continue;
};
let modifier = match event.modifiers {
KeyModifiers::NONE => "".to_string(),
_ => format!("{:}+", event.modifiers),
};
println!("Key pressed: {modifier}{code}\r", code = event.code);
if event.code == KeyCode::Esc {
break;
}
}
Ok(())
Expand Down
259 changes: 207 additions & 52 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,54 @@ impl Event {
)
}

/// Returns `true` if the event is a key release event.
#[inline]
pub fn is_key_release(&self) -> bool {
matches!(
self,
Event::Key(KeyEvent {
kind: KeyEventKind::Release,
..
})
)
}

/// Returns `true` if the event is a key repeat event.
#[inline]
pub fn is_key_repeat(&self) -> bool {
matches!(
self,
Event::Key(KeyEvent {
kind: KeyEventKind::Repeat,
..
})
)
}

/// Returns the key event if the event is a key event, otherwise `None`.
///
/// This is a convenience method that makes apps that only care about key events easier to write.
///
/// # Examples
///
/// The following code runs a loop that only processes key events:
///
/// ```no_run
/// use crossterm::event;
///
/// while let Some(key_event) = event::read()?.as_key_event() {
/// // ...
/// }
/// # std::io::Result::Ok(())
/// ```
#[inline]
pub fn as_key_event(&self) -> Option<KeyEvent> {
match self {
Event::Key(event) => Some(*event),
_ => None,
}
}

/// Returns an Option containing the KeyEvent if the event is a key press event.
///
/// This is a convenience method that makes apps that only care about key press events, and not
Expand All @@ -608,14 +656,100 @@ impl Event {
/// use crossterm::event;
///
/// while let Ok(event) = event::read() {
/// if let Some(key) = event.as_key_press() {
/// if let Some(key) = event.as_key_press_event() {
/// // ...
/// }
/// }
#[inline]
pub fn as_key_press(&self) -> Option<&KeyEvent> {
pub fn as_key_press_event(&self) -> Option<KeyEvent> {
match self {
Event::Key(event) if self.is_key_press() => Some(*event),
_ => None,
}
}

/// Returns an Option containing the KeyEvent if the event is a key release event.
#[inline]
pub fn as_key_release_event(&self) -> Option<KeyEvent> {
match self {
Event::Key(event) if event.kind == KeyEventKind::Release => Some(*event),
_ => None,
}
}

/// Returns an Option containing the KeyEvent if the event is a key repeat event.
#[inline]
pub fn as_key_repeat_event(&self) -> Option<KeyEvent> {
match self {
Event::Key(event) if event.kind == KeyEventKind::Repeat => Some(*event),
_ => None,
}
}

/// Returns the mouse event if the event is a mouse event, otherwise `None`.
///
/// This is a convenience method that makes code which only cares about mouse events easier to
/// write.
///
/// # Examples
///
/// ```no_run
/// use crossterm::event;
///
/// while let Some(mouse_event) = event::read()?.as_mouse_event() {
/// // ...
/// }
/// # std::io::Result::Ok(())
/// ```
#[inline]
pub fn as_mouse_event(&self) -> Option<MouseEvent> {
match self {
Event::Mouse(event) => Some(*event),
_ => None,
}
}

/// Returns the pasted string if the event is a paste event, otherwise `None`.
///
/// This is a convenience method that makes code which only cares about paste events easier to write.
///
/// # Examples
///
/// ```no_run
/// use crossterm::event;
///
/// while let Some(paste) = event::read()?.as_paste_event() {
/// // ...
/// }
/// # std::io::Result::Ok(())
/// ```
#[cfg(feature = "bracketed-paste")]
#[inline]
pub fn as_paste_event(&self) -> Option<&str> {
match self {
Event::Paste(paste) => Some(paste),
_ => None,
}
}

/// Returns the size as a tuple if the event is a resize event, otherwise `None`.
///
/// This is a convenience method that makes code which only cares about resize events easier to write.
///
/// # Examples
///
/// ```no_run
/// use crossterm::event;
///
/// while let Some((columns, rows)) = event::read()?.as_resize_event() {
/// // ...
/// }
/// # std::io::Result::Ok(())
/// ```
#[inline]
pub fn as_resize_event(&self) -> Option<(u16, u16)> {
match self {
Event::Key(event) if self.is_key_press() => Some(event),
Event::Resize(columns, rows) => Some((*columns, *rows)),
_ => None,
}
}
Expand Down Expand Up @@ -1485,86 +1619,107 @@ mod tests {
assert_eq!(format!("{}", Modifier(RightSuper)), "Right Super");
}

const ESC_PRESSED: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Press);
const ESC_RELEASED: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Release);
const ESC_REPEAT: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Repeat);
const MOUSE_CLICK: MouseEvent = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 1,
row: 1,
modifiers: KeyModifiers::empty(),
};

#[test]
fn test_event_is() {
fn event_is() {
let event = Event::FocusGained;
assert!(event.is_focus_gained());
assert!(event.is_focus_gained());
assert!(!event.is_key());

let event = Event::FocusLost;
assert!(event.is_focus_lost());
assert!(!event.is_focus_gained());
assert!(!event.is_key());

let event = Event::Resize(1, 1);
assert!(event.is_resize());
assert!(!event.is_key());

let event = Event::Key(KeyCode::Esc.into());
assert!(event.is_key());
assert!(!event.is_focus_gained());

let event = Event::Mouse(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 1,
row: 1,
modifiers: KeyModifiers::empty(),
});
assert!(event.is_mouse());
assert!(!event.is_key());
}

const ESC_PRESSED: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Press);
const ESC_RELEASED: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Release);
const ESC_REPEAT: KeyEvent =
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Repeat);

#[test]
fn test_event_is_key_press() {
let event = Event::Key(ESC_PRESSED);
assert!(event.is_key());
assert!(event.is_key_press());
assert!(!event.is_key_release());
assert!(!event.is_key_repeat());
assert!(!event.is_focus_gained());

let event = Event::Key(ESC_RELEASED);
assert!(event.is_key());
assert!(!event.is_key_press());
assert!(event.is_key_release());
assert!(!event.is_key_repeat());
assert!(!event.is_focus_gained());

let event = Event::Key(ESC_REPEAT);
assert!(event.is_key());
assert!(!event.is_key_press());
assert!(!event.is_key_release());
assert!(event.is_key_repeat());
assert!(!event.is_focus_gained());

let event = Event::FocusGained;
assert!(!event.is_key_press());
let event = Event::Mouse(MOUSE_CLICK);
assert!(event.is_mouse());
assert!(!event.is_key());

#[cfg(feature = "bracketed-paste")]
{
let event = Event::Paste("".to_string());
assert!(event.is_paste());
assert!(!event.is_key());
}
}

#[test]
fn test_event_as_key_press() {
fn event_as() {
let event = Event::FocusGained;
assert_eq!(event.as_key_event(), None);

let event = Event::Key(ESC_PRESSED);
assert_eq!(event.as_key_press(), Some(&ESC_PRESSED));
assert_eq!(event.as_key_event(), Some(ESC_PRESSED));
assert_eq!(event.as_key_press_event(), Some(ESC_PRESSED));
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_resize_event(), None);

let event = Event::Key(ESC_RELEASED);
assert_eq!(event.as_key_press(), None);
assert_eq!(event.as_key_event(), Some(ESC_RELEASED));
assert_eq!(event.as_key_release_event(), Some(ESC_RELEASED));
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_resize_event(), None);

let event = Event::Key(ESC_REPEAT);
assert_eq!(event.as_key_press(), None);
assert_eq!(event.as_key_event(), Some(ESC_REPEAT));
assert_eq!(event.as_key_repeat_event(), Some(ESC_REPEAT));
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_resize_event(), None);

let event = Event::FocusGained;
assert_eq!(event.as_key_press(), None);
}
let event = Event::Resize(1, 1);
assert_eq!(event.as_resize_event(), Some((1, 1)));
assert_eq!(event.as_key_event(), None);

#[test]
fn test_key_event_is() {
let event = ESC_PRESSED;
assert!(event.is_press());
assert!(!event.is_release());
assert!(!event.is_repeat());

let event = ESC_RELEASED;
assert!(!event.is_press());
assert!(event.is_release());
assert!(!event.is_repeat());

let event = ESC_REPEAT;
assert!(!event.is_press());
assert!(!event.is_release());
assert!(event.is_repeat());
let event = Event::Mouse(MOUSE_CLICK);
assert_eq!(event.as_mouse_event(), Some(MOUSE_CLICK));
assert_eq!(event.as_key_event(), None);

#[cfg(feature = "bracketed-paste")]
{
let event = Event::Paste("".to_string());
assert_eq!(event.as_paste_event(), Some(""));
assert_eq!(event.as_key_event(), None);
}
}
}

0 comments on commit 5ee4931

Please sign in to comment.