changeset 32:316b0230a849

Lots more work on the GUI. Also renamed lots of files. Lots of changes to the GUI. Renderer is now used exclusively for rendering and WidgetDecoration is gone. Renamed lots of files to conform to case policies. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 30 Apr 2008 18:05:56 +0100
parents baa87e68d7dc
children 6886402c1545
files codeDoc/jobs.txt codeDoc/policies.txt data/conf/gui.mtt mde/Options.d mde/SDL.d mde/events.d mde/gl/draw.d mde/global.d mde/gui/Gui.d mde/gui/IGui.d mde/gui/Ifaces.d mde/gui/Widget.d mde/gui/Window.d mde/gui/decoration.d mde/gui/gui.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/renderer/createRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/Widget.d mde/gui/widget/Window.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/input/Config.d mde/input/Input.d mde/input/config.d mde/input/input.d mde/options.d mde/scheduler/Init.d mde/scheduler/Init2.d mde/scheduler/InitFunctions.d mde/scheduler/init2.d mde/scheduler/initFunctions.d mde/sdl.d
diffstat 34 files changed, 2339 insertions(+), 2159 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Tue Apr 29 18:10:58 2008 +0100
+++ b/codeDoc/jobs.txt	Wed Apr 30 18:05:56 2008 +0100
@@ -48,9 +48,5 @@
 
 
 Done (for git log message):
-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).
+Lots of changes to the GUI. Renderer is now used exclusively for rendering and WidgetDecoration is gone.
+Renamed lots of files to conform to case policies.
\ No newline at end of file
--- a/codeDoc/policies.txt	Tue Apr 29 18:10:58 2008 +0100
+++ b/codeDoc/policies.txt	Wed Apr 30 18:05:56 2008 +0100
@@ -53,9 +53,9 @@
 
 Long lines: Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code so just try to keep it neat.
 
-Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not underscores. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Use CAPS for consts (with underscores as word separators), Capitalisation for class/interface/struct/union names, smallLetters for variables and class instances.
+Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not underscores. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Use CAPS for consts (with underscores as word separators), Capitalisation for class/interface/struct/union names, smallLetters for variables and class instances. Don't capitalise acronyms like SDL; instead use capitalisation like outlined above (personally I hate doing so but it does make things clearer).
 
-Module/file names: If the module corresponds directly to a single class, use the class name with correct Capitalisation as the module name. Otherwise, preferably use lower case. Always use the correct case when importing a module to keep code portable.
+Module/file names: If the module corresponds directly to a class (possibly also with some associated classes), use the class name with correct Capitalisation as the module name. Otherwise, use lower case. Always use the correct case when importing a module to keep code portable.
 
 Spelling (within code): I am not going to stipulate British/American/other spellings, but keep to good English and use some consistancy (e.g. don't use both "defense" and "instance" together, unless unavoidable due to existing code).
 
@@ -77,9 +77,15 @@
 --- Package design principle ---
 Use a separate package for each module of the engine. In most packages where there is only one module (file) imported by other parts of the engine, that module should have the same name as the package and be designed to have a standardised interface to the package so that the package could be replaced with another as a drop-in replacement (written with the same interface). Of course in many cases it may not be possible to swich one package for another quite this easily, but holding to this principle should at least minimise the amount of work necessary when doing so.
 
-Module imports: don't use public imports much, although for importing interfaces it may make sense. Use static/renamed/selective imports when importing a module containing a lot of module-level symbols unless it is a very closely related module.
+
+Module imports:
+Only publically import module A into module B if you can be fairly sure that any module C importing module B will also need module A.
 
-Chain dependancies: If at all possible, avoid chain dependancies (e.g. use interfaces). Chain dependancies can cause many problems.
+Try to make it obvious which module the symbols you use come from. Sometimes using static or renamed imports is the best way to do this. Sometimes selective imports also help avoid confusion.
+
+Where it is not obvious, show what the import is used for with a comment or selective import showing which symbols are used.
+
+Chain dependancies: Avoid chain dependancies between modules (e.g. use interfaces). Chain dependancies can cause many problems.
 
 
 
--- a/data/conf/gui.mtt	Tue Apr 29 18:10:58 2008 +0100
+++ b/data/conf/gui.mtt	Wed Apr 30 18:05:56 2008 +0100
@@ -1,4 +1,5 @@
 {MT01}
+<char[]|Renderer="Simple">
 {W1}
 <int|x=0>
 <int|y=0>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/Options.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,330 @@
+/* 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 handles stored options, currently all except input maps.
+*
+* The purpose of having all options centrally controlled is to allow generic handling by the GUI
+* and ease saving and loading of values. The Options class is only really designed around handling
+* small numbers of variables for now.
+*/
+module mde.Options;
+
+import mde.exception;
+
+import mde.mergetag.Reader;
+import mde.mergetag.Writer;
+import mde.mergetag.DataSet;
+import mde.mergetag.exception;
+import mde.resource.paths;
+
+import tango.scrapple.text.convert.parseTo : parseTo;
+import tango.scrapple.text.convert.parseFrom : parseFrom;
+
+import tango.core.Exception : ArrayBoundsException;
+import tango.util.log.Log : Log, Logger;
+
+/** Base class for handling options.
+*
+* This class itself handles no options and should not be instantiated, but provides a sub-classable
+* base for generic options handling. Also, the static portion of this class tracks sub-class
+* instances and provides loading and saving methods.
+*
+* Each sub-class provides named variables for maximal-speed reading. Local sub-class references
+* should be used for reading variables, and via the addOptionsClass() hook will be loaded from
+* files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
+* not be saved; use, for example, Options.setBool(...).
+*
+* Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
+* char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
+* built-in support in Options is only for bool, int and char[] types (a float type may get added).
+* Further to this, a generic class is used to store all options which have been changed, and if any
+* have been changed, is merged with options from the user conf dir and saved on exit.
+*/
+class Options : IDataSection
+{
+    // No actual options are stored by this class. However, much of the infrastructure is
+    // present since it need not be redefined in sub-classes.
+    
+    // The "pointer lists":
+    protected bool*  [ID]   optsBool;
+    protected char[]*[ID]   optsCharA;
+    protected int*   [ID]   optsInt;
+    
+    //BEGIN Mergetag loading/saving code
+    void addTag (char[] tp, ID id, char[] dt) {
+        if (tp == "bool") {
+            bool** p = id in optsBool;
+            if (p !is null) **p = parseTo!(bool) (dt);
+        } else if (tp == "char[]") {
+            char[]** p = id in optsCharA;
+            if (p !is null) **p = parseTo!(char[]) (dt);
+        } else if (tp == "int") {
+            int** p = id in optsInt;
+            if (p !is null) **p = parseTo!(int) (dt);
+        }
+    }
+    void writeAll (ItemDelg dlg) {
+        foreach (ID id, bool*   val; optsBool)  dlg ("bool"  , id, parseFrom!(bool  ) (*val));
+        foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
+        foreach (ID id, int*    val; optsInt)   dlg ("int"   , id, parseFrom!(int   ) (*val));
+    }
+    //END Mergetag loading/saving code
+    
+    //BEGIN Static
+    /** Add an options sub-class to the list for loading and saving.
+    *
+    * Call from static this() (before Init calls load()). */
+    static void addOptionsClass (Options c, char[] i)
+    in {    // Trap a couple of potential coding errors:
+        assert (c !is null);    // Instance must be created before calling addOptionsClass
+        assert (((cast(ID) i) in subClasses) is null);  // Don't allow a silent replacement
+    } body {
+        subClasses[cast(ID) i] = c;
+        subClassChanges[cast(ID) i] = new OptionsGeneric;
+    }
+    
+    /** Set option symbol of Options class subClass to val.
+    *
+    * Due to the way options are handled generically, string IDs must be used to access the options
+    * via hash-maps, which is a little slower than direct access but necessary since the option
+    * must be changed in two separate places. */
+    private static const ERR_MSG = "Options.setXXX called with incorrect parameters!";
+    static void setBool (char[] subClass, char[] symbol, bool val) {
+        changed = true;     // something got set (don't bother checking this isn't what it already was)
+        
+        try {
+            *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val;
+            subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val);
+        } catch (ArrayBoundsException) {
+            // log and ignore:
+            logger.error (ERR_MSG);
+        }
+    }
+    static void setInt (char[] subClass, char[] symbol, int val) {
+        changed = true;     // something got set (don't bother checking this isn't what it already was)
+        
+        try {
+            *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val;
+            subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val);
+        } catch (ArrayBoundsException) {
+            // log and ignore:
+            logger.error (ERR_MSG);
+        }
+    }
+    static void setCharA (char[] subClass, char[] symbol, char[] val) {
+        changed = true;     // something got set (don't bother checking this isn't what it already was)
+        
+        try {
+            *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val;
+            subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val);
+        } catch (ArrayBoundsException) {
+            // log and ignore:
+            logger.error (ERR_MSG);
+        }
+    }
+    
+    // Track all sections for saving/loading/other generic handling.
+    static Options[ID] subClasses;
+    static OptionsGeneric[ID] subClassChanges;
+    static bool changed = false;    // any changes at all, i.e. do we need to save?
+    
+    /* Load/save options from file.
+    *
+    * If the file doesn't exist, no reading is attempted (options are left at default values).
+    */
+    private static const fileName = "options";
+    private static const MT_LOAD_EXC = "Loading options aborted:";
+    static void load () {
+        // Check it exists (if not it should still be created on exit).
+        // Don't bother checking it's not a folder, because it could still be a block or something.
+        if (!confDir.exists (fileName)) return;
+        
+        try {
+            IReader reader;
+            reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
+            reader.dataSecCreator = delegate IDataSection(ID id) {
+                /* Recognise each defined section, and return null for unrecognised sections. */
+                Options* p = id in subClasses;
+                if (p !is null) return *p;
+                else return null;
+            };
+            reader.read;
+        } catch (MTException e) {
+            logger.fatal (MT_LOAD_EXC);
+            logger.fatal (e.msg);
+            throw new optionsLoadException ("Mergetag exception (see above message)");
+        }
+    }
+    static void save () {
+        if (!changed) return;   // no changes to save
+        
+        DataSet ds = new DataSet();
+        foreach (id, sec; subClassChanges) ds.sec[id] = sec;
+        
+        // Read locally-stored options
+        try {
+            IReader reader;
+            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
+            reader.dataSecCreator = delegate IDataSection(ID id) {
+                return null;    // All recognised sections are already in the dataset.
+            };
+            reader.read;
+        } catch (MTFileIOException) {
+            // File either didn't exist or couldn't be opened.
+            // Presuming the former, this is not a problem.
+        } catch (MTException e) {
+            // Log a message and continue, overwriting the file:
+            logger.error (MT_LOAD_EXC);
+            logger.error (e.msg);
+        }
+        
+        try {
+            IWriter writer;
+            writer = confDir.makeMTWriter (fileName, ds);
+            writer.write();
+        } catch (MTException e) {
+            logger.error ("Saving options aborted! Reason:");
+            logger.error (e.msg);
+        }
+    }
+    
+    private static Logger logger;
+    static this() {
+        logger = Log.getLogger ("mde.options");
+    }
+    //END Static
+    
+    //BEGIN Templates
+    template store(A...) {
+        alias A a;
+    }
+    template decRecurse(char[] A, B...) {
+        static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B);
+        else                 const char[] decRecurse = A;
+    }
+    template decBool(A...) {
+        const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n";
+    }
+    template decCharA(A...) {
+        const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n";
+    }
+    template decInt(A...) {
+        const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n";
+    }
+    template aaRecurse(char[] A, B...) {
+        static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B);
+        else                 const char[] aaRecurse = "\""~A~"\"[]:&"~A;
+    }
+    template aaBool(A...) {
+        const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n";
+    }
+    template aaInt(A...) {
+        const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n";
+    }
+    template aaCharA(A...) {
+        const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n";
+    }
+    //END Templates
+}
+
+/* Special class to store all locally changed options, whatever the section. */
+class OptionsGeneric : Options {
+    // 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.
+    bool[] bools;
+    int[] ints;
+    char[][] strings;
+    
+    this () {}
+    
+    void setBool (ID id, bool x) {
+        bool** p = id in optsBool;
+        if (p !is null) **p = x;
+        else {
+            bools ~= x;
+            optsBool[id] = &bools[$-1];
+        }
+    }
+    void setInt (ID id, int x) {
+        int** p = id in optsInt;
+        if (p !is null) **p = x;
+        else {
+            ints ~= x;
+            optsInt[id] = &ints[$-1];
+        }
+    }
+    void setCharA (ID id, char[] x) {
+        char[]** p = id in optsCharA;
+        if (p !is null) **p = x;
+        else {
+            strings ~= x;
+            optsCharA[id] = &strings[$-1];
+        }
+    }
+    
+    //BEGIN Mergetag loading/saving code
+    // Reverse priority: only load symbols not currently existing
+    void addTag (char[] tp, ID id, char[] dt) {
+        if (tp == "bool") {
+            if ((id in optsBool) is null) {
+                bools ~= parseTo!(bool) (dt);
+                optsBool[id] = &bools[$-1];
+            }
+        } else if (tp == "char[]") {
+            if ((id in optsCharA) is null) {
+                strings ~= parseTo!(char[]) (dt);
+                optsCharA[id] = &strings[$-1];
+            }
+            char[]** p = id in optsCharA;
+            if (p !is null) **p = parseTo!(char[]) (dt);
+        } else if (tp == "int") {
+            if ((id in optsInt) is null) {
+                ints ~= parseTo!(int) (dt);
+                optsInt[id] = &ints[$-1];
+            }
+        }
+    }
+    //END Mergetag loading/saving code
+}
+
+/* NOTE: These Options classes use templates to ease inserting contents.
+*
+* Each entry has an I18nTranslation entry; see data/L10n/ClassName.mtt for descriptions.
+*
+* To create a new class, just copy and paste anywhere and adjust.
+*/
+
+/** A home for all miscellaneous options, at least for now. */
+OptionsMisc miscOpts;
+class OptionsMisc : Options {
+    alias store!("useThreads") BOOL;
+    alias store!("logLevel") INT;
+    alias store!("L10n") CHARA;
+    
+    mixin (decBool!(BOOL.a));
+    mixin (decInt!(INT.a));
+    mixin (decCharA!(CHARA.a));
+    
+    this () {
+        mixin (aaBool!(BOOL.a));
+        mixin (aaInt!(INT.a));
+        mixin (aaCharA!(CHARA.a));
+    }
+    
+    static this() {
+        miscOpts = new OptionsMisc;
+        Options.addOptionsClass (miscOpts, "misc");
+    }
+}
--- a/mde/SDL.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +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/>. */
-
-/** Just a temporary place to put SDL Init and Video stuff.
-*/
-module mde.SDL;
-
-import mde.scheduler.InitFunctions;
-import mde.input.joystick;
-import mde.options;
-import mde.gl.basic;
-import global = mde.global;
-
-import tango.util.log.Log : Log, Logger;
-import tango.stdc.stringz;
-
-import derelict.sdl.sdl;
-
-private Logger logger;
-static this() {
-    logger = Log.getLogger ("mde.SDL");
-    
-    init.addFunc (&initSdlAndGl, "initSdlAndGl");
-}
-
-private uint flags = 0;
-
-void initSdlAndGl() {   // init func
-    // Initialise SDL
-    if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
-        logger.fatal ("SDL initialisation failed:");
-        char* msg = SDL_GetError ();
-        logger.fatal (msg ? fromStringz(msg) : "no reason available");
-        
-        setInitFailure ();
-        return;
-    }
-    
-    debug logger.trace ("SDL initialised");
-    
-    // Must be called after SDL has been initialised, so cannot be a separate Init function.
-    openJoysticks ();                   // after SDL init
-    cleanup.addFunc (&cleanupSDL, "cleanupSDL");
-
-    setupWindow();
-}
-
-void setupWindow() {    // indirect init func (depends on initSdlAndGl)
-    // Window creation flags and size
-    flags = SDL_OPENGL;
-    if (vidOpts.hardware) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
-    else flags |= SDL_SWSURFACE;
-    int w, h;
-    if (vidOpts.fullscreen) {
-        flags |= SDL_FULLSCREEN;
-        w = vidOpts.screenW;
-        h = vidOpts.screenH;
-    }
-    else {
-        if (vidOpts.resizable) flags |= SDL_RESIZABLE;
-        if (vidOpts.noFrame) flags |= SDL_NOFRAME;
-        w = vidOpts.windowW;
-        h = vidOpts.windowH;
-    }
-    
-    // OpenGL attributes
-    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,    8);
-    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,  8);
-    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,   8);
-    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
-    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
-    
-    // Open a window
-    if (SDL_SetVideoMode (w, h, 32, flags) is null) {
-        logger.fatal ("Unable to set video mode:");
-        char* msg = SDL_GetError ();
-        logger.fatal (msg ? fromStringz(msg) : "no reason available");
-        
-        setInitFailure ();
-        return;
-    }
-    
-    // Now (must be done after GL context is created) try to load later version:
-    /+ No later GL features are currently used.
-    try {
-        DerelictGL.loadVersions(GLVersion.Version21);
-    } catch (DerelictException de) {
-        logger.fatal ("Loading OpenGL version > 1.1 failed:");
-        logger.fatal (de.msg);
-        
-        setInitFailure ();
-        return;
-    }
-    +/
-    
-    // OpenGL stuff:
-    glSetup();
-    setProjection (w, h);
-    
-    // Window-manager settings
-    SDL_WM_SetCaption (toStringz ("mde"), null);
-    // SDL_WM_GrabInput (use later)
-}
-
-void resizeWindow (int w, int h) {
-    if (vidOpts.fullscreen) {
-        Options.setInt ("video", "screenW", w);
-        Options.setInt ("video", "screenH", h);
-    } else {
-        Options.setInt ("video", "windowW", w);
-        Options.setInt ("video", "windowH", h);
-    }
-    
-    if (SDL_SetVideoMode (w, h, 32, flags) is null) {
-        logger.fatal ("Unable to reset video mode:");
-        char* msg = SDL_GetError ();
-        logger.fatal (msg ? fromStringz(msg) : "no reason available");
-        
-        global.run = false;
-    }
-    
-    // Reset the projection and viewport
-    setProjection (w, h);
-}
-
-void cleanupSDL () {    // cleanup func
-    closeJoysticks();
-    SDL_Quit();
-}
-
-    /+ Load of info-printing stuff (currently doesn't have a use)
-    // Print a load of info:
-    logger.info ("Available video modes:");
-    char[128] tmp;
-    SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
-    if (modes is null) logger.info ("None!");
-    else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
-    else {
-    for (uint i = 0; modes[i] !is null; ++i) {
-    logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h));
-    }
-    }
-    
-    SDL_VideoInfo* vi = SDL_GetVideoInfo ();
-    if (vi !is null) {
-    logger.info ("Video info:");
-    logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
-    logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem));
-    
-    if (vi.vfmt !is null) {
-    logger.info ("Best video mode:");
-    logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel));
-    }
-    }
-    +/
-
-
-/** All video options. */
-OptionsVideo vidOpts;
-class OptionsVideo : Options {
-    alias store!("fullscreen","hardware","resizable","noFrame") BOOL;
-    alias store!("screenW","screenH","windowW","windowH") INT;
-    //alias store!() CHARA;
-    
-    mixin (decBool!(BOOL.a));
-    mixin (decInt!(INT.a));
-    //mixin (decCharA!(CHARA.a));
-    
-    this () {
-        mixin (aaBool!(BOOL.a));
-        mixin (aaInt!(INT.a));
-        //mixin (aaCharA!(CHARA.a));
-    }
-    
-    static this() {
-        vidOpts = new OptionsVideo;
-        Options.addOptionsClass (vidOpts, "video");
-    }
-}
--- a/mde/events.d	Tue Apr 29 18:10:58 2008 +0100
+++ b/mde/events.d	Wed Apr 30 18:05:56 2008 +0100
@@ -17,9 +17,9 @@
 module mde.events;
 
 import global = mde.global;
