changeset 97:30470bc19ca4

Floating widgets now work nicely: customizable borders added, resizing, moving. gl.basic abstraction module removed (seemed pointless). Some changes to SimpleRenderer (largely to accomodate floating widgets).
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 10 Nov 2008 16:44:44 +0000
parents dbf332403c6e
children 49e7cfed4b34
files codeDoc/jobs.txt data/conf/gui.mtt mde/font/FontTexture.d mde/font/font.d mde/gl/basic.d mde/gui/WidgetManager.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Floating.d mde/setup/Screen.d
diffstat 10 files changed, 304 insertions(+), 518 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Thu Nov 06 13:16:39 2008 +0000
+++ b/codeDoc/jobs.txt	Mon Nov 10 16:44:44 2008 +0000
@@ -8,6 +8,7 @@
 
 Bugs:
 Sometimes nothing is drawn until a resize, and fonts are blocks. Cause: init-stages always appear to get divided between two threads. If Inpt, Font and SWnd are called by the main thread and not a sub-thread, the bug doesn't occur. A temporary fix is just to set maxThreads=1. A redesign of threading init stages could solve this, but doesn't seem worth the effort right now.
+Drawing of floating widgets is not restricted to the floating area; see SimpleRenderer.restrict().
 
 
 
--- a/data/conf/gui.mtt	Thu Nov 06 13:16:39 2008 +0000
+++ b/data/conf/gui.mtt	Mon Nov 10 16:44:44 2008 +0000
@@ -14,7 +14,10 @@
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
 <WidgetData|optVal={0:[0x6030]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
-<WidgetData|floating={0:[0x8200],1:["text","button"]}>
+<WidgetData|floating={0:[0x8200,1,6,14],1:["text","button","blank"]}>
 <WidgetData|text={0:[0x21,0xE0E000],1:["Floating text"]}>
+<WDims|root=[6,590,196,6,580,6]>
+<WDims|content=[590,190,364,18]>
+<WDims|floating=[25,21,103,27,437,23,74,74,217,90,123,100]>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/mde/font/FontTexture.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/font/FontTexture.d	Mon Nov 10 16:44:44 2008 +0000
@@ -217,6 +217,7 @@
             glEnd ();
         }
         
+        glDisable(GL_TEXTURE_2D);
         glDisable(GL_BLEND);
     }
     
@@ -321,6 +322,7 @@
         glTexCoord2f (0f, 1f);	glVertex2i (0, dimH);
         glEnd ();
         
+        glDisable(GL_TEXTURE_2D);
         glDisable(GL_BLEND);
     }
     
--- a/mde/font/font.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/font/font.d	Mon Nov 10 16:44:44 2008 +0000
@@ -27,7 +27,6 @@
 import mde.setup.exception;     // InitStage stuff
 
 import derelict.freetype.ft;
-import derelict.opengl.gl;
 
 import mde.file.deserialize;
 import tango.stdc.stringz;
--- a/mde/gl/basic.d	Thu Nov 06 13:16:39 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/* 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 basic OpenGL code for drawing.
-*
-* Everything here is really intended as makeshift code to enable GUI development. */
-module mde.gl.basic;
-
-import derelict.opengl.gl;
-
-import tango.time.Time;     // TimeSpan (type only; unused)
-
-//BEGIN Drawing utils
-// Simple drawing commands for use by GUI
-// (temporary)
-void setColor (float r, float g, float b) {
-    glColor3f (r, g, b);
-}
-void drawBox (int x, int y, int w, int h) {
-    glDisable(GL_TEXTURE_2D);
-    glRecti(x, y+h, x+w, y);
-}
-//END Drawing utils
--- a/mde/gui/WidgetManager.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/gui/WidgetManager.d	Mon Nov 10 16:44:44 2008 +0000
@@ -127,7 +127,6 @@
         w = cast(wdim) nw;
         h = cast(wdim) nh;
         
-        debug logger.trace ("Resize to: {},{}", nw, nh);
         if (w < mw || h < mh)
             logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h);
         
