changeset 45:0fd51d2c6c8a

Several changes to resising windows and layout widgets. This commit still has some bugs. Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget. Rewrote some of GridLayoutWidget's implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems. Allowed layout's to resize from either direction (only with window resizes). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 22 May 2008 11:34:09 +0100
parents 07bd1a09e161
children 03fa79a48c48
files codeDoc/jobs.txt data/conf/gui.mtt mde/gl/basic.d mde/gl/draw.d mde/gl/tex2d.d mde/gui/Gui.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/gui/widget/miscWidgets.d mde/mde.d mde/resource/font.d mde/scheduler/Init.d mde/sdl.d
diffstat 16 files changed, 562 insertions(+), 458 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Fri May 16 12:22:10 2008 +0100
+++ b/codeDoc/jobs.txt	Thu May 22 11:34:09 2008 +0100
@@ -4,11 +4,16 @@
 
 In progress:
 Implementing font rendering
+Reading ft tutorial
+Use cartesian coordinates? Or not?
+Make layout's adjustCellSizes method call setSize?
+More resizing stuff...
 
 
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
 Also see todo.txt and FIXME/NOTE comment marks.
+5   mergetag crashes with no ending > on a tag and doesn't add header data when comments are included!
 4   Not guaranteed to catch up-click ending callback! Appears not to be a problem...
 4   OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
 3   on-event draw support (mde.events and GUI need to tell mde.mde)
@@ -51,3 +56,6 @@
 
 
 Done (for git log message):
+Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget.
+Rewrote some of GridLayoutWidget's implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems.
+Allowed layout's to resize from either direction (only with window resizes).
--- a/data/conf/gui.mtt	Fri May 16 12:22:10 2008 +0100
+++ b/data/conf/gui.mtt	Thu May 22 11:34:09 2008 +0100
@@ -8,3 +8,7 @@
 <int|x=150>
 <int|y=200>
 <int[][int]|widgetData=[0:[0xB004,5,5,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2],1:[0x3001],2:[0x2]]>
+{WEmbedded}
+<int|x=20>
+<int|y=100>
+<int[][int]|widgetData=[1:[0x4010,50,50],2:[0xB004,3,1,3,1,3],3:[0x3001],0:[0xB004,3,1,3,1,3]]>
--- a/mde/gl/basic.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gl/basic.d	Thu May 22 11:34:09 2008 +0100
@@ -24,8 +24,10 @@
 
 //BEGIN GL & window setup
 void glSetup () {
+    glDisable(GL_DEPTH_TEST);
+    glEnable (GL_TEXTURE_2D);
+    
     glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
-    glDisable(GL_DEPTH_TEST);
     
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
@@ -38,8 +40,9 @@
     glViewport (0,0,w,h);
     
     // Make the top-left the origin (see gui/GUI notes.txt):
+    // Note that this only affects vertex operations − direct rasterisation operations are
+    // unaffected!
     glOrtho (0.0,w, h,0.0, -1.0, 1.0);
-    //glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0);
     
     glMatrixMode(GL_MODELVIEW);
 }
--- a/mde/gl/draw.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gl/draw.d	Thu May 22 11:34:09 2008 +0100
@@ -24,6 +24,12 @@
 import derelict.opengl.gl;
 
 import tango.time.Time;     // TimeSpan (type only; unused)
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gl.draw");
+}
 
 //BEGIN Drawing loop
 // Temporary draw function
@@ -32,7 +38,13 @@
     
     gui.draw ();
     
-    glFlush();
+    GLenum err = glGetError();
+    if (err != GL_NO_ERROR) {
+        char[128] tmp;
+        logger.error (logger.format (tmp, "GL error: {}", err));
+    }
+    
+    glFinish();		// Use Finish rather than Flush to make sure gl is ready to swap buffers
     SDL_GL_SwapBuffers();
 }
 //END Drawing loop
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gl/tex2d.d	Thu May 22 11:34:09 2008 +0100
@@ -0,0 +1,20 @@
+/* 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/>. */
+
+/// Experiment for texturing
+module mde.gl.tex2d;
+
+import derelict.opengl.gl;
+
--- a/mde/gui/Gui.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/Gui.d	Thu May 22 11:34:09 2008 +0100
@@ -63,26 +63,20 @@
         }
         
         IReader reader;
-        try {
-            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true);
-            reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
-                return new Window (id);
-            };
-            reader.read;
-        } catch (mt.MTException e) {
-            logger.error ("Loading GUI aborted:");
-            logger.error (e.msg);
-            
-            return;
-        }
+        reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true);
+        reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
+            return new Window (id);
+        };
+        reader.read;
         
         // Get the renderer
         char[]* p = RENDERER in reader.dataset.header.Arg!(char[]).Arg;
         if (p is null || *p is null) {
-            logger.error ("Loading GUI aborted: no renderer specified");
-            return;
+            logger.warn ("no renderer specified: defaulting to Simple");
+            rendName = "Simple";
         }
-        rendName = *p;
+        else
+            rendName = *p;
         
         // get list
         windows.length = reader.dataset.sec.length; // pre-allocate
@@ -94,8 +88,8 @@
                 continue;
             }
             try {
-                w.finalise (this);
-                windows ~= w;       // only add if load successful
+                w.finalise (this);	// if this fails, the window (but nothing else) won't work
+                windows ~= w;		// only add if load successful
             } catch (Exception e) {
                 logger.error ("Window failed to load: " ~ e.msg);
             }
--- a/mde/gui/widget/Ifaces.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Thu May 22 11:34:09 2008 +0100
@@ -110,8 +110,8 @@
     bool isWSizable ();
     bool isHSizable (); /// ditto
     
-    /** Calculate the minimal size the widget could be shrunk to, taking into account
-     * child-widgets. */
+    /** Calculate the minimal size the widget could be shrunk to (or its fixed size), taking into
+     * account child-widgets or other contents. */
     void getMinimalSize (out int w, out int h);
     
     /** Get the current size of the widget. */
