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
//! Query the player's input and turn it into controls.
//!
//! **NOTE: EVENT-STYLE CONTROLS ARE NOT IMPLEMENTED YET!**
//!
//! This module defines two main structs: `PollingInputHandler` and `EventInputHandler`.
//!
//! `PollingInputHandler` is used when your game engine provides player input by polling for it. The GGEZ library
//! uses this method; you can call `keyboard::get_keyboard_input` and get what keys are pressed that way.
//!
//! `EventInputHandler` is used when your game engine provides player input by producing Input events. SDL2 and
//! browsers with WASM work this way. I believe Piston also works this way, but I've never worked with Piston.
//!
//! Both styles of input handling should expose the same API for querying inputs.
//!
//! # Updating the Inputs
//!
//! It is VERY IMPORTANT that before you do *any* processing in your main loop you call `InputHandler::update`.
//! This tells the input handler that the next frame has elapsed.
//!
//! For `PollingInputHandler`, you must pass in a `&HashSet` containing all the pressed keys to `update`.
//!
//! `EventInputHandler` also exposes `TODO`, which you must call upon getting an input event.
//!
//! # Generics
//!
//! To work with all the ways game libraries deal with input handling, the Input Handlers are generic over
//! the inputs `I` that the game engine gives you, and the controls `C` that your game uses.
//!
//! Inputs must be hashable (so, `Hash + Eq + PartialEq`).
//!
//! Controls must be useable with the EnumMap crate. You can do that easily enough with `#[derive(Enum)]`. (Internally,
//! the handlers use EnumMap to map each control to how long it has been held down.).
//!
//! Both of them must impl Clone (or Copy).
//!
//! It will probably be useful to define some type aliases to prevent having to type out your generics over and over
//! again. For example:
//!
// we ignore this block of code because i really don't want to bother with importing
// all those game libraries...
//! ```ignore
//! ##[derive(Enum)]
//! enum MyControls {
//!     Up,
//!     Down,
//!     Left,
//!     Right,
//!     Jump,
//!     Pause,
//! }
//!
//! /// When you're writing a game with GGEZ
//! mod ggez_game {
//!     type InputHandler = PollingInputHandler<ggez::input::keyboard::KeyCode, super::MyControls>;
//! }
//!
//! /// When you're writing a game with Piston
//! mod piston_game {
//!     type InputHandler = EventInputHandler<piston::input::keyboard::Key, super::MyControls>;
//! }
//!
//! /// When you're writing a game for the browser with wasm-pack or similar
//! mod browser_wasm_js_something {
//!     type InputHandler = EventInputHandler<String, super::MyControls>;
//! }
//!
//! ```
//!
//! # Changing Controls on the Fly
//!
//! Both input handlers support changing controls on the fly (perhaps through some sort of menu).
//! Call `listen_for_control_change` with the control you want to update the input for, and the next time an input
//! is received, that control will be associated with that input.
//!
//! If multiple controls are pressed at the same time during a frame where the input handler is
//! listening for a control change, it's undefined which one the control will be set to.
//! It will be set to one of them, however.

use std::{collections::HashMap, collections::HashSet, hash::Hash};

use enum_map::{Enum, EnumMap};

/// Polling-based input handler.
/// See module-level documentation for more.
pub struct PollingInputHandler<I: Hash + Eq + PartialEq + Clone, C: Enum<u32> + Clone> {
    /// Maps inputs to the controls they activate
    control_config: HashMap<I, C>,
    /// How long each control has been pressed
    input_time: EnumMap<C, u32>,
    /// If this is Some, we're waiting for a new control config.
    listening_for_input: Option<C>,
}

impl<I: Hash + Eq + PartialEq + Clone, C: Enum<u32> + Clone> PollingInputHandler<I, C> {
    /// Create a new PollingInputHandler without any controls.
    pub fn new_empty() -> Self {
        Self {
            control_config: HashMap::new(),
            // conveniently, the default value for u32 is 0!
            // and we want the map to start full of zeros.
            // (zeroes?)
            input_time: EnumMap::new(),
            listening_for_input: None,
        }
    }

    /// Create a new PollingInputHandler with the specified controls.
    /// The HashMap in should map inputs to the controls you want them to actuate.
    pub fn new(control_config: HashMap<I, C>) -> Self {
        Self {
            control_config,
            input_time: EnumMap::new(),
            listening_for_input: None,
        }
    }

    /// Update the input handler. You MUST CALL THIS FIRST THING in your game loop.
    /// Otherwise things won't get updated correctly.
    pub fn update(&mut self, new_inputs: &HashSet<I>) {
        match &self.listening_for_input {
            None => {
                for (input, control) in self.control_config.iter() {
                    if new_inputs.contains(input) {
                        // this input is getting pressed!
                        // increment our timer
                        self.input_time[control.to_owned()] += 1;
                    } else {
                        // this input is not getting pressed
                        // reset our timer
                        self.input_time[control.to_owned()] = 0;
                    }
                }
            }
            Some(ctrl) => {
                if let Some(input) = new_inputs.iter().next() {
                    // we're pressing something!
                    self.control_config
                        .insert(input.to_owned(), ctrl.to_owned());
                }
            }
        }
    }

    /// Is this input pressed down?
    /// i.e. is the player pressing the button?
    pub fn pressed(&self, control: C) -> bool {
        self.input_time[control] >= 1
    }

    /// Is this input released?
    /// i.e. is the player *not* pressing the button?
    pub fn released(&self, control: C) -> bool {
        self.input_time[control] == 0
    }

    /// Is this input being clicked down?
    /// i.e. was it up last frame, but down this frame?
    pub fn clicked_down(&self, control: C) -> bool {
        self.input_time[control] == 1
    }
}