#[cfg(feature = "crossterm")]
use crossterm::event as crossterm_event;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u32, u32),
Paste(String),
FocusGained,
FocusLost,
}
impl Event {
pub fn key_char(c: char) -> Self {
Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
})
}
pub fn key(code: KeyCode) -> Self {
Event::Key(KeyEvent {
code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
})
}
pub fn key_ctrl(c: char) -> Self {
Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::CONTROL,
kind: KeyEventKind::Press,
})
}
pub fn key_mod(code: KeyCode, modifiers: KeyModifiers) -> Self {
Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
})
}
pub fn resize(width: u32, height: u32) -> Self {
Event::Resize(width, height)
}
pub fn mouse_click(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::Down(MouseButton::Left),
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn mouse_move(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::Moved,
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn mouse_drag(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::Drag(MouseButton::Left),
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn mouse_up(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::Up(MouseButton::Left),
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn scroll_up(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::ScrollUp,
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn scroll_down(x: u32, y: u32) -> Self {
Event::Mouse(MouseEvent {
kind: MouseKind::ScrollDown,
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
})
}
pub fn key_release(c: char) -> Self {
Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Release,
})
}
pub fn paste(text: impl Into<String>) -> Self {
Event::Paste(text.into())
}
pub fn as_key(&self) -> Option<&KeyEvent> {
match self {
Event::Key(k) => Some(k),
_ => None,
}
}
pub fn as_mouse(&self) -> Option<&MouseEvent> {
match self {
Event::Mouse(m) => Some(m),
_ => None,
}
}
pub fn as_resize(&self) -> Option<(u32, u32)> {
match self {
Event::Resize(w, h) => Some((*w, *h)),
_ => None,
}
}
pub fn as_paste(&self) -> Option<&str> {
match self {
Event::Paste(s) => Some(s),
_ => None,
}
}
pub fn is_key(&self) -> bool {
matches!(self, Event::Key(_))
}
pub fn is_mouse(&self) -> bool {
matches!(self, Event::Mouse(_))
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
pub kind: KeyEventKind,
}
impl KeyEvent {
pub fn is_char(&self, c: char) -> bool {
self.code == KeyCode::Char(c)
&& self.modifiers == KeyModifiers::NONE
&& self.kind == KeyEventKind::Press
}
pub fn is_ctrl_char(&self, c: char) -> bool {
self.code == KeyCode::Char(c)
&& self.modifiers == KeyModifiers::CONTROL
&& self.kind == KeyEventKind::Press
}
pub fn is_code(&self, code: KeyCode) -> bool {
self.code == code
&& self.modifiers == KeyModifiers::NONE
&& self.kind == KeyEventKind::Press
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyEventKind {
Press,
Release,
Repeat,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyCode {
Char(char),
Enter,
Backspace,
Tab,
BackTab,
Esc,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
Delete,
Insert,
Null,
CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
Menu,
KeypadBegin,
F(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct KeyModifiers(pub u8);
impl KeyModifiers {
pub const NONE: Self = Self(0);
pub const SHIFT: Self = Self(1 << 0);
pub const CONTROL: Self = Self(1 << 1);
pub const ALT: Self = Self(1 << 2);
pub const SUPER: Self = Self(1 << 3);
pub const HYPER: Self = Self(1 << 4);
pub const META: Self = Self(1 << 5);
#[inline]
pub fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MouseEvent {
pub kind: MouseKind,
pub x: u32,
pub y: u32,
pub modifiers: KeyModifiers,
pub pixel_x: Option<u16>,
pub pixel_y: Option<u16>,
}
impl MouseEvent {
pub fn new(
kind: MouseKind,
x: u32,
y: u32,
modifiers: KeyModifiers,
pixel_x: Option<u16>,
pixel_y: Option<u16>,
) -> Self {
Self {
kind,
x,
y,
modifiers,
pixel_x,
pixel_y,
}
}
pub fn is_scroll(&self) -> bool {
matches!(
self.kind,
MouseKind::ScrollUp
| MouseKind::ScrollDown
| MouseKind::ScrollLeft
| MouseKind::ScrollRight
)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MouseKind {
Down(MouseButton),
Up(MouseButton),
Drag(MouseButton),
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
Moved,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[cfg(feature = "crossterm")]
fn convert_modifiers(modifiers: crossterm_event::KeyModifiers) -> KeyModifiers {
let mut out = KeyModifiers::NONE;
if modifiers.contains(crossterm_event::KeyModifiers::SHIFT) {
out.0 |= KeyModifiers::SHIFT.0;
}
if modifiers.contains(crossterm_event::KeyModifiers::CONTROL) {
out.0 |= KeyModifiers::CONTROL.0;
}
if modifiers.contains(crossterm_event::KeyModifiers::ALT) {
out.0 |= KeyModifiers::ALT.0;
}
if modifiers.contains(crossterm_event::KeyModifiers::SUPER) {
out.0 |= KeyModifiers::SUPER.0;
}
if modifiers.contains(crossterm_event::KeyModifiers::HYPER) {
out.0 |= KeyModifiers::HYPER.0;
}
if modifiers.contains(crossterm_event::KeyModifiers::META) {
out.0 |= KeyModifiers::META.0;
}
out
}
#[cfg(feature = "crossterm")]
fn convert_button(button: crossterm_event::MouseButton) -> MouseButton {
match button {
crossterm_event::MouseButton::Left => MouseButton::Left,
crossterm_event::MouseButton::Right => MouseButton::Right,
crossterm_event::MouseButton::Middle => MouseButton::Middle,
}
}
#[cfg(feature = "crossterm")]
pub(crate) fn from_crossterm(raw: crossterm_event::Event) -> Option<Event> {
match raw {
crossterm_event::Event::Key(k) => {
let code = match k.code {
crossterm_event::KeyCode::Char(c) => KeyCode::Char(c),
crossterm_event::KeyCode::Enter => KeyCode::Enter,
crossterm_event::KeyCode::Backspace => KeyCode::Backspace,
crossterm_event::KeyCode::Tab => KeyCode::Tab,
crossterm_event::KeyCode::BackTab => KeyCode::BackTab,
crossterm_event::KeyCode::Esc => KeyCode::Esc,
crossterm_event::KeyCode::Up => KeyCode::Up,
crossterm_event::KeyCode::Down => KeyCode::Down,
crossterm_event::KeyCode::Left => KeyCode::Left,
crossterm_event::KeyCode::Right => KeyCode::Right,
crossterm_event::KeyCode::Home => KeyCode::Home,
crossterm_event::KeyCode::End => KeyCode::End,
crossterm_event::KeyCode::PageUp => KeyCode::PageUp,
crossterm_event::KeyCode::PageDown => KeyCode::PageDown,
crossterm_event::KeyCode::Delete => KeyCode::Delete,
crossterm_event::KeyCode::Insert => KeyCode::Insert,
crossterm_event::KeyCode::Null => KeyCode::Null,
crossterm_event::KeyCode::CapsLock => KeyCode::CapsLock,
crossterm_event::KeyCode::ScrollLock => KeyCode::ScrollLock,
crossterm_event::KeyCode::NumLock => KeyCode::NumLock,
crossterm_event::KeyCode::PrintScreen => KeyCode::PrintScreen,
crossterm_event::KeyCode::Pause => KeyCode::Pause,
crossterm_event::KeyCode::Menu => KeyCode::Menu,
crossterm_event::KeyCode::KeypadBegin => KeyCode::KeypadBegin,
crossterm_event::KeyCode::F(n) => KeyCode::F(n),
_ => return None,
};
let modifiers = convert_modifiers(k.modifiers);
let kind = match k.kind {
crossterm_event::KeyEventKind::Press => KeyEventKind::Press,
crossterm_event::KeyEventKind::Repeat => KeyEventKind::Repeat,
crossterm_event::KeyEventKind::Release => KeyEventKind::Release,
};
Some(Event::Key(KeyEvent {
code,
modifiers,
kind,
}))
}
crossterm_event::Event::Mouse(m) => {
let kind = match m.kind {
crossterm_event::MouseEventKind::Down(btn) => MouseKind::Down(convert_button(btn)),
crossterm_event::MouseEventKind::Up(btn) => MouseKind::Up(convert_button(btn)),
crossterm_event::MouseEventKind::Drag(btn) => MouseKind::Drag(convert_button(btn)),
crossterm_event::MouseEventKind::Moved => MouseKind::Moved,
crossterm_event::MouseEventKind::ScrollUp => MouseKind::ScrollUp,
crossterm_event::MouseEventKind::ScrollDown => MouseKind::ScrollDown,
crossterm_event::MouseEventKind::ScrollLeft => MouseKind::ScrollLeft,
crossterm_event::MouseEventKind::ScrollRight => MouseKind::ScrollRight,
};
Some(Event::Mouse(MouseEvent {
kind,
x: m.column as u32,
y: m.row as u32,
modifiers: convert_modifiers(m.modifiers),
pixel_x: None,
pixel_y: None,
}))
}
crossterm_event::Event::Resize(cols, rows) => Some(Event::Resize(cols as u32, rows as u32)),
crossterm_event::Event::Paste(mut s) => {
const MAX_PASTE_BYTES: usize = 1 << 20;
if s.len() > MAX_PASTE_BYTES {
let mut end = MAX_PASTE_BYTES;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
s.truncate(end);
s.push('…');
}
Some(Event::Paste(s))
}
crossterm_event::Event::FocusGained => Some(Event::FocusGained),
crossterm_event::Event::FocusLost => Some(Event::FocusLost),
}
}
#[cfg(test)]
mod event_constructor_tests {
use super::*;
#[test]
fn test_key_char() {
let e = Event::key_char('q');
if let Event::Key(k) = e {
assert!(matches!(k.code, KeyCode::Char('q')));
assert_eq!(k.modifiers, KeyModifiers::NONE);
assert!(matches!(k.kind, KeyEventKind::Press));
} else {
panic!("Expected Key event");
}
}
#[test]
fn test_key() {
let e = Event::key(KeyCode::Enter);
if let Event::Key(k) = e {
assert!(matches!(k.code, KeyCode::Enter));
assert_eq!(k.modifiers, KeyModifiers::NONE);
assert!(matches!(k.kind, KeyEventKind::Press));
} else {
panic!("Expected Key event");
}
}
#[test]
fn test_key_ctrl() {
let e = Event::key_ctrl('s');
if let Event::Key(k) = e {
assert!(matches!(k.code, KeyCode::Char('s')));
assert_eq!(k.modifiers, KeyModifiers::CONTROL);
assert!(matches!(k.kind, KeyEventKind::Press));
} else {
panic!("Expected Key event");
}
}
#[test]
fn test_key_mod() {
let modifiers = KeyModifiers(KeyModifiers::SHIFT.0 | KeyModifiers::ALT.0);
let e = Event::key_mod(KeyCode::Tab, modifiers);
if let Event::Key(k) = e {
assert!(matches!(k.code, KeyCode::Tab));
assert_eq!(k.modifiers, modifiers);
assert!(matches!(k.kind, KeyEventKind::Press));
} else {
panic!("Expected Key event");
}
}
#[test]
fn test_resize() {
let e = Event::resize(80, 24);
assert!(matches!(e, Event::Resize(80, 24)));
}
#[test]
fn test_mouse_click() {
let e = Event::mouse_click(10, 5);
if let Event::Mouse(m) = e {
assert!(matches!(m.kind, MouseKind::Down(MouseButton::Left)));
assert_eq!(m.x, 10);
assert_eq!(m.y, 5);
assert_eq!(m.modifiers, KeyModifiers::NONE);
} else {
panic!("Expected Mouse event");
}
}
#[test]
fn test_mouse_move() {
let e = Event::mouse_move(10, 5);
if let Event::Mouse(m) = e {
assert!(matches!(m.kind, MouseKind::Moved));
assert_eq!(m.x, 10);
assert_eq!(m.y, 5);
assert_eq!(m.modifiers, KeyModifiers::NONE);
} else {
panic!("Expected Mouse event");
}
}
#[test]
fn test_paste() {
let e = Event::paste("hello");
assert!(matches!(e, Event::Paste(s) if s == "hello"));
}
}