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/// Values are the current value of the action, if its pressed, if its released and the sub values
20/// that make up the current value.
21type ActionValue = (f32, bool, bool, Vec<(f32, Vec<f32>)>);
22/// Binds are the list of connected actions and its sub indices
23type BindHash<F> = Vec<(F, usize, usize)>;
24/// Binds are a list of actions and their bindings
25pub type Binds<F> = Vec<(F, Vec<Vec<InputCode>>)>;
26/// A struct that handles all your input needs once you've hooked it up to winit and gilrs.
27/// ```
28/// use gilrs::Gilrs;
29/// use winit::{event::*, application::*, window::*, event_loop::*};
30/// use winit_input_map::*;
31/// struct App {
32///     window: Option<Window>,
33///     input: InputMap<()>,
34///     gilrs: Gilrs
35/// }
36/// impl ApplicationHandler for App {
37///     fn resumed(&mut self, event_loop: &ActiveEventLoop) {
38///         self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
39///     }
40///     fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
41///         self.input.update_with_window_event(&event);
42///         if let WindowEvent::CloseRequested = &event { event_loop.exit() }
43///     }
44///     fn device_event(&mut self, _: &ActiveEventLoop, id: DeviceId, event: DeviceEvent) {
45///         self.input.update_with_device_event(id, &event);
46///     }
47///     fn about_to_wait(&mut self, _: &ActiveEventLoop) {
48///         self.input.update_with_gilrs(&mut self.gilrs);
49/// 
50///         // put your code here
51///
52///         self.input.init();
53///     }
54/// }
55/// ```
56pub struct InputMap<F: Hash + Copy> {
57    /// Stores what each input code previous press value and what action its bound to and its sub
58    /// indices 
59    bind_hash: HashMap<InputCode, BindHash<F>>,
60    /// f32s and each bound current val, 1st bool is pressed and 2nd bool is released.
61    action_val: HashMap<F, ActionValue>,
62    /// weather the window has focus and therefor, if it should recieve inputs
63    #[cfg(all(feature = "mice-keyboard", feature = "gamepad"))]
64    focus: bool,
65    /// The mouse position
66    #[cfg(feature = "mice-keyboard")]
67    pub mouse_pos: Vec2,
68    /// The last input event, even if it isn't in the binds. Useful for handling rebinding
69    pub recently_pressed: Option<InputCode>,
70    /// The text typed this loop
71    pub text_typed: Option<String>,
72    /// Since most values are from 0-1 reducing the mouse sensitivity will result in better
73    /// consistancy
74    #[cfg(feature = "mice-keyboard")]
75    pub mouse_scale: f32,
76    /// Since most values are from 0-1 reducing the scroll sensitivity will result in better
77    /// consistancy
78    #[cfg(feature = "mice-keyboard")]
79    pub scroll_scale: f32,
80    /// The minimum value something has to be at to count as being pressed. Values over 1 will
81    /// result in most buttons being unusable
82    pub press_sensitivity: f32
83}
84impl InputMap<()> { 
85    /// Use if you dont want to have any actions and binds. Will still have access to everything else.
86    pub fn empty() -> Self { Self::default() }
87}
88impl<F: Hash + Copy> Default for InputMap<F> {
89    fn default() -> Self {
90        Self {
91            press_sensitivity:  0.5,
92            #[cfg(feature = "mice-keyboard")]
93            mouse_scale:        0.02,
94            #[cfg(feature = "mice-keyboard")]
95            scroll_scale:       1.0,
96            #[cfg(feature = "mice-keyboard")]
97            mouse_pos:  v(0.0, 0.0),
98            recently_pressed:  None,
99            text_typed:        None,
100            bind_hash:  HashMap::<InputCode, BindHash<F>>::new(),
101            action_val: HashMap::<F, ActionValue>::new(),
102            #[cfg(all(feature = "mice-keyboard", feature = "gamepad"))]
103            focus: true,
104        }
105    }
106}
107impl<F: Hash + Copy + Eq> InputMap<F> {
108    /// Creates a new input system. Takes the action and a list of its associated binds. An action
109    /// will count as being pressed if any of the binds are pressed. A bind is a list of
110    /// `InputCode`s that need to all be pressed for the bind to count as being pressed.
111    ///
112    /// It's recommended to use the `input_map!` macro to reduce boilerplate
113    /// and increase readability.
114    /// ```
115    /// use Action::*;
116    /// use winit_input_map::*;
117    /// use winit::keyboard::KeyCode;
118    /// #[derive(Hash, PartialEq, Eq, Clone, Copy)]
119    /// enum Action {
120    ///     Forward,
121    ///     Back,
122    ///     Pos,
123    ///     Neg
124    /// }
125    /// // doesnt have to be the same ordered as the enum.
126    /// let input = InputMap::new(&vec![
127    ///     (Forward, vec![ vec![KeyCode::KeyW.into()] ]),
128    ///     (Pos,     vec![ vec![KeyCode::KeyA.into()] ]),
129    ///     (Back,    vec![ vec![KeyCode::KeyS.into()] ]),
130    ///     (Neg,     vec![ vec![KeyCode::KeyD.into()] ])
131    /// ]);
132    /// ```
133    pub fn new(binds: &Binds<F>) -> Self {
134        let mut result = Self::default();
135        result.add_binds(binds);
136        result
137    }
138    /// Takes binds and adds them to the currently existing map. The `binds!()` macro will help
139    /// reduce the boiler_plate of this function.
140    pub fn add_binds(&mut self, binds: &Binds<F>) {
141        for (action, binds) in binds {
142            for (bind_i, bind) in binds.iter().enumerate() {
143                self.action_val.entry(*action).or_default().3.push((0.0, vec![]));
144                for (code_i, code) in bind.iter().enumerate() {
145                    self.action_val.entry(*action).or_default().3[bind_i].1.push(0.0);
146                    self.bind_hash.entry(*code).or_default().push((*action, bind_i, code_i));
147                }
148            }
149        }
150        self.action_val.shrink_to_fit();
151        self.bind_hash.shrink_to_fit();
152    }
153    /// Removes all binds and then adds the inputed binds. The `binds!()` macro will help
154    /// reduce the boiler_plate of this function.
155    pub fn set_binds(&mut self, binds: &Binds<F>) {
156        self.bind_hash.clear();
157        self.add_binds(binds);
158    }
159    /// Returns the current binds of the InputMap, may not be in the same order as the inputed
160    /// binds.
161    pub fn get_binds(&self) -> Binds<F> {
162        let mut results = Vec::new();
163
164        for (input_code,  binds) in self.bind_hash.iter() {
165            for (action, binds_i, bind_i) in binds.iter() {
166                let action_i = if let Some(result) = results.iter().position(|(a, _)| a == action) { result }
167                else { results.push((*action, vec![])); results.len() - 1 };
168
169                let len = results[action_i].1.len();
170                if *binds_i >= len { results[action_i].1.append(&mut vec![vec![]; binds_i - len + 1]); }
171
172                let vec = &mut results[action_i].1[*binds_i];
173
174                let len = vec.len();
175                if *bind_i >= len { vec.append(&mut vec![*input_code; bind_i - len + 1]); }
176            }
177        }
178        results
179    }
180    /// Updates the input map using a winit event. Make sure to call `input.init()` when your done with
181    /// the input this loop.
182    /// ```no_run
183    /// use winit::{event::*, window::WindowAttributes, event_loop::EventLoop};
184    /// use winit_input_map::*;
185    ///
186    /// let mut event_loop = EventLoop::new().unwrap();
187    /// event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
188    /// let _window = event_loop.create_window(WindowAttributes::default()).unwrap();
189    ///
190    /// let mut input = input_map!();
191    ///
192    /// event_loop.run(|event, target|{
193    ///     input.update_with_winit(&event);
194    ///     match &event{
195    ///         Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => target.exit(),
196    ///         Event::AboutToWait => input.init(),
197    ///         _ => ()
198    ///     }
199    /// });
200    /// ```
201    #[cfg(feature = "mice-keyboard")]
202    #[deprecated = "use `update_with_window_event` and `update_with_device_event`"]
203    pub fn update_with_winit(&mut self, event: &Event<()>) {
204        match event {
205            Event::WindowEvent { event, .. } => self.update_with_window_event(event),
206            Event::DeviceEvent { event, device_id, .. } => self.update_with_device_event(*device_id, event),
207            _ => ()
208        }
209    }
210    #[cfg(feature = "mice-keyboard")]
211    pub fn update_with_device_event(&mut self, id: DeviceId, event: &DeviceEvent) {
212        use base_input_codes::*;
213        match event {
214            DeviceEvent::MouseMotion { delta } => {
215                let x = delta.0 as f32 * self.mouse_scale;
216                let y = delta.1 as f32 * self.mouse_scale;
217                self.modify_val(MouseMoveRight.with_id(id), |v| v + x.max(0.0));
218                self.modify_val(MouseMoveLeft .with_id(id), |v| v - x.min(0.0));
219                self.modify_val(MouseMoveDown .with_id(id), |v| v + y.max(0.0));
220                self.modify_val(MouseMoveUp   .with_id(id), |v| v - y.min(0.0));
221            },
222            DeviceEvent::MouseWheel { delta } => self.update_scroll(*delta, id),
223             _ => (),
224        }
225    }
226    #[cfg(feature = "mice-keyboard")]
227    pub fn update_with_window_event(&mut self, event: &WindowEvent) {
228        match event {
229            WindowEvent::CursorMoved { position, .. } => self.update_mouse(*position),
230            WindowEvent::MouseWheel { delta, device_id, .. } => self.update_scroll(*delta, *device_id),
231            WindowEvent::MouseInput { state, button, device_id } => self.update_buttons(state, *device_id, *button),
232            WindowEvent::KeyboardInput { event, device_id, .. } => self.update_keys(*device_id, event),
233            WindowEvent::Focused(false) => {
234                for val in self.action_val.values_mut() {
235                    val.3.iter_mut().for_each(|i| { i.0 = 0.0; i.1.iter_mut().for_each(|i| *i = 0.0) });
236                    val.0 = 0.0;
237                }
238                #[cfg(feature = "gamepad")] { self.focus = false; }
239            },
240            #[cfg(feature = "gamepad")]
241            WindowEvent::Focused(true) => self.focus = true,
242            _ => ()
243        }
244    }
245    #[cfg(feature = "gamepad")]
246    pub fn update_with_gilrs(&mut self, gilrs: &mut gilrs::Gilrs) {
247        while let Some(ev) = gilrs.next_event() {
248            if self.focus { self.update_gamepad(ev); }
249        }
250    }
251    /// Makes the input map ready to recieve new events.
252    pub fn init(&mut self) {
253        #[cfg(feature = "mice-keyboard")]
254        {
255            use base_input_codes::*;
256            for i in [MouseMoveLeft, MouseMoveRight,   
257            MouseMoveUp, MouseMoveDown, MouseScrollUp,
258            MouseScrollDown, MouseScrollLeft, 
259            MouseScrollRight] {
260                self.update_val(i.into(), 0.0);
261            }
262        }
263        self.action_val.values_mut().for_each(|(_, p, r, _)| (*p, *r) = (false, false));
264        self.recently_pressed = None;
265        self.text_typed = None;
266    }
267    #[cfg(feature = "mice-keyboard")]
268    fn update_scroll(&mut self, delta: MouseScrollDelta, id: DeviceId) {
269        use base_input_codes::*;
270        let (x, y) = match delta {
271        MouseScrollDelta::LineDelta(x, y) => (x, y),
272            MouseScrollDelta::PixelDelta(PhysicalPosition { x, y }) => (x as f32, y as f32)
273        };
274        let (x, y) = (x * self.scroll_scale, y * self.scroll_scale);
275        
276        self.modify_val(MouseScrollUp.with_id(id),    |v| v + y.max(0.0));
277        self.modify_val(MouseScrollDown.with_id(id),  |v| v - y.min(0.0));
278        self.modify_val(MouseScrollLeft.with_id(id),  |v| v + x.max(0.0));
279        self.modify_val(MouseScrollRight.with_id(id), |v| v - x.min(0.0));
280    }
281    #[cfg(feature = "mice-keyboard")]
282    fn update_mouse(&mut self, position: PhysicalPosition<f64>) {
283        self.mouse_pos = v(position.x as f32, position.y as f32);
284    }
285    #[cfg(feature = "mice-keyboard")]
286    fn update_keys(&mut self, id: DeviceId, event: &KeyEvent) {
287        let input_code: DeviceInput = event.physical_key.into();
288
289        if let (Some(string), Some(new)) = (&mut self.text_typed, &event.text) {
290            string.push_str(new);
291        } else { self.text_typed = event.text.as_ref().map(|i| i.to_string()) }
292
293        self.update_val(input_code.with_id(id), event.state.is_pressed().into());
294    }
295    #[cfg(feature = "mice-keyboard")]
296    fn update_buttons(&mut self, state: &ElementState, id: DeviceId, button: MouseButton) {
297        let input_code: DeviceInput = button.into();
298        self.update_val(input_code.with_id(id), state.is_pressed().into());
299    }
300    /// updates provided input code
301    fn update_val(&mut self, input_code: InputCode, val: f32) {
302        self.modify_val(input_code, |_| val);
303    }
304    fn modify_val<FN: Fn(f32) -> f32>(&mut self, input_code: InputCode, f: FN) {
305        self.modify_single_val(input_code, &f);
306        self.modify_single_val(input_code.set_any(), f);
307    }
308    /// doesnt update both generic ids and specified ids, use `update_val` or `modify_val` for that
309    fn modify_single_val<FN: Fn(f32) -> f32>(&mut self, input_code: InputCode, f: FN) {
310        let Some(binds) = self.bind_hash.get(&input_code) else {
311            if f(0.0) >= self.press_sensitivity && !input_code.is_any() { self.recently_pressed = Some(input_code) }
312            return;
313        };
314
315        for &(action, index, sub_index) in binds {
316            let (curr_val, pressing, releasing, sub_values) = &mut self.action_val.get_mut(&action).unwrap();
317            
318            let old_sub_sub_val = sub_values[index].1[sub_index];
319            let new_sub_sub_val = f(old_sub_sub_val);
320            let change = new_sub_sub_val / old_sub_sub_val;
321            sub_values[index].1[sub_index] = new_sub_sub_val;
322
323            let sub_value = sub_values[index].0;
324            let new_sub_val = if change.is_finite() { sub_value * change }
325                else { sub_values[index].1.iter().fold(1.0, |a, b| a * b) };
326            sub_values[index].0 = new_sub_val;
327
328            *curr_val += new_sub_val - sub_value;
329
330            let now_pressing = *curr_val >= self.press_sensitivity;
331            if now_pressing && !input_code.is_any() { self.recently_pressed = Some(input_code) }
332
333            let jpressed = now_pressing && !*pressing;
334            let released = !now_pressing && *pressing;
335            *pressing = jpressed;
336            *releasing = released;
337        }
338    }
339    #[cfg(feature = "gamepad")]
340    fn update_gamepad(&mut self, event: gilrs::Event) {
341        let gilrs::Event { id, event, .. } = event;
342        use crate::input_code::{axis_pos, axis_neg};
343        use gilrs::ev::EventType;
344        match event {
345            EventType::ButtonChanged(b, v, _) => {
346                let a: GamepadInput = b.into();
347                self.update_val(a.with_id(id), v);
348            },
349            EventType::AxisChanged(b, v, _) => {
350                let dir_pos = v.max(0.0);
351                let dir_neg = (-v).max(0.0);
352                let input_pos = axis_pos(b);
353                let input_neg = axis_neg(b);
354
355                self.update_val(input_pos.with_id(id), dir_pos);
356                self.update_val(input_neg.with_id(id), dir_neg);
357            },
358            EventType::Disconnected => {
359                // reset input
360
361                use GamepadInput::*;
362                for i in [LeftStickLeft, LeftStickRight, LeftStickUp, LeftStickDown, LeftStickPress,
363                 RightStickLeft, RightStickRight, RightStickUp, RightStickDown,
364                 RightStickPress, DPadLeft, DPadRight, DPadUp, DPadDown, LeftZ, RightZ,
365                 South, East, North, West, LeftBumper, LeftTrigger, RightBumper,
366                 RightTrigger,  Select, Start, Mode, Other].iter() {
367                    self.update_val(i.with_id(id), 0.0);
368                 }
369            }
370            _ => ()
371        }
372    }
373    /// Checks if action is being pressed currently based on the `press_sensitivity`.
374    /// same as `self.value(action) >= self.press_sensitivty`.
375    pub fn pressing(&self, action: F) -> bool {
376        self.value(action) >= self.press_sensitivity
377    }
378    /// Checks how much an action is being pressed. May be higher than 1 in the case of scroll
379    /// wheels, mouse movement or when multiple binds are bound to an action.
380    pub fn value(&self, action: F) -> f32 {
381        if let Some(&(v, _, _, _)) = self.action_val.get(&action) { v } else {  0.0  }
382    }
383    /// Checks if action was just pressed.
384    pub fn pressed(&self, action: F) -> bool {
385        if let Some(&(_, v, _, _)) = self.action_val.get(&action) { v } else { false }
386    }
387    /// Checks if action was just released.
388    pub fn released(&self, action: F) -> bool {
389        if let Some(&(_, _, v, _)) = self.action_val.get(&action) { v } else { false }
390    }
391    /// Returns f32 based on how much pos and neg are pressed. may return values higher than 1.0 in
392    /// the case of mouse movement and scrolling. usefull for movement controls. for 2d values see
393    /// `dir` and `dir_max_len_1`
394    /// ```no_test
395    /// let move_dir = input.axis(Neg, Pos);
396    /// ```
397    /// same as `input.value(pos) - input.value(neg)`
398    pub fn axis(&self, pos: F, neg: F) -> f32 {
399        self.value(pos) - self.value(neg)
400    }
401    /// Returns a vector based off of the x and y axis. Can return values with a length higher than
402    /// 1, if this is undesirable 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}
415/*#[cfg(test)]
416mod tests {
417    use super::*;
418    use test::Bencher;
419    extern crate test;
420    #[bench]
421    fn bench_input(b: &mut Bencher) {
422        use Action::*;
423        #[derive(PartialEq, Eq, Clone, Copy, Hash)]
424        enum Action {
425            Test1,
426            Test2,
427            Test3,
428            Test4,
429        }
430        let mut input = { use base_input_codes::*; crate::input_map!(
431            (Test1, KeyI, (ControlLeft, KeyZ), (ShiftLeft, KeyZ)),
432            (Test2, (ControlLeft, KeyS), KeyZ, KeyU),
433            (Test3, (KeyX, KeyI), (KeyS, KeyV, ControlLeft, KeyZ)),
434            (Test4, KeyZ)
435        ) };
436        
437        b.iter(|| {
438            input.update_val(base_input_codes::KeyZ.into(), 1.0);
439            input.update_val(base_input_codes::ControlLeft.into(), 1.0);
440        });
441    }
442}*/