changeset 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 57d000574d75
children 8c4c96f04e7f
files codeDoc/jobs.txt data/conf/gui.mtt mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/Widget.d mde/gui/widget/Window.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/input/Input.d
diffstat 10 files changed, 378 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Fri May 02 17:38:43 2008 +0100
+++ b/codeDoc/jobs.txt	Mon May 05 14:47:25 2008 +0100
@@ -48,7 +48,3 @@
 
 
 Done (for git log message):
-Renamed mde.global to mde.imde.
-Enabled drawing on demand.
-Allowed options to take double values.
-Made the main loop's polling interval (sleep duration) settable from config files.
\ No newline at end of file
--- a/data/conf/gui.mtt	Fri May 02 17:38:43 2008 +0100
+++ b/data/conf/gui.mtt	Mon May 05 14:47:25 2008 +0100
@@ -3,8 +3,8 @@
 {W1}
 <int|x=30>
 <int|y=80>
-<int[][int]|widgetData=[0:[1003,200,200]]>
+<int[][int]|widgetData=[0:[0x4010,200,200]]>
 {W2}
 <int|x=150>
 <int|y=200>
-<int[][int]|widgetData=[0:[1002,1,3,2,3,5],2:[1001,150,150],3:[1003,150,150],5:[1002,2,1,6,6],6:[1003,73,73]]>
+<int[][int]|widgetData=[0:[0xB004,1,3,2,3,5],2:[0x1,150,150],3:[0x4010,100,100],5:[0xB004,3,1,6,1,6],6:[0x4010,60,60],1:[0x3001]]>
--- a/mde/gui/renderer/IRenderer.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/renderer/IRenderer.d	Mon May 05 14:47:25 2008 +0100
@@ -40,7 +40,7 @@
     /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */
     void drawWidgetBack (int x, int y, int w, int h);
     
-    /** Draw a basic box. Doesn't set the colour. Eventually to be removed. */
-    void drawBox (int x, int y, int w, int h);
+    /** Draws a button frame, in if pushed == true. */
+    void drawButton (int x, int y, int w, int h, bool pushed);
     //END draw routines
 }
--- a/mde/gui/renderer/SimpleRenderer.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/renderer/SimpleRenderer.d	Mon May 05 14:47:25 2008 +0100
@@ -48,7 +48,11 @@
 
     void drawWidgetBack (int x, int y, int w, int h) {}
     
-    void drawBox (int x, int y, int w, int h) {
+    void drawButton (int x, int y, int w, int h, bool pushed) {
+        if (pushed)
+            gl.setColor (1f, 0f, 1f);
+        else
+            gl.setColor (.6f, 0f, .6f);
         gl.drawBox (x,y, w,h);
     }
 }
--- a/mde/gui/widget/Ifaces.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Mon May 05 14:47:25 2008 +0100
@@ -65,22 +65,41 @@
 * Where window is the root window (the window to which the widget belongs), parent is the parent
 * widget, and data is an array of initialisation data. The method should throw a
 * WidgetDataException (created without parameters) if the data has wrong length or is otherwise
