diff 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 diff
--- a/mde/gui/widget/layout.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/widget/layout.d	Mon May 05 14:47:25 2008 +0100
@@ -19,53 +19,125 @@
 import mde.gui.widget.Widget;
 import mde.gui.exception : WidgetDataException;
 
-/// Encapsulates a grid of Widgets
-class GridWidget : Widget
+/** 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
-        if (data.length < 2) throw new WidgetDataException;
+        // 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
-        // Check: correct data length and rows*cols >= 0 (know data.length - 2 >= 0).
-        if (data.length != 2 + rows * cols) throw new WidgetDataException;
         subWidgets.length = rows*cols;
         foreach (i, inout subWidget; subWidgets) {
             subWidget = window.makeWidget (data[i+2], this);
         }
-        
-        getMinimumSize (w,h);   // Calculate the size (current size is not saved)
     }
     
-    // Calculates from all rows and columns of widgets.
-    void getMinimumSize (out int w, out int h) {
-        if (rows*cols == 0) {    // special case
-            w = h = 0;
-            return;
+    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:;
         }
         
-        // Find the sizes of all subWidgets
-        int[] widgetW = new int[subWidgets.length]; // dimensions
-        int[] widgetH = new int[subWidgets.length];
-        foreach (i,widget; subWidgets) widget.getCurrentSize (widgetW[i],widgetH[i]);
+        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);
         
-        // Find row heights and column widths (non cumulative)
-        rowH.length = rows;
-        colW.length = cols; //WARNING: code reliant on these being initialised to zero
-        for (uint i = 0; i < subWidgets.length; ++i) {
-            uint n = i / cols;  // row
-            if (rowH[n] < widgetH[i]) rowH[n] = widgetH[i];
-            n = i % cols;       // column
-            if (colW[n] < widgetW[i]) colW[n] = widgetW[i];
+        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;
         }
         
-        // rowY / colX
+        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;
-        colX.length = cols;
         int spacing = window.renderer.layoutSpacing;
         
         int cum = 0;
@@ -73,13 +145,16 @@
             rowY[i] = cum;
             cum += x + spacing;
         }
-        h = cum - spacing;      // total height
+        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) {
@@ -90,10 +165,9 @@
             widget.setPosition (x + colX[i % cols], y + rowY[i / cols]);
     }
     
+    
     // Find the relevant widget.
     IWidget getWidget (int cx, int cy) {
-        if (rows*cols == 0) return this;    // special case
-        
         int lx = cx - x, ly = cy - y;       // use coords relative to this widget
         
         // Find the column
@@ -119,6 +193,7 @@
         widg.getCurrentSize (i,j);
         if (lx < i && ly < j)
             return widg.getWidget (cx, cy);
+        return this;    // wasn't in cell
     }
     
     void draw () {
@@ -128,12 +203,40 @@
             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 rows, cols;     // number of cells in grid
+    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[] colW;         // column width (widest widget)
-    int[] rowY;         // cumulative rowH[i-1] + border and padding
-    int[] colX;         // cumulative colW[i-1] + border and padding
+    
+    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 ] */