view mde/gui/widget/Floating.d @ 97:30470bc19ca4

Floating widgets now work nicely: customizable borders added, resizing, moving. gl.basic abstraction module removed (seemed pointless). Some changes to SimpleRenderer (largely to accomodate floating widgets).
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 10 Nov 2008 16:44:44 +0000
parents dbf332403c6e
children 5de5810e3516
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 tango.util.log.Log : Log, Logger;

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

/** An area to contain floating widgets.
 *
 * The position of each sub-widget is set from dimension data, but not the size.
 * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to
 * know their positions.
 *
 * 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 : AParentWidget
{
    this (IWidgetManager mgr, widgetID id, WidgetData data) {
        if (data.ints.length != 1 + data.strings.length)
            throw new WidgetDataException (this);
        
        subWidgets.length = data.strings.length;        // widgets created from string data
        sWOrder.length = subWidgets.length;
        sWData.length = subWidgets.length;
        foreach (i,s; data.strings) {
            subWidgets[i] = mgr.makeWidget (s);
            sWOrder[i] = i;
            sWData[i].borderType = cast(BTYPE) data.ints[i+1];
        }
        
        wdim[] dd = mgr.dimData (id);
        if (dd.length == subWidgets.length * 4) {
            foreach (i, ref d; sWData)
                (&d.x)[0..4] = dd[i*4..i*4+4];
        }
        
        super (mgr, id, data);
    }
    
    void finalize () {
        foreach (i, ref d; sWData) with (d) {
            auto widg = subWidgets[i];
            d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable);
            mw = widg.minWidth  + border.x1 + border.x2;
            mh = widg.minHeight + border.y1 + border.y2;
            if (w < mw) w = mw;
            if (h < mh) h = mh;
            widg.setWidth  (w - border.x1 - border.x2, -1);
            widg.setHeight (h - border.y1 - border.y2, -1);
        }
    }
    
    bool saveChanges () {
        wdim[] dd = new wdim[sWData.length*4];
        foreach (i, ref d; sWData) {
            subWidgets[i].saveChanges ();
            dd[4*i..4*i+4] = (&d.x)[0..4];
        }
        
        mgr.setDimData (id, dd);  // save positions
        return true;
    }
    
    void setWidth (wdim nw, int) {
        w = nw;
        // check all floating widgets are visible
        foreach (i, ref d; sWData) with (d) {
            if (x + w > this.w)
                x = (w > this.w) ? 0 : this.w - w;
        }
    }
    void setHeight (wdim nh, int) {
        h = nh;
        foreach (i, ref d; sWData) with (d) {
            if (y + h > this.h)
                y = (h > this.h) ? 0 : this.h - h;
        }
    }
    
    bool isWSizable () {    return true;    }
    bool isHSizable () {    return true;    }
    
    void setPosition (wdim nx, wdim ny) {
        x = nx;
        y = ny;
        
        size_t n = subWidgets.length;
        foreach (i, ref d; sWData)
            subWidgets[i].setPosition (x + d.x + d.border.x1, y + d.y + d.border.y1);
    }
    
    void draw () {
        super.draw;
        
        mgr.renderer.restrict (x,y, w,h);
        foreach (i; sWOrder)
            with (sWData[i]) {
                mgr.renderer.drawWindow (&border, this.x + x, this.y + y, w, h);
                subWidgets[i].draw;
            }
        mgr.renderer.relax;
    }
    
    IChildWidget getWidget (wdim cx, wdim cy) {
        debug scope (failure)
            logger.warn ("getWidget: failure; values: click, pos, width - {}, {}, {} - {}, {}, {}", cx, x, w, cy, y, h);
        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
        
        foreach_reverse (j,i; sWOrder) with (sWData[i]) {
            wdim lx = cx - (this.x + x);
            wdim ly = cy - (this.y + y);
            if (lx >= 0 && lx < w &&
                ly >= 0 && ly < h)
            {
                sWOrder[j..$-1] = sWOrder[j+1..$].dup;
                sWOrder[$-1] = i;
                mgr.requestRedraw;
                if (lx >= border.x1 && lx < w-border.x2 &&
                    ly >= border.y1 && ly < h-border.y2)
                    return subWidgets[i].getWidget (cx,cy);
                event = i;
                return this;
            }
        }
        event = size_t.max;
        return this;    // no match
    }
    
    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
        if (event > subWidgets.length) return;
        if (b == 1 && state == true) {
            active = event;
            with (sWData[active]) {
                resizeType = border.getResize (cx - this.x - x, cy - this.y - y, w,h);
                alias border.RESIZE RESIZE;
                
                if (resizeType != RESIZE.NONE) {
                    // Set x/yDrag (unfortunately these need to be different for each edge)
                    if (resizeType & RESIZE.X1)
                        xDrag = w + cx;
                    else if (resizeType & RESIZE.X2)
                        xDrag = w - cx;
                    
                    if (resizeType & RESIZE.Y1)
                        yDrag = h + cy;
                    else if (resizeType & RESIZE.Y2)
                        yDrag = h - cy;
                    
                    mgr.addClickCallback (&endCallback);
                    mgr.addMotionCallback (&resizeCallback);
                } else if (borderType & BTYPE.MOVE) {   // window is being moved
                    xDrag = cx - x;
                    yDrag = cy - y;
                    
                    mgr.addClickCallback (&endCallback);
                    mgr.addMotionCallback (&moveCallback);
                }
            }
        }
    }
    
protected:
    void moveCallback (wdabs cx, wdabs cy) {
        with (sWData[active]) {
                x = cx-xDrag;
                y = cy-yDrag;
                
                if (x < 0)
                    x = 0;
                else if (x + w > this.w)
                    x = (w > this.w) ? 0 : this.w - w;
                if (y < 0)
                    y = 0;
                else if (y + h > this.h)
                    y = (h > this.h) ? 0 : this.h - h;
                
                subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1);
        }
        mgr.requestRedraw;
    }
    void resizeCallback (wdabs cx, wdabs cy) {
        with (sWData[active]) {
            if (resizeType & RESIZE.X1) {
                wdim ow = w;
                w = xDrag - cx;
                if (w < mw) w = mw;
                x += ow - w;
                subWidgets[active].setWidth  (w - border.x1 - border.x2, 1);
            }
            else if (resizeType & RESIZE.X2) {
                w = xDrag + cx;
                if (w < mw) w = mw;
                subWidgets[active].setWidth  (w - border.x1 - border.x2, -1);
            }
            if (resizeType & RESIZE.Y1) {
                wdim oh = h;
                h = yDrag - cy;
                if (h < mh) h = mh;
                y += oh - h;
                subWidgets[active].setHeight (h - border.y1 - border.y2, 1);
            }
            else if (resizeType & RESIZE.Y2) {
                h = yDrag + cy;
                if (h < mh) h = mh;
                subWidgets[active].setHeight (h - border.y1 - border.y2, -1);
            }
            // Reposition widget and sub-widgets:
            subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1);
        }
        mgr.requestRedraw;
    }
    bool endCallback (wdabs, wdabs, ubyte b, bool state) {
        if (b == 1 && state == false) { // end of a move/resize
            mgr.removeCallbacks (cast(void*) this);
            return true;    // we've handled the up-click
        }
        return false;       // we haven't handled it
    }
    
    struct SWData {     // NOTE: x,y,w,h must be first elements; search (&d.x)
        wdim x,y;       // position (corner of border)
        wdim w,h;       // size (including border)
        wdim mw,mh;
        BTYPE borderType;       // what type of border to put around the widget
        IRenderer.Border border;
    }
    static assert (SWData.alignof == 4); // assumptions for optimization; search (&d.x)
    SWData[] sWData;
    size_t[] sWOrder;   // indexes for draw order (top widget at end of list)
    
    // Click/drag information:
    alias IRenderer.Border.BTYPE BTYPE;
    alias IRenderer.Border.RESIZE RESIZE;
    size_t event  = size_t.max; // Border with last click/release: size_t.max: main area (no subwidget), i: subwidget[i]
    size_t active = size_t.max; // Like event, but refers to widget being moved/resized
    wdim xDrag, yDrag;          // where a drag starts relative to x and y
    RESIZE resizeType;          // Type of current resize
}