changeset 31:baa87e68d7dc

GUI now supports basic interactible widgets, widget colour and border are more unified, and some code cleanup. Removed some circular dependencies which slipped in. As a result, the OpenGL code got separated into different files. Enabled widgets to recieve events. New IParentWidget interface allowing widgets to interact with their parents. New Widget base class. New WidgetDecoration class. New ButtonWidget class responding to events (in a basic way). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 29 Apr 2008 18:10:58 +0100
parents 467c74d4804d
children 316b0230a849
files codeDoc/gui/GUI notes.txt codeDoc/jobs.txt codeDoc/todo.txt data/conf/gui.mtt mde/SDL.d mde/events.d mde/gl.d mde/gl/basic.d mde/gl/draw.d mde/gui/IWindow.d mde/gui/Ifaces.d mde/gui/Widget.d mde/gui/Window.d mde/gui/decoration.d mde/gui/gui.d mde/input/input.d mde/input/joystick.d mde/mde.d mde/scheduler/Init.d mde/scheduler/Init2.d mde/scheduler/InitFunctions.d
diffstat 21 files changed, 709 insertions(+), 403 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/gui/GUI notes.txt	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,28 @@
+Copyright © 2007-2008 Diggory Hardy
+License: GNU General Public License version 2 or later (see COPYING)
+
+Todo plan:
+* means done
+
+GUI:
+->  Basic OpenGL code to:
+    ->* create orthographic projection
+    ->* draw boxes
+    ->  maybe more (text, textures, ...)
+->* Windows with size & position
+->  Widgets:
+    ->* minimum size but expandable, auto-set
+        ->  no ability to resize yet except from config files
+    ->* grid "layout" widgets
+    ->  scripted widgets
+    ->  decent rendering/theme system
+->  Text rendering
+    -> text library?
+
+
+
+Notes:
+Some unifications of the coordinate system are needed:
+    By default OpenGL uses the bottom left as the origin, with the first (bottom-left most) pixel at 0,0.
+    SDL's mouse events use the top left as the origin, with the first (top-left most) pixel at 1,1.
+I decided, for the GUI, to use the top-left at the origin with the top-left most pixel at 0,0. For OpenGL, the projection can simply be modified to achieve this; for SDL's events 1 is subtracted from each coordinate when the event is recieved.
--- a/codeDoc/jobs.txt	Mon Apr 28 10:59:47 2008 +0100
+++ b/codeDoc/jobs.txt	Tue Apr 29 18:10:58 2008 +0100
@@ -3,7 +3,6 @@
 
 
 In progress:
-Started buttonWidget (on hold)
 
 
 
@@ -49,7 +48,9 @@
 
 
 Done (for git log message):
-Revamped Scheduler. Functions can be removed, have multiple schedules, have their scheduling changed, etc.
-Scheduler has a unittest. Checked all pass.
-Main loop scheduler moved to mde. Draw-on-demand currently disabled, simplifying this.
-Made mtunitest.d remove the temporary file it uses afterwards.
+Removed some circular dependencies which slipped in. As a result, the OpenGL code got separated into different files.
+Enabled widgets to recieve events.
+New IParentWidget interface allowing widgets to interact with their parents.
+New Widget base class.
+New WidgetDecoration class.
+New ButtonWidget class responding to events (in a basic way).
--- a/codeDoc/todo.txt	Mon Apr 28 10:59:47 2008 +0100
+++ b/codeDoc/todo.txt	Tue Apr 29 18:10:58 2008 +0100
@@ -2,14 +2,19 @@
 License: GNU General Public License version 2 or later (see COPYING)
 
 
+* means done
+
 GUI:
 ->  Basic OpenGL code to:
-    -> create orthographic projection
-    -> draw boxes
-    -> maybe more (text, textures, ...)
-->  Windows with size & position
+    ->* create orthographic projection
+    ->* draw boxes
+    ->  maybe more (text, textures, ...)
+->* Windows with size & position
 ->  Widgets:
-    ->  minimum size but expandable, auto-set
-    ->  grid "layout" widgets
+    ->* minimum size but expandable, auto-set
+        ->  no ability to resize yet except from config files
+    ->* grid "layout" widgets
+    ->  scripted widgets
+    ->  decent rendering/theme system
 ->  Text rendering
     -> text library?
--- a/data/conf/gui.mtt	Mon Apr 28 10:59:47 2008 +0100
+++ b/data/conf/gui.mtt	Tue Apr 29 18:10:58 2008 +0100
@@ -1,9 +1,9 @@
 {MT01}
 {W1}
-<int|x=10>
-<int|y=50>
-<int[][int]|widgetData=[0:[1001,200,200]]>
+<int|x=0>
+<int|y=0>
+<int[][int]|widgetData=[0:[1003,200,200]]>
 {W2}
 <int|x=150>
 <int|y=200>
-<int[][int]|widgetData=[0:[1002,3,2,2,2,3,3,3,3],2:[1001,150,150],3:[1001,100,100]]>
+<int[][int]|widgetData=[0:[1002,3,2,2,2,3,3,2,5],2:[1001,150,150],3:[1003,150,150],5:[1002,2,2,6,6,6,6],6:[1003,73,73]]>
--- a/mde/SDL.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/SDL.d	Tue Apr 29 18:10:58 2008 +0100
@@ -20,7 +20,7 @@
 import mde.scheduler.InitFunctions;
 import mde.input.joystick;
 import mde.options;
-import mde.gl;
+import mde.gl.basic;
 import global = mde.global;
 
 import tango.util.log.Log : Log, Logger;
@@ -107,8 +107,6 @@
     
     // OpenGL stuff:
     glSetup();
