view mde/gui/widget/Floating.d @ 82:ac1e3fd07275

New ssi file format. (De)serializer now supports non-ascii wide characters (encoded to UTF-8) and no longer supports non-ascii 8-bit chars which would result in bad UTF-8. Moved/renamed a few things left over from the last commit.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 30 Aug 2008 09:37:35 +0100
parents ea58f277f487
children b525ff28774b
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/>. */

/** The Window class. Becoming a widget. */
module mde.gui.widget.Floating;

import mde.gui.widget.Widget;
import mde.gui.exception;
/+import mde.gui.widget.createWidget;

import mde.gui.IGui;
import mde.gui.renderer.createRenderer;

import mt = mde.mergetag.DataSet;
import mde.mergetag.parse.parseTo : parseTo;
import mde.mergetag.parse.parseFrom : parseFrom;
+/
import tango.util.log.Log : Log, Logger;

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.gui.widget.Floating");
}
//FIXME - documentation

/** GUI Window class
 *
 * A window class instance does two things: (1) specify a region of the screen upon which the window
 * and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets.
 *
 * Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when
 * created, be given its int[] of data, which this() must confirm is valid (or throw).
 */
/** An area to contain floating widgets.
 *
 * The position of each sub-widget is set from data, but not the size.
 * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to
 * know their positions. Size setting is still under work FIXME.
 *
 * Data: Each string item is interpreted as a subwidget widgetID.
 * Ints supplied may consist of just the widget type or
 * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords).
 */
class FloatingAreaWidget : SizableWidget, IParentWidget
{
    this (IWidgetManager mgr, WidgetData data) {
        subWidgets.length = data.strings.length;
        foreach (i,s; data.strings)
            subWidgets[i] = mgr.makeWidget (s, this);
        sWCoords.length = subWidgets.length;
        
        if (data.ints.length != 1) {
            if (data.ints.length != 2*subWidgets.length + 1) {
                throw new WidgetDataException;
            }
            foreach (i, ref c; sWCoords) {
                c.x = cast(wdim) data.ints[i + 1];
                c.y = cast(wdim) data.ints[i + 1 + sWCoords.length];
            }
        }
        
        super (mgr, data);
        
        foreach (w; subWidgets) {
            //FIXME: set default size
            w.setWidth  (w.minWidth, -1);
            w.setHeight (w.minHeight, -1);
        }
    }
    
    void setPosition (wdim x, wdim y) {
        super.setPosition (x,y);
        
        foreach (i,c; sWCoords)
            subWidgets[i].setPosition (x+c.x, y+c.y);
    }
    
    void draw () {
        super.draw;
        
        mgr.renderer.restrict (x,y, w,h);
        
        foreach (w; subWidgets)
            w.draw;
    }
    
protected:
    IChildWidget[] subWidgets;  // all subwidgets, framed or not
    wdimPair[] sWCoords;        // coords for subwidgets, relative to this widget
    
    /+
    /** Call after loading is finished to setup the window and confirm that it's valid.
     *
     * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an
     * exception occurs! */
    void finalise (IGui gui)
    in {
        assert (gui !is null, "Window.finalise ("~name~"): gui is null");
    } body {
        // Check data was loaded:
        if (widgetData is null)
            throw new WindowLoadException ("No widget creation data");
        
        // Save gui and create the renderer:
        gui_ = gui;
        rend = createRenderer (gui.rendererName);
        
        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
        // Note: GridLayoutWidget's this relies on rend.layoutSpacing.
        widget = makeWidget (0);        // primary widget always has ID 0.
        // This data is no longer needed by Window, although its sub-arrays may still be used, so
        // let the GC collect what it can:
        widgetData = null;
        widgetStrings = null;
        
        // get border sizes:
        border = rend.setSizable (isWSizable, isHSizable);  // depends on widget
        
        // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty:
        if ((widget.adjust (mutableData)).length != 0)	// adjust/set size, etc., depends on rend
            logger.warn ("Local widget position data is invalid!");
        mutableData = null;             // no longer needed
        
        widget.getCurrentSize (w,h);    // and get this size
        w += border.l + border.r;       // Adjust for border
        h += border.t + border.b;
        
        widgetX = x + border.l;         // widget position
        widgetY = y + border.t;         // must be updated if the window is moved
        widget.setPosition (widgetX, widgetY);
        
        // Calculate mw/mh and xw/yh (cached data):
        mw = widget.minWidth  + border.l + border.r;
        mh = widget.minHeight + border.t + border.b;
        
        xw = x+w;
        yh = y+h;
    }
    //END Methods for GUI
    
