view mde/input/Input.d @ 124:a2ef6b549101

Dynamic minimal size changing is now fully supported. Support for reducing minimal size in layouts. Editing numbers as text now always converts new number back to string at end of edit. Floating point number display format changed.
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 05 Jan 2009 12:43:27 +0000
parents 20f7d813bb0f
children 264028f4115a
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

/**
 * This module contains the interface to the input system; it should be the only module of the
 * input package imported from outside this package.
 */
module mde.input.Input;

// package imports
import mde.input.Config;
import mde.input.exception;

// sdl imports
import derelict.sdl.events;
import derelict.sdl.keyboard;
import derelict.sdl.types;	// only SDL_PRESSED
import derelict.sdl.joystick;	// SDL_HAT_*

import Utf = tango.text.convert.Utf;
import tango.util.log.Log : Log, Logger;

/**************************************************************************************************
 * Class encapsulating all input functionality.
 *
 * This class has several modes which affect output: interaction mode (default), text input mode,
 * mouse gui mode and axis/button binding modes.
 *
 * TODO: Gui mode and button capture and axis capture modes for key binding, disabling all
 * other modes (except gui-type mouse info?).
 * TODO: Possible revisions: remove by-index lookup, only providing callbacks?
 * TODO: Make callbacks send the time of the event?
 * TODO: Adjusters, e.g. double-press, hold/click differences. Axis output: via short or double?
 * TODO: add an Axis1Callback similar to getAxis1? Or remove getAxis1 and provide a conversion function?
 * TODO: allow callbacks to be removed. Currently not needed.
 * TODO: modifiers in text-input mode: shortcut handling? Global shortcuts - either mode?
 *
 * The primary mode is the interaction mode, mapping each button and axis to a configurable index,
 * and allowing event callback functions to be bound per index as well as allowing the state to be
 * looked up directly.
 * ---
 * // For keyboard, joystick and mouse button input
 * bool getButton (inputID id);
 * void addButtonCallback (inputID id, ButtonCallback dg);  // callback receives both up and down events
 *
 * // For joystick axis input
 * short getAxis (inputID id);      // range: -32767 .. 32767
 * double getAxis1 (inputID id);    // range: -1.0 .. 1.0
 * void addAxisCallback (inputID id, AxisCallback dg);
 *
 * // For mouse (and joystick ball) relative motion input
 * void getRelMotion (inputID id, out double x, out double y);
 * void addRelMotionCallback (inputID id, RelMotionCallback dg);
 * ---
 *
 * The keyboard can be put in text input mode, disabling interaction-mode keyboard access and
 * providing a callback called on each letter press with it's UTF-8 code. Setting a LetterCallback
 * activates text input mode and removing it disables this mode; only one may be active at once.
 * ---
 * void setLetterCallback (LetterCallback dg);
 * ---
 *
 * Mouse input can be recieved via gui-oriented click/coordinate callbacks in both interaction
 * mode and gui mode, however interaction-mode button and relative motion input is not received in
 * gui mode.
 * ---
 * void getMouseScreenPos (out uint x, out uint y);
 * void addMouseClickCallback (MouseClickCallback dg);
 * void addMouseMotionCallback (MouseMotionCallback dg);
 * ---
 *
 * The following methods are provided for setup & posting events:
 * ---
 * bool opCall (ref SDL_Event event);   // Handles an event, making all the above work
 * void frameReset ();  // Needs to be called once per frame for correct relative input
 * void loadConfig (char[] profile = "Default");    // Configuration for interaction-mode indexes
 * ---
 ***************************************************/
class Input
{
    /// Typedef for all indexes (type is uint).
    typedef uint				inputID;
    alias void delegate(inputID, bool)		ButtonCallback;
    alias void delegate(inputID, short)		AxisCallback;
    alias void delegate(inputID, double,double)	RelMotionCallback;
    alias void delegate(ushort, ushort, ubyte, bool)	MouseClickCallback;
    alias void delegate(ushort, ushort)		MouseMotionCallback;
    alias void delegate(ushort, char[])		LetterCallback;