-    
-    // Projection (mde.gl)
     setProjection (w, h);
     
     // Window-manager settings
--- a/mde/events.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/events.d	Tue Apr 29 18:10:58 2008 +0100
@@ -20,9 +20,6 @@
 import sdl = mde.SDL;           // resizeWindow
 
 import mde.input.input;
-import mde.input.exception;
-
-import mde.scheduler.InitFunctions;
 
 import derelict.sdl.events;
 
@@ -32,24 +29,6 @@
 private Logger logger;
 static this() {
     logger = Log.getLogger ("mde.events");
-    
-    init.addFunc (&initInput, "initInput");
-}
-
-void initInput () { // init func
-    try {
-        global.input = new Input();
-        global.input.loadConfig ();         // (may also create instance)
-    
-        global.input.addButtonCallback (cast(Input.inputID) 0x0u, delegate void(Input.inputID i, bool b) {
-            if (b) {
-                logger.info ("Quiting...");
-                global.run = false;
-            }
-        } );
-    } catch (Exception) {
-        setInitFailure;
-    }
 }
 
 void pollEvents (TimeSpan) {
@@ -71,7 +50,7 @@
             default:
                 try {
                     global.input (event);
-                } catch (InputClassException e) {
+                } catch (Exception e) {
                     logger.error ("Caught input exception; event will be ignored. Exception was:");
                     logger.error (e.msg);
                 }
--- a/mde/gl.d	Mon Apr 28 10:59:47 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +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.
-*
-* Everything here is really intended as makeshift code to enable GUI development. */
-module mde.gl;
-
-import global = mde.global;
-import mde.gui.gui;
-
-import derelict.sdl.sdl;
-import derelict.opengl.gl;
-
-import tango.time.Time;     // TimeSpan (type only; unused)
-
-//BEGIN GL & window setup
-void glSetup () {
-    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
-}
-
-void setProjection (int w, int h) {
-    glMatrixMode (GL_PROJECTION);
-    glLoadIdentity ();
-    
-    glViewport (0,0,w,h);
-    glOrtho (0.0,w, 0.0,h, -1.0, 1.0);
-    //glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0);
-    
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-}
-//END GL & window setup
-
-//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 l, int r, int b, int t) {
-    glBegin (GL_QUADS);
-    {
-        glVertex2i (l, b);
-        glVertex2i (r, b);
-        glVertex2i (r, t);
-        glVertex2i (l, t);
-    }
-    glEnd();
-}
-//END Drawing utils
-
-//BEGIN Drawing loop
-// Temporary draw function
-void draw (TimeSpan) {
-    glClear(GL_COLOR_BUFFER_BIT);
-    
-    gui.draw ();
-    
-    glFlush();
-    SDL_GL_SwapBuffers();
-}
-//END Drawing loop
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gl/basic.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,61 @@
+/* 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 setting up a projection and 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 GL & window setup
+void glSetup () {
+    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+void setProjection (int w, int h) {
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity ();
+    
+    glViewport (0,0,w,h);
+    
+    // Make the top-left the origin (see gui/GUI notes.txt):
+    glOrtho (0.0,w, h,0.0, -1.0, 1.0);
+    //glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0);
+    
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+}
+//END GL & window setup
+
+//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) {
+    glBegin (GL_QUADS);
+    {
+        glVertex2i (x, y+h);
+        glVertex2i (x+w, y+h);
+        glVertex2i (x+w, y);
+        glVertex2i (x, y);
+    }
+    glEnd();
+}
+//END Drawing utils
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gl/draw.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,38 @@
+/* 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/>. */
+
+/** The OpenGL draw loop.
+*
+* Everything here is really intended as makeshift code to enable GUI development. */
+module mde.gl.draw;
+
+import mde.gui.gui;
+
+import derelict.sdl.sdl;
+import derelict.opengl.gl;
+
+import tango.time.Time;     // TimeSpan (type only; unused)
+
+//BEGIN Drawing loop
+// Temporary draw function
+void draw (TimeSpan) {
+    glClear(GL_COLOR_BUFFER_BIT);
+    
+    gui.draw ();
+    
+    glFlush();
+    SDL_GL_SwapBuffers();
+}
+//END Drawing loop
--- a/mde/gui/IWindow.d	Mon Apr 28 10:59:47 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +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/>. */
-
-/// Window interface (used by Widgets).
-module mde.gui.IWindow;
-
-import mde.gui.Widget;
-
-interface IWindow
-{
-    /** Widget ID type. Each ID is unique under this window.
-    *
-    * Type is int since this is the widget data type. */
-    alias int widgetID;
-    
-    /** Get a widget by ID.
-    *
-    * Returns the widget with the given ID from the Window's widget list. If the widget hasn't yet
-    * been created, creates it using the Window's widget creation data (throws on error; don't
-    * catch the exception). */
-    IWidget getWidget (widgetID i);
-    
-    /** Called by a sub-widget when a redraw is necessary (since drawing may sometimes be done on
-    * event. */
-    void requestRedraw ();
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/Ifaces.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,91 @@
+/* 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/>. */
+
+/** Window and widget interfaces. */
+module mde.gui.Ifaces;
+
+public import mde.gui.decoration;
+
+/** Interface for Window, allowing widgets to call some of Window's methods.
+*
+* Contains the methods in Window available for widgets to call on their root. */
+interface IWindow : IParentWidget
+{
+    /** Widget ID type. Each ID is unique under this window.
+    *
+    * Type is int since this is the widget data type. */
+    alias int widgetID;
+    
+    /** Get a widget by ID.
+    *
+    * Returns the widget with the given ID from the Window's widget list. If the widget hasn't yet
+    * been created, creates it using the Window's widget creation data (throws on error; don't
+    * catch the exception). */
+    IWidget getWidget (widgetID i, IParentWidget parent);
+    /+ Currently draw-on-event isn't used.
+    /** Called by a sub-widget when a redraw is necessary (since drawing may sometimes be done on
+    * event. */
+    void requestRedraw ();+/
+}
+
+/** Methods exposed by both Window and Widgets which widgets can call on a parent. */
+interface IParentWidget
+{
+    /// Return the parent's decoration
+    WidgetDecoration decoration ();
+}
+
+/** Interface for widgets.
+*
+* Note that Window also implements this interface so that widgets can interact with their parent in
+* a uniform way.
+*
+* A widget is a region of a GUI window which handles rendering and user-interaction for itself
+* and is able to communicate with it's window and parent/child widgets as necessary.
+*
+* A widget's constructor should have this prototype:
+* ----------------------------------
+* this (IWindow window, IParentWidget parent, int[] data);
+* ----------------------------------
+* Where window is the root window (the window to which the widget belongs), parent is the parent
+* widget, and data is an array of initialisation data. The method should throw a
+* WidgetDataException (created without parameters) if the data has wrong length or is otherwise
+* invalid. */
+interface IWidget : IParentWidget
+{
+    /** Draw, starting from given x and y.
+     *
+     * Maybe later enforce clipping of all sub-widget drawing, particularly for cases where only
+     * part of the widget is visible: scroll bars or a hidden window. */
+    void draw (int x, int y);
+    
+    /** Receive a mouse click event.
+     *
+     * See mde.input.input.Input.MouseClickCallback for parameters. However, cx and cy are adjusted
+     * to the Widget's local coordinates.
+     *
+     * Widget may assume coordinates are on the widget (caller must check). */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state);
+    
+    /** Calculate the minimum size the widget could be shrunk to, taking into account
+     * child-widgets. */
+    void getMinimumSize (out int w, out int h);
+    
+    /** Get the current size of the widget.
+     *
+     * On the first call (during loading), this may be a value saved as part of the config or
+     * something else (e.g. revert to getMinimumSize). */
+    void getCurrentSize (out int w, out int h);
+}
--- a/mde/gui/Widget.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/gui/Widget.d	Tue Apr 29 18:10:58 2008 +0100
@@ -16,136 +16,119 @@
 /// GUI Widget module.
 module mde.gui.Widget;
 
