view mde/gui/widget/Widget.d @ 126:c9843fbaac88

Dynamic minimal size changing improved; works over layouts sharing alignment. EnumContent sub-contents use EnumValueContent instead of BoolContent; fixes a few small bugs. EnumContent substrings get translated (bug fixed). The widget manager no longer attempts to set widget sizes smaller than their minimals, even though some will not be shown. SwitchWidget: has fixed sizableness now.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 08 Jan 2009 13:05:44 +0000
parents d3b2cefd46c9
children ad91de8867a0
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.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.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 IParentWidget methods
    // Don't override; use the WIDGET_TYPE.SAFE_RECURSION flag for safe widgets.
    //NOTE: should be override (compiler bug)
    final void recursionCheck (widgetID a) {
        debug assert (id !is null && parent !is null, "recursionCheck called before parent and id set");
        if (a is id)
            throw new GuiException ("Infite recursion of "~a);
        parent.recursionCheck (a);
    }
    
    // Parent widgets need to implement this.
    override void minWChange (IChildWidget widget, wdim mw) {}
    override void minHChange (IChildWidget widget, wdim mh) {}
//END IParentWidget methods
    
//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,uint) {
	return false;
    }
    
    // Don't save any data: fine for many widgets.
    override bool saveChanges () {
        return false;
    }
//END Load and save
    
//BEGIN Size and position
    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: suitable for widgets with no text input. */
    override void keyEvent (ushort, char[]) {}
    override void keyFocusLost () {}
    
    // Currently only applies to popup widgets.
    override void highlight (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);
    }
    
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);
    }
    
    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.
}

/*************************************************************************************************
 * Abstract base widget classes to facilitate parent widgets.
 * 
 * Parent widgets probably need to overload these functions (from AWidget):
 * setup, saveChanges, setPosition, getWidget, draw, setWidth and setHeight.
 *************************************************************************************************/
abstract class AParentWidget : AWidget
{
    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
        super (mgr, parent, id);
    }
    
    override bool setup (uint n, uint flags) {
        debug (mdeWidgets) logger.trace ("AParentWidget.setup");
        bool c = false;
	foreach (w; subWidgets) {
	    debug assert (w);
	    c |= w.setup (n,flags);
	}
	return c;
    }
    
    override bool saveChanges () {
        bool c = false;
        foreach (w; subWidgets)
            c |= w.saveChanges;
        return c;
    }
    
    size_t getWidgetIndex (IChildWidget widg) {
        foreach (i,w; subWidgets)
            if (w is widg)
                return i;
        
        throw new GuiException ("getWidgetIndex: widget not found (code error)");
    }
    
protected:
    IChildWidget[] subWidgets;
}
/** ditto */
abstract class AParentSingleWidget : AWidget
{
    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
        super (mgr, parent, id);
    }
    
    override bool setup (uint n, uint flags) {
        debug (mdeWidgets) logger.trace ("AParentSingleWidget.setup");
        debug assert (subWidget);
	return subWidget.setup (n,flags);
    }
    
    override bool saveChanges () {
	return subWidget.saveChanges;
    }
	
protected:
    IChildWidget subWidget;
}

/** 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, 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 : AWidget {
    // 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 : AWidget
{
    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
        super (mgr, parent, id);
    }
    
    /// 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 && state == true) {
            pushed = true;
            mgr.requestRedraw;
            mgr.addClickCallback (&clickWhilePushed);
            mgr.addMotionCallback (&motionWhilePushed);
        }
	return 0;
    }
    
    /// 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)
}