-import sdl = mde.SDL;           // resizeWindow
+import sdl = mde.sdl;           // resizeWindow
 
-import mde.input.input;
+import mde.input.Input;
 
 import derelict.sdl.events;
 
--- a/mde/gl/draw.d	Tue Apr 29 18:10:58 2008 +0100
+++ b/mde/gl/draw.d	Wed Apr 30 18:05:56 2008 +0100
@@ -18,7 +18,7 @@
 * Everything here is really intended as makeshift code to enable GUI development. */
 module mde.gl.draw;
 
-import mde.gui.gui;
+import mde.gui.Gui;
 
 import derelict.sdl.sdl;
 import derelict.opengl.gl;
--- a/mde/global.d	Tue Apr 29 18:10:58 2008 +0100
+++ b/mde/global.d	Wed Apr 30 18:05:56 2008 +0100
@@ -23,7 +23,7 @@
 */
 module mde.global;
 
-import mde.input.input;
+import mde.input.Input;
 import mde.scheduler.Scheduler;
 
 /** Some enums used by per request functions. */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/Gui.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,134 @@
+/* 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 Gui class.
+*
+* This is the module to use externally to create a graphical user interface (likely also with
+* content modules).
+*
+* Possibly add a GuiManager to update all active GUIs and pass coordinates (remapping if necessary). */
+module mde.gui.Gui;
+
+import mde.gui.IGui;
+import mde.gui.widget.Window;
+import mde.gui.renderer.createRenderer;
+import mde.gui.exception;
+
+// For loading from file:
+import mt = mde.mergetag.DataSet;
+import mt = mde.mergetag.DefaultData;
+import mt = mde.mergetag.exception;
+import mde.mergetag.Reader;
+import mde.resource.paths;
+
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.gui");
+    
+    gui = new Gui;  // until Guis are handled otherwise, this may as well be the case
+}
+
+Gui gui;    // Currently just one instance; handle differently later.
+// 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 simply calls draw and clickEvent on them all.
+* Coords should be stored here and draw/clickEvent should be called like for widgets.
+* (Also functionality like z-order?) */
+class Gui : IGui {
+    //BEGIN Methods for external use
+    //BEGIN Loading code
+    /** Load all windows from the file 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!)
+        }
+        
+        IReader reader;
+        try {
+            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, null, true);
+            reader.dataSecCreator = delegate mt.IDataSection(mt.ID) {
+                return new Window;
+            };
+            reader.read;
+        } catch (mt.MTException e) {
+            logger.error ("Loading GUI aborted:");
+            logger.error (e.msg);
+            
+            return;
+        }
+        
+        // Get the renderer
+        char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]).Arg;
+        if (p is null || *p is null) {
+            logger.error ("Loading GUI aborted: no renderer specified");
+            return;
+        }
+        rend = createRenderer (*p);
+        
+        // get list
+        windows.length = reader.dataset.sec.length; // pre-allocate
+        windows.length = 0;
+        foreach (sec; reader.dataset.sec) {
+            Window w = cast(Window) sec;
+            debug if (w is null) {
+                logger.error (__FILE__ ~ "(GUI.load): code error (w is null)");
+                continue;
+            }
+            try {
+                w.finalise (this);
+                windows ~= w;       // only add if load successful
+            } catch (Exception e) {
+                logger.error ("Window failed to load: " ~ e.msg);
+            }
+        }
+    }
+    //END Loading code
+    
+    /** Draw each window.
+    *
+    * Currently no concept of how to draw overlapping windows, or how to not bother drawing windows
+    * which don't need redrawing. */
+    void draw() {
+        foreach (w; windows)
+            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);
+    }
+    //END Methods for external use
+    
+    //BEGIN IGui methods
+    IRenderer renderer ()
+    in {
+        assert (rend !is null, "Gui: rend is null");
+    } body {
+        return rend;
+    }
+    //END IGui methods
+    
+private:
+    Window[] windows;
+    IRenderer rend;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/IGui.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,28 @@
+/* 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 Gui interface.
+*
+* This contains the functions for use by Windows, not those for external use (use Gui directly for
+* that). */
+module mde.gui.IGui;
+
+public import mde.gui.renderer.IRenderer;
+
+interface IGui
+{
+    /** Get the Gui's renderer. May be overriden by the window. */
+    IRenderer renderer ();
+}
--- a/mde/gui/Ifaces.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +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 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	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +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/>. */
-
-/// GUI Widget module.
-module mde.gui.Widget;
-
-import mde.gui.Ifaces;
-import mde.gui.exception;
-
-import gl = mde.gl.basic;
-
-import tango.io.Stdout;
-
-//BEGIN createWidget
-/// Widget types. Start high so they can be reordered easily later.
-enum WIDGET_TYPE : int {
-    BOX = 1001, GRID, BUTTON
-}
-
-/** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
-* data [1..$]. */
-IWidget createWidget (IWindow window, 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_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 createWidget
-
-/** 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
-{
-    /** Basic draw method: draw the background */
-    void draw (int x, int y) {
-        deco.setColor();
-        gl.drawBox (x,y, w,h);
-    }
-    
-    /** Dummy event method (ignore) */
-    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
-    
-    /** 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 : Widget
-{
-    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], this);
-        }
-        
-        getMinimumSize (w,h);   // Calculate the size (current size is not saved)
-    }
-    
-    void draw (int x, int y) {
-        deco.setColor;
-        gl.drawBox (x,y, w,h);
-        
-        foreach (i,widget; subWidgets) {
-            widget.draw (x + colX[i % cols], y + rowY[i / cols]);
-        }
-    }
-    
-    // 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*deco.border;
-            return;
-        }
-        
-        // Find the sizes of all subWidgets
-        int[] widgetW = new int[subWidgets.length]; // dimensions
-        int[] widgetH = new int[subWidgets.length];
-        foreach (i,widget; subWidgets) widget.getCurrentSize (widgetW[i],widgetH[i]);
-        
-        // Find row heights and column widths (non cumulative)
-        rowH.length = rows;
-        colW.length = cols; //WARNING: code reliant on these being initialised to zero
-        for (uint i = 0; i < subWidgets.length; ++i) {
-            uint x = i / cols;  // row
-            if (rowH[x] < widgetH[i]) rowH[x] = widgetH[i];
-            x = i % cols;       // column
-            if (colW[x] < widgetW[i]) colW[x] = widgetW[i];
-        }
-        
-        // rowY / colX
-        rowY.length = rows;
-        colX.length = cols;
-        int cum = deco.border;
-        foreach (i, x; rowH) {
-            rowY[i] = cum;
-            cum += x + PADDING;
-        }
-        h = cum + deco.border - PADDING;     // total height
-        cum = deco.border;
-        foreach (i, x; colW) {
-            colX[i] = cum;
-            cum += x + PADDING;
-        }
-        w = cum + deco.border - PADDING;     // total width
-    }
-    
-    // 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 ] */
-}
-
-/// First interactible widget
-class ButtonWidget : Widget
-{
-    bool pushed = false;// true if button is pushed in
-    
-    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;
-    }
-    
-    void draw (int x, int y) {
-        if (pushed)
-            gl.setColor (1f, 0f, 1f);
-        else
-            gl.setColor (.6f, 0f, .6f);
-        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 clickEvent (ushort, ushort, ubyte b, bool state) {
-        if (b == 1) pushed = state; // very basic
-    }
-}
-//END Widgets
--- a/mde/gui/Window.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +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/>. */
-
-/** 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)
-}
--- a/mde/gui/decoration.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +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/>. */
-
-/** 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	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +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/>. */
-
-/** 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.Window;
-import mde.gui.exception;
-
-import mt = mde.mergetag.DataSet;
-import mt = mde.mergetag.exception;
-import mde.mergetag.Reader;
-
-import mde.resource.paths;
-
-import tango.util.log.Log : Log, Logger;
-
-private Logger logger;
-static this () {
-    logger = Log.getLogger ("mde.gui.gui");
-}
-
-GUI gui;    // Currently just one instance; handle differently later.
-// 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(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!)
-        }
-        
-        IReader reader;
-        try {
-            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY);
-            reader.dataSecCreator = delegate mt.IDataSection(mt.ID) {
-                return new Window;
-            };
-            reader.read;
-        } catch (mt.MTException e) {
-            logger.error ("Loading GUI aborted:");
-            logger.error (e.msg);
-            
-            return;
-        }
-        
-        // get list
-        windows.length = reader.dataset.sec.length; // pre-allocate
-        windows.length = 0;
-        foreach (sec; reader.dataset.sec) {
-            Window w = cast(Window) sec;
-            debug if (w is null) {
-                logger.error (__FILE__ ~ "(GUI.load): code error (w is null)");
-                continue;
-            }
-            try {
-                w.finalise();
-                windows ~= w;       // only add if load successful
-            } catch (Exception e) {
-                logger.error ("Window failed to load: " ~ e.msg);
-            }
-        }
-    }
-    
-    /** Draw each window.
-    *
-    * Currently no concept of how to draw overlapping windows, or how to not bother drawing windows
-    * which don't need redrawing. */
-    void draw() {
-        foreach (w; windows)
-            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;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/renderer/IRenderer.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,46 @@
+/* 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/>. */
+
+/** Interface for the renderer. This is planned to replace decoration.d */
+module mde.gui.renderer.IRenderer;
+
+/** Interface for renderers.
+*
+* Renderers provide unified drawing methods for widget, e.g. to draw a window background, a frame,
+* or a button. The renderer will effectively be synonymous with the theme, except that a scripted
+* renderer may also be available.
+*
+* The renderer is intended to be per-GUI. */
+interface IRenderer
+{
+    //BEGIN Get dimensions
+    /** Return the renderer's window border width. */
+    int windowBorder ();
+    
+    /** Return the renderer's between-widget spacing (for layout widgets). */
+    int layoutSpacing ();
+    //END Get dimensions
+    
+    //BEGIN draw routines
+    /** Draw a window border plus background. */
+    void drawWindow (int x, int y, int w, int h);
+    
+    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */
+    void drawWidgetBack (int x, int y, int w, int h);
+    
+    /** Draw a basic box. Doesn't set the colour. Eventually to be removed. */
+    void drawBox (int x, int y, int w, int h);
+    //END draw routines
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,54 @@
+/* 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/>. */
+
+/** A simple renderer. */
+module mde.gui.renderer.SimpleRenderer;
+
+import mde.gui.renderer.IRenderer;
+
+import gl = mde.gl.basic;
+
+/** Interface for renderers.
+*
+* Renderers provide unified drawing methods for widget, e.g. to draw a window background, a frame,
+* or a button. The renderer will effectively be synonymous with the theme, except that a scripted
+* renderer may also be available.
+*
+* The renderer is intended to be per-GUI. */
+class SimpleRenderer : IRenderer
+{
+    int windowBorder () {
+        return 20;
+    }
+    
+    int layoutSpacing () {
+        return 4;
+    }
+    
+    
+    void drawWindow (int x, int y, int w, int h) {
+        gl.setColor (0f, 0f, 1f);
+        gl.drawBox (x,y, w,h);
+        
+        gl.setColor (.3f, .3f, .3f);
+        gl.drawBox (x+20, y+20, w-40, h-40);
+    }
+
+    void drawWidgetBack (int x, int y, int w, int h) {}
+    
+    void drawBox (int x, int y, int w, int h) {
+        gl.drawBox (x,y, w,h);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/renderer/createRenderer.d	Wed Apr 30 18:05:56 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/>. */
+
+/** This module allows any renderer to be created from a string parameter. */
+module mde.gui.renderer.createRenderer;
+
+public import mde.gui.renderer.IRenderer;
+import mde.gui.renderer.SimpleRenderer;
+
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.renderer.createRenderer");
+}
+
+/** Return an instance of renderer name.
+ *
+ * If name does not match any renderer class, SimpleRenderer is used as a default. */
+IRenderer createRenderer (char[] name) {
+    if (name == "Simple") return new SimpleRenderer;
+    else {
+        logger.warn ("Renderer name \"" ~ name ~ "\" is not recognised. Defaulting to SimpleRenderer.");
+        return new SimpleRenderer;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/Ifaces.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,96 @@
+/* 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.widget.Ifaces;
+
+public import mde.gui.renderer.IRenderer;
+
+/** 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 ();+/
+    
+    /** Get the window's renderer.
+    *
+    * Normally specific to the GUI, but widgets have no direct contact with the GUI and this
+    * provides the possibility of per-window renderers (if desired). */
+    IRenderer renderer ();
+}
+
+/** Methods exposed by both Window and Widgets which widgets can call on a parent. */
+interface IParentWidget
+{
+}
+
+/** 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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/Widget.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,103 @@
+/* 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/>. */
+
+/// GUI Widget module.
+module mde.gui.widget.Widget;
+
+public import mde.gui.widget.Ifaces;
+import mde.gui.exception;
+
+import gl = mde.gl.basic;
+
+import tango.io.Stdout;
+
+/** A base widget class. Widgets need not inherit this (they only need implement IWidget), but this
+* class provides a useful basic implementation for widgets.
+*
+* Do not use directly (i.e. only for inheriting from).
+*/
+class Widget : IWidget
+{
+    /** Basic draw method: draw the background */
+    void draw (int x, int y) {
+        window.renderer.drawWidgetBack (x,y, w,h);
+    }
+    
+    /** Dummy event method (ignore) */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
+    
+    /** 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;
+    }
+    
+protected:
+    IWindow window;         // the enclosing window
+    int w, h;               // size
+}
+
+//BEGIN Widgets
+/// Draws a box. That's it.
+class BoxWidget : Widget
+{
+    this (IWindow wind, IParentWidget, int[] data) {
+        if (data.length != 2) throw new WidgetDataException;
+        
+        window = wind;
+        
+        w = data[0];
+        h = data[1];
+    }
+    void draw (int x, int y) {
+        gl.setColor(1f,0f,0f);
+        window.renderer.drawBox (x,y, w,h);
+    }
+}
+
+/// First interactible widget
+class ButtonWidget : Widget
+{
+    bool pushed = false;// true if button is pushed in
+    
+    this (IWindow wind, IParentWidget, int[] data) {
+        if (data.length != 2) throw new WidgetDataException;
+        
+        window = wind;
+        
+        w = data[0];
+        h = data[1];
+    }
+    
+    void draw (int x, int y) {
+        if (pushed)
+            gl.setColor (1f, 0f, 1f);
+        else
+            gl.setColor (.6f, 0f, .6f);
+        window.renderer.drawBox (x,y, w,h);
+    }
+    
+    void getMinimumSize (out int w, out int h) {
+        w = this.w; // button is not resizable
+        h = this.h;
+    }
+    
+    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/widget/Window.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,147 @@
+/* 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. Hopefully eventually this will become a widget to make things a bit more
+* generic. */
+module mde.gui.widget.Window;
+
+import mde.gui.widget.Ifaces;
+import mde.gui.widget.createWidget;
+
+import mde.gui.IGui;
+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 (IGui gui) {
+        // Check data was loaded:
+        if (widgetData is null) throw new WindowLoadException ("No widget data");
+        
+        // Create the renderer:
+        rend = gui.renderer;
+        
+        // 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 + rend.windowBorder;  // widget position
+        widgetY = y + rend.windowBorder;  // must be updated if the window is moved
+        
+        widget.getCurrentSize (w,h);// Find the initial size
+        w += rend.windowBorder * 2;       // Adjust for border
+        h += rend.windowBorder * 2;
+        
+        xw = x+w;
+        yh = y+h;
+    }
+    
+    void draw () {
+        // background
+        rend.drawWindow (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-rend.windowBorder && cy >= widgetY && cy < yh-rend.windowBorder)   // 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 () {
+    }+/
+    
+    IRenderer renderer () {
+        return rend;
+    }
+    //END IWindow methods
+    
+    //BEGIN IParentWidget methods
+    //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.
+    
+    IRenderer rend;                 // The window's renderer
+    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/widget/createWidget.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,47 @@
+/* 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/>. */
+
+/// GUI Widget module.
+module mde.gui.widget.createWidget;
+
+// Widgets to create:
+import mde.gui.widget.layout;
+import mde.gui.widget.Widget;
+
+import mde.gui.exception : WidgetDataException;
+
+//BEGIN createWidget
+/// Widget types. Start high so they can be reordered easily later.
+enum WIDGET_TYPE : int {
+    BOX = 1001, GRID, BUTTON
+}
+
+/** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
+* data [1..$]. */
+IWidget createWidget (IWindow window, 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_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 createWidget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/layout.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,131 @@
+/* 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/>. */
+
+/// Gui layout widgets.
+module mde.gui.widget.layout;
+
+import mde.gui.widget.Widget;
+import mde.gui.exception : WidgetDataException;
+
+/// Encapsulates a grid of Widgets
+class GridWidget : Widget
+{
+    this (IWindow wind, IParentWidget, int[] data) {
+        // Get grid size
+        if (data.length < 2) throw new WidgetDataException;
+        rows = data[0];
+        cols = data[1];
+        
+        window = wind;
+        
+        // Get all sub-widgets
+        // Check: correct data length and rows*cols >= 0 (know data.length - 2 >= 0).
+        if (data.length != 2 + rows * cols) throw new WidgetDataException;
+        subWidgets.length = rows*cols;
+        foreach (i, inout subWidget; subWidgets) {
+            subWidget = window.getWidget (data[i+2], this);
+        }
+        
+        getMinimumSize (w,h);   // Calculate the size (current size is not saved)
+    }
+    
+    // Calculates from all rows and columns of widgets.
+    void getMinimumSize (out int w, out int h) {
+        if (rows*cols == 0) {    // special case
+            w = h = 0;
+            return;
+        }
+        
+        // Find the sizes of all subWidgets
+        int[] widgetW = new int[subWidgets.length]; // dimensions
+        int[] widgetH = new int[subWidgets.length];
+        foreach (i,widget; subWidgets) widget.getCurrentSize (widgetW[i],widgetH[i]);
+        
+        // Find row heights and column widths (non cumulative)
+        rowH.length = rows;
+        colW.length = cols; //WARNING: code reliant on these being initialised to zero
+        for (uint i = 0; i < subWidgets.length; ++i) {
+            uint x = i / cols;  // row
+            if (rowH[x] < widgetH[i]) rowH[x] = widgetH[i];
+            x = i % cols;       // column
+            if (colW[x] < widgetW[i]) colW[x] = widgetW[i];
+        }
+        
+        // rowY / colX
+        rowY.length = rows;
+        colX.length = cols;
+        int spacing = window.renderer.layoutSpacing;
+        
+        int cum = 0;
+        foreach (i, x; rowH) {
+            rowY[i] = cum;
+            cum += x + spacing;
+        }
+        h = cum - spacing;      // total height
+        cum = 0;
+        foreach (i, x; colW) {
+            colX[i] = cum;
+            cum += x + spacing;
+        }
+        w = cum - spacing;      // total width
+    }
+    
+    void draw (int x, int y) {
+        super.draw (x,y);
+        
+        foreach (i,widget; subWidgets) {
+            widget.draw (x + colX[i % cols], y + rowY[i / cols]);
+        }
+    }
+    
+    // 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:
+    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 ] */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/input/Config.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,200 @@
+/* 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 contains a class for holding configs and handles saving, loading and editing.
+module mde.input.Config;
+
+debug import tango.scrapple.text.convert.parseFrom : parseFrom;
+
+import mde.input.exception;
+
+import MT = mde.mergetag.Reader;
+import mde.resource.paths;
+import tango.scrapple.text.convert.parseTo : parseTo;
+
+import tango.util.log.Log : Log, Logger;
+import tango.util.collection.TreeBag : TreeBag;
+
+/** Class to hold the configuration for the input system. Thus loading and switching between
+ *  multiple configurations should be easy.
+ *
+ * Class extends DataSection so that it can be loaded by mergetag easily.
+ */
+class Config : MT.IDataSection
+{
+    alias uint[] outQueue;		// This is the type for the out queue config data.
+    /** Button event type bit-codes
+     *
+     *  These bitcodes are OR'd to the identifier code for the input device, to indicate which type
+     *  of input they are for. E.g. when a key event is recieved with code x, look up
+     *  $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ
+     *  for other libraries.
+     *
+     *  For joystick hat events, a motion is converted into up and down events on separate U,L,D,R
+     *  positions and up and down events sent to the appropriate outputs (effectively making four
+     *  buttons, some pairs of which can be pressed simultaneously). For output to axes, see
+     *  A.JOYHAT_* .
+     */
+    enum B : uint {
+        KEY		= 0x8000_0000u,		/// 0x8000_0000
+        SDLKEY		= 0x8800_0000u,		/// 0x8800_0000
+        MOUSE		= 0x4000_0000u,		/// 0x4000_0000
+        JOYBUTTON	= 0x2000_0000u,		/// 0x2000_0000
+        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
+        JOYHAT_U	= 0x1800_0000u,		/// 0x1800_0000
+        JOYHAT_D	= 0x1400_0000u,		/// 0x1400_0000
+        JOYHAT_L	= 0x1200_0000u,		/// 0x1200_0000
+        JOYHAT_R	= 0x1100_0000u,		/// 0x1100_0000
+    }
+    
+    /** Axis event type bit-codes
+     *
+     *  SDL only supports one type of axis now, but this could be extended in the future.
+     *
+     *  This can also be used to make joystick hats output to two axes (and can be used in
+     *  conjuction with B.JOYHAT_* to output as buttons as well).
+    */
+    enum A : uint {
+        JOYAXIS		= 0x8000_0000u,		/// 0x8000_0000
+        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
+        JOYHAT_LR	= 0x1300_0000u,		/// 0x1300_0000
+        JOYHAT_UD	= 0x1C00_0000u,		/// 0x1C00_0000
+    }
+    
+    /** Mouse & Joystick ball event type bit-codes
+     *
+     *  Currently, mouse input only comes from the window manager: the code is exactly M.WMMOUSE.
+     */
+    enum M : uint {
+        MOUSE		= 0x8000_0000u,		/// 0x8000_0000
+        WMMOUSE		= 0x8800_0000u,		/// 0x8800_0000
+        JOYBALL		= 0x4000_0000u,		/// 0x4000_0000
+    }
+    
+    /** Output queues: the core of the input configuration.
+    *
+    *  These are all mapped with a uint key; upon any event outQueues are looked for in the
+    *  appropriate associative array with a key as follows.
+    *
+    *  The index is split into two parts;
+    *  the first byte specifies the type of input (given by the above enums), and the last three
+    *  bytes define where the input comes from.
+    *
+    *  For type B.SDLKEY, the last three bytes are for the SDL keysym.
+    *  For B.MOUSE, B.JOY*, A.JOY* & M.JOY*, the last three bytes are split into two sets of 12
+    *  bits (with masks 0x00FF_F000 and 0x0000_0FFF), the higher of which specifies the device
+    *  (which mouse or joystick), and the lower of which specifies the button/axis/ball.
+    *
+    *  For all three types of output, the outQueues are used as follows. The first value in the queue is
+    *  read, and a function is called with the event details dependant on this; most of the time an
+    *  output function is called directly. Other functions may be used, however, to allow further
+    *  functionality such as modifier keys, timed keys, and key sequences.
+    *
+    *  The output functions all have code 0x100; these read a single item from the outQueue which
+    *  is the inputID the event outputs to (i.e. any callbacks at that ID called and status set for
+    *  that ID).
+    *
+    */
+    outQueue[][uint] button;
+    outQueue[][uint] axis;	/// ditto
+    outQueue[][uint] relMotion;	/// ditto
+    
+    // FIXME:
+    char[] name;		/// Name for user to save this under.
+    uint[] inheritants;		/// Other profiles to inherit.
+    
+    static Config[char[]] configs;	/// All configs loaded by load().
+    private static TreeBag!(char[]) loadedFiles;	// all filenames load tried to read
+    
+    private static Logger logger;
+    static this() {
+        logger = Log.getLogger ("mde.input.config.Config");
+    }
+    
+//BEGIN File loading/saving code
+    static this () {
+        loadedFiles = new TreeBag!(char[]);
+    }
+    
+    // Load all configs from a file.
+    static void load (char[] filename) {
+        if (loadedFiles.contains (filename)) return;	// forget it; already done that
+        loadedFiles.add (filename);
+        
+        MT.IReader file;
+        
+        try {
+            // open and read header:
+            file = confDir.makeMTReader (filename, PRIORITY.LOW_HIGH, null, true);
+            
+            file.dataSecCreator =
+            delegate MT.IDataSection (MT.ID) {	return new Config;	};
+            
+            // D2.0: enum MT.ID CONFIGS = "Configs";
+            const MT.ID CONFIGS = cast(MT.ID)"Configs";
+            // Restrict config sections if this tag exists:
+            auto file_configs_p = CONFIGS in file.dataset.header._charAA;
+            MT.ID[] file_configs = null;
+            if (file_configs_p) {
+                file_configs = cast(MT.ID[]) *file_configs_p;
+            }
+            
+            if (file_configs)	file.read(file_configs);	// restrict to this set IF a restriction was given
+            else		file.read();			// otherwise read all
+        }
+        catch (MT.MTException) {
+            logger.fatal ("Unable to load configs from: " ~ filename);
+            throw new ConfigLoadException;
+        }
+        
+        // Trying to directly cast dataset.sec to configs resulted in the Configs losing their data.
+        // Also this is safer since it checks types (and must be done if configs wasn't previously empty).
+        foreach (i, sec; file.dataset.sec) {
+            Config c = cast(Config) sec;
+            if (c) configs[i] = c;		// Check, because we don't want null entries in configs
+            else debug logger.error ("Ended up with DataSection of wrong type; this should never happen.");
+        }
+        
+        debug (MDE_CONFIG_DUMP) {
+            char tmp[128] = void;
+            logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length));
+            foreach (id, cfg; configs) {
+                logger.trace ("Section " ~ id ~
+                ":\n\tbutton:\t\t" ~ parseFrom!(uint[][][uint])(cfg.button) ~
+                "\n\taxis:\t\t" ~ parseFrom!(uint[][][uint])(cfg.axis) ~
+                "\n\trelMotion:\t" ~ parseFrom!(uint[][][uint])(cfg.relMotion) );
+            }
+        }
+    }
+    
+    // D2.0: private enum QUEUE : MT.ID { BUTTON = "B", AXIS = "A", MOUSE = "M" }
+    private struct QUEUE {
+        static const MT.ID BUTTON = cast(MT.ID)"B", AXIS = cast(MT.ID)"A", MOUSE = cast(MT.ID)"M";
+    }
+    private this() {}	// Private since this class should only be created from here.
+    
+    void addTag (char[] tp, MT.ID id, char[] dt) {
+        if (tp == "uint[][uint]") {
+            if (id == QUEUE.BUTTON) button = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
+            else if (id == QUEUE.AXIS) axis = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
+            else if (id == QUEUE.MOUSE) relMotion = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
+            else logger.warn ("Unexpected tag encountered with ID " ~ cast(char[])id);
+        } // FIXME: add support for name and inheritants.
+    }
+    void writeAll (ItemDelg) {
+        // FIXME
+    }
+//END File loading/saving code
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/input/Input.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,611 @@
+/* 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 contains the interface to the input system; it should be the only module of the
+ * input package imported from outside this package.
+ */
+module mde.input.Input;
+
+// package imports
+import mde.input.Config;
+import mde.input.exception;
+
+// sdl imports
+import derelict.sdl.events;
+import derelict.sdl.types;	// only SDL_PRESSED
+import derelict.sdl.joystick;	// SDL_HAT_*
+
+import tango.util.log.Log : Log, Logger;
+
+/// Class encapsulating all input functionality.
+class Input
+{
+    /// Typedef for all indexes (type is uint).
+    typedef uint				inputID;
+    alias void delegate(inputID, bool)		ButtonCallback;
+    alias void delegate(inputID, short)		AxisCallback;
+    alias void delegate(inputID, real,real)	RelMotionCallback;
+    alias void delegate(ushort, ushort, ubyte, bool)	MouseClickCallback;
+
+    /** Get key status at this ID.
+    *
+    * Returns: value (true = down, false = up) or false if no value at this ID. */
+    bool getButton (inputID id) {
+        bool* retp = id in button;
+        if (retp) return *retp;
+        else return false;
+    }
+    
+    /** Get axis status at this ID.
+    *
+    * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */
+    short getAxis (inputID id) {
+        short* retp = id in axis;
+        if (retp) return *retp;
+        else return 0;
+    }
+    /** Get axis status at this ID.
+    *
+    * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
+    real getAxis1 (inputID id) {
+        short* retp = id in axis;
+        if (retp) return (*retp) * 3.0518509475997192e-05;
+        else return 0.0;
+    }
+            
+    /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call).
+    *
+    * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item).
+    *
+    * To avoid confusion over the ID here, the idea is for the input-layer upward to support
+    * multiple mice, in case future platforms do.
+    * Also joystick balls (supported by SDL) can be used in the same way as a mouse for relative
+    * positions.
+    */
+    void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) {
+        RelPair* rp = id in relMotion;
+        if (rp) {
+            x = rp.x;	y = rp.y;
+        }
+    }
+    /** Get mouse pointer position in screen coordinates.
+    *
+    * Window managers only support one mouse, so there will only be one screen coordinate.
+    * Unlike nearly everything else, this is not configurable.
+    */
+    void getMouseScreenPos (out uint x, out uint y) {
+        x = mouse_x;	y = mouse_y;
+    }
+    // /// Is this modifier on?
+    //bool modifierStatus (inputID id);
+
+    /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
+    *
+    * Delegate receives event status.
+    */
+    void addButtonCallback (inputID id, ButtonCallback dg) {
+        buttonCallbacks[id] ~= dg;
+    }
+
+    /** Adds a callback delegate for axis events with this ID.
+    *
+    * Delegate receives event status (as per what getAxis returns).
+    */
+    void addAxisCallback (inputID id, AxisCallback dg) {
+        axisCallbacks[id] ~= dg;
+    }
+
+    /** Adds a callback delegate for mouse motion/joystick ball events with this ID.
+    *
+    * Delegate receives event status. As the name suggests, this is relative motion not screen
+    * position, with sensitivity adjustments applied.
+    *
+    * (A separate callback for mouse screen position changes is not
+    * necessary since this will be triggered by the same event - use mouseScreenPos from within the
+    * function to get new screen coordinates.)
+    */
+    void addRelMotionCallback (inputID id, RelMotionCallback dg) {
+        relMotionCallbacks[id] ~= dg;
+    }
+    
+    /** Adds a callback delegate for all mouse clicks & releases.
+    *
+    * Delegate recieves x,y screen position (at time of click/release), button index (1 for left,
+    * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or
+    * released (true if pressed).
+    *
+    * The point of this over a standard button callback is firstly to avoid mouse configuration for
+    * the GUI, and secondly to give the pointer position at the time of the event, not the time the
+    * callback gets called.
+    */
+    void addMouseClickCallback (MouseClickCallback dg) {
+        mouseClickCallbacks ~= dg;
+    }
+
+    /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
+    *
+    * Other types of event functions may be added. Returns true if the event was used, false if not
+    * or no config was available. Hmm... doesn't seem very useful, but has practically no cost.
+    *
+    * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should
+    * be fine.
+    */
+    bool opCall (ref SDL_Event event) {
+        /* Non-config events.
+        *
+        * 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 - 1, event.button.y - 1,
+                        event.button.button, event.button.state == SDL_PRESSED);
+                break;
+            
+            case SDL_MOUSEMOTION:
+                mouse_x = event.motion.x - 1;
+                mouse_y = event.motion.y - 1;
+                break;
+            
+            default:
+        }
+        
+        /* No config available, so don't try to access it and segfault.
+        * Don't log a message because this function is called per-event (i.e. frequently).
+        * A message should already have been logged by loadConfig anyway.
+        */
+        if (!config) return false;
+        
+        switch (event.type) {
+            // Keyboard events:
+            case SDL_KEYDOWN:
+            case SDL_KEYUP:
+                outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
+                if (p) foreach (outQueue q; *p) {
+                    bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q));
+                }
+                break;
+            
+            // Mouse events:
+            case SDL_MOUSEBUTTONDOWN:
+            case SDL_MOUSEBUTTONUP:
+                // Button events:
+                outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button;
+                if (p) foreach (outQueue q; *p) {
+                    bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q));
+                }
+                break;
+            
+            case SDL_MOUSEMOTION:
+                // Relative motion:
+                outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion;
+                if (p) foreach (outQueue q; *p) {
+                    mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q));
+                }
+                break;
+            
+            // Joystick events:
+            case SDL_JOYBUTTONDOWN:
+            case SDL_JOYBUTTONUP:
+                outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button;
+                if (p) foreach (outQueue q; *p) {
+                    bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q));
+                }
+                break;
+            
+            case SDL_JOYAXISMOTION:
+                outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis;
+                if (p) foreach (outQueue q; *p) {
+                    aEvent (this, event.jaxis.value, readOutQueue(q));
+                }
+                break;
+            
+            case SDL_JOYBALLMOTION:
+                outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion;
+                if (p) foreach (outQueue q; *p) {
+                    mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q));
+                }
+                break;
+            
+            case SDL_JOYHATMOTION:
+                static ubyte[uint] oldJHatVals;		// necessary to store this to know which "axis" changed
+        
+                uint index = (event.jhat.which << 12) | event.jhat.hat;
+                ubyte* oVal_p = index in oldJHatVals;
+                ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED;
+                
+                // Carry out functionality for an axis.
+                void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) {
+                    // Check if there's any change on this axis (if not, nothing to do):
+                    ubyte filter = neg | pos;
+                    if ((oldJHatVal & filter) != (event.jhat.value & filter)) {
+                        // Now we know this axis changed position, so can unset old value and set
+                        // new value (if not centre).
+                        
+                        // Cancel old button status:
+                        if (oldJHatVal & neg) {
+                            outQueue[]* p = (neg_b | index) in config.button;
+                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
+                        } else if (oldJHatVal & pos) {
+                            outQueue[]* p = (pos_b | index) in config.button;
+                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
+                        }
+                        
+                        // Set new button status and position:
+                        short position = 0;
+                        if (event.jhat.value & neg) {
+                            position = -32767;
+                            outQueue[]* p = (neg_b | index) in config.button;
+                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
+                        } else if (event.jhat.value & pos) {
+                            position = 32767;
+                            outQueue[]* p = (pos_b | index) in config.button;
+                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
+                        }
+                        
+                        // New axis event:
+                        outQueue[]* p = (axis | index) in config.axis;
+                        if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q));
+                    }
+                }
+                
+                // Now run the code for each axis:
+                hatExamine (SDL_HAT_UP, SDL_HAT_DOWN,
+                            Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD);
+                hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT,
+                            Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR);
+                
+                oldJHatVals[index] = event.jhat.value;
+                break;
+            
+            // Other events:
+            default:
+                return false;	// event not used
+        }
+        return true;		// event used
+    }
+    
+    /** Resets relative movement of mice / joystick balls to zero.
+    *
+    * Should be called once-per-frame if these are used, but must be called after their state has
+    * been read (e.g. just before updating the input).
+    */
+    void frameReset () {
+        foreach (rp; relMotion) {
+            rp.x = rp.y = 0.0;
+        }
+    }
+    
+    /** Loads all configs, activating the requested id.
+    *
+    * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
+    *   found.
+    */
+    void loadConfig (char[] profile = "Default") {
+        Config.load("input");	// FIXME: filename
+        Config* c_p = profile in Config.configs;
+        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!");
+        }
+    }
+    
+private:
+    // Static constructor for event stream (fills es_*_fcts tables).
+    static this () {
+        es_b_fcts = [ ES_B.OUT : &es_b_out ];
+        es_a_fcts = [ ES_A.OUT : &es_a_out, ES_A.REVERSE : &es_a_reverse ];
+        es_m_fcts = [ ES_M.OUT : &es_m_out ];
+        
+        logger = Log.getLogger ("mde.input.input.Input");
+    }
+    
+    struct RelPair {	// for mouse/joystick ball motion
+        real x, y;
+        static RelPair opCall (real a, real b) {
+            RelPair ret;
+            ret.x = a;	ret.y = b;
+            return ret;
+        }
+    }
+    
+    static Logger logger;
+
+    Config config;			// Configuration
+    
+    bool[inputID] button;		// Table of button states
+    short[inputID] axis;		// Table of axes states
+    ushort mouse_x, mouse_y;		// Current screen coords of the window manager mouse
+    RelPair[inputID] relMotion;		// Table of relative mouse / joystick ball motions
+    
+    // FIXME: currently no means of removal
+    ButtonCallback[][inputID]	buttonCallbacks;
+    AxisCallback[][inputID]	axisCallbacks;
+    RelMotionCallback[][inputID] relMotionCallbacks;
+    MouseClickCallback[]	mouseClickCallbacks;
+        
+    //BEGIN Event stream functionality
+    /* This section contains functions called on an event, which may modify the event (adjuster
+    * functions), and finally output to one (or more) of the state tables (the event stream).
+    *
+    * Adjuster and other event functions should have a format to fit the ES_X_Func types, for X is B
+    * (button event), A (axis event) or M (mouse relative motion event or joystick ball event).
+    * Adjusters should call one of the xEvent() functions with their output and the remainder of
+    * the readOutQueue.
+    *
+    * To control which adjusters get called and pass parameters, a stack of sorts is used: outQueue.
+    */
+    //BEGIN ES Definitions
+    /* Note: We really want an array, not a stack. We cannot edit the lists, so we can either
+    * copy to a stack or just iterate through it as an array.
+    */
+    alias Config.outQueue outQueue;
+    struct readOutQueue {		// A convenient structure for reading an outQueue item by item.
+        private Config.outQueue _q;		// the queue, stored by reference to the original
+        private uint p = 0;		// current read position (start at beginning)
+    
+        static readOutQueue opCall (Config.outQueue q) {	// Static constructor
+            readOutQueue ret;
+            ret._q = q;
+            return ret;
+        }
+        uint pop () {			// Get the next element and advance. Throws an exception if there isn't another.
+            if (p >= _q.length)
+                throw new InputClassException ("Input: Invalid configuration: incomplete config stack");
+            uint ret = _q[p];
+            ++p;
+            return ret;
+        }
+        debug uint next () {		// Get the next element. No checks; for debug use only.
+            return _q[p];
+        }
+    }
+    
+    // These aliases are for pointers to the event functions.
+    // These need to use "this", but cannot be delegates because they musn't be bound to a
+    // particular instance of Input, hence this must be passed when called.
+    alias void function (Input, bool, readOutQueue) ES_B_Func;
+    alias void function (Input, short, readOutQueue) ES_A_Func;
+    alias void function (Input, short, short, readOutQueue) ES_M_Func;
+    
+    /* These are the codes allowing the config to specify event functions.
+    *
+    * They are organised as defined in doc/input_ID_assignments.
+    */
+    enum ES_B : uint {
+        OUT	= 0x1000u,
+    }
+    enum ES_A : uint {
+        OUT	= 0x1000u,
+        REVERSE	= 0x2000u,
+    }
+    enum ES_M : uint {
+        OUT	= 0x1000u,
+    }
+    //END ES Definitions
+    
+    // ES Data:
+    // These are the tables for looking up which event function to call.
+    static ES_B_Func[uint] es_b_fcts;
+    static ES_A_Func[uint] es_a_fcts;
+    static ES_M_Func[uint] es_m_fcts;
+    
+    //BEGIN ES Functions
+    // These 3 functions pass an event to the appropriate event function (adjuster or output func).
+    // They are used to start and continue an event stream.
+    // They must be static to allow calling from event functions.
+    const EVCONF_ERR = "Invalid configuration: bad event function code";
+    static void bEvent (Input myThis, bool b, readOutQueue s)
+    {
+        ES_B_Func* func = (s.pop() in es_b_fcts);
+        if (func != null) (*func)(myThis, b, s);
+        else throw new InputClassException (EVCONF_ERR);
+    }
+    static void aEvent (Input myThis, short x, readOutQueue s)
+    {
+        ES_A_Func* func = (s.pop() in es_a_fcts);
+        if (func != null) (*func)(myThis, x, s);
+        else throw new InputClassException (EVCONF_ERR);
+    }
+    // Only handles relative output since position-on-screen is not stored with an ID:
+    static void mEvent (Input myThis, short x, short y, readOutQueue s)
+    {
+        ES_M_Func* func = (s.pop() in es_m_fcts);
+        if (func != null) (*func)(myThis, x, y, s);
+        else throw new InputClassException (EVCONF_ERR);
+    }
+    
+    // The remaining functions are the stream functions, for adjusting and outputting an event.
+    // They need to work like non-static functions, but are called via a function pointer, hencne
+    // should be static with their first parameter being instead of this.
+    
+    // Simple output function
+    static void es_b_out (Input myThis, bool b, readOutQueue s) {
+        inputID id = cast(inputID) s.pop();
+                
+        myThis.button[id] = b;
+        
+        ButtonCallback[]* cb_p = id in myThis.buttonCallbacks;
+        if (cb_p) foreach (cb; *cb_p) cb (id, b);
+    }
+    // Adjuster to check modifier keys
+    static void es_b_modifier (Input myThis, bool b, readOutQueue s);
+
+    // Simple output function
+    static void es_a_out (Input myThis, short x, readOutQueue s) {
+        inputID id = cast(inputID) s.pop();
+        
+        myThis.axis[id] = x;
+        
+        AxisCallback[]* cb_p = id in myThis.axisCallbacks;
+        if (cb_p) foreach (cb; *cb_p) cb (id, x);
+    }
+    
+    // Just reverses an axis's value
+    static void es_a_reverse (Input myThis, short x, readOutQueue s) {
+        aEvent (myThis, -x, s);
+    }
+
+    // Simple output function
+    static void es_m_out (Input myThis, short x, short y, readOutQueue s) {
+        inputID id = cast(inputID) s.pop();
+        
+        myThis.relMotion[id] = RelPair(x,y);
+        
+        RelMotionCallback[]* cb_p = id in myThis.relMotionCallbacks;
+        if (cb_p) foreach (cb; *cb_p) cb (id, x,y);
+    }
+    //END ES Functions
+    //END Event stream functionality
+    
+    
+    /* This unittest covers input.config.Config and input.input.Input for some events of all types.
+    * It is not bullet-proof, and does not cover the steam-functions other than the direct output
+    * ones (largely because these may get added at any time and tested via input tests).
+    *
+    * What it does test:
+    *	Events of all types.
+    *	Callbacks of all types.
+    *	Status set from events for all types (button,axis,relMotion).
+    *
+    * It relies on config loaded from a file (dependant on where input bindings are loaded from;
+    * currently conf/input.mtt).
+    */
+    debug (mdeUnitTest) unittest {
+        Input ut = new Input();
+        ut.loadConfig ("UnitTest");
+            
+        int[6] counters;	// counters for callbacks
+        // Should become: [2,2,0,2,1,1]
+            
+        //BEGIN Set up some callbacks
+        ut.addButtonCallback (0x03F0, delegate void(inputID id, bool status) {
+            assert (status == !counters[0]);	// true first call, false next
+            counters[0] += 1;
+        });
+        ut.addButtonCallback (0x06F0, delegate void(inputID id, bool status) {
+            assert (status == !counters[1]);	// true first call, false next
+            counters[1] += 1;
+        });
+        ut.addButtonCallback (0x07F0, delegate void(inputID id, bool status) {
+            counters[2] += 1;
+        });
+        ut.addAxisCallback (0x22F0, delegate void(inputID id, short x) {
+            assert (x == (counters[3] ? 0 : 32767));
+            counters[3] += 1;
+        });
+        ut.addRelMotionCallback (0x11F0, delegate void(inputID id, real x, real y) {
+            assert (x == 14.0);
+            assert (y == -1.0);
+            counters[4] += 1;
+        });
+        ut.addMouseClickCallback (delegate void(ushort x, ushort y, ubyte b, bool s) {
+            assert (x == 291);
+            assert (y == 10010);
+            assert (b == 3);
+            assert (s == false);
+            counters[5] += 1;
+        });
+        //END Set up some callbacks
+                        
+        //BEGIN Post a lot of events
+        SDL_Event e;
+            
+        // F11 down:
+        e.type = SDL_KEYDOWN;
+        e.key.state = SDL_PRESSED;
+        e.key.keysym.sym = 292;	// SDLK_F11
+        ut(e);
+        // Right mouse button up:
+        e.type = SDL_MOUSEBUTTONUP;
+        e.button.button = 3;	// SDL_BUTTON_RIGHT
+        e.button.state = SDL_RELEASED;
+        e.button.x = 291;
+        e.button.y = 10010;
+        ut(e);
+        // Mouse motion:
+        e.type = SDL_MOUSEMOTION;
+        e.motion.x = 63;
+        e.motion.y = 44;
+        e.motion.xrel = 14;
+        e.motion.yrel = -1;
+        ut(e);
+        // Joystick 2 button 5 down:
+        e.type = SDL_JOYBUTTONDOWN;
+        e.jbutton.which = 2;
+        e.jbutton.button = 5;
+        e.jbutton.state = SDL_PRESSED;
+        ut(e);
+        // Same button released:
+        e.jbutton.state = SDL_RELEASED;
+        ut(e);
+        // Joystick 1 axis 8 motion:
+        e.type = SDL_JOYAXISMOTION;
+        e.jaxis.which = 1;
+        e.jaxis.axis = 8;
+        e.jaxis.value = 32767;
+        ut(e);
+        // Joystick 22 ball 100 motion:
+        e.type = SDL_JOYBALLMOTION;
+        e.jball.which = 22;
+        e.jball.ball = 100;
+        e.jball.xrel = -21;
+        e.jball.yrel = 1024;
+        ut(e);
+        // Joystick 214 hat 12 DOWN-RIGHT:
+        e.type = SDL_JOYHATMOTION;
+        e.jhat.which = 214;
+        e.jhat.hat = 12;
+        e.jhat.value = SDL_HAT_RIGHTDOWN;
+        ut(e);
+        // Same hat LEFT:
+        e.jhat.value = SDL_HAT_LEFT;
+        ut(e);
+        //END Post a lot of events
+            
+        //BEGIN Check states
+        assert (ut.getButton(0xF0) == true);
+        assert (ut.getButton(0x1F0) == false);
+        assert (ut.getButton(0x2F0) == false);
+        assert (ut.getButton(0x4F0) == false);
+        assert (ut.getButton(0x5F0) == true);
+        assert (ut.getButton(0x6F0) == false);
+        assert (ut.getButton(0x7F0) == false);
+            
+        assert (ut.getAxis(0x20F0) == 32767);
+        assert (ut.getAxis(0x21F0) == -32767);
+        assert (ut.getAxis1(0x22F0) == 0.0);
+            
+        real s,t;
+        ut.getRelMotion(0x10F0, s,t);
+        assert (s == 14.0);
+        assert (t == -1.0);
+        ut.getRelMotion(0x12F0, s,t);
+        assert (s == -21.0);
+        assert (t == 1024.0);
+                                                
+        assert (counters == [2,2,0,2,1,1]);
+        //END Check states
+        
+        logger.info ("Unittest complete.");
+    }
+}
--- a/mde/input/config.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +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/>. */
-
-/// This module contains a class for holding configs and handles saving, loading and editing.
-module mde.input.config;
-
-debug import tango.scrapple.text.convert.parseFrom : parseFrom;
-
-import mde.input.exception;
-
-import MT = mde.mergetag.Reader;
-import mde.resource.paths;
-import tango.scrapple.text.convert.parseTo : parseTo;
-
-import tango.util.log.Log : Log, Logger;
-import tango.util.collection.TreeBag : TreeBag;
-
-/** Class to hold the configuration for the input system. Thus loading and switching between
- *  multiple configurations should be easy.
- *
- * Class extends DataSection so that it can be loaded by mergetag easily.
- */
-class Config : MT.IDataSection
-{
-    alias uint[] outQueue;		// This is the type for the out queue config data.
-    /** Button event type bit-codes
-     *
-     *  These bitcodes are OR'd to the identifier code for the input device, to indicate which type
-     *  of input they are for. E.g. when a key event is recieved with code x, look up
-     *  $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ
-     *  for other libraries.
-     *
-     *  For joystick hat events, a motion is converted into up and down events on separate U,L,D,R
-     *  positions and up and down events sent to the appropriate outputs (effectively making four
-     *  buttons, some pairs of which can be pressed simultaneously). For output to axes, see
-     *  A.JOYHAT_* .
-     */
-    enum B : uint {
-        KEY		= 0x8000_0000u,		/// 0x8000_0000
-        SDLKEY		= 0x8800_0000u,		/// 0x8800_0000
-        MOUSE		= 0x4000_0000u,		/// 0x4000_0000
-        JOYBUTTON	= 0x2000_0000u,		/// 0x2000_0000
-        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
-        JOYHAT_U	= 0x1800_0000u,		/// 0x1800_0000
-        JOYHAT_D	= 0x1400_0000u,		/// 0x1400_0000
-        JOYHAT_L	= 0x1200_0000u,		/// 0x1200_0000
-        JOYHAT_R	= 0x1100_0000u,		/// 0x1100_0000
-    }
-    
-    /** Axis event type bit-codes
-     *
-     *  SDL only supports one type of axis now, but this could be extended in the future.
-     *
-     *  This can also be used to make joystick hats output to two axes (and can be used in
-     *  conjuction with B.JOYHAT_* to output as buttons as well).
-    */
-    enum A : uint {
-        JOYAXIS		= 0x8000_0000u,		/// 0x8000_0000
-        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
-        JOYHAT_LR	= 0x1300_0000u,		/// 0x1300_0000
-        JOYHAT_UD	= 0x1C00_0000u,		/// 0x1C00_0000
-    }
-    
-    /** Mouse & Joystick ball event type bit-codes
-     *
-     *  Currently, mouse input only comes from the window manager: the code is exactly M.WMMOUSE.
-     */
-    enum M : uint {
-        MOUSE		= 0x8000_0000u,		/// 0x8000_0000
-        WMMOUSE		= 0x8800_0000u,		/// 0x8800_0000
-        JOYBALL		= 0x4000_0000u,		/// 0x4000_0000
-    }
-    
-    /** Output queues: the core of the input configuration.
-    *
-    *  These are all mapped with a uint key; upon any event outQueues are looked for in the
-    *  appropriate associative array with a key as follows.
-    *
-    *  The index is split into two parts;
-    *  the first byte specifies the type of input (given by the above enums), and the last three
-    *  bytes define where the input comes from.
-    *
-    *  For type B.SDLKEY, the last three bytes are for the SDL keysym.
-    *  For B.MOUSE, B.JOY*, A.JOY* & M.JOY*, the last three bytes are split into two sets of 12
-    *  bits (with masks 0x00FF_F000 and 0x0000_0FFF), the higher of which specifies the device
-    *  (which mouse or joystick), and the lower of which specifies the button/axis/ball.
-    *
-    *  For all three types of output, the outQueues are used as follows. The first value in the queue is
-    *  read, and a function is called with the event details dependant on this; most of the time an
-    *  output function is called directly. Other functions may be used, however, to allow further
-    *  functionality such as modifier keys, timed keys, and key sequences.
-    *
-    *  The output functions all have code 0x100; these read a single item from the outQueue which
-    *  is the inputID the event outputs to (i.e. any callbacks at that ID called and status set for
-    *  that ID).
-    *
-    */
-    outQueue[][uint] button;
-    outQueue[][uint] axis;	/// ditto
-    outQueue[][uint] relMotion;	/// ditto
-    
-    // FIXME:
-    char[] name;		/// Name for user to save this under.
-    uint[] inheritants;		/// Other profiles to inherit.
-    
-    static Config[char[]] configs;	/// All configs loaded by load().
-    private static TreeBag!(char[]) loadedFiles;	// all filenames load tried to read
-    
-    private static Logger logger;
-    static this() {
-        logger = Log.getLogger ("mde.input.config.Config");
-    }
-    
-//BEGIN File loading/saving code
-    static this () {
-        loadedFiles = new TreeBag!(char[]);
-    }
-    
-    // Load all configs from a file.
-    static void load (char[] filename) {
-        if (loadedFiles.contains (filename)) return;	// forget it; already done that
-        loadedFiles.add (filename);
-        
-        MT.IReader file;
-        
-        try {
-            // open and read header:
-            file = confDir.makeMTReader (filename, PRIORITY.LOW_HIGH, null, true);
-            
-            file.dataSecCreator =
-            delegate MT.IDataSection (MT.ID) {	return new Config;	};
-            
-            // D2.0: enum MT.ID CONFIGS = "Configs";
-            const MT.ID CONFIGS = cast(MT.ID)"Configs";
-            // Restrict config sections if this tag exists:
-            auto file_configs_p = CONFIGS in file.dataset.header._charAA;
-            MT.ID[] file_configs = null;
-            if (file_configs_p) {
-                file_configs = cast(MT.ID[]) *file_configs_p;
-            }
-            
-            if (file_configs)	file.read(file_configs);	// restrict to this set IF a restriction was given
-            else		file.read();			// otherwise read all
-        }
-        catch (MT.MTException) {
-            logger.fatal ("Unable to load configs from: " ~ filename);
-            throw new ConfigLoadException;
-        }
-        
-        // Trying to directly cast dataset.sec to configs resulted in the Configs losing their data.
-        // Also this is safer since it checks types (and must be done if configs wasn't previously empty).
-        foreach (i, sec; file.dataset.sec) {
-            Config c = cast(Config) sec;
-            if (c) configs[i] = c;		// Check, because we don't want null entries in configs
-            else debug logger.error ("Ended up with DataSection of wrong type; this should never happen.");
-        }
-        
-        debug (MDE_CONFIG_DUMP) {
-            char tmp[128] = void;
-            logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length));
-            foreach (id, cfg; configs) {
-                logger.trace ("Section " ~ id ~
-                ":\n\tbutton:\t\t" ~ parseFrom!(uint[][][uint])(cfg.button) ~
-                "\n\taxis:\t\t" ~ parseFrom!(uint[][][uint])(cfg.axis) ~
-                "\n\trelMotion:\t" ~ parseFrom!(uint[][][uint])(cfg.relMotion) );
-            }
-        }
-    }
-    
-    // D2.0: private enum QUEUE : MT.ID { BUTTON = "B", AXIS = "A", MOUSE = "M" }
-    private struct QUEUE {
-        static const MT.ID BUTTON = cast(MT.ID)"B", AXIS = cast(MT.ID)"A", MOUSE = cast(MT.ID)"M";
-    }
-    private this() {}	// Private since this class should only be created from here.
-    
-    void addTag (char[] tp, MT.ID id, char[] dt) {
-        if (tp == "uint[][uint]") {
-            if (id == QUEUE.BUTTON) button = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
-            else if (id == QUEUE.AXIS) axis = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
-            else if (id == QUEUE.MOUSE) relMotion = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
-            else logger.warn ("Unexpected tag encountered with ID " ~ cast(char[])id);
-        } // FIXME: add support for name and inheritants.
-    }
-    void writeAll (ItemDelg) {
-        // FIXME
-    }
-//END File loading/saving code
-}
--- a/mde/input/input.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,611 +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/>. */
-
-/**
- * This module contains the interface to the input system; it should be the only module of the
- * input package imported from outside this package.
- */
-module mde.input.input;
-
-// package imports
-import mde.input.config;
-import mde.input.exception;
-
-// sdl imports
-import derelict.sdl.events;
-import derelict.sdl.types;	// only SDL_PRESSED
-import derelict.sdl.joystick;	// SDL_HAT_*
-
-import tango.util.log.Log : Log, Logger;
-
-/// Class encapsulating all input functionality.
-class Input
-{
-    /// Typedef for all indexes (type is uint).
-    typedef uint				inputID;
-    alias void delegate(inputID, bool)		ButtonCallback;
-    alias void delegate(inputID, short)		AxisCallback;
-    alias void delegate(inputID, real,real)	RelMotionCallback;
-    alias void delegate(ushort, ushort, ubyte, bool)	MouseClickCallback;
-
-    /** Get key status at this ID.
-    *
-    * Returns: value (true = down, false = up) or false if no value at this ID. */
-    bool getButton (inputID id) {
-        bool* retp = id in button;
-        if (retp) return *retp;
-        else return false;
-    }
-    
-    /** Get axis status at this ID.
-    *
-    * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */
-    short getAxis (inputID id) {
-        short* retp = id in axis;
-        if (retp) return *retp;
-        else return 0;
-    }
-    /** Get axis status at this ID.
-    *
-    * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */
-    real getAxis1 (inputID id) {
-        short* retp = id in axis;
-        if (retp) return (*retp) * 3.0518509475997192e-05;
-        else return 0.0;
-    }
-            
-    /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call).
-    *
-    * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item).
-    *
-    * To avoid confusion over the ID here, the idea is for the input-layer upward to support
-    * multiple mice, in case future platforms do.
-    * Also joystick balls (supported by SDL) can be used in the same way as a mouse for relative
-    * positions.
-    */
-    void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) {
-        RelPair* rp = id in relMotion;
-        if (rp) {
-            x = rp.x;	y = rp.y;
-        }
-    }
-    /** Get mouse pointer position in screen coordinates.
-    *
-    * Window managers only support one mouse, so there will only be one screen coordinate.
-    * Unlike nearly everything else, this is not configurable.
-    */
-    void getMouseScreenPos (out uint x, out uint y) {
-        x = mouse_x;	y = mouse_y;
-    }
-    // /// Is this modifier on?
-    //bool modifierStatus (inputID id);
-
-    /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
-    *
-    * Delegate receives event status.
-    */
-    void addButtonCallback (inputID id, ButtonCallback dg) {
-        buttonCallbacks[id] ~= dg;
-    }
-
-    /** Adds a callback delegate for axis events with this ID.
-    *
-    * Delegate receives event status (as per what getAxis returns).
-    */
-    void addAxisCallback (inputID id, AxisCallback dg) {
-        axisCallbacks[id] ~= dg;
-    }
-
-    /** Adds a callback delegate for mouse motion/joystick ball events with this ID.
-    *
-    * Delegate receives event status. As the name suggests, this is relative motion not screen
-    * position, with sensitivity adjustments applied.
-    *
-    * (A separate callback for mouse screen position changes is not
-    * necessary since this will be triggered by the same event - use mouseScreenPos from within the
-    * function to get new screen coordinates.)
-    */
-    void addRelMotionCallback (inputID id, RelMotionCallback dg) {
-        relMotionCallbacks[id] ~= dg;
-    }
-    
-    /** Adds a callback delegate for all mouse clicks & releases.
-    *
-    * Delegate recieves x,y screen position (at time of click/release), button index (1 for left,
-    * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or
-    * released (true if pressed).
-    *
-    * The point of this over a standard button callback is firstly to avoid mouse configuration for
-    * the GUI, and secondly to give the pointer position at the time of the event, not the time the
-    * callback gets called.
-    */
-    void addMouseClickCallback (MouseClickCallback dg) {
-        mouseClickCallbacks ~= dg;
-    }
-
-    /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
-    *
-    * Other types of event functions may be added. Returns true if the event was used, false if not
-    * or no config was available. Hmm... doesn't seem very useful, but has practically no cost.
-    *
-    * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should
-    * be fine.
-    */
-    bool opCall (ref SDL_Event event) {
-        /* Non-config events.
-        *
-        * 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 - 1, event.button.y - 1,
-                        event.button.button, event.button.state == SDL_PRESSED);
-                break;
-            
-            case SDL_MOUSEMOTION:
-                mouse_x = event.motion.x - 1;
-                mouse_y = event.motion.y - 1;
-                break;
-            
-            default:
-        }
-        
-        /* No config available, so don't try to access it and segfault.
-        * Don't log a message because this function is called per-event (i.e. frequently).
-        * A message should already have been logged by loadConfig anyway.
-        */
-        if (!config) return false;
-        
-        switch (event.type) {
-            // Keyboard events:
-            case SDL_KEYDOWN:
-            case SDL_KEYUP:
-                outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button;
-                if (p) foreach (outQueue q; *p) {
-                    bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q));
-                }
-                break;
-            
-            // Mouse events:
-            case SDL_MOUSEBUTTONDOWN:
-            case SDL_MOUSEBUTTONUP:
-                // Button events:
-                outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button;
-                if (p) foreach (outQueue q; *p) {
-                    bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q));
-                }
-                break;
-            
-            case SDL_MOUSEMOTION:
-                // Relative motion:
-                outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion;
-                if (p) foreach (outQueue q; *p) {
-                    mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q));
-                }
-                break;
-            
-            // Joystick events:
-            case SDL_JOYBUTTONDOWN:
-            case SDL_JOYBUTTONUP:
-                outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button;
-                if (p) foreach (outQueue q; *p) {
-                    bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q));
-                }
-                break;
-            
-            case SDL_JOYAXISMOTION:
-                outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis;
-                if (p) foreach (outQueue q; *p) {
-                    aEvent (this, event.jaxis.value, readOutQueue(q));
-                }
-                break;
-            
-            case SDL_JOYBALLMOTION:
-                outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion;
-                if (p) foreach (outQueue q; *p) {
-                    mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q));
-                }
-                break;
-            
-            case SDL_JOYHATMOTION:
-                static ubyte[uint] oldJHatVals;		// necessary to store this to know which "axis" changed
-        
-                uint index = (event.jhat.which << 12) | event.jhat.hat;
-                ubyte* oVal_p = index in oldJHatVals;
-                ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED;
-                
-                // Carry out functionality for an axis.
-                void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) {
-                    // Check if there's any change on this axis (if not, nothing to do):
-                    ubyte filter = neg | pos;
-                    if ((oldJHatVal & filter) != (event.jhat.value & filter)) {
-                        // Now we know this axis changed position, so can unset old value and set
-                        // new value (if not centre).
-                        
-                        // Cancel old button status:
-                        if (oldJHatVal & neg) {
-                            outQueue[]* p = (neg_b | index) in config.button;
-                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
-                        } else if (oldJHatVal & pos) {
-                            outQueue[]* p = (pos_b | index) in config.button;
-                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q));
-                        }
-                        
-                        // Set new button status and position:
-                        short position = 0;
-                        if (event.jhat.value & neg) {
-                            position = -32767;
-                            outQueue[]* p = (neg_b | index) in config.button;
-                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
-                        } else if (event.jhat.value & pos) {
-                            position = 32767;
-                            outQueue[]* p = (pos_b | index) in config.button;
-                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q));
-                        }
-                        
-                        // New axis event:
-                        outQueue[]* p = (axis | index) in config.axis;
-                        if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q));
-                    }
-                }
-                
-                // Now run the code for each axis:
-                hatExamine (SDL_HAT_UP, SDL_HAT_DOWN,
-                            Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD);
-                hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT,
-                            Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR);
-                
-                oldJHatVals[index] = event.jhat.value;
-                break;
-            
-            // Other events:
-            default:
-                return false;	// event not used
-        }
-        return true;		// event used
-    }
-    
-    /** Resets relative movement of mice / joystick balls to zero.
-    *
-    * Should be called once-per-frame if these are used, but must be called after their state has
-    * been read (e.g. just before updating the input).
-    */
-    void frameReset () {
-        foreach (rp; relMotion) {
-            rp.x = rp.y = 0.0;
-        }
-    }
-    
-    /** Loads all configs, activating the requested id.
-    *
-    * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
-    *   found.
-    */
-    void loadConfig (char[] profile = "Default") {
-        Config.load("input");	// FIXME: filename
-        Config* c_p = profile in Config.configs;
-        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!");
-        }
-    }
-    
-private:
-    // Static constructor for event stream (fills es_*_fcts tables).
-    static this () {
-        es_b_fcts = [ ES_B.OUT : &es_b_out ];
-        es_a_fcts = [ ES_A.OUT : &es_a_out, ES_A.REVERSE : &es_a_reverse ];
-        es_m_fcts = [ ES_M.OUT : &es_m_out ];
-        
-        logger = Log.getLogger ("mde.input.input.Input");
-    }
-    
-    struct RelPair {	// for mouse/joystick ball motion
-        real x, y;
-        static RelPair opCall (real a, real b) {
-            RelPair ret;
-            ret.x = a;	ret.y = b;
-            return ret;
-        }
-    }
-    
-    static Logger logger;
-
-    Config config;			// Configuration
-    
-    bool[inputID] button;		// Table of button states
-    short[inputID] axis;		// Table of axes states
-    ushort mouse_x, mouse_y;		// Current screen coords of the window manager mouse
-    RelPair[inputID] relMotion;		// Table of relative mouse / joystick ball motions
-    
-    // FIXME: currently no means of removal
-    ButtonCallback[][inputID]	buttonCallbacks;
-    AxisCallback[][inputID]	axisCallbacks;
-    RelMotionCallback[][inputID] relMotionCallbacks;
-    MouseClickCallback[]	mouseClickCallbacks;
-        
-    //BEGIN Event stream functionality
-    /* This section contains functions called on an event, which may modify the event (adjuster
-    * functions), and finally output to one (or more) of the state tables (the event stream).
-    *
-    * Adjuster and other event functions should have a format to fit the ES_X_Func types, for X is B
-    * (button event), A (axis event) or M (mouse relative motion event or joystick ball event).
-    * Adjusters should call one of the xEvent() functions with their output and the remainder of
-    * the readOutQueue.
-    *
-    * To control which adjusters get called and pass parameters, a stack of sorts is used: outQueue.
-    */
-    //BEGIN ES Definitions
-    /* Note: We really want an array, not a stack. We cannot edit the lists, so we can either
-    * copy to a stack or just iterate through it as an array.
-    */
-    alias Config.outQueue outQueue;
-    struct readOutQueue {		// A convenient structure for reading an outQueue item by item.
-        private Config.outQueue _q;		// the queue, stored by reference to the original
-        private uint p = 0;		// current read position (start at beginning)
-    
-        static readOutQueue opCall (Config.outQueue q) {	// Static constructor
-            readOutQueue ret;
-            ret._q = q;
-            return ret;
-        }
-        uint pop () {			// Get the next element and advance. Throws an exception if there isn't another.
-            if (p >= _q.length)
-                throw new InputClassException ("Input: Invalid configuration: incomplete config stack");
-            uint ret = _q[p];
-            ++p;
-            return ret;
-        }
-        debug uint next () {		// Get the next element. No checks; for debug use only.
-            return _q[p];
-        }
-    }
-    
-    // These aliases are for pointers to the event functions.
-    // These need to use "this", but cannot be delegates because they musn't be bound to a
-    // particular instance of Input, hence this must be passed when called.
-    alias void function (Input, bool, readOutQueue) ES_B_Func;
-    alias void function (Input, short, readOutQueue) ES_A_Func;
-    alias void function (Input, short, short, readOutQueue) ES_M_Func;
-    
-    /* These are the codes allowing the config to specify event functions.
-    *
-    * They are organised as defined in doc/input_ID_assignments.
-    */
-    enum ES_B : uint {
-        OUT	= 0x1000u,
-    }
-    enum ES_A : uint {
-        OUT	= 0x1000u,
-        REVERSE	= 0x2000u,
-    }
-    enum ES_M : uint {
-        OUT	= 0x1000u,
-    }
-    //END ES Definitions
-    
-    // ES Data:
-    // These are the tables for looking up which event function to call.
-    static ES_B_Func[uint] es_b_fcts;
-    static ES_A_Func[uint] es_a_fcts;
-    static ES_M_Func[uint] es_m_fcts;
-    
-    //BEGIN ES Functions
-    // These 3 functions pass an event to the appropriate event function (adjuster or output func).
-    // They are used to start and continue an event stream.
-    // They must be static to allow calling from event functions.
-    const EVCONF_ERR = "Invalid configuration: bad event function code";
-    static void bEvent (Input myThis, bool b, readOutQueue s)
-    {
-        ES_B_Func* func = (s.pop() in es_b_fcts);
-        if (func != null) (*func)(myThis, b, s);
-        else throw new InputClassException (EVCONF_ERR);
-    }
-    static void aEvent (Input myThis, short x, readOutQueue s)
-    {
-        ES_A_Func* func = (s.pop() in es_a_fcts);
-        if (func != null) (*func)(myThis, x, s);
-        else throw new InputClassException (EVCONF_ERR);
-    }
-    // Only handles relative output since position-on-screen is not stored with an ID:
-    static void mEvent (Input myThis, short x, short y, readOutQueue s)
-    {
-        ES_M_Func* func = (s.pop() in es_m_fcts);
-        if (func != null) (*func)(myThis, x, y, s);
-        else throw new InputClassException (EVCONF_ERR);
-    }
-    
-    // The remaining functions are the stream functions, for adjusting and outputting an event.
-    // They need to work like non-static functions, but are called via a function pointer, hencne
-    // should be static with their first parameter being instead of this.
-    
-    // Simple output function
-    static void es_b_out (Input myThis, bool b, readOutQueue s) {
-        inputID id = cast(inputID) s.pop();
-                
-        myThis.button[id] = b;
-        
-        ButtonCallback[]* cb_p = id in myThis.buttonCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, b);
-    }
-    // Adjuster to check modifier keys
-    static void es_b_modifier (Input myThis, bool b, readOutQueue s);
-
-    // Simple output function
-    static void es_a_out (Input myThis, short x, readOutQueue s) {
-        inputID id = cast(inputID) s.pop();
-        
-        myThis.axis[id] = x;
-        
-        AxisCallback[]* cb_p = id in myThis.axisCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, x);
-    }
-    
-    // Just reverses an axis's value
-    static void es_a_reverse (Input myThis, short x, readOutQueue s) {
-        aEvent (myThis, -x, s);
-    }
-
-    // Simple output function
-    static void es_m_out (Input myThis, short x, short y, readOutQueue s) {
-        inputID id = cast(inputID) s.pop();
-        
-        myThis.relMotion[id] = RelPair(x,y);
-        
-        RelMotionCallback[]* cb_p = id in myThis.relMotionCallbacks;
-        if (cb_p) foreach (cb; *cb_p) cb (id, x,y);
-    }
-    //END ES Functions
-    //END Event stream functionality
-    
-    
-    /* This unittest covers input.config.Config and input.input.Input for some events of all types.
-    * It is not bullet-proof, and does not cover the steam-functions other than the direct output
-    * ones (largely because these may get added at any time and tested via input tests).
-    *
-    * What it does test:
-    *	Events of all types.
-    *	Callbacks of all types.
-    *	Status set from events for all types (button,axis,relMotion).
-    *
-    * It relies on config loaded from a file (dependant on where input bindings are loaded from;
-    * currently conf/input.mtt).
-    */
-    debug (mdeUnitTest) unittest {
-        Input ut = new Input();
-        ut.loadConfig ("UnitTest");
-            
-        int[6] counters;	// counters for callbacks
-        // Should become: [2,2,0,2,1,1]
-            
-        //BEGIN Set up some callbacks
-        ut.addButtonCallback (0x03F0, delegate void(inputID id, bool status) {
-            assert (status == !counters[0]);	// true first call, false next
-            counters[0] += 1;
-        });
-        ut.addButtonCallback (0x06F0, delegate void(inputID id, bool status) {
-            assert (status == !counters[1]);	// true first call, false next
-            counters[1] += 1;
-        });
-        ut.addButtonCallback (0x07F0, delegate void(inputID id, bool status) {
-            counters[2] += 1;
-        });
-        ut.addAxisCallback (0x22F0, delegate void(inputID id, short x) {
-            assert (x == (counters[3] ? 0 : 32767));
-            counters[3] += 1;
-        });
-        ut.addRelMotionCallback (0x11F0, delegate void(inputID id, real x, real y) {
-            assert (x == 14.0);
-            assert (y == -1.0);
-            counters[4] += 1;
-        });
-        ut.addMouseClickCallback (delegate void(ushort x, ushort y, ubyte b, bool s) {
-            assert (x == 291);
-            assert (y == 10010);
-            assert (b == 3);
-            assert (s == false);
-            counters[5] += 1;
-        });
-        //END Set up some callbacks
-                        
-        //BEGIN Post a lot of events
-        SDL_Event e;
-            
-        // F11 down:
-        e.type = SDL_KEYDOWN;
-        e.key.state = SDL_PRESSED;
-        e.key.keysym.sym = 292;	// SDLK_F11
-        ut(e);
-        // Right mouse button up:
-        e.type = SDL_MOUSEBUTTONUP;
-        e.button.button = 3;	// SDL_BUTTON_RIGHT
-        e.button.state = SDL_RELEASED;
-        e.button.x = 291;
-        e.button.y = 10010;
-        ut(e);
-        // Mouse motion:
-        e.type = SDL_MOUSEMOTION;
-        e.motion.x = 63;
-        e.motion.y = 44;
-        e.motion.xrel = 14;
-        e.motion.yrel = -1;
-        ut(e);
-        // Joystick 2 button 5 down:
-        e.type = SDL_JOYBUTTONDOWN;
-        e.jbutton.which = 2;
-        e.jbutton.button = 5;
-        e.jbutton.state = SDL_PRESSED;
-        ut(e);
-        // Same button released:
-        e.jbutton.state = SDL_RELEASED;
-        ut(e);
-        // Joystick 1 axis 8 motion:
-        e.type = SDL_JOYAXISMOTION;
-        e.jaxis.which = 1;
-        e.jaxis.axis = 8;
-        e.jaxis.value = 32767;
-        ut(e);
-        // Joystick 22 ball 100 motion:
-        e.type = SDL_JOYBALLMOTION;
-        e.jball.which = 22;
-        e.jball.ball = 100;
-        e.jball.xrel = -21;
-        e.jball.yrel = 1024;
-        ut(e);
-        // Joystick 214 hat 12 DOWN-RIGHT:
-        e.type = SDL_JOYHATMOTION;
-        e.jhat.which = 214;
-        e.jhat.hat = 12;
-        e.jhat.value = SDL_HAT_RIGHTDOWN;
-        ut(e);
-        // Same hat LEFT:
-        e.jhat.value = SDL_HAT_LEFT;
-        ut(e);
-        //END Post a lot of events
-            
-        //BEGIN Check states
-        assert (ut.getButton(0xF0) == true);
-        assert (ut.getButton(0x1F0) == false);
-        assert (ut.getButton(0x2F0) == false);
-        assert (ut.getButton(0x4F0) == false);
-        assert (ut.getButton(0x5F0) == true);
-        assert (ut.getButton(0x6F0) == false);
-        assert (ut.getButton(0x7F0) == false);
-            
-        assert (ut.getAxis(0x20F0) == 32767);
-        assert (ut.getAxis(0x21F0) == -32767);
-        assert (ut.getAxis1(0x22F0) == 0.0);
-            
-        real s,t;
-        ut.getRelMotion(0x10F0, s,t);
-        assert (s == 14.0);
-        assert (t == -1.0);
-        ut.getRelMotion(0x12F0, s,t);
-        assert (s == -21.0);
-        assert (t == 1024.0);
-                                                
-        assert (counters == [2,2,0,2,1,1]);
-        //END Check states
-        
-        logger.info ("Unittest complete.");
-    }
-}
--- a/mde/options.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,330 +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/>. */
-
-/** This module handles stored options, currently all except input maps.
-*
-* The purpose of having all options centrally controlled is to allow generic handling by the GUI
-* and ease saving and loading of values. The Options class is only really designed around handling
-* small numbers of variables for now.
-*/
-module mde.options;
-
-import mde.exception;
-
-import mde.mergetag.Reader;
-import mde.mergetag.Writer;
-import mde.mergetag.DataSet;
-import mde.mergetag.exception;
-import mde.resource.paths;
-
-import tango.scrapple.text.convert.parseTo : parseTo;
-import tango.scrapple.text.convert.parseFrom : parseFrom;
-
-import tango.core.Exception : ArrayBoundsException;
-import tango.util.log.Log : Log, Logger;
-
-/** Base class for handling options.
-*
-* This class itself handles no options and should not be instantiated, but provides a sub-classable
-* base for generic options handling. Also, the static portion of this class tracks sub-class
-* instances and provides loading and saving methods.
-*
-* Each sub-class provides named variables for maximal-speed reading. Local sub-class references
-* should be used for reading variables, and via the addOptionsClass() hook will be loaded from
-* files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
-* not be saved; use, for example, Options.setBool(...).
-*
-* Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
-* char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
-* built-in support in Options is only for bool, int and char[] types (a float type may get added).
-* Further to this, a generic class is used to store all options which have been changed, and if any
-* have been changed, is merged with options from the user conf dir and saved on exit.
-*/
-class Options : IDataSection
-{
-    // No actual options are stored by this class. However, much of the infrastructure is
-    // present since it need not be redefined in sub-classes.
-    
-    // The "pointer lists":
-    protected bool*  [ID]   optsBool;
-    protected char[]*[ID]   optsCharA;
-    protected int*   [ID]   optsInt;
-    
-    //BEGIN Mergetag loading/saving code
-    void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "bool") {
-            bool** p = id in optsBool;
-            if (p !is null) **p = parseTo!(bool) (dt);
-        } else if (tp == "char[]") {
-            char[]** p = id in optsCharA;
-            if (p !is null) **p = parseTo!(char[]) (dt);
-        } else if (tp == "int") {
-            int** p = id in optsInt;
-            if (p !is null) **p = parseTo!(int) (dt);
-        }
-    }
-    void writeAll (ItemDelg dlg) {
-        foreach (ID id, bool*   val; optsBool)  dlg ("bool"  , id, parseFrom!(bool  ) (*val));
-        foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
-        foreach (ID id, int*    val; optsInt)   dlg ("int"   , id, parseFrom!(int   ) (*val));
-    }
-    //END Mergetag loading/saving code
-    
-    //BEGIN Static
-    /** Add an options sub-class to the list for loading and saving.
-    *
-    * Call from static this() (before Init calls load()). */
-    static void addOptionsClass (Options c, char[] i)
-    in {    // Trap a couple of potential coding errors:
-        assert (c !is null);    // Instance must be created before calling addOptionsClass
-        assert (((cast(ID) i) in subClasses) is null);  // Don't allow a silent replacement
-    } body {
-        subClasses[cast(ID) i] = c;
-        subClassChanges[cast(ID) i] = new OptionsGeneric;
-    }
-    
-    /** Set option symbol of Options class subClass to val.
-    *
-    * Due to the way options are handled generically, string IDs must be used to access the options
-    * via hash-maps, which is a little slower than direct access but necessary since the option
-    * must be changed in two separate places. */
-    private static const ERR_MSG = "Options.setXXX called with incorrect parameters!";
-    static void setBool (char[] subClass, char[] symbol, bool val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
-        
-        try {
-            *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
-        }
-    }
-    static void setInt (char[] subClass, char[] symbol, int val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
-        
-        try {
-            *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
-        }
-    }
-    static void setCharA (char[] subClass, char[] symbol, char[] val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
-        
-        try {
-            *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
-        }
-    }
-    
-    // Track all sections for saving/loading/other generic handling.
-    static Options[ID] subClasses;
-    static OptionsGeneric[ID] subClassChanges;
-    static bool changed = false;    // any changes at all, i.e. do we need to save?
-    
-    /* Load/save options from file.
-    *
-    * If the file doesn't exist, no reading is attempted (options are left at default values).
-    */
-    private static const fileName = "options";
-    private static const MT_LOAD_EXC = "Loading options aborted:";
-    static void load () {
-        // Check it exists (if not it should still be created on exit).
-        // Don't bother checking it's not a folder, because it could still be a block or something.
-        if (!confDir.exists (fileName)) return;
-        
-        try {
-            IReader reader;
-            reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
-            reader.dataSecCreator = delegate IDataSection(ID id) {
-                /* Recognise each defined section, and return null for unrecognised sections. */
-                Options* p = id in subClasses;
-                if (p !is null) return *p;
-                else return null;
-            };
-            reader.read;
-        } catch (MTException e) {
-            logger.fatal (MT_LOAD_EXC);
-            logger.fatal (e.msg);
-            throw new optionsLoadException ("Mergetag exception (see above message)");
-        }
-    }
-    static void save () {
-        if (!changed) return;   // no changes to save
-        
-        DataSet ds = new DataSet();
-        foreach (id, sec; subClassChanges) ds.sec[id] = sec;
-        
-        // Read locally-stored options
-        try {
-            IReader reader;
-            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
-            reader.dataSecCreator = delegate IDataSection(ID id) {
-                return null;    // All recognised sections are already in the dataset.
-            };
-            reader.read;
-        } catch (MTFileIOException) {
-            // File either didn't exist or couldn't be opened.
-            // Presuming the former, this is not a problem.
-        } catch (MTException e) {
-            // Log a message and continue, overwriting the file:
-            logger.error (MT_LOAD_EXC);
-            logger.error (e.msg);
-        }
-        
-        try {
-            IWriter writer;
-            writer = confDir.makeMTWriter (fileName, ds);
-            writer.write();
-        } catch (MTException e) {
-            logger.error ("Saving options aborted! Reason:");
-            logger.error (e.msg);
-        }
-    }
-    
-    private static Logger logger;
-    static this() {
-        logger = Log.getLogger ("mde.options");
-    }
-    //END Static
-    
-    //BEGIN Templates
-    template store(A...) {
-        alias A a;
-    }
-    template decRecurse(char[] A, B...) {
-        static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B);
-        else                 const char[] decRecurse = A;
-    }
-    template decBool(A...) {
-        const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n";
-    }
-    template decCharA(A...) {
-        const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n";
-    }
-    template decInt(A...) {
-        const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n";
-    }
-    template aaRecurse(char[] A, B...) {
-        static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B);
-        else                 const char[] aaRecurse = "\""~A~"\"[]:&"~A;
-    }
-    template aaBool(A...) {
-        const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    template aaInt(A...) {
-        const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    template aaCharA(A...) {
-        const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n";
-    }
-    //END Templates
-}
-
-/* Special class to store all locally changed options, whatever the section. */
-class OptionsGeneric : Options {
-    // 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.
-    bool[] bools;
-    int[] ints;
-    char[][] strings;
-    
-    this () {}
-    
-    void setBool (ID id, bool x) {
-        bool** p = id in optsBool;
-        if (p !is null) **p = x;
-        else {
-            bools ~= x;
-            optsBool[id] = &bools[$-1];
-        }
-    }
-    void setInt (ID id, int x) {
-        int** p = id in optsInt;
-        if (p !is null) **p = x;
-        else {
-            ints ~= x;
-            optsInt[id] = &ints[$-1];
-        }
-    }
-    void setCharA (ID id, char[] x) {
-        char[]** p = id in optsCharA;
-        if (p !is null) **p = x;
-        else {
-            strings ~= x;
-            optsCharA[id] = &strings[$-1];
-        }
-    }
-    
-    //BEGIN Mergetag loading/saving code
-    // Reverse priority: only load symbols not currently existing
-    void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "bool") {
-            if ((id in optsBool) is null) {
-                bools ~= parseTo!(bool) (dt);
-                optsBool[id] = &bools[$-1];
-            }
-        } else if (tp == "char[]") {
-            if ((id in optsCharA) is null) {
-                strings ~= parseTo!(char[]) (dt);
-                optsCharA[id] = &strings[$-1];
-            }
-            char[]** p = id in optsCharA;
-            if (p !is null) **p = parseTo!(char[]) (dt);
-        } else if (tp == "int") {
-            if ((id in optsInt) is null) {
-                ints ~= parseTo!(int) (dt);
-                optsInt[id] = &ints[$-1];
-            }
-        }
-    }
-    //END Mergetag loading/saving code
-}
-
-/* NOTE: These Options classes use templates to ease inserting contents.
-*
-* Each entry has an I18nTranslation entry; see data/L10n/ClassName.mtt for descriptions.
-*
-* To create a new class, just copy and paste anywhere and adjust.
-*/
-
-/** A home for all miscellaneous options, at least for now. */
-OptionsMisc miscOpts;
-class OptionsMisc : Options {
-    alias store!("useThreads") BOOL;
-    alias store!("logLevel") INT;
-    alias store!("L10n") CHARA;
-    
-    mixin (decBool!(BOOL.a));
-    mixin (decInt!(INT.a));
-    mixin (decCharA!(CHARA.a));
-    
-    this () {
-        mixin (aaBool!(BOOL.a));
-        mixin (aaInt!(INT.a));
-        mixin (aaCharA!(CHARA.a));
-    }
-    
-    static this() {
-        miscOpts = new OptionsMisc;
-        Options.addOptionsClass (miscOpts, "misc");
-    }
-}
--- a/mde/scheduler/Init.d	Tue Apr 29 18:10:58 2008 +0100
+++ b/mde/scheduler/Init.d	Wed Apr 30 18:05:56 2008 +0100
@@ -22,11 +22,11 @@
  *************************************************************************************************/
 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.init2;     // This module is responsible for setting up some init functions.
