1 module cat.wheel.input; 2 3 import std.algorithm.searching; 4 import core.vararg; 5 import bindbc.sdl; 6 7 import cat.wheel.events; 8 public import cat.wheel.keysym; 9 import cat.wheel.structs; 10 11 /** 12 * Arguments to keyboard events 13 */ 14 class KeyboardEventArgs : EventArgs { 15 package this(Keysym s) { sym = s; } 16 17 /// The key involved in the event 18 public const(Keysym) sym; 19 } 20 21 /** 22 * Arguments to events involving mouse buttons 23 */ 24 class MouseButtonEventArgs : EventArgs { 25 package this(MouseButton m) { button = m; } 26 27 /// The mouse button involved in the event 28 public const(MouseButton) button; 29 } 30 31 /** 32 * The argument to the mouse movement event 33 */ 34 class MouseMotionEventArgs : EventArgs { 35 package this(MouseMotion m) { motion = m; } 36 37 /// The mouse motion data for the event 38 public const(MouseMotion) motion; 39 } 40 41 /** 42 * The argument to the mouse wheel event 43 */ 44 class MouseWheelEventArgs : EventArgs { 45 package this(MouseWheel m) { wheel = m; } 46 47 /// The mouse wheel data for the event 48 public const(MouseWheel) wheel; 49 } 50 51 /** 52 * An input handler, which keeps track of what inputs are being held, pressed, released, etc. 53 * Has to be registered to an event handler 54 */ 55 class InputHandler { 56 57 /// Exists for unit testing purposes 58 private this() {} 59 60 /** 61 * Constructs an input handler from an SDL handler, which it attaches delegates to. 62 */ 63 this(Handler h) nothrow { 64 _handler = h; 65 66 _handler.addDelegate((EventArgs) => nextFrame(_handler.time), ED_PRE_PUMP); 67 68 _handler.addDelegate((EventArgs arg) { 69 if (arg.classinfo == typeid(PumpEventArgs)) store((cast(PumpEventArgs) arg).event); 70 }, ED_PUMP); 71 72 _handler.addDelegate((EventArgs) => pumpEvents(), ED_PRE_TICK); 73 } 74 75 /// Gets how long a key has been held for if it hasn't been released 76 int getHeldFor(Keysym key) { 77 return _heldKeys[key]; 78 } 79 80 /// Whether the key was pressed, ignoring modifiers 81 bool wasKeyPressed(SDL_Keycode s) { 82 return canFind!((Keysym a, SDL_Keycode b) => a.code == b)(_pressedKeys, s); 83 } 84 85 /// Whether the key is held, ignoring modifiers 86 bool isKeyHeld(SDL_Keycode s) { 87 return canFind!((Keysym a, SDL_Keycode b) => a.code == b)(_heldKeys.keys, s); 88 } 89 90 /// Whether the key is down, ignoring modifiers. Checks isKeyHeld and wasKeyPressed, in that order 91 bool isKeyDown(SDL_Keycode s) { 92 return isKeyHeld(s) || wasKeyPressed(s); 93 } 94 95 /// Whether the key was released, ignoring modifiers 96 bool wasKeyReleased(SDL_Keycode s) { 97 return canFind!((Keysym a, SDL_Keycode b) => a.code == b)(_releasedKeys, s); 98 } 99 100 /// Whether the mouse button was pressed 101 bool wasMousePressed(ubyte u) { 102 return canFind!((MouseButton a, ubyte b) => a.button == b)(_mouseButtonsPressed, u); 103 } 104 105 /// Whether the mouse button was released 106 bool wasMouseReleased(ubyte u) { 107 return canFind!((MouseButton a, ubyte b) => a.button == b)(_mouseButtonsReleased, u); 108 } 109 110 /// The keys pressed this frame 111 @property pressedKeys() { 112 return _pressedKeys.dup; 113 } 114 115 /// The keys that are being held; use getHeldFor to get time held 116 @property heldKeys() { 117 return _heldKeys.keys; 118 } 119 120 /// The keys that were released this frame 121 @property releasedKeys() { 122 return _releasedKeys.dup; 123 } 124 125 /// The mouse buttons that were pressed this frame 126 @property pressedMouseButtons() { 127 return _mouseButtonsPressed.dup; 128 } 129 130 /// The mouse buttons that were released this frame 131 @property releasedMouseButtons() { 132 return _mouseButtonsReleased.dup; 133 } 134 135 /// The mouse movements that happend this frame 136 @property mouseMotionEvents() { 137 return _mouseMotionEvents.dup; 138 } 139 140 /// The mouse scrolling that happened this frame 141 @property mouseWheelEvents() { 142 return _mouseWheelEvents.dup; 143 } 144 145 private: 146 Handler _handler; 147 148 Keysym[] _pressedKeys; 149 int[Keysym] _heldKeys; 150 Keysym[] _releasedKeys; 151 152 MouseButton[] _mouseButtonsPressed; 153 MouseButton[] _mouseButtonsReleased; 154 MouseMotion[] _mouseMotionEvents; 155 MouseWheel[] _mouseWheelEvents; 156 157 void store(SDL_Event e) nothrow pure @safe { 158 //Keyboard 159 if (e.type == SDL_KEYDOWN) { 160 _pressedKeys ~= Keysym(e.key.keysym.sym, cast(SDL_Keymod) e.key.keysym.mod); 161 } else if (e.type == SDL_KEYUP) { 162 auto keysym = Keysym(e.key.keysym.sym, cast(SDL_Keymod) e.key.keysym.mod); 163 _heldKeys.remove(keysym); 164 _releasedKeys ~= keysym; 165 } 166 //Mouse 167 else if (e.type == SDL_MOUSEBUTTONDOWN) { 168 static if (sdlSupport == SDLSupport.sdl200) { 169 _mouseButtonsPressed ~= MouseButton(e.button.which, e.button.button, 0, Vector2(e.button.x, e.button.y)); 170 } else { 171 _mouseButtonsPressed ~= MouseButton(e.button.which, e.button.button, e.button.clicks, Vector2(e.button.x, e.button.y)); 172 } 173 } else if (e.type == SDL_MOUSEBUTTONUP) { 174 static if (sdlSupport == SDLSupport.sdl200) { 175 _mouseButtonsReleased ~= MouseButton(e.button.which, e.button.button, 0, Vector2(e.button.x, e.button.y)); 176 } else { 177 _mouseButtonsReleased ~= MouseButton(e.button.which, e.button.button, e.button.clicks, Vector2(e.button.x, e.button.y)); 178 } 179 } else if (e.type == SDL_MOUSEMOTION) { 180 _mouseMotionEvents ~= MouseMotion(e.motion.which, e.motion.state, Vector2(e.motion.x, e.motion.y), Vector2(e.motion.xrel, e.motion.yrel)); 181 } else if (e.type == SDL_MOUSEWHEEL) { 182 static if (sdlSupport >= SDLSupport.sdl204) { 183 _mouseWheelEvents ~= MouseWheel(e.wheel.which, Vector2(e.wheel.x, e.wheel.y), e.wheel.direction); 184 } else { 185 _mouseWheelEvents ~= MouseWheel(e.wheel.which, Vector2(e.wheel.x, e.wheel.y)); 186 } 187 } 188 } 189 190 void pumpEvents() { 191 foreach (key; _pressedKeys) { 192 _handler.callEvent(EI_KEY_PRESSED, new KeyboardEventArgs(key)); 193 } 194 195 foreach (key; _heldKeys.byKey) { 196 _handler.callEvent(EI_KEY_HELD, new KeyboardEventArgs(key)); 197 } 198 199 foreach (key; _releasedKeys) { 200 _handler.callEvent(EI_KEY_RELEASED, new KeyboardEventArgs(key)); 201 } 202 203 foreach (button; _mouseButtonsPressed) { 204 _handler.callEvent(EI_MOUSE_PRESSED, new MouseButtonEventArgs(button)); 205 } 206 207 foreach (button; _mouseButtonsReleased) { 208 _handler.callEvent(EI_MOUSE_RELEASED, new MouseButtonEventArgs(button)); 209 } 210 211 foreach (motion; _mouseMotionEvents) { 212 _handler.callEvent(EI_MOUSE_MOVED, new MouseMotionEventArgs(motion)); 213 } 214 215 foreach (wheel; _mouseWheelEvents) { 216 _handler.callEvent(EI_MOUSE_WHEEL, new MouseWheelEventArgs(wheel)); 217 } 218 } 219 220 void nextFrame(int deltaTime) nothrow { 221 foreach (key; _pressedKeys) { 222 _heldKeys[key] = 0; 223 } 224 225 foreach (key; _heldKeys.byKey) { 226 _heldKeys[key] += deltaTime; 227 } 228 229 _pressedKeys = _pressedKeys.init; 230 _releasedKeys = _releasedKeys.init; 231 232 _mouseButtonsPressed = _mouseButtonsPressed.init; 233 _mouseButtonsReleased = _mouseButtonsReleased.init; 234 _mouseMotionEvents = _mouseMotionEvents.init; 235 _mouseWheelEvents = _mouseWheelEvents.init; 236 } 237 } 238 239 unittest { 240 auto h = new InputHandler(); 241 auto e = SDL_Event(); 242 243 e.key = SDL_KeyboardEvent( 244 SDL_KEYDOWN, //Keydown/keyup 245 0, 0, //Timestamp and window information; ignored (perhaps use window info?) 246 SDL_PRESSED, //Pressed/released 247 0, 0, 0, //Repeat (first 0) and padding (unused) 248 SDL_Keysym( 249 SDL_SCANCODE_UNKNOWN, //Scancode not checked 250 SDLK_5, //Virtual key 251 0, //Modifiers 252 0 //Unused 253 ) 254 ); 255 h.store(e); 256 257 auto pressed = Keysym(SDLK_5, KMOD_NONE); 258 assert(h._pressedKeys.canFind(pressed)); 259 260 e.key = SDL_KeyboardEvent(SDL_KEYUP, 0, 0, SDL_PRESSED, 0, 0, 0, SDL_Keysym(SDL_SCANCODE_UNKNOWN, SDLK_RETURN, KMOD_LCTRL, 0)); 261 h.store(e); 262 263 auto released = Keysym(SDLK_RETURN, KMOD_LCTRL); 264 assert(h._releasedKeys.canFind(released)); 265 266 h.nextFrame(50); 267 assert(h._heldKeys.byKey.canFind(pressed)); 268 assert(h._heldKeys[pressed] == 50); 269 assert(!h._releasedKeys.canFind(released)); 270 } 271 272 /** 273 * InputHandler's extensions to standard events. 274 */ 275 enum { 276 EI_KEY_PRESSED = 0b10000, 277 EI_KEY_HELD = 0b10001, 278 EI_KEY_RELEASED = 0b10010, 279 EI_MOUSE_PRESSED = 0b10011, 280 EI_MOUSE_RELEASED = 0b10100, 281 EI_MOUSE_MOVED = 0b10101, 282 EI_MOUSE_WHEEL = 0b10110 283 }