view mde/gui/widget/Widget.d @ 95:2a364c7d82c9

Boolean options can be adjusted from the gui now (using a very basic widget). Also some bug-fixes. Fixed a minor bug where layouts with the same id but without shared alignments would be messed up. Tracked down the "nothing trawn until a resize" bug (see jobs.txt). If widgets throw during creation they're now replaced by debug widgets. Function pointers are converted to delegates using a safer method.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 06 Nov 2008 11:07:18 +0000
parents 08a4ae11454b
children dbf332403c6e
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/>. */

/*************************************************************************************************
 * GUI Widget module.
 *
 * This module contains some base widget classes suitable for widget classes to inherit. However,
 * inheriting one of them is by no means necessary for a widget so long as the IWidget interface
 * is implemented.
 * 
 * Abstract widget classes have an 'A' prepended to the name, similar to the 'I' convention for
 * interfaces.
 *************************************************************************************************/
module mde.gui.widget.Widget;

public import mde.gui.widget.Ifaces;
import mde.gui.exception;

debug {
    import tango.util.log.Log : Log, Logger;
    private Logger logger;
    static this () {
        logger = Log.getLogger ("mde.gui.widget.Widget");
    }
}


/*************************************************************************************************
 * An abstract base widget class.
 *
 * This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a
 * useful basic implementation for widgets. Widgets need not inherit these (they only need
 * implement IWidget); they are simply provided for convenience and to promote code reuse.
 *************************************************************************************************/
abstract class AWidget : IChildWidget
{
//BEGIN Load and save
    // Base this() for child Widgets.
    protected this (IWidgetManager mgr, widgetID id, WidgetData) {
        this.mgr = mgr;
        this.id = id;
    }
    
    // Most widgets don't need this; all initialization os usually done in this()
    void prefinalize () {}
    void finalize () {}
    
    // ParentWidget is inteded for parent widgets to derive
    IChildWidget[] children () {
        return null;
    }
    
    // Don't save any data: fine for many widgets.
    bool saveChanges () {
        return false;
    }
    
    // Very basic implementation which assumes the renderer cannot affect the widget's size.
    bool rendererChanged () {
        return false;
    }
//END Load and save
    
//BEGIN Size and position
    bool isWSizable () {    return false;   }
    bool isHSizable () {    return false;   }
    
    /* Return minimal/fixed size. */
    wdim minWidth () {
        return mw;
    }
    wdim minHeight () {
        return mh;
    }
    
    wdim width () {
        return w;
    }
    wdim height() {
        return h;
    }
    
    deprecated void getCurrentSize (out wdim cw, out wdim ch) {
        cw = w;
        ch = h;
    }
    
    /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow
     * enlarging, so in both cases this is a correct implementation. */
    void setWidth (wdim nw, int) {
        debug if (nw < mw) logger.warn ("Widget width set below minimal size");
        w = (nw >= mw ? nw : mw);
    }
    void setHeight (wdim nh, int) {
        debug if (nh < mh) logger.warn ("Widget height set below minimal size");
        h = (nh >= mh ? nh : mh);
    }
    
    void setPosition (wdim nx, wdim ny) {
        x = nx;
        y = ny;
    }
//END Size and position
    
//BEGIN Events
    /* This method is only called when the location is over this widget; hence for all widgets
     * without children this method is valid. */
    IChildWidget getWidget (wdim cx, wdim cy) {
        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
        return this;
    }
    
    /* Dummy event method (suitable for all widgets which don't respond to events). */
    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {}
//END Events
    
    /* Basic draw method: draw the background (all widgets should do this). */
    void draw () {
        mgr.renderer.drawWidgetBack (x,y, w,h);
    }
    
protected:
    /**********************************************************************************************
     * Widgets may use WDCheck as a utility to check what data holds. Its use is encouraged, so
     * that the checks can easily be updated should WidgetData be changed.
     * 
     * Params:
     *  data    = the WidgetData to check lengths of
     *  n_ints  = number of integers wanted
     *  n_strings= number of strings (default 0 since not all widgets use strings)
     *********************************************************************************************/
    void WDCheck (WidgetData data, size_t n_ints, size_t n_strings = 0) {
    if (data.ints.length    != n_ints ||
        data.strings.length != n_strings)
        throw new WidgetDataException (this);
    }
    
    widgetID id;                // The widget's ID, used for saving data
    IWidgetManager mgr;		// the enclosing window
    wdim x, y;			// position
    wdim w, h;			// size
    wdim mw = 0, mh = 0;	// minimal or fixed size, depending on whether the widget is
    				// resizible; both types of widgets should actually be expandable.
}

/*************************************************************************************************
* An abstract base widget class for parent widgets.
*************************************************************************************************/
abstract class AParentWidget : AWidget
{
    this (IWidgetManager mgr, widgetID id, WidgetData data) {
        super (mgr, id, data);
    }
    
    IChildWidget[] children () {
        return subWidgets;
    }
    
    bool saveChanges () {
        bool c = false;
        foreach (w; subWidgets)
            c |= w.saveChanges;
        return c;
    }
    
protected:
    IChildWidget[] subWidgets;
}

/** A base for fixed-size widgets taking their size from the creation data. */
class FixedWidget : AWidget {
    // Check data.length is at least 3 before calling!
    /** Constructor for a fixed-size [blank] widget.
     *
     * Widget uses the initialisation data:
     * [widgetID, w, h]
     * where w, h is the fixed size. */
    this (IWidgetManager mgr, widgetID id, WidgetData data) {
        super (mgr, id, data);
        w = mw = cast(wdim) data.ints[1];
        h = mh = cast(wdim) data.ints[2];
    }
}

/** A base for resizable widgets. */
class SizableWidget : AWidget {
    // Check data.length is at least 1 before calling!
    /// Constructor for a completely resizable [blank] widget.
    this (IWidgetManager mgr, widgetID id, WidgetData data) {
        super (mgr, id, data);
    }
    
    bool isWSizable () {    return true;    }
    bool isHSizable () {    return true;    }
}

/** For pressable buttons.
 *
 * Normally overriding classes implement this, draw and activated. */
abstract class AButtonWidget : AWidget
{
    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
        super (mgr, id, data);
    }
    
    /// May be over-ridden. Pushed is true if the button has been pushed and not released.
    void draw () {
        mgr.renderer.drawButton (x,y, w,h, pushed);
    }
    
    /// Handles the down-click
    void clickEvent (wdabs, wdabs, ubyte b, bool state) {
        if (b == 1 && state == true) {
            pushed = true;
            mgr.requestRedraw;
            mgr.addClickCallback (&clickWhilePushed);
            mgr.addMotionCallback (&motionWhilePushed);
        }
    }
    
    /// Called when a mouse click event occurs while held; handles up-click
    bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) {
        if (b == 1 && state == false) {
            if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
                activated();
            
            pushed = false;
            mgr.requestRedraw;
            mgr.removeCallbacks (cast(void*) this);
            
            return true;
        }
        return false;
    }
    /// Called when a mouse motion event occurs while held; handles pushing in/out on hover
    void motionWhilePushed (wdabs cx, wdabs cy) {
        bool oldPushed = pushed;
        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
        else pushed = false;
        if (oldPushed != pushed)
            mgr.requestRedraw;
    }
    
    /// The action triggered when the button is clicked...
    void activated ();
    
protected:
    bool pushed = false;        /// True if button is pushed in (visually)
}