use alloc::borrow::Cow;
use livesplit_hotkey::KeyCode;
use crate::{
hotkey::{Hook, Hotkey},
HotkeyConfig, SharedTimer,
};
pub use crate::hotkey::{Error, Result};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum Action {
Split,
Reset,
Undo,
Skip,
Pause,
UndoAllPauses,
PreviousComparison,
NextComparison,
ToggleTimingMethod,
}
impl Action {
fn set_hotkey(self, config: &mut HotkeyConfig, hotkey: Option<Hotkey>) {
match self {
Action::Split => config.split = hotkey,
Action::Reset => config.reset = hotkey,
Action::Undo => config.undo = hotkey,
Action::Skip => config.skip = hotkey,
Action::Pause => config.pause = hotkey,
Action::UndoAllPauses => config.undo_all_pauses = hotkey,
Action::PreviousComparison => config.previous_comparison = hotkey,
Action::NextComparison => config.next_comparison = hotkey,
Action::ToggleTimingMethod => config.toggle_timing_method = hotkey,
}
}
const fn get_hotkey(self, config: &HotkeyConfig) -> Option<Hotkey> {
match self {
Action::Split => config.split,
Action::Reset => config.reset,
Action::Undo => config.undo,
Action::Skip => config.skip,
Action::Pause => config.pause,
Action::UndoAllPauses => config.undo_all_pauses,
Action::PreviousComparison => config.previous_comparison,
Action::NextComparison => config.next_comparison,
Action::ToggleTimingMethod => config.toggle_timing_method,
}
}
fn callback(self, timer: SharedTimer) -> Box<dyn FnMut() + Send + 'static> {
match self {
Action::Split => Box::new(move || timer.write().unwrap().split_or_start()),
Action::Reset => Box::new(move || timer.write().unwrap().reset(true)),
Action::Undo => Box::new(move || timer.write().unwrap().undo_split()),
Action::Skip => Box::new(move || timer.write().unwrap().skip_split()),
Action::Pause => Box::new(move || timer.write().unwrap().toggle_pause_or_start()),
Action::UndoAllPauses => Box::new(move || timer.write().unwrap().undo_all_pauses()),
Action::PreviousComparison => {
Box::new(move || timer.write().unwrap().switch_to_previous_comparison())
}
Action::NextComparison => {
Box::new(move || timer.write().unwrap().switch_to_next_comparison())
}
Action::ToggleTimingMethod => {
Box::new(move || timer.write().unwrap().toggle_timing_method())
}
}
}
}
pub struct HotkeySystem {
config: HotkeyConfig,
hook: Hook,
timer: SharedTimer,
is_active: bool,
}
impl HotkeySystem {
pub fn new(timer: SharedTimer) -> Result<Self> {
Self::with_config(timer, Default::default())
}
pub fn with_config(timer: SharedTimer, config: HotkeyConfig) -> Result<Self> {
let mut hotkey_system = Self {
config,
hook: Hook::new()?,
timer,
is_active: false,
};
hotkey_system.activate()?;
Ok(hotkey_system)
}
fn register_inner(&mut self, action: Action) -> Result<()> {
let inner = self.timer.clone();
if let Some(hotkey) = action.get_hotkey(&self.config) {
self.hook.register(hotkey, action.callback(inner))?;
}
Ok(())
}
fn register(&mut self, action: Action, hotkey: Option<Hotkey>) -> Result<()> {
action.set_hotkey(&mut self.config, hotkey);
self.register_inner(action)
}
fn unregister_inner(&mut self, action: Action) -> Result<()> {
if let Some(hotkey) = action.get_hotkey(&self.config) {
self.hook.unregister(hotkey)?;
}
Ok(())
}
fn unregister(&mut self, action: Action) -> Result<()> {
self.unregister_inner(action)?;
action.set_hotkey(&mut self.config, None);
Ok(())
}
fn set_hotkey(&mut self, action: Action, hotkey: Option<Hotkey>) -> Result<()> {
if action.get_hotkey(&self.config) == hotkey {
return Ok(());
}
if self.is_active {
self.unregister(action)?;
self.register(action, hotkey)?;
} else {
action.set_hotkey(&mut self.config, hotkey);
}
Ok(())
}
pub fn set_split(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::Split, hotkey)
}
pub fn set_reset(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::Reset, hotkey)
}
pub fn set_pause(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::Pause, hotkey)
}
pub fn set_skip(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::Skip, hotkey)
}
pub fn set_undo(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::Undo, hotkey)
}
pub fn set_previous_comparison(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::PreviousComparison, hotkey)
}
pub fn set_next_comparison(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::NextComparison, hotkey)
}
pub fn set_undo_all_pauses(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::UndoAllPauses, hotkey)
}
pub fn set_toggle_timing_method(&mut self, hotkey: Option<Hotkey>) -> Result<()> {
self.set_hotkey(Action::ToggleTimingMethod, hotkey)
}
pub fn deactivate(&mut self) -> Result<()> {
if self.is_active {
self.unregister_inner(Action::Split)?;
self.unregister_inner(Action::Reset)?;
self.unregister_inner(Action::Undo)?;
self.unregister_inner(Action::Skip)?;
self.unregister_inner(Action::Pause)?;
self.unregister_inner(Action::UndoAllPauses)?;
self.unregister_inner(Action::PreviousComparison)?;
self.unregister_inner(Action::NextComparison)?;
self.unregister_inner(Action::ToggleTimingMethod)?;
}
self.is_active = false;
Ok(())
}
pub fn activate(&mut self) -> Result<()> {
if !self.is_active {
self.register_inner(Action::Split)?;
self.register_inner(Action::Reset)?;
self.register_inner(Action::Undo)?;
self.register_inner(Action::Skip)?;
self.register_inner(Action::Pause)?;
self.register_inner(Action::UndoAllPauses)?;
self.register_inner(Action::PreviousComparison)?;
self.register_inner(Action::NextComparison)?;
self.register_inner(Action::ToggleTimingMethod)?;
}
self.is_active = true;
Ok(())
}
pub const fn is_active(&self) -> bool {
self.is_active
}
pub const fn config(&self) -> HotkeyConfig {
self.config
}
pub fn set_config(&mut self, config: HotkeyConfig) -> Result<()> {
self.set_split(config.split)?;
self.set_reset(config.reset)?;
self.set_undo(config.undo)?;
self.set_skip(config.skip)?;
self.set_pause(config.pause)?;
self.set_previous_comparison(config.previous_comparison)?;
self.set_next_comparison(config.next_comparison)?;
self.set_undo_all_pauses(config.undo_all_pauses)?;
self.set_toggle_timing_method(config.toggle_timing_method)?;
Ok(())
}
pub fn resolve(&self, key_code: KeyCode) -> Cow<'static, str> {
key_code.resolve(&self.hook)
}
}