changeset 95:2a364c7d82c9

Boolean options can be adjusted from the gui now (using a very basic widget). Also some bug-fixes. Fixed a minor bug where layouts with the same id but without shared alignments would be messed up. Tracked down the "nothing trawn until a resize" bug (see jobs.txt). If widgets throw during creation they're now replaced by debug widgets. Function pointers are converted to delegates using a safer method.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 06 Nov 2008 11:07:18 +0000
parents 9520cc0448e5
children dbf332403c6e
files codeDoc/jobs.txt data/L10n/FontOptions.mtt data/L10n/MiscOptions.mtt data/L10n/OptionsFont.mtt data/L10n/OptionsMisc.mtt data/L10n/OptionsVideo.mtt data/L10n/VideoOptions.mtt data/conf/gui.mtt data/conf/options.mtt examples/guiDemo.d mde/gui/content/Content.d mde/gui/content/options.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/gui/widget/miscWidgets.d mde/imde.d mde/input/joystick.d mde/lookup/Options.d mde/mde.d mde/scheduler/Scheduler.d mde/setup/Init.d mde/setup/InitStage.d mde/setup/Screen.d mde/util.d
diffstat 31 files changed, 398 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Thu Oct 23 17:45:49 2008 +0100
+++ b/codeDoc/jobs.txt	Thu Nov 06 11:07:18 2008 +0000
@@ -7,14 +7,12 @@
 
 
 Bugs:
-Sometimes nothing is drawn until a resize, and fonts are blocks? External bug? Info: doesn't happen when limited to one thread.
+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.
 
 
 
 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.
-4   Data saving for widgetIDs with multiple instances?
-3   Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file)
 3   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour.
 3   glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point.
 3   Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/FontOptions.mtt	Thu Nov 06 11:07:18 2008 +0000
@@ -0,0 +1,4 @@
+{MT01}
+{en-GB}
+<entry|lcdFilter={0:"LCD filtering",1:"Enable or disable sub-pixel rendering. Note that the FreeType library may be compiled without support due to patent issues."}>
+<entry|renderMode={0:"Font rendering mode",1:"Controls how fonts are rendered: in gray-scale, or for LCDs (with a horizontal (usual) or vertical layout, with an RGB (usual) or BGR sub-pixel mode."}>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/MiscOptions.mtt	Thu Nov 06 11:07:18 2008 +0000
@@ -0,0 +1,7 @@
+{MT01}
+{en-GB}
+<entry|maxThreads={0:"Max threads",1:"Maximum number of threads to use in mde (currently only applies to init stages run in parallel)."}>
+<entry|logLevel={0:"Logging level",1:"Controls which messages are logged, from 0=trace to 6=none (default: 1=info)."}>
+<entry|L10n={0:"Localisation",1:"Specifies the language to use."}>
+<entry|pollInterval={0:"Polling interval",1:"Delay in main loop to limit CPU usage"}>
+<entry|exitImmediately={0:"Exit immediately",1:"Load files and exit immediately, without running main loop (for debugging)"}>
--- a/data/L10n/OptionsFont.mtt	Thu Oct 23 17:45:49 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-{MT01}
-{en-GB}
-<entry|lcdFilter=["LCD filtering","Enable or disable sub-pixel rendering. Note that the FreeType library may be compiled without support due to patent issues."]>
-<entry|renderMode=["Font rendering mode","Controls how fonts are rendered: in gray-scale, or for LCDs (with a horizontal (usual) or vertical layout, with an RGB (usual) or BGR sub-pixel mode."]>
--- a/data/L10n/OptionsMisc.mtt	Thu Oct 23 17:45:49 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-{MT01}
-{en-GB}
-<entry|maxThreads={0:"Max threads",1:"Maximum number of threads to use in mde (currently only applies to init stages run in parallel)."}>
-<entry|logLevel={0:"Logging level",1:"Controls which messages are logged, from 0=trace to 6=none (default: 1=info)."}>
-<entry|L10n={0:"Localisation",1:"Specifies the language to use."}>
-<entry|pollInterval={0:"Polling interval",1:"Delay in main loop to limit CPU usage"}>
-<entry|exitImmediately={0:"Exit immediately",1:"Load files and exit immediately, without running main loop (for debugging)"}>
--- a/data/L10n/OptionsVideo.mtt	Thu Oct 23 17:45:49 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-{MT01}
-{en-GB}
-<entry|fullscreen=["Fullscreen","If true use the whole screen, if false use a window."]>
-<entry|hardware=["Hardware","Create the video surface in hardware or software memory."]>
-<entry|resizable=["Resizable","In windowed mode, allow the window to be resized by the window manager."]>
-<entry|noFrame=["No frame","In windowed mode, don't draw a window border."]>
-<entry|screenW=["Screen width","Horizontal resolution (fullscreen mode)."]>
-<entry|screenH=["Screen height","Vertical resolution (fullscreen mode)."]>
-<entry|windowW=["Window width","Horizontal size (windowed mode)."]>
-<entry|windowH=["Window height","Vertical size (windowed mode)."]>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/L10n/VideoOptions.mtt	Thu Nov 06 11:07:18 2008 +0000
@@ -0,0 +1,10 @@
+{MT01}
+{en-GB}
+<entry|fullscreen={0:"Fullscreen",1:"If true use the whole screen, if false use a window."}>
+<entry|hardware={0:"Hardware",1:"Create the video surface in hardware or software memory."}>
+<entry|resizable={0:"Resizable",1:"In windowed mode, allow the window to be resized by the window manager."}>
+<entry|noFrame={0:"No frame",1:"In windowed mode, don't draw a window border."}>
+<entry|screenW={0:"Screen width",1:"Horizontal resolution (fullscreen mode)."}>
+<entry|screenH={0:"Screen height",1:"Vertical resolution (fullscreen mode)."}>
+<entry|windowW={0:"Window width",1:"Horizontal size (windowed mode)."}>
+<entry|windowH={0:"Window height",1:"Vertical size (windowed mode)."}>
--- a/data/conf/gui.mtt	Thu Oct 23 17:45:49 2008 +0100
+++ b/data/conf/gui.mtt	Thu Nov 06 11:07:18 2008 +0000
@@ -7,12 +7,12 @@
 <WidgetData|content={0:[0xC100,0,4,2],1:["floating","button","blank","blank","f2","opts","blank","blank"]}>
 <WidgetData|button={0:[0x10,50,50]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x8110,0],1:["optDBox","MiscOptions"]}>
