livesplit_core/
hotkey_config.rs

1#![allow(clippy::trivially_copy_pass_by_ref)]
2
3use crate::{
4    hotkey::Hotkey,
5    platform::prelude::*,
6    settings::{Field, SettingsDescription, Value},
7};
8use serde::{Deserialize, Serialize};
9
10/// The configuration to use for a [`HotkeySystem`](crate::HotkeySystem). It describes which [`Hotkey`](livesplit_hotkey::Hotkey) to use as hotkeys for the different actions.
11#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
12#[serde(default)]
13pub struct HotkeyConfig {
14    /// The key to use for splitting and starting a new attempt.
15    pub split: Option<Hotkey>,
16    /// The key to use for resetting the current attempt.
17    pub reset: Option<Hotkey>,
18    /// The key to use for undoing the last split.
19    pub undo: Option<Hotkey>,
20    /// The key to use for skipping the current split.
21    pub skip: Option<Hotkey>,
22    /// The key to use for pausing the current attempt and starting a new
23    /// attempt.
24    pub pause: Option<Hotkey>,
25    /// The key to use for removing all the pause times from the current time.
26    pub undo_all_pauses: Option<Hotkey>,
27    /// The key to use for switching to the previous comparison.
28    pub previous_comparison: Option<Hotkey>,
29    /// The key to use for switching to the next comparison.
30    pub next_comparison: Option<Hotkey>,
31    /// The key to use for toggling between the `Real Time` and `Game Time`
32    /// timing methods.
33    pub toggle_timing_method: Option<Hotkey>,
34}
35
36impl Default for HotkeyConfig {
37    fn default() -> Self {
38        use crate::hotkey::KeyCode::*;
39        Self {
40            split: Some(Numpad1.into()),
41            reset: Some(Numpad3.into()),
42            undo: Some(Numpad8.into()),
43            skip: Some(Numpad2.into()),
44            pause: Some(Numpad5.into()),
45            undo_all_pauses: None,
46            previous_comparison: Some(Numpad4.into()),
47            next_comparison: Some(Numpad6.into()),
48            toggle_timing_method: None,
49        }
50    }
51}
52
53impl HotkeyConfig {
54    /// Accesses a generic description of the settings available for the hotkey
55    /// configuration and their current values.
56    pub fn settings_description(&self) -> SettingsDescription {
57        SettingsDescription::with_fields(vec![
58            Field::new("Start / Split".into(), self.split.into()),
59            Field::new("Reset".into(), self.reset.into()),
60            Field::new("Undo Split".into(), self.undo.into()),
61            Field::new("Skip Split".into(), self.skip.into()),
62            Field::new("Pause".into(), self.pause.into()),
63            Field::new("Undo All Pauses".into(), self.undo_all_pauses.into()),
64            Field::new(
65                "Previous Comparison".into(),
66                self.previous_comparison.into(),
67            ),
68            Field::new("Next Comparison".into(), self.next_comparison.into()),
69            Field::new(
70                "Toggle Timing Method".into(),
71                self.toggle_timing_method.into(),
72            ),
73        ])
74    }
75
76    /// Sets a setting's value by its index to the given value.
77    ///
78    /// # Errors
79    ///
80    /// An error is returned if a hotkey is already in use by a different
81    /// action.
82    ///
83    /// # Panics
84    ///
85    /// This panics if the type of the value to be set is not compatible with
86    /// the type of the setting's value. A panic can also occur if the index of
87    /// the setting provided is out of bounds.
88    pub fn set_value(&mut self, index: usize, value: Value) -> Result<(), ()> {
89        let value: Option<Hotkey> = value.into();
90
91        if value.is_some() {
92            let any = [
93                self.split,
94                self.reset,
95                self.undo,
96                self.skip,
97                self.pause,
98                self.undo_all_pauses,
99                self.previous_comparison,
100                self.next_comparison,
101                self.toggle_timing_method,
102            ]
103            .into_iter()
104            .enumerate()
105            .filter(|&(i, _)| i != index)
106            .any(|(_, v)| v == value);
107
108            if any {
109                return Err(());
110            }
111        }
112
113        match index {
114            0 => self.split = value,
115            1 => self.reset = value,
116            2 => self.undo = value,
117            3 => self.skip = value,
118            4 => self.pause = value,
119            5 => self.undo_all_pauses = value,
120            6 => self.previous_comparison = value,
121            7 => self.next_comparison = value,
122            8 => self.toggle_timing_method = value,
123            _ => panic!("Unsupported Setting Index"),
124        }
125
126        Ok(())
127    }
128
129    /// Decodes the hotkey configuration from JSON.
130    #[cfg(feature = "std")]
131    pub fn from_json<R>(reader: R) -> serde_json::Result<Self>
132    where
133        R: std::io::Read,
134    {
135        serde_json::from_reader(reader)
136    }
137
138    /// Encodes the hotkey configuration as JSON.
139    #[cfg(feature = "std")]
140    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
141    where
142        W: std::io::Write,
143    {
144        serde_json::to_writer(writer, self)
145    }
146}