#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum KeyCode {
Char(char),
Enter,
Backspace,
Tab,
BackTab,
Esc,
Up,
Down,
Right,
Left,
Home,
End,
Insert,
Delete,
PageUp,
PageDown,
F(u8),
CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
Menu,
KeypadBegin,
Modifier(ModifierKeyCode),
Media(MediaKeyCode),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum ModifierKeyCode {
LeftShift,
LeftControl,
LeftAlt,
LeftSuper,
LeftHyper,
LeftMeta,
RightShift,
RightControl,
RightAlt,
RightSuper,
RightHyper,
RightMeta,
IsoLevel3Shift,
IsoLevel5Shift,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum MediaKeyCode {
Play,
Pause,
PlayPause,
Reverse,
Stop,
FastForward,
Rewind,
TrackNext,
TrackPrevious,
Record,
LowerVolume,
RaiseVolume,
MuteVolume,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct KeyModifiers: u8 {
const NONE = 0b0000_0000;
const SHIFT = 0b0000_0001;
const ALT = 0b0000_0010;
const CONTROL = 0b0000_0100;
const SUPER = 0b0000_1000;
const HYPER = 0b0001_0000;
const META = 0b0010_0000;
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum KeyEventKind {
#[default]
Press,
Repeat,
Release,
}
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct KeyEventState: u8 {
const CAPS_LOCK = 0b0000_0001;
const NUM_LOCK = 0b0000_0010;
const KEYPAD = 0b0000_0100;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
pub kind: KeyEventKind,
pub state: KeyEventState,
pub unshifted_codepoint: Option<char>,
pub consumed_modifiers: KeyModifiers,
}
impl KeyEvent {
pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
Self {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
unshifted_codepoint: None,
consumed_modifiers: KeyModifiers::empty(),
}
}
pub fn with_kind(mut self, kind: KeyEventKind) -> Self {
self.kind = kind;
self
}
pub fn with_state(mut self, state: KeyEventState) -> Self {
self.state = state;
self
}
pub fn with_unshifted_codepoint(mut self, codepoint: char) -> Self {
self.unshifted_codepoint = Some(codepoint);
self
}
pub fn with_consumed_modifiers(mut self, modifiers: KeyModifiers) -> Self {
self.consumed_modifiers = modifiers;
self
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MouseButton {
Left,
Middle,
Right,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MouseEventKind {
Down(MouseButton),
Up(MouseButton),
Drag(MouseButton),
Moved,
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct MouseEvent {
pub kind: MouseEventKind,
pub row: u16,
pub col: u16,
pub modifiers: KeyModifiers,
}
impl MouseEvent {
#[must_use]
pub const fn new(kind: MouseEventKind, row: u16, col: u16, modifiers: KeyModifiers) -> Self {
Self {
kind,
row,
col,
modifiers,
}
}
}
#[cfg(feature = "crossterm")]
mod crossterm_conv {
use super::*;
impl From<crossterm::event::KeyCode> for KeyCode {
fn from(code: crossterm::event::KeyCode) -> Self {
use crossterm::event::KeyCode as CK;
match code {
CK::Char(c) => KeyCode::Char(c),
CK::Enter => KeyCode::Enter,
CK::Backspace => KeyCode::Backspace,
CK::Tab => KeyCode::Tab,
CK::BackTab => KeyCode::BackTab,
CK::Esc => KeyCode::Esc,
CK::Up => KeyCode::Up,
CK::Down => KeyCode::Down,
CK::Right => KeyCode::Right,
CK::Left => KeyCode::Left,
CK::Home => KeyCode::Home,
CK::End => KeyCode::End,
CK::Insert => KeyCode::Insert,
CK::Delete => KeyCode::Delete,
CK::PageUp => KeyCode::PageUp,
CK::PageDown => KeyCode::PageDown,
CK::F(n) => KeyCode::F(n),
CK::CapsLock => KeyCode::CapsLock,
CK::ScrollLock => KeyCode::ScrollLock,
CK::NumLock => KeyCode::NumLock,
CK::PrintScreen => KeyCode::PrintScreen,
CK::Pause => KeyCode::Pause,
CK::Menu => KeyCode::Menu,
CK::KeypadBegin => KeyCode::KeypadBegin,
CK::Modifier(m) => KeyCode::Modifier(m.into()),
CK::Media(m) => KeyCode::Media(m.into()),
CK::Null => KeyCode::Char('\0'),
}
}
}
impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
fn from(m: crossterm::event::ModifierKeyCode) -> Self {
use crossterm::event::ModifierKeyCode as CM;
match m {
CM::LeftShift => ModifierKeyCode::LeftShift,
CM::LeftControl => ModifierKeyCode::LeftControl,
CM::LeftAlt => ModifierKeyCode::LeftAlt,
CM::LeftSuper => ModifierKeyCode::LeftSuper,
CM::LeftHyper => ModifierKeyCode::LeftHyper,
CM::LeftMeta => ModifierKeyCode::LeftMeta,
CM::RightShift => ModifierKeyCode::RightShift,
CM::RightControl => ModifierKeyCode::RightControl,
CM::RightAlt => ModifierKeyCode::RightAlt,
CM::RightSuper => ModifierKeyCode::RightSuper,
CM::RightHyper => ModifierKeyCode::RightHyper,
CM::RightMeta => ModifierKeyCode::RightMeta,
CM::IsoLevel3Shift => ModifierKeyCode::IsoLevel3Shift,
CM::IsoLevel5Shift => ModifierKeyCode::IsoLevel5Shift,
}
}
}
impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
fn from(m: crossterm::event::MediaKeyCode) -> Self {
use crossterm::event::MediaKeyCode as CM;
match m {
CM::Play => MediaKeyCode::Play,
CM::Pause => MediaKeyCode::Pause,
CM::PlayPause => MediaKeyCode::PlayPause,
CM::Reverse => MediaKeyCode::Reverse,
CM::Stop => MediaKeyCode::Stop,
CM::FastForward => MediaKeyCode::FastForward,
CM::Rewind => MediaKeyCode::Rewind,
CM::TrackNext => MediaKeyCode::TrackNext,
CM::TrackPrevious => MediaKeyCode::TrackPrevious,
CM::Record => MediaKeyCode::Record,
CM::LowerVolume => MediaKeyCode::LowerVolume,
CM::RaiseVolume => MediaKeyCode::RaiseVolume,
CM::MuteVolume => MediaKeyCode::MuteVolume,
}
}
}
impl From<crossterm::event::KeyModifiers> for KeyModifiers {
fn from(m: crossterm::event::KeyModifiers) -> Self {
let mut out = KeyModifiers::empty();
if m.contains(crossterm::event::KeyModifiers::SHIFT) {
out |= KeyModifiers::SHIFT;
}
if m.contains(crossterm::event::KeyModifiers::ALT) {
out |= KeyModifiers::ALT;
}
if m.contains(crossterm::event::KeyModifiers::CONTROL) {
out |= KeyModifiers::CONTROL;
}
if m.contains(crossterm::event::KeyModifiers::SUPER) {
out |= KeyModifiers::SUPER;
}
if m.contains(crossterm::event::KeyModifiers::HYPER) {
out |= KeyModifiers::HYPER;
}
if m.contains(crossterm::event::KeyModifiers::META) {
out |= KeyModifiers::META;
}
out
}
}
impl From<crossterm::event::KeyEventKind> for KeyEventKind {
fn from(k: crossterm::event::KeyEventKind) -> Self {
match k {
crossterm::event::KeyEventKind::Press => KeyEventKind::Press,
crossterm::event::KeyEventKind::Repeat => KeyEventKind::Repeat,
crossterm::event::KeyEventKind::Release => KeyEventKind::Release,
}
}
}
impl From<crossterm::event::KeyEventState> for KeyEventState {
fn from(s: crossterm::event::KeyEventState) -> Self {
let mut out = KeyEventState::empty();
if s.contains(crossterm::event::KeyEventState::CAPS_LOCK) {
out |= KeyEventState::CAPS_LOCK;
}
if s.contains(crossterm::event::KeyEventState::NUM_LOCK) {
out |= KeyEventState::NUM_LOCK;
}
if s.contains(crossterm::event::KeyEventState::KEYPAD) {
out |= KeyEventState::KEYPAD;
}
out
}
}
fn us_unshift(c: char) -> Option<char> {
Some(match c {
'!' => '1',
'@' => '2',
'#' => '3',
'$' => '4',
'%' => '5',
'^' => '6',
'&' => '7',
'*' => '8',
'(' => '9',
')' => '0',
'_' => '-',
'+' => '=',
'~' => '`',
'{' => '[',
'}' => ']',
'|' => '\\',
':' => ';',
'"' => '\'',
'<' => ',',
'>' => '.',
'?' => '/',
'A'..='Z' => (c as u8 - b'A' + b'a') as char,
_ => return None,
})
}
impl From<crossterm::event::KeyEvent> for KeyEvent {
fn from(e: crossterm::event::KeyEvent) -> Self {
let modifiers: KeyModifiers = e.modifiers.into();
let unshifted_codepoint = match e.code {
crossterm::event::KeyCode::Char(c) if modifiers.contains(KeyModifiers::SHIFT) => {
us_unshift(c)
}
_ => None,
};
Self {
code: e.code.into(),
modifiers,
kind: e.kind.into(),
state: e.state.into(),
unshifted_codepoint,
consumed_modifiers: KeyModifiers::empty(),
}
}
}
impl From<&crossterm::event::KeyEvent> for KeyEvent {
fn from(e: &crossterm::event::KeyEvent) -> Self {
(*e).into()
}
}
impl From<crossterm::event::MouseButton> for MouseButton {
fn from(b: crossterm::event::MouseButton) -> Self {
match b {
crossterm::event::MouseButton::Left => MouseButton::Left,
crossterm::event::MouseButton::Middle => MouseButton::Middle,
crossterm::event::MouseButton::Right => MouseButton::Right,
}
}
}
impl From<crossterm::event::MouseEventKind> for MouseEventKind {
fn from(k: crossterm::event::MouseEventKind) -> Self {
use crossterm::event::MouseEventKind as CK;
match k {
CK::Down(b) => MouseEventKind::Down(b.into()),
CK::Up(b) => MouseEventKind::Up(b.into()),
CK::Drag(b) => MouseEventKind::Drag(b.into()),
CK::Moved => MouseEventKind::Moved,
CK::ScrollUp => MouseEventKind::ScrollUp,
CK::ScrollDown => MouseEventKind::ScrollDown,
CK::ScrollLeft => MouseEventKind::ScrollLeft,
CK::ScrollRight => MouseEventKind::ScrollRight,
}
}
}
impl From<crossterm::event::MouseEvent> for MouseEvent {
fn from(e: crossterm::event::MouseEvent) -> Self {
Self {
kind: e.kind.into(),
row: e.row,
col: e.column,
modifiers: e.modifiers.into(),
}
}
}
impl From<&crossterm::event::MouseEvent> for MouseEvent {
fn from(e: &crossterm::event::MouseEvent) -> Self {
(*e).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{
KeyCode as CK, KeyEvent as CKE, KeyEventKind as CKK, KeyEventState as CKS,
KeyModifiers as CKM,
};
fn ct_key(code: CK, modifiers: CKM) -> CKE {
CKE {
code,
modifiers,
kind: CKK::Press,
state: CKS::empty(),
}
}
#[test]
fn from_crossterm_shift_digit_recovers_unshifted() {
let key: KeyEvent = ct_key(CK::Char('!'), CKM::SHIFT).into();
assert_eq!(key.code, KeyCode::Char('!'));
assert_eq!(key.unshifted_codepoint, Some('1'));
assert_eq!(key.consumed_modifiers, KeyModifiers::empty());
}
#[test]
fn from_crossterm_shift_punctuation_recovers_unshifted() {
let key: KeyEvent = ct_key(CK::Char(':'), CKM::SHIFT).into();
assert_eq!(key.unshifted_codepoint, Some(';'));
}
#[test]
fn from_crossterm_shift_letter_recovers_lowercase() {
let key: KeyEvent = ct_key(CK::Char('A'), CKM::SHIFT).into();
assert_eq!(key.unshifted_codepoint, Some('a'));
}
#[test]
fn from_crossterm_no_shift_leaves_unshifted_none() {
let key: KeyEvent = ct_key(CK::Char('!'), CKM::empty()).into();
assert_eq!(key.unshifted_codepoint, None);
}
#[test]
fn from_crossterm_shift_unknown_char_leaves_unshifted_none() {
let key: KeyEvent = ct_key(CK::Char('é'), CKM::SHIFT).into();
assert_eq!(key.unshifted_codepoint, None);
}
#[test]
fn from_crossterm_non_char_leaves_unshifted_none() {
let key: KeyEvent = ct_key(CK::F(5), CKM::SHIFT).into();
assert_eq!(key.unshifted_codepoint, None);
}
}
}
#[cfg(test)]
mod mouse_event_tests {
use super::*;
#[test]
fn new_takes_kind_then_row_then_col_then_modifiers() {
let ev = MouseEvent::new(
MouseEventKind::Down(MouseButton::Left),
7,
3,
KeyModifiers::SHIFT,
);
assert_eq!(ev.kind, MouseEventKind::Down(MouseButton::Left));
assert_eq!(ev.row, 7);
assert_eq!(ev.col, 3);
assert_eq!(ev.modifiers, KeyModifiers::SHIFT);
}
}