-<WidgetData|optDBox={0:[0xC100,0,2,1],1:["optBox","optDesc"]}>
+<WidgetData|opts={0:[0x8110,0],1:["optDBox","VideoOptions"]}>
+<WidgetData|optDBox={0:[0xC100,1,2,1],1:["optBox","optDesc"]}>
 <WidgetData|optBox={0:[0xC100,1,1,3],1:["optName","optSep","optVal"]}>
 <WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
-<WidgetData|optVal={0:[0x4020, 0, 0xBF00]}>
+<WidgetData|optVal={0:[0x6030]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
 <WidgetData|floating={0:[0x8200,20,20],1:["text"]}>
 <WidgetData|f2={0:[0x8200,50,20],1:["button"]}>
--- a/data/conf/options.mtt	Thu Oct 23 17:45:49 2008 +0100
+++ b/data/conf/options.mtt	Thu Nov 06 11:07:18 2008 +0000
@@ -16,7 +16,7 @@
 <bool|hardware=false>
 <bool|fullscreen=false>
 <int|screenW=1280>
+<int|screenH=1024>
 <int|windowW=800>
-<int|screenH=1024>
 <int|windowH=600>
 
--- a/examples/guiDemo.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/examples/guiDemo.d	Thu Nov 06 11:07:18 2008 +0000
@@ -67,7 +67,7 @@
      * 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, &Screen.draw); // Draw, per event only.
+    mainSchedule.add (SCHEDULE.DRAW, &Screen.draw).request = true;      // Draw, per event and first frame only.
     mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true;
     //END Main loop setup
     
--- a/mde/gui/content/Content.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/content/Content.d	Thu Nov 06 11:07:18 2008 +0000
@@ -94,14 +94,20 @@
 
 class BoolContent : ValueContent
 {
-    this () {}
-    this (bool val) {
+    /** Create a content with _symbol name symbol. */
+    this (char[] symbol, bool val = false) {
+        symb = symbol;
         v = val;
     }
     
+    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
+    BoolContent addChangeCb (void delegate (char[] symbol,bool value) cb) {
+        cngCb ~= cb;
+        return this;
+    }
+    
     /// Get the text.
     char[] toString (uint i) {
-        debug logger.trace ("BoolContent.toString");
         return (i == 0) ? v ? "true" : "false"
              : (i == 1) ? name_
              : (i == 2) ? desc_
@@ -110,12 +116,17 @@
     
     void opAssign (bool val) {
         v = val;
+        foreach (cb; cngCb)
+            cb(symb, val);
     }
     bool opCall () {
         return v;
     }
     
-    protected bool v;
+    bool v;     //FIXME: should be protected but Options needs to set without calling callbacks
+protected:
+    char[] symb;
+    void delegate (char[],bool)[] cngCb;       // change callbacks
 }
 
 /** Text content. */
--- a/mde/gui/content/options.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/content/options.d	Thu Nov 06 11:07:18 2008 +0000
@@ -25,12 +25,10 @@
 import mde.lookup.Options;
 import mde.lookup.Translation;
 
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this () {
-        logger = Log.getLogger ("mde.gui.content.options");
-    }
+import tango.util.log.Log : Log, Logger;
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.content.options");
 }
 
 class OptionList
--- a/mde/gui/renderer/IRenderer.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/renderer/IRenderer.d	Thu Nov 06 11:07:18 2008 +0000
@@ -76,9 +76,7 @@
      * 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);
-    
-    /** See restrict. */
-    void relax ();
+    void relax ();      /// ditto
     
     /** Draw a window border plus background. */
     void drawWindow (wdim x, wdim y, wdim w, wdim h);
@@ -92,8 +90,16 @@
     /** Draws a button frame, in if pushed == true. */
     void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed);
     
-    /** Get a TextAdapter to draw some text. */
-    TextAdapter getAdapter (char[] text, int colour);
+    /** 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.
      *
--- a/mde/gui/renderer/SimpleRenderer.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/renderer/SimpleRenderer.d	Thu Nov 06 11:07:18 2008 +0000
@@ -119,6 +119,21 @@
         gl.drawBox (x,y, w,h);
     }
     
+    wdimPair getToggleSize () {
+        wdimPair r;
+        r.x = 16;
+        r.y = 16;
+        return r;
+    }
+    void drawToggle (wdim x, wdim y, bool state, bool pushed) {
+        float c = pushed ? .7f : .5f;
+        if (state)
+            gl.setColor (0f, c, 0f);
+        else
+            gl.setColor (c, 0f, 0f);
+        gl.drawBox (x+2,y+2, 12,12);
+    }
+    
     TextAdapter getAdapter (char[] text, int col) {
         TextAdapter a;
         a.font = defaultFont;
--- a/mde/gui/widget/Floating.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/Floating.d	Thu Nov 06 11:07:18 2008 +0000
@@ -53,7 +53,7 @@
  * Ints supplied may consist of just the widget type or
  * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords).
  */
