view mde/input/Input.d @ 132:264028f4115a

Cleaned up mde.imde and a couple of widget functions. New mde.menus module to add default menus. The input singleton is now created in mde.input.Input instead of mde.imde.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 23 Jan 2009 14:59:05 +0000
parents a2ef6b549101
children 4084f07f2c7a
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. */
    Input addButtonCallback (inputID id, ButtonCallback dg) {
        buttonCallbacks[id] ~= dg;
        return this;
    }

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

    /** 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.) */
    Input addRelMotionCallback (inputID id, RelMotionCallback dg) {
        relMotionCallbacks[id] ~= dg;
        return this;
    }
    
    /** 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. */
    Input addMouseClickCallback (MouseClickCallback dg) {
        mouseClickCallbacks ~= dg;
        return this;
    }
    
    /** 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.). */
    Input addMouseMotionCallback (MouseMotionCallback dg) {
        mouseMotionCallbacks ~= dg;
        return this;
    }
    
    /** 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.
     *
     * May throw InputClassExceptions (on configuration errors). Catching the
     * exception and continuing should be fine. */
    bool send (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).
        * 
        * If no config, exit from this switch. */
        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:
                if (!config) return false;	// event not used
        }
        if (!config) return true;		// event used
        
        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 {
            logger.error ("Config profile \""~profile~"\" not found: input won't work unless a valid profile is loaded!");
            throw new ConfigLoadException;
        }
    }
    
    /** For now, use as a singleton. (Could be changed later to allow multiple
     * "players". */
    static Input singleton () {
        if (instance is null)
            instance = new Input();
        return instance;
    }
    
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 Input instance;
    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.");
    }
}