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 }