-import mde.gui.IWindow;
+import mde.gui.Ifaces;
 import mde.gui.exception;
 
-import gl = mde.gl;
-import mde.scheduler.InitFunctions;
+import gl = mde.gl.basic;
+
+import tango.io.Stdout;
 
-//BEGIN Iface and createWidget
-/** Interface for widgets (may become a base class).
-*
-* A widget is a region of a GUI window which handles rendering and user-interaction for itself
-* and is able to communicate with it's window and parent/child widgets as necessary.
-*
-* A widget's constructor should have this prototype:
-* ----------------------------------
-* this (IWindow window, int[] data);
-* ----------------------------------
-* Where window is the parent window and data is an array of initialisation data. The method should
-* throw a WidgetDataException (created without parameters) if the data has wrong length or is
-* otherwise invalid.
-*/
-//FIXME: check code reuse later!
-interface IWidget
-{
-    /** Draw, starting from given x and y.
-    *
-    * Maybe later enforce clipping of all sub-widget drawing, particularly for cases where only
-    * part of the widget is visible: scroll bars or a hidden window. */
-    void draw (int x, int y);
-    
-    /** Calculate the minimum size the widget could be shrunk to, taking into account
-    * child-widgets. */
-    void getMinimumSize (out int w, out int h);
-    
-    /** Get the current size of the widget.
-    *
-    * On the first call (during loading), this may be a value saved as part of the config or
-    * something else (e.g. revert to getMinimumSize). */
-    void getCurrentSize (out int w, out int h);
-}
-
+//BEGIN createWidget
 /// Widget types. Start high so they can be reordered easily later.