    /** Get key status at this ID.
    *
    * Returns: value (true = down, false = up) or false if no value at this ID. */
    bool getButton (inputID id) {
        bool* retp = id in button;
        if (retp) return *retp;
        else return false;
    }
    
    /** Get axis status at this ID.
    *
    * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */
    short getAxis (inputID id) {
        short* retp = id in axis;
        if (retp) return *retp;
        else return 0;
    }
    /** Get axis status at this ID.
    *
    * Returns: value (double; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
    double getAxis1 (inputID id) {
        short* retp = id in axis;
        if (retp) return (*retp) * 3.0518509475997192e-05;
        else return 0.0;
    }
    
    /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call).
    *
    * Future: Converts to a double via sensitivity settings (defaults may be set and overriden per item).
    *
    * To avoid confusion over the ID here, the idea is for the input-layer upward to support
    * multiple mice, in case future platforms do.
    * Also joystick balls (supported by SDL) can be used in the same way as a mouse for relative
    * positions. */
    void getRelMotion (inputID id, out double x = 0.0, out double y = 0.0) {
        RelPair* rp = id in relMotion;
        if (rp) {
            x = rp.x;	y = rp.y;
        }
    }

    /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
    *
    * Delegate receives event status. */
    void addButtonCallback (inputID id, ButtonCallback dg) {
        buttonCallbacks[id] ~= dg;
    }

    /** Adds a callback delegate for axis events with this ID.
    *
    * Delegate receives event status (as per what getAxis returns). */
    void addAxisCallback (inputID id, AxisCallback dg) {
        axisCallbacks[id] ~= dg;
    }

    /** Adds a callback delegate for mouse motion/joystick ball events with this ID.
    *
    * Delegate receives event status. As the name suggests, this is relative motion not screen
    * position, with sensitivity adjustments applied.
    *
    * (A separate callback for mouse screen position changes is not
    * necessary since this will be triggered by the same event - use mouseScreenPos from within the
    * function to get new screen coordinates.) */
    void addRelMotionCallback (inputID id, RelMotionCallback dg) {
        relMotionCallbacks[id] ~= dg;
    }
    
    /** Adds a callback delegate for all mouse clicks & releases.
    *
    * Delegate recieves x,y screen position (at time of click/release), button index (1 for left,
    * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or
    * released (true if pressed).
    *
    * The point of this over a standard button callback is firstly to avoid mouse configuration for
    * the GUI, and secondly to give the pointer position at the time of the event, not the time the
    * callback gets called. */
    void addMouseClickCallback (MouseClickCallback dg) {
        mouseClickCallbacks ~= dg;
    }
    
    /** Adds a callback delegate for all mouse motion events.
    *
    * Really just for graphical user interfaces. Use addRelMotionCallback for relative motion (for
    * manipulating 3D views, etc.). */
    void addMouseMotionCallback (MouseMotionCallback dg) {
        mouseMotionCallbacks ~= dg;
    }
    
    /** Sets a callback delegate to recieve key presses as a Utf-8 char[].
    *
    * Since it is normal to type into only one location at once, setting a new LetterCallback
    * removes the last set one (however active ButtonCallbacks will still receive events).
    * Supplying a null delegate will turn off the slight overhead of unicode conversion.
    * 
    * The char[] received by the delegate must be copied and not stored or edited directly. */
    void setLetterCallback (LetterCallback dg = null) {
        if (dg) {
	    SDL_EnableUNICODE (1);
	    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	} else {
	    SDL_EnableUNICODE (0);
	    SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
	}
        letterCallback = dg;
    }