-class FloatingAreaWidget : ParentWidget
+class FloatingAreaWidget : AParentWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
         subWidgets.length = data.strings.length;
--- a/mde/gui/widget/Ifaces.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Thu Nov 06 11:07:18 2008 +0000
@@ -151,8 +151,8 @@
     // NOTE - change?
     /** Called on all widgets after all widgets have been created in a deepest first order.
      *
-     * Must be called before any other methods on the widget, which means this cannot call sub-
-     * widgets' methods, but finalize can. */
+     * finalize must be called before any other methods on the widget, which means this() cannot
+     * call sub-widgets' methods, but finalize() can. */
     void prefinalize ();
     void finalize ();   /// ditto
     
--- a/mde/gui/widget/TextWidget.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/TextWidget.d	Thu Nov 06 11:07:18 2008 +0000
@@ -32,18 +32,14 @@
     }
 }
 
-/// Basic text widget
-class TextLabelWidget : Widget
+/** Base text widget.
+ *
+ * Little use currently except to ease future additions. */
+class ATextWidget : AWidget
 {
-    /** Constructor for a widget containing [fixed] content.
-     *
-     * Widget uses the initialisation data:
-     * [widgetID, contentID, colour]
-     * where contentID is an ID for the string ID of the contained content
-     * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
+    /** Set the adapter first:
+     * adapter = mgr.renderer.getAdapter ("string", 0xRRGGBB); */
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        WDCheck (data, 2, 1);
-        adapter = mgr.renderer.getAdapter (data.strings[0], data.ints[1]);
         adapter.getDimensions (mw, mh);
         super (mgr, id, data);
     }
@@ -57,26 +53,37 @@
     IRenderer.TextAdapter adapter;
 }
 
+
+/// Basic text widget
+class TextLabelWidget : ATextWidget
+{
+    /** Constructor for a widget containing [fixed] content.
+     *
+     * Widget uses the initialisation data:
+     * [widgetID, contentID, colour]
+     * where contentID is an ID for the string ID of the contained content
+     * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        WDCheck (data, 2, 1);
+        adapter = mgr.renderer.getAdapter (data.strings[0], data.ints[1]);
+        super (mgr, id, data);
+    }
+}
+
 /// Basic widget displaying a label from a content.
-class ContentLabelWidget : Widget
+class ContentLabelWidget : ATextWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
         debug assert (c, "content is null (code error)");
         WDCheck (data, 3, 0);
         content = c;
+        if (!content) throw new ContentException ();
         index = data.ints[1];
         adapter = mgr.renderer.getAdapter (content.toString(index), data.ints[2]);
-        adapter.getDimensions (mw, mh);
         super (mgr, id,data);
     }
     
-    void draw () {
-        super.draw();
-        adapter.draw (x,y);
-    }
-    
 protected:
-    IRenderer.TextAdapter adapter;
     IContent content;
     int index;
 }
--- a/mde/gui/widget/Widget.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/Widget.d	Thu Nov 06 11:07:18 2008 +0000
@@ -19,6 +19,9 @@
  * 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.
+ * 
+ * Abstract widget classes have an 'A' prepended to the name, similar to the 'I' convention for
+ * interfaces.
  *************************************************************************************************/
 module mde.gui.widget.Widget;
 
@@ -41,11 +44,11 @@
  * useful basic implementation for widgets. Widgets need not inherit these (they only need
  * implement IWidget); they are simply provided for convenience and to promote code reuse.
  *************************************************************************************************/
-abstract class Widget : IChildWidget
+abstract class AWidget : IChildWidget
 {
 //BEGIN Load and save
     // Base this() for child Widgets.
-    this (IWidgetManager mgr, widgetID id, WidgetData) {
+    protected this (IWidgetManager mgr, widgetID id, WidgetData) {
         this.mgr = mgr;
         this.id = id;
     }
@@ -155,7 +158,7 @@
 /*************************************************************************************************
 * An abstract base widget class for parent widgets.
 *************************************************************************************************/
-abstract class ParentWidget : Widget
+abstract class AParentWidget : AWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
         super (mgr, id, data);
@@ -177,7 +180,7 @@
 }
 
 /** A base for fixed-size widgets taking their size from the creation data. */
-class FixedWidget : Widget {
+class FixedWidget : AWidget {
     // Check data.length is at least 3 before calling!
     /** Constructor for a fixed-size [blank] widget.
      *
@@ -186,15 +189,13 @@
      * where w, h is the fixed size. */
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
         super (mgr, id, data);
-        mw = cast(wdim) data.ints[1];
-        mh = cast(wdim) data.ints[2];
-        w = mw;
-        h = mh;
+        w = mw = cast(wdim) data.ints[1];
+        h = mh = cast(wdim) data.ints[2];
     }
 }
 
 /** A base for resizable widgets. */
-class SizableWidget : Widget {
+class SizableWidget : AWidget {
     // Check data.length is at least 1 before calling!
     /// Constructor for a completely resizable [blank] widget.
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
@@ -204,3 +205,59 @@
     bool isWSizable () {    return true;    }
     bool isHSizable () {    return true;    }
 }
+
+/** For pressable buttons.
+ *
+ * Normally overriding classes implement this, draw and activated. */
+abstract class AButtonWidget : AWidget
+{
+    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        super (mgr, id, data);
+    }
+    
+    /// May be over-ridden. Pushed is true if the button has been pushed and not released.
+    void draw () {
+        mgr.renderer.drawButton (x,y, w,h, pushed);
+    }
+    
+    /// Handles the down-click
+    void clickEvent (wdabs, wdabs, ubyte b, bool state) {
+        if (b == 1 && state == true) {
+            pushed = true;
+            mgr.requestRedraw;
+            mgr.addClickCallback (&clickWhilePushed);
+            mgr.addMotionCallback (&motionWhilePushed);
+        }
+    }
+    
+    /// Called when a mouse click event occurs while held; handles up-click
+    bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) {
+        if (b == 1 && state == false) {
+            if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
+                activated();
+            
+            pushed = false;
+            mgr.requestRedraw;
+            mgr.removeCallbacks (cast(void*) this);
+            
+            return true;
+        }
+        return false;
+    }
+    /// Called when a mouse motion event occurs while held; handles pushing in/out on hover
+    void motionWhilePushed (wdabs cx, wdabs cy) {
+        bool oldPushed = pushed;
+        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
+        else pushed = false;
+        if (oldPushed != pushed)
+            mgr.requestRedraw;
+    }
+    
+    /// The action triggered when the button is clicked...
+    void activated ();
+    
+protected:
+    bool pushed = false;        /// True if button is pushed in (visually)
+}
+
+
--- a/mde/gui/widget/createWidget.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Thu Nov 06 11:07:18 2008 +0000
@@ -27,6 +27,7 @@
 import mde.gui.widget.layout;
 import mde.gui.widget.miscWidgets;
 import mde.gui.widget.TextWidget;
+import mde.gui.widget.miscContent;
 import mde.gui.widget.Floating;
 import tango.util.log.Log : Log, Logger;
 
@@ -56,11 +57,15 @@
     }
     int type = data.ints[0];    // type is first element of data
     