+import mde.scheduler.initFunctions;
 import mde.scheduler.exception;
 
-import mde.options;
+import mde.Options;
 import paths = mde.resource.paths;
 import mde.exception;
 
--- a/mde/scheduler/Init2.d	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +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/>. */
-
-/** 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	Tue Apr 29 18:10:58 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +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/>. */
-
-/** This module is responsible for calling all init functions.
-*
-* It is also responsible for setting up all scheduled functions for now.
-
-* 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[]". */
-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;
-}
-
-/** Represents all functions to be called for a particular init stage. */
-struct InitStage
-{
-    struct InitFunction {
-        void delegate() func;       // the actual function
-        char[] name;                // it's name;
-    }
-    
-    /** Add a function to be called during this init stage.
-    *
-    * Called in order added when not threaded (reverse order for cleanup).
-    *
-    * 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 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 = [];
-}
-
-InitStage init;     // all functions called during init (all should be thread-safe)
-InitStage cleanup;  // all functions called during cleanup (all should be thread-safe)
-
-package:
-bool initFailure = false;   // set on failure (throwing through threads isn't a good idea)
-
-private:
-//Logger logger;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/init2.d	Wed Apr 30 18:05:56 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:";
++/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/initFunctions.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,74 @@
+/* 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 responsible for calling all init functions.
+*
+* It is also responsible for setting up all scheduled functions for now.
+
+* 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[]". */
+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;
+}
+
+/** Represents all functions to be called for a particular init stage. */
+struct InitStage
+{
+    struct InitFunction {
+        void delegate() func;       // the actual function
+        char[] name;                // it's name;
+    }
+    
+    /** Add a function to be called during this init stage.
+    *
+    * Called in order added when not threaded (reverse order for cleanup).
+    *
+    * 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 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 = [];
+}
+
+InitStage init;     // all functions called during init (all should be thread-safe)
+InitStage cleanup;  // all functions called during cleanup (all should be thread-safe)
+
+package:
+bool initFailure = false;   // set on failure (throwing through threads isn't a good idea)
+
+private:
+//Logger logger;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/sdl.d	Wed Apr 30 18:05:56 2008 +0100
@@ -0,0 +1,191 @@
+/* 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/>. */
+
+/** Just a temporary place to put SDL Init and Video stuff.
+*/
+module mde.SDL;
+
+import mde.scheduler.initFunctions;
+import mde.input.joystick;
+import mde.Options;
+import mde.gl.basic;
+import global = mde.global;
+
+import tango.util.log.Log : Log, Logger;
+import tango.stdc.stringz;
+
+import derelict.sdl.sdl;
+
+private Logger logger;
+static this() {
+    logger = Log.getLogger ("mde.SDL");
+    
+    init.addFunc (&initSdlAndGl, "initSdlAndGl");
+}
+
+private uint flags = 0;
+
+void initSdlAndGl() {   // init func
+    // Initialise SDL
+    if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
+        logger.fatal ("SDL initialisation failed:");
+        char* msg = SDL_GetError ();
+        logger.fatal (msg ? fromStringz(msg) : "no reason available");
+        
+        setInitFailure ();
+        return;
+    }
+    
+    debug logger.trace ("SDL initialised");
+    
+    // Must be called after SDL has been initialised, so cannot be a separate Init function.
+    openJoysticks ();                   // after SDL init
+    cleanup.addFunc (&cleanupSDL, "cleanupSDL");
+
+    setupWindow();
+}
+
+void setupWindow() {    // indirect init func (depends on initSdlAndGl)
+    // Window creation flags and size
+    flags = SDL_OPENGL;
+    if (vidOpts.hardware) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
+    else flags |= SDL_SWSURFACE;
+    int w, h;
+    if (vidOpts.fullscreen) {
+        flags |= SDL_FULLSCREEN;
+        w = vidOpts.screenW;
+        h = vidOpts.screenH;
+    }
+    else {
+        if (vidOpts.resizable) flags |= SDL_RESIZABLE;
+        if (vidOpts.noFrame) flags |= SDL_NOFRAME;
+        w = vidOpts.windowW;
+        h = vidOpts.windowH;
+    }
+    
+    // OpenGL attributes
+    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,    8);
+    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,  8);
+    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,   8);
+    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
+    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
+    
+    // Open a window
+    if (SDL_SetVideoMode (w, h, 32, flags) is null) {
+        logger.fatal ("Unable to set video mode:");
+        char* msg = SDL_GetError ();
+        logger.fatal (msg ? fromStringz(msg) : "no reason available");
+        
+        setInitFailure ();
+        return;
+    }
+    
+    // Now (must be done after GL context is created) try to load later version:
+    /+ No later GL features are currently used.
+    try {
+        DerelictGL.loadVersions(GLVersion.Version21);
+    } catch (DerelictException de) {
+        logger.fatal ("Loading OpenGL version > 1.1 failed:");
+        logger.fatal (de.msg);
+        
+        setInitFailure ();
+        return;
+    }
+    +/
+    
+    // OpenGL stuff:
+    glSetup();
+    setProjection (w, h);
+    
+    // Window-manager settings
+    SDL_WM_SetCaption (toStringz ("mde"), null);
+    // SDL_WM_GrabInput (use later)
+}
+
+void resizeWindow (int w, int h) {
+    if (vidOpts.fullscreen) {
+        Options.setInt ("video", "screenW", w);
+        Options.setInt ("video", "screenH", h);
+    } else {
+        Options.setInt ("video", "windowW", w);
+        Options.setInt ("video", "windowH", h);
+    }
+    
+    if (SDL_SetVideoMode (w, h, 32, flags) is null) {
+        logger.fatal ("Unable to reset video mode:");
+        char* msg = SDL_GetError ();
+        logger.fatal (msg ? fromStringz(msg) : "no reason available");
+        
+        global.run = false;
+    }
+    
+    // Reset the projection and viewport
+    setProjection (w, h);
+}
+
+void cleanupSDL () {    // cleanup func
+    closeJoysticks();
+    SDL_Quit();
+}
+
+    /+ Load of info-printing stuff (currently doesn't have a use)
+    // Print a load of info:
+    logger.info ("Available video modes:");
+    char[128] tmp;
+    SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
+    if (modes is null) logger.info ("None!");
+    else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
+    else {
+    for (uint i = 0; modes[i] !is null; ++i) {
+    logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h));
+    }
+    }
+    
+    SDL_VideoInfo* vi = SDL_GetVideoInfo ();
+    if (vi !is null) {
+    logger.info ("Video info:");
+    logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
+    logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem));
+    
+    if (vi.vfmt !is null) {
+    logger.info ("Best video mode:");
+    logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel));
+    }
+    }
+    +/
+
+
+/** All video options. */
+OptionsVideo vidOpts;
+class OptionsVideo : Options {
+    alias store!("fullscreen","hardware","resizable","noFrame") BOOL;
+    alias store!("screenW","screenH","windowW","windowH") INT;
+    //alias store!() CHARA;
+    
+    mixin (decBool!(BOOL.a));
+    mixin (decInt!(INT.a));
+    //mixin (decCharA!(CHARA.a));
+    
+    this () {
+        mixin (aaBool!(BOOL.a));
+        mixin (aaInt!(INT.a));
+        //mixin (aaCharA!(CHARA.a));
+    }
+    
+    static this() {
+        vidOpts = new OptionsVideo;
+        Options.addOptionsClass (vidOpts, "video");
+    }
+}