winit_input_map/
input.rs

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