bevy_jakput_manager/
input_manager.rs

1use std::{fmt::Debug, hash::Hash, marker::PhantomData};
2
3#[cfg(feature = "serialize")]
4use std::{
5    fs::{File, read_to_string},
6    io::Write,
7};
8
9use bevy::{
10    input::{
11        ButtonState, InputSystems,
12        gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent},
13        keyboard::{Key, KeyboardInput},
14        mouse::{MouseButtonInput, MouseMotion, MouseWheel},
15    },
16    platform::collections::{HashMap, HashSet},
17    prelude::*,
18};
19
20#[cfg(feature = "serialize")]
21use ron::ser::PrettyConfig;
22#[cfg(feature = "serialize")]
23use serde::{Deserialize, Serialize, de::DeserializeOwned};
24
25#[cfg(feature = "serialize")]
26use crate::error::Error;
27
28#[cfg_attr(not(feature = "serialize"), derive(Hash, Eq, PartialEq, Clone, Debug))]
29#[cfg_attr(
30    feature = "serialize",
31    derive(Hash, Eq, PartialEq, Clone, Debug, Serialize, Deserialize)
32)]
33pub enum SimpleAxis {
34    RightStickUp,
35    RightStickRight,
36    RightStickDown,
37    RightStickLeft,
38    LeftStickUp,
39    LeftStickRight,
40    LeftStickDown,
41    LeftStickLeft,
42    RightTrigger,
43    LeftTrigger,
44}
45
46#[cfg_attr(not(feature = "serialize"), derive(Hash, Eq, PartialEq, Clone, Debug))]
47#[cfg_attr(
48    feature = "serialize",
49    derive(Hash, Eq, PartialEq, Clone, Debug, Serialize, Deserialize)
50)]
51pub enum SimpleScroll {
52    Up,
53    Down,
54}
55
56#[cfg_attr(not(feature = "serialize"), derive(Hash, Eq, PartialEq, Clone, Debug))]
57#[cfg_attr(
58    feature = "serialize",
59    derive(Hash, Eq, PartialEq, Clone, Debug, Serialize, Deserialize)
60)]
61pub enum ActionInput {
62    Key(bevy::input::keyboard::Key),
63    KeyCode(bevy::input::keyboard::KeyCode),
64    MouseButton(bevy::input::mouse::MouseButton),
65    GamepadButton(bevy::input::gamepad::GamepadButton),
66    GamepadAxis(SimpleAxis),
67    Scroll(SimpleScroll),
68}
69
70impl From<KeyCode> for ActionInput {
71    fn from(value: KeyCode) -> Self {
72        ActionInput::KeyCode(value)
73    }
74}
75
76impl From<MouseButton> for ActionInput {
77    fn from(value: MouseButton) -> Self {
78        ActionInput::MouseButton(value)
79    }
80}
81
82impl From<GamepadButton> for ActionInput {
83    fn from(value: GamepadButton) -> Self {
84        ActionInput::GamepadButton(value)
85    }
86}
87
88impl From<Key> for ActionInput {
89    fn from(value: Key) -> Self {
90        ActionInput::Key(value)
91    }
92}
93
94impl From<SimpleAxis> for ActionInput {
95    fn from(value: SimpleAxis) -> Self {
96        ActionInput::GamepadAxis(value)
97    }
98}
99
100impl From<SimpleScroll> for ActionInput {
101    fn from(value: SimpleScroll) -> Self {
102        ActionInput::Scroll(value)
103    }
104}
105
106impl ActionInput {
107    pub fn is_gamepad(&self) -> bool {
108        matches!(
109            self,
110            ActionInput::GamepadButton(_) | ActionInput::GamepadAxis(_)
111        )
112    }
113
114    pub fn is_key(&self) -> bool {
115        matches!(self, ActionInput::Key(_))
116    }
117
118    pub fn is_key_code(&self) -> bool {
119        matches!(self, ActionInput::KeyCode(_))
120    }
121}
122
123#[cfg(not(feature = "serialize"))]
124pub trait InputConfig:
125    Debug + Eq + Hash + Clone + Reflect + TypePath + FromReflect + Send + Sync + 'static
126{
127    fn get_config() -> HashMap<ActionInput, HashSet<Self>>;
128
129    fn get_threshold() -> f32;
130}
131
132#[cfg(feature = "serialize")]
133#[allow(clippy::type_complexity)]
134pub trait InputConfig:
135    Debug + Eq + Hash + Clone + Reflect + TypePath + FromReflect + Send + Sync + 'static
136{
137    fn get_config() -> HashMap<ActionInput, HashSet<Self>>;
138
139    fn get_threshold() -> f32;
140
141    fn get_config_file() -> &'static str;
142}
143
144pub struct InputManagerPlugin<C> {
145    _phantom: PhantomData<C>,
146}
147
148#[cfg(not(feature = "serialize"))]
149impl<C: InputConfig> Default for InputManagerPlugin<C> {
150    fn default() -> Self {
151        Self {
152            _phantom: PhantomData,
153        }
154    }
155}
156
157#[cfg(feature = "serialize")]
158impl<C: InputConfig + DeserializeOwned> Default for InputManagerPlugin<C> {
159    fn default() -> Self {
160        Self {
161            _phantom: PhantomData,
162        }
163    }
164}
165
166#[derive(Message)]
167pub struct InputTypeChanged;
168
169#[cfg(not(feature = "serialize"))]
170impl<C: InputConfig> Plugin for InputManagerPlugin<C> {
171    fn build(&self, app: &mut App) {
172        app.init_resource::<InputManager<C>>()
173            .add_message::<InputTypeChanged>()
174            .add_systems(PreUpdate, handle_input::<C>.after(InputSystems))
175            .add_systems(First, clear::<C>);
176    }
177}
178
179#[cfg(feature = "serialize")]
180impl<C: InputConfig + DeserializeOwned> Plugin for InputManagerPlugin<C> {
181    fn build(&self, app: &mut App) {
182        app.init_resource::<InputManager<C>>()
183            .add_message::<InputTypeChanged>()
184            .add_systems(PreUpdate, handle_input::<C>.after(InputSystems))
185            .add_systems(First, clear::<C>);
186    }
187}
188
189#[derive(Clone, Copy, Eq, PartialEq)]
190pub enum InputType {
191    Gamepad,
192    Keyboard,
193}
194
195#[derive(Clone, Copy, Eq, PartialEq, Debug)]
196pub enum MouseInput {
197    Button(MouseButton),
198    ScrollUp,
199    ScrollDown,
200}
201
202struct MouseState {
203    pos: Vec2,
204    delta: Vec2,
205    scroll: Vec2,
206}
207
208#[allow(dead_code)]
209impl MouseState {
210    fn new() -> Self {
211        Self {
212            pos: Vec2::ZERO,
213            delta: Vec2::ZERO,
214            scroll: Vec2::ZERO,
215        }
216    }
217
218    fn get_scroll(&self) -> Vec2 {
219        self.scroll
220    }
221
222    fn delta(&mut self, delta: Vec2) {
223        self.delta += delta;
224    }
225
226    fn set_pos(&mut self, pos: Vec2) {
227        self.pos = pos;
228    }
229
230    fn set_scroll(&mut self, scroll: Vec2) {
231        self.scroll += scroll;
232    }
233
234    fn clear(&mut self) {
235        self.scroll = Vec2::ZERO;
236        self.delta = Vec2::ZERO;
237    }
238}
239
240#[derive(Resource)]
241pub struct InputManager<C> {
242    input: HashMap<ActionInput, HashSet<C>>,
243    just_pressed: HashSet<C>,
244    just_released: HashSet<C>,
245    pressed: HashMap<C, f32>,
246    mouse: MouseState,
247    input_type: InputType,
248    axis_threshold: f32,
249    inputs_just_pressed: Vec<ActionInput>,
250}
251
252#[cfg(not(feature = "serialize"))]
253impl<C: InputConfig> Default for InputManager<C> {
254    fn default() -> Self {
255        let input = C::get_config();
256
257        let threshold = C::get_threshold();
258        Self {
259            input,
260            just_pressed: HashSet::new(),
261            just_released: HashSet::new(),
262            pressed: HashMap::new(),
263            mouse: MouseState::new(),
264            input_type: InputType::Keyboard,
265            axis_threshold: threshold,
266            inputs_just_pressed: Vec::new(),
267        }
268    }
269}
270
271#[cfg(feature = "serialize")]
272impl<C: InputConfig + DeserializeOwned> Default for InputManager<C> {
273    fn default() -> Self {
274        let input = if let Ok(string) = read_to_string(C::get_config_file()) {
275            ron::from_str::<HashMap<ActionInput, HashSet<C>>>(&string).unwrap()
276        } else {
277            C::get_config()
278        };
279
280        let threshold = C::get_threshold();
281        Self {
282            input,
283            just_pressed: HashSet::new(),
284            just_released: HashSet::new(),
285            pressed: HashMap::new(),
286            mouse: MouseState::new(),
287            input_type: InputType::Keyboard,
288            axis_threshold: threshold,
289            inputs_just_pressed: Vec::new(),
290        }
291    }
292}
293
294impl<C: InputConfig> InputManager<C> {
295    fn mouse_delta(&mut self, delta: Vec2) {
296        self.mouse.delta(delta);
297    }
298
299    fn mouse_pos(&mut self, pos: Vec2) {
300        self.mouse.set_pos(pos);
301    }
302
303    fn scroll(&mut self, scroll: Vec2) {
304        self.mouse.set_scroll(scroll);
305    }
306
307    pub fn pressed(&self, action: C) -> bool {
308        self.pressed.contains_key(&action)
309    }
310
311    pub fn just_pressed(&self, action: C) -> bool {
312        self.just_pressed.contains(&action)
313    }
314
315    fn press(&mut self, action: C, value: f32) {
316        if self.pressed.insert(action.clone(), value).is_none() {
317            self.just_pressed.insert(action);
318        }
319    }
320
321    /// For inputs that can only be pressed and cannot be released
322    /// like the scroll wheel
323    fn just_press(&mut self, action: C) {
324        self.just_pressed.insert(action);
325    }
326
327    fn release(&mut self, action: C) {
328        if self.pressed.remove(&action).is_some() {
329            self.just_released.insert(action.clone());
330        }
331    }
332
333    pub fn just_released(&self, action: C) -> bool {
334        self.just_released.contains(&action)
335    }
336
337    pub fn clear(&mut self) {
338        self.just_pressed.clear();
339        self.just_released.clear();
340        self.mouse.clear();
341        self.inputs_just_pressed.clear();
342    }
343
344    pub fn get_action_strength(&self, action: C) -> Option<f32> {
345        self.pressed.get(&action).cloned()
346    }
347
348    pub fn get_strength(&self, pos_x: C, neg_x: C, pos_y: C, neg_y: C) -> Vec2 {
349        let mut strength = Vec2::ZERO;
350        strength.x += self.get_action_strength(pos_x).unwrap_or_default()
351            - self.get_action_strength(neg_x).unwrap_or_default();
352        strength.y += self.get_action_strength(pos_y).unwrap_or_default()
353            - self.get_action_strength(neg_y).unwrap_or_default();
354        strength
355    }
356
357    pub fn get_mouse_delta(&self) -> Vec2 {
358        self.mouse.delta
359    }
360
361    pub fn is_gamepad(&self) -> bool {
362        self.input_type == InputType::Gamepad
363    }
364
365    pub fn change_input_type(&mut self, input_type: InputType) {
366        self.input_type = input_type;
367    }
368
369    pub fn get_action_inputs(&self, action: C) -> Vec<ActionInput> {
370        self.input
371            .iter()
372            .filter(|(_, value)| value.contains(&action))
373            .map(|(key, _)| key.clone())
374            .collect()
375    }
376
377    pub fn map_input_action(&mut self, input: ActionInput, action: C) {
378        self.input.entry(input).or_default().insert(action);
379    }
380
381    pub fn clear_action(&mut self, action: C) {
382        self.get_action_inputs(action.clone())
383            .iter()
384            .for_each(|input| {
385                self.input.entry(input.clone()).or_default().remove(&action);
386            });
387    }
388
389    fn add_input(&mut self, input: ActionInput) {
390        self.inputs_just_pressed.push(input);
391    }
392
393    pub fn get_inputs_just_pressed(&self) -> Vec<ActionInput> {
394        self.inputs_just_pressed.clone()
395    }
396
397    pub fn default_input(&mut self) {
398        self.input = C::get_config();
399    }
400
401    pub fn set_threshold(&mut self, threshold: f32) {
402        self.axis_threshold = threshold;
403    }
404}
405
406#[cfg(feature = "serialize")]
407impl<C: InputConfig + Serialize> InputManager<C> {
408    pub fn write_config(&self) -> Result<(), Error> {
409        let toml = ron::ser::to_string_pretty(&self.input, PrettyConfig::default()).unwrap();
410        let mut file = File::create(C::get_config_file())?;
411        file.write_all(toml.as_bytes())?;
412        Ok(())
413    }
414}
415
416#[allow(clippy::too_many_arguments)]
417fn handle_input<C: InputConfig>(
418    mut keyboard: MessageReader<KeyboardInput>,
419    mut mouse: MessageReader<MouseButtonInput>,
420    mut gamepad: MessageReader<GamepadButtonChangedEvent>,
421    mut axis_events: MessageReader<GamepadAxisChangedEvent>,
422    mut mouse_motion_events: MessageReader<MouseMotion>,
423    mut cursor_moved_events: MessageReader<CursorMoved>,
424    mut scroll_wheel_events: MessageReader<MouseWheel>,
425    mut input_manager: ResMut<InputManager<C>>,
426    mut writer: MessageWriter<InputTypeChanged>,
427) {
428    let input_type = input_manager.input_type;
429
430    for event in keyboard.read() {
431        if event.repeat {
432            continue;
433        }
434
435        let physical_key = ActionInput::from(event.key_code);
436        if event.state == ButtonState::Pressed {
437            input_manager.add_input(physical_key.clone());
438        }
439        if let Some(actions) = input_manager.input.get(&physical_key).cloned() {
440            if event.state == ButtonState::Pressed {
441                actions.into_iter().for_each(|action| {
442                    input_manager.press(action, 1.0);
443                });
444            } else {
445                actions.into_iter().for_each(|action| {
446                    input_manager.release(action);
447                });
448            }
449        }
450
451        let logical_key = ActionInput::from(event.logical_key.clone());
452        if event.state == ButtonState::Pressed {
453            input_manager.add_input(logical_key.clone());
454        }
455        if let Some(actions) = input_manager.input.get(&logical_key).cloned() {
456            if event.state == ButtonState::Pressed {
457                actions.into_iter().for_each(|action| {
458                    input_manager.press(action, 1.0);
459                });
460            } else {
461                actions.into_iter().for_each(|action| {
462                    input_manager.release(action);
463                });
464            }
465        }
466
467        input_manager.change_input_type(InputType::Keyboard);
468    }
469
470    for event in mouse.read() {
471        if event.state == ButtonState::Pressed {
472            input_manager.add_input(ActionInput::from(event.button));
473        }
474        if let Some(actions) = input_manager
475            .input
476            .get(&ActionInput::from(event.button))
477            .cloned()
478        {
479            if event.state == ButtonState::Pressed {
480                actions.into_iter().for_each(|action| {
481                    input_manager.press(action, 1.0);
482                });
483            } else {
484                actions.into_iter().for_each(|action| {
485                    input_manager.release(action);
486                });
487            }
488        }
489
490        input_manager.change_input_type(InputType::Keyboard);
491    }
492
493    for event in gamepad.read() {
494        if event.state == ButtonState::Pressed {
495            input_manager.add_input(ActionInput::from(event.button));
496        }
497        if let Some(actions) = input_manager
498            .input
499            .get(&ActionInput::from(event.button))
500            .cloned()
501        {
502            if event.state == ButtonState::Pressed {
503                actions.into_iter().for_each(|action| {
504                    input_manager.press(action, event.value);
505                });
506            } else {
507                actions.into_iter().for_each(|action| {
508                    input_manager.release(action);
509                });
510            }
511        }
512
513        input_manager.change_input_type(InputType::Gamepad);
514    }
515
516    for event in axis_events.read() {
517        let simple_axis = match event.axis {
518            GamepadAxis::LeftStickX => {
519                if event.value >= 0.0 {
520                    SimpleAxis::LeftStickRight
521                } else {
522                    SimpleAxis::LeftStickLeft
523                }
524            }
525            GamepadAxis::LeftStickY => {
526                if event.value >= 0.0 {
527                    SimpleAxis::LeftStickUp
528                } else {
529                    SimpleAxis::LeftStickDown
530                }
531            }
532            GamepadAxis::LeftZ => SimpleAxis::LeftTrigger,
533            GamepadAxis::RightStickX => {
534                if event.value >= 0.0 {
535                    SimpleAxis::RightStickRight
536                } else {
537                    SimpleAxis::RightStickLeft
538                }
539            }
540            GamepadAxis::RightStickY => {
541                if event.value >= 0.0 {
542                    SimpleAxis::RightStickUp
543                } else {
544                    SimpleAxis::RightStickDown
545                }
546            }
547            GamepadAxis::RightZ => SimpleAxis::RightTrigger,
548            _ => {
549                continue;
550            }
551        };
552
553        if event.value.abs() >= input_manager.axis_threshold {
554            input_manager.add_input(ActionInput::from(simple_axis.clone()));
555        }
556
557        if let Some(actions) = input_manager
558            .input
559            .get(&ActionInput::from(simple_axis.clone()))
560            .cloned()
561        {
562            if event.value.abs() >= input_manager.axis_threshold {
563                input_manager.change_input_type(InputType::Gamepad);
564                actions.into_iter().for_each(|action| {
565                    input_manager.press(action, event.value.abs());
566                });
567            } else {
568                actions.into_iter().for_each(|action| {
569                    input_manager.release(action);
570                });
571            }
572        }
573    }
574
575    for event in mouse_motion_events.read() {
576        input_manager.mouse_delta(event.delta);
577    }
578
579    for event in cursor_moved_events.read() {
580        input_manager.mouse_pos(event.position);
581    }
582
583    for event in scroll_wheel_events.read() {
584        let scroll = vec2(event.x, event.y);
585        input_manager.scroll(scroll);
586        let simple_scroll = if event.y >= 0.0 {
587            SimpleScroll::Up
588        } else {
589            SimpleScroll::Down
590        };
591
592        input_manager.change_input_type(InputType::Keyboard);
593        input_manager.add_input(ActionInput::from(simple_scroll.clone()));
594
595        if let Some(actions) = input_manager
596            .input
597            .get(&ActionInput::from(simple_scroll))
598            .cloned()
599        {
600            actions.into_iter().for_each(|action| {
601                input_manager.just_press(action);
602            });
603        }
604    }
605
606    if input_type != input_manager.input_type {
607        writer.write(InputTypeChanged);
608    }
609}
610
611fn clear<C: InputConfig>(mut input_manager: ResMut<InputManager<C>>) {
612    input_manager.clear();
613}