1use alloc::borrow::Cow;
2use livesplit_hotkey::KeyCode;
3
4use crate::{
5 hotkey::{Hook, Hotkey},
6 HotkeyConfig, SharedTimer,
7};
8
9pub use crate::hotkey::{Error, Result};
10
11#[derive(Debug, Copy, Clone, Eq, PartialEq)]
13enum Action {
14 Split,
15 Reset,
17 Undo,
19 Skip,
21 Pause,
24 UndoAllPauses,
26 PreviousComparison,
28 NextComparison,
30 ToggleTimingMethod,
33}
34
35impl Action {
36 fn set_hotkey(self, config: &mut HotkeyConfig, hotkey: Option<Hotkey>) {
37 match self {
38 Action::Split => config.split = hotkey,
39 Action::Reset => config.reset = hotkey,
40 Action::Undo => config.undo = hotkey,
41 Action::Skip => config.skip = hotkey,
42 Action::Pause => config.pause = hotkey,
43 Action::UndoAllPauses => config.undo_all_pauses = hotkey,
44 Action::PreviousComparison => config.previous_comparison = hotkey,
45 Action::NextComparison => config.next_comparison = hotkey,
46 Action::ToggleTimingMethod => config.toggle_timing_method = hotkey,
47 }
48 }
49
50 const fn get_hotkey(self, config: &HotkeyConfig) -> Option<Hotkey> {
51 match self {
52 Action::Split => config.split,
53 Action::Reset => config.reset,
54 Action::Undo => config.undo,
55 Action::Skip => config.skip,
56 Action::Pause => config.pause,
57 Action::UndoAllPauses => config.undo_all_pauses,
58 Action::PreviousComparison => config.previous_comparison,
59 Action::NextComparison => config.next_comparison,
60 Action::ToggleTimingMethod => config.toggle_timing_method,
61 }
62 }
63
64 fn callback(self, timer: SharedTimer) -> Box<dyn FnMut() + Send + 'static> {
65 match self {
66 Action::Split => Box::new(move || timer.write().unwrap().split_or_start()),
67 Action::Reset => Box::new(move || timer.write().unwrap().reset(true)),
68 Action::Undo => Box::new(move || timer.write().unwrap().undo_split()),
69 Action::Skip => Box::new(move || timer.write().unwrap().skip_split()),
70 Action::Pause => Box::new(move || timer.write().unwrap().toggle_pause_or_start()),
71 Action::UndoAllPauses => Box::new(move || timer.write().unwrap().undo_all_pauses()),
72 Action::PreviousComparison => {
73 Box::new(move || timer.write().unwrap().switch_to_previous_comparison())
74 }
75 Action::NextComparison => {
76 Box::new(move || timer.write().unwrap().switch_to_next_comparison())
77 }
78 Action::ToggleTimingMethod => {
79 Box::new(move || timer.write().unwrap().toggle_timing_method())
80 }
81 }
82 }
83}
84
85pub struct HotkeySystem {
91 config: HotkeyConfig,
92 hook: Hook,
93 timer: SharedTimer,
94 is_active: bool,
95}
96
97impl HotkeySystem {
98 pub fn new(timer: SharedTimer) -> Result<Self> {
100 Self::with_config(timer, Default::default())
101 }
102
103 pub fn with_config(timer: SharedTimer, config: HotkeyConfig) -> Result<Self> {
106 let mut hotkey_system = Self {
107 config,
108 hook: Hook::new()?,
109 timer,
110 is_active: false,
111 };
112 hotkey_system.activate()?;
113 Ok(hotkey_system)
114 }
115
116 fn register_inner(&mut self, action: Action) -> Result<()> {
119 let inner = self.timer.clone();
120 if let Some(hotkey) = action.get_hotkey(&self.config) {
121 self.hook.register(hotkey, action.callback(inner))?;
122 }
123 Ok(())
124 }
125
126 fn register(&mut self, action: Action, hotkey: Option<Hotkey>) -> Result<()> {
127 action.set_hotkey(&mut self.config, hotkey);
128 self.register_inner(action)
129 }
130
131 fn unregister_inner(&mut self, action: Action) -> Result<()> {
134 if let Some(hotkey) = action.get_hotkey(&self.config) {
135 self.hook.unregister(hotkey)?;
136 }
137 Ok(())
138 }
139
140 fn unregister(&mut self, action: Action) -> Result<()> {
141 self.unregister_inner(action)?;
142 action.set_hotkey(&mut self.config, None);
143 Ok(())
144 }
145
146 fn set_hotkey(&mut self, action: Action, hotkey: Option<Hotkey>) -> Result<()> {
147 if action.get_hotkey(&self.config) == hotkey {
149 return Ok(());
150 }
151 if self.is_active {
152 self.unregister(action)?;
153 self.register(action, hotkey)?;
154 } else {
155 action.set_hotkey(&mut self.config, hotkey);
156 }
157 Ok(())
158 }
159
160 pub fn set_split(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
162 self.set_hotkey(Action::Split, hotkey)
163 }
164
165 pub fn set_reset(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
167 self.set_hotkey(Action::Reset, hotkey)
168 }
169
170 pub fn set_pause(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
173 self.set_hotkey(Action::Pause, hotkey)
174 }
175
176 pub fn set_skip(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
178 self.set_hotkey(Action::Skip, hotkey)
179 }
180
181 pub fn set_undo(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
183 self.set_hotkey(Action::Undo, hotkey)
184 }
185
186 pub fn set_previous_comparison(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
188 self.set_hotkey(Action::PreviousComparison, hotkey)
189 }
190
191 pub fn set_next_comparison(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
193 self.set_hotkey(Action::NextComparison, hotkey)
194 }
195
196 pub fn set_undo_all_pauses(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
199 self.set_hotkey(Action::UndoAllPauses, hotkey)
200 }
201
202 pub fn set_toggle_timing_method(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
205 self.set_hotkey(Action::ToggleTimingMethod, hotkey)
206 }
207
208 pub fn deactivate(&mut self) -> Result<()> {
211 if self.is_active {
212 self.unregister_inner(Action::Split)?;
213 self.unregister_inner(Action::Reset)?;
214 self.unregister_inner(Action::Undo)?;
215 self.unregister_inner(Action::Skip)?;
216 self.unregister_inner(Action::Pause)?;
217 self.unregister_inner(Action::UndoAllPauses)?;
218 self.unregister_inner(Action::PreviousComparison)?;
219 self.unregister_inner(Action::NextComparison)?;
220 self.unregister_inner(Action::ToggleTimingMethod)?;
221 }
222 self.is_active = false;
223 Ok(())
224 }
225
226 pub fn activate(&mut self) -> Result<()> {
229 if !self.is_active {
230 self.register_inner(Action::Split)?;
231 self.register_inner(Action::Reset)?;
232 self.register_inner(Action::Undo)?;
233 self.register_inner(Action::Skip)?;
234 self.register_inner(Action::Pause)?;
235 self.register_inner(Action::UndoAllPauses)?;
236 self.register_inner(Action::PreviousComparison)?;
237 self.register_inner(Action::NextComparison)?;
238 self.register_inner(Action::ToggleTimingMethod)?;
239 }
240 self.is_active = true;
241 Ok(())
242 }
243
244 pub const fn is_active(&self) -> bool {
246 self.is_active
247 }
248
249 pub const fn config(&self) -> HotkeyConfig {
251 self.config
252 }
253
254 pub fn set_config(&mut self, config: HotkeyConfig) -> Result<()> {
259 self.set_split(config.split)?;
260 self.set_reset(config.reset)?;
261 self.set_undo(config.undo)?;
262 self.set_skip(config.skip)?;
263 self.set_pause(config.pause)?;
264 self.set_previous_comparison(config.previous_comparison)?;
265 self.set_next_comparison(config.next_comparison)?;
266 self.set_undo_all_pauses(config.undo_all_pauses)?;
267 self.set_toggle_timing_method(config.toggle_timing_method)?;
268
269 Ok(())
270 }
271
272 pub fn resolve(&self, key_code: KeyCode) -> Cow<'static, str> {
274 key_code.resolve(&self.hook)
275 }
276}