view mde/gui/widget/AChildWidget.d @ 176:d5d5fe04ca6c

Fixes to CollapsibleWidget. Disabled AChildWidget.invariant.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 12 Sep 2009 09:14:43 +0200
parents 1cbde9807293
children af40e9679436
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.IContent;
import mde.gui.exception;

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, IWidget
{
//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.
    public override bool saveChanges () {
        return false;
    }
    
    // Widgets with content need to override these
    override IContent content () {
       return null;
    }
    override void setContent (IContent) {}
//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 ({}: {})", id, this);
        w = (nw >= mw ? nw : mw);
    }
    override void setHeight (wdim nh, int) {
        debug if (nh < mh) logger.warn ("Widget height set below minimal size ({}: {})", id, this);
        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. */
    override void dragMotion (wdabs cx, wdabs cy, IChildWidget) {}
    override 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. */
    public override void keyEvent (ushort, char[]) {}
    public override void keyFocusLost () {}
    
    // Called when mouse moves over or off this
    override void underMouse (bool state) {}
    
    public override bool dropContent (IContent content) {
	return parent.dropContent (content);
    }
    
    // Only useful to widgets creating popups.
    protected override void popupClose () {}
    protected override bool popupParentClick () {
        return true;
    }
//END Events
    
    /* Basic draw method: draw the background (all widgets should do this). */
    public override void draw () {
	//TODO: possibly enforce all widgets to implement this so their invariant runs:
	//assert (false, "all widgets should override draw");
        mgr.renderer.drawWidgetBack (x,y, w,h);
    }
    
    // Debug function to print size info. Intended to be correct not optimal.
    debug public 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 (IContent) {
        mgr.requestRedraw;
    }
    
    /** TODO: in progess - design of invariants
     *
     * Ideally this wants to be called after minXChange has finished, but not
     * before/during. minXChange is called in 2 cases:
     * 	recursively from a child
     * 	from a change of content (setContent, keyEvent, content callbacks) - these can all be public
     * 
     * So minXChange should not be public. At least the draw() method should be
     * public so the invariant gets called often. The only protection that can
     * be used for minXChange is package, however there's compiler bugs
     * preventing package functions from being virtual! */
    invariant {
	scope (failure)
	    logger.warn ("invariant failed ({}: {}; parent is {})", id, this, parent);
	/+ FIXME - see comment above
	assert (w >= mw);
	assert (h >= mh); +/
    }
    
    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 (parentIPPW.menuActive && b == 1 || b == 3) {
            parentIPPW.menuDone;
            activated;
        } else if (state && b == 1) {
            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...
    abstract void activated ();
    
protected:
    bool pushed = false;        /// True if button is pushed in (visually)
    IPopupParentWidget parentIPPW;
}