view mde/gui/widget/layout.d @ 37:052df9b2fe07

Allowed widget resizing, changed widget IDs and made Input catch any callback exceptions. Enabled widget resizing. Removed IRenderer's temporary drawBox method and added drawButton for ButtonWidget. Made the Widget class abstract and added FixedWidget and SizableWidget classes. Rewrote much of createWidget to use meta-code; changed widget IDs. Made Input catch callback exceptions and report error messages. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 05 May 2008 14:47:25 +0100
parents 6b4116e6355c
children 8c4c96f04e7f
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/>. */

/// Gui layout widgets.
module mde.gui.widget.layout;

import mde.gui.widget.Widget;
import mde.gui.exception : WidgetDataException;

/** Encapsulates a grid of Widgets.
*
* Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */
class GridLayoutWidget : Widget
{
    this (IWindow wind, IWidget, int[] data) {
        // Get grid size and check data
        // Check sufficient data for rows, cols, and at least one widget:
        if (data.length < 3) throw new WidgetDataException;
        rows = data[0];
        cols = data[1];
        if (data.length != 2 + rows * cols) throw new WidgetDataException;
        /* data.length >= 3 so besides checking the length is correct, this tells us:
         *      rows * cols >= 3 - 2 = 1            a free check!
         * The only thing not checked is whether both rows and cols are negative, which would
         * cause an exception when dynamic arrays are allocated later (and is unlikely). */
        
        window = wind;
        
        // Get all sub-widgets
        subWidgets.length = rows*cols;
        foreach (i, inout subWidget; subWidgets) {
            subWidget = window.makeWidget (data[i+2], this);
        }
    }
    
    bool isWSizable () {
        if (colSizable == -2) {     // check whether any columns are resizable
            for1:
            for (uint i = 0; i < cols; ++i) {                       // for each column
                for (uint j = 0; j < subWidgets.length; j += cols)  // for each row
                    if (!subWidgets[i+j].isWSizable)        // column not resizable
                        continue for1;                      // continue the outer for loop
                
                // column is resizable if we get to here
                colSizable = i;
                goto break1;        // use goto in lieu of for...else
            }
            
            // if we get here, no resizable column was found
            colSizable = -1;
            
            break1:;
        }
        
        if (colSizable >= 0) return true;
        else return false;
    }
    
    bool isHSizable () {
        if (rowSizable == -2) {     // check whether any columns are resizable
            for2:
            for (uint i = 0; i < subWidgets.length; i += cols) {    // for each row
                for (uint j = 0; j < cols; ++j)                     // for each column
                    if (!subWidgets[i+j].isHSizable)
                        continue for2;
                
                rowSizable = i / cols;  // the current row
                goto break2;
            }
            
            rowSizable = -1;
            
            break2:;
        }
        
        if (rowSizable >= 0) return true;
        else return false;
    }
    
    /* Calculates the minimal size from all rows and columns of widgets. */
    void getMinimalSize (out int mw, out int mh) {
        // If rowHMin & colWMin are null, calculate them. They are set null whenever the contents
        // or the contents' minimal size change, as well as when this widget is created.
        if (rowHMin is null)
            genMinRowColSizes;
        
        // Calculate the size, starting with the spacing:
        mh = window.renderer.layoutSpacing;     // use temporarily
        mw = mh * (cols - 1);
        mh *= (rows - 1);
        
        foreach (x; colWMin)            // add the column/row's dimensions
            mw += x;
        foreach (x; rowHMin)
            mh += x;
    }
    