--- a/mde/gui/renderer/IRenderer.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/gui/renderer/IRenderer.d	Mon Nov 10 16:44:44 2008 +0000
@@ -28,79 +28,60 @@
 * The renderer is intended to be per-GUI. */
 interface IRenderer
 {
-    //BEGIN Get dimensions
-    /// A container for the dimensions
-    struct BorderDimensions {
-        /// The dimensions: left, top, right, bottom
-        wdim l, t, r, b;
+    //BEGIN Types
+    /** For floating widget borders. Like TextAdapter above, could be more flexible. */
+    struct Border {
+        /** Border type.
+         * 
+         * No border, small non-functional border, movement only border, resize only border, or
+         * full border. */
+        enum BTYPE : int {
+            NONE = 0, SMALL = 1, LARGE = 2, MOVE = 4, RESIZE = 8, BOTH = MOVE | RESIZE
+        }
+        /** Which edges of a window are being resized.
+         *
+         * E.g. X1 == left, X2 | Y1 == right and top. */
+        enum RESIZE : int {
+            NONE = 0x0, X1 = 0x1, X2 = 0x2, Y1 = 0x4, Y2 = 0x8
+        }
+        
+        wdim x1,x2;     /// First and second (lef & right) horizontal borders
+        wdim y1,y2;     /// First and second vertical borders
+        RESIZE capability;      /// Which resizes are possible.
         
-        void opAddAssign (BorderDimensions d) {
-            l += d.l;
-            t += d.t;
-            r += d.r;
-            b += d.b;
+        void opAddAssign (Border d) {
+            x1 += d.x1;
+            x2 += d.x2;
+            y1 += d.y1;
+            y2 += d.y2;
+        }
+        
+        /** Used to tell if a click on a window's border is for resizing or moving.
+         *
+         * Depends on setSizable's parameters.
+         *
+         * Params:
+         *   cx =
+         *   cy = click coordinates relative to window border
+         *   w  =
+         *   h  = window size
+         *
+         * Returns:
+         *   RESIZE = Describes whether the click resizes or moves the widget. */
+        RESIZE getResize (wdim cx, wdim cy, wdim w, wdim h) {
+            RESIZE r = RESIZE.NONE;
+            if (cx + cy < x1 + y1)
+                r = RESIZE.X1 | RESIZE.Y1;
+            else if (cx + h - cy < x1 + y2)
+                r = RESIZE.X1 | RESIZE.Y2;
+            else if (w - cx + cy < x2 + y1)
+                r = RESIZE.X2 | RESIZE.Y1;
+            else if (w - cx + h - cy < x2 + y2)
+                r = RESIZE.X2 | RESIZE.Y2;
+            return r & capability;
         }
     }
     
-    /** Use to set and reset these parameters, and to get the border size (which may depend on
-     * them). */
-    BorderDimensions setSizable (bool wSizable, bool hSizable);
-    
-    /// Which edges of a window are being resized
-    enum RESIZE_TYPE : ubyte {
-        NONE = 0x0, L = 0x1, R = 0x2, T = 0x4, B = 0x8
-    }
-    
-    /** Used to tell if a click on a window's border is for resizing or moving.
-    *
-    * Depends on setSizable's parameters.
-    *
-    * Params:
-    *   cx =
-    *   cy = click coordinates relative to window border
-    *   w  =
-    *   h  = window size
-    *
-    * Returns:
-    *   RESIZE_TYPE = NONE for a move, an or'd combination of L/R/T/B for resizing.
-    */
-    RESIZE_TYPE getResizeType (wdim cx, wdim cy, wdim w, wdim h);
-    
-    /** Return the renderer's between-widget spacing (for layout widgets). */
-    wdim layoutSpacing ();
-    //END Get dimensions
-    
-    //BEGIN draw routines
-    /** Restrict following draw operations to given box.
-     *
-     * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the
-     * restriction. */
-    void restrict (wdim x, wdim y, wdim w, wdim h);
-    void relax ();      /// ditto
-    
-    /** Draw a window border plus background. */
-    void drawWindow (wdim x, wdim y, wdim w, wdim h);
-    
-    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */
-    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h);
-    
-    /** Draws a blank widget (temporary) */
-    void drawBlank (wdim x, wdim y, wdim w, wdim h);
-    
-    /** Draws a button frame, in if pushed == true. */
-    void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed);
-    
-    /** Toggle buttons.
-     *
-     * These have a fixed size which getToggleSize returns. */
-    wdimPair getToggleSize ();
-    void drawToggle (wdim x, wdim y, bool state, bool pushed);  /// ditto
-    
-    /** Get a TextAdapter to draw some text.
-     *
-     * If no colour is passes, a default is used (white). */
-    TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF);
-    
     /** For drawing text - one instance per string.
      *
      * NOTE: currently inflexible. Could use (function) pointers, class interfaces or struct
@@ -126,5 +107,46 @@
         Colour colour;
         FontStyle font;
     }
-    //END draw routines
+    //END Types
+    
+    //BEGIN Methods
+    /** Returns a Border containing dimensions.
+     *
+     * The dimensions may depend on whether the widget is resizable (horizontally and vertically).
+     */
+    Border getBorder (Border.BTYPE type, bool wSizable, bool hSizable);
+    
+    /** Return the renderer's between-widget spacing (for layout widgets). */
+    wdim layoutSpacing ();
+    
+    /** Restrict following draw operations to given box.
+     *
+     * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the
+     * restriction. */
+    void restrict (wdim x, wdim y, wdim w, wdim h);
+    void relax ();      /// ditto
+    
+    /** Draw a window border plus background. */
+    void drawWindow (Border* border, wdim x, wdim y, wdim w, wdim h);
+    
+    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */
+    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h);
+    
+    /** Draws a blank widget (temporary) */
+    void drawBlank (wdim x, wdim y, wdim w, wdim h);
+    
+    /** Draws a button frame, in if pushed == true. */
+    void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed);
+    
+    /** Toggle buttons.
+     *
+     * These have a fixed size which getToggleSize returns. */
+    wdimPair getToggleSize ();
+    void drawToggle (wdim x, wdim y, bool state, bool pushed);  /// ditto
+    
+    /** Get a TextAdapter to draw some text.
+     *
+     * If no colour is passes, a default is used (white). */
+    TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF);
+    //END Methods
 }
