view mde/gui/widget/Ifaces.d @ 133:9fd705793568

Fixed menu popup bug, improved recursion detection. Menu popups can now determine whether or not they are sub-menus. Recursion detection can now also check content (if not the same, there's not a risk of infinite recursion).
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 23 Jan 2009 16:05:05 +0000
parents 9cff74f68b84
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/>. */

/******************************************************************************
 * Widget interfaces.
 * 
 * This module contains the primary documentation for the declared methods;
 * also the Widget module has some brief comments and basic implementations.
 *
 * Widgets are connected as the nodes of a tree. Widgets know their parent as a
 * IParentWidget class and their children as IChildWidget classes. The gui
 * manager is a special widget only implementing IParentWidget; all other
 * widgets must implement IChildWidget and optionally IParentWidget.
 * 
 * It's recommended that widgets inherit one of the A*Widget classes rather
 * than impement I*Widget directly.
 *****************************************************************************/
module mde.gui.widget.Ifaces;

public import mde.gui.types;
public import mde.gui.renderer.IRenderer;
import mde.content.Content;


/******************************************************************************
 * Interface for parent widgets, including IWidgetManager.
 *
 * All widgets implement this via AWidget to make things simpler (code sharing).
 *
 * Notation:
 *  Positive/negative direction: along the x/y axis in this direction.
 *  Layout widget: a widget containing multiple sub-widges (which hence
 *   controls how they are laid out).
 *****************************************************************************/
interface IParentWidget
{
    /** Checks for recursion of unsafe widgets to prevent infinite recursion. */
    void recursionCheck (widgetID, IContent);
    
    /** IPPWs return self, other widgets recurse call on parent. */
    IPopupParentWidget getParentIPPW ();
    
    /** Child widgets should call this on their parent if their minimal size changes, since they
     * cannot resize themselves.
     * 
     * Children may depend on their parent resizing them if necessary to keep width valid.
     * Widgets may also depend on setPosition being called afterwards.
     * 
     * Params:
     *	widget	= The child widget calling the function
     *	mw	= New minimal width
     *	mh	= New minimal height */
    void minWChange (IChildWidget widget, wdim mw);
    void minHChange (IChildWidget widget, wdim mh);	/// ditto
    
    /** Governor of parent widget resizability, where this is determined by sub-widgets
     * (e.g. this doesn't apply to FloatingAreaWidget).
     *
     * NEVER and ALWAYS often don't result in usable GUIs, and aren't expected to be used except
     * for debugging.
     * 
     * Note: ANY_SUBWIDGETS can cause problems like enlarging a menu bar containing a resizable
     * blank instead of the main part of a window. */
    enum SIZABILITY_ENUM {
        NEVER		= 0,	/// Parents are never resizable
        ALL_SUBWIDGETS	= 3,	/// Parents are only resizable if all sub-widgets are
        ANY_SUBWIDGETS	= 2,	/// Parents are resizable if any sub-widgets are
        ALWAYS		= 1,	/// Parents are always resizable
        START_TRUE	= 1,	/// Flag set by ALWAYS and ALL_SUBWIDGETS
        SUBWIDGETS	= 2,	/// Flag set by ALL_SUBWIDGETS and ANY_SUBWIDGETS
    }
    static const SIZABILITY = SIZABILITY_ENUM.ALL_SUBWIDGETS;	/// ditto
}

/******************************************************************************
 * Interface for parents of popups and the widget manager.
 * 
 * An IPopupParentWidget (IPPW) may have 0 or 1 child IPPWs; the child may have
 * its own IPPW. Each IPPW when added to a parent IPPW is responsible for
 * managing one popup.
 * 
 * So any child IPPW should also be a descendant widget, and is usually the or
 * a descendant of the popup.
 * 
 * An IPPW usually has one popup to manage (the popup being one of its child
 * widgets), but the popup is only active if the IPPW is added as a child of
 * its parent IPPW.
 * 
 * The widget manager is an IPPW, but unlike most IPPWs its popup(s), if
 * existing, probably have nothing to do with its child IPPWs.
 *****************************************************************************/
interface IPopupParentWidget : IParentWidget
{
    /** Add caller ippw as current child IPopupParentWidget of called IPPW.
     *
     * ippw is added as called IPPW's child IPPW, and its functions are
     * called to draw popup and pass events. The called IPPW's previous child
     * IPPW is replaced.
     * 
     * ippw is then responsible for managing a popup. */
    void addChildIPPW (IPopupParentWidget ippw);
    /** Remove ippw from being the called IPPW's child IPPW and disable
     * menuActive.
     * 
     * Do nothing if ippw is not the called IPPW's child IPPW.
     *
     * Returns: true if ippw was the child IPPW.
     *
     * I.e. this deactivates ippw's popup. */
    bool removeChildIPPW (IPopupParentWidget ippw);
    
    /** Notify the called IPPW that it has been removed. */
    void removedIPPW ();
    
    /** Set/get menuActive state.
     *
     * This is set on the parent IPPW when a popup menu is opened and unset
     * when the menu is closed.
     * If set on the parent IPPW, popup menus can be opened with just a mouse-
     * over and buttons activated with an up-click. */
    void menuActive (bool);
    bool menuActive ();
    /** Returns the IPPW's parent's menuActive (WM returns false). If true,
     * popup widgets may assume they are sub-menu popups not top-level menu
     * popups. */
    bool parentMenuActive ();
    
    /** Called by descendant widgets such as buttons when an action occurred,
     * which should close a menu. (But also called when not in a menu.) */
    void menuDone ();
    
    /** Get the widget under cx,cy.
     *
     * The IPPW should first recurse the call to its child IPPW if it exists.
     * If this doesn't yield a widget, it should try getting one from its popup
     * and then itself. It should return the first widget found or null.
     * 
     * If closePopup is true and a widget isn't returned from the childIPPW,
     * the childIPPW should be removed (to close popups when a click is not on
     * the popup or its parent) and menuActive set false. */
    IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup);
    
    /** Draw.
     *
     * The IPPW should first draw its popup, then call on its child IPPW if
     * that exists. */
    void drawPopup ();
    
    /// Returns true if ippw is child IPPW.
    debug bool isChild (IPopupParentWidget ippw);
}