-    //pragma (msg, binarySearch ("type", WIDGETS));
-    mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]);
+    try {
+        //pragma (msg, binarySearch ("type", WIDGETS));
+        mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]);
+        // Not returned a new widget or thrown:
+        logger.error ("Bad widget type: {}; creating a debug widget instead",type);
+    } catch (Exception e) {
+        logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg);
+    }
     
-    // Not returned a new widget...
-    logger.error ("Bad widget type: {}; creating a debug widget instead.",type);
     return new DebugWidget (mgr, id, data);
 }
 
@@ -77,6 +82,7 @@
 private:
 /// Widget types.
 enum WIDGET_TYPE : int {
+    FUNCTION                = 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
     TAKES_CONTENT           = 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
     PARENT                  = 0x8000,   // widget can have children; not used by code (except in data files)
     
@@ -95,6 +101,10 @@
     ContentLabel	    = TAKES_CONTENT | 0x20,
     TextLabel               = 0x21,
     
+    // content editables: 0x30
+    editContent             = FUNCTION | TAKES_CONTENT | 0x30,
+    BoolContent             = TAKES_CONTENT | 0x31,
+    
     GridLayout              = TAKES_CONTENT | PARENT | 0x100,
     TrialContentLayout      = PARENT | 0x110,
     
@@ -111,6 +121,8 @@
         "Button",
         "TextLabel",
         "ContentLabel",
+        "BoolContent",
+        "editContent",
         "TrialContentLayout",
         "FloatingArea",
         "GridLayout"];