--- a/mde/gui/renderer/SimpleRenderer.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Mon Nov 10 16:44:44 2008 +0000
@@ -18,7 +18,7 @@
 
 import mde.gui.renderer.IRenderer;
 
-import gl = mde.gl.basic;
+import derelict.opengl.gl;
 import mde.font.font;
 
 /** Interface for renderers.
@@ -34,51 +34,25 @@
         defaultFont = FontStyle.get("default");
     }
     
-    BorderDimensions setSizable (bool wS, bool hS) {
-        wSizable = wS;
-        hSizable = hS;
-        
-        // Set the border size based on the above
+    alias Border.BTYPE BTYPE;
+    Border getBorder (BTYPE type, bool wS, bool hS) {
+        Border border;
         with (border) {
-            l = r = t = b = 14;
+            if (type & BTYPE.RESIZE) {
+                if (wS) capability  = RESIZE.X1 | RESIZE.X2;
+                if (hS) capability |= RESIZE.Y1 | RESIZE.Y2;
+            }
+            if (type & BTYPE.LARGE) {
+                y1 = 12;
+                y2 = 6;
+            }
+            else if (type & BTYPE.SMALL)
+                y1 = y2 = 4;
+            x1 = x2 = y2;
         }
-        with (resize) {
-            if (wSizable)
-                l = r = 6;
-            else
-                l = r = 0;
-            if (hSizable) {
-                t = b = 6;
-            } else
-                t = b = 0;
-        }
-        border += resize;
         return border;
     }
     
-    RESIZE_TYPE getResizeType (wdim cx, wdim cy, wdim w, wdim h) {
-        RESIZE_TYPE resizeType = RESIZE_TYPE.NONE;
-        if (cx < resize.l || cx >= w - resize.r ||
-            cy < resize.t || cy >= h - resize.b) { // window is being resized
-                /* check for resizes (different to above; use whole border giving larger area for
-                * diagonal resizes). */
-            
-            if (wSizable) {
-                if (cx < border.l)
-                    resizeType = RESIZE_TYPE.L;
-                else if (cx >= w - border.r)
-                    resizeType = RESIZE_TYPE.R;
-            }
-            if (hSizable) {
-                if (cy < border.t)
-                    resizeType |= RESIZE_TYPE.T;
-                else if (cy >= h - border.b)
-                    resizeType |= RESIZE_TYPE.B;
-            }
-        }
-        return resizeType;
-    }
-    
     wdim layoutSpacing () {
         return 4;
     }
@@ -88,35 +62,57 @@
     void restrict (wdim x, wdim y, wdim w, wdim h) {}
     void relax () {}
     
-    void drawWindow (wdim x, wdim y, wdim w, wdim h) {
-        gl.setColor (0f, 0f, .7f);
-        gl.drawBox (x,y, w,h);
+    void drawWindow (Border* border, wdim x, wdim y, wdim w, wdim h) {
+        glColor3f (0f, 0f, .8f);
+        glRecti(x, y+h, x+w, y);
         
-        gl.setColor (0f, 0f, 1f);
-        gl.drawBox (x+resize.l, y+resize.t, w-resize.l-resize.r, h-resize.t-resize.b);
+        if (border.capability != 0) {
+            glColor3f (0f, 0f, .7f);
+            glBegin (GL_TRIANGLES);
+            wdim t = border.x1 + border.y1;
+            glVertex2i (x, y);
+            glVertex2i (x+t, y);
+            glVertex2i (x, y+t);
+            
+            t = border.x2 + border.y1;
+            glVertex2i (x+w, y);
+            glVertex2i (x+w, y+t);
+            glVertex2i (x+w-t, y);
+            
+            t = border.x2 + border.y2;
+            glVertex2i (x+w, y+h);
+            glVertex2i (x+w-t, y+h);
+            glVertex2i (x+w, y+h-t);
+            
+            t = border.x1 + border.y2;
+            glVertex2i (x, y+h);
+            glVertex2i (x, y+h-t);
+            glVertex2i (x+t, y+h);
+            glEnd ();
+        }
         
-        gl.setColor (.3f, .3f, .3f);
-        gl.drawBox (x+border.l, y+border.t, w-border.l-border.r, h-border.t-border.b);
+        glColor3f (0f, 0f, 0f);
+        glRecti(x+border.x1, y+h-border.y2, x+w-border.x2, y+border.y1);
     }
 
     void drawWidgetBack (wdim x, wdim y, wdim w, wdim h) {
         debug {
-            gl.setColor (0f, .2f, .2f);
-            gl.drawBox (x,y, w,h);
+            glColor3f (0f, .2f, .2f);
+            glRecti (x,y+h, x+w,y);
         }
     }
     
     void drawBlank (wdim x, wdim y, wdim w, wdim h) {
-        gl.setColor (.4f, .4f, .4f);
-        gl.drawBox (x,y, w,h);
+        glColor3f (.4f, .4f, .4f);
+        glRecti(x, y+h, x+w, y);
     }
     
     void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed) {
         if (pushed)
-            gl.setColor (1f, 0f, 1f);
+            glColor3f (1f, 0f, 1f);
         else
-            gl.setColor (.6f, 0f, .6f);
-        gl.drawBox (x,y, w,h);
+            glColor3f (.6f, 0f, .6f);
+        glRecti(x, y+h, x+w, y);
     }
     
     wdimPair getToggleSize () {
@@ -128,10 +124,10 @@
     void drawToggle (wdim x, wdim y, bool state, bool pushed) {
         float c = pushed ? .7f : .5f;
         if (state)
-            gl.setColor (0f, c, 0f);
+            glColor3f (0f, c, 0f);
         else
-            gl.setColor (c, 0f, 0f);
-        gl.drawBox (x+2,y+2, 12,12);
+            glColor3f (c, 0f, 0f);
+        glRecti (x+2,y+14, x+14,y+2);
     }
     
     TextAdapter getAdapter (char[] text, int col) {
@@ -142,8 +138,5 @@
     }
     
 protected:
-    bool wSizable, hSizable;
-    BorderDimensions border;
-    BorderDimensions resize;
     FontStyle defaultFont;
 }
