Changeset 10:4c3575400769
- Timestamp:
- 02/18/08 06:54:56 (11 months ago)
- Files:
-
- conf/input.mtt (modified) (1 diff)
- doc/changelog (added)
- doc/input_ID_assignments (added)
- doc/jobs (modified) (1 diff)
- doc/policies.txt (modified) (4 diffs)
- dsss.conf (modified) (1 diff)
- mde/events.d (modified) (3 diffs)
- mde/exception.d (modified) (2 diffs)
- mde/global.d (added)
- mde/init.d (modified) (9 diffs)
- mde/input/config.d (modified) (6 diffs)
- mde/input/exception.d (modified) (3 diffs)
- mde/input/input.d (modified) (14 diffs)
- mde/input/joystick.d (modified) (1 diff)
- mde/mde.d (modified) (2 diffs)
- mde/mergetag/dataset.d (modified) (2 diffs)
- mde/mergetag/defaultdata.d (modified) (3 diffs)
- mde/mergetag/doc/issues.txt (modified) (2 diffs)
- mde/mergetag/exception.d (modified) (1 diff)
- mde/mergetag/mtunittest.d (added)
- mde/mergetag/read.d (modified) (4 diffs)
- mde/mergetag/write.d (modified) (4 diffs)
- mde/test.d (deleted)
- mde/text/exception.d (deleted)
- mde/text/format.d (deleted)
- mde/text/parse.d (deleted)
- mde/text/util.d (deleted)
- test/mdeTest.d (added)
- test/util.d (added)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
conf/input.mtt
r9 r10 1 1 {MT01} 2 2 <char[][]|Configs=["Std"]> 3 <char[]|Test="1"> 3 4 {Default} 4 <uint[][uint]|B=[0x20000000 : [0x100, 3], 0x20000001 : [0x100, 5] ]> 5 <uint[][uint]|B=[ 0x8800001B : [[0x1000, 0x0]] ]> 6 {UnitTest} 7 <uint[][uint]|B=[ 8 0x88000124 : [[0x1000, 0x00F0]], 9 0x40000003 : [[0x1000, 0x01F0]], 10 0x20002005 : [ [0x1000,0x02F0],[0x1000,0x03F0] ], 11 0x110D600C : [[0x1000,0x04F0]], 12 0x120D600C : [[0x1000,0x05F0]], 13 0x140D600C : [[0x1000,0x06F0]], 14 0x180D600C : [[0x1000,0x07F0]] ]> 15 <uint[][uint]|A=[0x80001008 : [[0x1000,0x20F0]],0x130D600C : [[0x1000,0x21F0]] , 0x1C0D600C : [[0x1000,0x22F0]]]> 16 <uint[][uint]|M=[ 0x88000000 : [[0x1000, 0x10F0], [0x1000, 0x11F0]], 0x40016064:[[0x1000,0x12F0]]]> doc/jobs
r9 r10 1 1 In progress: 2 * Why doesn't input.config filtering via headers "Configs" work?3 2 4 3 To do: 5 * change init threads (should catch own exceptions) 6 * finish event callback support 7 * add remaining SDL event support 4 * Write Input unittests; remove untested notes 5 * Why doesn't input.config filtering via headers "Configs" work? 8 6 * add options support; in particular for whether or not to use threads (and adjust Init to use this). 9 7 * OutOfMemoryException is not currently checked for â it should be at least in critical places (use high-level catching of all errors?). 8 * Sensitivity adjustments. From es_a_out: 9 /+ FIXME: revise. 10 + I can't see any point using HALF_RANGE here, since it should really be used dependant on 11 + the device attached, not the axis. Also what about adjusted range like X3's throttle? 12 + 13 + Sensitivity: is this the right place to adjust it? For things like throttle where the 14 + ends of the interval must remain fixed, multiplying cannot be used to adjust and adjusting 15 + the curve via a power function doesn't seem to be what we want. For things where the 16 + end points needn't remain fixed, multiplying seems the right thing to do, but cannot be 17 + done here since we don't know the end points can be changed. 18 19 real y = x; 20 uint conf = s.pop(); 21 enum : uint { 22 HALF_RANGE = 0x8000_0000u, 23 SENSITIVITY = 0x0080_0000u, 24 } 25 // Convert ranges into standard intervals (with or without reverse values) 26 if (conf & HALF_RANGE) y = (y + 32767.0) * 1.5259254737998596e-05; // range 0.0 - 1.0 27 else y *= 3.0518509475997192e-05; // range -1.0 - 1.0 28 real a; 29 if (conf & SENSITIVITY) a = s.pop(); 30 /+ When a global sensitivity is available (possibly only use if it's enabled)... 31 else a = axis.sensitivity; 32 y = sign(y) * pow(abs(y), a); // sensitivity adjustment by a +/ 33 myThis.axis[cast(inputID) s.pop()] = y; 34 +/ 35 36 Done (for git log message): 37 * Init threads now catch own exceptions. 38 * Assigned some inputID devisions. 39 * add remaining SDL event support 40 * Rewrote most of mde.mergetag.defaultdata using generic programming to generate read & write rules for all types. As a result, defaultdata can now write properly. 41 * Axis output now stored with short instead of real. 42 * Input unittest 43 * Moved mde/text to scrapple 44 * DefaultData unittest. Then commit. doc/policies.txt
r8 r10 3 3 These are principles, not cast-iron rules, which I (Diggory Hardy) generally try to adhere to. If any other programmers have better principles to apply over these rules, they may do so for their own coding providing they have a good reason (i.e. not simply wanting to be a little different). 4 4 5 A warning: I have several times changed my mind about items I've written here. So don't expect all existing code to conform, and if you feel that a principle listed is not a good idea don't think you have to conform to it. 5 6 6 Coding conventions: Mostly stick to those provided in the D specification. Generally indent with four spaces and use tabs to align comments. Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code. 7 8 9 Coding conventions: Mostly stick to those provided in the D specification. Generally indent with four spaces and use tabs to align comments. Keep editor's tab-width at 8. Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code. 7 10 8 11 Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not _s. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Again, this is only really a guideline. … … 11 14 12 15 Spelling: Use british or american (or other) spellings as you like, but BE CONSISTANT at least for code symbols within packages (i.e. if you write your own package you choose the spelling, and for comments it doesn't matter, unless it's actually refering to a symbol). For text output to the user there need be no convention until internationalisation support is built-in. As far as internationalisation/localisation is concerned, does it make sense to translate log messages or not? (They are going to be seen by end-users, but will largely be used by developers.) 16 13 17 14 18 … … 20 24 21 25 Engine-wide initialisation and cleanup should be handled or invoked by mde.init.Init's CTOR and DTOR methods where this is viable. 26 22 27 23 28 … … 41 46 ⢠error messages indicate that something big is wrong, and if the program still runs it is unlikely to be usable as intended; 42 47 ⢠fatal messages indicate a problem preventing the program from running. 43 44 48 45 49 46 50 Thrown errors should use an exception class specific to at least the package involved to enable specific catching of errors. Exception classes should be defined within a module exception.d in the package directory. Exception classes should generally follow the conventions within mde/exception.d to aid in providing reasonable error messages. 51 52 53 54 Unittests (last block in the module where multiple unittest blocks are used) should end with: 55 logger.info ("Unittest complete."); 56 No more logging should be needed, since if it fails whoever runs the unittest will know about it, and logging messages cannot be used to tell how complete the unittesting is. These messages just confirm that the unittests ran really. 57 58 Unittests may be defined in their own modules or other modules. Unittests should be wrapped in 59 debug(mdeUnitTest) 60 statements (these may also be used to wrap imports only needed by the unittest). Any modules containing unittests should be imported by test.mdeTest. dsss.conf
r8 r10 1 defaulttargets = mde/mde.d 2 3 [*] 4 version (Posix) { 5 buildflags=-L-ldl 6 } else { 7 warn Only posix builds have been tested; elsewhere other libraries will probably need to be linked. 8 } 9 1 10 [mde/mde.d] 2 buildflags=-L-ldl 3 target=bin/mde 4 #[mde/mergetag] 5 #[mde/input] 6 #[test/MTTest.d] 7 #target=bin/MTTest 11 version (Posix) { 12 target=bin/mde 13 } else version (Windows) { 14 target=bin/mde.exe 15 } 8 16 17 [test/mdeTest.d] 18 version (Posix) { 19 buildflags=-L-ldl -debug=mdeUnitTest -unittest 20 target=bin/mdeTest 21 } else version (Windows) { 22 warn Extra linking probably needed! 23 buildflags=-debug=mdeUnitTest -unittest 24 target=bin/mdeTest.exe 25 } 26 noinstall mde/events.d
r8 r10 3 3 4 4 import mde.scheduler; 5 import global = mde.global; 5 6 6 7 import mde.input.input; 8 import mde.input.exception; 7 9 8 10 import derelict.sdl.events; … … 14 16 logger = Log.getLogger ("mde.events"); 15 17 } 16 17 static bool run = true;18 18 19 19 void addEventsSchedule () { … … 27 27 case SDL_QUIT: 28 28 logger.info ("Quit requested"); 29 run = false;29 global.run = false; 30 30 break; 31 31 default: 32 input (event); 32 try { 33 global.input (event); 34 } catch (InputClassException e) { 35 logger.warn ("Caught input exception; event will be ignored. Exception was:"); 36 logger.warn (e.msg); 37 } 33 38 } 34 39 } mde/exception.d
r8 r10 5 5 * 6 6 * All packages should have their own base exception type extending this one, and for each package 7 * level a CTOR taking a message should pass the message to the super. The const string symbol 7 * level a CTOR taking a message should pass the message to the super. 8 * A CTOR not taking a message and calling the super without a parameter may also be provided. 9 * 10 * The static string symbol 8 11 * should be overriden as below so that it ends up as something like "mde.file" or 9 * "mde.pkg.file.Class" describing where the exception was thrown. 10 * A CTOR not taking a message and calling the super without a parameter may also be provided.12 * "mde.pkg.file.Class" describing where the exception was thrown. (Since only methods overload 13 * correctly, symbol is made static and an overloadable method is used to access the correct symbol.) 11 14 */ 12 15 class mdeException : Exception { 13 const symbol = "mde"; /// Override in derived classes to name the module where the error occured. 16 static const symbol = "mde"; /// Override in derived classes to name the module where the error occured. 17 char[] getSymbol () { 18 return symbol; 19 } 14 20 this (char[] msg) { 15 super( symbol~ ": " ~ msg);21 super(getSymbol() ~ ": " ~ msg); 16 22 } 17 23 this () { // No supplied error message. … … 21 27 22 28 class initException : mdeException { 23 const override symbol = super.symbol ~ ".init"; 29 // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can 30 static const char[] symbol; 31 static this () { symbol = super.symbol ~ ".init"; } 32 char[] getSymbol () { 33 return symbol; 34 } 35 24 36 this (char[] msg) { 25 37 super(msg); 26 38 } 27 39 } 40 41 debug (mdeUnitTest) { 42 import tango.util.log.Log : Log, Logger; 43 44 private Logger logger; 45 static this() { 46 logger = Log.getLogger ("mde.events"); 47 } 48 49 unittest { 50 // Check message prepending works correctly. 51 mdeException mE = new initException(""); 52 assert (mE.getSymbol() == "mde.init", mE.getSymbol()); 53 try { 54 throw new initException ("ABC"); 55 assert (false); 56 } catch (Exception e) { 57 assert (e.msg == "mde.init: ABC", e.msg); 58 } 59 60 logger.info ("Unittest complete."); 61 } 62 } mde/init.d
r9 r10 6 6 module mde.init; 7 7 8 publicimport mde.exception;8 import mde.exception; 9 9 10 10 import mde.events; 11 import global = mde.global; 11 12 import mde.input.input; 12 13 import mde.input.joystick; … … 14 15 // tango imports 15 16 import tango.core.Thread; 17 import tango.core.Exception; 16 18 import tango.util.log.Log : Log, Logger; 17 19 import tango.util.log.ConsoleAppender : ConsoleAppender; … … 20 22 import derelict.sdl.sdl; 21 23 import derelict.util.exception; 24 25 private Logger logger; 22 26 23 27 /** … … 29 33 static this() 30 34 { 31 // For now, just log to the console: 32 Logger root = Log.getRootLogger(); 33 root.setLevel(root.Level.Trace); 34 root.addAppender(new ConsoleAppender); 35 debug(mdeUnitTest) {} // For unittests, the logger is set up by test.mdeTest 36 else { 37 // For now, just log to the console: 38 Logger root = Log.getRootLogger(); 39 root.setLevel(root.Level.Trace); 40 root.addAppender(new ConsoleAppender); 41 } 42 43 logger = Log.getLogger ("mde.init"); 35 44 } 36 45 static ~this() … … 71 80 72 81 bool initFailure = false; // Set true if something goes wrong and we need to abort. 73 void setFailure () { // For synchronization, although may be unnecessary82 void setFailure () { // For synchronization, although shouldn't be necessary 74 83 synchronized initFailure = true; 75 84 } 76 85 void delegate() [] initFuncs = [ 77 86 delegate void() { 87 logger = Log.getLogger ("mde.init.Init.SDL"); 78 88 // Inits SDL and related stuff (joystick). 79 89 try { 80 // SDL Joystick, used by mde.input81 90 DerelictSDL.load(); 82 91 } catch (DerelictException de) { … … 112 121 try { // creating/starting threads can fail 113 122 foreach (func; initFuncs) tg.create(func); 114 } catch ( Exception e) { // should be a ThreadException, but catch all Exceptions123 } catch (ThreadException e) { // to do with threading; try without threads 115 124 logger.warn ("Caught exception while trying to create threads:"); 116 125 logger.warn (e.msg); … … 121 130 122 131 // Do some initialisation in the main thread 123 input.loadConfig (); // (may also create instance) 132 global.input = new Input(); 133 global.input.loadConfig (); // (may also create instance) 124 134 addEventsSchedule (); 125 135 … … 181 191 } 182 192 183 unittest {193 debug (mdeUnitTest) unittest { 184 194 /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts() 185 195 * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause … … 197 207 Init.runCleanupFcts(); 198 208 assert (!initialised); 199 } 209 210 logger.info ("Unittest complete."); 211 } mde/input/config.d
r9 r10 2 2 module mde.input.config; 3 3 4 debug import mde.text.format;4 debug import tango.scrapple.text.convert.parseFrom : parseFrom; 5 5 6 publicimport mde.input.exception;6 import mde.input.exception; 7 7 8 8 import MT = mde.mergetag.read; 9 import mde.text.parse;9 import tango.scrapple.text.convert.parseTo : parseTo; 10 10 11 11 import tango.util.log.Log : Log, Logger; … … 29 29 * These bitcodes are OR'd to the identifier code for the input device, to indicate which type 30 30 * of input they are for. E.g. when a key event is recieved with code x, look up 31 * $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ for other32 * libraries.31 * $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ 32 * for other libraries. 33 33 * 34 * For joystick hat events, a motion should be converted into up and down events on separate 35 * U,L,D,R positions and up and down events sent to the appropriate outputs. 34 * For joystick hat events, a motion is converted into up and down events on separate U,L,D,R 35 * positions and up and down events sent to the appropriate outputs (effectively making four 36 * buttons, some pairs of which can be pressed simultaneously). For output to axes, see 37 * A.JOYHAT_* . 36 38 */ 37 39 enum B : uint { 38 KEY = 0x8000_0000u, /// 0x8000_0000 u39 SDLKEY = 0x8800_0000u, /// 0x8800_0000 u40 MOUSE = 0x4000_0000u, /// 0x4000_0000 u41 JOYBUTTON = 0x2000_0000u, /// 0x2000_0000 u42 JOYHAT = 0x1000_0000u, /// 0x1000_0000 u43 JOYHAT_U = 0x1800_0000u, /// 0x1800_0000 u44 JOYHAT_D = 0x1400_0000u, /// 0x1400_0000 u45 JOYHAT_L = 0x1200_0000u, /// 0x1200_0000 u46 JOYHAT_R = 0x1100_0000u, /// 0x1100_0000 u40 KEY = 0x8000_0000u, /// 0x8000_0000 41 SDLKEY = 0x8800_0000u, /// 0x8800_0000 42 MOUSE = 0x4000_0000u, /// 0x4000_0000 43 JOYBUTTON = 0x2000_0000u, /// 0x2000_0000 44 JOYHAT = 0x1000_0000u, /// 0x1000_0000 45 JOYHAT_U = 0x1800_0000u, /// 0x1800_0000 46 JOYHAT_D = 0x1400_0000u, /// 0x1400_0000 47 JOYHAT_L = 0x1200_0000u, /// 0x1200_0000 48 JOYHAT_R = 0x1100_0000u, /// 0x1100_0000 47 49 } 48 50 49 51 /** Axis event type bit-codes 50 52 * 51 * Well, SDL only supports one type of axis now, but this could be extended in the future. 53 * SDL only supports one type of axis now, but this could be extended in the future. 54 * 55 * This can also be used to make joystick hats output to two axes (and can be used in 56 * conjuction with B.JOYHAT_* to output as buttons as well). 52 57 */ 53 58 enum A : uint { 54 JOYAXIS = 0x8000_0000u, /// 0x8000_0000u 59 JOYAXIS = 0x8000_0000u, /// 0x8000_0000 60 JOYHAT = 0x1000_0000u, /// 0x1000_0000 61 JOYHAT_LR = 0x1300_0000u, /// 0x1300_0000 62 JOYHAT_UD = 0x1C00_0000u, /// 0x1C00_0000 55 63 } 56 64 … … 60 68 */ 61 69 enum M : uint { 62 MOUSE = 0x8000_0000u, /// 0x8000_0000 u63 WMMOUSE = 0x8800_0000u, /// 0x8800_0000 u64 JOYBALL = 0x4000_0000u, /// 0x4000_0000 u70 MOUSE = 0x8000_0000u, /// 0x8000_0000 71 WMMOUSE = 0x8800_0000u, /// 0x8800_0000 72 JOYBALL = 0x4000_0000u, /// 0x4000_0000 65 73 } 66 74 67 75 /** Output queues: the core of the input configuration. 68 76 * 69 * button, axis and mouse each have their own index specifications. This is split into two parts: 77 * These are all mapped with a uint key; upon any event outQueues are looked for in the 78 * appropriate associative array with a key as follows. 79 * 80 * The index is split into two parts; 70 81 * the first byte specifies the type of input (given by the above enums), and the last three 71 82 * bytes define where the input comes from. 72 83 * 73 * For B.SDLKEY, the last three bytes are for the SDL keysym.84 * For type B.SDLKEY, the last three bytes are for the SDL keysym. 74 85 * For B.MOUSE, B.JOY*, A.JOY* & M.JOY*, the last three bytes are split into two sets of 12 75 86 * bits (with masks 0x00FF_F000 and 0x0000_0FFF), the higher of which specifies the device 76 87 * (which mouse or joystick), and the lower of which specifies the button/axis/ball. 77 88 * 78 * The code for mouse motion is currently only M.WMMOUSE. If/when multiple mice are supported 79 * new codes will be defined. 89 * For all three types of output, the outQueues are used as follows. The first value in the queue is 90 * read, and a function is called with the event details dependant on this; most of the time an 91 * output function is called directly. Other functions may be used, however, to allow further 92 * functionality such as modifier keys, timed keys, and key sequences. 93 * 94 * The output functions all have code 0x100; these read a single item from the outQueue which 95 * is the inputID the event outputs to (i.e. any callbacks at that ID called and status set for 96 * that ID). 97 * 80 98 */ 81 outQueue[ uint] button;82 outQueue[ uint] axis; /// ditto83 outQueue[ uint] mouse; /// ditto99 outQueue[][uint] button; 100 outQueue[][uint] axis; /// ditto 101 outQueue[][uint] relMotion; /// ditto 84 102 103 // FIXME: 85 104 char[] name; /// Name for user to save this under. 86 105 uint[] inheritants; /// Other profiles to inherit. … … 112 131 MT.ID[] file_configs; // active config sections (may not exist) 113 132 MT.ID[]* file_configs_p = cast(MT.ID[]*) (CONFIGS in file.dataset.header._charAA); 114 133 debug foreach (i,d; file.dataset.header._charAA) logger.trace ("ID: "~cast(char[])i); 134 debug foreach (i,d; file.dataset.header._charA) logger.trace ("ID: "~cast(char[])i); 135 115 136 if (file_configs_p) file.read(*file_configs_p); // restrict to this set IF a restriction was given 116 137 else file.read(); // otherwise read all … … 133 154 logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length)); 134 155 foreach (id, cfg; configs) { 135 logger.trace ("Section "~id~": " ~ format!(uint[][uint])(cfg.button)); 156 logger.trace ("Section " ~ id ~ 157 ":\n\tbutton:\t\t" ~ parseFrom!(uint[][][uint])(cfg.button) ~ 158 "\n\taxis:\t\t" ~ parseFrom!(uint[][][uint])(cfg.axis) ~ 159 "\n\trelMotion:\t" ~ parseFrom!(uint[][][uint])(cfg.relMotion) ); 136 160 } 137 161 } … … 146 170 void addTag (char[] tp, MT.ID id, char[] dt) { 147 171 if (tp == "uint[][uint]") { 148 if (id == QUEUE.BUTTON) button = cast(outQueue[ uint]) parse!(uint[][uint]) (dt);149 else if (id == QUEUE.AXIS) axis = cast(outQueue[ uint]) parse!(uint[][uint]) (dt);150 else if (id == QUEUE.MOUSE) mouse = cast(outQueue[uint]) parse!(uint[][uint]) (dt);172 if (id == QUEUE.BUTTON) button = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 173 else if (id == QUEUE.AXIS) axis = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 174 else if (id == QUEUE.MOUSE) relMotion = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 151 175 else logger.warn ("Unexpected tag encountered with ID " ~ cast(char[])id); 152 176 } // FIXME: add support for name and inheritants. 153 else throw new MT.MTUnknownTypeException ("Input Config: only uint[][ uint] type supported");177 else throw new MT.MTUnknownTypeException ("Input Config: only uint[][][uint] type supported"); 154 178 } 155 179 void writeAll (ItemDelg) { mde/input/exception.d
r8 r10 5 5 /// Base Input exception class. 6 6 class inputException : mdeException { 7 const override symbol = super.symbol ~ ".input"; 7 static const char[] symbol; 8 static this () { symbol = super.symbol ~ ".input"; } 9 char[] getSymbol () { 10 return symbol; 11 } 12 8 13 this (char[] msg) { 9 14 super(msg); … … 13 18 14 19 class InputClassException : inputException { 15 const override symbol = super.symbol ~ ".input.Input"; 20 static const char[] symbol; 21 static this () { symbol = super.symbol ~ ".input.Input"; } 22 char[] getSymbol () { 23 return symbol; 24 } 25 16 26 this (char[] msg) { 17 27 super(msg); … … 21 31 22 32 class ConfigLoadException : inputException { 23 const override symbol = super.symbol ~ ".config.Config"; 33 static const char[] symbol; 34 static this () { symbol = super.symbol ~ ".config.Config"; } 35 char[] getSymbol () { 36 return symbol; 37 } 38 24 39 this (char[] msg) { 25 40 super(msg); mde/input/input.d
r9 r10 7 7 // package imports 8 8 import mde.input.config; 9 publicimport mde.input.exception;9 import mde.input.exception; 10 10 11 11 // sdl imports 12 12 import derelict.sdl.events; 13 import derelict.sdl.types; // only SDL_PRESSED 14 import derelict.sdl.joystick; // SDL_HAT_* 13 15 14 16 import tango.util.log.Log : Log, Logger; 15 16 public Input input; /// Global instance of input.17 static this() {18 input = new Input();19 }20 17 21 18 /// Class encapsulating all input functionality. … … 25 22 typedef uint inputID; 26 23 alias void delegate(inputID, bool) ButtonCallback; 27 alias void delegate(inputID, real) AxisCallback; 28 alias void delegate(inputID, real,real) MouseCallback; 24 alias void delegate(inputID, short) AxisCallback; 25 alias void delegate(inputID, real,real) RelMotionCallback; 26 alias void delegate(ushort, ushort, ubyte, bool) MouseClickCallback; 29 27 30 28 /** Get key status at this ID. … … 32 30 * Returns: value (true = down, false = up) or false if no value at this ID. */ 33 31 bool getButton (inputID id) { 34 assert (this is input);35 32 bool* retp = id in button; 36 33 if (retp) return *retp; 37 34 else return false; 38 35 } 36 39 37 /** Get axis status at this ID. 40 38 * 41 * Returns: value ( range -1.0 .. 1.0) or 0.0 if no value at this ID. */42 realgetAxis (inputID id) {43 real* retp = id in axis;39 * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */ 40 short getAxis (inputID id) { 41 short* retp = id in axis; 44 42 if (retp) return *retp; 43 else return 0; 44 } 45 /** Get axis status at this ID. 46 * 47 * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */ 48 real getAxis1 (inputID id) { 49 short* retp = id in axis; 50 if (retp) return (*retp) * 3.0518509475997192e-05; 45 51 else return 0.0; 46 52 } 47 /** Get mouse pointer position in screen coordinates. 48 * 49 * Window managers only support one mouse, so there will only be one screen coordinate. 50 * Unlike everything else, this is not configurable. 51 */ 52 void mouseScreenPos (out uint x, out uint y) { 53 x = mouse_x; y = mouse_y; 54 } 55 /** Get relative mouse position (also for joystick balls). 53 54 /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call). 56 55 * 57 56 * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item). … … 62 61 * positions. 63 62 */ 64 void mouseRelativePos(inputID id, out real x = 0.0, out real y = 0.0) {65 RelPair* rp = id in axis_rel;63 void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) { 64 RelPair* rp = id in relMotion; 66 65 if (rp) { 67 66 x = rp.x; y = rp.y; 68 67 } 69 68 } 69 /** Get mouse pointer position in screen coordinates. 70 * 71 * Window managers only support one mouse, so there will only be one screen coordinate. 72 * Unlike nearly everything else, this is not configurable. 73 */ 74 void getMouseScreenPos (out uint x, out uint y) { 75 x = mouse_x; y = mouse_y; 76 } 70 77 // /// Is this modifier on? 71 78 //bool modifierStatus (inputID id); … … 76 83 */ 77 84 void addButtonCallback (inputID id, ButtonCallback dg) { 78 buttonCallbacks[id] = dg;85 buttonCallbacks[id] ~= dg; 79 86 } 80 87 81 88 /** Adds a callback delegate for axis events with this ID. 82 89 * 83 * Delegate receives event status .90 * Delegate receives event status (as per what getAxis returns). 84 91 */ 85 92 void addAxisCallback (inputID id, AxisCallback dg) { 86 axisCallbacks[id] = dg;93 axisCallbacks[id] ~= dg; 87 94 } 88 95 89 96 /** Adds a callback delegate for mouse motion/joystick ball events with this ID. 90 97 * 91 * Delegate receives event status. (A separate callback for mouse pointer position changes is not 98 * Delegate receives event status. As the name suggests, this is relative motion not screen 99 * position, with sensitivity adjustments applied. 100 * 101 * (A separate callback for mouse screen position changes is not 92 102 * necessary since this will be triggered by the same event - use mouseScreenPos from within the 93 103 * function to get new screen coordinates.) 94 104 */ 95 void addMouseCallback (inputID id, MouseCallback dg) { 96 mouseCallbacks[id] = dg; 105 void addRelMotionCallback (inputID id, RelMotionCallback dg) { 106 relMotionCallbacks[id] ~= dg; 107 } 108 109 /** Adds a callback delegate for all mouse clicks & releases. 110 * 111 * Delegate recieves x,y screen position (at time of click/release), button index (1 for left, 112 * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or 113 * released (true if pressed). 114 * 115 * The point of this over a standard button callback is firstly to avoid mouse configuration for 116 * the GUI, and secondly to give the pointer position at the time of the event, not the time the 117 * callback gets called. 118 */ 119 void addMouseClickCallback (MouseClickCallback dg) { 120 mouseClickCallbacks ~= dg; 97 121 } 98 122 … … 100 124 * 101 125 * Other types of event functions may be added. Returns true if the event was used, false if not. 126 * 127 * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should 128 * be fine. 102 129 */ 103 130 bool opCall (ref SDL_Event event) { 104 assert (this is input);105 131 switch (event.type) { 132 // Keyboard events: 133 case SDL_KEYDOWN: 134 case SDL_KEYUP: 135 outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button; 136 if (p) foreach (outQueue q; *p) { 137 bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q)); 138 } 139 break; 140 141 // Mouse events: 142 case SDL_MOUSEBUTTONDOWN: 143 case SDL_MOUSEBUTTONUP: 144 // Mouse clicks: 145 foreach (dg; mouseClickCallbacks) 146 dg (event.button.x, event.button.y, event.button.button, event.button.state == SDL_PRESSED); 147 148 // Button events: 149 outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button; 150 if (p) foreach (outQueue q; *p) { 151 bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q)); 152 } 153 break; 154 155 case SDL_MOUSEMOTION: 156 // Screen coordinates (set here since relMotion is also used for joystick balls): 157 mouse_x = event.motion.x; 158 mouse_y = event.motion.y; 159 160 // Relative motion: 161 outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion; 162 if (p) foreach (outQueue q; *p) { 163 mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q)); 164 } 165 break; 166 167 // Joystick events: 106 168 case SDL_JOYBUTTONDOWN: 107 169 case SDL_JOYBUTTONUP: 108 outQueue* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button; 109 if (p) bEventOut (event.jbutton.state == 0x1, readOutQueue(*p)); 110 break; 111 112 /+ 113 case SDL_KEYDOWN: 114 case SDL_KEYUP: 115 outQueue* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button; 116 if (p) eventstream.bEventOut (event.key.state == SDL_PRESSED, readOutQueue(*p)); 117 break; 118 case SDL_MOUSEMOTION: 119 mouse_x = event.motion.x; 120 mouse_y = event.motion.y; 121 outQueue* p = (Config.M.WMMOUSE) in config.mouse; 122 if (p) eventstream.mEventOut (event.motion.xrel, event.motion.yrel, readOutQueue(*p)); 123 +/ 170 outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button; 171 if (p) foreach (outQueue q; *p) { 172 bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q)); 173 } 174 break; 175 176 case SDL_JOYAXISMOTION: 177 outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis; 178 if (p) foreach (outQueue q; *p) { 179 aEvent (this, event.jaxis.value, readOutQueue(q)); 180 } 181 break; 182 183 case SDL_JOYBALLMOTION: // NOTE: untested 184 outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion; 185 if (p) foreach (outQueue q; *p) { 186 mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q)); 187 } 188 break; 189 190 case SDL_JOYHATMOTION: // NOTE: untested 191 static ubyte[uint] oldJHatVals; // necessary to store this to know which "axis" changed 192 193 uint index = (event.jhat.which << 12) | event.jhat.hat; 194 ubyte* oVal_p = index in oldJHatVals; 195 ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED; 196 197 // Carry out functionality for an axis. 198 void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) { 199 // Check if there's any change on this axis (if not, nothing to do): 200 ubyte filter = neg | pos; 201 if ((oldJHatVal & filter) != (event.jhat.value & filter)) { 202 // Now we know this axis changed position, so can unset old value and set 203 // new value (if not centre). 204 205 // Cancel old button status: 206 if (oldJHatVal & neg) { 207 outQueue[]* p = (neg_b | index) in config.button; 208 if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q)); 209 } else if (oldJHatVal & pos) { 210 outQueue[]* p = (pos_b | index) in config.button; 211 if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q)); 212 } 213 214 // Set new button status and position: 215 short position = 0; 216 if (event.jhat.value & neg) { 217 position = -32767; 218 outQueue[]* p = (neg_b | index) in config.button; 219 if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q)); 220 } else if (event.jhat.value & pos) { 221 position = 32767; 222 outQueue[]* p = (pos_b | index) in config.button; 223 if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q)); 224 } 225 226 // New axis event: 227 outQueue[]* p = (axis | index) in config.axis; 228 if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q)); 229 } 230 } 231 232 // Now run the code for each axis: 233 hatExamine (SDL_HAT_UP, SDL_HAT_DOWN, 234 Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD); 235 hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT, 236 Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR); 237 238 oldJHatVals[index] = event.jhat.value; 239 break; 240 241 // Other events: 124 242 default: 125 return false; 126 } 127 return true; 243 return false; // event not used 244 } 245 return true; // event used 128 246 } 129 247 … … 134 252 */ 135 253 void frameReset () { 136 foreach (rp; axis_rel) {254 foreach (rp; relMotion) { 137 255 rp.x = rp.y = 0.0; 138 256 } … … 143 261 * Returns: true if the requested config id wasn't found. 144 262 */ 145 bool loadConfig ( ) {263 bool loadConfig (char[] profile = "Default") { 146 264 Config.load("conf/input.mtt"); // FIXME: filename
