veloren_voxygen/settings/
control.rs

1use crate::{game_input::GameInput, window::KeyMouse};
2use hashbrown::{HashMap, HashSet};
3use serde::{Deserialize, Serialize};
4use strum::IntoEnumIterator;
5use winit::{
6    event::MouseButton,
7    keyboard::{Key, NamedKey},
8};
9
10// ControlSetting-like struct used by Serde, to handle not serializing/building
11// post-deserializing the inverse_keybindings hashmap
12#[derive(Serialize, Deserialize)]
13struct ControlSettingsSerde {
14    keybindings: HashMap<GameInput, Option<KeyMouse>>,
15}
16
17impl From<ControlSettings> for ControlSettingsSerde {
18    fn from(control_settings: ControlSettings) -> Self {
19        let mut user_bindings: HashMap<GameInput, Option<KeyMouse>> = HashMap::new();
20        // Do a delta between default() ControlSettings and the argument, and let
21        // keybindings be only the custom keybindings chosen by the user.
22        for (k, v) in control_settings.keybindings {
23            if ControlSettings::default_binding(k) != v {
24                // Keybinding chosen by the user
25                user_bindings.insert(k, v);
26            }
27        }
28        ControlSettingsSerde {
29            keybindings: user_bindings,
30        }
31    }
32}
33
34/// `ControlSettings` contains keybindings.
35#[derive(Clone, Debug, Serialize, Deserialize)]
36#[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")]
37pub struct ControlSettings {
38    pub keybindings: HashMap<GameInput, Option<KeyMouse>>,
39    pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, // used in event loop
40}
41
42impl From<ControlSettingsSerde> for ControlSettings {
43    fn from(control_serde: ControlSettingsSerde) -> Self {
44        let user_keybindings = control_serde.keybindings;
45        let mut control_settings = ControlSettings::default();
46        for (k, maybe_v) in user_keybindings {
47            match maybe_v {
48                Some(v) => control_settings.modify_binding(k, v),
49                None => control_settings.remove_binding(k),
50            }
51        }
52        control_settings
53    }
54}
55
56/// Since Macbook trackpads lack middle click, on OS X we default to Shift
57/// instead. It is an imperfect heuristic, but hopefully it will be a slightly
58/// better default, and the two places we default to middle click currently
59/// (roll and wall jump) are both situations where you cannot glide (the other
60/// default mapping for Shift).
61#[cfg(target_os = "macos")]
62const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(Key::Named(NamedKey::Shift));
63#[cfg(not(target_os = "macos"))]
64const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle);
65
66impl ControlSettings {
67    pub fn remove_binding(&mut self, game_input: GameInput) {
68        if let Some(inverse) = self
69            .keybindings
70            .insert(game_input, None)
71            .flatten()
72            .and_then(|key_mouse| self.inverse_keybindings.get_mut(&key_mouse))
73        {
74            inverse.remove(&game_input);
75        }
76    }
77
78    pub fn get_binding(&self, game_input: GameInput) -> Option<KeyMouse> {
79        self.keybindings.get(&game_input).cloned().flatten()
80    }
81
82    pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<GameInput>> {
83        self.inverse_keybindings.get(key_mouse)
84    }
85
86    pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
87        self.keybindings.insert(game_input, Some(key_mouse.clone()));
88        self.inverse_keybindings
89            .entry(key_mouse)
90            .or_default()
91            .insert(game_input);
92    }
93
94    pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
95        // For the KeyMouse->GameInput hashmap, we first need to remove the GameInput
96        // from the old binding
97        if let Some(old_binding) = self.get_binding(game_input) {
98            self.inverse_keybindings
99                .entry(old_binding)
100                .or_default()
101                .remove(&game_input);
102        }
103        // then we add the GameInput to the proper key
104        self.inverse_keybindings
105            .entry(key_mouse.clone())
106            .or_default()
107            .insert(game_input);
108        // For the GameInput->KeyMouse hashmap, just overwrite the value
109        self.keybindings.insert(game_input, Some(key_mouse));
110    }
111
112    /// Return true if this key is used for multiple GameInputs that aren't
113    /// expected to be safe to have bound to the same key at the same time
114    pub fn has_conflicting_bindings(&self, key_mouse: KeyMouse) -> bool {
115        if let Some(game_inputs) = self.inverse_keybindings.get(&key_mouse) {
116            for a in game_inputs.iter() {
117                for b in game_inputs.iter() {
118                    if !GameInput::can_share_bindings(*a, *b) {
119                        return true;
120                    }
121                }
122            }
123        }
124        false
125    }
126
127    pub fn default_binding(game_input: GameInput) -> Option<KeyMouse> {
128        let char = |s| Key::Character(winit::keyboard::SmolStr::new(s));
129
130        // If a new GameInput is added, be sure to update GameInput::iterator() too!
131        Some(KeyMouse::Key(match game_input {
132            GameInput::Primary => return Some(KeyMouse::Mouse(MouseButton::Left)),
133            GameInput::Secondary => return Some(KeyMouse::Mouse(MouseButton::Right)),
134            GameInput::Block => Key::Named(NamedKey::Alt),
135            GameInput::ToggleCursor => char(","),
136            GameInput::Escape => Key::Named(NamedKey::Escape),
137            GameInput::Chat => Key::Named(NamedKey::Enter),
138            GameInput::Command => char("/"),
139            GameInput::MoveForward => char("W"),
140            GameInput::MoveLeft => char("A"),
141            GameInput::MoveBack => char("S"),
142            GameInput::MoveRight => char("D"),
143            GameInput::Jump => Key::Named(NamedKey::Space),
144            GameInput::WallJump => Key::Named(NamedKey::Space),
145            GameInput::Sit => char("K"),
146            GameInput::Crawl => Key::Named(NamedKey::ArrowDown),
147            GameInput::Dance => char("J"),
148            GameInput::Greet => char("H"),
149            GameInput::Glide => Key::Named(NamedKey::Control),
150            GameInput::SwimUp => Key::Named(NamedKey::Space),
151            GameInput::SwimDown => Key::Named(NamedKey::Shift),
152            GameInput::Fly => char("H"),
153            GameInput::Sneak => Key::Named(NamedKey::Shift),
154            GameInput::CancelClimb => Key::Named(NamedKey::Shift),
155            GameInput::ToggleLantern => char("G"),
156            GameInput::Mount => char("F"),
157            GameInput::StayFollow => char("V"),
158            GameInput::Map => char("M"),
159            GameInput::Inventory => char("I"),
160            GameInput::Trade => char("T"),
161            GameInput::Social => char("O"),
162            GameInput::Crafting => char("C"),
163            GameInput::Diary => char("P"),
164            GameInput::Settings => Key::Named(NamedKey::F10),
165            GameInput::Controls => Key::Named(NamedKey::F1),
166            GameInput::ToggleInterface => Key::Named(NamedKey::F2),
167            GameInput::ToggleDebug => Key::Named(NamedKey::F3),
168            #[cfg(feature = "egui-ui")]
169            GameInput::ToggleEguiDebug => Key::Named(NamedKey::F7),
170            GameInput::ToggleChat => Key::Named(NamedKey::F5),
171            GameInput::Fullscreen => Key::Named(NamedKey::F11),
172            GameInput::Screenshot => Key::Named(NamedKey::F4),
173            GameInput::ToggleIngameUi => Key::Named(NamedKey::F6),
174            GameInput::Roll => return Some(MIDDLE_CLICK_KEY),
175            GameInput::GiveUp => Key::Named(NamedKey::Space),
176            GameInput::Respawn => Key::Named(NamedKey::Space),
177            GameInput::Interact => char("E"),
178            GameInput::ToggleWield => char("R"),
179            GameInput::FreeLook => char("L"),
180            GameInput::AutoWalk => char("."),
181            GameInput::ZoomIn => char(")"),
182            GameInput::ZoomOut => char("("),
183            GameInput::ZoomLock => return None,
184            GameInput::CameraClamp => char("'"),
185            GameInput::CycleCamera => char("0"),
186            GameInput::Slot1 => char("1"),
187            GameInput::Slot2 => char("2"),
188            GameInput::Slot3 => char("3"),
189            GameInput::Slot4 => char("4"),
190            GameInput::Slot5 => char("5"),
191            GameInput::Slot6 => char("6"),
192            GameInput::Slot7 => char("7"),
193            GameInput::Slot8 => char("8"),
194            GameInput::Slot9 => char("9"),
195            GameInput::Slot10 => char("Q"),
196            GameInput::SwapLoadout => Key::Named(NamedKey::Tab),
197            GameInput::Select => char("X"),
198            GameInput::AcceptGroupInvite => char("Y"),
199            GameInput::DeclineGroupInvite => char("N"),
200            GameInput::MapZoomIn => char("+"),
201            GameInput::MapZoomOut => char("-"),
202            GameInput::MapSetMarker => return Some(KeyMouse::Mouse(MouseButton::Middle)),
203            GameInput::SpectateSpeedBoost => Key::Named(NamedKey::Control),
204            GameInput::SpectateViewpoint => return Some(KeyMouse::Mouse(MouseButton::Middle)),
205            GameInput::MuteMaster => Key::Named(NamedKey::AudioVolumeMute),
206            GameInput::MuteInactiveMaster => return None,
207            GameInput::MuteMusic => Key::Named(NamedKey::F8),
208            GameInput::MuteSfx => return None,
209            GameInput::MuteAmbience => return None,
210            GameInput::ToggleWalk => char("B"),
211        }))
212    }
213}
214
215impl Default for ControlSettings {
216    fn default() -> Self {
217        let mut new_settings = Self {
218            keybindings: HashMap::new(),
219            inverse_keybindings: HashMap::new(),
220        };
221        // Sets the initial keybindings for those GameInputs.
222        for game_input in GameInput::iter() {
223            match ControlSettings::default_binding(game_input) {
224                None => {},
225                Some(default) => new_settings.insert_binding(game_input, default),
226            };
227        }
228        new_settings
229    }
230}