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