-* invalid. */
+* invalid.
+*
+* The widget's size should be set either by it's this() method or by the first call to
+* setSize(). setSize() is called on all widgets immediately after their creation, and throwing an
+* exception at this point (but not on later calls to setSize) is an acceptible method of failure.
+*/
+//NOTE: add another this() without the data for default initialization, for the GUI editor?
 interface IWidget
 {
-    /** Calculate the minimum size the widget could be shrunk to, taking into account
+    /** is the width / height resizable?
+     *
+     * If not, the widget has fixed dimensions equal the output of getMinimalSize. */
+    bool isWSizable ();
+    bool isHSizable (); /// ditto
+    
+    /** Calculate the minimal size the widget could be shrunk to, taking into account
      * child-widgets. */
-    void getMinimumSize (out int w, out int h);
+    void getMinimalSize (out int w, out int h);
     
-    /** Get the current size of the widget.
+    /** Get the current size of the widget. */
+    void getCurrentSize (out int w, out int h);
+    
+    /** Used to adjust the size.
      *
-     * On the first call (during loading), this may be a value saved as part of the config or
-     * something else (e.g. revert to getMinimumSize). */
-    void getCurrentSize (out int w, out int h);
+     * The size should be clamped to the widget's minimal size, i.e. the size set may be larger
+     * than that given by the parameters. Conversely, the size should not be reduced to the
+     * widget's maximal size (if any) but expanded as necessary (alignment to be implemented).
+     *
+     * If the actual size is needed, call getCurrentSize afterwards. */
+    void setSize (int w, int h);
     
     /** Set the current position (i.e. called on init and move). */
     void setPosition (int x, int y);
     
+    
     /** Recursively scan the widget tree to find the widget under (x,y).
      *
      * If called on a widget, that widget should assume the location is over itself, and so should
@@ -100,6 +119,7 @@
      * Widget may assume coordinates are on the widget (caller must check). */
     void clickEvent (ushort cx, ushort cy, ubyte b, bool state);
     
+    
     /** Draw, using the stored values of x and y.
      *
      * Maybe later enforce clipping of all sub-widget drawing, particularly for cases where only
--- a/mde/gui/widget/Widget.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/widget/Widget.d	Mon May 05 14:47:25 2008 +0100
@@ -20,40 +20,35 @@
 import mde.gui.IGui;
 import mde.gui.exception;
 
-import gl = mde.gl.basic;
-
 import tango.io.Stdout;
 
-/** A base widget class. Widgets need not inherit this (they only need implement IWidget), but this
-* class provides a useful basic implementation for widgets.
+/** An abstract base widget class.
 *
-* Do not use directly (i.e. only for inheriting from).
-*/
-class Widget : IWidget
+* This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a
+* useful basic implementation for widgets. Widgets need not inherit these (they only need implement
+* IWidget); they are simply provided for convenience and to promote code reuse. */
+abstract class Widget : IWidget
 {
-    /** Minimum size is zero. */
-    void getMinimumSize (out int w, out int h) {}   // w,h initialised to 0
-    /** Current size. */
-    void getCurrentSize (out int w, out int h) {
-        w = this.w;
-        h = this.h;
+    void getCurrentSize (out int cw, out int ch) {
+        cw = w;
+        ch = h;
     }
     
-    void setPosition (int x, int y) {
-        this.x = x;
-        this.y = y;
+    void setPosition (int nx, int ny) {
+        x = nx;
+        y = ny;
     }
     
-    /** Return self, since we don't have child widgets and the method wouldn't have been called
+    /* Return self, since we don't have child widgets and the method wouldn't have been called
     * unless the location was over us. Valid for all widgets without children. */
     IWidget getWidget (int,int) {
         return this;
     }
     
-    /** Dummy event method (widget doesn't respond to events) */
+    /* Dummy event method (widget doesn't respond to events) */
     void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
     
-    /** Basic draw method: draw the background (all widgets should do this) */
+    /* Basic draw method: draw the background (all widgets should do this) */
     void draw () {
         window.renderer.drawWidgetBack (x,y, w,h);
     }
@@ -63,27 +58,67 @@
     int x, y;               // position
     int w, h;               // size
 }
+/** A base for fixed-size widgets. */
+class FixedWidget : Widget {
+    bool isWSizable () {    return false;   }
+    bool isHSizable () {    return false;   }
+    
+    /* Not resizable, so return current size. */
+    void getMinimalSize (out int mw, out int mh) {
+        mw = wF;
+        mh = hF;
+    }
+    
+    /* Ignore: a fixed size widget. */
+    void setSize (int nw, int nh) {
+        w = (nw >= wF ? nw : wF);
+        h = (nh >= hF ? nh : hF);
+    }
+    
+protected:
+    int wF, hF;             // The "fixed" size, i.e. the preferred & minimal size
+}
+/** A base for resizable widgets. */
+class SizableWidget : Widget {
+    bool isWSizable () {    return true;    }
+    bool isHSizable () {    return true;    }
+    
+    /* Return zero. */
+    void getMinimalSize (out int mw, out int mh) {}
+    
+    /* Set size: a fully resizable widget. */
+    void setSize (int nw, int nh) {
+        w = (nw >= 0 ? nw : 0);
+        h = (nh >= 0 ? nh : 0);
+    }
+}
 
 //BEGIN Widgets
-/// Draws a box. That's it.
-class BoxWidget : Widget
+/// A fixed-size blank widget.
+class FixedBlankWidget : FixedWidget
 {
     this (IWindow wind, IWidget, int[] data) {
         if (data.length != 2) throw new WidgetDataException;
         
         window = wind;
         
-        w = data[0];
-        h = data[1];
+        w = wF = data[0];
+        h = hF = data[1];
     }
-    void draw () {
-        gl.setColor(1f,0f,0f);
-        window.renderer.drawBox (x,y, w,h);
+}
+
+/// A completely resizable blank widget (initial size zero).
+class SizableBlankWidget : SizableWidget
+{
+    this (IWindow wind, IWidget, int[] data) {
+        if (data.length != 0) throw new WidgetDataException;
+        
+        window = wind;
     }
 }
 
 /// First interactible widget
-class ButtonWidget : Widget
+class ButtonWidget : FixedWidget
 {
     bool pushed = false;    // true if button is pushed in (visually)
     // pushed is not the same as the button being clicked but not yet released.
@@ -94,21 +129,12 @@
         
         window = wind;
         
-        w = data[0];
-        h = data[1];
+        w = wF = data[0];
+        h = hF = data[1];
     }
     
     void draw () {
-        if (pushed)
-            gl.setColor (1f, 0f, 1f);
-        else
-            gl.setColor (.6f, 0f, .6f);
-        window.renderer.drawBox (x,y, w,h);
-    }
-    
-    void getMinimumSize (out int w, out int h) {
-        w = this.w; // button is not resizable
-        h = this.h;
+        window.renderer.drawButton (x,y, w,h, pushed);
     }
     
     void clickEvent (ushort, ushort, ubyte b, bool state) {
--- a/mde/gui/widget/Window.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/widget/Window.d	Mon May 05 14:47:25 2008 +0100
@@ -52,7 +52,8 @@
     
     /** Call after loading is finished to setup the window and confirm that it's valid.
      *
-     * Throws: WindowLoadException. Do not use the instance in this case! */
+     * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an
+     * exception occurs! */
     void finalise (IGui gui)
     in {
         assert (gui !is null, "Window.finalise ("~name~"): gui is null");
@@ -64,17 +65,17 @@
         rend = gui.renderer;
         
         // Create the primary widget (and indirectly all sub-widgets), throwing on error:
-        widget = makeWidget (0, this);// primary widget always has ID 0.
-        
-        widgetData = null;          // data is no longer needed: allow GC to collect (cannot safely delete)
+        widget = makeWidget (0, this);  // primary widget always has ID 0.
+        widgetData = null;  // data is no longer needed: allow GC to collect (cannot safely delete)
         
-        widgetX = x + rend.windowBorder;  // widget position
-        widgetY = y + rend.windowBorder;  // must be updated if the window is moved
-        widget.setPosition (widgetX, widgetY);
+        widget.setSize (0,0);           // set the minimal size
+        widget.getCurrentSize (w,h);    // and get this size
+        w += rend.windowBorder * 2;     // Adjust for border
+        h += rend.windowBorder * 2;
         
-        widget.getCurrentSize (w,h);// Find the initial size
-        w += rend.windowBorder * 2;       // Adjust for border
-        h += rend.windowBorder * 2;
+        widgetX = x + rend.windowBorder;    // widget position
+        widgetY = y + rend.windowBorder;    // must be updated if the window is moved
+        widget.setPosition (widgetX, widgetY);
         
         xw = x+w;
         yh = y+h;
@@ -133,20 +134,35 @@
     //END IWindow methods
     
     //BEGIN IWidget methods
-    void getMinimumSize (out int w, out int h) {
-        widget.getMinimumSize (x,y);
-        w += rend.windowBorder * 2;       // Adjust for border
-        h += rend.windowBorder * 2;
+    bool isWSizable () {
+        return widget.isWSizable;
+    }
+    bool isHSizable () {
+        return widget.isHSizable;
+    }
+    
+    void getMinimalSize (out int mw, out int mh) {
+        mw = w + rend.windowBorder * 2;
+        mh = h + rend.windowBorder * 2;
     }
     void getCurrentSize (out int cw, out int ch) {
         cw = w;
         ch = h;
     }
     
-    void setPosition (int x, int y) {
-        // Currently only used internally
-        this.x = x;
-        this.y = y;
+    void setSize (int nw, int nh) {
+        w = nw;
+        h = nh;
+        
+        xw = x+w;
+        yh = y+h;
+        
+        widget.setSize (w - rend.windowBorder * 2, h - rend.windowBorder * 2);
+    }
+    
+    void setPosition (int nx, int ny) {
+        x = nx;
+        y = ny;
         
         xw = x+w;
         yh = y+h;
--- a/mde/gui/widget/createWidget.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Mon May 05 14:47:25 2008 +0100
@@ -22,12 +22,6 @@
 
 import mde.gui.exception : WidgetDataException;
 
-//BEGIN createWidget
-/// Widget types. Start high so they can be reordered easily later.
-enum WIDGET_TYPE : int {
-    BOX = 1001, GRID, BUTTON
-}
-
 /** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
 * data [1..$]. */
 IWidget createWidget (IWindow window, IWidget parent, int[] data)
@@ -39,9 +33,77 @@
     int type = data[0];     // type is first element of data
     data = data[1..$];      // the rest is passed to the Widget
     
-    if (type == WIDGET_TYPE.BOX) return new BoxWidget (window, parent, data);
-    else if (type == WIDGET_TYPE.GRID) return new GridWidget (window, parent, data);
-    else if (type == WIDGET_TYPE.BUTTON) return new ButtonWidget (window, parent, data);
-    else throw new WidgetDataException ("Bad widget type");
+    mixin (binarySearch ("type", WIDGETS));
+    throw new WidgetDataException ("Bad widget type");
+}
+
+/+ for converting to a char[] name (unused)
+static this() {
+    WIDGET_NAMES = [
+            FixedBlank : "FixedBlank",
+            SizableBlank : "SizableBlank",
+            Button : "Button",
+            GridLayout : "GridLayout"
+                    ];
+}+/
+
+private:
+/// Widget types.
+enum WIDGET_TYPE : int {
+    WSIZABLE                = 0x1000,   // horizontally resizable
+    HSIZABLE                = 0x2000,   // vertically resizable
+    INTERACTIBLE            = 0x4000,   // any specific interaction
+    LAYOUT                  = 0x8000,   // is a layout widget (i.e. has sub-widgets)?
+    
+    // Use widget names rather than usual capitals convention
+    
+    // blank: 0x1
+    FixedBlank              = 0x1,
+    SizableBlank            = WSIZABLE | HSIZABLE | 0x1,
+    
+    // buttons: 0x10
+    Button                  = INTERACTIBLE | 0x10,
+    
+    GridLayout              = LAYOUT | WSIZABLE | HSIZABLE | 0x4
 }
-//END createWidget
+
+//const char[][int] WIDGET_NAMES;
+
+// Only used for binarySearch algorithm generation; must be ordered by numerical values.
+const char[][] WIDGETS = [
+        "FixedBlank",
+        "SizableBlank",
+        "Button",
+        "GridLayout"   ];
+
+// Purely to add indentation. Could just return "" without affecting functionality.
+/+static char[] indent (uint i) {
+    char[] ret;
+    for (; i > 0; --i) ret ~= "  ";
+        // This is not executable at compile time:
+        //ret.length = i * 4;		// number of characters for each indentation
+        //ret[] = ' ';		// character to indent with
+    return ret;
+}+/
+char[] indent (uint) {  return "";  }
+
+/* Generates a binary search algorithm. */
+char[] binarySearch (char[] var, char[][] consts, int indents = 0) {
+    if (consts.length > 3) {
+        return indent(indents) ~ "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~
+                binarySearch (var, consts[0 .. $/2], indents + 1) ~
+                indent(indents) ~ "} else {\n" ~
+                binarySearch (var, consts[$/2 .. $], indents + 1) ~
+                indent(indents) ~ "}\n";
+    } else {
+        char[] ret;
+        ret ~= indent(indents);
+        foreach (c; consts) {
+            ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~
+                    indent(indents+1) ~ "return new " ~ c ~ "Widget (window, parent, data);\n" ~
+                    indent(indents) ~ "} else ";
+        }
+        ret = ret[0..$-6] ~ '\n';  // remove last else
+        return ret;
+    }
+}
--- 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 ] */
--- a/mde/input/Input.d	Fri May 02 17:38:43 2008 +0100
+++ b/mde/input/Input.d	Mon May 05 14:47:25 2008 +0100
@@ -196,17 +196,25 @@
         switch (event.type) {
             case SDL_MOUSEBUTTONDOWN:
             case SDL_MOUSEBUTTONUP:
-                foreach (dg; mouseClickCallbacks)
-                    dg (event.button.x - 1, event.button.y - 1,
-                        event.button.button, event.button.state == SDL_PRESSED);
+                foreach (dg; mouseClickCallbacks) {
+                    try
+                        dg (event.button.x - 1, event.button.y - 1,
+                            event.button.button, event.button.state == SDL_PRESSED);
+                    catch (Exception e)
+                        logger.error (CB_EXC ~ e.msg);
+                }
                 break;
             
             case SDL_MOUSEMOTION:
                 mouse_x = event.motion.x - 1;
                 mouse_y = event.motion.y - 1;
                 
-                foreach (dg; mouseMotionCallbacks)
-                    dg (event.motion.x - 1, event.motion.y - 1);
+                foreach (dg; mouseMotionCallbacks) {
+                    try
+                        dg (event.motion.x - 1, event.motion.y - 1);
+                    catch (Exception e)
+                        logger.error (CB_EXC ~ e.msg);
+                }
                 break;
             
             default:
@@ -369,6 +377,8 @@
         }
     }
     
+    static const CB_EXC = "Callback exception: ";
+    
     static Logger logger;
 
     Config config;			// Configuration
@@ -485,7 +495,12 @@
         myThis.button[id] = b;
         
         ButtonCallback[]* cb_p = id in myThis.buttonCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, b);
+        if (cb_p) foreach (cb; *cb_p) {
+            try
+                cb (id, b);
+            catch (Exception e)
+                logger.error (CB_EXC ~ e.msg);
+        }
     }
     // Adjuster to check modifier keys
     static void es_b_modifier (Input myThis, bool b, readOutQueue s);
@@ -497,7 +512,12 @@
         myThis.axis[id] = x;
         
         AxisCallback[]* cb_p = id in myThis.axisCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, x);
+        if (cb_p) foreach (cb; *cb_p) {
+            try
+                cb (id, x);
+            catch (Exception e)
+                logger.error (CB_EXC ~ e.msg);
+        }
     }
     
     // Just reverses an axis's value
@@ -512,7 +532,12 @@
         myThis.relMotion[id] = RelPair(x,y);
         
         RelMotionCallback[]* cb_p = id in myThis.relMotionCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, x,y);
+        if (cb_p) foreach (cb; *cb_p) {
+            try
+                cb (id, x,y);
+            catch (Exception e)
+                logger.error (CB_EXC ~ e.msg);
+        }
     }
     //END ES Functions
     //END Event stream functionality