Skip to main content

veloren_voxygen/settings/
control.rs

1use crate::{
2    game_input::GameInput,
3    window::{KeyMouse, MenuInput},
4};
5use hashbrown::{HashMap, HashSet};
6use serde::{Deserialize, Serialize};
7use strum::IntoEnumIterator;
8use winit::{
9    event::MouseButton,
10    keyboard::{Key, NamedKey},
11};
12
13// ControlSetting-like struct used by Serde, to handle not serializing/building
14// post-deserializing the inverse_keybindings hashmap
15#[derive(Serialize, Deserialize)]
16#[serde(default)]
17struct ControlSettingsSerde {
18    keybindings: HashMap<GameInput, Option<KeyMouse>>,
19    menubindings: HashMap<MenuInput, Option<KeyMouse>>,
20}
21
22impl From<ControlSettings> for ControlSettingsSerde {
23    fn from(control_settings: ControlSettings) -> Self {
24        let mut user_bindings: HashMap<GameInput, Option<KeyMouse>> = HashMap::new();
25        // Do a delta between default() ControlSettings and the argument, and let
26        // keybindings be only the custom keybindings chosen by the user.
27        for (k, v) in control_settings.keybindings {
28            if ControlSettings::default_binding(k) != v {
29                // Keybinding chosen by the user
30                user_bindings.insert(k, v);
31            }
32        }
33
34        let mut menu_bindings: HashMap<MenuInput, Option<KeyMouse>> = HashMap::new();
35        for (k, v) in control_settings.menubindings {
36            if ControlSettings::default_menu_binding(k) != v {
37                // Menubinding chosen by the user
38                menu_bindings.insert(k, v);
39            }
40        }
41
42        ControlSettingsSerde {
43            keybindings: user_bindings,
44            menubindings: menu_bindings,
45        }
46    }
47}
48
49impl Default for ControlSettingsSerde {
50    fn default() -> Self { ControlSettings::default().into() }
51}
52
53/// `ControlSettings` contains keybindings.
54#[derive(Clone, Debug, Serialize, Deserialize)]
55#[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")]
56pub struct ControlSettings {
57    pub keybindings: HashMap<GameInput, Option<KeyMouse>>,
58    pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, // used in event loop
59    pub menubindings: HashMap<MenuInput, Option<KeyMouse>>,
60    pub inverse_menubindings: HashMap<KeyMouse, HashSet<MenuInput>>,
61}
62
63impl From<ControlSettingsSerde> for ControlSettings {
64    fn from(control_serde: ControlSettingsSerde) -> Self {
65        let user_keybindings = control_serde.keybindings;
66        let menu_bindings = control_serde.menubindings;
67        let mut control_settings = ControlSettings::default();
68        for (k, maybe_v) in user_keybindings {
69            match maybe_v {
70                Some(v) => control_settings.modify_binding(k, v),
71                None => control_settings.remove_binding(k),
72            }
73        }
74        // update menu bindings
75        for (k, maybe_v) in menu_bindings {
76            match maybe_v {
77                Some(v) => control_settings.modify_menu_binding(k, v),
78                None => control_settings.remove_menu_binding(k),
79            }
80        }
81        control_settings
82    }
83}
84
85/// Since Macbook trackpads lack middle click, on OS X we default to Shift
86/// instead. It is an imperfect heuristic, but hopefully it will be a slightly
87/// better default, and the two places we default to middle click currently
88/// (roll and wall jump) are both situations where you cannot glide (the other
89/// default mapping for Shift).
90#[cfg(target_os = "macos")]
91const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(Key::Named(NamedKey::Shift));
92#[cfg(not(target_os = "macos"))]
93const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle);
94
95impl ControlSettings {
96    pub fn remove_binding(&mut self, game_input: GameInput) {
97        if let Some(inverse) = self
98            .keybindings
99            .insert(game_input, None)
100            .flatten()
101            .and_then(|key_mouse| self.inverse_keybindings.get_mut(&key_mouse))
102        {
103            inverse.remove(&game_input);
104        }
105    }
106
107    pub fn remove_menu_binding(&mut self, menu_input: MenuInput) {
108        if let Some(inverse) = self
109            .menubindings
110            .insert(menu_input, None)
111            .flatten()
112            .and_then(|key_mouse| self.inverse_menubindings.get_mut(&key_mouse))
113        {
114            inverse.remove(&menu_input);
115        }
116    }
117
118    pub fn get_binding(&self, game_input: GameInput) -> Option<KeyMouse> {
119        self.keybindings.get(&game_input).cloned().flatten()
120    }
121
122    pub fn get_menu_binding(&self, menu_input: MenuInput) -> Option<KeyMouse> {
123        self.menubindings.get(&menu_input).cloned().flatten()
124    }
125
126    pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<GameInput>> {
127        self.inverse_keybindings.get(key_mouse)
128    }
129
130    pub fn get_associated_menu_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<MenuInput>> {
131        self.inverse_menubindings.get(key_mouse)
132    }
133
134    pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
135        self.keybindings.insert(game_input, Some(key_mouse.clone()));
136        self.inverse_keybindings
137            .entry(key_mouse)
138            .or_default()
139            .insert(game_input);
140    }
141
142    pub fn insert_menu_binding(&mut self, menu_input: MenuInput, key_mouse: KeyMouse) {
143        self.menubindings
144            .insert(menu_input, Some(key_mouse.clone()));
145        self.inverse_menubindings
146            .entry(key_mouse)
147            .or_default()
148            .insert(menu_input);
149    }
150
151    pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
152        // For the KeyMouse->GameInput hashmap, we first need to remove the GameInput
153        // from the old binding
154        if let Some(old_binding) = self.get_binding(game_input) {
155            self.inverse_keybindings
156                .entry(old_binding)
157                .or_default()
158                .remove(&game_input);
159        }
160        // then we add the GameInput to the proper key
161        self.inverse_keybindings
162            .entry(key_mouse.clone())
163            .or_default()
164            .insert(game_input);
165        // For the GameInput->KeyMouse hashmap, just overwrite the value
166        self.keybindings.insert(game_input, Some(key_mouse));
167    }
168
169    pub fn modify_menu_binding(&mut self, menu_input: MenuInput, key_mouse: KeyMouse) {
170        // For the KeyMouse->MenuInput hashmap, we first need to remove the MenuInput
171        // from the old binding
172        if let Some(old_binding) = self.get_menu_binding(menu_input) {
173            self.inverse_menubindings
174                .entry(old_binding)
175                .or_default()
176                .remove(&menu_input);
177        }
178        // Then we add the MenuInput to the proper key
179        self.inverse_menubindings
180            .entry(key_mouse.clone())
181            .or_default()
182            .insert(menu_input);
183        // For the MenuInput->KeyMouse hashmap, just overwrite the value
184        self.menubindings.insert(menu_input, Some(key_mouse));
185    }
186
187    /// Return true if this key is used for multiple GameInputs that aren't
188    /// expected to be safe to have bound to the same key at the same time
189    pub fn has_conflicting_bindings(&self, key_mouse: KeyMouse) -> bool {
190        if let Some(game_inputs) = self.inverse_keybindings.get(&key_mouse) {
191            for a in game_inputs.iter() {
192                for b in game_inputs.iter() {
193                    if !GameInput::can_share_bindings(*a, *b) {
194                        return true;
195                    }
196                }
197            }
198        }
199        false
200    }
201
202    /// Return true if this key is used for multiple MenuInputs that aren't
203    /// expected to be safe to have bound to the same key at the same time
204    pub fn has_conflicting_menu_bindings(&self, key_mouse: KeyMouse) -> bool {
205        self.inverse_menubindings
206            .get(&key_mouse)
207            .is_some_and(|menu_inputs| menu_inputs.len() > 1)
208    }
209
210    pub fn default_binding(game_input: GameInput) -> Option<KeyMouse> {
211        let char = |s| Key::Character(winit::keyboard::SmolStr::new(s));
212
213        // If a new GameInput is added, be sure to update GameInput::iterator() too!
214        Some(KeyMouse::Key(match game_input {
215            GameInput::Primary => return Some(KeyMouse::Mouse(MouseButton::Left)),
216            GameInput::Secondary => return Some(KeyMouse::Mouse(MouseButton::Right)),
217            GameInput::Block => Key::Named(NamedKey::Alt),
218            GameInput::ToggleCursor => char(","),
219            GameInput::Escape => Key::Named(NamedKey::Escape),
220            GameInput::Chat => Key::Named(NamedKey::Enter),
221            GameInput::Command => char("/"),
222            GameInput::MoveForward => char("W"),
223            GameInput::MoveLeft => char("A"),
224            GameInput::MoveBack => char("S"),
225            GameInput::MoveRight => char("D"),
226            GameInput::Jump => Key::Named(NamedKey::Space),
227            GameInput::WallJump => Key::Named(NamedKey::Space),
228            GameInput::Sit => char("K"),
229            GameInput::Crawl => Key::Named(NamedKey::ArrowDown),
230            GameInput::Dance => char("J"),
231            GameInput::Greet => char("H"),
232            GameInput::Glide => Key::Named(NamedKey::Control),
233            GameInput::SwimUp => Key::Named(NamedKey::Space),
234            GameInput::SwimDown => Key::Named(NamedKey::Shift),
235            GameInput::Fly => char("H"),
236            GameInput::Sneak => Key::Named(NamedKey::Shift),
237            GameInput::CancelClimb => Key::Named(NamedKey::Shift),
238            GameInput::ToggleLantern => char("G"),
239            GameInput::Mount => char("F"),
240            GameInput::StayFollow => char("V"),
241            GameInput::Map => char("M"),
242            GameInput::Inventory => char("I"),
243            GameInput::Trade => char("T"),
244            GameInput::Social => char("O"),
245            GameInput::Crafting => char("C"),
246            GameInput::Diary => char("P"),
247            GameInput::Settings => Key::Named(NamedKey::F10),
248            GameInput::Controls => Key::Named(NamedKey::F1),
249            GameInput::ToggleInterface => Key::Named(NamedKey::F2),
250            GameInput::ToggleDebug => Key::Named(NamedKey::F3),
251            #[cfg(feature = "egui-ui")]
252            GameInput::ToggleEguiDebug => Key::Named(NamedKey::F7),
253            GameInput::ToggleChat => Key::Named(NamedKey::F5),
254            GameInput::Fullscreen => Key::Named(NamedKey::F11),
255            GameInput::Screenshot => Key::Named(NamedKey::F4),
256            GameInput::ToggleIngameUi => Key::Named(NamedKey::F6),
257            GameInput::Roll => return Some(MIDDLE_CLICK_KEY),
258            GameInput::GiveUp => Key::Named(NamedKey::Space),
259            GameInput::Respawn => Key::Named(NamedKey::Space),
260            GameInput::Interact => char("E"),
261            GameInput::ToggleWield => char("R"),
262            GameInput::FreeLook => char("L"),
263            GameInput::AutoWalk => char("."),
264            GameInput::ZoomIn => char(")"),
265            GameInput::ZoomOut => char("("),
266            GameInput::ZoomLock => return None,
267            GameInput::CameraClamp => char("'"),
268            GameInput::CycleCamera => char("0"),
269            GameInput::Slot1 => char("1"),
270            GameInput::Slot2 => char("2"),
271            GameInput::Slot3 => char("3"),
272            GameInput::Slot4 => char("4"),
273            GameInput::Slot5 => char("5"),
274            GameInput::Slot6 => char("6"),
275            GameInput::Slot7 => char("7"),
276            GameInput::Slot8 => char("8"),
277            GameInput::Slot9 => char("9"),
278            GameInput::Slot10 => char("Q"),
279            GameInput::NextSlot => return None,
280            GameInput::PreviousSlot => return None,
281            GameInput::CurrentSlot => return None,
282            GameInput::SwapLoadout => Key::Named(NamedKey::Tab),
283            GameInput::Select => char("X"),
284            GameInput::AcceptGroupInvite => char("Y"),
285            GameInput::DeclineGroupInvite => char("N"),
286            GameInput::MapZoomIn => char("+"),
287            GameInput::MapZoomOut => char("-"),
288            GameInput::MapSetMarker => return Some(KeyMouse::Mouse(MouseButton::Middle)),
289            GameInput::SpectateSpeedBoost => Key::Named(NamedKey::Control),
290            GameInput::SpectateViewpoint => return Some(KeyMouse::Mouse(MouseButton::Middle)),
291            GameInput::MuteMaster => Key::Named(NamedKey::AudioVolumeMute),
292            GameInput::MuteInactiveMaster => return None,
293            GameInput::MuteMusic => Key::Named(NamedKey::F8),
294            GameInput::MuteSfx => return None,
295            GameInput::MuteAmbience => return None,
296            GameInput::ToggleWalk => char("B"),
297        }))
298    }
299
300    pub fn default_menu_binding(menu_input: MenuInput) -> Option<KeyMouse> {
301        //let char = |s| Key::Character(winit::keyboard::SmolStr::new(s));
302
303        Some(KeyMouse::Key(match menu_input {
304            MenuInput::Up => Key::Named(NamedKey::ArrowUp),
305            MenuInput::Down => Key::Named(NamedKey::ArrowDown),
306            MenuInput::Left => Key::Named(NamedKey::ArrowLeft),
307            MenuInput::Right => Key::Named(NamedKey::ArrowRight),
308            MenuInput::ScrollUp => return None,
309            MenuInput::ScrollDown => return None,
310            MenuInput::ScrollLeft => return None,
311            MenuInput::ScrollRight => return None,
312            MenuInput::PageDown => return None, //char("Q"),
313            MenuInput::PageUp => return None,   //char("E"),
314            MenuInput::Apply => Key::Named(NamedKey::Enter),
315            MenuInput::Back => Key::Named(NamedKey::Escape),
316            MenuInput::Exit => return None,
317            MenuInput::LocalFocus => return None,
318            MenuInput::GlobalFocus => return None,
319            MenuInput::EmulateLeftClick => return None,
320            MenuInput::EmulateRightClick => return None,
321        }))
322    }
323}
324
325impl Default for ControlSettings {
326    fn default() -> Self {
327        let mut new_settings = Self {
328            keybindings: HashMap::new(),
329            inverse_keybindings: HashMap::new(),
330            menubindings: HashMap::new(),
331            inverse_menubindings: HashMap::new(),
332        };
333        // Sets the initial keybindings for those GameInputs.
334        for game_input in GameInput::iter() {
335            match ControlSettings::default_binding(game_input) {
336                None => {},
337                Some(default) => new_settings.insert_binding(game_input, default),
338            };
339        }
340        // Sets the initial keybindings for the MenuInputs
341        for menu_input in MenuInput::iter() {
342            if let Some(default) = ControlSettings::default_menu_binding(menu_input) {
343                new_settings.insert_menu_binding(menu_input, default);
344            }
345        }
346        new_settings
347    }
348}