Skip to main content

ez_tui/inputs/
hotkeys.rs

1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MediaKeyCode};
2use std::fmt::Display;
3
4/// A struct representing a hotkey combination.
5#[derive(Debug, Clone, Eq, PartialEq)]
6pub struct HotKey {
7    /// The key code of the hotkey.
8    pub key: KeyCode,
9    /// An optional modifier for the hotkey.
10    pub mods: KeyModifiers,
11}
12
13impl HotKey {
14    /// Check if the given key event matches the hotkey.
15    #[must_use]
16    pub fn matches(&self, event: &KeyEvent) -> bool {
17        // Special case for backtab
18        event.code == self.key && (event.modifiers == self.mods || event.code == KeyCode::BackTab)
19    }
20}
21
22/// A struct representing global hotkeys for the application.
23#[derive(Debug, Clone)]
24pub struct GlobalHotKeys {
25    /// Hotkey to switch to the next focusable component.
26    pub next_focus: Option<HotKey>,
27    /// Hotkey to switch to the previous focusable component.
28    pub previous_focus: Option<HotKey>,
29    /// Hotkey to quit the application.
30    pub quit: Option<HotKey>,
31}
32impl Default for GlobalHotKeys {
33    fn default() -> Self {
34        Self {
35            next_focus: HotKey::from(KeyCode::Tab).into(),
36            previous_focus: HotKey::from(KeyCode::BackTab).into(),
37            quit: HotKey::from((KeyCode::Char('Q'), KeyModifiers::SHIFT)).into(),
38        }
39    }
40}
41impl From<KeyCode> for HotKey {
42    fn from(key: KeyCode) -> Self {
43        Self {
44            key,
45            mods: KeyModifiers::NONE,
46        }
47    }
48}
49
50impl From<(KeyCode, KeyModifiers)> for HotKey {
51    fn from(input: (KeyCode, KeyModifiers)) -> Self {
52        Self {
53            key: input.0,
54            mods: input.1,
55        }
56    }
57}
58
59impl Display for HotKey {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        let hide_mods = if let KeyCode::Char(c) = self.key {
62            c.is_ascii_alphabetic()
63        } else {
64            false
65        };
66
67        let code_str: String = match self.key {
68            KeyCode::Char(c) => match c {
69                ' ' => "SPACE".to_string(),
70                _ => c.to_string(),
71            },
72            KeyCode::Backspace => "DEL".to_string(),
73            KeyCode::Enter => "↲".to_string(),
74            KeyCode::Left => "←".to_string(),
75            KeyCode::Right => "→".to_string(),
76            KeyCode::Up => "↑".to_string(),
77            KeyCode::Down => "↓".to_string(),
78            KeyCode::Home => "HOME".to_string(),
79            KeyCode::End => "END".to_string(),
80            KeyCode::PageUp => "⤒".to_string(),
81            KeyCode::PageDown => "⤓".to_string(),
82            KeyCode::Tab => "⇒".to_string(),
83            KeyCode::BackTab => "⇐".to_string(),
84            KeyCode::Delete => "SUPPR".to_string(),
85            KeyCode::Insert => "INSER".to_string(),
86            KeyCode::F(x) => format!("F{x}").to_string(),
87            KeyCode::Null => "NULL".to_string(),
88            KeyCode::Esc => "ESC".to_string(),
89            KeyCode::CapsLock => "CAPS_LOCKS".to_string(),
90            KeyCode::ScrollLock => "SCROLL_LOCK".to_string(),
91            KeyCode::NumLock => "NUM_LOCK".to_string(),
92            KeyCode::PrintScreen => "PRT_SCREEN".to_string(),
93            KeyCode::Pause => "PAUSE".to_string(),
94            KeyCode::Menu => "MENU".to_string(),
95            KeyCode::KeypadBegin => "KEYPD_BEGIN".to_string(),
96            KeyCode::Media(inner) => match inner {
97                MediaKeyCode::Play => "Play".to_string(),
98                MediaKeyCode::Pause => "Pause".to_string(),
99                MediaKeyCode::PlayPause => "PlayPause".to_string(),
100                MediaKeyCode::Reverse => "Reverse".to_string(),
101                MediaKeyCode::Stop => "Stop".to_string(),
102                MediaKeyCode::FastForward => "FastForward".to_string(),
103                MediaKeyCode::Rewind => "Rewind".to_string(),
104                MediaKeyCode::TrackNext => "TrackNext".to_string(),
105                MediaKeyCode::TrackPrevious => "TrackPrevious".to_string(),
106                MediaKeyCode::Record => "Record".to_string(),
107                MediaKeyCode::LowerVolume => "LowerVolume".to_string(),
108                MediaKeyCode::RaiseVolume => "RaiseVolume".to_string(),
109                MediaKeyCode::MuteVolume => "MuteVolume".to_string(),
110            },
111            KeyCode::Modifier(_) => "WTF".to_string(),
112        };
113
114        if hide_mods || self.mods.is_empty() {
115            write!(f, "{code_str}")
116        } else {
117            write!(f, "{} + {code_str}", self.mods)
118        }
119    }
120}