view mde/input/input.d @ 9:1885a9080f2a

Joystick button input now works with config. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 30 Jan 2008 11:33:56 +0000
parents f63f4f41a2dc
children 4c3575400769
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;
public import mde.input.exception;

// sdl imports
import derelict.sdl.events;

import tango.util.log.Log : Log, Logger;

public Input input;			/// Global instance of input.
static this() {
    input = new Input();
}

/// Class encapsulating all input functionality.
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;

    /** Get key status at this ID.
    *
    * Returns: value (true = down, false = up) or false if no value at this ID. */
    bool getButton (inputID id) {
        assert (this is input);
        bool* retp = 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 = 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 = 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) {
        assert (this is input);
        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 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; 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 () {
        Config.load("conf/input.mtt");	// FIXME: filename
        Config* c_p = "Default" in Config.configs;
        if (c_p) {
            config = *c_p;
            return false;
        }
        debug logger.warn ("Config \"Default\" not found.");
        return true;
    }
    
private:
    // Static constructor for event stream (fills es_*_fcts tables).
    static this () {
        es_b_fcts = [ ES_B_OUT : &es_b_out ];
        
        logger = Log.getLogger ("mde.input.input.Input");
    }
    
    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;
        }
    }
    
    static Logger logger;

    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 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:
    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.
    const EVCONF_ERR = "Input: Invalid configuration: bad event function code";
    void bEventOut (bool b, readOutQueue s)
    {
        ES_B_Func* func = (s.pop() in es_b_fcts);
        if (func != null) (*func)(this, b, s);
        else throw new InputClassException (EVCONF_ERR);
    }
    void aEventOut (short x, readOutQueue s)
    {
        ES_A_Func* func = (s.pop() in es_a_fcts);
        if (func != null) (*func)(this, x, s);
        else throw new InputClassException (EVCONF_ERR);
    }
    void mEventOut (short x, short y, readOutQueue s)
    {
        ES_M_Func* func = (s.pop() in es_m_fcts);
        if (func != null) (*func)(this, 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) (*cb_p) (id, b);
    }
    // Adjuster to check modifier keys
    void es_b_modifier (Input myThis, bool b, readOutQueue s);

    /* Simple output function

    Adds 1-2 items on the stack.
    */
    void es_a_out (Input myThis, short x, readOutQueue s) {
        real y = x;
        uint conf = s.pop();
        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.pop();
        /+ 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 +/
        myThis.axis[cast(inputID) s.pop()] = y;
    }

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