winit-input-map 0.5.0

Input Map for Winit with gamepad support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#[cfg(feature = "mice-keyboard")]
use winit::{
    dpi::PhysicalPosition,
    event::*,
};
use crate::input_code::*;
use std::collections::HashMap;
use std::{cmp::Eq, hash::Hash};
#[cfg(not(feature = "glium-types"))]
type Vec2 = (f32, f32);
#[cfg(feature = "glium-types")]
type Vec2 = glium_types::vectors::Vec2;
fn v(a: f32, b: f32) -> Vec2 {
    #[cfg(not(feature = "glium-types"))]
    { (a, b) }
    #[cfg(feature = "glium-types")]
    { Vec2::new(a, b) }
}
/// A struct that handles all your input needs once you've hooked it up to winit and gilrs.
/// ```
/// use gilrs::Gilrs;
/// use winit::{event::*, application::*, window::*, event_loop::*};
/// use winit_input_map::*;
/// struct App {
///     window: Option<Window>,
///     input: InputMap<()>,
///     gilrs: Gilrs
/// }
/// impl ApplicationHandler for App {
///     fn resumed(&mut self, event_loop: &ActiveEventLoop) {
///         self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
///     }
///     fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
///         self.input.update_with_window_event(&event);
///         if let WindowEvent::CloseRequested = &event { event_loop.exit() }
///     }
///     fn device_event(&mut self, _: &ActiveEventLoop, id: DeviceId, event: DeviceEvent) {
///         self.input.update_with_device_event(id, &event);
///     }
///     fn about_to_wait(&mut self, _: &ActiveEventLoop) {
///         self.input.update_with_gilrs(&mut self.gilrs);
/// 
///         // put your code here
///
///         self.input.init();
///     }
/// }
/// ```
pub struct InputMap<F: Hash + Eq + Clone + Copy> {
    /// Stores what each input code is bound to and its previous press value
    pub binds: HashMap<InputCode, (f32, Vec<F>)>,
    /// f32 is current val, 1st bool is pressed and 2nd bool is released.
    action_val: HashMap<F, (f32, bool, bool)>,
    /// The mouse position
    #[cfg(feature = "mice-keyboard")]
    pub mouse_pos: Vec2,
    /// The last input event, even if it isn't in the binds. Useful for handling rebinding
    pub recently_pressed: Option<InputCode>,
    /// The text typed this loop
    pub text_typed: Option<String>,
    /// Since most values are from 0-1 reducing the mouse sensitivity will result in better
    /// consistancy
    #[cfg(feature = "mice-keyboard")]
    pub mouse_scale: f32,
    /// Since most values are from 0-1 reducing the scroll sensitivity will result in better
    /// consistancy
    #[cfg(feature = "mice-keyboard")]
    pub scroll_scale: f32,
    /// The minimum value something has to be at to count as being pressed. Values over 1 will
    /// result in regular buttons being unusable
    pub press_sensitivity: f32
}
impl<F: Hash + Eq + Copy> Default for InputMap<F> {
    fn default() -> Self {
        Self {
            #[cfg(feature = "mice-keyboard")]
            mouse_scale:        0.01,
            press_sensitivity:  0.5,
            #[cfg(feature = "mice-keyboard")]
            scroll_scale:       1.0,
            #[cfg(feature = "mice-keyboard")]
            mouse_pos:  v(0.0, 0.0),
            recently_pressed:  None,
            text_typed:        None,
            binds:      HashMap::<InputCode, (f32, Vec<F>)>::new(),
            action_val: HashMap::<F,     (f32, bool, bool)>::new()
        }
    }
}
impl<F: Hash + Eq + Copy> InputMap<F> {
    /// Create new input system. It's recommended to use the `input_map!` macro to reduce boilerplate
    /// and increase readability.
    /// ```
    /// use Action::*;
    /// use winit_input_map::*;
    /// use winit::keyboard::KeyCode;
    /// #[derive(Hash, PartialEq, Eq, Clone, Copy)]
    /// enum Action {
    ///     Forward,
    ///     Back,
    ///     Pos,
    ///     Neg
    /// }
    /// //doesnt have to be the same ordered as the enum.
    /// let input = InputMap::new(&[
    ///     (Forward, vec![KeyCode::KeyW.into()]),
    ///     (Pos,     vec![KeyCode::KeyA.into()]),
    ///     (Back,    vec![KeyCode::KeyS.into()]),
    ///     (Neg,     vec![KeyCode::KeyD.into()])
    /// ]);
    /// ```
    pub fn new(binds: &[(F, Vec<InputCode>)]) -> Self {
        let mut result = Self::default();
        for (i, binds) in binds {
            for bind in binds {
                result.mut_bind(*bind).push(*i);
            }
        }
        result.binds.shrink_to_fit();
        result
    }
    /// Use if you dont want to have any actions and binds. Will still have access to everything else.
    pub fn empty() -> InputMap<()> {
        InputMap::<()>::default()
    }
    /// Gets a mutable vector of what actions input_code is bound to
    pub fn mut_bind(&mut self, input_code: InputCode) -> &mut Vec<F> {
        let has_val = self.binds.contains_key(&input_code);
        &mut (if has_val { self.binds.get_mut(&input_code) } else {
            self.binds.insert(input_code, (0.0, vec![]));
            self.binds.get_mut(&input_code)
        }).unwrap().1
    }
    /// Updates the input map using a winit event. Make sure to call `input.init()` when your done with
    /// the input this loop.
    /// ```
    /// use winit::{event::*, window::Window, event_loop::EventLoop};
    /// use winit_input_map::InputMap;
    ///
    /// let mut event_loop = EventLoop::new().unwrap();
    /// event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
    /// let _window = Window::new(&event_loop).unwrap();
    ///
    /// let mut input = input_map!();
    ///
    /// event_loop.run(|event, target|{
    ///     input.update_with_winit(&event);
    ///     match &event{
    ///         Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => target.exit(),
    ///         Event::AboutToWait => input.init(),
    ///         _ => ()
    ///     }
    /// });
    /// ```
    #[cfg(feature = "mice-keyboard")]
    #[deprecated = "use `update_with_window_event` and `update_with_device_event`"]
    pub fn update_with_winit(&mut self, event: &Event<()>) {
        match event {
            Event::WindowEvent { event, .. } => self.update_with_window_event(event),
            Event::DeviceEvent { event, device_id, .. } => self.update_with_device_event(*device_id, event),
            _ => ()
        }
    }
    #[cfg(feature = "mice-keyboard")]
    pub fn update_with_device_event(&mut self, id: DeviceId, event: &DeviceEvent) {
        use base_input_codes::*;
        match event {
            DeviceEvent::MouseMotion { delta } => {
                let x = delta.0 as f32 * self.mouse_scale;
                let y = delta.1 as f32 * self.mouse_scale;
                self.modify_val(MouseMoveLeft.with_id(id),  |v| v + x.max(0.0));
                self.modify_val(MouseMoveRight.with_id(id), |v| v - x.min(0.0));
                self.modify_val(MouseMoveUp.with_id(id),    |v| v + y.max(0.0));
                self.modify_val(MouseMoveDown.with_id(id),  |v| v - y.min(0.0));
            },
            DeviceEvent::MouseWheel { delta } => self.update_scroll(*delta, id),
             _ => (),
        }
    }
    #[cfg(feature = "mice-keyboard")]
    pub fn update_with_window_event(&mut self, event: &WindowEvent) {
        match event {
            WindowEvent::CursorMoved { position, .. } => self.update_mouse(*position),
            WindowEvent::MouseWheel { delta, device_id, .. } => self.update_scroll(*delta, *device_id),
            WindowEvent::MouseInput { state, button, device_id } => self.update_buttons(state, *device_id, *button),
            WindowEvent::KeyboardInput { event, device_id, .. } => self.update_keys(*device_id, event),
            _ => ()
        }
    }
    #[cfg(feature = "gamepad")]
    pub fn update_with_gilrs(&mut self, gilrs: &mut gilrs::Gilrs) {
        while let Some(ev) = gilrs.next_event() {
            self.update_gamepad(ev);
        }
    }
    /// Makes the input map ready to recieve new events.
    pub fn init(&mut self) {
        #[cfg(feature = "mice-keyboard")]
        {
            use base_input_codes::*;
            for i in [MouseMoveLeft, MouseMoveRight,   
            MouseMoveUp, MouseMoveDown, MouseScrollUp,
            MouseScrollDown, MouseScrollLeft, 
            MouseScrollRight] {
                self.update_val(i.into(), 0.0);
            }
        }
        self.action_val.iter_mut().for_each(|(_, i)|
            *i = (i.0, false, false)

        );
        self.recently_pressed = None;
        self.text_typed = None;
    }
    #[cfg(feature = "mice-keyboard")]
    fn update_scroll(&mut self, delta: MouseScrollDelta, id: DeviceId) {
        use base_input_codes::*;
        let (x, y) = match delta {
        MouseScrollDelta::LineDelta(x, y) => (x, y),
            MouseScrollDelta::PixelDelta(PhysicalPosition { x, y }) => (x as f32, y as f32)
        };
        let (x, y) = (x * self.scroll_scale, y * self.scroll_scale);
        
        self.modify_val(MouseScrollUp.with_id(id),    |v| v + y.max(0.0));
        self.modify_val(MouseScrollDown.with_id(id),  |v| v - y.min(0.0));
        self.modify_val(MouseScrollLeft.with_id(id),  |v| v + x.max(0.0));
        self.modify_val(MouseScrollRight.with_id(id), |v| v - x.min(0.0));
    }
    #[cfg(feature = "mice-keyboard")]
    fn update_mouse(&mut self, position: PhysicalPosition<f64>) {
        self.mouse_pos = v(position.x as f32, position.y as f32);
    }
    #[cfg(feature = "mice-keyboard")]
    fn update_keys(&mut self, id: DeviceId, event: &KeyEvent) {
        let input_code: DeviceInput = event.physical_key.into();

        if let (Some(string), Some(new)) = (&mut self.text_typed, &event.text) {
            string.push_str(new);
        } else { self.text_typed = event.text.as_ref().map(|i| i.to_string()) }

        self.update_val(input_code.with_id(id), event.state.is_pressed().into());
    }
    #[cfg(feature = "mice-keyboard")]
    fn update_buttons(&mut self, state: &ElementState, id: DeviceId, button: MouseButton) {
        let input_code: DeviceInput = button.into();
        self.update_val(input_code.with_id(id), state.is_pressed().into());
    }
    /// updates provided input code
    fn update_val(&mut self, input_code: InputCode, val: f32) {
        let pressing = val >= self.press_sensitivity;
        if pressing && !input_code.is_any() { self.recently_pressed = Some(input_code) }

        let Some((bind_val, binds)) = self.binds.get(&input_code) else {
            if !input_code.is_any() {
                let any = input_code.set_any(); 
                if self.binds.contains_key(&any) {
                    self.modify_any_val(any, |_| val);
                }
            }
            return
        };
        
        let diff = val - bind_val; // change between current and last val
        for &action in binds {
            let pressed = self.pressing(action);
            let jpressed = !pressed && pressing;
            let released = pressed && !pressing;
            // fixes overriding other input bound to the same action
            let mut val = self.action_val(action) + diff;
            if val <= f32::EPSILON { val = 0.0 }
            self.action_val.insert(action, (val, jpressed, released));
        }
        
        self.binds.get_mut(&input_code).unwrap().0 = val;

        if !input_code.is_any() {
            let any = input_code.set_any(); 
            if self.binds.contains_key(&any) {
                self.modify_val(any, |v| v + diff);
            }
        }
    }
    fn modify_val<FN: Fn(f32) -> f32>(&mut self, input_code: InputCode, f: FN) {
        let Some((bind_val, binds)) = self.binds.get(&input_code) else {
            if f(0.0) >= self.press_sensitivity && !input_code.is_any() { self.recently_pressed = Some(input_code) }
            if !input_code.is_any() {
                let any = input_code.set_any(); 
                if self.binds.contains_key(&any) {
                    self.modify_any_val(any, f);
                }
            }
            return;
        };

        let val = f(*bind_val);
        let diff = val - *bind_val;
        for &action in binds {
            let pressing = val >= self.press_sensitivity;
            if pressing && !input_code.is_any() { self.recently_pressed = Some(input_code) }
            
            let pressed = self.pressing(action);
            let jpressed = pressing && !pressed;
            let released = !pressing && pressed;

            let val = self.action_val(action) + diff;
            self.action_val.insert(action, (val, jpressed, released));
        }
        
        self.binds.get_mut(&input_code).unwrap().0 = val;

        if !input_code.is_any() {
            let any = input_code.set_any(); 
            if self.binds.contains_key(&any) {
                self.modify_any_val(any, |v| v + diff);
            }
        }
    }
    fn modify_any_val<FN: Fn(f32) -> f32>(&mut self, input_code: InputCode, f: FN) {
        let Some((bind_val, binds)) = self.binds.get(&input_code) else {
            if input_code.is_any() { return }
            if f(0.0) >= self.press_sensitivity && !input_code.is_any() { self.recently_pressed = Some(input_code) }
            return;
        };

        let val = f(*bind_val);
        let diff = val - *bind_val;
        for &action in binds {
            let pressing = val >= self.press_sensitivity;
            if pressing && !input_code.is_any() { self.recently_pressed = Some(input_code) }
            
            let pressed = self.pressing(action);
            let jpressed = pressing && !pressed;
            let released = !pressing && pressed;

            let val = self.action_val(action) + diff;
            self.action_val.insert(action, (val, jpressed, released));
        }
        
        self.binds.get_mut(&input_code).unwrap().0 = val;
    }
    #[cfg(feature = "gamepad")]
    fn update_gamepad(&mut self, event: gilrs::Event) {
        let gilrs::Event { id, event, .. } = event;
        use crate::input_code::{axis_pos, axis_neg};
        use gilrs::ev::EventType;
        match event {
            EventType::ButtonChanged(b, v, _) => {
                let a: GamepadInput = b.into();
                self.update_val(a.with_id(id), v);
            },
            EventType::AxisChanged(b, v, _) => {
                let dir_pos = v.max(0.0);
                let dir_neg = (-v).max(0.0);
                let input_pos = axis_pos(b);
                let input_neg = axis_neg(b);

                self.update_val(input_pos.with_id(id), dir_pos);
                self.update_val(input_neg.with_id(id), dir_neg);
            },
            EventType::Disconnected => {
                // reset input

                use GamepadInput::*;
                for i in [LeftStickLeft, LeftStickRight, LeftStickUp, LeftStickDown, LeftStickPress,
                 RightStickLeft, RightStickRight, RightStickUp, RightStickDown,
                 RightStickPress, DPadLeft, DPadRight, DPadUp, DPadDown, LeftZ, RightZ,
                 South, East, North, West, LeftTrigger, LeftTrigger2, RightTrigger,
                 RightTrigger2,  Select, Start, Mode, Other].iter() {
                    self.update_val(i.with_id(id), 0.0);
                 }
            }
            _ => ()
        }
    }
    /// Checks if action is being pressed currently. same as `input.action_val(action) >=
    /// input.press_sensitivity`
    pub fn pressing(&self, action: F) -> bool {
        self.action_val(action) >= self.press_sensitivity
    }
    /// Checks how wheremuch action is being pressed. May be higher than 1 in the case of scroll wheels
    /// and mouse movement.
    pub fn action_val(&self, action: F) -> f32 {
        if let Some(&(v, _, _)) = self.action_val.get(&action) { v } else {  0.0  }
    }
    /// checks if action was just pressed
    pub fn pressed(&self, action: F) -> bool {
        if let Some(&(_, v, _)) = self.action_val.get(&action) { v } else { false }
    }
    /// checks if action was just released
    pub fn released(&self, action: F) -> bool {
        if let Some(&(_, _, v)) = self.action_val.get(&action) { v } else { false }
    }
    /// Returns f32 based on how much pos and neg are pressed. may return values higher than 1.0 in
    /// the case of mouse movement and scrolling. usefull for movement controls. for 2d values see
    /// `[dir]` and `[dir_max_len_1]`
    /// ```no_run
    /// let move_dir = input.axis(Neg, Pos);
    /// ```
    /// same as `input.action_val(pos) - input.action_val(neg)`
    pub fn axis(&self, pos: F, neg: F) -> f32 {
        self.action_val(pos) - self.action_val(neg)
    }
    /// Returns a vector based off of x and y axis. For movement controls see `dir_max_len_1`
    pub fn dir(&self, pos_x: F, neg_x: F, pos_y: F, neg_y: F) -> Vec2 {
        v(self.axis(pos_x, neg_x), self.axis(pos_y, neg_y))
    }
    /// Returns a vector based off of x and y axis with a maximum length of 1 (the same as a normalised
    /// vector). If this undesirable see `dir`
    pub fn dir_max_len_1(&self, pos_x: F, neg_x: F, pos_y: F, neg_y: F) -> Vec2 {
        let (x, y) = (self.axis(pos_x, neg_x), self.axis(pos_y, neg_y));
        // if lower than 1, set to 1. since x/1 = x, that means anything lower than 1 is left unchanged
        let length = (x*x + y*y).sqrt().max(1.0);
        v(x/length, y/length)
    }
}