Skip to main content

dreamwell_engine/input/
binding.rs

1// VirtualKey + InputBinding — platform-independent key codes and action mappings.
2// VirtualKey abstracts over winit KeyCode, egui Key, browser KeyboardEvent.code, etc.
3// InputBinding maps VirtualKey → InputAction for rebindable controls.
4
5use std::collections::HashMap;
6
7use super::action::InputAction;
8
9/// Platform-independent key/button identifiers.
10/// Covers keyboard, mouse, and gamepad inputs.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum VirtualKey {
13    // Letters
14    A,
15    B,
16    C,
17    D,
18    E,
19    F,
20    G,
21    H,
22    I,
23    J,
24    K,
25    L,
26    M,
27    N,
28    O,
29    P,
30    Q,
31    R,
32    S,
33    T,
34    U,
35    V,
36    W,
37    X,
38    Y,
39    Z,
40
41    // Numbers
42    Num0,
43    Num1,
44    Num2,
45    Num3,
46    Num4,
47    Num5,
48    Num6,
49    Num7,
50    Num8,
51    Num9,
52
53    // Arrows
54    ArrowUp,
55    ArrowDown,
56    ArrowLeft,
57    ArrowRight,
58
59    // Modifiers
60    ShiftLeft,
61    ShiftRight,
62    CtrlLeft,
63    CtrlRight,
64    AltLeft,
65    AltRight,
66
67    // Common
68    Space,
69    Enter,
70    Escape,
71    Tab,
72    Backspace,
73    Delete,
74    Home,
75    End,
76    PageUp,
77    PageDown,
78    F1,
79    F2,
80    F3,
81    F4,
82    F5,
83    F6,
84    F7,
85    F8,
86    F9,
87    F10,
88    F11,
89    F12,
90
91    // Mouse
92    MouseLeft,
93    MouseRight,
94    MouseMiddle,
95    MouseButton4,
96    MouseButton5,
97
98    // Gamepad
99    GamepadA,
100    GamepadB,
101    GamepadX,
102    GamepadY,
103    GamepadLB,
104    GamepadRB,
105    GamepadLT,
106    GamepadRT,
107    GamepadStart,
108    GamepadSelect,
109    GamepadDpadUp,
110    GamepadDpadDown,
111    GamepadDpadLeft,
112    GamepadDpadRight,
113    GamepadLeftStickPress,
114    GamepadRightStickPress,
115}
116
117/// Rebindable input binding map. Maps VirtualKey → InputAction.
118/// Multiple keys can map to the same action, but each key maps to at most one action.
119/// If a single physical input should trigger context-dependent behavior (e.g., MouseLeft
120/// is PrimaryAction in UI but Attack in combat), resolve that at the game layer, not here.
121pub struct InputBindingMap {
122    bindings: HashMap<VirtualKey, InputAction>,
123}
124
125impl InputBindingMap {
126    pub fn new() -> Self {
127        Self {
128            bindings: HashMap::new(),
129        }
130    }
131
132    /// Create default WASD + mouse bindings for tile-based and 3D movement.
133    pub fn default_bindings() -> Self {
134        let mut map = Self::new();
135
136        // WASD movement
137        map.bind(VirtualKey::W, InputAction::MoveForward);
138        map.bind(VirtualKey::S, InputAction::MoveBackward);
139        map.bind(VirtualKey::A, InputAction::MoveLeft);
140        map.bind(VirtualKey::D, InputAction::MoveRight);
141
142        // Arrow keys (alternative movement)
143        map.bind(VirtualKey::ArrowUp, InputAction::MoveForward);
144        map.bind(VirtualKey::ArrowDown, InputAction::MoveBackward);
145        map.bind(VirtualKey::ArrowLeft, InputAction::MoveLeft);
146        map.bind(VirtualKey::ArrowRight, InputAction::MoveRight);
147
148        // Vertical movement (3D)
149        map.bind(VirtualKey::Space, InputAction::Jump);
150        map.bind(VirtualKey::ShiftLeft, InputAction::Sprint);
151
152        // Camera
153        map.bind(VirtualKey::MouseRight, InputAction::CameraRotate);
154        map.bind(VirtualKey::MouseMiddle, InputAction::CameraPan);
155
156        // Interaction
157        map.bind(VirtualKey::MouseLeft, InputAction::PrimaryAction);
158        map.bind(VirtualKey::E, InputAction::Interact);
159        map.bind(VirtualKey::Escape, InputAction::Cancel);
160        map.bind(VirtualKey::Enter, InputAction::Confirm);
161
162        // Combat (MouseLeft → PrimaryAction above; game layer derives Attack from
163        // PrimaryAction + combat context. Don't double-bind the same key.)
164        map.bind(VirtualKey::Q, InputAction::Block);
165
166        // UI
167        map.bind(VirtualKey::I, InputAction::OpenInventory);
168        map.bind(VirtualKey::M, InputAction::OpenMap);
169        map.bind(VirtualKey::T, InputAction::OpenChat);
170
171        // Quick slots (1-9)
172        map.bind(VirtualKey::Num1, InputAction::QuickSlot(0));
173        map.bind(VirtualKey::Num2, InputAction::QuickSlot(1));
174        map.bind(VirtualKey::Num3, InputAction::QuickSlot(2));
175        map.bind(VirtualKey::Num4, InputAction::QuickSlot(3));
176        map.bind(VirtualKey::Num5, InputAction::QuickSlot(4));
177        map.bind(VirtualKey::Num6, InputAction::QuickSlot(5));
178        map.bind(VirtualKey::Num7, InputAction::QuickSlot(6));
179        map.bind(VirtualKey::Num8, InputAction::QuickSlot(7));
180        map.bind(VirtualKey::Num9, InputAction::QuickSlot(8));
181
182        // System
183        map.bind(VirtualKey::F11, InputAction::ToggleFullscreen);
184        map.bind(VirtualKey::F12, InputAction::Screenshot);
185
186        // Gamepad
187        map.bind(VirtualKey::GamepadA, InputAction::Confirm);
188        map.bind(VirtualKey::GamepadB, InputAction::Cancel);
189        map.bind(VirtualKey::GamepadX, InputAction::Interact);
190        map.bind(VirtualKey::GamepadY, InputAction::OpenInventory);
191        map.bind(VirtualKey::GamepadRB, InputAction::Attack);
192        map.bind(VirtualKey::GamepadLB, InputAction::Block);
193        map.bind(VirtualKey::GamepadStart, InputAction::Pause);
194
195        map
196    }
197
198    /// Bind a key to an action.
199    pub fn bind(&mut self, key: VirtualKey, action: InputAction) {
200        self.bindings.insert(key, action);
201    }
202
203    /// Remove a binding.
204    pub fn unbind(&mut self, key: VirtualKey) {
205        self.bindings.remove(&key);
206    }
207
208    /// Look up the action for a key.
209    pub fn action_for(&self, key: VirtualKey) -> Option<InputAction> {
210        self.bindings.get(&key).copied()
211    }
212
213    /// Number of bindings.
214    pub fn len(&self) -> usize {
215        self.bindings.len()
216    }
217
218    /// Whether the map is empty.
219    pub fn is_empty(&self) -> bool {
220        self.bindings.is_empty()
221    }
222
223    /// All bindings as (key, action) pairs.
224    pub fn iter(&self) -> impl Iterator<Item = (&VirtualKey, &InputAction)> {
225        self.bindings.iter()
226    }
227}
228
229impl Default for InputBindingMap {
230    fn default() -> Self {
231        Self::default_bindings()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn default_bindings_has_wasd() {
241        let map = InputBindingMap::default_bindings();
242        assert_eq!(map.action_for(VirtualKey::W), Some(InputAction::MoveForward));
243        assert_eq!(map.action_for(VirtualKey::S), Some(InputAction::MoveBackward));
244        assert_eq!(map.action_for(VirtualKey::A), Some(InputAction::MoveLeft));
245        assert_eq!(map.action_for(VirtualKey::D), Some(InputAction::MoveRight));
246    }
247
248    #[test]
249    fn bind_and_unbind() {
250        let mut map = InputBindingMap::new();
251        map.bind(VirtualKey::X, InputAction::Attack);
252        assert_eq!(map.action_for(VirtualKey::X), Some(InputAction::Attack));
253
254        map.unbind(VirtualKey::X);
255        assert_eq!(map.action_for(VirtualKey::X), None);
256    }
257
258    #[test]
259    fn rebind_overwrites() {
260        let mut map = InputBindingMap::new();
261        map.bind(VirtualKey::Space, InputAction::Jump);
262        map.bind(VirtualKey::Space, InputAction::Dodge);
263        assert_eq!(map.action_for(VirtualKey::Space), Some(InputAction::Dodge));
264    }
265
266    #[test]
267    fn default_bindings_nonempty() {
268        let map = InputBindingMap::default_bindings();
269        assert!(!map.is_empty());
270        assert!(map.len() > 20);
271    }
272
273    #[test]
274    fn arrow_keys_bound() {
275        let map = InputBindingMap::default_bindings();
276        assert_eq!(map.action_for(VirtualKey::ArrowUp), Some(InputAction::MoveForward));
277        assert_eq!(map.action_for(VirtualKey::ArrowDown), Some(InputAction::MoveBackward));
278        assert_eq!(map.action_for(VirtualKey::ArrowLeft), Some(InputAction::MoveLeft));
279        assert_eq!(map.action_for(VirtualKey::ArrowRight), Some(InputAction::MoveRight));
280    }
281
282    #[test]
283    fn quick_slots_bound() {
284        let map = InputBindingMap::default_bindings();
285        assert_eq!(map.action_for(VirtualKey::Num1), Some(InputAction::QuickSlot(0)));
286        assert_eq!(map.action_for(VirtualKey::Num9), Some(InputAction::QuickSlot(8)));
287    }
288
289    #[test]
290    fn mouse_left_maps_to_primary_action() {
291        let map = InputBindingMap::default_bindings();
292        // MouseLeft must map to PrimaryAction, not be silently overwritten.
293        assert_eq!(map.action_for(VirtualKey::MouseLeft), Some(InputAction::PrimaryAction));
294    }
295
296    #[test]
297    fn gamepad_bindings() {
298        let map = InputBindingMap::default_bindings();
299        assert_eq!(map.action_for(VirtualKey::GamepadA), Some(InputAction::Confirm));
300        assert_eq!(map.action_for(VirtualKey::GamepadB), Some(InputAction::Cancel));
301        assert_eq!(map.action_for(VirtualKey::GamepadStart), Some(InputAction::Pause));
302    }
303}