    void setSize (int nw, int nh) {
        // Step 1: calculate the minimal row/column sizes.
        int mw, mh; // FIXME: use w,h directly?
        getMinimalSize (mw, mh);
        colW = colWMin;         // start with these dimensions, and increase if necessary
        rowH = rowHMin;
        
        // Step 2: clamp nw/nh or expand a column/row to achieve the required size
        if (nw <= mw) nw = mw;  // clamp to minimal size
        else {
            if (isWSizable)     // calculates colSizable; true if any is resizable
                colW[colSizable] += nw - mw; // new width
            else                // no resizable column; so force the last one
                colW[$-1] += nw - mw;
        }
        
        if (nh <= mh) nh = mh;
        else {
            if (isHSizable)
                rowH[rowSizable] += nh - mh;
            else
                rowH[$-1] += nh - mh;
        }
        
        // Step 3: set each sub-widget's size.
        foreach (i,widget; subWidgets)
            widget.setSize (colW[i % cols], rowH[i / cols]);
        
        // Step 4: calculate the column and row positions
        colX.length = cols;
        rowY.length = rows;
        int spacing = window.renderer.layoutSpacing;
        
        int cum = 0;
        foreach (i, x; rowH) {
            rowY[i] = cum;
            cum += x + spacing;
        }
        h = cum - spacing;      // set the total height
        assert (h == nh);       // FIXME: remove and set w/h directly once this is asserted
        
        cum = 0;
        foreach (i, x; colW) {
            colX[i] = cum;
            cum += x + spacing;
        }
        w = cum - spacing;      // total width
        assert (w == nw);
    }
    
    void setPosition (int x, int y) {
        this.x = x;
        this.y = y;
        
        foreach (i,widget; subWidgets)
            widget.setPosition (x + colX[i % cols], y + rowY[i / cols]);
    }
    
    
    // Find the relevant widget.
    IWidget getWidget (int cx, int cy) {
        int lx = cx - x, ly = cy - y;       // use coords relative to this widget
        
        // Find the column
        int i = cols - 1;                   // starting from right...
        while (lx < colX[i]) {              // decrement while left of this column
            if (i == 0) return this;        // left of first column
            --i;
        }                                   // now (lx >= colX[i])
        if (lx >= colX[i] + colW[i]) return this;   // between columns
        
        // Find the row;
        int j = rows - 1;
        while (ly < rowY[j]) {
            if (j == 0) return this;
            --j;
        }
        if (ly >= rowY[j] + rowH[j]) return this;
        
        // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell)
        lx -= colX[i];
        ly -= rowY[j];
        IWidget widg = subWidgets[i + j*cols];
        widg.getCurrentSize (i,j);
        if (lx < i && ly < j)
            return widg.getWidget (cx, cy);
        return this;    // wasn't in cell
    }
    
    void draw () {
        super.draw ();
        
        foreach (widget; subWidgets)
            widget.draw ();
    }
    
private:
    void genMinRowColSizes () {
        // Find the sizes of all subWidgets
        int[] widgetW = new int[subWidgets.length]; // dimensions
        int[] widgetH = new int[subWidgets.length];
        foreach (i,widget; subWidgets)
            widget.getMinimalSize (widgetW[i],widgetH[i]);
            
        // Find the minimal row heights and column widths (non cumulative)
        colWMin = new int[cols];    // set length
        rowHMin = new int[rows];
        for (uint i = 0; i < subWidgets.length; ++i) {
            uint n;
            n = i % cols;           // column
            if (colWMin[n] < widgetW[i]) colWMin[n] = widgetW[i];
            n = i / cols;           // row
            if (rowHMin[n] < widgetH[i]) rowHMin[n] = widgetH[i];
        }
    }
    
protected:
    int cols, rows;     // number of cells in grid
    
    int colSizable = -2;// 0..cols-1 means this column is resizable
    int rowSizable = -2;// -2 means not calculated yet, -1 means not resizable
    
    int[] colWMin;      // minimal column width
    int[] rowHMin;      // minimal row height
    int[] colW;         // column width (widest widget)
    int[] rowH;         // row height (highest widget in the row)
    
    int[] colX;         // cumulative colW[i-1] + padding (add x to get column's left x-coord)
    int[] rowY;         // cumulative rowH[i-1] + padding
    
    IWidget[] subWidgets;   // all widgets in the grid (by row):
    /* SubWidget order:    [ 0 1 ]
    *                      [ 2 3 ] */
}