    //BEGIN IWindow methods
    /** Get/create a widget by ID.
     *
     * Should $(I only) be called internally and by sub-widgets! */
    IWidget makeWidget (widgetID i)
    in {
        // widgetData is normally left to be garbage collected after widgets have been created:
        assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null");
    } body {
        /* Each widget returned should be a unique object; if multiple widgets are requested with
        * the same ID, a new widget is created each time. */
        
        int[]* d = i in widgetData;
        if (d is null)
            throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
        
        // Throws WidgetDataException (a WindowLoadException) if bad data:
        return createWidget (this, *d);
    }
    
    char[] getWidgetString (int i)
    in {
        // widgetStrings is freed at same time as widgetData
        // but widgetData is guaranteed to be read
        assert (widgetData !is null, "getWidgetString called after widget creation finished");
    } body {
        char[]* p;
        if (widgetStrings is null ||
             (p = i in widgetStrings) is null )
            throw new WindowLoadException ("Needed widgetStrings not set for Window");
        
        return *p;
    }
    
    /** Add this widget's data to that to be saved, returning it's widgetID. */
    widgetID addCreationData (IWidget widget)
    {
        widgetID i;
        if (widgetData is null)
            i = 0;
        else
            i = widgetData.keys[$-1] + 1;
        
        /+ Doesn't this have no effect except when getCreationData throws, in which case the data
         + isn't used anyway? I'm sure it was added for a reason... FIXME and below
        widgetData[i] = null;   // Make sure the same ID doesn't get used by a recursive call
        +/
        widgetData[i] = widget.getCreationData;
        
        return i;
    }
    
    int addWidgetString (char[] str)
    {
        int i;
        if (widgetStrings is null)
            i = 0;
        else
            i = widgetStrings.keys[$-1] + 1;
        
        /+ See above. FIXME
        widgetStrings[i] = null;   // Make sure the same ID doesn't get used by a recursive call
        +/
        widgetStrings[i] = str;
        
        return i;
    }
    
    IGui gui () {
        return gui_;
    }
    
    void requestRedraw () {
        gui_.requestRedraw;
    }
    
    IRenderer renderer ()
    in {
        assert (rend !is null, "Window.renderer: rend is null");
    } body {
        return rend;
    }
    //END IWindow methods
    
    //BEGIN IWidget methods
    //FIXME: how many of these methods are actually needed/used?
    int[] adjust (int[]) {          // simply not relevant (never used)
        return [];
    }
    int[] getCreationData () {      // simply not relevant (never used)
        return [];
    }
    int[] getMutableData () {       // simply not relevant (never used)
        return [];
    }
    
    bool isWSizable () {
        return widget.isWSizable;
    }
    bool isHSizable () {
        return widget.isHSizable;
    }
    
    wdim minWidth () {
        return mw;
    }
    wdim minHeight () {
        return mh;
    }
    void getCurrentSize (out wdim cw, out wdim ch) {
        cw = w;
        ch = h;
    }
    
    void setWidth (wdim nw, int dir) {
        if (nw < mw) w = mw;	// clamp
        else w = nw;
        xw = x + w;
        widget.setWidth (w - border.l - border.r, dir);
    }
    void setHeight (wdim nh, int dir) {
        if (nh < mh) h = mh;	// clamp
        else h = nh;
        yh = y + h;
        widget.setHeight (h - border.t - border.b, dir);
    }
    
    void setPosition (wdim nx, wdim ny) {
        x = nx;
        y = ny;
        
        xw = x+w;
        yh = y+h;
        
        widgetX = x + border.l;
        widgetY = y + border.t;
        
        widget.setPosition (widgetX, widgetY);
        
        gui_.requestRedraw ();  // necessary whenever the window is moved; setPosition is called after resizes and moves
    }
    
