1use iced::keyboard::{Key, Modifiers, key::Named};
7
8#[derive(Debug, Clone)]
10pub struct Shortcut {
11 pub key: Key,
12 pub modifiers: Modifiers,
13 pub description: &'static str,
14 pub action: &'static str, }
16
17#[must_use]
19pub fn all_shortcuts() -> Vec<Shortcut> {
20 vec![
21 Shortcut {
22 key: Key::Character("d".into()),
23 modifiers: Modifiers::CTRL,
24 description: "Deploy mods",
25 action: "deploy",
26 },
27 Shortcut {
28 key: Key::Character("f".into()),
29 modifiers: Modifiers::CTRL,
30 description: "Focus filter",
31 action: "focus_filter",
32 },
33 Shortcut {
34 key: Key::Named(Named::F5),
35 modifiers: Modifiers::empty(),
36 description: "Refresh",
37 action: "refresh",
38 },
39 Shortcut {
40 key: Key::Named(Named::Delete),
41 modifiers: Modifiers::empty(),
42 description: "Remove selected mod",
43 action: "remove_selected",
44 },
45 Shortcut {
46 key: Key::Named(Named::Space),
47 modifiers: Modifiers::empty(),
48 description: "Toggle selected mod",
49 action: "toggle_selected",
50 },
51 Shortcut {
52 key: Key::Character("s".into()),
53 modifiers: Modifiers::CTRL,
54 description: "Save settings",
55 action: "save_settings",
56 },
57 Shortcut {
58 key: Key::Character("i".into()),
59 modifiers: Modifiers::CTRL,
60 description: "Open mod info",
61 action: "open_info",
62 },
63 Shortcut {
64 key: Key::Character("e".into()),
65 modifiers: Modifiers::CTRL,
66 description: "Export mod list",
67 action: "export",
68 },
69 Shortcut {
70 key: Key::Named(Named::Escape),
71 modifiers: Modifiers::empty(),
72 description: "Dismiss modal / cancel",
73 action: "dismiss_modal",
74 },
75 ]
76}
77
78#[must_use]
81pub fn match_shortcut(key: &Key, modifiers: Modifiers) -> Option<&'static str> {
82 for shortcut in all_shortcuts() {
83 if keys_match(key, &shortcut.key) && modifiers == shortcut.modifiers {
84 return Some(shortcut.action);
85 }
86 }
87 None
88}
89
90fn keys_match(a: &Key, b: &Key) -> bool {
91 match (a, b) {
92 (Key::Character(a), Key::Character(b)) => a == b,
93 (Key::Named(a), Key::Named(b)) => a == b,
94 _ => false,
95 }
96}
97
98#[must_use]
100pub fn help_text() -> String {
101 let mut lines = vec!["Keyboard Shortcuts:".to_string(), String::new()];
102 for s in all_shortcuts() {
103 let mod_str = format_modifiers(s.modifiers);
104 let key_str = format_key(&s.key);
105 let combo = if mod_str.is_empty() {
106 key_str
107 } else {
108 format!("{mod_str}+{key_str}")
109 };
110 lines.push(format!(" {:<20} {}", combo, s.description));
111 }
112 lines.join("\n")
113}
114
115fn format_modifiers(m: Modifiers) -> String {
116 let mut parts = Vec::new();
117 if m.control() {
118 parts.push("Ctrl");
119 }
120 if m.shift() {
121 parts.push("Shift");
122 }
123 if m.alt() {
124 parts.push("Alt");
125 }
126 parts.join("+")
127}
128
129fn format_key(key: &Key) -> String {
130 match key {
131 Key::Character(c) => c.to_uppercase(),
132 Key::Named(n) => format!("{n:?}"),
133 _ => "?".to_string(),
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_match_ctrl_d() {
143 let key = Key::Character("d".into());
144 let result = match_shortcut(&key, Modifiers::CTRL);
145 assert_eq!(result, Some("deploy"));
146 }
147
148 #[test]
149 fn test_match_f5() {
150 let key = Key::Named(Named::F5);
151 let result = match_shortcut(&key, Modifiers::empty());
152 assert_eq!(result, Some("refresh"));
153 }
154
155 #[test]
156 fn test_match_escape() {
157 let key = Key::Named(Named::Escape);
158 let result = match_shortcut(&key, Modifiers::empty());
159 assert_eq!(result, Some("dismiss_modal"));
160 }
161
162 #[test]
163 fn test_no_match() {
164 let key = Key::Character("z".into());
165 let result = match_shortcut(&key, Modifiers::empty());
166 assert_eq!(result, None);
167 }
168
169 #[test]
170 fn test_help_text() {
171 let text = help_text();
172 assert!(!text.is_empty());
173 assert!(text.contains("Ctrl+D"));
174 assert!(text.contains("Deploy mods"));
175 assert!(text.contains("F5"));
176 assert!(text.contains("Refresh"));
177 }
178
179 #[test]
180 fn test_all_shortcuts_unique() {
181 let shortcuts = all_shortcuts();
182 let mut seen = std::collections::HashSet::new();
183 for s in &shortcuts {
184 let key_repr = format!("{:?}+{:?}", s.modifiers, s.key);
185 assert!(
186 seen.insert(key_repr.clone()),
187 "Duplicate shortcut: {key_repr}"
188 );
189 }
190 }
191}