comparison mde/input/Input.d @ 32:316b0230a849

Lots more work on the GUI. Also renamed lots of files. Lots of changes to the GUI. Renderer is now used exclusively for rendering and WidgetDecoration is gone. Renamed lots of files to conform to case policies. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 30 Apr 2008 18:05:56 +0100
parents
children 6b4116e6355c
comparison
equal deleted inserted replaced
31:baa87e68d7dc 32:316b0230a849
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 /**
17 * This module contains the interface to the input system; it should be the only module of the
18 * input package imported from outside this package.
19 */
20 module mde.input.Input;
21
22 // package imports
23 import mde.input.Config;
24 import mde.input.exception;
25
26 // sdl imports
27 import derelict.sdl.events;
28 import derelict.sdl.types; // only SDL_PRESSED
29 import derelict.sdl.joystick; // SDL_HAT_*
30
31 import tango.util.log.Log : Log, Logger;
32
33 /// Class encapsulating all input functionality.
34 class Input
35 {
36 /// Typedef for all indexes (type is uint).
37 typedef uint inputID;
38 alias void delegate(inputID, bool) ButtonCallback;
39 alias void delegate(inputID, short) AxisCallback;
40 alias void delegate(inputID, real,real) RelMotionCallback;
41 alias void delegate(ushort, ushort, ubyte, bool) MouseClickCallback;
42
43 /** Get key status at this ID.
44 *
45 * Returns: value (true = down, false = up) or false if no value at this ID. */
46 bool getButton (inputID id) {
47 bool* retp = id in button;
48 if (retp) return *retp;
49 else return false;
50 }
51
52 /** Get axis status at this ID.
53 *
54 * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */
55 short getAxis (inputID id) {
56 short* retp = id in axis;
57 if (retp) return *retp;
58 else return 0;
59 }
60 /** Get axis status at this ID.
61 *
62 * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
63 real getAxis1 (inputID id) {
64 short* retp = id in axis;
65 if (retp) return (*retp) * 3.0518509475997192e-05;
66 else return 0.0;
67 }
68
69 /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call).
70 *
71 * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item).
72 *
73 * To avoid confusion over the ID here, the idea is for the input-layer upward to support
74 * multiple mice, in case future platforms do.
75 * Also joystick balls (supported by SDL) can be used in the same way as a mouse for relative
76 * positions.
77 */
78 void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) {
79 RelPair* rp = id in relMotion;
80 if (rp) {
81 x = rp.x; y = rp.y;
82 }
83 }
84 /** Get mouse pointer position in screen coordinates.
85 *
86 * Window managers only support one mouse, so there will only be one screen coordinate.
87 * Unlike nearly everything else, this is not configurable.
88 */
89 void getMouseScreenPos (out uint x, out uint y) {
90 x = mouse_x; y = mouse_y;
91 }
92 // /// Is this modifier on?
93 //bool modifierStatus (inputID id);
94
95 /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
96 *
97 * Delegate receives event status.
98 */
99 void addButtonCallback (inputID id, ButtonCallback dg) {
100 buttonCallbacks[id] ~= dg;
101 }
102
103 /** Adds a callback delegate for axis events with this ID.
104 *
105 * Delegate receives event status (as per what getAxis returns).
106 */
107 void addAxisCallback (inputID id, AxisCallback dg) {
108 axisCallbacks[id] ~= dg;
109 }
110
111 /** Adds a callback delegate for mouse motion/joystick ball events with this ID.
112 *
113 * Delegate receives event status. As the name suggests, this is relative motion not screen
114 * position, with sensitivity adjustments applied.
115 *
116 * (A separate callback for mouse screen position changes is not
117 * necessary since this will be triggered by the same event - use mouseScreenPos from within the
118 * function to get new screen coordinates.)
119 */
120 void addRelMotionCallback (inputID id, RelMotionCallback dg) {
121 relMotionCallbacks[id] ~= dg;
122 }
123
124 /** Adds a callback delegate for all mouse clicks & releases.
125 *
126 * Delegate recieves x,y screen position (at time of click/release), button index (1 for left,
127 * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or
128 * released (true if pressed).
129 *
130 * The point of this over a standard button callback is firstly to avoid mouse configuration for
131 * the GUI, and secondly to give the pointer position at the time of the event, not the time the
132 * callback gets called.
133 */
134 void addMouseClickCallback (MouseClickCallback dg) {
135 mouseClickCallbacks ~= dg;
136 }
137
138 /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
139 *
140 * Other types of event functions may be added. Returns true if the event was used, false if not
141 * or no config was available. Hmm... doesn't seem very useful, but has practically no cost.
142 *
143 * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should
144 * be fine.
145 */
146 bool opCall (ref SDL_Event event) {
147 /* Non-config events.
148 *
149 * Mouse events don't need config for the GUI. Handle them first so that if no config exists
150 * some functionality at least is retained.
151 *
152 * Note that the mouse coordinates as reported by SDL put the top-left most pixel at 1,1.
153 * Internal coordinates put that pixel at 0,0 (see gui/GUI notes.txt).
154 */
155 switch (event.type) {
156 case SDL_MOUSEBUTTONDOWN:
157 case SDL_MOUSEBUTTONUP:
158 foreach (dg; mouseClickCallbacks)
159 dg (event.button.x - 1, event.button.y - 1,
160 event.button.button, event.button.state == SDL_PRESSED);
161 break;
162
163 case SDL_MOUSEMOTION:
164 mouse_x = event.motion.x - 1;
165 mouse_y = event.motion.y - 1;
166 break;
167
168 default:
169 }
170
171 /* No config available, so don't try to access it and segfault.
172 * Don't log a message because this function is called per-event (i.e. frequently).
173 * A message should already have been logged by loadConfig anyway.
174 */
175 if (!config) return false;
176
177 switch (event.type) {
178 // Keyboard events:
179 case SDL_KEYDOWN:
180 case SDL_KEYUP:
181 outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
182 if (p) foreach (outQueue q; *p) {
183 bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q));
184 }
185 break;
186
187 // Mouse events:
188 case SDL_MOUSEBUTTONDOWN:
189 case SDL_MOUSEBUTTONUP:
190 // Button events:
191 outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button;
192 if (p) foreach (outQueue q; *p) {
193 bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q));
194 }
195 break;
196
197 case SDL_MOUSEMOTION:
198 // Relative motion:
199 outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion;
200 if (p) foreach (outQueue q; *p) {
201 mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q));
202 }
203 break;
204
205 // Joystick events:
206 case SDL_JOYBUTTONDOWN:
207 case SDL_JOYBUTTONUP:
208 outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button;
209 if (p) foreach (outQueue q; *p) {
210 bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q));
211 }
212 break;
213
214 case SDL_JOYAXISMOTION:
215 outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis;
216 if (p) foreach (outQueue q; *p) {
217 aEvent (this, event.jaxis.value, readOutQueue(q));
218 }
219 break;
220
221 case SDL_JOYBALLMOTION:
222 outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion;
223 if (p) foreach (outQueue q; *p) {
224 mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q));
225 }
226 break;
227
228 case SDL_JOYHATMOTION:
229 static ubyte[uint] oldJHatVals; // necessary to store this to know which "axis" changed
230
231 uint index = (event.jhat.which << 12) | event.jhat.hat;
232 ubyte* oVal_p = index in oldJHatVals;
233 ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED;
234
235 // Carry out functionality for an axis.
236 void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) {
237 // Check if there's any change on this axis (if not, nothing to do):
238 ubyte filter = neg | pos;
239 if ((oldJHatVal & filter) != (event.jhat.value & filter)) {
240 // Now we know this axis changed position, so can unset old value and set
241 // new value (if not centre).
242
243 // Cancel old button status:
244 if (oldJHatVal & neg) {
245 outQueue[]* p = (neg_b | index) in config.button;
246 if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
247 } else if (oldJHatVal & pos) {
248 outQueue[]* p = (pos_b | index) in config.button;
249 if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
250 }
251
252 // Set new button status and position:
253 short position = 0;
254 if (event.jhat.value & neg) {
255 position = -32767;
256 outQueue[]* p = (neg_b | index) in config.button;
257 if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
258 } else if (event.jhat.value & pos) {
259 position = 32767;
260 outQueue[]* p = (pos_b | index) in config.button;
261 if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
262 }
263
264 // New axis event:
265 outQueue[]* p = (axis | index) in config.axis;
266 if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q));
267 }
268 }
269
270 // Now run the code for each axis:
271 hatExamine (SDL_HAT_UP, SDL_HAT_DOWN,
272 Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD);
273 hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT,
274 Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR);
275
276 oldJHatVals[index] = event.jhat.value;
277 break;
278
279 // Other events:
280 default:
281 return false; // event not used
282 }
283 return true; // event used
284 }
285
286 /** Resets relative movement of mice / joystick balls to zero.
287 *
288 * Should be called once-per-frame if these are used, but must be called after their state has
289 * been read (e.g. just before updating the input).
290 */
291 void frameReset () {
292 foreach (rp; relMotion) {
293 rp.x = rp.y = 0.0;
294 }
295 }
296
297 /** Loads all configs, activating the requested id.
298 *
299 * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
300 * found.
301 */
302 void loadConfig (char[] profile = "Default") {
303 Config.load("input"); // FIXME: filename
304 Config* c_p = profile in Config.configs;
305 if (c_p) config = *c_p;
306 else {
307 throw new ConfigLoadException;
308 logger.error ("Config profile \""~profile~"\" not found: input won't work unless a valid profile is loaded!");
309 }
310 }
311
312 private:
313 // Static constructor for event stream (fills es_*_fcts tables).
314 static this () {
315 es_b_fcts = [ ES_B.OUT : &es_b_out ];
316 es_a_fcts = [ ES_A.OUT : &es_a_out, ES_A.REVERSE : &es_a_reverse ];
317 es_m_fcts = [ ES_M.OUT : &es_m_out ];
318
319 logger = Log.getLogger ("mde.input.input.Input");
320 }
321
322 struct RelPair { // for mouse/joystick ball motion
323 real x, y;
324 static RelPair opCall (real a, real b) {
325 RelPair ret;
326 ret.x = a; ret.y = b;
327 return ret;
328 }
329 }
330
331 static Logger logger;
332
333 Config config; // Configuration
334
335 bool[inputID] button; // Table of button states
336 short[inputID] axis; // Table of axes states
337 ushort mouse_x, mouse_y; // Current screen coords of the window manager mouse
338 RelPair[inputID] relMotion; // Table of relative mouse / joystick ball motions
339
340 // FIXME: currently no means of removal
341 ButtonCallback[][inputID] buttonCallbacks;
342 AxisCallback[][inputID] axisCallbacks;
343 RelMotionCallback[][inputID] relMotionCallbacks;
344 MouseClickCallback[] mouseClickCallbacks;
345
346 //BEGIN Event stream functionality
347 /* This section contains functions called on an event, which may modify the event (adjuster
348 * functions), and finally output to one (or more) of the state tables (the event stream).
349 *
350 * Adjuster and other event functions should have a format to fit the ES_X_Func types, for X is B
351 * (button event), A (axis event) or M (mouse relative motion event or joystick ball event).
352 * Adjusters should call one of the xEvent() functions with their output and the remainder of
353 * the readOutQueue.
354 *
355 * To control which adjusters get called and pass parameters, a stack of sorts is used: outQueue.
356 */
357 //BEGIN ES Definitions
358 /* Note: We really want an array, not a stack. We cannot edit the lists, so we can either
359 * copy to a stack or just iterate through it as an array.
360 */
361 alias Config.outQueue outQueue;
362 struct readOutQueue { // A convenient structure for reading an outQueue item by item.
363 private Config.outQueue _q; // the queue, stored by reference to the original
364 private uint p = 0; // current read position (start at beginning)
365
366 static readOutQueue opCall (Config.outQueue q) { // Static constructor
367 readOutQueue ret;
368 ret._q = q;
369 return ret;
370 }
371 uint pop () { // Get the next element and advance. Throws an exception if there isn't another.
372 if (p >= _q.length)
373 throw new InputClassException ("Input: Invalid configuration: incomplete config stack");
374 uint ret = _q[p];
375 ++p;
376 return ret;
377 }
378 debug uint next () { // Get the next element. No checks; for debug use only.
379 return _q[p];
380 }
381 }
382
383 // These aliases are for pointers to the event functions.
384 // These need to use "this", but cannot be delegates because they musn't be bound to a
385 // particular instance of Input, hence this must be passed when called.
386 alias void function (Input, bool, readOutQueue) ES_B_Func;
387 alias void function (Input, short, readOutQueue) ES_A_Func;
388 alias void function (Input, short, short, readOutQueue) ES_M_Func;
389
390 /* These are the codes allowing the config to specify event functions.
391 *
392 * They are organised as defined in doc/input_ID_assignments.
393 */
394 enum ES_B : uint {
395 OUT = 0x1000u,
396 }
397 enum ES_A : uint {
398 OUT = 0x1000u,
399 REVERSE = 0x2000u,
400 }
401 enum ES_M : uint {
402 OUT = 0x1000u,
403 }
404 //END ES Definitions
405
406 // ES Data:
407 // These are the tables for looking up which event function to call.
408 static ES_B_Func[uint] es_b_fcts;
409 static ES_A_Func[uint] es_a_fcts;
410 static ES_M_Func[uint] es_m_fcts;
411
412 //BEGIN ES Functions
413 // These 3 functions pass an event to the appropriate event function (adjuster or output func).
414 // They are used to start and continue an event stream.
415 // They must be static to allow calling from event functions.
416 const EVCONF_ERR = "Invalid configuration: bad event function code";
417 static void bEvent (Input myThis, bool b, readOutQueue s)
418 {
419 ES_B_Func* func = (s.pop() in es_b_fcts);
420 if (func != null) (*func)(myThis, b, s);
421 else throw new InputClassException (EVCONF_ERR);
422 }
423 static void aEvent (Input myThis, short x, readOutQueue s)
424 {
425 ES_A_Func* func = (s.pop() in es_a_fcts);
426 if (func != null) (*func)(myThis, x, s);
427 else throw new InputClassException (EVCONF_ERR);
428 }
429 // Only handles relative output since position-on-screen is not stored with an ID:
430 static void mEvent (Input myThis, short x, short y, readOutQueue s)
431 {
432 ES_M_Func* func = (s.pop() in es_m_fcts);
433 if (func != null) (*func)(myThis, x, y, s);
434 else throw new InputClassException (EVCONF_ERR);
435 }
436
437 // The remaining functions are the stream functions, for adjusting and outputting an event.
438 // They need to work like non-static functions, but are called via a function pointer, hencne
439 // should be static with their first parameter being instead of this.
440
441 // Simple output function
442 static void es_b_out (Input myThis, bool b, readOutQueue s) {
443 inputID id = cast(inputID) s.pop();
444
445 myThis.button[id] = b;
446
447 ButtonCallback[]* cb_p = id in myThis.buttonCallbacks;
448 if (cb_p) foreach (cb; *cb_p) cb (id, b);
449 }
450 // Adjuster to check modifier keys
451 static void es_b_modifier (Input myThis, bool b, readOutQueue s);
452
453 // Simple output function
454 static void es_a_out (Input myThis, short x, readOutQueue s) {
455 inputID id = cast(inputID) s.pop();
456
457 myThis.axis[id] = x;
458
459 AxisCallback[]* cb_p = id in myThis.axisCallbacks;
460 if (cb_p) foreach (cb; *cb_p) cb (id, x);
461 }
462
463 // Just reverses an axis's value
464 static void es_a_reverse (Input myThis, short x, readOutQueue s) {
465 aEvent (myThis, -x, s);
466 }
467
468 // Simple output function
469 static void es_m_out (Input myThis, short x, short y, readOutQueue s) {
470 inputID id = cast(inputID) s.pop();
471
472 myThis.relMotion[id] = RelPair(x,y);
473
474 RelMotionCallback[]* cb_p = id in myThis.relMotionCallbacks;
475 if (cb_p) foreach (cb; *cb_p) cb (id, x,y);
476 }
477 //END ES Functions
478 //END Event stream functionality
479
480
481 /* This unittest covers input.config.Config and input.input.Input for some events of all types.
482 * It is not bullet-proof, and does not cover the steam-functions other than the direct output
483 * ones (largely because these may get added at any time and tested via input tests).
484 *
485 * What it does test:
486 * Events of all types.
487 * Callbacks of all types.
488 * Status set from events for all types (button,axis,relMotion).
489 *
490 * It relies on config loaded from a file (dependant on where input bindings are loaded from;
491 * currently conf/input.mtt).
492 */
493 debug (mdeUnitTest) unittest {
494 Input ut = new Input();
495 ut.loadConfig ("UnitTest");
496
497 int[6] counters; // counters for callbacks
498 // Should become: [2,2,0,2,1,1]
499
500 //BEGIN Set up some callbacks
501 ut.addButtonCallback (0x03F0, delegate void(inputID id, bool status) {
502 assert (status == !counters[0]); // true first call, false next
503 counters[0] += 1;
504 });
505 ut.addButtonCallback (0x06F0, delegate void(inputID id, bool status) {
506 assert (status == !counters[1]); // true first call, false next
507 counters[1] += 1;
508 });
509 ut.addButtonCallback (0x07F0, delegate void(inputID id, bool status) {
510 counters[2] += 1;
511 });
512 ut.addAxisCallback (0x22F0, delegate void(inputID id, short x) {
513 assert (x == (counters[3] ? 0 : 32767));
514 counters[3] += 1;
515 });
516 ut.addRelMotionCallback (0x11F0, delegate void(inputID id, real x, real y) {
517 assert (x == 14.0);
518 assert (y == -1.0);
519 counters[4] += 1;
520 });
521 ut.addMouseClickCallback (delegate void(ushort x, ushort y, ubyte b, bool s) {
522 assert (x == 291);
523 assert (y == 10010);
524 assert (b == 3);
525 assert (s == false);
526 counters[5] += 1;
527 });
528 //END Set up some callbacks
529
530 //BEGIN Post a lot of events
531 SDL_Event e;
532
533 // F11 down:
534 e.type = SDL_KEYDOWN;
535 e.key.state = SDL_PRESSED;
536 e.key.keysym.sym = 292; // SDLK_F11
537 ut(e);
538 // Right mouse button up:
539 e.type = SDL_MOUSEBUTTONUP;
540 e.button.button = 3; // SDL_BUTTON_RIGHT
541 e.button.state = SDL_RELEASED;
542 e.button.x = 291;
543 e.button.y = 10010;
544 ut(e);
545 // Mouse motion:
546 e.type = SDL_MOUSEMOTION;
547 e.motion.x = 63;
548 e.motion.y = 44;
549 e.motion.xrel = 14;
550 e.motion.yrel = -1;
551 ut(e);
552 // Joystick 2 button 5 down:
553 e.type = SDL_JOYBUTTONDOWN;
554 e.jbutton.which = 2;
555 e.jbutton.button = 5;
556 e.jbutton.state = SDL_PRESSED;
557 ut(e);
558 // Same button released:
559 e.jbutton.state = SDL_RELEASED;
560 ut(e);
561 // Joystick 1 axis 8 motion:
562 e.type = SDL_JOYAXISMOTION;
563 e.jaxis.which = 1;
564 e.jaxis.axis = 8;
565 e.jaxis.value = 32767;
566 ut(e);
567 // Joystick 22 ball 100 motion:
568 e.type = SDL_JOYBALLMOTION;
569 e.jball.which = 22;
570 e.jball.ball = 100;
571 e.jball.xrel = -21;
572 e.jball.yrel = 1024;
573 ut(e);
574 // Joystick 214 hat 12 DOWN-RIGHT:
575 e.type = SDL_JOYHATMOTION;
576 e.jhat.which = 214;
577 e.jhat.hat = 12;
578 e.jhat.value = SDL_HAT_RIGHTDOWN;
579 ut(e);
580 // Same hat LEFT:
581 e.jhat.value = SDL_HAT_LEFT;
582 ut(e);
583 //END Post a lot of events
584
585 //BEGIN Check states
586 assert (ut.getButton(0xF0) == true);
587 assert (ut.getButton(0x1F0) == false);
588 assert (ut.getButton(0x2F0) == false);
589 assert (ut.getButton(0x4F0) == false);
590 assert (ut.getButton(0x5F0) == true);
591 assert (ut.getButton(0x6F0) == false);
592 assert (ut.getButton(0x7F0) == false);
593
594 assert (ut.getAxis(0x20F0) == 32767);
595 assert (ut.getAxis(0x21F0) == -32767);
596 assert (ut.getAxis1(0x22F0) == 0.0);
597
598 real s,t;
599 ut.getRelMotion(0x10F0, s,t);
600 assert (s == 14.0);
601 assert (t == -1.0);
602 ut.getRelMotion(0x12F0, s,t);
603 assert (s == -21.0);
604 assert (t == 1024.0);
605
606 assert (counters == [2,2,0,2,1,1]);
607 //END Check states
608
609 logger.info ("Unittest complete.");
610 }
611 }