view mde/gui/widget/Floating.d @ 144:66c58e5b0062

Added a BoolContent-based collapsible widget.
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 10 Feb 2009 12:57:09 +0000
parents 29a524e7c858
children 075705ad664a
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.AParentWidget;
import mde.gui.exception;
import mde.content.IContent;

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 sub-widget widgetID to add as a floating window.
 * Ints consist of widget type followed by a border type (flags from IRenderer.Border.BTYPE) for
 * each sub-widget. */
class FloatingAreaWidget : AParentWidget
{
    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) {
        if (data.ints.length != 1 + data.strings.length)
            throw new WidgetDataException (this);
        super (mgr, parent, id);
        
        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 (this, s, content);
            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];
        }
    }
    
    override bool setup (uint n, uint flags) {
        debug (mdeWidgets) logger.trace ("FloatingAreaWidget.setup");
        foreach (i, ref d; sWData) with (d) {
            auto widg = subWidgets[i];
	    if (!widg.setup (n, flags) && !(flags & 1))
		continue;	// no changes; skip the rest
	    
	    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 || !widg.isWSizable) w = mw;
	    if (h < mh || !widg.isHSizable) h = mh;
            widg.setWidth  (w - border.x1 - border.x2, -1);
            widg.setHeight (h - border.y1 - border.y2, -1);
        }
	return n == 0;	// floating area size is not changed
    }
    
    override 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.dimData (id, dd);	// save positions
        return true;
    }
    
    override 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;
        }
    }
    override 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;
        }
    }
    
    override bool isWSizable () {
        return true;
    }
    override bool isHSizable () {
        return true;
    }
    
    override 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);
    }
    
    override void minWChange (IChildWidget widg, wdim nmw) {
        with (sWData[getWidgetIndex(widg)]) {
            mw = nmw + border.x1 + border.x2;
            
            if (mw > w || !widg.isWSizable) {
                w = mw;
                widg.setWidth  (w - border.x1 - border.x2, -1);
            }
            
            if (x + w > this.w)
                x = (w > this.w) ? 0 : this.w - w;
            
            widg.setPosition (this.x + border.x1 + x,this.y + border.y1 + y);
            mgr.requestRedraw;
        }
    }
    override void minHChange (IChildWidget widg, wdim nmh) {
        with (sWData[getWidgetIndex(widg)]) {
            mh = nmh + border.y1 + border.y2;
            
            if (mh > h || !widg.isHSizable) {
                h = mh;
                widg.setHeight (h - border.y1 - border.y2, -1);
            }
            
            if (y + h > this.h)
                y = (h > this.h) ? 0 : this.h - h;
            
            widg.setPosition (this.x + border.x1 + x,this.y + border.y1 + y);
            mgr.requestRedraw;
        }
    }
    
    override 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;
    }
    
    override IChildWidget getWidget (wdim cx, wdim cy) {
        debug scope (failure)
            logger.warn ("getWidget: failure; values: click; pos; width: {},{}; {},{}; {},{}", cx, cy, x, y, w, 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
    }
    
    override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
        if (event > subWidgets.length) return 0;
        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);
                }
            }
        }
	return 0;
    }
    
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;		// new width
                if (w < mw) w = mw;	// limit to min width
                x += ow - w;		// move by difference
                if (x < 0) {		// limit to left edge of area
                    w += x;
                    x = 0;
                }
                subWidgets[active].setWidth  (w - border.x1 - border.x2, 1);
            }
            else if (resizeType & RESIZE.X2) {
                w = xDrag + cx;		// new width
                if (w < mw) w = mw;	// limit to min width
                if (x + w > this.w)	// limit to right edge of area
                    w = this.w - x;
                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;
                if (y < 0) {
                    h += y;
                    y = 0;
                }
                subWidgets[active].setHeight (h - border.y1 - border.y2, 1);
            }
            else if (resizeType & RESIZE.Y2) {
                h = yDrag + cy;
                if (h < mh) h = mh;
                if (y + h > this.h)
                    h = this.h - y;
                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
}