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 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}