--- a/mde/gui/widget/Floating.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/gui/widget/Floating.d	Mon Nov 10 16:44:44 2008 +0000
@@ -18,15 +18,7 @@
 
 import mde.gui.widget.Widget;
 import mde.gui.exception;
-/+import mde.gui.widget.createWidget;
 
-import mde.gui.IGui;
-import mde.gui.renderer.createRenderer;
-
-import mt = mde.mergetag.DataSet;
-import mde.mergetag.parse.parseTo : parseTo;
-import mde.mergetag.parse.parseFrom : parseFrom;
-+/
 import tango.util.log.Log : Log, Logger;
 
 private Logger logger;
@@ -47,410 +39,223 @@
 class FloatingAreaWidget : AParentWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        if (data.ints.length != 1)
+        if (data.ints.length != 1 + data.strings.length)
             throw new WidgetDataException (this);
         
         subWidgets.length = data.strings.length;        // widgets created from string data
         sWOrder.length = subWidgets.length;
+        sWData.length = subWidgets.length;
         foreach (i,s; data.strings) {
             subWidgets[i] = mgr.makeWidget (s);
             sWOrder[i] = i;
+            sWData[i].borderType = cast(BTYPE) data.ints[i+1];
         }
         
-        sWCoords = mgr.dimData (id);
-        if (sWCoords.length != subWidgets.length * 2) {
-            // don't bother logging a warning; correct data will be saved anyway
-            sWCoords.length = subWidgets.length * 2;    // maybe some data kept
+        wdim[] dd = mgr.dimData (id);
+        if (dd.length == subWidgets.length * 4) {
+            foreach (i, ref d; sWData)
+                (&d.x)[0..4] = dd[i*4..i*4+4];
         }
         
         super (mgr, id, data);
     }
     