@@ -119,16 +119,19 @@
     
     /** Used to adjust the size.
      *
-     * setPosition should always be called after setSize (for layout widgets). Adding this
-     * restriction appears to be the most efficient approach without a lot more tests.
+     * w,h is the new size. The boolean parameters describe which direction to resize from and is
+     * only really relevent to layout widgets (see GridLayoutWidget's implementation). When
+     * calling, just past true,true if it doesn't matter.
      *
      * Implementation:
      * 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).
+     * This should be true for both resizable and fixed widgets; fixed widgets may still be scaled
+     * to fill a whole row/column in a layout widget.
      *
      * If the actual size is needed, call getCurrentSize afterwards. */
-    void setSize (int w, int h);
+    void setSize (int w, int h, bool, bool);
     
     /** Set the current position (i.e. called on init and move). */
     void setPosition (int x, int y);
--- a/mde/gui/widget/Widget.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/widget/Widget.d	Thu May 22 11:34:09 2008 +0100
@@ -13,17 +13,16 @@
 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 Widget module.
+/** GUI Widget module.
+ *
+ * This module contains some base widget classes suitable for widget classes to inherit. However,
+ * inheriting one of them is by no means necessary for a widget so long as the IWidget interface is
+ * implemented. */
 module mde.gui.widget.Widget;
 
 public import mde.gui.widget.Ifaces;
-import mde.gui.exception;
 import mde.gui.renderer.IRenderer;
 