@@ -128,7 +140,9 @@
         foreach (c; consts) {
             ret ~=  "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~
                     "   debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n" ~
-                    "   static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~
+                    "   static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.FUNCTION)\n" ~
+                    "       return " ~ c ~ " (mgr, id, data, content);\n" ~
+                    "   else static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~
                     "       return new " ~ c ~ "Widget (mgr, id, data, content);\n" ~
                     "   else\n" ~
                     "       return new " ~ c ~ "Widget (mgr, id, data);\n" ~
--- a/mde/gui/widget/layout.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/layout.d	Thu Nov 06 11:07:18 2008 +0000
@@ -106,7 +106,7 @@
         cols = 1;
         if ((rows = optsList.list.length) > 0) {
             // Get all sub-widgets
-            subWidgets.length = rows*cols;
+            subWidgets.length = rows;
             foreach (i, c; optsList.list) {
                 subWidgets[i] = mgr.makeWidget (data.strings[0], c);
             }
@@ -139,7 +139,7 @@
  *
  * The grid has no border but has spacing between widgets.
  *************************************************************************************************/
-abstract class GridWidget : ParentWidget
+abstract class GridWidget : AParentWidget
 {
     //BEGIN Creation & saving
     /** Partial constructor for a grid layout widget.
@@ -274,8 +274,7 @@
             widget.draw ();
     }
     
-private:
-    //BEGIN Cache calculation functions
+package:
     /* Calculations which need to be run whenever a new sub-widget structure is set
      * (i.e. to produce cached data calculated from construction data).
      * Also need to be re-run if the renderer changes.
@@ -320,9 +319,8 @@
             row.sizable[i / cols] = true;
         }
     }
-    //END Cache calculation functions
     
-    
+private:
     void setColWidth (myIt i, wdim w, int dir) {
         for (myIt j = 0; j < rows; ++j) {
             subWidgets[i + cols*j].setWidth (w, dir);
@@ -385,7 +383,7 @@
  * Cells are not directly interacted with, but minimal widths for each column are passed, and
  * callback functions are used to adjust the width of any column.
  *************************************************************************************************/
-class AlignColumns
+package class AlignColumns
 {
     /** Instance returned will be shared with any other widgets of same widgetID.
      *
@@ -395,6 +393,7 @@
         if (p) {
             if (p.minWidth.length != columns)
                 throw new GuiException ("AlignColumns: no. of columns varies between sharing widgets (code error)");
+            //logger.trace ("Shared alignment for: "~id);
             return *p;
         } else {
             auto a = new AlignColumns (columns);
@@ -417,35 +416,52 @@
      * Widths should be set after calling, as on creation. */
     void reset (myIt columns) {
         if (columns < 1)
-            throw new GuiException("AlignColumns: created with <1 column");
+            throw new GuiException("AlignColumns: created with <1 column (code error)");
         minWidth = new wdim[columns];
         sizable = new bool[columns];
         width = null;   // enforce calling setWidths after this
         firstSizable = -1;
         lastSizable = -1;
-        spare = 0;
     }
     
-    /** Initialize widths as minimal widths. */
-    void setWidths () {
+    /** Initialize widths, either from minWidths or from supplied list, checking validity.
+     *
+     * Also calculates first/lastSizable from sizable, overall minimal width and column positions.
+     */
+    void setWidths (wdim[] data = null) {
         if (!width) {
-            width = minWidth.dup;
-            initCalc;
-        }
-    }
-    /** Initialize widths from supplied list, checking validity. */
-    void setWidths (wdim[] data) {
-        if (!width) {
-            // 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;
+            if (data) {
+                debug assert (data.length == minWidth.length, "setWidths called with bad data length (code error)");
+                width = data.dup;       // data is shared by other widgets with same id so must be .dup'ed
+                // And check sizes are valid:
+                foreach (i, m; minWidth) {
+                    if (!sizable[i] || width[i] < m)    // if width is fixed or less than minimum
+                        width[i] = m;
+                }
+            } else
+                width = minWidth.dup;
+            
+            /* Calculate the minimal width of all columns plus spacing. */
+            mw = spacing * cast(wdim)(minWidth.length - 1);
+            foreach (imw; minWidth)
+                mw += imw;
+            
+            genPositions;
+            
+            foreach (i,s; sizable) {
+                if (s) {
+                    firstSizable = i;
+                    goto gotFirst;
                 }
             }
-            initCalc;
+            return; // none resizable - don't search for lastSizable
+            gotFirst:
+            foreach_reverse (i,s; sizable) {
+                if (s) {
+                    lastSizable = i;
+                    return; // done
+                }
+            }
         }
     }
     
@@ -462,18 +478,16 @@
     /** Get the row/column of relative position l.
      *
      * returns:
-     * -i if in space to left of col i, or i if on col i, or -(num cols + 1) if in $(I spare)
-     * space to right of last column. */
+     * -i if in space to left of col i, or i if on col i. */
     myDiff getCell (wdim l) {
+        debug assert (width, "AlignColumns not initialized when getCell called (code error)");
         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 or in spare space after last column
-            debug assert (i+1 < minWidth.length ||
-                          l < pos[i] + width[i] + spare,
-                          "getCell: l >= total width (code error)");
+        if (l >= pos[i] + width[i]) {       // between columns
+            debug assert (i+1 < minWidth.length, "getCell: l >= total width (code error)");
             return -i - 1;                  // note: i might be 0 so cannot just return -i
         }
         return i;
@@ -483,6 +497,7 @@
      *
      * nw should be at least the minimal width. */
     wdim resizeWidth (wdim nw, int dir) {
+        debug assert (width, "AlignColumns not initialized when resizeWidth called (code error)");
         if (nw < mw) {
             debug logger.warn ("Widget dimension set below minimal");
             nw = mw;
@@ -490,14 +505,13 @@
         if (nw == w) return w;
         
         wdim diff = nw - w;
-        if (firstSizable == -1) {
-            spare += diff;
-            w = nw;
-        } else
-            adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir);
+        if (firstSizable == -1)
+            diff = adjustCellSizes (diff, minWidth.length-1, -1);
+        else
+            diff = adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir);
         
         debug if (nw != w) {
-            logger.trace ("resizeWidth to {} failed, new width: {}",nw,w);
+            logger.trace ("resizeWidth on {} to {} failed, new width: {}, diff {}, firstSizable {}, columns {}",cast(void*)this, nw,w, diff, firstSizable, minWidth.length);
             /+ Also print column widths & positions:
             logger.trace ("resizeWidth to {} failed! Column dimensions and positions:",nw);
             foreach (i,w; width)
@@ -511,8 +525,8 @@
      * This and resizeCols are for moving dividers between cells. */
     bool findResizeCols (wdim l) {
         resizeU = -getCell (l);             // potential start for upward-resizes
-        if (resizeU <= 0 || resizeU > minWidth.length)
-            return true;        // not on a space between cells or in spare space after last cell
+        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)
@@ -548,33 +562,6 @@
         }
     }
     
-    /** Intitialization triggered by setWidths.
-     * 
-     * Calculates first/lastSizable from sizable, minimal width and positions. */
-    private void initCalc () {
-        /* Calculate the minimal width of all columns plus spacing. */
-        mw = spacing * cast(wdim)(minWidth.length - 1);
-        foreach (w; minWidth)
-            mw += w;
-        
-        genPositions;
-        foreach (i,s; sizable) {
-            if (s) {
-                firstSizable = i;
-                goto gotFirst;
-            }
-        }
-        return; // none resizable - don't search for lastSizable
-        gotFirst:
-        
-        foreach_reverse (i,s; sizable) {
-            if (s) {
-                lastSizable = i;
-                return; // done
-            }
-        }
-    }
-    
     /* Generate position infomation for each column and set w. */
     private void genPositions () {
         pos.length = minWidth.length;
@@ -632,16 +619,13 @@
                     dg(i, width[i], incr);
                 // rd is remainder to decrease by
                 
-                
-                bool it = true;     // iterate (force first time)
-                while (it) {
+                do {
                     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
-                }
+                } while (!sizable[i])       // iterate again if row/col isn't resizable
             }
         }
         // else no adjustment needed (diff == 0)
@@ -664,14 +648,13 @@
     
     /** Current width, relative position (for each column)
      *
-     * Treat as READ ONLY! */
+     * Treat as READ ONLY outside this class! */
     wdim[]  width;              // only adjusted within the class
     wdim[]  pos;                /// ditto
-    protected:
+protected:
     myDiff  resizeD,            // resizeCols works down from this index (<0 if not resizing)
             resizeU;            // and up from this index
     wdim    spacing;            // used by genPositions (which cannot access the layout class's data)
-    wdim    spare;              // fixed size only: extra blank space filler
     wdim    w,mw;               // current & minimal widths
     /* indicies of the first/last resizable column (negative if none are resizable). */
     myDiff  firstSizable = -1, lastSizable = -1;  // set by calcFLSbl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/miscContent.d	Thu Nov 06 11:07:18 2008 +0000
@@ -0,0 +1,73 @@
+/* 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 content widgets. */
+module mde.gui.widget.miscContent;
+
+import mde.gui.widget.Widget;
+import mde.gui.widget.TextWidget;
+import mde.gui.exception;
+import mde.gui.renderer.IRenderer;
+
+import mde.gui.content.Content;
+
+/// Chooses the most appropriate content editing widget
+IChildWidget editContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    if (cast(BoolContent) c)
+        return new BoolContentWidget(mgr,id,data,c);
+    else        // generic uneditable option
+        return new DisplayContentWidget(mgr,id,data,c);
+}
+
+/// Just displays the content
+class DisplayContentWidget : ATextWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        WDCheck(data, 1);
+        content = c;
+        if (!content) throw new ContentException ();
+        adapter = mgr.renderer.getAdapter (content.toString(0));
+        super (mgr, id, data);
+    }
+    
+protected:
+    IContent content;
+}
+
+/// Editable boolean widget
+class BoolContentWidget : AButtonWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        WDCheck(data, 1);
+        content = cast(BoolContent) c;
+        if (!content) throw new ContentException ();
+        wdimPair s = mgr.renderer.getToggleSize;
+        w = mw = s.x;
+        h = mh = s.y;
+        super (mgr, id, data);
+    }
+    
+    void draw () {
+        mgr.renderer.drawToggle (x,y, content(), pushed);
+    }
+    
+    void activated () {
+        content = !content();
+    }
+    
+protected:
+    BoolContent content;
+}
+
--- a/mde/gui/widget/miscWidgets.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/gui/widget/miscWidgets.d	Thu Nov 06 11:07:18 2008 +0000
@@ -76,48 +76,16 @@
 }
 
 /// First interactible widget