+    void finalize () {
+        foreach (i, ref d; sWData) with (d) {
+            auto widg = subWidgets[i];
+            d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable);
+            mw = widg.minWidth  + border.x1 + border.x2;
+            mh = widg.minHeight + border.y1 + border.y2;
+            if (w < mw) w = mw;
+            if (h < mh) h = mh;
+            widg.setWidth  (w - border.x1 - border.x2, -1);
+            widg.setHeight (h - border.y1 - border.y2, -1);
+        }
+    }
+    
     bool saveChanges () {
-        foreach (widget; subWidgets)
-            widget.saveChanges ();
+        wdim[] dd = new wdim[sWData.length*4];
+        foreach (i, ref d; sWData) {
+            subWidgets[i].saveChanges ();
+            dd[4*i..4*i+4] = (&d.x)[0..4];
+        }
         
-        mgr.setDimData (id, sWCoords);  // save positions
+        mgr.setDimData (id, dd);  // save positions
         return true;
     }
     
     void setWidth (wdim nw, int) {
         w = nw;
         // check all floating widgets are visible
-        foreach (i, widg; subWidgets) {
-            wdim d;
-            if (sWCoords[i] + (d = widg.width) > w) {
-                if (d > w)
-                    sWCoords[i] = 0;
-                else
-                    sWCoords[i] = w - d;
-            }
+        foreach (i, ref d; sWData) with (d) {
+            if (x + w > this.w)
+                x = (w > this.w) ? 0 : this.w - w;
         }
     }
     void setHeight (wdim nh, int) {
         h = nh;
-        foreach (i, widg; subWidgets) {
-            wdim d;
-            size_t n = i + subWidgets.length;
-            if (sWCoords[n] + (d = widg.height) > h) {
-                if (d > h)
-                    sWCoords[n] = 0;
-                else
-                    sWCoords[n] = h - d;
-            }
+        foreach (i, ref d; sWData) with (d) {
+            if (y + h > this.h)
+                y = (h > this.h) ? 0 : this.h - h;
         }
     }
     
     bool isWSizable () {    return true;    }
     bool isHSizable () {    return true;    }
     
-    void setPosition (wdim x, wdim y) {
-        super.setPosition (x,y);
+    void setPosition (wdim nx, wdim ny) {
+        x = nx;
+        y = ny;
         
         size_t n = subWidgets.length;
-        foreach (i,widg; subWidgets)
-            widg.setPosition (x+sWCoords[i], y+sWCoords[i+n]);
+        foreach (i, ref d; sWData)
+            subWidgets[i].setPosition (x + d.x + d.border.x1, y + d.y + d.border.y1);
     }
     
     void draw () {
         super.draw;
         
         mgr.renderer.restrict (x,y, w,h);
-        
         foreach (i; sWOrder)
-            subWidgets[i].draw;
+            with (sWData[i]) {
+                mgr.renderer.drawWindow (&border, this.x + x, this.y + y, w, h);
+                subWidgets[i].draw;
+            }
+        mgr.renderer.relax;
     }
     
     IChildWidget getWidget (wdim cx, wdim cy) {
+        debug scope (failure)
+            logger.warn ("getWidget: failure; values: click, pos, width - {}, {}, {} - {}, {}, {}", cx, x, w, cy, y, h);
         debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
         
-        size_t n = subWidgets.length;
-        foreach_reverse (j,i; sWOrder) {
-            wdim lx = cx - (x + sWCoords[i  ]);
-            wdim ly = cy - (y + sWCoords[i+n]);
-            if (lx >= 0 && lx < subWidgets[i].width &&
-                ly >= 0 && ly < subWidgets[i].height)
+        foreach_reverse (j,i; sWOrder) with (sWData[i]) {
+            wdim lx = cx - (this.x + x);
+            wdim ly = cy - (this.y + y);
+            if (lx >= 0 && lx < w &&
+                ly >= 0 && ly < h)
             {
                 sWOrder[j..$-1] = sWOrder[j+1..$].dup;
                 sWOrder[$-1] = i;
                 mgr.requestRedraw;
-                return subWidgets[i];
+                if (lx >= border.x1 && lx < w-border.x2 &&
+                    ly >= border.y1 && ly < h-border.y2)
+                    return subWidgets[i].getWidget (cx,cy);
+                event = i;
+                return this;
             }
         }
+        event = size_t.max;
         return this;    // no match
     }
     
-protected:
-    wdim[] sWCoords;    // coords for subwidgets, relative to this widget: [x1,x2,...,y1,y2,...]
-    size_t[] sWOrder;   // indexes for draw order (top widget at end of list)
-    
-    /+
-    /** Call after loading is finished to setup the window and confirm that it's valid.
-     *
-     * 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");
-    } body {
-        // Check data was loaded:
-        if (widgetData is null)
-            throw new WindowLoadException ("No widget creation data");
-        
-        // Save gui and create the renderer:
-        gui_ = gui;
-        rend = createRenderer (gui.rendererName);
-        
-        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
-        // Note: GridLayoutWidget's this relies on rend.layoutSpacing.
-        widget = makeWidget (0);        // primary widget always has ID 0.
-        // This data is no longer needed by Window, although its sub-arrays may still be used, so
-        // let the GC collect what it can:
-        widgetData = null;
-        widgetStrings = null;
-        
-        // get border sizes:
-        border = rend.setSizable (isWSizable, isHSizable);  // depends on widget
-        
-        // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty:
-        if ((widget.adjust (mutableData)).length != 0)	// adjust/set size, etc., depends on rend
-            logger.warn ("Local widget position data is invalid!");
-        mutableData = null;             // no longer needed
-        
-        widget.getCurrentSize (w,h);    // and get this size
-        w += border.l + border.r;       // Adjust for border
-        h += border.t + border.b;
-        
-        widgetX = x + border.l;         // widget position
-        widgetY = y + border.t;         // must be updated if the window is moved
-        widget.setPosition (widgetX, widgetY);
-        
-        // Calculate mw/mh and xw/yh (cached data):
-        mw = widget.minWidth  + border.l + border.r;
-        mh = widget.minHeight + border.t + border.b;
-        
-        xw = x+w;
-        yh = y+h;
-    }
-    //END Methods for GUI
-    
-    //BEGIN IWindow methods
-    /** Get/create a widget by ID.
-     *
-     * Should $(I only) be called internally and by sub-widgets! */
-    IWidget makeWidget (widgetID i)
-    in {
-        // widgetData is normally left to be garbage collected after widgets have been created:
-        assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null");
-    } body {
-        /* Each widget returned should be a unique object; if multiple widgets are requested with
-        * the same ID, a new widget is created each time. */
-        
-        int[]* d = i in widgetData;
-        if (d is null)
-            throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
-        
-        // Throws WidgetDataException (a WindowLoadException) if bad data:
-        return createWidget (this, *d);
-    }
-    
-    char[] getWidgetString (int i)
-    in {
-        // widgetStrings is freed at same time as widgetData
-        // but widgetData is guaranteed to be read
-        assert (widgetData !is null, "getWidgetString called after widget creation finished");
-    } body {
-        char[]* p;
-        if (widgetStrings is null ||
-             (p = i in widgetStrings) is null )
-            throw new WindowLoadException ("Needed widgetStrings not set for Window");
-        
-        return *p;
-    }
-    
-    /** Add this widget's data to that to be saved, returning it's widgetID. */
-    widgetID addCreationData (IWidget widget)
-    {
-        widgetID i;
-        if (widgetData is null)
-            i = 0;
-        else
-            i = widgetData.keys[$-1] + 1;
-        
-        /+ Doesn't this have no effect except when getCreationData throws, in which case the data
-         + isn't used anyway? I'm sure it was added for a reason... FIXME and below
-        widgetData[i] = null;   // Make sure the same ID doesn't get used by a recursive call
-        +/
-        widgetData[i] = widget.getCreationData;
-        
-        return i;
-    }
-    
-    int addWidgetString (char[] str)
-    {
-        int i;
-        if (widgetStrings is null)
-            i = 0;
-        else
-            i = widgetStrings.keys[$-1] + 1;
-        
-        /+ See above. FIXME
-        widgetStrings[i] = null;   // Make sure the same ID doesn't get used by a recursive call
-        +/
-        widgetStrings[i] = str;
-        
-        return i;
-    }
-    
-    IGui gui () {
-        return gui_;
-    }
-    
-    void requestRedraw () {
-        gui_.requestRedraw;
-    }
-    
-    IRenderer renderer ()
-    in {
-        assert (rend !is null, "Window.renderer: rend is null");
-    } body {
-        return rend;
-    }
-    //END IWindow methods
-    
-    //BEGIN IWidget methods
-    //FIXME: how many of these methods are actually needed/used?
-    int[] adjust (int[]) {          // simply not relevant (never used)
-        return [];
-    }
-    int[] getCreationData () {      // simply not relevant (never used)
-        return [];
-    }
-    int[] getMutableData () {       // simply not relevant (never used)
-        return [];
-    }
-    
-    bool isWSizable () {
-        return widget.isWSizable;
-    }
-    bool isHSizable () {
-        return widget.isHSizable;
-    }
-    
-    wdim minWidth () {
-        return mw;
-    }
-    wdim minHeight () {
-        return mh;
-    }
-    void getCurrentSize (out wdim cw, out wdim ch) {
-        cw = w;
-        ch = h;
-    }
-    
-    void setWidth (wdim nw, int dir) {
-        if (nw < mw) w = mw;	// clamp
-        else w = nw;
-        xw = x + w;
-        widget.setWidth (w - border.l - border.r, dir);
-    }
-    void setHeight (wdim nh, int dir) {
-        if (nh < mh) h = mh;	// clamp
-        else h = nh;
-        yh = y + h;
-        widget.setHeight (h - border.t - border.b, dir);
-    }
-    
-    void setPosition (wdim nx, wdim ny) {
-        x = nx;
-        y = ny;
-        
-        xw = x+w;
-        yh = y+h;
-        
-        widgetX = x + border.l;
-        widgetY = y + border.t;
-        
-        widget.setPosition (widgetX, widgetY);
-        
-        gui_.requestRedraw ();  // necessary whenever the window is moved; setPosition is called after resizes and moves
-    }
-    
-    IWidget getWidget (wdim cx, wdim cy) {
-        if (cx < x || cx >= xw || cy < y || cy >= yh)   // not over window
-            return null;
-        if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b)
-                                                        // over the widget
-            return widget.getWidget (cx, cy);
-        else                                            // over the window border
-            return this;
-    }
     void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
+        if (event > subWidgets.length) return;
         if (b == 1 && state == true) {
-            resizeType = rend.getResizeType (cx-x, cy-y, w,h);
-            
-            if (resizeType != RESIZE_TYPE.NONE) {    // Some type of resize
-                // Set x/yDrag (unfortunately these need to be different for each edge)
-                if (resizeType & RESIZE_TYPE.L)
-                    xDrag = w + cx;
-                else if (resizeType & RESIZE_TYPE.R)
-                    xDrag = w - cx;
+            active = event;
+            with (sWData[active]) {
+                resizeType = border.getResize (cx - this.x - x, cy - this.y - y, w,h);
+                alias border.RESIZE RESIZE;
                 
-                if (resizeType & RESIZE_TYPE.T)
-                    yDrag = h + cy;
-                else if (resizeType & RESIZE_TYPE.B)
-                    yDrag = h - cy;
-                
-                // Add the callbacks (they use resizeType which has already been set)
-                gui_.addClickCallback (&endCallback);
-                gui_.addMotionCallback (&resizeCallback);
-            } else {                             // window is being moved
-                xDrag = cx - x;
-                yDrag = cy - y;
-                
-                gui_.addClickCallback (&endCallback);
-                gui_.addMotionCallback (&moveCallback);
+                if (resizeType != RESIZE.NONE) {
+                    // Set x/yDrag (unfortunately these need to be different for each edge)
+                    if (resizeType & RESIZE.X1)
+                        xDrag = w + cx;
+                    else if (resizeType & RESIZE.X2)
+                        xDrag = w - cx;
+                    
+                    if (resizeType & RESIZE.Y1)
+                        yDrag = h + cy;
+                    else if (resizeType & RESIZE.Y2)
+                        yDrag = h - cy;
+                    
+                    mgr.addClickCallback (&endCallback);
+                    mgr.addMotionCallback (&resizeCallback);
+                } else if (borderType & BTYPE.MOVE) {   // window is being moved
+                    xDrag = cx - x;
+                    yDrag = cy - y;
+                    
+                    mgr.addClickCallback (&endCallback);
+                    mgr.addMotionCallback (&moveCallback);
+                }
             }
         }
     }
     
-    void draw () {
-        // background
-        rend.drawWindow (x,y, w,h);
-        
-        // Tell the widget to draw itself:
-        widget.draw();
-    }
-    //END IWidget methods
-    
-private:
-    alias IRenderer.BorderDimensions BorderDimensions;
-    alias IRenderer.RESIZE_TYPE RESIZE_TYPE;
-    
-    //BEGIN Window moving and resizing
+protected:
     void moveCallback (wdabs cx, wdabs cy) {
-        setPosition (cx-xDrag, cy-yDrag);
+        with (sWData[active]) {
+                x = cx-xDrag;
+                y = cy-yDrag;
+                
+                if (x < 0)
+                    x = 0;
+                else if (x + w > this.w)
+                    x = (w > this.w) ? 0 : this.w - w;
+                if (y < 0)
+                    y = 0;
+                else if (y + h > this.h)
+                    y = (h > this.h) ? 0 : this.h - h;
+                
+                subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1);
+        }
+        mgr.requestRedraw;
     }
     void resizeCallback (wdabs cx, wdabs cy) {
-        debug scope(failure)
-                logger.trace ("resizeCallback: failure");
-        
-        // This function is only called if some resize is going to happen.
-        // x,y are used as parameters to setPosition as well as being affected by it; somewhat
-        // pointless but fairly effective.
-        
-        if (resizeType & RESIZE_TYPE.L) {
-            wdim xSize = xDrag - cx;
-            if (xSize < mw) xSize = mw;	// clamp
-            x += w - xSize;
-            setWidth (xSize, 1);
-        }
-        else if (resizeType & RESIZE_TYPE.R) {
-            setWidth (xDrag + cx, -1);
+        with (sWData[active]) {
+            if (resizeType & RESIZE.X1) {
+                wdim ow = w;
+                w = xDrag - cx;
+                if (w < mw) w = mw;
+                x += ow - w;
+                subWidgets[active].setWidth  (w - border.x1 - border.x2, 1);
+            }
+            else if (resizeType & RESIZE.X2) {
+                w = xDrag + cx;
+                if (w < mw) w = mw;
+                subWidgets[active].setWidth  (w - border.x1 - border.x2, -1);
+            }
+            if (resizeType & RESIZE.Y1) {
+                wdim oh = h;
+                h = yDrag - cy;
+                if (h < mh) h = mh;
+                y += oh - h;
+                subWidgets[active].setHeight (h - border.y1 - border.y2, 1);
+            }
+            else if (resizeType & RESIZE.Y2) {
+                h = yDrag + cy;
+                if (h < mh) h = mh;
+                subWidgets[active].setHeight (h - border.y1 - border.y2, -1);
+            }
+            // Reposition widget and sub-widgets:
+            subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1);
         }
-        if (resizeType & RESIZE_TYPE.T) {
-            wdim ySize = yDrag - cy;
-            if (ySize < mh) ySize = mh;
-            y += h - ySize;
-            setHeight (ySize, 1);
-        }
-        else if (resizeType & RESIZE_TYPE.B) {
-            setHeight (yDrag + cy, -1);
-        }
-        
-        // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the
-        // resizing:
-        setPosition (x, y);
+        mgr.requestRedraw;
     }
-    bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) {
-        if (b == 1 && state == false) {
-            // The mouse shouldn't have moved since the motion callback
-            // was last called, so there's nothing else to do now.
-            gui_.removeCallbacks (cast(void*) this);
-            
+    bool endCallback (wdabs, wdabs, ubyte b, bool state) {
+        if (b == 1 && state == false) { // end of a move/resize
+            mgr.removeCallbacks (cast(void*) this);
             return true;    // we've handled the up-click
         }
         return false;       // we haven't handled it
     }
-    wdim xDrag, yDrag;              // where a drag starts relative to x and y
-    IRenderer.RESIZE_TYPE resizeType;   // Type of current resize
-    //END Window moving and resizing
-    
-    // Load/save data:
-    public char[] name;             // The window's name (id from config file)
-    //bool edited = false;            // True if any widgets have been edited (excluding scaling)
-    
-    IGui gui_;                      // The gui managing this window
-    IRenderer rend;                 // The window's renderer
     
-    IWidget widget;                 // The primary widget in this window.
+    struct SWData {     // NOTE: x,y,w,h must be first elements; search (&d.x)
+        wdim x,y;       // position (corner of border)
+        wdim w,h;       // size (including border)
+        wdim mw,mh;
+        BTYPE borderType;       // what type of border to put around the widget
+        IRenderer.Border border;
+    }
+    static assert (SWData.alignof == 4); // assumptions for optimization; search (&d.x)
+    SWData[] sWData;
+    size_t[] sWOrder;   // indexes for draw order (top widget at end of list)
     
-    wdim x = -1, y = -1;            // Window position
-    wdsize w,h;                     // Window size (calculated from Widgets)
-    wdim xw, yh;                    // x+w, y+h (frequent use by clickEvent)
-    wdim widgetX, widgetY;          // Widget position (= window position plus BORDER_WIDTH)
-    wdim mw = -1, mh = -1;          // minimal size (negative means requires calculation)
-    
-    BorderDimensions border;        // Total border size (move plus resize)
-    +/
+    // Click/drag information:
+    alias IRenderer.Border.BTYPE BTYPE;
+    alias IRenderer.Border.RESIZE RESIZE;
+    size_t event  = size_t.max; // Border with last click/release: size_t.max: main area (no subwidget), i: subwidget[i]
+    size_t active = size_t.max; // Like event, but refers to widget being moved/resized
+    wdim xDrag, yDrag;          // where a drag starts relative to x and y
+    RESIZE resizeType;          // Type of current resize
 }
--- a/mde/setup/Screen.d	Thu Nov 06 13:16:39 2008 +0000
+++ b/mde/setup/Screen.d	Mon Nov 10 16:44:44 2008 +0000
@@ -28,7 +28,7 @@
 import tango.time.Time;     // TimeSpan (type only; unused)
 
 import derelict.sdl.sdl;
-import derelict.opengl.gl;	// for loading a later gl version
+import derelict.opengl.gl;
 import derelict.util.exception;
 
 /** Currently just used as a namespace. Potential for multiple screen support later? */
@@ -56,9 +56,8 @@
     /** Init function to initialize SDL. */
     StageState init () {      // init func
         // Initialise SDL
-        debug logger.trace ("Calling SDL_Init");
-        //FIXME: init SDL_INIT_TIMER?
-        if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER /+| SDL_INIT_EVENTTHREAD+/)) {
+        debug logger.trace ("Calling SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)");
+        if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
             logger.fatal ("SDL initialisation failed:");
             char* msg = SDL_GetError ();
             logger.fatal (msg ? fromStringz(msg) : "no reason available");
@@ -69,7 +68,7 @@
     }
     /** SDL shutdown */
     StageState cleanup () {
-        debug logger.trace ("Calling SDL_Quit");
+        debug logger.trace ("Calling SDL_Quit ()");
         SDL_Quit();
         return StageState.INACTIVE;
     }
@@ -134,7 +133,6 @@
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        glEnable(GL_TEXTURE_2D);
         glShadeModel(GL_SMOOTH);
         
         glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
@@ -148,7 +146,6 @@
         //NOTE: wrap mode may have an effect, but shouldn't be noticed...
         
         // Window-manager settings
-        debug logger.trace ("Calling SDL_WM_SetCaption");
         SDL_WM_SetCaption (toStringz ("mde"), null);
         // SDL_WM_GrabInput (use later)
         //END Create window and initialize OpenGL
@@ -217,7 +214,7 @@
         }
         
         //debug logger.trace ("Setting video mode {}x{}, 32-bit, flags: {}", w,h,flags);
-        debug logger.trace ("Calling SDL_SetVideoMode");
+        debug logger.trace ("Calling SDL_SetVideoMode ({}, {}, 32, 0x{:x})", w,h, flags);
         if (SDL_SetVideoMode (w, h, 32, flags) is null) {
             logger.fatal ("Unable to set video mode:");
             char* msg = SDL_GetError ();