view mde/gui/widget/AChildWidget.d @ 131:9cff74f68b84

Major revisions to popup handling. Buttons can close menus now, plus some smaller impovements. Removed Widget module. Moved Widget.AWidget to AChildWidget.AChildWidget and Widget.AParentWidget to AParentWidget.AParentWidget. Removed ASingleParentWidget to improve code sharing. AChildWidget doesn't implement IParentWidget like AWidget did. New IPopupParentWidget extending IParentWidget for the WM and some widgets to handle popups. Cut old popup management code. New underMouse() function replacing highlight(); called on all widgets. Separate menu-popup and button widgets aren't needed for menus now. Functions returning content widgets have been moved to their own module. Cleaned up jobs.txt. Switched to 80 line length for Ddoc.
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 21 Jan 2009 13:01:40 +0000
parents mde/gui/widget/Widget.d@c5c38eaadb64
children 9f035cd139c6
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,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
    // 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: 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);
    }
    
    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 (state) {
            pushed = true;
            mgr.requestRedraw;
            mgr.addClickCallback (&clickWhilePushed);
            mgr.addMotionCallback (&motionWhilePushed);
        }
        if (parentIPPW.menuActive) {
            parentIPPW.menuDone;
            activated;
        }
	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
    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
                parentIPPW.menuDone;
                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)
    IPopupParentWidget parentIPPW;
}