    /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
    *
    * Other types of event functions may be added. Returns true if the event was used, false if not
    * or no config was available. Hmm... doesn't seem very useful, but has practically no cost.
    * (Due to lack of use of this feature, false is returned even for used events when no config is
    * available).
    *
    * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should
    * be fine. */
    bool opCall (ref SDL_Event event) {
        /* Non-config events.
        *
        * Handle these first so that if no config exists some functionality at least is retained.
        *
        * Coordinates don't need adjusting (they put the top-left most pixel at 0,0).
        */
        switch (event.type) {
            case SDL_KEYDOWN:
                if (letterCallback) {
                    try
                        letterCallback (event.key.keysym.sym, Utf.toString ([cast(wchar)event.key.keysym.unicode], cast(char[])utfBuf));
                    catch (Exception e)
                        logger.error (CB_EXC ~ e.msg);
                }
                break;
            
            case SDL_MOUSEBUTTONDOWN:
            case SDL_MOUSEBUTTONUP:
                foreach (dg; mouseClickCallbacks) {
                    try
                        dg (event.button.x, event.button.y,
                            event.button.button, event.button.state == SDL_PRESSED);
                    catch (Exception e)
                        logger.error (CB_EXC ~ e.msg);
                }
                break;
            
            case SDL_MOUSEMOTION:
                foreach (dg; mouseMotionCallbacks) {
                    try
                        dg (event.motion.x, event.motion.y);
                    catch (Exception e)
                        logger.error (CB_EXC ~ e.msg);
                }
                break;
            
            default:
        }
        
        /* No config available, so don't try to access it and segfault.
        * Don't log a message because this function is called per-event (i.e. frequently).
        * A message should already have been logged by loadConfig anyway. */
        if (!config) return false;
        
        switch (event.type) {
            // Keyboard events:
            case SDL_KEYDOWN:
            case SDL_KEYUP:
		if (letterCallback) break;	// text input mode; no keyboard input from mappings
                outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
                if (p) foreach (outQueue q; *p) {
                    bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q));
                }
                break;
            
            // Mouse events:
            case SDL_MOUSEBUTTONDOWN:
            case SDL_MOUSEBUTTONUP:
                // Button events:
                outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button;
                if (p) foreach (outQueue q; *p) {
                    bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q));
                }
                break;
            
            case SDL_MOUSEMOTION:
                // Relative motion:
                outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion;
                if (p) foreach (outQueue q; *p) {
                    mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q));
                }
                break;
            
            // Joystick events:
            case SDL_JOYBUTTONDOWN:
            case SDL_JOYBUTTONUP:
                outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button;
                if (p) foreach (outQueue q; *p) {
                    bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q));
                }
                break;
            
            case SDL_JOYAXISMOTION:
                outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis;
                if (p) foreach (outQueue q; *p) {
                    aEvent (this, event.jaxis.value, readOutQueue(q));
                }
                break;
            
            case SDL_JOYBALLMOTION:
                outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion;
                if (p) foreach (outQueue q; *p) {
                    mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q));
                }
                break;
            
            case SDL_JOYHATMOTION:
                static ubyte[uint] oldJHatVals;		// necessary to store this to know which "axis" changed
        
                uint index = (event.jhat.which << 12) | event.jhat.hat;
                ubyte* oVal_p = index in oldJHatVals;
                ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED;
                
                // Carry out functionality for an axis.
                void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) {
                    // Check if there's any change on this axis (if not, nothing to do):
                    ubyte filter = neg | pos;
                    if ((oldJHatVal & filter) != (event.jhat.value & filter)) {
                        // Now we know this axis changed position, so can unset old value and set
                        // new value (if not centre).
                        
                        // Cancel old button status:
                        if (oldJHatVal & neg) {
                            outQueue[]* p = (neg_b | index) in config.button;
                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
                        } else if (oldJHatVal & pos) {
                            outQueue[]* p = (pos_b | index) in config.button;
                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
                        }
                        
                        // Set new button status and position:
                        short position = 0;
                        if (event.jhat.value & neg) {
                            position = -32767;
                            outQueue[]* p = (neg_b | index) in config.button;
                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
                        } else if (event.jhat.value & pos) {
                            position = 32767;
                            outQueue[]* p = (pos_b | index) in config.button;
                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
                        }
                        
                        // New axis event:
                        outQueue[]* p = (axis | index) in config.axis;
                        if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q));
                    }
                }
                
                // Now run the code for each axis:
                hatExamine (SDL_HAT_UP, SDL_HAT_DOWN,
                            Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD);
                hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT,
                            Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR);
                
                oldJHatVals[index] = event.jhat.value;
                break;
            
            // Other events:
            default:
                return false;	// event not used
        }
        return true;		// event used
    }
    
    /** Resets relative movement of mice / joystick balls to zero.
    *
    * Should be called once-per-frame if these are used, but must be called after their state has
    * been read (e.g. just before updating the input). */
    void frameReset () {
        foreach (rp; relMotion) {
            rp.x = rp.y = 0.0;
        }
    }
    
    /** Loads all configs, activating the requested id.
    *
    * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
    *   found. */
    void loadConfig (char[] file, char[] profile = "Default") {
        Config.load(file);
        Config* c_p = profile in Config.configs;
        if (c_p) config = *c_p;
        else {
            throw new ConfigLoadException;
            logger.error ("Config profile \""~profile~"\" not found: input won't work unless a valid profile is loaded!");
        }
    }
    
