view mde/input/input.d @ 7:b544c3a7c9ca

Some changes to exceptions and a few more debug commands. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 16 Jan 2008 12:48:07 +0000
parents 9a990644948c
children f63f4f41a2dc
line wrap: on
line source

/**
 * 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;

class Input
{
    /// Typedef for all indexes (type is uint).
    typedef uint				inputID;
    alias void delegate(inputID, bool)		ButtonCallback;
    alias void delegate(inputID, real)		AxisCallback;
    alias void delegate(inputID, real,real)	MouseCallback;
        
    /// Stores a static instance of Input for most usage.
    static Input instance () {
        static Input instance;
    
        if (instance is null) instance = new Input;
        return instance;
    }

    /** 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 = cast(inputID) id in button;
        if (retp) return *retp;
        else return false;
    }
    /** Get axis status at this ID.
    *
    * Returns: value (range -1.0 .. 1.0) or 0.0 if no value at this ID. */
    real getAxis (inputID id) {
        real* retp = cast(inputID) id in axis;
        if (retp) return *retp;
        else return 0.0;
    }
    /** Get mouse pointer position in screen coordinates.
    *
    * Window managers only support one mouse, so there will only be one screen coordinate.
    * Unlike everything else, this is not configurable.
    */
    void mouseScreenPos (out uint x, out uint y) {
        x = mouse_x;	y = mouse_y;
    }
    /** Get relative mouse position (also for joystick balls).
    *
    * Future: Converts to a real 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 mouseRelativePos (inputID id, out real x = 0.0, out real y = 0.0) {
        RelPair* rp = cast(inputID) id in axis_rel;
        if (rp) {
            x = rp.x;	y = rp.y;
        }
    }
    // /// Is this modifier on?
    //bool modifierStatus (inputID id);

    /** 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.
    */
    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. (A separate callback for mouse pointer 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 addMouseCallback (inputID id, MouseCallback dg) {
        mouseCallbacks[id] = 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.
    */
    bool opCall (ref SDL_Event event) {
        switch (event.type) {
            case SDL_JOYBUTTONDOWN:
            case SDL_JOYBUTTONUP:
                outQueue* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button;
                if (p) bEventOut (event.jbutton.state == 0x1, readOutQueue(*p));
            break;
        
            /+
            case SDL_KEYDOWN:
            case SDL_KEYUP:
            outQueue* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
            if (p) eventstream.bEventOut (event.key.state == SDL_PRESSED, readOutQueue(*p));
            break;
            case SDL_MOUSEMOTION:
            mouse_x = event.motion.x;
            mouse_y = event.motion.y;
            outQueue* p = (Config.M.WMMOUSE) in config.mouse;
            if (p) eventstream.mEventOut (event.motion.xrel, event.motion.yrel, readOutQueue(*p));
            +/
            default:
            return false;
        }
        return true;
    }
    
    /** Resets relative movement of mice / joystick balls to zero.
    *
    * Should probably be called once-per-frame if these are used.
    */
    void frameReset () {
        foreach (rp; axis_rel) {
            rp.x = rp.y = 0.0;
        }
    }
    
    /** Loads all configs, activating the requested id.
    *
    * Returns: true if the requested config id wasn't found.
    */
    bool loadConfig (uint id) {
        Config.load("conf/input.mtt");	// FIXME: filename
        Config* c_p = id in Config.configs;
        if (c_p) {
            config = *c_p;
            return false;
            logger.info ("Succesfully loaded config.");
        }
        return true;
    }
    
private:
    // Static constructor for event stream (fills es_*_fcts tables).
    static this () {
        es_b_fcts = [ ES_B_OUT : &es_b_out ];
    }
    
    struct RelPair {	// for mouse/joystick ball motion
        real x, y;
        static RelPair opCall (real a, real b) {
            RelPair ret;
            ret.x = a;	ret.y = b;
            return ret;
        }
    }
    
    Config config;			// Configuration
    
    bool[inputID] button;		// Table of button states
    real[inputID] axis;			// Table of axes states
    ushort mouse_x, mouse_y;		// Current screen coords of the mouse
    // FIXME: might need a bit of work... at any rate defining a default ID.
    RelPair[inputID] axis_rel;		// Table of relative mouse / joystick ball motions
    
    // FIXME: these need to be more like multimaps, supporting multiple dgs (also some means of removal?)
    ButtonCallback[inputID] buttonCallbacks;
    AxisCallback[inputID] axisCallbacks;
    MouseCallback[inputID] mouseCallbacks;
        
    //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 xEventOut() 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 next () {			// Get the next element. 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;
        }
    }
    
    // These aliases are for pointers to the event functions.
    alias void function (bool, readOutQueue) ES_B_Func;
    alias void function (short, readOutQueue) ES_A_Func;
    alias void function (short, short, readOutQueue) ES_M_Func;
    
    // These are the codes allowing the config to specify event functions:
    enum : uint {
        ES_B_OUT	= 0x0000_0100u,
        ES_A_OUT	= 0x0000_0200u,
        ES_M_OUT	= 0x0000_0300u,
    }
    //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.
    void bEventOut (bool b, readOutQueue s)
    {
        ES_B_Func* func = (s.next() in es_b_fcts);
        if (func != null) (*func)(b,s);
        else throw new InputClassException ("Input: Invalid configuration: bad event function code");
    }
    void aEventOut (short x, readOutQueue s)
    {
        ES_A_Func* func = (s.next() in es_a_fcts);
        if (func != null) (*func)(x,s);
        else throw new InputClassException ("Input: Invalid configuration: bad event function code");
    }
    void mEventOut (short x, short y, readOutQueue s)
    {
        ES_M_Func* func = (s.next() in es_m_fcts);
        if (func != null) (*func)(x,y,s);
        else throw new InputClassException ("Input: Invalid configuration: bad event function code");
    }
    
    // The remaining functions are the stream functions, for adjusting and outputting an event.
    
    // Simple output function
    void es_b_out (bool b, readOutQueue s) {
        inputID id = cast(inputID) s.next();
        button[id] = b;
        ButtonCallback* cb_p = id in buttonCallbacks;
        if (cb_p) (*cb_p) (id, b);
    }
    // Adjuster to check modifier keys
    void es_b_modifier (bool b, readOutQueue s);

    /* Simple output function

    Adds 1-2 items on the stack.
    */
    void es_a_out (short x, readOutQueue s) {
        real y = x;
        uint conf = s.next();
        enum : uint {
            HALF_RANGE	= 0x8000_0000u,
            SENSITIVITY	= 0x0080_0000u,
        }
        // Convert ranges into standard intervals (with or without reverse values)
        if (conf & HALF_RANGE) y = (y + 32767.0) * 1.5259254737998596e-05;	// range  0.0 - 1.0
        else y *= 3.0518509475997192e-05;					// range -1.0 - 1.0
        real a;
        if (conf & SENSITIVITY) a = s.next();
        /+ When a global sensitivity is available (possibly only use if it's enabled)...
        else a = axis.sensitivity;
        y = sign(y) * pow(abs(y), a);		// sensitivity adjustment by a +/
        axis[cast(inputID) s.next()] = y;
    }

    // Simple output function
    void es_m_out (short x, short y, readOutQueue s) {
        axis_rel[cast(inputID) s.next()] = RelPair(x,y);
    }
    //END ES Functions
    //END Event stream functionality
}