    IWidget getWidget (wdim cx, wdim cy) {
        if (cx < x || cx >= xw || cy < y || cy >= yh)   // not over window
            return null;
        if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b)
                                                        // over the widget
            return widget.getWidget (cx, cy);
        else                                            // over the window border
            return this;
    }
    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
        if (b == 1 && state == true) {
            resizeType = rend.getResizeType (cx-x, cy-y, w,h);
            
            if (resizeType != RESIZE_TYPE.NONE) {    // Some type of resize
                // Set x/yDrag (unfortunately these need to be different for each edge)
                if (resizeType & RESIZE_TYPE.L)
                    xDrag = w + cx;
                else if (resizeType & RESIZE_TYPE.R)
                    xDrag = w - cx;
                
                if (resizeType & RESIZE_TYPE.T)
                    yDrag = h + cy;
                else if (resizeType & RESIZE_TYPE.B)
                    yDrag = h - cy;
                
                // Add the callbacks (they use resizeType which has already been set)
                gui_.addClickCallback (&endCallback);
                gui_.addMotionCallback (&resizeCallback);
            } else {                             // window is being moved
                xDrag = cx - x;
                yDrag = cy - y;
                
                gui_.addClickCallback (&endCallback);
                gui_.addMotionCallback (&moveCallback);
            }
        }
    }
    
    void draw () {
        // background
        rend.drawWindow (x,y, w,h);
        
        // Tell the widget to draw itself:
        widget.draw();
    }
    //END IWidget methods
    
private:
    alias IRenderer.BorderDimensions BorderDimensions;
    alias IRenderer.RESIZE_TYPE RESIZE_TYPE;
    
    //BEGIN Window moving and resizing
    void moveCallback (wdabs cx, wdabs cy) {
        setPosition (cx-xDrag, cy-yDrag);
    }
    void resizeCallback (wdabs cx, wdabs cy) {
        debug scope(failure)
                logger.trace ("resizeCallback: failure");
        
        // This function is only called if some resize is going to happen.
        // x,y are used as parameters to setPosition as well as being affected by it; somewhat
        // pointless but fairly effective.
        
        if (resizeType & RESIZE_TYPE.L) {
            wdim xSize = xDrag - cx;
            if (xSize < mw) xSize = mw;	// clamp
            x += w - xSize;
            setWidth (xSize, 1);
        }
        else if (resizeType & RESIZE_TYPE.R) {
            setWidth (xDrag + cx, -1);
        }
        if (resizeType & RESIZE_TYPE.T) {
            wdim ySize = yDrag - cy;
            if (ySize < mh) ySize = mh;
            y += h - ySize;
            setHeight (ySize, 1);
        }
        else if (resizeType & RESIZE_TYPE.B) {
            setHeight (yDrag + cy, -1);
        }
        
        // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the
        // resizing:
        setPosition (x, y);
    }
    bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) {
        if (b == 1 && state == false) {
            // The mouse shouldn't have moved since the motion callback
            // was last called, so there's nothing else to do now.
            gui_.removeCallbacks (cast(void*) this);
            
            return true;    // we've handled the up-click
        }
        return false;       // we haven't handled it
    }
    wdim xDrag, yDrag;              // where a drag starts relative to x and y
    IRenderer.RESIZE_TYPE resizeType;   // Type of current resize
    //END Window moving and resizing
    
    // Load/save data:
    public char[] name;             // The window's name (id from config file)
    //bool edited = false;            // True if any widgets have been edited (excluding scaling)
    
    IGui gui_;                      // The gui managing this window
    IRenderer rend;                 // The window's renderer
    
    IWidget widget;                 // The primary widget in this window.
    
    wdim x = -1, y = -1;            // Window position
    wdsize w,h;                     // Window size (calculated from Widgets)
    wdim xw, yh;                    // x+w, y+h (frequent use by clickEvent)
    wdim widgetX, widgetY;          // Widget position (= window position plus BORDER_WIDTH)
    wdim mw = -1, mh = -1;          // minimal size (negative means requires calculation)
    
    BorderDimensions border;        // Total border size (move plus resize)
    +/
}