1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MediaKeyCode};
2use std::fmt::Display;
3
4#[derive(Debug, Clone, Eq, PartialEq)]
6pub struct HotKey {
7 pub key: KeyCode,
9 pub mods: KeyModifiers,
11}
12
13impl HotKey {
14 #[must_use]
16 pub fn matches(&self, event: &KeyEvent) -> bool {
17 event.code == self.key && (event.modifiers == self.mods || event.code == KeyCode::BackTab)
19 }
20}
21
22#[derive(Debug, Clone)]
24pub struct GlobalHotKeys {
25 pub next_focus: Option<HotKey>,
27 pub previous_focus: Option<HotKey>,
29 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}