view mde/gui/widget/Floating.d @ 131:9cff74f68b84

Major revisions to popup handling. Buttons can close menus now, plus some smaller impovements. Removed Widget module. Moved Widget.AWidget to AChildWidget.AChildWidget and Widget.AParentWidget to AParentWidget.AParentWidget. Removed ASingleParentWidget to improve code sharing. AChildWidget doesn't implement IParentWidget like AWidget did. New IPopupParentWidget extending IParentWidget for the WM and some widgets to handle popups. Cut old popup management code. New underMouse() function replacing highlight(); called on all widgets. Separate menu-popup and button widgets aren't needed for menus now. Functions returning content widgets have been moved to their own module. Cleaned up jobs.txt. Switched to 80 line length for Ddoc.
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 21 Jan 2009 13:01:40 +0000
parents c5c38eaadb64
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/>. */

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

import mde.gui.widget.AParentWidget;
import mde.gui.exception;
import mde.content.Content;

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) && n != 0 && !(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 false;	// 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, 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
    }
    
    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
}