use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum KeyInput {
Char(char),
Enter,
Backspace,
Delete,
Tab,
Escape,
Arrow(Arrow),
Modifier(Modifier),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[expect(missing_docs, reason = "the directions are their names")]
#[non_exhaustive]
pub enum Arrow {
Up,
Down,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[expect(missing_docs, reason = "the keys are their labels")]
#[non_exhaustive]
pub enum Modifier {
Shift,
Fn,
Ctrl,
Opt,
Alt,
}
impl KeyInput {
#[must_use]
pub const fn char(self) -> Option<char> {
match self {
Self::Char(c) => Some(c),
Self::Enter => Some('\n'),
Self::Tab => Some('\t'),
_ => None,
}
}
}
impl fmt::Display for KeyInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Char(' ') => f.write_str("Space"),
Self::Char(c) => write!(f, "{c}"),
Self::Enter => f.write_str("Enter"),
Self::Backspace => f.write_str("Backspace"),
Self::Delete => f.write_str("Delete"),
Self::Tab => f.write_str("Tab"),
Self::Escape => f.write_str("Esc"),
Self::Arrow(arrow) => write!(f, "{arrow}"),
Self::Modifier(modifier) => write!(f, "{modifier}"),
}
}
}
impl fmt::Display for Arrow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Up => "Up",
Self::Down => "Down",
Self::Left => "Left",
Self::Right => "Right",
})
}
}
impl fmt::Display for Modifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Shift => "Shift",
Self::Fn => "Fn",
Self::Ctrl => "Ctrl",
Self::Opt => "Opt",
Self::Alt => "Alt",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum KeyEvent {
Pressed(KeyInput),
Released(KeyInput),
}
impl KeyEvent {
#[must_use]
pub const fn input(self) -> KeyInput {
match self {
Self::Pressed(input) | Self::Released(input) => input,
}
}
#[must_use]
pub const fn is_press(self) -> bool {
matches!(self, Self::Pressed(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_keys_have_a_character_and_the_others_dont() {
assert_eq!(KeyInput::Char('a').char(), Some('a'));
assert_eq!(KeyInput::Enter.char(), Some('\n'));
assert_eq!(KeyInput::Tab.char(), Some('\t'));
assert_eq!(KeyInput::Backspace.char(), None);
assert_eq!(KeyInput::Arrow(Arrow::Up).char(), None);
assert_eq!(KeyInput::Modifier(Modifier::Shift).char(), None);
}
#[test]
fn keys_display_as_their_names() {
let name = |input: KeyInput| input.to_string();
assert_eq!(name(KeyInput::Char('A')), "A");
assert_eq!(name(KeyInput::Char(' ')), "Space");
assert_eq!(name(KeyInput::Escape), "Esc");
assert_eq!(name(KeyInput::Arrow(Arrow::Left)), "Left");
assert_eq!(name(KeyInput::Modifier(Modifier::Ctrl)), "Ctrl");
}
#[test]
fn an_event_tells_its_key_and_whether_it_went_down() {
let press = KeyEvent::Pressed(KeyInput::Tab);
let release = KeyEvent::Released(KeyInput::Tab);
assert_eq!(press.input(), KeyInput::Tab);
assert_eq!(release.input(), KeyInput::Tab);
assert!(press.is_press());
assert!(!release.is_press());
}
}