-class ButtonWidget : FixedWidget
+class ButtonWidget : AButtonWidget
 {
-    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 (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 3);
+        w = mw = cast(wdim) data.ints[1];
+        h = mh = cast(wdim) data.ints[2];
         super (mgr, id, data);
     }
     
-    void draw () {
-        mgr.renderer.drawButton (x,y, w,h, pushed);
-    }
-    
-    void clickEvent (wdabs, wdabs, ubyte b, bool state) {
-        if (b == 1 && state == true) {
-            pushed = true;
-            mgr.requestRedraw;
-            mgr.addClickCallback (&clickWhileHeld);
-            mgr.addMotionCallback (&motionWhileHeld);
-        }
-    }
-    // Called when a mouse motion/click event occurs while (held == true)
-    bool clickWhileHeld (wdabs cx, wdabs 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;
-            mgr.requestRedraw;
-            mgr.removeCallbacks (cast(void*) this);
-            
-            return true;
-        }
-        return false;
-    }
-    void motionWhileHeld (wdabs cx, wdabs cy) {
-        bool oldPushed = pushed;
-        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
-        else pushed = false;
-        if (oldPushed != pushed)
-            mgr.requestRedraw;
+    void activated () {
+        Stdout ("Button clicked!").newline;
     }
 }
--- a/mde/imde.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/imde.d	Thu Nov 06 11:07:18 2008 +0000
@@ -13,7 +13,8 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** This module is for interfacing with the mde.mde module and some global items. */
+/** This module is for interfacing with the mde.mde module (or other module containing main()) and
+ * some global items. */
 module mde.imde;
 
 import mde.input.Input;