-enum WIDGET_TYPES : int {
-    BOX = 1001, GRID
+enum WIDGET_TYPE : int {
+    BOX = 1001, GRID, BUTTON
 }
 
 /** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
 * data [1..$]. */
-IWidget createWidget (IWindow window, int[] data) {
+IWidget createWidget (IWindow window, IParentWidget parent, int[] data)
+in {
+    assert (window !is null, "createWidget: window is null");
+    assert (parent !is null, "createWidget: parent is null");
+} body {
     if (data.length < 1) throw new WidgetDataException ("No widget data");
     int type = data[0];     // type is first element of data
     data = data[1..$];      // the rest is passed to the Widget
     
-    if (type == WIDGET_TYPES.BOX) return new BoxWidget (window, data);
-    else if (type == WIDGET_TYPES.GRID) return new GridWidget (window, data);
+    if (type == WIDGET_TYPE.BOX) return new BoxWidget (window, parent, data);
+    else if (type == WIDGET_TYPE.GRID) return new GridWidget (window, parent, data);
+    else if (type == WIDGET_TYPE.BUTTON) return new ButtonWidget (window, parent, data);
     else throw new WidgetDataException ("Bad widget type");
 }
-//END Iface and createWidget
+//END createWidget
 
-//BEGIN Widgets
-/// Draws a box. That's it.
-class BoxWidget : IWidget
+/** A base widget class. Widgets need not inherit this (they only need implement IWidget), but this
+* class provides a useful basic implementation for widgets.
+*
+* Technically this class could be instantiated, but it wouldn't do anything, not even draw itself.
+*/
+class Widget : IWidget
 {
-    int w, h;   // size
-    
-    this (IWindow, int[] data) {
-        if (data.length != 2) throw new WidgetDataException;
-        
-        w = data[0];
-        h = data[1];
+    /** Basic draw method: draw the background */
+    void draw (int x, int y) {
+        deco.setColor();
+        gl.drawBox (x,y, w,h);
     }
     
-    void draw (int x, int y) {
-        gl.setColor (1.0f, 1.0f, 0.0f);
-        gl.drawBox (x,x+w, y,y+h);
-    }
+    /** Dummy event method (ignore) */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
     
-    void getMinimumSize (out int w, out int h) {
-        w = h = 0;  // box has no content
-    }
+    /** Minimum size is zero. */
+    void getMinimumSize (out int w, out int h) {}   // w,h initialised to 0
+    /** Current size. */
     void getCurrentSize (out int w, out int h) {
         w = this.w;
         h = this.h;
     }
+    
+    WidgetDecoration decoration () {
+        return deco;
+    }
+    
+protected:
+    WidgetDecoration deco;  // the widget's decoration
+    int w, h;               // size
+}
+
+//BEGIN Widgets
+/// Draws a box. That's it.
+class BoxWidget : Widget
+{
+    this (IWindow, IParentWidget parent, int[] data) {
+        if (data.length != 2) throw new WidgetDataException;
+        
+        deco = new WidgetDecoration (parent.decoration);
+        
+        w = data[0] + 2*deco.border;
+        h = data[1] + 2*deco.border;
+    }
 }
 
 /// Encapsulates a grid of Widgets
-class GridWidget : IWidget
+class GridWidget : Widget
 {
-    //NOTE: maybe remove the padding and have each widget include a border? Or vice-versa (no borders on widgets)?
-    const PADDING = 3;  // padding between rows/cols
-    const BORDER = 8;   // border width
-    int w, h;           // size
-    int rows, cols;     // number of cells in grid
-    int[] rowH;         // row height (highest widget in the row)
-    int[] colW;         // column width (widest widget)
-    int[] rowY;         // cumulative rowH[i-1] + BORDER/PADDING
-    int[] colX;         // cumulative colW[i-1] + BORDER/PADDING
-    IWidget[] subWidgets;   // all widgets in the grid (by row):
-    /* SubWidget order:    [ 2 3 ]
-    *                      [ 0 1 ] */
-    
-    this (IWindow window, int[] data) {
+    this (IWindow window, IParentWidget parent, int[] data) {
         // Get grid size
         if (data.length < 2) throw new WidgetDataException;
         rows = data[0];
         cols = data[1];
         
+        deco = new WidgetDecoration (parent.decoration, TypeFlags.LAYOUT);
+        
         // Get all sub-widgets
+        // Check: correct data length and rows*cols >= 0 (know data.length - 2 >= 0).
         if (data.length != 2 + rows * cols) throw new WidgetDataException;
         subWidgets.length = rows*cols;
         foreach (i, inout subWidget; subWidgets) {
-            subWidget = window.getWidget (data[i+2]);
+            subWidget = window.getWidget (data[i+2], this);
         }
         
         getMinimumSize (w,h);   // Calculate the size (current size is not saved)
     }
     
     void draw (int x, int y) {
-        gl.setColor (1.0f, 0.6f, 0.0f);
-        gl.drawBox (x,x+w, y,y+h);
+        deco.setColor;
+        gl.drawBox (x,y, w,h);
         
         foreach (i,widget; subWidgets) {
             widget.draw (x + colX[i % cols], y + rowY[i / cols]);
         }
     }
     
-    /** Also recalculates row/column widths. */
+    // Calculates from all rows and columns of widgets.
     void getMinimumSize (out int w, out int h) {
         if (rows*cols == 0) {    // special case
-            w = h = 2*BORDER;
+            w = h = 2*deco.border;
             return;
         }
         
@@ -156,7 +139,7 @@
         
         // Find row heights and column widths (non cumulative)
         rowH.length = rows;
-        colW.length = cols;
+        colW.length = cols; //WARNING: code reliant on these being initialised to zero
         for (uint i = 0; i < subWidgets.length; ++i) {
             uint x = i / cols;  // row
             if (rowH[x] < widgetH[i]) rowH[x] = widgetH[i];
@@ -167,37 +150,73 @@
         // rowY / colX
         rowY.length = rows;
         colX.length = cols;
-        int cum = BORDER;
+        int cum = deco.border;
         foreach (i, x; rowH) {
             rowY[i] = cum;
             cum += x + PADDING;
         }
-        h = cum + BORDER - PADDING;     // total height
-        cum = BORDER;
+        h = cum + deco.border - PADDING;     // total height
+        cum = deco.border;
         foreach (i, x; colW) {
             colX[i] = cum;
             cum += x + PADDING;
         }
-        w = cum + BORDER - PADDING;     // total width
+        w = cum + deco.border - PADDING;     // total width
     }
-    void getCurrentSize (out int wC, out int hC) {
-        wC = w;
-        hC = h;
+    
+    // Pass event on to relevant widget. Simply return if not on a widget.
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+        if (rows*cols == 0) return; // special case
+        
+        // Find the column
+        int i = cols - 1;          // starting from right...
+        while (cx < colX[i]) {      // decrement while left of this column
+            if (i == 0) return;     // left of first column
+            --i;
+        }                           // now (cx >= colX[i])
+        if (cx >= colX[i] + colW[i]) return;    // between columns
+        
+        // Find the row;
+        int j = rows - 1;
+        while (cy < rowY[j]) {
+            if (j == 0) return;
+            --j;
+        }
+        if (cy >= rowY[j] + rowH[j]) return;
+        
+        // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell)
+        cx -= colX[i];
+        cy -= rowY[j];
+        IWidget widg = subWidgets[i + j*cols];
+        widg.getCurrentSize (i,j);
+        if (cx < i && cy < j)
+            widg.clickEvent (cx, cy, b, state);
     }
+    
+protected:
+    const PADDING = 4;  // padding between rows/cols
+    int rows, cols;     // number of cells in grid
+    int[] rowH;         // row height (highest widget in the row)
+    int[] colW;         // column width (widest widget)
+    int[] rowY;         // cumulative rowH[i-1] + border and padding
+    int[] colX;         // cumulative colW[i-1] + border and padding
+    IWidget[] subWidgets;   // all widgets in the grid (by row):
+    /* SubWidget order:    [ 2 3 ]
+    *                      [ 0 1 ] */
 }
-/+ On hold until after next commit
+
 /// First interactible widget
-class ButtonWidget : IWidget
+class ButtonWidget : Widget
 {
-    const BORDER = 5;   // border width
-    int w, h;           // size
     bool pushed = false;// true if button is pushed in
     
-    this (IWindow, int[] data) {
+    this (IWindow, IParentWidget parent, int[] data) {
         if (data.length != 2) throw new WidgetDataException;
         
-        w = data[0] + 2*BORDER;
-        h = data[1] + 2*BORDER;
+        deco = new WidgetDecoration (parent.decoration);
+        
+        w = data[0] + 2*deco.border;
+        h = data[1] + 2*deco.border;
     }
     
     void draw (int x, int y) {
@@ -205,18 +224,16 @@
             gl.setColor (1f, 0f, 1f);
         else
             gl.setColor (.6f, 0f, .6f);
-        gl.drawBox (x,x+w, y,y+h);
+        gl.drawBox (x,y, w,h);
     }
     
     void getMinimumSize (out int w, out int h) {
         w = this.w; // button is not resizable
         h = this.h;
     }
-    void getCurrentSize (out int w, out int h) {
-        w = this.w;
-        h = this.h;
+    
+    void clickEvent (ushort, ushort, ubyte b, bool state) {
+        if (b == 1) pushed = state; // very basic
     }
-    
 }
-+/
 //END Widgets
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/Window.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,144 @@
+/* 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/>. */
+
+/** The Window class. */
+module mde.gui.Window;
+
+import mde.gui.Ifaces;
+import mde.gui.Widget;
+import mde.gui.exception;
+
+import mt = mde.mergetag.DataSet;
+import tango.scrapple.text.convert.parseTo : parseTo;
+// not yet implemented:
+//import tango.scrapple.text.convert.parseFrom : parseFrom;
+
+/** GUI Window class
+ *
+ * A window class instance does two things: (1) specify a region of the screen upon which the window
+ * and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets.
+ *
+ * Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when
+ * created, be given its int[] of data, which this() must confirm is valid (or throw).
+ */
+class Window : mt.IDataSection, IWindow
+{
+    //BEGIN Methods for GUI
+    /** Call after loading is finished to setup the window and confirm that it's valid.
+     *
+     * Throws: WindowLoadException. Do not use the instance in this case! */
+    void finalise () {
+        // Check data was loaded:
+        if (widgetData is null) throw new WindowLoadException ("No widget data");
+        
+        // Create the decoration:
+        deco = new WidgetDecoration;
+        
+        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
+        widget = getWidget (0, this);// primary widget always has ID 0.
+        
+        widgetData = null;          // data is no longer needed: allow GC to collect (cannot safely delete)
+        
+        widgetX = x + deco.border;  // widget position
+        widgetY = y + deco.border;  // must be updated if the window is moved
+        
+        widget.getCurrentSize (w,h);// Find the initial size
+        w += deco.border * 2;       // Adjust for border
+        h += deco.border * 2;
+        
+        xw = x+w;
+        yh = y+h;
+    }
+    
+    void draw () {
+        // background
+        deco.setColor;
+        gl.drawBox (x,y, w,h);
+        
+        // Tell the widget to draw itself:
+        widget.draw(widgetX, widgetY);
+    }
+    
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+        if (cx >= x && cx < xw && cy >= y && cy < yh) { // click on window?
+            if (cx >= widgetX && cx < xw-deco.border && cy >= widgetY && cy < yh-deco.border)   // click on widget?
+                widget.clickEvent (cx-widgetX, cy-widgetY, b, state);
+            // FIXME: else window dragging?
+        }
+    }
+    
+    //BEGIN Mergetag code
+    void addTag (char[] tp, mt.ID id, char[] dt) {
+        if (tp == "int[][int]") {
+            if (id == "widgetData") {
+                widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt);
+            }
+        } else if (tp == "int") {
+            if (id == "x") {
+                x = parseTo!(int) (dt);
+            } else if (id == "y") {
+                y = parseTo!(int) (dt);
+            }
+        }
+    }
+    void writeAll (ItemDelg dlg) {
+    }
+    //END Mergetag code
+    //END Methods for GUI
+    
+    //BEGIN IWindow methods
+    /** Get/create a widget by ID.
+     *
+     * Should $(I only) be called internally and by sub-widgets! */
+    IWidget getWidget (widgetID i, IParentWidget parent)
+    in {
+        // widgetData is normally left to be garbage collected after widgets have been created:
+        assert (widgetData !is null, "getWidget: widgetData is null");
+    } body {
+        // See if it's already been created:
+        IWidget* p = i in widgets;
+        if (p !is null) return *p;  // yes
+        else {                      // no
+            int[]* d = i in widgetData;
+            if (d is null) throw new WindowLoadException ("Widget not found");
+            
+            // Throws WidgetDataException (a WindowLoadException) if bad data:
+            IWidget widg = createWidget (this, parent, *d);
+            widgets[i] = widg;
+            return widg;
+        }
+    }
+    
+    /+void requestRedraw () {
+    }+/
+    //END IWindow methods
+    
+    //BEGIN IParentWidget methods
+    WidgetDecoration decoration () {
+        return deco;
+    }
+    //END IParentWidget methods
+    
+private:
+    int[][widgetID] widgetData;     // Data for all widgets under this window (deleted after loading)
+    IWidget[widgetID] widgets;      // List of all widgets under this window (created on demand).
+    IWidget widget;                 // The primary widget in this window.
+    
+    WidgetDecoration deco;          // the window's decoration
+    int x,y;                        // Window position
+    int w,h;                        // Window size (calculated from Widgets)
+    int xw, yh;                     // x+w, y+h (frequent use by clickEvent)
+    int widgetX, widgetY;           // Widget position (= window position plus BORDER_WIDTH)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/decoration.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,70 @@
+/* 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/>. */
+
+/** Widget decoration / rendering code. */
+module mde.gui.decoration;
+
+import gl = mde.gl.basic;
+
+enum TypeFlags
+{
+    NONE = 0,
+    LAYOUT = 0x10,
+    BUTTON = 0x20
+}
+
+/** An attempt at unifying and generally improving apon the looks of Widgets.
+*
+* Probably only a stop-gap measure until something better turns up...
+* Although this could be extended to do the actual rendering, etc.? */
+class WidgetDecoration {
+    /** Create a decoration for a window. */
+    this () {
+        border = 20;
+        r = g = 0.0f;
+        b = 1.0f;
+    }
+    
+    /** Create an appropriate decoration for a Widget. Pass parent. */
+    this (WidgetDecoration parent, TypeFlags flags = TypeFlags.NONE)
+    in {
+        assert (parent !is null, "WidgetDecoration: parent is null");
+    } body {
+        //depth = parent.depth + 1;
+        
+        if (flags & TypeFlags.LAYOUT) {
+            if (!(parent.flags & TypeFlags.LAYOUT)) border = 4;
+            r = g = b = 1f;
+        } else if (flags & TypeFlags.BUTTON) {
+            r = b = .6f;
+            g = 0f;
+        } else {
+            r = 1.0f;
+            g = b = 0f; //cast(float) depth % 2;
+        }
+        this.flags = flags;
+    }
+    
+    void setColor () {
+        gl.setColor (r,g,b);
+    }
+    
+final:
+    int border;     // width of border
+private:
+    float r,g,b;    // colour
+    //int depth;      // Window's WidgetDecoration has depth 0; each generation is one step deeper
+    TypeFlags flags;// used by children
+}
--- a/mde/gui/gui.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/gui/gui.d	Tue Apr 29 18:10:58 2008 +0100
@@ -13,11 +13,12 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/// Base GUI module.
+/** The GUI struct and Window class.
+*
+* Possibly add a GUIManager to update all active GUIs and pass coordinates (remapping if necessary). */
 module mde.gui.gui;
 
-import mde.gui.IWindow;
-import mde.gui.Widget;
+import mde.gui.Window;
 import mde.gui.exception;
 
 import mt = mde.mergetag.DataSet;
@@ -25,32 +26,23 @@
 import mde.mergetag.Reader;
 
 import mde.resource.paths;
-import mde.scheduler.InitFunctions;
-
-import tango.scrapple.text.convert.parseTo : parseTo;
-import tango.scrapple.text.convert.parseFrom : parseFrom;
 
 import tango.util.log.Log : Log, Logger;
 
 private Logger logger;
 static this () {
     logger = Log.getLogger ("mde.gui.gui");
-    
-    init.addFunc (&loadGUI, "loadGUI");
 }
 
 GUI gui;    // Currently just one instance; handle differently later.
-// Wrap gui.load, since init doesn't handle delegates
-// (do it this way since GUI handling will eventually be changed)
-void loadGUI () {
-    gui.load();
-}
+// Handle externally or with a GUI Manager?
 
 /** A GUI handles a bunch of windows, all to be drawn to the same device. */
+/* NOTE: currently GUI just keeps a list of windows and draw and clickEvent simply calls them all.
+* Coords should be stored (and functionality like z-order?). */
 struct GUI {
     /** Load all windows from the file gui. */
-    void load() {
-        static const fileName = "gui";
+    void load(char[] fileName) {
         if (!confDir.exists (fileName)) {
             logger.error ("Unable to load GUI: no config file!");
             return; // not a fatal error (so long as the game can run without a GUI!)
@@ -80,12 +72,9 @@
                 continue;
             }
             try {
-                //logger.trace ("1");
-                int x;
                 w.finalise();
-                x = 6;
                 windows ~= w;       // only add if load successful
-            } catch (WindowLoadException e) {
+            } catch (Exception e) {
                 logger.error ("Window failed to load: " ~ e.msg);
             }
         }
@@ -100,103 +89,15 @@
             w.draw();
     }
     
+    /** Send an input event.
+    *
+    * I.e. send all mouse click events to all active GUIs, which check the coordinates and forward
+    * to any relevent windows. */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+        foreach (w; windows)
+            w.clickEvent (cx,cy,b,state);
+    }
+    
     private:
     Window[] windows;
 }
-
-package:    // Nothing else here is for external use.
-
-/** GUI Window class
-*
-* A window class instance does two things: (1) specify a region of the screen upon which the window
-* and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets.
-*
-* Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when
-* created, be given its int[] of data, which this() must confirm is valid (or throw).
-*/
-class Window : mt.IDataSection, IWindow
-{
-    alias int widgetID;     // Widget ID type. Each ID is unique under this window. Type is int since this is the widget data type.
-    
-    /** Call after loading is finished to setup the window and confirm that it's valid.
-    *
-    * Throws: WindowLoadException. Do not use the instance in this case! */
-    void finalise () {
-        // Check data was loaded:
-        if (widgetData is null) throw new WindowLoadException ("No widget data");
-        
-        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
-        widget = getWidget (0);     // primary widget always has ID 0.
-        
-        widgetData = null;          // data is no longer needed: allow GC to collect (cannot safely delete)
-        
-        widget.getCurrentSize (w,h);// Find the initial size
-        w += BORDER_WIDTH * 2;      // Adjust for border
-        h += BORDER_WIDTH * 2;
-    }
-    
-    /** Get/create a widget by ID.
-    *
-    * Should $(I only) be called internally and by sub-widgets! */
-    IWidget getWidget (widgetID i)
-    in {
-        // widgetData is normally left to be garbage collected after widgets have been created:
-        assert (widgetData !is null, "getWidget: widgetData is null");
-    } body {
-        // See if it's already been created:
-        IWidget* p = i in widgets;
-        if (p !is null) return *p;  // yes
-        else {                      // no
-            int[]* d = i in widgetData;
-            if (d is null) throw new WindowLoadException ("Widget not found");
-            
-            // Throws WidgetDataException (a WindowLoadException) if bad data:
-            IWidget w = createWidget (this, *d);
-            widgets[i] = w;
-            return w;
-        }
-    }
-    
-    void requestRedraw () {
-    //FIXME
-    }
-    
-    void draw () {
-        //BEGIN Window border/back
-        gl.setColor (0.0f, 0.0f, 0.5f);
-        gl.drawBox (x,x+w, y,y+h);
-        //END Window border/back
-        
-        // Tell the widget to draw itself:
-        widget.draw(x + BORDER_WIDTH, y + BORDER_WIDTH);
-    }
-    
-    //BEGIN Mergetag code
-    void addTag (char[] tp, mt.ID id, char[] dt) {
-        if (tp == "int[][int]") {
-            if (id == "widgetData") {
-                widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt);
-            }
-        } else if (tp == "int") {
-            if (id == "x") {
-                x = parseTo!(int) (dt);
-            } else if (id == "y") {
-                y = parseTo!(int) (dt);
-            }
-        }
-    }
-    void writeAll (ItemDelg dlg) {
-    }
-    //END Mergetag code
-    
-    private:
-    int[][widgetID] widgetData;     // Data for all widgets under this window (deleted after loading)
-    IWidget[widgetID] widgets;      // List of all widgets under this window (created on demand).
-    
-    IWidget widget;                 // The primary widget in this window.
-    int x,y;                        // Window position
-    int w,h;                        // Window size (calculated from Widgets)
-    
-    const BORDER_WIDTH = 8;         // Temporary way to handle window decorations
-
-}
--- a/mde/input/input.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/input/input.d	Tue Apr 29 18:10:58 2008 +0100
@@ -148,17 +148,21 @@
         *
         * Mouse events don't need config for the GUI. Handle them first so that if no config exists
         * some functionality at least is retained.
+        *
+        * Note that the mouse coordinates as reported by SDL put the top-left most pixel at 1,1.
+        * Internal coordinates put that pixel at 0,0 (see gui/GUI notes.txt).
         */
         switch (event.type) {
             case SDL_MOUSEBUTTONDOWN:
             case SDL_MOUSEBUTTONUP:
                 foreach (dg; mouseClickCallbacks)
-                    dg (event.button.x, event.button.y, event.button.button, event.button.state == SDL_PRESSED);
+                    dg (event.button.x - 1, event.button.y - 1,
+                        event.button.button, event.button.state == SDL_PRESSED);
                 break;
             
             case SDL_MOUSEMOTION:
-                mouse_x = event.motion.x;
-                mouse_y = event.motion.y;
+                mouse_x = event.motion.x - 1;
+                mouse_y = event.motion.y - 1;
                 break;
             
             default:
@@ -292,17 +296,17 @@
     
     /** Loads all configs, activating the requested id.
     *
-    * Returns: true if the requested config id wasn't found.
+    * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
+    *   found.
     */
-    bool loadConfig (char[] profile = "Default") {
+    void loadConfig (char[] profile = "Default") {
         Config.load("input");	// FIXME: filename
         Config* c_p = profile in Config.configs;
-        if (c_p) {
-            config = *c_p;
-            return false;
+        if (c_p) config = *c_p;
+        else {
+            throw new ConfigLoadException;
+            logger.error ("Config profile \""~profile~"\" not found: input won't work unless a valid profile is loaded!");
         }
-        logger.error ("Config profile \""~profile~"\" not found: input won't work unless a valid profile is loaded!");
-        return true;
     }
     
 private:
--- a/mde/input/joystick.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/input/joystick.d	Tue Apr 29 18:10:58 2008 +0100
@@ -53,6 +53,8 @@
     foreach (js; joysticks) {
         // FIXME: this is sometimes causing a SIGSEGV (Address boundary error)
         // FIXME: when init fails
+        debug logger.trace ("Closing joysticks (this sometimes fails when mde exits prematurely)");
         if(js !is null) SDL_JoystickClose(js);	// only close if successfully opened
+        debug logger.trace ("Done closing joysticks");
     }
 }
--- a/mde/mde.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/mde.d	Tue Apr 29 18:10:58 2008 +0100
@@ -22,7 +22,7 @@
 // Comment to show use, where only used minimally:
 
 import global = mde.global;             // global.run
-import gl = mde.gl;                     // gl.draw
+import gl = mde.gl.draw;                // gl.draw()
 import mde.events;                      // pollEvents
 
 import mde.scheduler.Init;
--- a/mde/scheduler/Init.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/scheduler/Init.d	Tue Apr 29 18:10:58 2008 +0100
@@ -22,6 +22,7 @@
  *************************************************************************************************/
 module mde.scheduler.Init;
 
+import mde.scheduler.Init2;     // This module is responsible for setting up some init functions.
 import mde.scheduler.InitFunctions;
 import mde.scheduler.exception;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/Init2.d	Tue Apr 29 18:10:58 2008 +0100
@@ -0,0 +1,89 @@
+/* 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/>. */
+
+/** This module is the start of implementing the following:
+*
+* Idea: change import direction so this module adds all init functions. All init functions are
+* wrapped in another function before being run in a thread (i.e. run indirectly). Functions fail
+* either by throwing an exception or by returning a boolean. Functions may take parameters, e.g.
+* "out cleanupFunc[]".
+*
+* This should make it much easier to tell what actually happens during init and to order init such
+* that dependencies are honoured.
+*
+* Currently some external modules depend on InitFunctions, while some are set up from here. Once
+* all are set up from here, the Init* modules can be rearranged. */
+module mde.scheduler.Init2;
+
+import mde.scheduler.InitFunctions;
+
+import tango.util.log.Log : Log, Logger;
+
+// Modules requiring init code running:
+import global = mde.global;
+import mde.gui.gui;
+import mde.input.input;
+
+// NOTE: error reporting needs revision
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.scheduler.Init2");
+    
+    init.addFunc (&initInput, "initInput");
+    init.addFunc (&guiLoad, "guiLoad");
+}
+
+void guiLoad () {
+    try {
+        gui.load ("gui");
+    } catch (Exception e) {
+        logger.fatal ("guiLoad failed: " ~ e.msg);
+        setInitFailure;
+    }
+}
+
+void initInput () { // init func
+    try {
+        global.input = new Input();
+        global.input.loadConfig ();         // (may also create instance)
+        
+        // Quit on escape. NOTE: quit via SDL_QUIT event is handled completely independently!
+        global.input.addButtonCallback (cast(Input.inputID) 0x0u, delegate void(Input.inputID i, bool b) {
+            if (b) {
+                logger.info ("Quiting...");
+                global.run = false;
+            }
+        } );
+        global.input.addMouseClickCallback(&gui.clickEvent);
+    } catch (Exception e) {
+        logger.fatal ("initInput failed: " ~ e.msg);
+        setInitFailure;
+    }
+}
+
+/+ Potential wrapper function:
+// Template to call function, catching exceptions:
+void wrap(alias Func) () {
+    try {
+        Func();
+    } catch (Exception e) {
+        logger.fatal (FAIL_MSG);
+        logger.fatal (e.msg);
+        setInitFailure;
+    }
+}
+private const FAIL_MSG = "Unexpected exception caught:";
++/
--- a/mde/scheduler/InitFunctions.d	Mon Apr 28 10:59:47 2008 +0100
+++ b/mde/scheduler/InitFunctions.d	Tue Apr 29 18:10:58 2008 +0100
@@ -23,10 +23,11 @@
 * "out cleanupFunc[]". */
 module mde.scheduler.InitFunctions;
 
+/+ unused
 import tango.util.log.Log : Log, Logger;
 static this() {
     logger = Log.getLogger ("mde.scheduler.InitFunctions");
-}
+}+/
 
 void setInitFailure () {    /// Call to indicate failure in an init function
     initFailure = true;
@@ -36,7 +37,7 @@
 struct InitStage
 {
     struct InitFunction {
-        void function() func;       // the actual function
+        void delegate() func;       // the actual function
         char[] name;                // it's name;
     }
     
@@ -47,12 +48,18 @@
     * Exceptions should never be thrown, since each function may run as a thread, and catching
     * thread exceptions is not guaranteed to work. Log a message, call setFailure() and return
     * instead. */
-    void addFunc (void function() f, char[] name) {
+    void addFunc (void delegate() f, char[] name) {
         InitFunction s;
         s.func = f;
         s.name = name;
         funcs ~= s;
     }
+    void addFunc (void function() f, char[] name) { /// ditto
+        InitFunction s;
+        s.func.funcptr = f;
+        s.name = name;
+        funcs ~= s;
+    }
     
     InitFunction[] funcs = [];
 }
@@ -64,21 +71,4 @@
 bool initFailure = false;   // set on failure (throwing through threads isn't a good idea)
 
 private:
-Logger logger;
-/+ I keep changing my mind about wrapping all init functions:
-const LOG_MSG = "Init function ";
-const TRACE_START = " - running";
-const TRACE_END = " - completed";
-const FAIL_MSG = " - failed: ";
-// Template to call function, catching exceptions:
-void initInput(alias Func, char[] name) () {
-    try {
-        debug logger.trace (LOG_MSG ~ name ~ TRACE_START);
-        Func();
-        debug logger.trace (LOG_MSG ~ name ~ TRACE_END);
-    } catch (Exception e) {
-        logger.fatal (LOG_MSG ~ name ~ FAIL_MSG ~ e.msg);
-        initFailure = true;
-    }
-}
-+/
+//Logger logger;