/******************************************************************************
 * Interface for the widget manager.
 * 
 * This class handles widget rendering, input, loading and saving.
 *****************************************************************************/
interface IWidgetManager : IPopupParentWidget
{
    // Loading/saving:
    /** Create a widget by looking up the data for id then looking up data.ints[0] in WIDGET_TYPES.
     *
     * Params:
     *  id      = Identifier, within data files, of the data for the widget.
     *  data    = Pass this data to the widget, not data looked up via id.
     *  content = An IContent may be passed to some widgets on creation.
     *
     * When used in a this(), super() should be called before any calls to makeWidget (or at least
     * parent and id set) due to recursionCheck being called on the widget.
     *
     * Creates a widget, using the widget data with index id. Widget data is loaded from files,
     * and per design (multiple gui layouts, called designs, may exist; data is per design). */
    IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null);
    
    /** Get or set widget id's WidgetData or dimension data.
     *
     * WidgetData is for most data, dimensional data (wdims) is for dimensions.
     *
     * Data should only be set from IChildWidget.saveChanges() to
     * avoid setting multiple times when a widget id has several instances. */
    WidgetData widgetData (widgetID id);
    void widgetData (widgetID id, WidgetData data);	/// ditto
    wdims dimData (widgetID id);			/// ditto
    void dimData (widgetID id, wdims d);		/// ditto
    
    /** Position popup left or right of parent, or left or right of parent
     * (flags & 1 == 1). */
    void positionPopup (IChildWidget parent, IChildWidget popup, int flags = 0);
    
    // Rendering:
    /** For when a widget needs redrawing.
     * 
     * Must be called because rendering may only be done on events.
     * 
     * Currently it just causes everything to be redrawn next frame. */
    void requestRedraw ();
    
    /** Get the window's renderer.
    *
    * Normally specific to the GUI, but widgets have no direct contact with the GUI and this
    * provides the possibility of per-window renderers (if desired). */
    IRenderer renderer ();
    
    // User input:
    /** Add a mouse click callback.
     * 
     * This is a delegate this will be called for all mouse click events recieved by the gui, not
     * simply all click events on the widget (as clickEvent recieves).
     *
     * The delegate should return true if it accepts the event and no further processing is
     * required (i.e. the event should not be handled by anything else), false otherwise. */
    void addClickCallback (bool delegate (wdabs cx, wdabs cy, ubyte b, bool state) dg);
    
    /** Add a mouse motion callback: delegate will be called for all motion events recieved by the
     * gui. */
    void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg);
    
    /** Remove all event callbacks on this widget (according to the delegate's .ptr). */
    // Note: don't try to pass a reference and cast to void* in the function; it's a different address.
    void removeCallbacks (void* frame);
}


/******************************************************************************
 * Interface for (child) widgets, i.e. all widgets other than the manager.
 *
 * A widget is a region of a GUI window which handles rendering and user-interaction for itself
 * and is able to communicate with its manager and child widgets as necessary. (Passing widgets
 * a reference to their parent has not been found useful.)
 *
 * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the
 * createWidget module, and have a constructor of the following form. It should also update it's
 * creation data if necessary, either when changed or when saveChanges() is called, using
 * IWidgetManager.setData().
 * It should use Ddoc to explain what initialization data is used.
 * ----------------------------------
 * /++ Constructor for a ... widget.
 *  +
 *  + Widget uses the initialisation data:
 *  + [widgetID, x, y]
 *  + where x is ... and y is ... +/
 * this (IWidgetManager mgr, widgetID id, WidgetData data);
 * 
 * /// The CTOR may take an IContent reference:
 * this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content);
 * ----------------------------------
 * Where mgr is the widget manager, id is the _id passed to makeWidget() and data is
 * initialisation data. The method should throw a WidgetDataException (created without
 * parameters) if the data has wrong length or is otherwise invalid.
 *
 * All widgets should set their own size in this() or setup() (must be setup() if it could change),
 * although some parents may set child-widgets' size during their creation.
 *****************************************************************************/