--- a/mde/input/joystick.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/input/joystick.d	Thu Nov 06 11:07:18 2008 +0000
@@ -54,6 +54,7 @@
 StageState closeJoysticks () {
     foreach (js; joysticks) {
         // NOTE: This sometimes causes a SIGSEGV (Address boundary error) when init fails.
+        debug logger.trace ("Calling SDL_JoystickClose");
         if(js !is null) SDL_JoystickClose(js);	// only close if successfully opened
     }
     return StageState.INACTIVE;
--- a/mde/lookup/Options.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/lookup/Options.d	Thu Nov 06 11:07:18 2008 +0000
@@ -81,7 +81,8 @@
     // All supported types, for generic handling via templates. It should be possible to change
     // the supported types simply by changing this list now (untested).
     template store(A...) { alias A store; }
-    alias store!(int, double, char[]) TYPES;//FIXME removed bool
+    alias store!(bool, int, double, char[]) TYPES;
+    alias store!(int, double, char[]) TYPES2;
     //BEGIN Templates: internal
     private {
         // Get name of a type. Basically just stringof, but special handling for arrays.
@@ -122,7 +123,7 @@
     auto p = id in opts;
     if (p) {
         auto q = cast(BoolContent) (*p);
-        if (q) q = parseTo!(`~T.stringof~`) (dt);
+        if (q) q.v = parseTo!(`~T.stringof~`) (dt);
     }
 }`;
             } else
@@ -136,15 +137,6 @@
                 const char[] addTagMixin = ifBlock;
         }
         
-        // For writeAll
-        template writeAllMixin(A...) {
-            static if (A.length) {
-                const char[] writeAllMixin =
-                        `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]);
-            } else
-                const char[] writeAllMixin = ``;
-        }
-        
         // For list
         template listMixin(A...) {
             static if (A.length) {
@@ -288,7 +280,7 @@
     /** List the names of all options of a specific type. */
     char[][] list () {
         char[][] ret;
-        mixin (listMixin!(TYPES));
+        mixin (listMixin!(TYPES2));
         return ret;
     }
     
@@ -305,7 +297,7 @@
         OptionChanges optionChanges;	// all changes to options (for saving)
     	
         // The "pointer lists", e.g. char[]*[ID] optscharA;
-        mixin (PLists!(TYPES)); //FIXME adds unused optsbool
+        mixin (PLists!(TYPES2)); //FIXME
         ValueContent[char[]] opts;      // generic list of option values
     }
     
@@ -313,10 +305,8 @@
     void addTag (char[] tp, ID id, char[] dt) {
         mixin(addTagMixin!(TYPES).addTagMixin);
     }
-
-    void writeAll (ItemDelg dlg) {
-        mixin(writeAllMixin!(TYPES));
-    }
+    // Only OptionChanges writes stuff
+    void writeAll (ItemDelg dlg) {}
     //END Mergetag loading/saving code
     //END Non-static
     
@@ -419,7 +409,7 @@
             else static if (A[0] == ' ')
                 const char[] createBCs = createBCs!(A[1..$]);
             else
-                const char[] createBCs = A[0..cIndex!(A)]~ " = new BoolContent (false);\n"~
+                const char[] createBCs = A[0..cIndex!(A)]~ ` = (new BoolContent ("`~A[0..cIndex!(A)]~"\")).addChangeCb (&optionChanges.set);\n"~
                 createBCs!(A[cIndex!(A)+1..$]);
         }
         // for recursing on TYPES
@@ -446,13 +436,13 @@
          * Basic types are replaced with a ValueContent class to keep the option synchronized and
          * generalize use. */
         template declVals(char[] A) {
-            const char[] declVals = declValsInternal!(A, TYPES,bool);
+            const char[] declVals = declValsInternal!(A, TYPES);
         }
         /** Produces the implementation code to go in the constuctor. */
         template optionsThis(char[] A) {
             const char[] optionsThis =
                     "optionChanges = new OptionChanges;\n" ~
-                    optionsThisInternal!(A,TYPES,bool);
+                    optionsThisInternal!(A,TYPES);
         }
         /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
         /** Produces the implementation code to go in the static constuctor. */
@@ -502,6 +492,16 @@
             } else
                 const char[] Vars = ``;
         }
+        // For set
+        template Set(A...) {
+            static if (A.length) {
+                const char[] Set= `void set (char[] s,`~A[0].stringof~` v) {
+   Options.changed = true;
+   `~TName!(A[0])~`s[s] = v;
+}`~ Set!(A[1..$]);
+            } else
+                const char[] Set = ``;
+        }
         
         // For addTag
         template addTagMixin(T, A...) {
@@ -529,6 +529,8 @@
     // These store the actual values, but are never accessed directly except when initially added.
     // optsX store pointers to each item added along with the ID and are used for access.
     mixin(Vars!(TYPES));
+    // set (char[] symbol, T value);    (not templates but for each type T)
+    mixin(Set!(TYPES));
     
     this () {}
     
--- a/mde/mde.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/mde.d	Thu Nov 06 11:07:18 2008 +0000
@@ -75,7 +75,7 @@
      * 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, &Screen.draw);     // Draw, per event only.
+    mainSchedule.add (SCHEDULE.DRAW, &Screen.draw).request = true;      // Draw, per event and first frame only.
     mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true;
     //END Main loop setup
     
--- a/mde/scheduler/Scheduler.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/scheduler/Scheduler.d	Thu Nov 06 11:07:18 2008 +0000
@@ -18,6 +18,7 @@
  * This class implements most functionality a generic scheduler might want, however currently it
  * doesn't any uses where equivalent functionality couldn't be achived very easily anyway. */
 module mde.scheduler.Scheduler;
+import mde.util;
 
 public import tango.time.Time;
 
@@ -80,10 +81,7 @@
     * scheduler.get(15).frame = true;
     */
     ScheduleFunc add (ID id, scheduleFct func) {
-        // Convert to a delegate. Maybe someday implicit casts will work...
-        scheduleDlg d;
-        d.funcptr = func;
-        return add (id, d);
+        return add (id, toDg(func));
     }
     /** ditto */
     ScheduleFunc add (ID id, scheduleDlg func)
--- a/mde/setup/Init.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/setup/Init.d	Thu Nov 06 11:07:18 2008 +0000
@@ -262,6 +262,14 @@
         Mutex toRunM = new Mutex;       // synchronization on toRun, numWorking
         Condition toRunC = new Condition(toRunM);       // used by threads waiting for remaining stages' dependencies to be met
         