private:
    // Static constructor for event stream (fills es_*_fcts tables).
    static this () {
        es_b_fcts = [ ES_B.OUT : &es_b_out ];
        es_a_fcts = [ ES_A.OUT : &es_a_out, ES_A.REVERSE : &es_a_reverse ];
        es_m_fcts = [ ES_M.OUT : &es_m_out ];
        
        logger = Log.getLogger ("mde.input.Input");
    }
    
    struct RelPair {	// for mouse/joystick ball motion
        double x, y;
        static RelPair opCall (double a, double b) {
            RelPair ret;
            ret.x = a;	ret.y = b;
            return ret;
        }
    }
    
    static const CB_EXC = "Callback exception: ";
    
    static Logger logger;

    Config config;		// Configuration
    char[6] utfBuf;		// Buffer for Utf.toString; reallocates if less than 5.
    
    bool[inputID] button;	// Table of button states
    short[inputID] axis;	// Table of axes states
    RelPair[inputID] relMotion;	// Table of relative mouse / joystick ball motions
    
    // NOTE: currently no means of removal
    ButtonCallback[][inputID]	buttonCallbacks;
    AxisCallback[][inputID]	axisCallbacks;
    RelMotionCallback[][inputID] relMotionCallbacks;
    MouseClickCallback[]	mouseClickCallbacks;
    MouseMotionCallback[]	mouseMotionCallbacks;
    LetterCallback              letterCallback;
    
    //BEGIN Event stream functionality
    /* This section contains functions called on an event, which may modify the event (adjuster
    * functions), and finally output to one (or more) of the state tables (the event stream).
    *
    * Adjuster and other event functions should have a format to fit the ES_X_Func types, for X is
    * B (button event), A (axis event) or M (mouse relative motion event or joystick ball event).
    * Adjusters should call one of the xEvent() functions with their output and the remainder of
    * the readOutQueue.
    *
    * To control which adjusters get called and pass parameters, a stack of sorts is used:
    * outQueue. */
    //BEGIN ES Definitions
    /* Note: We really want an array, not a stack. We cannot edit the lists, so we can either
    * copy to a stack or just iterate through it as an array. */
    alias Config.outQueue outQueue;
    struct readOutQueue {		// A convenient structure for reading an outQueue item by item.
        private Config.outQueue _q;		// the queue, stored by reference to the original
        private uint p = 0;		// current read position (start at beginning)
    
        static readOutQueue opCall (Config.outQueue q) {	// Static constructor
            readOutQueue ret;
            ret._q = q;
            return ret;
        }
        uint pop () {			// Get the next element and advance. Throws an exception if there isn't another.
            if (p >= _q.length)
                throw new InputClassException ("Input: Invalid configuration: incomplete config stack");
            uint ret = _q[p];
            ++p;
            return ret;
        }
        debug uint next () {		// Get the next element. No checks; for debug use only.
            return _q[p];
        }
    }
    
    // These aliases are for pointers to the event functions.
    // These need to use "this", but cannot be delegates because they musn't be bound to a
    // particular instance of Input, hence this must be passed when called.
    alias void function (Input, bool, readOutQueue) ES_B_Func;
    alias void function (Input, short, readOutQueue) ES_A_Func;
    alias void function (Input, short, short, readOutQueue) ES_M_Func;
    
    /* These are the codes allowing the config to specify event functions.
    *
    * They are organised as defined in doc/input_ID_assignments. */
    enum ES_B : uint {
        OUT	= 0x1000u,
    }
    enum ES_A : uint {
        OUT	= 0x1000u,
        REVERSE	= 0x2000u,
    }
    enum ES_M : uint {
        OUT	= 0x1000u,
    }
    //END ES Definitions
    
    // ES Data:
    // These are the tables for looking up which event function to call.
    static ES_B_Func[uint] es_b_fcts;
    static ES_A_Func[uint] es_a_fcts;
    static ES_M_Func[uint] es_m_fcts;
    
    //BEGIN ES Functions
    // These 3 functions pass an event to the appropriate event function (adjuster or output func).
    // They are used to start and continue an event stream.
    // They must be static to allow calling from event functions.
    const EVCONF_ERR = "Invalid configuration: bad event function code";
    static void bEvent (Input myThis, bool b, readOutQueue s)
    {
        ES_B_Func* func = (s.pop() in es_b_fcts);
        if (func != null) (*func)(myThis, b, s);
        else throw new InputClassException (EVCONF_ERR);
    }
    static void aEvent (Input myThis, short x, readOutQueue s)
    {
        ES_A_Func* func = (s.pop() in es_a_fcts);
        if (func != null) (*func)(myThis, x, s);
        else throw new InputClassException (EVCONF_ERR);
    }
    // Only handles relative output since position-on-screen is not stored with an ID:
    static void mEvent (Input myThis, short x, short y, readOutQueue s)
    {
        ES_M_Func* func = (s.pop() in es_m_fcts);
        if (func != null) (*func)(myThis, x, y, s);
        else throw new InputClassException (EVCONF_ERR);
    }
    
    // The remaining functions are the stream functions, for adjusting and outputting an event.
    // They need to work like non-static functions, but are called via a function pointer, hencne
    // should be static with their first parameter being instead of this.
    
    // Simple output function
    static void es_b_out (Input myThis, bool b, readOutQueue s) {
        inputID id = cast(inputID) s.pop();
                
        myThis.button[id] = b;
        
        ButtonCallback[]* cb_p = id in myThis.buttonCallbacks;
        if (cb_p) foreach (cb; *cb_p) {
            try
                cb (id, b);
            catch (Exception e)
                logger.error (CB_EXC ~ e.msg);
        }
    }
    // Adjuster to check modifier keys
    static void es_b_modifier (Input myThis, bool b, readOutQueue s);

    // Simple output function
    static void es_a_out (Input myThis, short x, readOutQueue s) {
        inputID id = cast(inputID) s.pop();
        
        myThis.axis[id] = x;
        
        AxisCallback[]* cb_p = id in myThis.axisCallbacks;
        if (cb_p) foreach (cb; *cb_p) {
            try
                cb (id, x);
            catch (Exception e)
                logger.error (CB_EXC ~ e.msg);
        }
    }
    
    // Just reverses an axis's value
    static void es_a_reverse (Input myThis, short x, readOutQueue s) {
        aEvent (myThis, -x, s);
    }

    // Simple output function
    static void es_m_out (Input myThis, short x, short y, readOutQueue s) {
        inputID id = cast(inputID) s.pop();
        
        myThis.relMotion[id] = RelPair(x,y);
        
        RelMotionCallback[]* cb_p = id in myThis.relMotionCallbacks;
        if (cb_p) foreach (cb; *cb_p) {
            try
                cb (id, x,y);
            catch (Exception e)
                logger.error (CB_EXC ~ e.msg);
        }
    }
    //END ES Functions
    //END Event stream functionality
    
    
    /* This unittest covers input.config.Config and input.input.Input for some events of all types.
    * It is not bullet-proof, and does not cover the stream-functions other than the direct output
    * ones (largely because these may get added at any time and tested via input tests).
    *
    * What it does test:
    *	Events of all types.
    *	Callbacks of all types.
    *	Status set from events for all types (button,axis,relMotion).
    *
    * It relies on config loaded from a file (dependant on where input bindings are loaded from;
    * currently conf/input.mtt). */
    debug (mdeUnitTest) unittest {
        Input ut = new Input();
        ut.loadConfig ("InputConfig");
	
        int[6] counters;	// counters for callbacks
        // Should become: [2,2,0,2,1,1]
	
        //BEGIN Set up some callbacks
        ut.addButtonCallback (0x03F0, delegate void(inputID id, bool status) {
            assert (status == !counters[0]);	// true first call, false next
            counters[0] += 1;
        });
        ut.addButtonCallback (0x06F0, delegate void(inputID id, bool status) {
            assert (status == !counters[1]);	// true first call, false next
            counters[1] += 1;
        });
        ut.addButtonCallback (0x07F0, delegate void(inputID id, bool status) {
            counters[2] += 1;
        });
        ut.addAxisCallback (0x22F0, delegate void(inputID id, short x) {
            assert (x == (counters[3] ? 0 : 32767));
            counters[3] += 1;
        });
        ut.addRelMotionCallback (0x11F0, delegate void(inputID id, double x, double y) {
            assert (x == 14.0);
            assert (y == -1.0);
            counters[4] += 1;
        });
        ut.addMouseClickCallback (delegate void(ushort x, ushort y, ubyte b, bool s) {
            assert (x == 291);
            assert (y == 10010);
            assert (b == 3);
            assert (s == false);
            counters[5] += 1;
        });
        //END Set up some callbacks
                        
        //BEGIN Post a lot of events
        SDL_Event e;
            
        // F11 down:
        e.type = SDL_KEYDOWN;
        e.key.state = SDL_PRESSED;
        e.key.keysym.sym = 292;	// SDLK_F11
        ut(e);
        // Right mouse button up:
        e.type = SDL_MOUSEBUTTONUP;
        e.button.button = 3;	// SDL_BUTTON_RIGHT
        e.button.state = SDL_RELEASED;
        e.button.x = 291;
        e.button.y = 10010;
        ut(e);
        // Mouse motion:
        e.type = SDL_MOUSEMOTION;
        e.motion.x = 63;
        e.motion.y = 44;
        e.motion.xrel = 14;
        e.motion.yrel = -1;
        ut(e);
        // Joystick 2 button 5 down:
        e.type = SDL_JOYBUTTONDOWN;
        e.jbutton.which = 2;
        e.jbutton.button = 5;
        e.jbutton.state = SDL_PRESSED;
        ut(e);
        // Same button released:
        e.jbutton.state = SDL_RELEASED;
        ut(e);
        // Joystick 1 axis 8 motion:
        e.type = SDL_JOYAXISMOTION;
        e.jaxis.which = 1;
        e.jaxis.axis = 8;
        e.jaxis.value = 32767;
        ut(e);
        // Joystick 22 ball 100 motion:
        e.type = SDL_JOYBALLMOTION;
        e.jball.which = 22;
        e.jball.ball = 100;
        e.jball.xrel = -21;
        e.jball.yrel = 1024;
        ut(e);
        // Joystick 214 hat 12 DOWN-RIGHT:
        e.type = SDL_JOYHATMOTION;
        e.jhat.which = 214;
        e.jhat.hat = 12;
        e.jhat.value = SDL_HAT_RIGHTDOWN;
        ut(e);
        // Same hat LEFT:
        e.jhat.value = SDL_HAT_LEFT;
        ut(e);
        //END Post a lot of events
            
        //BEGIN Check states
        assert (ut.getButton(0xF0) == true);
        assert (ut.getButton(0x1F0) == false);
        assert (ut.getButton(0x2F0) == false);
        assert (ut.getButton(0x4F0) == false);
        assert (ut.getButton(0x5F0) == true);
        assert (ut.getButton(0x6F0) == false);
        assert (ut.getButton(0x7F0) == false);
            
        assert (ut.getAxis(0x20F0) == 32767);
        assert (ut.getAxis(0x21F0) == -32767);
        assert (ut.getAxis1(0x22F0) == 0.0);
            
        double s,t;
        ut.getRelMotion(0x10F0, s,t);
        assert (s == 14.0);
        assert (t == -1.0);
        ut.getRelMotion(0x12F0, s,t);
        assert (s == -21.0);
        assert (t == 1024.0);
                                                
        assert (counters == [2,2,0,2,1,1]);
        //END Check states
        
        logger.info ("Unittest complete.");
    }
}