-import mde.resource.font;
-
-import tango.io.Stdout;
-
 /** An abstract base widget class.
 *
 * This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a
@@ -31,7 +30,8 @@
 * IWidget); they are simply provided for convenience and to promote code reuse. */
 abstract class Widget : IWidget
 {
-    // Base this(); all widgets must at least check data.length is correct.
+//BEGIN Load and save
+    // Base this(). All widgets must check data.length is correct before calling this method.
     this (IWindow wind, int[] data) {
         window = wind;
         widgetType = data[0];
@@ -40,14 +40,12 @@
     // Most widgets don't need to do adjustments based on mutable data, however they usually do
     // still need to set their size.
     int[] adjust (int[] data) {
-        setSize (0,0);
+        setSize (0,0,true,true);
         return data;
     }
     
-    bool isWSizable () {    return false;   }
-    bool isHSizable () {    return false;   }
-    
-    // Widget type should always be the first value.
+    // Widget type should always be the first value. Any widget using extra creation data will need
+    // to reimplemnt this method.
     int[] getCreationData () {
         return [widgetType];
     }
@@ -55,181 +53,83 @@
     int[] getMutableData () {
         return [];
     }
+//END Load and save
+    
+//BEGIN Size and position
+    bool isWSizable () {    return false;   }
+    bool isHSizable () {    return false;   }
+    
+    /* Return minimal/fixed size. */
+    void getMinimalSize (out int a, out int b) {
+        a = mw;
+        b = mh;
+    }
     
     void getCurrentSize (out int cw, out int ch) {
         cw = w;
         ch = h;
     }
     
+    /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow
+     * enlarging, so in both cases this is a correct implementation. */
+    void setSize (int nw, int nh, bool, bool) {
+        w = (nw >= mw ? nw : mw);
+        h = (nh >= mh ? nh : mh);
+    }
+    
     void setPosition (int nx, int ny) {
         x = nx;
         y = ny;
     }
+//END Size and position
     
-    /* 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. */
+//BEGIN Events
+    /* This method is only called when the location is over this widget; hence for all widgets
+     * without children this method is valid. */
     IWidget getWidget (int,int) {
         return this;
     }
     
-    /* Dummy event method (widget doesn't respond to events) */
+    /* Dummy event method (suitable for all widgets which don't respond to events). */
     void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
+//END Events
     
-    /* 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);
     }
     
 protected:
-    final int widgetType;   // the type (stored for saving)
-    IWindow window;         // the enclosing window
-    int x, y;               // position
-    int w, h;               // size
+    final int widgetType;	// the type (stored for saving)
+    IWindow window;		// the enclosing window
+    int x, y;			// position
+    int w, h;			// size
+    int mw = 0, mh = 0;		// minimal or fixed size, depending on whether the widget is
+    				// resizible; both types of widgets should actually be expandable.
 }
-/** A base for fixed-size widgets. */
+
+/** A base for fixed-size widgets taking their size from the creation data. */
 class FixedWidget : Widget {
+    // Check data.length is at least 3 before calling!
     this (IWindow wind, int[] data) {
-        w = wF;
-        h = hF;
+        mw = data[1];
+        mh = data[2];
         super (wind, data);
+        w = mw;
+        h = mh;
     }
     
     int[] getCreationData () {
-        return [widgetType, wF, hF];
-    }
-    
-    /* Not resizable, so return current size. */
-    void getMinimalSize (out int mw, out int mh) {
-        mw = wF;
-        mh = hF;
+        return [widgetType, mw, mh];
     }
-    
-    /* 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 {
+    // Check data.length is at least 1 before calling!
     this (IWindow wind, int[] data) {
         super (wind, data);
     }
     
     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);
-    }
-    
-    void draw () {
-        super.draw;
-        
-        window.renderer.drawBlank (x,y, w,h);
-    }
 }
-
-//BEGIN Widgets
-/// A fixed-size blank widget.
-class FixedBlankWidget : FixedWidget
-{
-    this (IWindow wind, int[] data) {
-        if (data.length != 3) throw new WidgetDataException;
-        wF = data[1];
-        hF = data[2];
-        super (wind, data);
-    }
-    void draw () {
-        super.draw;
-        
-        window.renderer.drawBlank (x,y, w,h);
-    }
-}
-
-/// A completely resizable blank widget (initial size zero).
-class SizableBlankWidget : SizableWidget
-{
-    this (IWindow wind, int[] data) {
-        if (data.length != 1) throw new WidgetDataException;
-        super (wind, data);
-    }
-}
-
-/// First interactible 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.
-    // it is whether the mouse is over the button after being clicked.
-    
-    this (IWindow wind, int[] data) {
-        if (data.length != 3) throw new WidgetDataException;
-        wF = data[1];
-        hF = data[2];
-        super (wind, data);
-    }
-    
-    void draw () {
-        window.renderer.drawButton (x,y, w,h, pushed);
-    }
-    
-    void clickEvent (ushort, ushort, ubyte b, bool state) {
-        if (b == 1 && state == true) {
-            pushed = true;
-            window.requestRedraw;
-            window.gui.addClickCallback (&clickWhileHeld);
-            window.gui.addMotionCallback (&motionWhileHeld);
-        }
-    }
-    // Called when a mouse motion/click event occurs while (held == true)
-    bool clickWhileHeld (ushort cx, ushort cy, ubyte b, bool state) {
-        if (b == 1 && state == false) {
-            if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
-                Stdout ("Button clicked!").newline;
-            
-            pushed = false;
-            window.requestRedraw;
-            window.gui.removeCallbacks (cast(void*) this);
-            
-            return true;
-        }
-        return false;
-    }
-    void motionWhileHeld (ushort cx, ushort cy) {
-        bool oldPushed = pushed;
-        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
-        else pushed = false;
-        if (oldPushed != pushed)
-            window.requestRedraw;
-    }
-}
-
-/// Basic text widget
-class TextWidget : FixedWidget
-{
-    this (IWindow wind, int[] data) {
-        if (data.length != 1) throw new WidgetDataException;
-        wF = 100;	//FIXME: set properly
-        hF = 25;
-        super (wind,data);
-    }
-    
-    void draw () {
-        super.draw();
-        if (font is null) font = Font.get("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf");
-        font.drawStr (x,y, "Text Widget");
-    }
-    
-protected:
-    static Font font;
-}
-//END Widgets
--- a/mde/gui/widget/Window.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/widget/Window.d	Thu May 22 11:34:09 2008 +0100
@@ -219,7 +219,7 @@
         ch = h;
     }
     
-    void setSize (int nw, int nh) {
+    void setSize (int nw, int nh, bool wB, bool hB) {
         getMinimalSize (w,h);
         if (nw > w) w = nw;     // expand if new size is larger, but don't go smaller
         if (nh > h) h = nh;
@@ -227,7 +227,7 @@
         xw = x+w;
         yh = y+h;
         
-        widget.setSize (w - border.l - border.r, h - border.t - border.b);
+        widget.setSize (w - border.l - border.r, h - border.t - border.b, wB, hB);
         
         gui_.requestRedraw ();  // obviously necessary whenever the window's size is changed
     }
@@ -303,32 +303,39 @@
         setPosition (cx-xDrag, cy-yDrag);
     }
     void resizeCallback (ushort cx, ushort cy) {
+        debug scope(failure)
+                logger.trace ("resizeCallback: failure");
+        
+        // This function is only called if some resize is going to happen.
+        // To improve efficiency, store parameters to resize to.
+        int xSize = w, ySize = h;	// new size
+        int xDiff, yDiff;		// difference to new position
+        bool xHigh, yHigh;		// resize from positive side
+        
         if (resizeType & RESIZE_TYPE.L) {
-            int mw, nw;
-            getMinimalSize (mw, nw);    // (only want mw)
-            nw = xDrag - cx;
-            if (nw < mw) nw = mw;       // clamp
-            mw = x + w - nw;            // reuse
-            setSize (nw, h);
-            setPosition (mw, y);
+            getMinimalSize (xDiff, xSize);	// (only want xDiff, temporarily used as mw)
+            xSize = xDrag - cx;
+            if (xSize < xDiff) xSize = xDiff;	// clamp
+            xDiff = w - xSize;			// now used as amount to move
         }
         else if (resizeType & RESIZE_TYPE.R) {
-            setSize (xDrag + cx, h);
-            setPosition (x, y);         // required to call after setSize.
+            xSize = xDrag + cx;
+            xHigh = true;
         }
         if (resizeType & RESIZE_TYPE.T) {
-            int mh, nh;
-            getMinimalSize (nh, mh);
-            nh = yDrag - cy;
-            if (nh < mh) nh = mh;
-            mh = y + h - nh;
-            setSize (w, nh);
-            setPosition (x, mh);
+            getMinimalSize (ySize, yDiff);
+            ySize = yDrag - cy;
+            if (ySize < yDiff) ySize = yDiff;
+            yDiff = h - ySize;
         }
         else if (resizeType & RESIZE_TYPE.B) {
-            setSize (w, yDrag + cy);
-            setPosition (x, y);
+            ySize = yDrag + cy;
+            yHigh = true;
         }
+        
+        setSize (xSize, ySize, xHigh, yHigh);
+        if (xDiff != 0 || yDiff != 0)
+            setPosition (x + xDiff, y + yDiff);
     }
     bool endCallback (ushort cx, ushort cy, ubyte b, bool state) {
         if (b == 1 && state == false) {
--- a/mde/gui/widget/createWidget.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Thu May 22 11:34:09 2008 +0100
@@ -16,11 +16,12 @@
 /// GUI Widget module.
 module mde.gui.widget.createWidget;
 
+import mde.gui.widget.Ifaces;
+import mde.gui.exception : WidgetDataException;
+
 // Widgets to create:
 import mde.gui.widget.layout;
-import mde.gui.widget.Widget;
-
-import mde.gui.exception : WidgetDataException;
+import mde.gui.widget.miscWidgets;
 
 /** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
 * data [1..$]. */
--- a/mde/gui/widget/layout.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/gui/widget/layout.d	Thu May 22 11:34:09 2008 +0100
@@ -19,7 +19,6 @@
 import mde.gui.widget.Widget;
 import mde.gui.exception;
 
-import tango.io.Stdout;
 debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
@@ -47,10 +46,11 @@
         rows = data[1];
         cols = data[2];
         if (data.length != 3 + rows * cols) throw new WidgetDataException;
-        /* data.length >= 3 so besides checking the length is correct, this tells us:
+        /* data.length >= 4 so besides checking the length is correct, this tells us:
          *      rows * cols >= 4 - 3 = 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). */
+         * cause an exception when dynamic arrays are allocated by genCachedConstructionData, which
+         * is an acceptible method of failure (and is unlikely anyway). */
         
         // Get all sub-widgets
         subWidgets.length = rows*cols;
@@ -74,25 +74,24 @@
         
         int lenUsed = 0;
         if (data.length < rows + cols) {    // data error; use defaults
-            colW = colWMin.dup;
-            rowH = rowHMin.dup;
+            col.dupMin;
+            row.dupMin;
         } else {                            // sufficient data
             lenUsed = rows+cols;
-            colW = data[0..cols];
-            rowH = data[cols..lenUsed];
-            
-            // Check row sizes are valid:
-            //NOTE: this could be made optional
-            //NOTE: could also check non-resizable sizes are not too large
-            foreach (i, ref w; colW)
-                if (w < colWMin[i]) w = colWMin[i];
-            foreach (i, ref h; rowH)
-                if (h < rowHMin[i]) h = rowHMin[i];
+            col.setCheck (data[0..cols]);
+            row.setCheck (data[cols..lenUsed]);
         }
         
-        genCachedMutableData;
-        w = colW[$-1] + colX[$-1];
-        h = rowY[$-1] + rowH[$-1];
+        
+        // Generate cached mutable data
+        // Calculate column and row locations:
+        w = col.genPositions;
+        h = row.genPositions;
+        
+        // Tell subwidgets their new sizes. Positions are given by a later call to setPosition.
+        foreach (i,widget; subWidgets)
+            // Resizing direction is arbitrarily set to "high direction":
+            widget.setSize (col.width[i % cols], row.width[i / cols], true, true);
         
         return data[lenUsed..$];
     }
@@ -113,16 +112,15 @@
         foreach (widget; subWidgets)
             ret ~= widget.getMutableData;
         
-        ret ~= colW ~ rowH;
+        ret ~= col.width ~ row.width;
         return ret;
     }
     
     bool isWSizable () {
-        return (sizableCols.length != 0);
+        return col.firstSizable >= 0;
     }
-    
     bool isHSizable () {
-        return (sizableRows.length != 0);
+        return row.firstSizable >= 0;
     }
     
     /* Calculates the minimal size from all rows and columns of widgets. */
@@ -131,16 +129,28 @@
         mh = this.mh;
     }
     