+        /* initThreadFct is now in a class to allow reliably identifying whcich instance is run in the main thread.
+         * The main thread gets index 0, other threads indexes 2,3,4,etc. (there is no 1). */
+        class InitStageThread : Thread {
+            this (int n) {
+                debug threadNum = n;
+                super (&initThreadFct);
+            }
+            debug int threadNum;
         /* This is a threadable member function to run init stages.
          * Operation follows:
          * 1 Look for a stage to run:
@@ -329,20 +337,20 @@
                     try {
                         // FIXME - old stage start&finish trace messages - we don't have a name!
                         static if (startup) {
-                            debug logger.trace ("InitStage {}: starting init", stage.name);
+                            debug logger.trace ("({}) InitStage {}: starting init", threadNum, stage.name);
                             stage.state = (*stage).init();  // init is a property of a pointer (oh no!)
                         } else {
-                            debug logger.trace ("InitStage {}: starting cleanup", stage.name);
+                            debug logger.trace ("({}) InitStage {}: starting cleanup", threadNum, stage.name);
                             stage.state = stage.cleanup();
                         }
-                        debug logger.trace ("InitStage {}: completed; state: {}", stage.name, stage.state);
+                        debug logger.trace ("({}) InitStage {}: completed; state: {}", threadNum, stage.name, stage.state);
                     } catch (InitStageException e) {
-                        debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name);
+                        debug logger.trace ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name);
                         stage.state = e.state;
                         doneInit = STATE.ABORT;
                         break threadLoop;
                     } catch (Exception e) {
-                        debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name);
+                        debug logger.trace ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name);
                         doneInit = STATE.ABORT;
                         break threadLoop;
                     }
@@ -355,19 +363,27 @@
             toRunC.notifyAll(); // Most likely if we're exiting, we should make sure others aren't waiting.
             return;
         }
+        }
         
         // Start min(miscOpts.maxThreads,toRun.size)-1 threads:
         try {
             ThreadGroup g = new ThreadGroup;
-            for (size_t i = numWorking; i > 1; --i)
-                g.create (&initThreadFct);
-            initThreadFct();    // also run in current thread
+            InitStageThread x;
+            for (size_t i = numWorking; i > 1; --i) {
+                //g.create (&initThreadFct);
+                x = new InitStageThread (i);
+                x.start;
+                g.add (x);
+            }
+            x = new InitStageThread (0);
+            x.initThreadFct();    // also run in current thread
             g.joinAll (false);  // don't rethrow exceptions - there SHOULD NOT be any
         } catch (ThreadException e) {
             logger.error ("Exception while using threads: "~e.msg);
             logger.error ("Disabling threads and attempting to continue.");
             miscOpts.set!(int)("NumThreads", 1);        // count includes current thread
-            initThreadFct();                            // try with just this thread
+            auto x = new InitStageThread (0);
+            x.initThreadFct();                            // try with just this thread
         }       // any other exception will be caught in main() and abort program
         
         if (doneInit & STATE.ABORT)
--- a/mde/setup/InitStage.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/setup/InitStage.d	Thu Nov 06 11:07:18 2008 +0000
@@ -19,6 +19,8 @@
 module mde.setup.InitStage;
 
 public import mde.setup.exception;
+import mde.util;
+
 import tango.util.container.HashMap;
 import tango.util.log.Log : Log, Logger;
 
@@ -55,10 +57,7 @@
     }
     /// Add a stage to be initialized.
     void addInitStage (char[4] name, StageState function() init, StageState function() cleanup = null, char[4][] depends = null) {
-        StageState delegate() i,c;
-        i.funcptr = init;
-        c.funcptr = cleanup;
-        addInitStage (name, i, c, depends);
+        addInitStage (name, toDg(init), toDg(cleanup), depends);
     }
     /// Add a stage to be initialized.
     void addInitStage (char[4] name, StageState delegate() init, StageState delegate() cleanup = null, char[4][] depends = null) {
--- a/mde/setup/Screen.d	Thu Oct 23 17:45:49 2008 +0100
+++ b/mde/setup/Screen.d	Thu Nov 06 11:07:18 2008 +0000
@@ -56,7 +56,9 @@
     /** Init function to initialize SDL. */
     StageState init () {      // init func
         // Initialise SDL
-        if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
+        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+/)) {
             logger.fatal ("SDL initialisation failed:");
             char* msg = SDL_GetError ();
             logger.fatal (msg ? fromStringz(msg) : "no reason available");
@@ -67,6 +69,7 @@
     }
     /** SDL shutdown */
     StageState cleanup () {
+        debug logger.trace ("Calling SDL_Quit");
         SDL_Quit();
         return StageState.INACTIVE;
     }
@@ -145,6 +148,7 @@
         //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
@@ -213,6 +217,7 @@
         }
         
         //debug logger.trace ("Setting video mode {}x{}, 32-bit, flags: {}", w,h,flags);
+        debug logger.trace ("Calling SDL_SetVideoMode");
         if (SDL_SetVideoMode (w, h, 32, flags) is null) {
             logger.fatal ("Unable to set video mode:");
             char* msg = SDL_GetError ();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/util.d	Thu Nov 06 11:07:18 2008 +0000
@@ -0,0 +1,15 @@
+module mde.util;
+
+/// Not my code: http://www.dsource.org/projects/tango/ticket/1174#comment:7
+R delegate(T) toDg(R, T...)(R function(T) fp) {
+    if (!fp) return null;
+    struct dg {
+        R opCall(T t) {
+            return (cast(R function(T)) this) (t);
+        }
+    }
+    R delegate(T) t;
+    t.ptr = fp;
+    t.funcptr = &dg.opCall;
+    return t;
+}