view mde/gui/WidgetManager.d @ 76:65780e0e48e6

Re-enabled click event passing in the gui to make ButtonWidget work. Bugfix (pass void* not class reference). Change to allow compilation with dmd 1.027 (don't use DefaultData's Arg!() template).
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 28 Jul 2008 18:49:18 +0100
parents 25cb7420dc91
children ea58f277f487
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/>. */

/*************************************************************************************************
 * The gui manager class.
 *
 * This is the module to use externally to create a graphical user interface (likely also with
 * content modules).
 *************************************************************************************************/
module mde.gui.WidgetManager;

public import mde.gui.WidgetData;
import mde.gui.widget.Ifaces;
import mde.gui.renderer.createRenderer;

// For adding the input event callbacks and requesting redraws:
import imde = mde.imde;
import mde.input.Input;
import mde.scheduler.Scheduler;

import tango.core.sync.Mutex;
import tango.util.log.Log : Log, Logger;

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.gui.WidgetManager");

    gui = new WidgetManager ("gui");
}

WidgetManager gui;


/*************************************************************************************************
 * The widget manager.
 * 
 * This is responsible for loading and saving an entire gui (although more than one may exist),
 * controlling the rendering device (e.g. the screen or a texture), and providing user input.
 * 
 * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate
 * them and possibly drop events for some uses, such as if the gui is drawn to a texture.
 * 
 * Aside from the IWidgetManager methods, this class should be thread-safe.
 *************************************************************************************************/
class WidgetManager : WidgetLoader {
    /** Construct a new widget manager.
     * 
     * params:
     *  fileName = Name of file specifying the gui, excluding path and extension.
     */
    this (char[] file) {
        super(file);
        
        // Events we want to know about:
        imde.input.addMouseClickCallback(&clickEvent);
        imde.input.addMouseMotionCallback(&motionEvent);
    }
    
    
    /** Draw the gui. */
    void draw() {
        synchronized(mutex)
            child.draw;
    }
    
    
    /** For mouse click events.
     *
     * Sends the event on to the relevant windows and all click callbacks. */
    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
        debug scope (failure)
            logger.warn ("clickEvent: failed!");
        mutex.lock;
        scope(exit) mutex.unlock;
        
        // NOTE: buttons receive the up-event even when drag-callbacks are in place.
        foreach (dg; clickCallbacks)
            // See IWidgetManager.addClickCallback's documentation:
            if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
        
        // NOTE: do we need to test if the click was on the gui (and thus child)?
        IChildWidget widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
        if (widg !is null)
            widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
        /+ FIXME: remove
        foreach (i,w; windows) {
            IWidget widg = w.getWidget (cast(wdabs)cx,cast(wdabs)cy);
            if (widg !is null) {
                // Bring to front
                windows = w ~ windows[0..i] ~ windows[i+1..$];
                
                widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
                requestRedraw;	// in case we've only moved to front
                return;     // only pass to first window
            }
        }+/
    }
    
    /** For mouse motion events.
     *
     * Sends the event on to all motion callbacks. */
    void motionEvent (ushort cx, ushort cy) {
        debug scope (failure)
                logger.warn ("motionEvent: failed!");
        mutex.lock;
        scope(exit) mutex.unlock;
        
        foreach (dg; motionCallbacks)
            dg (cast(wdabs)cx, cast(wdabs)cy);
    }
    
    
    void setSize (int x, int y) {
        mutex.lock;
        scope(exit) mutex.unlock;
        
        w = cast(wdim) x;
        h = cast(wdim) y;
        
        if (child is null)
            return;     // May not have been created before this is first run.
        child.setWidth  (w, -1);
        child.setHeight (h, -1);
        child.setPosition (0,0);
    }
    
    //BEGIN IWidgetManager methods
    // These methods are only intended for use within the gui package. They are not necessarily
    // thread-safe.
    IRenderer renderer () {
        assert (rend !is null, "WidgetManager.renderer: rend is null");
        return rend;
    }
    
    void requestRedraw () {
        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
    }
    
    void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) {
        clickCallbacks[dg.ptr] = dg;
    }
    void addMotionCallback (void delegate(wdabs, wdabs) dg) {
        motionCallbacks[dg.ptr] = dg;
    }
    void removeCallbacks (void* frame) {
        clickCallbacks.remove(frame);
        motionCallbacks.remove(frame);
    }
    //END IWidgetManager methods
    
protected:
    /* Second stage of widget loading. */
    void createRootWidget () {
        // The renderer needs to be created on the first load, but not after this.
        if (rend is null)
            rend = createRenderer (rendName);
        
        child = makeWidget ("root");
        
        child.setWidth  (w, -1);
        child.setHeight (h, -1);
        child.setPosition (0,0);
    }
    
private:
    // callbacks indexed by their frame pointers:
    bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
    void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
    IRenderer rend;
    wdim w,h;       // area available to the widgets
}