-    void setSize (int nw, int nh) {
-        // Step 1: calculate the row/column sizes.
-        w += adjustCellSizes (colW, colWMin, sizableCols, nw - w, true);
-        h += adjustCellSizes (rowH, rowHMin, sizableRows, nh - h, true);
+    void setSize (int nw, int nh, bool wHigh, bool hHigh) {
+        debug scope (failure) {
+            char[128] tmp;
+                logger.trace ("setSize failed: hHigh = " ~ (hHigh ? "true" : "false"));
+                logger.trace (logger.format (tmp, "rows to resize: {}, {}", row.firstSizable, row.lastSizable));
+        }
+        // Optimisation (could easily be called with same sizes if a parent layout widget is
+        // resized, since many columns/rows may not be resized).
+        if (nw == w && nh == h) return;
         
-        // Step 2: calculate the row/column offsets (positions) and set the sub-widget's sizes.
-        genCachedMutableData;
+        // calculate the row/column sizes (and new positions)
+        if (wHigh)
+            w += col.adjustCellSizes (nw - w, col.lastSizable, -1);
+        else
+            w += col.adjustCellSizes (nw - w, col.firstSizable, 1);
+        if (hHigh)
+            h += row.adjustCellSizes (nh - h, row.lastSizable, -1);
+        else
+            h += row.adjustCellSizes (nh - h, row.firstSizable, 1);
         
-        // Step 3: position needs to be set
-        // Currently this happens by specifying that setPosition should be run after setSize.
+        // set the sub-widget's sizes & positions
+        setSubWidgetSP (wHigh, hHigh);
     }
     
     void setPosition (int x, int y) {
@@ -148,38 +158,22 @@
         this.y = y;
         
         foreach (i,widget; subWidgets)
-            widget.setPosition (x + colX[i % cols], y + rowY[i / cols]);
+            widget.setPosition (x + col.pos[i % cols], y + row.pos[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
-            debug assert (i > 0, "getWidget: left of first column");  // should be impossible
-            --i;
-        }                                   // now (lx >= colX[i])
-        if (lx >= colX[i] + colW[i]) return this;   // between columns
+        debug scope (failure)
+                logger.warn ("getWidget: failure");
+        // Find row/column:
+        myDiff i = col.getCell (cx - x);
+        myDiff j = row.getCell (cy - y);
+        if (i < 0 || j < 0)	// on a space between widgets
+            return this;
         
-        // Find the row;
-        int j = rows - 1;
-        while (ly < rowY[j]) {
-            debug assert (j > 0, "getWidget: above first row");   // should be impossible
-            --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
+        // On a subwidget; recurse call:
+        return subWidgets[i + j*cols].getWidget (cx, cy);
     }
     
     // Resizing columns & rows
@@ -191,52 +185,9 @@
             * sub-widget, so we know it's on some divisor (so at least one of resizeCol and
             * resizeRow is non-negative). */
             
-            // Find the column
-            if (sizableCols.length != 0) {
-                int l = cx - x;                 // use relative coords
-                size_t i = cols - 1;            // index, from right
-                while (l < colX[i]) {           // decrement while left of this column
-                    debug assert (i > 0, "clickEvent: left of first column");
-                    --i;
-                }                               // now (l >= colX[resizeCol])
-                if (l < colX[i] + colW[i]) i = -1;  // on a sub-widget
-                
-                // Set resizeColsL / resizeColsH
-                // Want to find j such that [0..j],[j..$] divide sizableCols about i:
-                size_t j = 0;
-                while (j < sizableCols.length && sizableCols[j] <= i) ++j;
-                
-                resizeColsL = sizableCols[0..j];
-                resizeColsH = sizableCols[j..$];
-                
-                // Cannot resize if either list is empty. resizeCallback checks the length of L,
-                // but to save it checking R too, we set L's length zero if R's is.
-                if (resizeColsH.length == 0)
-                    resizeColsL = null;
-            }
-            
-            // Find the row
-            if (sizableRows.length != 0) {
-                int l = cy - y;
-                size_t i = rows - 1;
-                while (l < rowY[i]) {
-                    debug assert (i > 0, "clickEvent: above first row");
-                    --i;
-                }
-                if (l < rowY[i] + rowH[i]) i = -1;
-                
-                size_t j = 0;
-                while (j < sizableRows.length && sizableRows[j] <= i) ++j;
-                
-                resizeRowsL = sizableRows[0..j];
-                resizeRowsH = sizableRows[j..$];
-                
-                if (resizeRowsH.length == 0)
-                    resizeRowsL = null;
-            }
-            
-            if (resizeColsL is null && resizeRowsL is null)
-                return;     // no resizing to do
+            // find col/row's resizeD & resizeU
+            if (col.findResize (cx - x) && row.findResize (cy - y))
+                return;		// unable to resize
             
             dragX = cx;
             dragY = cy;
@@ -258,212 +209,265 @@
     /* Calculations which need to be run whenever a new sub-widget structure is set
      * (i.e. to produce cached data calculated from construction data). */
     void genCachedConstructionData () {
+        col.spacing = row.spacing = window.renderer.layoutSpacing;
+        
         // Calculate the minimal column and row sizes:
-        colWMin = new int[cols];    // set length, making sure the arrays are initialised to zero
-        rowHMin = new int[rows];
+        // set length, making sure the arrays are initialised to zero:
+        col.minWidth = new int[cols];
+        row.minWidth = new int[rows];
         int ww, wh;     // sub-widget minimal sizes
         foreach (i,widget; subWidgets) {
             widget.getMinimalSize (ww, wh);
             
             // Increase dimensions if current minimal size is larger:
-            uint n = i % cols;      // column
-            if (colWMin[n] < ww) colWMin[n] = ww;
-            n = i / cols;           // row
-            if (rowHMin[n] < wh) rowHMin[n] = wh;
+            myIt n = i % cols;	// column
+            if (col.minWidth[n] < ww) col.minWidth[n] = ww;
+            n = i / cols;		// row
+            if (row.minWidth[n] < wh) row.minWidth[n] = wh;
         }
         
         
         // Calculate the overall minimal size, starting with the spacing:
-        mh = window.renderer.layoutSpacing;     // use mh temporarily
+        mh = window.renderer.layoutSpacing;	// use mh temporarily
         mw = mh * (cols - 1);
         mh *= (rows - 1);
         
-        foreach (x; colWMin)            // add the column/row's dimensions
+        foreach (x; col.minWidth)		// add the column/row's dimensions
             mw += x;
-        foreach (x; rowHMin)
+        foreach (x; row.minWidth)
             mh += x;
         
         
         // Find which cols/rows are resizable:
-        sizableCols = sizableRows = null;   // reset; we're about to concatenate to them
+        // reset:
+        col.sizable = new bool[cols];
+        row.sizable = new bool[rows];
+        col.firstSizable = row.firstSizable = -1;
         
         forCols:
-        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 forCols;                   // continue the outer for loop
+        for (myIt i = 0; i < cols; ++i) {				// for each column
+            for (myIt j = 0; j < subWidgets.length; j += cols)	// for each row
+                if (!subWidgets[i+j].isWSizable)	// column not resizable
+                    continue forCols;			// continue the outer for loop
                 
             // column is resizable if we get to here
-            sizableCols ~= i;
+            col.sizable[i] = true;
+            if (col.firstSizable < 0)
+                col.firstSizable = i;
+            col.lastSizable = i;
         }
         
         forRows:
-        for (uint i = 0; i < subWidgets.length; i += cols) {    // for each row
-            for (uint j = 0; j < cols; ++j)                     // for each column
+        for (myIt i = 0; i < subWidgets.length; i += cols) {	// for each row
+            for (myIt j = 0; j < cols; ++j)			// for each column
                 if (!subWidgets[i+j].isHSizable)
                     continue forRows;
             
-            sizableRows ~= i / cols;
+            row.lastSizable = i / cols;
+            row.sizable[row.lastSizable] = true;
+            if (row.firstSizable < 0)
+                row.firstSizable = row.lastSizable;
         }
     }
     
-    /* Calculations which need to be run whenever resizing occurs (or deeper alterations)
-     * (i.e. to produce cached data calculated from construction and mutable data). */
-    void genCachedMutableData () {
-        // Calculate column and row locations:
-        colX.length = cols;
-        rowY.length = rows;
-        int spacing = window.renderer.layoutSpacing;
-        
-        int cum = 0;
-        foreach (i, x; rowH) {
-            rowY[i] = cum;
-            cum += x + spacing;
+    // set sub-widgets size & position (done after resizing widget or rows/columns)
+    void setSubWidgetSP (bool wH, bool hH) {
+        for (myIt i = 0; i < cols; ++i)
+            for (myIt j = 0; j < rows; ++j)
+        {
+            IWidget widget = subWidgets[i + cols*j];
+            widget.setSize (col.width[i], row.width[j], wH, hH);
+            widget.setPosition (x + col.pos[i], y + row.pos[j]);
         }
-        
-        cum = 0;
-        foreach (i, x; colW) {
-            colX[i] = cum;
-            cum += x + spacing;
-        }
-        
-        // Tell subwidgets their new sizes:
-        foreach (i,widget; subWidgets)
-            widget.setSize (colW[i % cols], rowH[i / cols]);
     }
     //END Cache calculation functions
     
-    /* Adjust the total size of rows/columns (including spacing) by diff.
-     *
-     * Params:
-     *  cellD       = current sizes; is adjusted by the function to new sizes
-     *  cellDMin    = minimal sizes (see colWMin/rowHMin)
-     *  sizableCells= List of indexes in cellD for cells which are resizable
-     *  diff        = amount to increase/decrease the total size
-     *  startHigh   = if true, start resizing from the tail end of cellD, instead of the beginning
-     *
-     * Returns:
-     *  The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin.
-     */
-    int adjustCellSizes (ref int[] cellD, ref int[] cellDMin, ref size_t[] sizableCells, int diff, bool startHigh)
-    in {// Could occur if adjust isn't called first, but this would be a code error:
-        assert (cellD !is null, "adjustCellSizes: cellD is null");
-    } body {
-        // Cannot resize if no cells are sizable:
-        if (sizableCells.length == 0) return 0;
-        
-        size_t si = (startHigh ? sizableCells.length-1 : 0);    // starting index of sizableCells
-        
-        // FIXME: could do with an overhaul
-        if (diff > 0) {         // increase size of first resizable cell
-            cellD[sizableCells[si]] += diff;
-            return diff;
-        }
-        else if (diff < 0) {    // decrease
-            size_t sc = (startHigh ? -1 : 1);   // amount to iterate
-            size_t ci;          // index in cellD
-            int rd = diff;      // running diff
-            while (true) {
-                ci = sizableCells[si];
-                
-                cellD[ci] += rd; // decrease this cell's size (but may be too much)
-                rd = cellD[ci] - cellDMin[ci];
-                if (rd >= 0)    // OK; we're done
-                    return diff;// we hit the mark exactly
-                
-                // else we decreased it too much!
-                cellD[ci] = cellDMin[ci];
-                // rd is remainder to decrease by
-                
-                si += sc;       // iterate
-                if (si < 0 || si >= sizableCells.length)        // run out of next cells
-                    return diff - rd;   // still had rd left to decrease
-            }
-        }
-        else                   // no adjustment needed
-            return 0;
-    }
     
     //BEGIN Col/row resizing
     void resizeCallback (ushort cx, ushort cy) {
-        int move;           // NOTE: all resizing is relative, unlike in Window
-        
-        if (resizeColsL.length != 0) {
-            move = cx - dragX;
-            
-            // do shrinking first (in case we hit the minimum)
-            // Note: we rely on x[a..b] pointing to the same memory as x
-            if (move < 0) {
-                move = -adjustCellSizes (colW, colWMin, resizeColsL, move, false);
-                adjustCellSizes (colW, colWMin, resizeColsH, move, true);
-            } else {
-                move = -adjustCellSizes (colW, colWMin, resizeColsH, -move, true);
-                adjustCellSizes (colW, colWMin, resizeColsL, move, false);
-            }
-            
-            dragX = cx;
-        }
+        col.resize (cx - dragX);
+        row.resize (cy - dragY);
         
-        if (resizeRowsL.length != 0) {
-            move = cy - dragY;
-            
-            // do shrinking first (in case we hit the minimum)
-            // Note: we rely on x[a..b] pointing to the same memory as x
-            if (move < 0) {
-                move = -adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false);
-                adjustCellSizes (rowH, rowHMin, resizeRowsH, move, true);
-            } else {
-                move = -adjustCellSizes (rowH, rowHMin, resizeRowsH, -move, true);
-                adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false);
-            }
-            
-            dragY = cy;
-        }
+        // NOTE: all adjustments are relative; might be better if they were absolute?
+        dragX = cx;
+        dragY = cy;
         
-        // NOTE: this might be able to be made more efficient, but basically this all needs to happen:
-        genCachedMutableData;
-        setPosition (x,y);
+        // NOTE: Resizing direction is set to "high direction" which isn't always going to be
+        // correct. A more accurate but more complex approach might be to get
+        // adjustCellSizes to do the work.
+        setSubWidgetSP (true, true);
         window.requestRedraw;
     }
     bool endCallback (ushort cx, ushort cy, ubyte b, bool state) {
         if (b == 1 && state == false) {
             window.gui.removeCallbacks (cast(void*) this);
-            // Remove unwanted data (although it shouldn't free any memory):
-            resizeColsL = resizeColsH = resizeRowsL = resizeRowsH = null;
-            return true;    // we've handled the up-click
+            return true;	// we've handled the up-click
         }
-        return false;       // we haven't handled it
+        return false;		// we haven't handled it
     }
     
 protected:
     // Data for resizing cols/rows:
-    int dragX, dragY;   // coords where drag starts
-    // Lists of columns/rows with lower/higher index than the resize position
-    size_t[] resizeColsL, resizeColsH, resizeRowsL, resizeRowsH;
+    int dragX, dragY;	// coords where drag starts
     //END Col/row resizing
     
     
-    //BEGIN Construction (i.e. non-mutable) data
-    int cols, rows;     // number of cells in grid
+    myIt cols, rows;	// number of cells in grid
     
     /* All widgets in the grid, by row. Order:  [ 0 1 ]
      *                                          [ 2 3 ] */
     IWidget[] subWidgets;
     
-    // Cached data calculated from construction data:
-    // Minimal column width / row height:
-    int[] colWMin, rowHMin;
-    int mw, mh;         // minimal dimensions
-    
-    // All resizable cols/rows, in order:
-    size_t[] sizableCols, sizableRows;
-    //END Construction data
+    /* Widths, positions, etc., either of columns or of rows
+     *
+     * The purpose of this struct is mostly to unify functionality which must work the same on both
+     * horizontal and vertical cell placement.
+     *
+     * Most notation corresponds to horizontal layout (columns), simply for easy of naming. */
+    struct CellDimensions {
+        int[] pos,	// relative position (cumulative width[i-1] plus spacing)
+              width,	// current widths
+              minWidth;	// minimal widths (set by genCachedConstructionData)
+        bool[] sizable;	// true if col is resizable (set by genCachedConstructionData)
+        myDiff firstSizable,	// first col which is resizable, negative if none
+               lastSizable;	// as above, but last (set by genCachedConstructionData)
+        myDiff resizeD,	// resize down from this index (<0 if not resizing)
+               resizeU;	// and up from this index
+        int spacing;	// used by genPositions (which cannot access the layout class's data)
+        
+        void dupMin () {
+            width = minWidth.dup;
+        }
+        void setCheck (int[] data) {
+            // Set to provided data:
+            width = data;
+            // And check sizes are valid:
+            foreach (i, m; minWidth)
+                // if fixed width or width is less than minimum:
+                if (!sizable[i] || width[i] < m)
+                    width[i] = m;
+        }
+        
+        // Generate position infomation and return total width (i.e. widget width/height)
+        int genPositions () {
+            pos.length = minWidth.length;
+            
+            int x = 0;
+            foreach (i, w; width) {
+                pos[i] = x;
+                x += w + spacing;
+            }
+            return x - spacing;
+        }
+        
+        // Get the row/column a click occured in
+        // Returns -i if in space to left of col i, or i if on col i
+        myDiff getCell (int l) {
+            myDiff i = minWidth.length - 1;	// starting from right...
+            while (l < pos[i]) {		// decrement while left of this column
+                debug assert (i > 0, "getCell: l < pos[0] (code error)");
+                --i;
+            }					// now (l >= pos[i])
+            if (l >= pos[i] + width[i])		// between columns
+                return -i - 1;			// note: i might be 0 so cannot just return -i
+            return i;
+        }
+        
+        // Calculate resizeU/resizeD, and return true if unable to resize.
+        bool findResize (int l) {
+            resizeU = -getCell (l);		// potential start for upward-resizes
+            if (resizeU <= 0) return true;	// not on a space between cells
+            resizeD = resizeU - 1;		// potential start for downward-resizes
+            
+            while (!sizable[resizeU]) {		// find first actually resizable column (upwards)
+                ++resizeU;
+                if (resizeU >= minWidth.length) {	// cannot resize
+                    resizeU = -1;
+                    return true;
+                }
+            }
+            
+            while (!sizable[resizeD]) {		// find first actually resizable column (downwards)
+                --resizeD;
+                if (resizeD < 0) {		// cannot resize
+                    resizeU = -1;	// resizeU is tested to check whether resizes are possible
+                    return true;
+                }
+            }
+            
+            return false;			// can resize
+        }
+        
+        /* Adjust the total size of rows/columns (including spacing) by diff.
+        *
+        * Params:
+        *  diff	= amount to increase/decrease the total size
+        *  start= index for col/row to start resizing on
+        *  incr	= direction to resize in (added to index each step). Must be either -1 or +1.
+        *
+        * Returns:
+        *  The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin.
+        */
+        int adjustCellSizes (int diff, myDiff start, myDiff incr)
+        in {// Could occur if adjust isn't called first, but this would be a code error:
+            char[128] tmp;
+            logger.trace (logger.format (tmp, "start is {}", start));
+            assert (width !is null, "adjustCellSizes: width is null");
+            assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start");
+            assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr");
+        } body {
+            debug scope(failure)
+                    logger.trace ("adjustCellSizes: failure");
+            myDiff i = start;
+            if (diff > 0) {		// increase size of first resizable cell
+                width[i] += diff;
+            }
+            else if (diff < 0) {	// decrease
+                int rd = diff;		// running diff
+                aCSwhile:
+                while (true) {
+                    width[i] += rd;	// decrease this cell's size (but may be too much)
+                    rd = width[i] - minWidth[i];
+                    if (rd >= 0)	// OK; we're done
+                        break;		// we hit the mark exactly: diff is correct
+                    
+                    // else we decreased it too much!
+                    width[i] = minWidth[i];
+                    // rd is remainder to decrease by
+                    
+                    bool it = true;	// iterate (force first time)
+                    while (it) {
+                        i += incr;
+                        if (i < 0 || i >= minWidth.length) {	// run out of next cells
+                            diff -= rd;	// still had rd left to decrease
+                            break aCSwhile;	// exception: Array index out of bounds
+                        }
+                        it = !sizable[i];	// iterate again if row/col isn't resizable
+                    }
+                }
+            }
+            // else no adjustment needed (diff == 0)
+            
+            genPositions;
+            return diff;
+        }
+        
+        void resize (int diff) {
+            if (resizeU <= 0) return;
+            
+            // do shrinking first (in case we hit the minimum)
+            if (diff >= 0) {
+                diff = -adjustCellSizes (-diff, resizeU, 1);
+                adjustCellSizes (diff, resizeD, -1);
+            } else {
+                diff = -adjustCellSizes (diff, resizeD, -1);
+                adjustCellSizes (diff, resizeU, 1);
+            }
+        }
+    }
+    CellDimensions col, row;
     
-    
-    //BEGIN Mutable data
-    int[] colW, rowH;   // column width / row height (largest size in col/row)
-    
-    // Cached data calculated from construction and mutable data:
-    int[] colX, rowY;   // cumulative colW[i-1] + padding (add x to get column's left x-coord)
-    //END Mutable data
+    // Index types. Note that in some cases they need to hold negative values.
+    alias size_t myIt;
+    alias ptrdiff_t myDiff;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/miscWidgets.d	Thu May 22 11:34:09 2008 +0100
@@ -0,0 +1,123 @@
+/* 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/>. */
+
+/** Some GUI Miscelaneas widgets. */
+module mde.gui.widget.miscWidgets;
+
+import mde.gui.widget.Widget;
+import mde.gui.exception;
+import mde.gui.renderer.IRenderer;
+
+import mde.resource.font;
+
+import tango.io.Stdout;
+
+
+/// A fixed-size blank widget.
+class FixedBlankWidget : FixedWidget
+{
+    this (IWindow wind, int[] data) {
+        if (data.length != 3) throw new WidgetDataException;
+        super (wind, data);
+    }
+    
+    void draw () {
+        super.draw;
+        
+        window.renderer.drawBlank (x,y, w,h);
+    }
+}
+
+/// A completely resizable blank widget (initial size zero).
+class SizableBlankWidget : SizableWidget
+{
+    this (IWindow wind, int[] data) {
+        if (data.length != 1) throw new WidgetDataException;
+        super (wind, data);
+    }
+    
+    void draw () {
+        super.draw;
+        
+        window.renderer.drawBlank (x,y, w,h);
+    }
+}
+
+/// First interactible 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.
+    // it is whether the mouse is over the button after being clicked.
+    
+    this (IWindow wind, int[] data) {
+        if (data.length != 3) throw new WidgetDataException;
+        super (wind, data);
+    }
+    
+    void draw () {
+        window.renderer.drawButton (x,y, w,h, pushed);
+    }
+    
+    void clickEvent (ushort, ushort, ubyte b, bool state) {
+        if (b == 1 && state == true) {
+            pushed = true;
+            window.requestRedraw;
+            window.gui.addClickCallback (&clickWhileHeld);
+            window.gui.addMotionCallback (&motionWhileHeld);
+        }
+    }
+    // Called when a mouse motion/click event occurs while (held == true)
+    bool clickWhileHeld (ushort cx, ushort cy, ubyte b, bool state) {
+        if (b == 1 && state == false) {
+            if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
+                Stdout ("Button clicked!").newline;
+            
+            pushed = false;
+            window.requestRedraw;
+            window.gui.removeCallbacks (cast(void*) this);
+            
+            return true;
+        }
+        return false;
+    }
+    void motionWhileHeld (ushort cx, ushort cy) {
+        bool oldPushed = pushed;
+        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
+        else pushed = false;
+        if (oldPushed != pushed)
+            window.requestRedraw;
+    }
+}
+
+/// Basic text widget
+class TextWidget : Widget
+{
+    this (IWindow wind, int[] data) {
+        if (data.length != 1) throw new WidgetDataException;
+        mw = 100;	//FIXME: set properly
+        mh = 50;
+        super (wind,data);
+    }
+    
+    void draw () {
+        super.draw();
+        if (font is null) font = Font.get("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf");
+        font.drawStr (x,y, "Text Widget");
+    }
+    
+protected:
+    static Font font;
+}
--- a/mde/mde.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/mde.d	Thu May 22 11:34:09 2008 +0100
@@ -59,8 +59,13 @@
     //END Initialisation
     
     //BEGIN Main loop setup
+    /* Note: the main loop is currently controlled by the scheduler. This is not really ideal,
+     * since it provides no direct control of the order in which components are executed and does
+     * not allow running components simultaeneously with threads.
+     * Note: probably drawing should start at the beginning of the loop and glFlush()/swapBuffers
+     * be called at the end to optimise. */
+    mainSchedule.add (SCHEDULE.DRAW, &gl.draw); // Draw, per event only.
     mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true;
-    mainSchedule.add (SCHEDULE.DRAW, &gl.draw); // Draw, per event only.
     //END Main loop setup
     
     while (run) {
--- a/mde/resource/font.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/resource/font.d	Thu May 22 11:34:09 2008 +0100
@@ -31,7 +31,9 @@
 
 /** Font class.
  *
- * Particular to a font and size. (Maybe not size?) */
+ * Particular to a font and size. (Maybe not size?)
+ * 
+ * Note: it is not currently intended to be thread-safe. */
 class Font
 {
     //BEGIN Static: manager
@@ -77,7 +79,7 @@
         if (FT_New_Face (library, toStringz(path), 0, &face))
             throw new fontLoadException ("Unable to read font: "~path);
         
-        if (FT_Set_Pixel_Sizes (face, 12,12))
+        if (FT_Set_Pixel_Sizes (face, 0,16))
             throw new fontLoadException ("Unable to set pixel size");
     }
     
@@ -94,9 +96,24 @@
         
         FT_Pos y_adj = 0;	// y adjustment (for height)
         
+        FT_Bool useKerning = FT_HAS_KERNING (face);
+        FT_UInt previous = 0;
+        
         foreach (chr; str) {
+            auto gi = FT_Get_Char_Index (face, chr);
+            
+            if (useKerning && previous && gi)
+            {
+                FT_Vector  delta;
+
+
+                FT_Get_Kerning (face, previous, gi, FT_Kerning_Mode.FT_KERNING_DEFAULT, &delta);
+
+                pen.x += delta.x;
+            }
+            
             FT_Set_Transform(face, &m, &pen);
-            if (FT_Load_Char(face, chr, FT_LOAD_RENDER))
+            if (FT_Load_Glyph(face, gi, FT_LOAD_RENDER))
                 return;	// give up
             
             if (y_adj < g.metrics.height) y_adj = g.metrics.height;
@@ -111,11 +128,12 @@
                 logger.info ("b.pitch != b.width");
             
             //NOTE: y direction!
-            glRasterPos2i (g.bitmap_left,g.bitmap_top + y_adj/64);
+            glRasterPos2i (g.bitmap_left,g.bitmap_top /+ (y_adj >> 6)+/);
             glDrawPixels (b.width, b.rows, GL_LUMINANCE, GL_UNSIGNED_BYTE, cast(void*) b.buffer);
             
             pen.x += g.advance.x;
             pen.y += g.advance.y;
+            previous = gi;
         }
     }
     
--- a/mde/scheduler/Init.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/scheduler/Init.d	Thu May 22 11:34:09 2008 +0100
@@ -212,6 +212,7 @@
         const LOG_CF_MSG = "Cleanup function ";
         const LOG_F_START = " - running";
         const LOG_F_END = " - completed";
+        const LOG_F_BAD = " - failed";
         const LOG_F_FAIL = " - failed: ";
         /* Runs all functions consecutively, first-to-last.
         * If any function fails, halts immediately. */
@@ -221,7 +222,7 @@
                 try {
                     debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
                     func.func();
-                    debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_END);
+                    debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                 } catch (Exception e) {
                     logger.fatal (LOG_IF_MSG ~ func.name ~ LOG_F_FAIL ~
                             ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
@@ -239,7 +240,7 @@
                 try {
                     debug logger.trace (LOG_CF_MSG ~ func.name ~ LOG_F_START);
                     func.func();
-                    debug logger.trace (LOG_CF_MSG ~ func.name ~ LOG_F_END);
+                    debug logger.trace (LOG_CF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                 } catch (Exception e) {
                     logger.fatal (LOG_CF_MSG ~ func.name ~ LOG_F_FAIL ~
                             ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
@@ -260,7 +261,7 @@
                 foreach (func; s.funcs) {   // Start all threads
                     debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
                     tg.create(func.func);
-                    debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_END);
+                    debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                 }
             } catch (ThreadException e) {   // Problem with threading; try without threads
                 logger.error ("Caught ThreadException while trying to create threads:");
--- a/mde/sdl.d	Fri May 16 12:22:10 2008 +0100
+++ b/mde/sdl.d	Thu May 22 11:34:09 2008 +0100
@@ -93,7 +93,8 @@
         return;
     }
     
-    // Now (must be done after GL context is created) try to load later version:
+    // Now (must be done after GL context is created) we can try to load later version.
+    // The initial loading provides opengl 1.1 features.
     /+ No later GL features are currently used.
     try {
         DerelictGL.loadVersions(GLVersion.Version21);