//NOTE: add another this() without the data for default initialization, for the GUI editor?
interface IChildWidget
{
//BEGIN Load and save
    /** 2nd stage of initialization for widgets; also called on some changes.
     *
     * Widgets should call recursively on their children, redo anything indicated by flags, and
     * adjust their size and other cached data dependant on any thing which may have changed.
     * Widgets may rely on setPosition being called afterwards.
     * 
     * Params:
     *	n =	Indicates this is the (n+1)-th time the function has been called.
     *	flags =	if (flags & 1) the renderer has been changed,
     *		if (flags & 2) translation strings are being reloaded.
     *		These flags are always true on first run.
     *
     * Returns:
     *	The method should return true if the dimensions (may) have been changed. This may not be
     *	the case on the first run (when n == 0)!.
     */
    bool setup (uint n, uint flags);
    
    /** When this is called, if the widget has any changed data to save it should call
     * IWidgetManager.setData (id, data) to set it and return true. Otherwise it should return
     * false.
     * 
     * If the widget has subwidgets, it should also be recursively called on these (passing their 
     * ids). */
    bool saveChanges ();
    
    /+ Use when widget editing is available? Requires widgets to know their parents.
    /** Called when a child widget's size has changed.
     * 
     * Should be propegated up to parents. */
    void childChanged ();
    +/
    
//END Load and save
    
//BEGIN Size and position
    /** Is the width / height resizable?
     *
     * Normally, this means does the widget benifit from being enlarged? If not, the widget has
     * "fixed" dimensions equal to it's minimal size; however it $(I may) still be enlarged.
     * 
     * Parents normally take their resizability from sub-widgets; see SIZABILITY for how they do
     * this. */
    bool isWSizable ();
    bool isHSizable (); /// ditto
    
    /** The minimal size the widget could be shrunk to (or its fixed size).
     *
     * Takes into account child-widgets and any other contents. */
    wdim minWidth ();
    wdim minHeight ();	/// ditto
    
    /** Get the current size of the widget. */
    wdim width ();
    wdim height();      /// ditto
    
    /** (Smallest) coordinates of widget. */
    wdabs xPos ();
    wdabs yPos ();	/// ditto
    
    /** Used to adjust the size.
     *
     * Params:
     *  nw/nh	= The new width/height
     *  dir	= Direction to resize from. This is only really applicable to layout widgets.
     *  	  It must be either -1 (start resizing from highest row/col index, decreasing the
     *  	  index as necessary), or +1 (resize from the lowest index, i.e. 0).
     *  	  Most widgets can simply ignore it.
     *
     * A widget should never be resized smaller than it's minimal size (if it is, it should assume
     * it's minimal size and print a warning when in debug mode).
     * A "fixed" size widget should enlarge itself as requested.
     *
     * setPosition must be called after calling either setWidth or setHeight. */
    void setWidth (wdim nw, int dir);
    void setHeight (wdim nh, int dir);	/// ditto
    
    /** Set the current position (called after setup and to move widget). */
    void setPosition (wdim x, wdim y);
//END Size and position
    
//BEGIN Events
    /** Recursively scan the widget tree to find the widget under (cx,cy).
     *
     * If called on a widget, that widget should assume the location is over itself, and so should
     * either return itself or the result of calling getWidget on the appropriate child widget.
     *
     * In the case of Window this may not be the case; it should check and return null if not under
     * (cx,cy).
     *
     * Note: use global coordinates (cx,cy) not coordinates relative to the widget. */
    IChildWidget getWidget (wdabs cx, wdabs cy);
    
    /** Return true if (cx,cy) is on self's box (doesn't matter if actually on a subwidget). */
    bool onSelf (wdabs cx, wdabs cy);
    
    /** Receive a mouse click event at cx,cy from button b (1-5 correspond to L,M,B, wheel up,down)
     * which is a down-click if state is true.
     *
     * Widget may assume coordinates are on the widget (caller must check).
     *
     * The return value has the following flags: 1 to request keyboard input. */
    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state);
    
    /** Receives keyboard events when requested.
     *
     * Params:
     *	sym	SDLKey key sym, useful for keys with no character code such as arrow keys
     *	letter	The character input, in UTF-8 */
    void keyEvent (ushort sym, char[] letter);
    
    /** Called when keyboard input focus is lost. */
    void keyFocusLost ();
    
    /** Called on all widgets when the mouse moves over it (state == true) and
     * when it leaves (state == false). */
    void underMouse (bool state);
    
    /** When a pop-up is closed the manager calls requestRedraw and this function on its parent. */
    void popupClose ();
    /** When a click is on the parent of a popup, this function is called instead of the usual
     * clickEvent.
     * 
     * Returns:
     *	true to close the popup.
     *
     * Note: this means the parent can't receive text input without code changes. */
    bool popupParentClick ();
    
//END Events
    
    /** Draw, using the stored values of x and y.
     *
     * Maybe later enforce clipping of all sub-widget drawing, particularly for cases where only
     * part of the widget is visible: scroll bars or a hidden window. */
    void draw ();
    
    /// Logs the current and minimal size of every widget.
    debug void logWidgetSize ();
}