view mde/gui/widget/AChildWidget.d @ 161:e3fe6acc16fb

Replaced WidgetManager's click and motion callbacks with a drag event system. This is less flexible, but much closer to what is required (and is simpler and less open to bugs through unintended use). The widget under the mouse is now passed (although could just as easily have been before).
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 21 May 2009 22:15:40 +0200
parents ccd01fde535e
children 2476790223b8
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/>. */

/******************************************************************************
 * Contains the AChildWidget class, intended for IChildWidgets to inherit.
 *
 * Abstract widget classes have an 'A' prepended to the name, similar to the
 * 'I' convention for interfaces.
 *****************************************************************************/
module mde.gui.widget.AChildWidget;

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

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

/******************************************************************************
 * An abstract base widget class for IChildWidgets.
 *
 * This abstract class, and the more concrete FixedWidget and ScalableWidget
 * classes provides useful basic implementations for widgets.
 *****************************************************************************/
abstract class AChildWidget : IChildWidget
{
//BEGIN Load and save
    // Base this() for child Widgets.
    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
        this.mgr	= mgr;
        this.parent	= parent;
        this.id		= id;
    }
    
    // Widgets need to do their initialization either in this() or setup().
    override bool setup (uint n,uint) {
	return n == 0;
    }
    
    // Don't save any data: fine for many widgets.
    override bool saveChanges () {
        return false;
    }
    
    // Widgets with content need to override this
    override IContent content () {
        return null;
    }
//END Load and save
    
//BEGIN Size and position
    // default to not resizable
    override bool isWSizable () {
        return false;
    }
    override bool isHSizable () {
        return false;
    }
    
    /* Return minimal/fixed size. */
    override wdim minWidth () {
        return mw;
    }
    override wdim minHeight () {
        return mh;
    }
    
    override wdim width () {
        return w;
    }
    override wdim height() {
        return h;
    }
    
    override wdabs xPos () {
	return x;
    }
    override wdabs yPos () {
	return y;
    }
    
    /* 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. */
    override void setWidth (wdim nw, int) {
        debug if (nw < mw) logger.warn ("Widget width set below minimal size");
        w = (nw >= mw ? nw : mw);
    }
    override void setHeight (wdim nh, int) {
        debug if (nh < mh) logger.warn ("Widget height set below minimal size");
        h = (nh >= mh ? nh : mh);
    }
    
    override 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. */
    override 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;
    }
    
    // Should be valid for any widget.
    override bool onSelf (wdabs cx, wdabs cy) {
        return cx >= x && cx < x + w && cy >= y && cy < y + h;
    }
    
    /* Dummy event method (suitable for all widgets which don't respond to events). */
    override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
	return 0;
    }
    
    /* Dummy functions; many widgets don't need to respond to dragging. */
    void dragMotion (wdabs cx, wdabs cy, IChildWidget) {}
    bool dragRelease (wdabs cx, wdabs cy, IChildWidget) {
	return false;	// any widgets not handling events should let them be passed as normal to clickEvent
    }
    
    /* Dummy functions: suitable for widgets with no text input. */
    override void keyEvent (ushort, char[]) {}
    override void keyFocusLost () {}
    
    // Called when mouse moves over or off this
    override void underMouse (bool state) {}
    
    // Only useful to widgets creating popups.
    override void popupClose () {}
    override bool popupParentClick () {
        return true;
    }
//END Events
    
    /* Basic draw method: draw the background (all widgets should do this). */
    override void draw () {
        mgr.renderer.drawWidgetBack (x,y, w,h);
    }
    
    // Debug function to print size info. Intended to be correct not optimal.
    debug override void logWidgetSize () {
        logger.trace ("size: {,4},{,4}; minimal: {,4},{,4}; sizable: {},{} - {,-50} {}", this.width, this.height, this.minWidth, this.minHeight, cast(int)this.isWSizable, cast(int)this.isHSizable, this, id);
    }
    
protected:
    /**************************************************************************
     * Widgets may use W*Check as a utility to check for existance of data. Its use is encouraged,
     * so that the checks can easily be updated should WidgetData be changed.
     * 
     * Variants:
     *	WDCheck checks the exact length of integer and string data.
     *	WDCCheck checks data as WDCheck and that the content passed is valid.
     *	WDCMinCheck does the same as WDCCheck, but allows more data than required (used by some
     *	generic widgets).
     * 
     * 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);
    }
    /** ditto */
    void WDCCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
	if (data.ints.length    != n_ints ||
	    data.strings.length != n_strings)
	    throw new WidgetDataException (this);
	if (c is null)
	    throw new ContentException (this);
    }
    /** ditto */
    void WDCMinCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
	if (data.ints.length    < n_ints ||
	    data.strings.length < n_strings)
	    throw new WidgetDataException (this);
	if (c is null)
	    throw new ContentException (this);
    }
    
    /** Hook this function to a Content callback to cause redraws.
     *
     * mgr.requestRedraw could be hooked in directly if the prototype changed. */
    void contentRedraw (Content) {
        mgr.requestRedraw;
    }
    
    IWidgetManager mgr;		// the enclosing window
    IParentWidget parent;	// the parent widget
    wdim x, y;			// position
    widgetID id;                // The widget's ID, used for saving data
    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.
}

/** A base for fixed-size widgets taking their size from the creation data. */
class FixedWidget : AChildWidget {
    // 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, IParentWidget parent, widgetID id, WidgetData data) {
        super (mgr, parent, id);
        w = mw = cast(wdim) data.ints[1];
        h = mh = cast(wdim) data.ints[2];
    }
}

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

/** For pressable buttons.
 *
 * Overriding classes should implement this() (setting the size), draw() and activated(). */
abstract class AButtonWidget : AChildWidget
{
    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
        super (mgr, parent, id);
        parentIPPW = parent.getParentIPPW;
    }
    
    /// May be over-ridden. Pushed is true if the button has been pushed and not released.
    override void draw () {
        mgr.renderer.drawButton (x,y, w,h, pushed);
    }
    
    /// Handles the down-click
    override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
        if (b != 1) return 0;
        if (parentIPPW.menuActive) {
            parentIPPW.menuDone;
            activated;
        } else if (state) {
            pushed = true;
            mgr.requestRedraw;
	    return 2;
        }
	return 0;
    }
    
    /// When menuActive, highlight on mouse-over
    override void underMouse (bool state) {
        if (!parentIPPW.menuActive) return;
        pushed = state;
        mgr.requestRedraw;
    }
    
    /// Called when a mouse click event occurs while held; handles up-click
    override bool dragRelease (wdabs cx, wdabs cy, IChildWidget) {
	if (pushed) {	// on button
	    parentIPPW.menuDone;
	    activated();
	}
	
	pushed = false;
	mgr.requestRedraw;
	
	return true;
    }
    /// Called when a mouse motion event occurs while held; handles pushing in/out on hover
    override void dragMotion (wdabs cx, wdabs cy, IChildWidget target) {
        bool oldPushed = pushed;
	// test against dimensions and not target to include sub-widgets
        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)
    IPopupParentWidget parentIPPW;
}