changeset 75:25cb7420dc91

A massive overhaul/rewrite for the gui's data management and setup code. Currently much that was working is broken. imde's classes are created in a static this instead of mde's main. gl setup code moved from gl/basic.d to gl/draw.d mergetag.DefaultData: now HIGH_LOW priority instead of LOW_HIGH. Reduced type list to only used types; small fix for indent function. setup.paths: new NoFileException thrown instead of MTFileIOException
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 28 Jul 2008 18:17:48 +0100
parents cee261eba249
children 65780e0e48e6
files codeDoc/gui/GUI notes.txt codeDoc/jobs.txt codeDoc/todo.txt data/conf/gui.mtt mde/gl/basic.d mde/gl/draw.d mde/gui/Gui.d mde/gui/IGui.d mde/gui/WidgetData.d mde/gui/WidgetManager.d mde/gui/exception.d mde/gui/renderer/IRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/Window.d mde/gui/widget/createWidget.d mde/gui/widget/miscWidgets.d mde/imde.d mde/lookup/Options.d mde/lookup/Translation.d mde/mde.d mde/mergetag/DefaultData.d mde/setup/init2.d mde/setup/paths.d mde/setup/sdl.d
diffstat 26 files changed, 847 insertions(+), 583 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/gui/GUI notes.txt	Mon Jul 07 15:54:47 2008 +0100
+++ b/codeDoc/gui/GUI notes.txt	Mon Jul 28 18:17:48 2008 +0100
@@ -5,18 +5,12 @@
 * means done
 
 GUI:
-->  Basic OpenGL code to:
-    ->* create orthographic projection
-    ->* draw boxes
-    ->  maybe more (text, textures, ...)
 ->  Windows with size & position
     ->  position from Gui
 ->  Widgets:
     ->* minimum size but expandable, auto-set
         ->  no ability to resize yet except from config files
     ->  scripted widgets
-->  Text rendering
-    -> text library?
 ->  Drag & drop
     ->  click/drag start triggers a callback on the widget
         ->  when button is released, callback:
--- a/codeDoc/jobs.txt	Mon Jul 07 15:54:47 2008 +0100
+++ b/codeDoc/jobs.txt	Mon Jul 28 18:17:48 2008 +0100
@@ -9,6 +9,7 @@
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
 Also see todo.txt and FIXME/NOTE comment marks.
+4   struct (via tuple) support for parseTo/From. Requires rewriting with static if (is T : type).
 4   Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file)
 4   Not guaranteed to catch up-click ending callback! Appears not to be a problem...
 4   OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
--- a/codeDoc/todo.txt	Mon Jul 07 15:54:47 2008 +0100
+++ b/codeDoc/todo.txt	Mon Jul 28 18:17:48 2008 +0100
@@ -2,13 +2,9 @@
 License: GNU General Public License version 2 or later (see COPYING)
 
 
-* means done:
-
 GUI:
 ->  Widgets:
     ->  rethink how widgets are created and receive creation data, so that they don't have to be created by the Window
-    ->* minimum size but expandable, auto-set
-    ->* grid "layout" widgets
     ->  scripted widgets
     ->  decent rendering/theme system
     ->  lists from content lists
@@ -23,7 +19,7 @@
     ->  widgets can receive int and string data types
 ->  possibilities
     ->  per-widget merging (i.e. separate tag(s) for each widget's data)?
-        ->  if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resolting are not created
+        ->  if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resulting are not created
         ->  if design changes in a base file, while the old design was modified in a derived file, will the result be sane?
         ->  if a locally modified gui is updated upstream (so the base files change), should:
             ->  the local modifications persist?
--- a/data/conf/gui.mtt	Mon Jul 07 15:54:47 2008 +0100
+++ b/data/conf/gui.mtt	Mon Jul 28 18:17:48 2008 +0100
@@ -1,5 +1,9 @@
 {MT01}
-!<char[]|Renderer="Simple">
+<char[]|Renderer="Simple">
+<char[]|Design="Basic">
+{Basic}
+<WidgetData|root=[0x4010,200,200],"">
+!{
 {W1}
 <int|x=30>
 <int|y=80>
@@ -13,3 +17,4 @@
 <int|x=20>
 <int|y=100>
 <int[][int]|widgetData=[0:[0xB005,0,0xB04000]]>
+}
\ No newline at end of file
--- a/mde/gl/basic.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gl/basic.d	Mon Jul 28 18:17:48 2008 +0100
@@ -13,7 +13,7 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** Some basic OpenGL code for setting up a projection and drawing.
+/** Some basic OpenGL code for drawing.
 *
 * Everything here is really intended as makeshift code to enable GUI development. */
 module mde.gl.basic;
@@ -22,43 +22,6 @@
 
 import tango.time.Time;     // TimeSpan (type only; unused)
 
-//BEGIN GL & window setup
-void glSetup () {
-    glDisable(GL_LIGHTING);
-    glDisable(GL_DEPTH_TEST);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glEnable(GL_TEXTURE_2D);
-    glShadeModel(GL_SMOOTH);
-    
-    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
-    
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-    
-    // Used for font rendering:
-    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    //NOTE: wrap mode may have an effect, but shouldn't be noticed...
-}
-
-void setProjection (int w, int h) {
-    glMatrixMode (GL_PROJECTION);
-    glLoadIdentity ();
-    
-    glViewport (0,0,w,h);
-    
-    // Make the top-left the origin (see gui/GUI notes.txt):
-    // Note that this only affects vertex operations − direct rasterisation operations are
-    // unaffected!
-    glOrtho (0.0,w, h,0.0, -1.0, 1.0);
-    
-    glMatrixMode(GL_MODELVIEW);
-}
-//END GL & window setup
-
 //BEGIN Drawing utils
 // Simple drawing commands for use by GUI
 // (temporary)
--- a/mde/gl/draw.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gl/draw.d	Mon Jul 28 18:17:48 2008 +0100
@@ -13,12 +13,13 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** The OpenGL draw loop.
+/** The OpenGL draw loop and some basic OpenGL code to set up a projection.
 *
 * Everything here is really intended as makeshift code to enable GUI development. */
 module mde.gl.draw;
 
-import mde.gui.Gui;
+import mde.gui.WidgetManager;
+import mde.imde;
 
 import derelict.sdl.sdl;
 import derelict.opengl.gl;
@@ -32,6 +33,46 @@
     logger = Log.getLogger ("mde.gl.draw");
 }
 
+//BEGIN GL & window setup
+void glSetup () {
+    glDisable(GL_LIGHTING);
+    glDisable(GL_DEPTH_TEST);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glEnable(GL_TEXTURE_2D);
+    glShadeModel(GL_SMOOTH);
+    
+    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
+    
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    
+    // Used for font rendering:
+    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    //NOTE: wrap mode may have an effect, but shouldn't be noticed...
+}
+
+void setProjection (int w, int h) {
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity ();
+    
+    glViewport (0,0,w,h);
+    
+    // Make the top-left the origin (see gui/GUI notes.txt):
+    // Note that this only affects vertex operations − direct rasterisation operations are
+    // unaffected!
+    glOrtho (0.0,w, h,0.0, -1.0, 1.0);
+    
+    glMatrixMode(GL_MODELVIEW);
+    
+    // The gui is tied to this viewport.
+    gui.setSize (w,h);
+}
+//END GL & window setup
+
 //BEGIN Drawing loop
 // Temporary draw function
 void draw (TimeSpan) {
--- a/mde/gui/Gui.d	Mon Jul 07 15:54:47 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +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 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.Ifaces;
-import mde.gui.widget.Window;
-import mde.gui.exception;
-
-// For adding the input event callbacks and requesting redraws:
-import imde = mde.imde;
-import mde.input.Input;
-import mde.scheduler.Scheduler;
-
-// 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.mergetag.Writer;
-import mde.setup.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. */
-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_LOW, null, true);
-            reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
-                return new Window (id);
-            };
-            reader.read;
-        } catch (Exception e) {
-            logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):");
-            logger.error (e.msg);
-            throw new GuiException ("Failure parsing config file");
-        }
-        
-        // Get the renderer
-        char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]);
-        if (p is null || *p is null) {
-            logger.warn ("no renderer specified: defaulting to Simple");
-            rendName = "Simple";
-        }
-        else
-            rendName = *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);	// if this fails, the window (but nothing else) won't work
-                windows ~= w;		// only add if load successful
-            } catch (Exception e) {
-                logger.error ("Window failed to load: " ~ e.msg);
-            }
-        }
-        
-        imde.input.addMouseClickCallback(&clickEvent);
-        imde.input.addMouseMotionCallback(&motionEvent);
-    }
-    
-    void save (char[] fileName) {
-        mt.DataSet ds = new mt.DataSet;
-        
-        // Add header:
-        ds.header = new mt.DefaultData;
-        ds.header.Arg!(char[])["Renderer"] = rendName;
-        
-        // Add windows to be saved:
-        foreach (window; windows)
-            ds.sec [window.name] = window;
-        
-        try {   // Save
-            IWriter writer;
-            writer = confDir.makeMTWriter (fileName, ds);
-            writer.write;
-        } catch (mt.MTException e) {
-            logger.error ("Saving GUI failed:");
-            logger.error (e.msg);
-            
-            return;
-        }
-    }
-    //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_reverse (w; windows)    // Draw, starting with back-most window.
-            w.draw;
-    }
-    
-    /** For mouse click events.
-    *
-    * Sends the event on to the relevant windows and all click callbacks. */
-    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
-        debug scope (failure)
-                logger.warn ("clickEvent: failed!");
-        
-        // NOTE: buttons receive the up-event even when drag-callbacks are in place.
-        foreach (dg; clickCallbacks)
-            if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;      // See IGui.addClickCallback's documentation
-        
-        foreach (i,w; windows) {
-            IWidget widg = w.getWidget (cast(wdabs)cx,cast(wdabs)cy);
-            if (widg !is null) {
-                // Bring to front
-                windows = w ~ windows[0..i] ~ windows[i+1..$];
-                
-                widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
-                requestRedraw;	// in case we've only moved to front
-                return;     // only pass to first window
-            }
-        }
-    }
-    
-    /** For mouse motion events.
-    *
-    * Sends the event on to all motion callbacks. */
-    void motionEvent (ushort cx, ushort cy) {
-        debug scope (failure)
-                logger.warn ("motionEvent: failed!");
-        foreach (dg; motionCallbacks)
-            dg (cast(wdabs)cx, cast(wdabs)cy);
-    }
-    
-    //END Methods for external use
-    
-    //BEGIN IGui methods
-    char[] rendererName () {
-        return rendName;
-    }
-    
-    void requestRedraw () {
-        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
-    }
-    
-    void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) {
-        clickCallbacks[dg.ptr] = dg;
-    }
-    void addMotionCallback (void delegate(wdabs, wdabs) dg) {
-        motionCallbacks[dg.ptr] = dg;
-    }
-    void removeCallbacks (void* frame) {
-        clickCallbacks.remove(frame);
-        motionCallbacks.remove(frame);
-    }
-    //END IGui methods
-    
-private:
-    Window[] windows;   // Windows. First window is "on top", others may be obscured.
-    
-    char[] rendName;    // Name of renderer; for saving and creating renderers
-    
-    // callbacks indexed by their frame pointers:
-    bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
-    void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
-}
--- a/mde/gui/IGui.d	Mon Jul 07 15:54:47 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +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/>. */
-
-/** Contains the IGui interface and some basic types used by the gui package. */
-module mde.gui.IGui;
-
-/** Window coordinate and dimension/size type (int).
- *
- * Used to disambiguate between general integers and coordinates; all widget positions/sizes should
- * use this type (or one of the aliases below). */
-typedef int		wdim;
-
-/** Aliases of wdim providing extra information about what their contents hold: absolute position,
- * position relative to the containing widget (wdrel should not be used if relative to anything
- * else), or size. Their use instead of wdim is optional (and in some cases wdim values aren't of
- * any of these types). Also don't use these aliases for variables which may also be used to other
- * effects, e.g. if they can have special values with special meanings. */
-alias wdim	wdabs;
-alias wdim	wdrel;	/// ditto
-alias wdim	wdsize;	/// ditto
-
-/** A pair of wdim variables, and strictly no other data (methods may be added if deemed useful).
- *
- * Potentially usable to return two wdim variables, e.g. width and height, from a function.
- * However, the current usage of out variables looks like it's better. */
-struct wdimPair {
-    wdim x, y;	/// data
-}
-
-/** The Gui interface.
-*
-* This contains the functions for use by Windows, not those for external use (use Gui directly for
-* that). */
-interface IGui
-{
-    /**  Get the GUI's rendererName for creating a renderer (may be overridden by the window). */
-    char[] rendererName ();
-    
-    /** Called by a sub-widget when a redraw is necessary (since drawing may sometimes be done on
-    * event.
-    *
-    * Currently forces the whole Gui to be redrawn. */
-    void requestRedraw ();
-    
-    /** Add a mouse click callback: delegate will be called for all mouse click events recieved.
-     *
-     * The delegate should return true if it accepts the event and no further processing is
-     * required (i.e. the event should not be handled by anything else), false otherwise.
-     * Note that this is not a mechanism to prevent unwanted event handling, and in the future may
-     * be removed (so event handling cannot be cut short). */
-    void addClickCallback (bool delegate (wdabs cx, wdabs cy, ubyte b, bool state) dg);
-    /** Add a mouse motion callback: delegate will be called for all motion events recieved. */
-    void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg);
-    /** Remove all event callbacks with _frame pointer frame. */
-    void removeCallbacks (void* frame);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/WidgetData.d	Mon Jul 28 18:17:48 2008 +0100
@@ -0,0 +1,351 @@
+/* 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/>. */
+
+/*************************************************************************************************
+ * Code to manage the data used to create widgets and save changes to it.
+ * 
+ * When loading, a WidgetDataSet instance is loaded from file and its data used to create the
+ * widgets. An empty WidgetDataChanges instance is also created.
+ * 
+ * If any data requires changing, it is added to the WidgetDataChanges instance, which also
+ * updates the the WidgetDataSet instance used to load the widgets (in case of a re-load from this
+ * data). When the data should be saved, if the WidgetDataChanges instance is not empty, data from
+ * the highest priority (i.e. the user) file is merged into it, preserving both the current
+ * changes and previous changes saved to the use file, before saving to the user file.
+ *************************************************************************************************/
+module mde.gui.WidgetData;
+
+import mde.gui.exception;
+import mde.gui.widget.Ifaces;
+import mde.gui.widget.createWidget;
+
+// 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.mergetag.Writer;
+import mde.setup.paths;
+import mde.mergetag.parse.parseTo;
+import mde.mergetag.parse.parseFrom : parseFrom;
+
+import tango.core.sync.Mutex;
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.WidgetData");
+}
+
+
+/*************************************************************************************************
+ * Contains the code for loading and saving the gui, but not the code for drawing it or handling
+ * user input.
+ * 
+ * This abstract class exists solely for separating out some of the functionality.
+ *************************************************************************************************/
+abstract scope class WidgetLoader : IWidgetManager
+{
+    /** Construct a new widget loader.
+     * 
+     * params:
+     *  fileName = Name of file specifying the gui, excluding path and extension.
+     */
+    protected this (char[] file) {
+        mutex = new Mutex;  // Used on functions intended to be called from outside the gui package.
+        fileName = file;
+    }
+    ~this () {
+        save;
+    }
+    
+    /* Load the widgets' data from the file specified to the CTOR.
+     * 
+     * params:
+     *  allDesigns = Load all sections
+    */
+    private void loadData (bool allDesigns = false) {
+        if (allLoaded || (defaultDesign !is null && allDesigns == false))
+            return; // test if already loaded
+        
+        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!)
+        }
+        
+        // Set up a reader
+        scope IReader reader;
+        try {
+            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true);
+            
+            // Read from the HEADER:
+            // Get the renderer
+            char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]);
+            if (p is null || *p is null) {
+                logger.warn ("No renderer specified: using \"Simple\"");
+                rendName = "Simple";
+            }
+            else
+                rendName = *p;
+            
+            // Get which section to use
+            p = "Design" in reader.dataset.header.Arg!(char[]);
+            if (p is null || *p is null) {
+                logger.warn ("No gui design specified: trying \"Default\"");
+                defaultDesign = "Default";
+            }
+            else
+                defaultDesign = *p;
+            
+            // Read the body:
+            // Load the chosen design
+            reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
+                WidgetDataSet* p = id in data;
+                if (p is null) {
+                    data[id] = new WidgetDataSet;
+                    return *(id in data);
+                }
+                return *p;
+            };
+        
+            if (allDesigns) {
+                reader.read;
+                allLoaded = true;
+            } else
+                reader.read([defaultDesign]);
+        } catch (Exception e) {
+            logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):");
+            logger.error (e.msg);
+            throw new GuiException ("Failure parsing config file");
+        }
+    }
+    
+    /** Load the gui from some design.
+     * 
+     * If a design was previously loaded, its changes are saved first.
+     * 
+     * Params:
+     *  name = Design to load. If null, the default will be loaded.
+     */
+    void loadDesign (char[] name = null) {
+        if (changes !is null)
+            save;       // own lock
+        
+        mutex.lock;
+        scope(exit) mutex.unlock;
+        
+        // Load data (loadData tests if it's already loaded first):
+        if (name is null) {
+            loadData (false);
+            name = defaultDesign;
+        } else
+            loadData (true);
+        
+        
+        // Get data:
+        curData = data[name]; // NOTE: may throw
+        
+        // Get/create a changes section:
+        if (changesDS is null)
+            changesDS = new mt.DataSet;
+        
+        mt.IDataSection* p = name in changesDS.sec;
+        if (p && ((changes = cast(WidgetDataChanges) *p) !is null)) {}
+        else {
+            changes = new WidgetDataChanges (curData);
+            changesDS.sec[name] = changes;
+        }
+        
+        // Create the widgets:
+        createRootWidget;
+    }
+    
+    /** Save changes, if any exist.
+     * 
+     * Is run when the manager is destroyed, but could be run at other times too. */
+    void save () {
+        mutex.lock;
+        scope(exit) mutex.unlock;
+        
+        if (noChanges)
+            return;
+        
+        if (loadUserFile) { // merge entries from user file into current changes
+            try {
+                scope IReader reader = confDir.makeMTReader (
+                        fileName, PRIORITY.HIGH_ONLY, changesDS, true);
+            
+                // Create if necessary, only corresponding to existing designs read:
+                reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
+                    WidgetDataSet* p = id in data;
+                    if (p is null)
+                        throw new Exception ("File has changed since it was loaded!");
+                    return new WidgetDataChanges (*p);
+                };
+        
+                reader.read;
+            } catch (NoFileException) {
+                // No user file exists; not an error.
+            } catch (Exception e) {
+                logger.error ("Error reading "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" prior to saving:");
+                logger.error (e.msg);
+                logger.error ("Overwriting the file.");
+                // Continue...
+            }
+            loadUserFile = false;   // don't need to do it again
+        }
+        
+        try {   // Save
+            IWriter writer;
+            writer = confDir.makeMTWriter (fileName, changesDS);
+            writer.write;
+        } catch (Exception e) {
+            logger.error ("Saving to "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" failed:");
+            logger.error (e.msg);
+            // No point in throwing since it doesn't affect anything else.
+        }
+    }
+    
+    /** Get the names of all designs available. */
+    char[][] designs() {
+        synchronized(mutex) {
+            loadData (true);
+            return data.keys;
+        }
+    }
+    
+    
+    /** Create a widget by ID. */
+    IChildWidget makeWidget (widgetID id) {
+        return createWidget (this, curData[id]);
+    }
+    
+    /** For making changes. */
+    void setData (widgetID id, WidgetData d) {
+        noChanges = false;
+        changes[id] = d;        // also updates WidgetDataSet in data.
+    }
+    
+    /** Second stage of loading the widgets.
+     * 
+     * loadDesign handles the data; this method needs to:
+     * ---
+     * // 1. Create the root widget:
+     * child = makeWidget ("root");
+     * // 2. Set the setSize, e.g.:
+     * child.setWidth  (child.minWidth,  1);
+     * child.setHeight (child.minHeight, 1);
+     * ---
+     */
+    // FIXME: abstract?
+    void createRootWidget();
+    
+protected:
+    final char[] fileName;
+    char[] defaultDesign;       // The design specified in the file header.
+    char[] rendName;    // Name of renderer; for saving and creating renderers
+    
+    // Loaded data, indexed by design name. May not be loaded for all gui designs:
+    scope WidgetDataSet[char[]] data;
+    private bool allLoaded = false;  // applies to data
+    WidgetDataSet curData;      // Current data
+    WidgetDataChanges changes;  // Changes for the current design.
+    scope mt.DataSet changesDS; // changes and sections from user file (used for saving)
+    bool loadUserFile = true;   // still need to load user file for saving?
+    bool noChanges = true;      // do we have any changes to save?
+    
+    scope IChildWidget child;   // The primary widget.
+    
+    Mutex mutex;    // lock on methods for use outside the package.
+}
+
+
+package:
+/*************************************************************************************************
+ * Contains data for all widgets in a GUI.
+ *************************************************************************************************/
+class WidgetDataSet : mt.IDataSection
+{
+    //BEGIN Mergetag code
+    void addTag (char[] tp, mt.ID id, char[] dt) {
+        // Priority is HIGH_LOW. Only load tag if it doesn't already exist.
+        if (tp == "WidgetData" && (id in widgetData) is null) {
+            // Note: is a very simple form of struct deserialization
+            WidgetData data;
+            with(data) {
+                char[][] strs = split (dt);
+                if (strs.length != 2)
+                    throw new ParseException ("Not two components");
+                ints = parseTo!(int[]) (strs[0]);
+                str = parseTo!(char[]) (strs[1]);
+            }
+            widgetData[id] = data;
+        }
+    }
+    // Only WidgetDataChanges is used for writing.
+    void writeAll (ItemDelg dlg) {}
+    //END Mergetag code
+    
+    /** Get the widget data for widget i. */
+    WidgetData opIndex (widgetID i) {
+        return widgetData[i];
+    }
+    
+    // Per-widget data:
+    protected WidgetData[widgetID] widgetData;
+}
+
+/*************************************************************************************************
+ * Contains changes to widget data.
+ * 
+ * Can be read as normal and written.
+ *************************************************************************************************/
+class WidgetDataChanges : WidgetDataSet
+{
+    /**
+     * Params:
+     *  wds = The base WidgetDataSet these changes are applied against. */
+    this (WidgetDataSet wds) {
+        base = wds;
+    }
+    
+    //BEGIN Mergetag code
+    // HIGH_LOW priority of addTag allows existing entries (i.e. the changes) to be preserved while
+    // other entries are read from files.
+    void writeAll (ItemDelg dlg) {
+        foreach (id,data; widgetData) {
+            // Note: is a very simple form of struct serialization
+            with(data) {
+                dlg ("WidgetData", id, 
+                     parseFrom!(int[]) (ints) ~ ',' ~ parseFrom!(char[]) (str) );
+            }
+        }
+    }
+    //END Mergetag code
+    
+    /** Set the widget data for widget i.
+     */
+    void opIndexAssign (WidgetData d, widgetID i) {
+        widgetData[i] = d;
+        base.widgetData[i] = d;
+    }
+    
+    /** Do any changes exist? True if no changes have been stored. */
+    bool none () {
+        return widgetData.length == 0;
+    }
+    
+    protected WidgetDataSet base;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/WidgetManager.d	Mon Jul 28 18:17:48 2008 +0100
@@ -0,0 +1,179 @@
+/* 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 manager class.
+ *
+ * This is the module to use externally to create a graphical user interface (likely also with
+ * content modules).
+ *************************************************************************************************/
+module mde.gui.WidgetManager;
+
+public import mde.gui.WidgetData;
+import mde.gui.widget.Ifaces;
+import mde.gui.renderer.createRenderer;
+
+// For adding the input event callbacks and requesting redraws:
+import imde = mde.imde;
+import mde.input.Input;
+import mde.scheduler.Scheduler;
+
+import tango.core.sync.Mutex;
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.WidgetManager");
+
+    gui = new WidgetManager ("gui");
+}
+
+WidgetManager gui;
+
+
+/*************************************************************************************************
+ * The widget manager.
+ * 
+ * This is responsible for loading and saving an entire gui (although more than one may exist),
+ * controlling the rendering device (e.g. the screen or a texture), and providing user input.
+ * 
+ * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate
+ * them and possibly drop events for some uses, such as if the gui is drawn to a texture.
+ * 
+ * Aside from the IWidgetManager methods, this class should be thread-safe.
+ *************************************************************************************************/
+class WidgetManager : WidgetLoader {
+    /** Construct a new widget manager.
+     * 
+     * params:
+     *  fileName = Name of file specifying the gui, excluding path and extension.
+     */
+    this (char[] file) {
+        super(file);
+        
+        // Events we want to know about:
+        imde.input.addMouseClickCallback(&clickEvent);
+        imde.input.addMouseMotionCallback(&motionEvent);
+    }
+    
+    
+    /** Draw the gui. */
+    void draw() {
+        synchronized(mutex)
+            child.draw;
+    }
+    
+    
+    /** For mouse click events.
+     *
+     * Sends the event on to the relevant windows and all click callbacks. */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+        debug scope (failure)
+            logger.warn ("clickEvent: failed!");
+        mutex.lock;
+        scope(exit) mutex.unlock;
+        
+        // NOTE: buttons receive the up-event even when drag-callbacks are in place.
+        foreach (dg; clickCallbacks)
+            // See IWidgetManager.addClickCallback's documentation:
+            if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
+        
+        /+
+        foreach (i,w; windows) {
+            IWidget widg = w.getWidget (cast(wdabs)cx,cast(wdabs)cy);
+            if (widg !is null) {
+                // Bring to front
+                windows = w ~ windows[0..i] ~ windows[i+1..$];
+                
+                widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
+                requestRedraw;	// in case we've only moved to front
+                return;     // only pass to first window
+            }
+        }+/
+    }
+    
+    /** For mouse motion events.
+     *
+     * Sends the event on to all motion callbacks. */
+    void motionEvent (ushort cx, ushort cy) {
+        debug scope (failure)
+                logger.warn ("motionEvent: failed!");
+        mutex.lock;
+        scope(exit) mutex.unlock;
+        
+        foreach (dg; motionCallbacks)
+            dg (cast(wdabs)cx, cast(wdabs)cy);
+    }
+    
+    
+    void setSize (int x, int y) {
+        mutex.lock;
+        scope(exit) mutex.unlock;
+        
+        w = cast(wdim) x;
+        h = cast(wdim) y;
+        
+        if (child is null)
+            return;     // May not have been created before this is first run.
+        child.setWidth  (w, -1);
+        child.setHeight (h, -1);
+        child.setPosition (0,0);
+    }
+    
+    //BEGIN IWidgetManager methods
+    // These methods are only intended for use within the gui package. They are not necessarily
+    // thread-safe.
+    IRenderer renderer () {
+        assert (rend !is null, "WidgetManager.renderer: rend is null");
+        return rend;
+    }
+    
+    void requestRedraw () {
+        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
+    }
+    
+    void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) {
+        clickCallbacks[dg.ptr] = dg;
+    }
+    void addMotionCallback (void delegate(wdabs, wdabs) dg) {
+        motionCallbacks[dg.ptr] = dg;
+    }
+    void removeCallbacks (IChildWidget frame) {
+        clickCallbacks.remove(cast(void*) frame);
+        motionCallbacks.remove(cast(void*) frame);
+    }
+    //END IWidgetManager methods
+    
+protected:
+    /* Second stage of widget loading. */
+    void createRootWidget () {
+        // The renderer needs to be created on the first load, but not after this.
+        if (rend is null)
+            rend = createRenderer (rendName);
+        
+        child = makeWidget ("root");
+        
+        child.setWidth  (w, -1);
+        child.setHeight (h, -1);
+        child.setPosition (0,0);
+    }
+    
+private:
+    // callbacks indexed by their frame pointers:
+    bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
+    void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
+    IRenderer rend;
+    wdim w,h;       // area available to the widgets
+}
--- a/mde/gui/exception.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/exception.d	Mon Jul 28 18:17:48 2008 +0100
@@ -29,16 +29,8 @@
     }
 }
 
-/// Thrown when something goes wrong while loading a window (usually a data error).
-class WindowLoadException : GuiException
-{
-    this (char[] msg) {
-        super(msg);
-    }
-}
-
 /// Thrown when createWidget or a Widget class's this() is called with invalid data.
-class WidgetDataException : WindowLoadException
+class WidgetDataException : GuiException
 {
     this () {   // Default, by Widget class's this
         super ("Bad widget data");
--- a/mde/gui/renderer/IRenderer.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/renderer/IRenderer.d	Mon Jul 28 18:17:48 2008 +0100
@@ -16,7 +16,8 @@
 /** Interface for the renderer. This is planned to replace decoration.d */
 module mde.gui.renderer.IRenderer;
 
-public import mde.gui.IGui;	// wdim type is used by just about everything including this
+// Put here to avoid circular import.
+typedef int wdim;
 
 /** Interface for renderers.
 *
--- a/mde/gui/widget/Ifaces.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Mon Jul 28 18:17:48 2008 +0100
@@ -13,46 +13,91 @@
 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. */
+/*************************************************************************************************
+ * Widget interfaces.
+ *
+ * Widgets are connected as the nodes of a tree. Widgets know their parent as a IParentWidget
+ * class and their children as IChildWidget classes. The gui manager is a special widget only
+ * implementing IParentWidget; all other widgets must implement IChildWidget and optionally
+ * IParentWidget.
+ *************************************************************************************************/
 module mde.gui.widget.Ifaces;
 
 public import mde.gui.renderer.IRenderer;
-public import mde.gui.IGui;
+
+
+/** Widget ID type. Each ID is unique under this window.
+ *
+ * Type is int since this is the widget data type. */
+alias char[] widgetID;
 
-/** Interface for Window, allowing widgets to call some of Window's methods.
+/** Window coordinate and dimension/size type (int).
  *
- * Contains the methods in Window available for widgets to call on their root.
+ * Used to disambiguate between general integers and coordinates; all widget positions/sizes should
+ * use this type (or one of the aliases below).
+ * 
+ * ---
+ * typedef int wdim;    // Declared in IRenderer to avoid a circular import.
+ * ---
+ * 
+ * Aliases of wdim providing extra information about what their contents hold: absolute position,
+ * position relative to the containing widget (wdrel should not be used if relative to anything
+ * else), or size. Their use instead of wdim is optional (and in some cases wdim values aren't of
+ * any of these types). Also don't use these aliases for variables which may also be used to other
+ * effects, e.g. if they can have special values with special meanings. */
+alias wdim	wdabs;
+alias wdim	wdrel;	/// ditto
+alias wdim	wdsize;	/// ditto
+
+/** A pair of wdim variables, and strictly no other data (methods may be added if deemed useful).
+ *
+ * Potentially usable to return two wdim variables, e.g. width and height, from a function.
+ * However, the current usage of out variables looks like it's better. */
+struct wdimPair {
+    wdim x, y;	/// data
+}
+
+
+/*************************************************************************************************
+ * Common interface for all widgets.
  *
  * Notation:
  *  Positive/negative direction: along the x/y axis in this direction.
- *  Layout widget: a widget containing multiple sub-widges (which hence controls how they are laid
- *  out). */
-interface IWindow : IWidget
+ *  Layout widget: a widget containing multiple sub-widges (which hence controls how they are
+ *  laid out).
+ *************************************************************************************************/
+//NOTE: keep this?
+interface IWidget
 {
-    /** 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.
+}
+
+
+/*************************************************************************************************
+ * Interface for the widget manager.
+ * 
+ * This class handles widget rendering, input, loading and saving.
+ *************************************************************************************************/
+interface IWidgetManager : IParentWidget
+{
+    // Loading/saving:
+    /** Create 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. */
-    IWidget makeWidget (widgetID i);
-    
-    /** Get a string from the widgetString associative array. */
-    char[] getWidgetString (int i);
+     * Creates a widget, using the widget data with index id. Widget data is loaded from files,
+     * and per design (multiple gui layouts, called designs, may exist; data is per design).
+     * 
+     * Note: this method is only for "named" widgets; generic widgets instanciated in lists are
+     * created differently. */
+    IChildWidget makeWidget (widgetID id);
     
-    /** Add widget's saveData to the data to be saved, returning it's widgetID. */
-    widgetID addCreationData (IWidget widget);
+    /** Record some changes, for saving. */
+    void setData (widgetID id, WidgetData);
     
-    /** Add a string to the widgetString associative array, returning it's index. */
-    int addWidgetString (char[] str);
-    
-    /** Returns the window's gui. */
-    IGui gui ();
-    
-    /** The widget/window needs redrawing. */
+    // Rendering:
+    /** For when a widget needs redrawing.
+     * 
+     * Must be called because rendering may only be done on events.
+     * 
+     * Currently it just causes everything to be redrawn next frame. */
     void requestRedraw ();
     
     /** Get the window's renderer.
@@ -60,67 +105,84 @@
     * 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 ();
+    
+    
+    // User input:
+    /** Add a mouse click callback: delegate will be called for all mouse click events recieved.
+     *
+     * The delegate should return true if it accepts the event and no further processing is
+     * required (i.e. the event should not be handled by anything else), false otherwise.
+     * 
+     * Note that this is not a mechanism to prevent unwanted event handling, and in the future
+     * may be removed (so event handling cannot be cut short). */
+    void addClickCallback (bool delegate (wdabs cx, wdabs cy, ubyte b, bool state) dg);
+    
+    /** Add a mouse motion callback: delegate will be called for all motion events recieved. */
+    void addMotionCallback (void delegate (wdabs cx, wdabs cy) dg);
+    
+    // FIXME: keyboard callback (letter only, for text input? Also used for setting keybindings though...)
+    
+    /** Remove all event callbacks on this widget (according to the delegate's .ptr). */
+    void removeCallbacks (IChildWidget frame);
 }
 
-/** Interface for widgets.
+
+/*************************************************************************************************
+ * Interface for parent widgets, including the gui manager.
  *
- * Note that Window also implements this interface so that widgets can interact with their parent
- * in a uniform way.
+ * A widget may call these methods on its parent, and on the gui manager.
+ *************************************************************************************************/
+interface IParentWidget : IWidget
+{
+    // NOTE: Likely some day this interface will really be used.
+    // NOTE: What widget is NOT going to implement this? It will probably be inherited.
+}
+
+
+/*************************************************************************************************
+ * Interface for (child) widgets, i.e. all widgets other than the manager.
  *
  * 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.
+ * and is able to communicate with its manager and parent/child widgets as necessary.
  *
- * If a widget is to be creatable by Window.makeWidget, it must be listed in the createWidget
- * module, have a constructor of the following form, and should implement getCreationData().
- * Use Ddoc to explain what initialization data is used.
+ * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the
+ * createWidget module, have a constructor of the following form, and should update it's
+ * creation data as necessary via IWidgetManager.setData().
+ * It should use Ddoc to explain what initialization data is used.
  * ----------------------------------
  * /++ Constructor for a ... widget.
  *  +
  *  + Widget uses the initialisation data:
  *  + [widgetID, x, y]
  *  + where x is ... and y is ... +/
- * this (IWindow window, int[] data);
+ * this (IWidgetManager mgr, WidgetData data);
  * ----------------------------------
- * Where window is the root window (the window to which the widget belongs) 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.
+ * Where mgr is the widget manager and data is
+ * initialisation data. The method should throw a WidgetDataException (created without
+ * parameters) if the data has wrong length or is otherwise invalid.
  *
- * The widget's size should be set either by it's this() method or by the first call to
- * setSize(). setSize() is called on all widgets immediately after their creation, and throwing an
- * exception at this point (but not on later calls to setSize) is an acceptible method of failure.
- */
+ * A parent widget is responsible for setting the size of its children widgets, however it must
+ * satisfy their minimal sizes as available from minWidth() and minHeight(). setWidth() and
+ * setHeight() are called on all widgets after creation.
+ *************************************************************************************************/
 //NOTE: add another this() without the data for default initialization, for the GUI editor?
-interface IWidget
+interface IChildWidget : IWidget
 {
 //BEGIN Load and save
-    /** Called after creating widgets to adjust size & other mutable attributes from saved data.
-     *
-     * As for setSize, setPosition should be called afterwards.
-     *
-     * Each widget should call adjust on each of its sub-widgets in turn with data, each time
-     * replacing data by the return value of the call. It should then take its own mutable data
-     * from the beginning of the array and return the remainder of the array.
-     *
-     * Adjust should handle errors gracefully by reverting to default values and not throwing.
-     * This is because the creation data and the user's mutable data may be stored separately and
-     * become out-of-sync during an update. */
-    int[] adjust (int[] data);
+    /** Called when the renderer is changed (at least when the changes affect dimensions).
+     * Also called after widget creation, before any other methods are called.
+     * 
+     * Returns: true when widget's dimensions (may) have changed.
+     * 
+     * Should be propegated down to all child widgets. */
+    bool rendererChanged ();
     
-    /** Output data suitible for recreating the widget (data to be passed to this()).
-     *
-     * Function may need to call Window's addCreationData() and addWidgetString() methods to save
-     * other data.
-     *
-     * Creation data is data only changed when the gui is edited. */
-    int[] getCreationData ();
-    
-    /** Output data containing the widget's current adjustments (data to be passed to adjust()).
-     *
-     * Mutable data is data which can be changed during normal gui use, such as the size of
-     * resizible widgets or current tab of a tab widget.
-     *
-     * Should be a concatenation of each sub-widget's mutable data and the widget's own. */
-    int[] getMutableData ();
+    /+ Use when widget editing is available? Requires widgets to know their parents.
+    /** Called when a child widget's size has changed.
+     * 
+     * Should be propegated up to parents. */
+    void childChanged ();
+    +/
 //END Load and save
     
 //BEGIN Size and position
@@ -136,8 +198,11 @@
     wdim minWidth ();
     wdim minHeight ();	/// ditto
     
-    /** Get the current size of the widget. */
-    void getCurrentSize (out wdim w, out wdim h);
+    /** Get the current size of the widget.
+     * 
+     * Deprecated: is it needed now?
+     */
+    deprecated void getCurrentSize (out wdim w, out wdim h);
     
     /** Used to adjust the size.
      *
@@ -148,15 +213,12 @@
      *  	  index as necessary), or +1 (resize from the lowest index, i.e. 0).
      *  	  Most widgets can simply ignore it.
      *
-     * Implementation:
-     * The size should be clamped to the widget's minimal size, i.e. the size set may be larger
-     * than that given by the parameters. Conversely, the size should not be reduced to the
-     * widget's maximal size (if any) but expanded as necessary (alignment to be implemented).
-     * This should be true for both resizable and fixed widgets; fixed widgets may still be scaled
-     * to fill a whole row/column in a layout widget.
+     * If called with dimensions less than minWidth/minHeight return: the widget may set its size
+     * to either the dimension given or its minimal dimension (even though this is larger). If the
+     * larger size is set, events won't be received in the extra area. FIXME: sort out rendering.
+     * Otherwise, the dimensions should always be set exactly.
      *
-     * If the actual size is needed, call getCurrentSize afterwards. setPosition must be called
-     * afterwards if the widget might be a layout widget. */
+     * setPosition must be called after calling either setWidth or setHeight. */
     void setWidth (wdim nw, int dir);
     void setHeight (wdim nh, int dir);	/// ditto
     
@@ -191,3 +253,16 @@
      * part of the widget is visible: scroll bars or a hidden window. */
     void draw ();
 }
+
+
+/*************************************************************************************************
+ * The data type all widgets creatable by the widget manager receive on creation.
+ * 
+ * Conversion code to/from MT tags is contained in the addTag and writeAll methods of
+ * WidgetDataSet and WidgetDataChanges.
+ *************************************************************************************************/
+struct WidgetData
+{
+    int[]   ints;	// An array of integer data
+    char[]  str;	// One string
+}
--- a/mde/gui/widget/TextWidget.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/TextWidget.d	Mon Jul 28 18:17:48 2008 +0100
@@ -68,11 +68,11 @@
      * [widgetID, contentID, colour]
      * where contentID is an ID for the string ID of the contained content
      * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
-    this (IWindow wind, int[] data) {
-        if (data.length != 3) throw new WidgetDataException;
-        text.set (wind.getWidgetString(data[1]), data[2]);
+    this (IWidgetManager mgr, WidgetData data) {
+        if (data.length != 2) throw new WidgetDataException;
+        text.set (data.str, data[1]);
         text.getDimensions (mw, mh);
-        super (wind,data);
+        super (mgr,data);
     }
     
     void draw () {
--- a/mde/gui/widget/Widget.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/Widget.d	Mon Jul 28 18:17:48 2008 +0100
@@ -28,32 +28,17 @@
 * This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a
 * useful basic implementation for widgets. Widgets need not inherit these (they only need implement
 * IWidget); they are simply provided for convenience and to promote code reuse. */
-abstract class Widget : IWidget
+abstract class Widget : IChildWidget
 {
 //BEGIN Load and save
-    // Base this(). All widgets must check data.length is correct before calling this method.
-    // The widget ID is saved to widgetType, for correct saving.
-    this (IWindow wind, int[] data) {
-        window = wind;
-        widgetType = data[0];
+    // Base this() for child Widgets.
+    this (IWidgetManager mgr, WidgetData data) {
+        this.mgr = mgr;
     }
     
-    // Most widgets don't need to do adjustments based on mutable data, however they usually do
-    // still need to set their size.
-    int[] adjust (int[] data) {
-        setWidth (0,-1);
-        setHeight (0,-1);
-        return data;
-    }
-    
-    // Widget type should always be the first value. Any widget using extra creation data will need
-    // to reimplemnt this method.
-    int[] getCreationData () {
-        return [widgetType];
-    }
-    // Most widgets don't use mutable data.
-    int[] getMutableData () {
-        return [];
+    // Very basic implementation which assumes the renderer cannot affect the widget's size.
+    bool rendererChanged () {
+        return false;
     }
 //END Load and save
     
@@ -69,7 +54,7 @@
         return mh;
     }
     
-    void getCurrentSize (out wdim cw, out wdim ch) {
+    deprecated void getCurrentSize (out wdim cw, out wdim ch) {
         cw = w;
         ch = h;
     }
@@ -102,12 +87,11 @@
     
     /* Basic draw method: draw the background (all widgets should do this). */
     void draw () {
-        window.renderer.drawWidgetBack (x,y, w,h);
+        mgr.renderer.drawWidgetBack (x,y, w,h);
     }
     
 protected:
-    final int widgetType;	// the type (stored for saving)
-    IWindow window;		// the enclosing window
+    IWidgetManager mgr;		// the enclosing window
     wdim x, y;			// position
     wdim w, h;			// size
     wdim mw = 0, mh = 0;	// minimal or fixed size, depending on whether the widget is
@@ -122,24 +106,21 @@
      * Widget uses the initialisation data:
      * [widgetID, w, h]
      * where w, h is the fixed size. */
-    this (IWindow wind, int[] data) {
-        mw = cast(wdim) data[1];
-        mh = cast(wdim) data[2];
-        super (wind, data);
+    this (IWidgetManager mgr, WidgetData data) {
+        super (mgr, data);
+        mw = cast(wdim) data.ints[1];
+        mh = cast(wdim) data.ints[2];
         w = mw;
         h = mh;
     }
-    
-    int[] getCreationData () {
-        return [widgetType, mw, mh];
-    }
 }
+
 /** A base for resizable widgets. */
 class SizableWidget : Widget {
     // Check data.length is at least 1 before calling!
     /// Constructor for a completely resizable [blank] widget.
-    this (IWindow wind, int[] data) {
-        super (wind, data);
+    this (IWidgetManager mgr, WidgetData data) {
+        super (mgr, data);
     }
     
     bool isWSizable () {    return true;    }
--- a/mde/gui/widget/Window.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/Window.d	Mon Jul 28 18:17:48 2008 +0100
@@ -97,46 +97,6 @@
         xw = x+w;
         yh = y+h;
     }
-    //BEGIN Mergetag code
-    void addTag (char[] tp, mt.ID id, char[] dt) {
-        // Priority is HIGH_LOW, so don't overwrite data which has already been loaded.
-        if (tp == "int[][int]") {
-            if (id == "widgetData" && widgetData == null) {
-                widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt);
-            }
-        } else if (tp == "char[][int]") {
-            if (id == "widgetStrings" && widgetStrings == null) {
-                widgetStrings = parseTo!(char[][int]) (dt);
-            }
-        } else if (tp == "int[]") {
-            if (id == "mutableData" && mutableData == null) {
-                mutableData = parseTo!(int[]) (dt);
-            }
-        } else if (tp == "int") {
-            if (id == "x" && x == -1) {
-                x = cast(wdim) parseTo!(int) (dt);
-            } else if (id == "y" && y == -1) {
-                y = cast(wdim) parseTo!(int) (dt);
-            }
-        }
-    }
-    void writeAll (ItemDelg dlg)
-    in {
-        assert (widgetData is null, "Window.writeAll: widgetData !is null");
-    } body {
-        /+ NOTE: currently editing is impossible...
-        if (edited) {   // only save the widget creation data if it's been adjusted:
-            addCreationData (widget);   // generate widget save data
-            dlg ("int[][int]", "widgetData", parseFrom!(int[][int]) (widgetData));
-            dlg ("char[][int]", "widgetStrings", parseFrom!(char[][int]) (widgetStrings));
-        }+/
-        // Save mutable data:
-        dlg ("int[]", "mutableData", parseFrom!(int[]) (widget.getMutableData));
-        // Save the window position:
-        dlg ("int", "x", parseFrom!(int) (x));
-        dlg ("int", "y", parseFrom!(int) (y));
-    }
-    //END Mergetag code
     //END Methods for GUI
     
     //BEGIN IWindow methods
@@ -385,11 +345,6 @@
     public char[] name;             // The window's name (id from config file)
     //bool edited = false;            // True if any widgets have been edited (excluding scaling)
     
-    // Data used for saving and loading (null in between):
-    int[][widgetID] widgetData = null;// Data for all widgets under this window
-    int[] mutableData = null;       // Widget's mutable data (adjusted sizes, etc.)
-    char[][int] widgetStrings = null;// Strings by ID; string-typed creation data
-    
     IGui gui_;                      // The gui managing this window
     IRenderer rend;                 // The window's renderer
     
--- a/mde/gui/widget/createWidget.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Mon Jul 28 18:17:48 2008 +0100
@@ -20,21 +20,20 @@
 import mde.gui.exception : WidgetDataException;
 
 // Widgets to create:
-import mde.gui.widget.layout;
+//import mde.gui.widget.layout;
 import mde.gui.widget.miscWidgets;
-import mde.gui.widget.TextWidget;
+//import mde.gui.widget.TextWidget;
 
-/** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
+/** Create a widget of type data[0] (see enum WIDGET_TYPES) under manager mgr, with initialisation
 * data [1..$]. */
-IWidget createWidget (IWindow window, int[] data)
+IChildWidget createWidget (IWidgetManager mgr, WidgetData data)
 in {
-    assert (window !is null, "createWidget: window is null");
+    assert (mgr !is null, "createWidget: mgr is null");
 } body {
-    if (data.length < 1) throw new WidgetDataException ("No widget data");
-    int type = data[0];     // type is first element of data
-    // the whole of data is passed to the Widget
+    if (data.ints.length < 1) throw new WidgetDataException ("No widget data");
+    int type = data.ints[0];    // type is first element of data
     
-    mixin (binarySearch ("type", WIDGETS));
+    mixin (binarySearch ("type", WIDGETS)); // creates widget by type as new *Widget (mgr, data);
     throw new WidgetDataException ("Bad widget type");
 }
 
@@ -78,39 +77,27 @@
 // Only used for binarySearch algorithm generation; must be ordered by numerical values.
 const char[][] WIDGETS = [
         "FixedBlank",
-        "Text",
-        "Int",
+        //"Text",
+        //"Int",
         "SizableBlank",
         "Button",
-        "GridLayout",
-        "TrialContentLayout"];
-
-// Purely to add indentation. Could just return "" without affecting functionality.
-static char[] indent (uint i) {
-    char[] ret;
-    for (; i > 0; --i) ret ~= "  ";
-        // This is not executable at compile time:
-        //ret.length = i * 4;		// number of characters for each indentation
-        //ret[] = ' ';		// character to indent with
-    return ret;
-}
-//char[] indent (uint) {  return "";  }
+        //"GridLayout",
+        /+"TrialContentLayout"+/];
 
 /* Generates a binary search algorithm. */
-char[] binarySearch (char[] var, char[][] consts, int indents = 0) {
+char[] binarySearch (char[] var, char[][] consts) {
     if (consts.length > 3) {
-        return indent(indents) ~ "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~
-                binarySearch (var, consts[0 .. $/2], indents + 1) ~
-                indent(indents) ~ "} else {\n" ~
-                binarySearch (var, consts[$/2 .. $], indents + 1) ~
-                indent(indents) ~ "}\n";
+        return "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~
+                binarySearch (var, consts[0 .. $/2]) ~
+                "} else {\n" ~
+                binarySearch (var, consts[$/2 .. $]) ~
+                "}\n";
     } else {
         char[] ret;
-        ret ~= indent(indents);
         foreach (c; consts) {
             ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~
-                    indent(indents+1) ~ "return new " ~ c ~ "Widget (window, data);\n" ~
-                    indent(indents) ~ "} else ";
+                    "return new " ~ c ~ "Widget (mgr, data);\n" ~
+                    "} else ";
         }
         ret = ret[0..$-6] ~ '\n';  // remove last else
         return ret;
--- a/mde/gui/widget/miscWidgets.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/gui/widget/miscWidgets.d	Mon Jul 28 18:17:48 2008 +0100
@@ -26,30 +26,30 @@
 /// A fixed-size blank widget.
 class FixedBlankWidget : FixedWidget
 {
-    this (IWindow wind, int[] data) {
-        if (data.length != 3) throw new WidgetDataException;
-        super (wind, data);
+    this (IWidgetManager mgr, WidgetData data) {
+        if (data.ints.length != 3) throw new WidgetDataException;
+        super (mgr, data);
     }
     
     void draw () {
         super.draw;
         
-        window.renderer.drawBlank (x,y, w,h);
+        mgr.renderer.drawBlank (x,y, w,h);
     }
 }
 
 /// A completely resizable blank widget (initial size zero).
 class SizableBlankWidget : SizableWidget
 {
-    this (IWindow wind, int[] data) {
-        if (data.length != 1) throw new WidgetDataException;
-        super (wind, data);
+    this (IWidgetManager mgr, WidgetData data) {
+        if (data.ints.length != 1) throw new WidgetDataException;
+        super (mgr, data);
     }
     
     void draw () {
         super.draw;
         
-        window.renderer.drawBlank (x,y, w,h);
+        mgr.renderer.drawBlank (x,y, w,h);
     }
 }
 
@@ -60,21 +60,21 @@
     // pushed is not the same as the button being clicked but not yet released.
     // it is whether the mouse is over the button after being clicked.
     
-    this (IWindow wind, int[] data) {
-        if (data.length != 3) throw new WidgetDataException;
-        super (wind, data);
+    this (IWidgetManager mgr, WidgetData data) {
+        if (data.ints.length != 3) throw new WidgetDataException;
+        super (mgr, data);
     }
     
     void draw () {
-        window.renderer.drawButton (x,y, w,h, pushed);
+        mgr.renderer.drawButton (x,y, w,h, pushed);
     }
     
     void clickEvent (wdabs, wdabs, ubyte b, bool state) {
         if (b == 1 && state == true) {
             pushed = true;
-            window.requestRedraw;
-            window.gui.addClickCallback (&clickWhileHeld);
-            window.gui.addMotionCallback (&motionWhileHeld);
+            mgr.requestRedraw;
+            mgr.addClickCallback (&clickWhileHeld);
+            mgr.addMotionCallback (&motionWhileHeld);
         }
     }
     // Called when a mouse motion/click event occurs while (held == true)
@@ -84,8 +84,8 @@
                 Stdout ("Button clicked!").newline;
             
             pushed = false;
-            window.requestRedraw;
-            window.gui.removeCallbacks (cast(void*) this);
+            mgr.requestRedraw;
+            mgr.removeCallbacks (this);
             
             return true;
         }
@@ -96,6 +96,6 @@
         if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
         else pushed = false;
         if (oldPushed != pushed)
-            window.requestRedraw;
+            mgr.requestRedraw;
     }
 }
--- a/mde/imde.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/imde.d	Mon Jul 28 18:17:48 2008 +0100
@@ -19,6 +19,13 @@
 import mde.input.Input;
 import mde.scheduler.Scheduler;
 
+static this () {
+    // Make these available to all importing modules' static CTORs, as well as during init.
+    input = new Input();
+    mainSchedule = new Scheduler;
+}
+
+
 Scheduler mainSchedule; /// The schedule used by the main loop.
 
 /** Some enums used by per request scheduled functions. */
--- a/mde/lookup/Options.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/lookup/Options.d	Mon Jul 28 18:17:48 2008 +0100
@@ -169,9 +169,8 @@
                     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 (NoFileException) {
+                // No user file exists; not an error.
             } catch (MTException e) {
             	// Log a message and continue, overwriting the file:
                 logger.error ("Loading options aborted:");
--- a/mde/lookup/Translation.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/lookup/Translation.d	Mon Jul 28 18:17:48 2008 +0100
@@ -128,7 +128,7 @@
             while (secsToLoad.length) {                 // while we have sections left to load
                 ID sec = secsToLoad[0];                 // take current section
                 secsToLoad = secsToLoad[1..$];          // and remove from list
-                                
+                
                 if (sec in loadedSecs) continue;        // skip if it's already been loaded
                 loadedSecs[sec] = true;
                 
--- a/mde/mde.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/mde.d	Mon Jul 28 18:17:48 2008 +0100
@@ -45,10 +45,6 @@
         return 0;
     }
     
-    // Create instances now, so they can be used during init (if necessary)
-    input = new Input();
-    mainSchedule = new Scheduler;
-    
     scope Init init;
     try {
         init = new Init(args);	// initialisation
--- a/mde/mergetag/DefaultData.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/mergetag/DefaultData.d	Mon Jul 28 18:17:48 2008 +0100
@@ -25,15 +25,20 @@
 import mde.mergetag.parse.parseFrom : parseFrom;
 
 
-/**
-* Default DataSection class.
-*
-* Supports most of the basic types supported by D (excluding cent/ucent and imaginary/complex
-* types) and array versions of each of these types, plus arrays of strings.
-*
-* Extending the class to support more types, even custom types, shouldn't be particularly difficult
-* provided mde.text.parseTo and mde.text.parseFrom are extended to support the new types.
-*/
+/*************************************************************************************************
+ * Default DataSection class.
+ * 
+ * Currently this is only used for headers, and thus the list of supported types has been
+ * reduced to just those used in headers. Load order is HIGH_LOW, i.e. existing entries aren't
+ * overwritten.
+ * 
+ * It did supports most of the basic types supported by D (excluding cent/ucent and
+ * imaginary/complex types) and array versions of each of these types, plus arrays of strings.
+ *
+ * Extending the class to support more types, even custom types, shouldn't be particularly
+ * difficult provided mde.text.parseTo and mde.text.parseFrom are extended to support the new
+ * types.
+ *************************************************************************************************/
 /* The implementation now uses a fair bit of generic programming. Adjusting the types supported
 * should be as simple as adjusting the list dataTypes, and possibly implemting new conversions in
 * parseFrom and parseTo if you add new types (e.g. for cent or imaginary/complex types, or user types).
@@ -78,7 +83,7 @@
     
     // Purely to add indentation. Could just return "" without affecting functionality.
     static char[] indent (uint i) {
-        char[] ret;
+        char[] ret = "";
         for (; i > 0; --i) ret ~= "  ";
         // This is not executable at compile time:
         //ret.length = i * 4;		// number of characters for each indentation
@@ -101,7 +106,9 @@
             ret ~= indent(indents);
             foreach (c; consts) {
                 ret ~= "if (" ~ var ~ " == \"" ~ c ~ "\") {\n" ~
-                    indent(indents+1) ~ varName(c) ~ "[id] = parseTo!(" ~ c ~ ") (dt);\n" ~
+                    //indent(indents+1) ~ varName(c) ~ "[id] = parseTo!(" ~ c ~ ") (dt);\n" ~
+                    indent(indents+1) ~ "if ((id in "~varName(c)~") is null)\n" ~
+                    indent(indents+2) ~ varName(c)~"[id] = parseTo!(" ~ c ~ ") (dt);\n" ~
                 indent(indents) ~ "} else ";
             }
             ret = ret[0..$-6] ~ '\n';  // remove last else
@@ -139,6 +146,7 @@
     * Note: trying to use Arg!(type) to implicitly refer to Arg!(type).Arg causes compiler errors
     * due to the "alias Name Arg;" statement actually being a mixin.
     */
+    /+ All types previously supported. Most of these weren't used.
     const char[][] dataTypes = ["bool","bool[]",
                                 "byte","byte[]",
                                 "char","char[]","char[][]",
@@ -152,6 +160,8 @@
                                 "uint","uint[]",
                                 "ulong","ulong[]",
                                 "ushort","ushort[]"];
+    +/
+    const char[][] dataTypes = ["char[]", "char[][]"];
     
     mixin (declerations (dataTypes));	// Declare all the variables.
     
--- a/mde/setup/init2.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/setup/init2.d	Mon Jul 28 18:17:48 2008 +0100
@@ -35,7 +35,7 @@
 
 // Modules requiring init code running:
 import imde = mde.imde;
-import mde.gui.Gui;
+import mde.gui.WidgetManager;
 import mde.input.Input;
 import font = mde.font.font;
 
@@ -52,7 +52,7 @@
 void guiLoad () {   // init func
     try {
         font.FontStyle.initialize;
-        gui.load (GUI);
+        gui.loadDesign();
         cleanup.addFunc (&guiSave, "guiSave");
     } catch (Exception e) {
         logger.fatal ("guiLoad failed: " ~ e.msg);
@@ -61,13 +61,12 @@
 }
 void guiSave () {   // cleanup func
     try {
-        gui.save (GUI);
+        gui.save;   // NOTE: can save() just be called from DTOR?
     } catch (Exception e) {
         logger.fatal ("guiSave failed: " ~ e.msg);
         setInitFailure;
     }
 }
-private const GUI = "gui";
 
 void initInput () { // init func
     try {
--- a/mde/setup/paths.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/setup/paths.d	Mon Jul 28 18:17:48 2008 +0100
@@ -73,7 +73,7 @@
     {
         FilePath[] files = getFiles (file, readOrder);
         if (files is null)
-            throw new MTFileIOException ("Unable to find the file: "~file~"[.mtt|mtb]");
+            throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]");
         
         return new mdeReader (files, ds, rdHeader);
     }
@@ -125,11 +125,7 @@
     
 private:
     FilePath[] getFiles (char[] filename, PRIORITY readOrder)
-    in {
-        assert (readOrder == PRIORITY.LOW_HIGH ||
-                readOrder == PRIORITY.HIGH_LOW ||
-                readOrder == PRIORITY.HIGH_ONLY );
-    } body {
+    {
         FilePath[] ret;
         
         debug (mdeUnitTest) {
@@ -150,6 +146,9 @@
                     ret ~= file;
             }
         } else {
+            assert (readOrder == PRIORITY.HIGH_LOW ||
+                    readOrder == PRIORITY.HIGH_ONLY );
+            
             for (int i = pathsLen - 1; i >= 0; --i) {
                 FilePath file = findFile (paths[i]~filename);
                 if (file !is null) {
@@ -367,4 +366,11 @@
     
             PRIORITY rdOrder;
     }
+}
+
+/// Thrown when makeMTReader couldn't find a file.
+class NoFileException : MTFileIOException {
+    this (char[] msg) {
+        super(msg);
+    }
 }
\ No newline at end of file
--- a/mde/setup/sdl.d	Mon Jul 07 15:54:47 2008 +0100
+++ b/mde/setup/sdl.d	Mon Jul 28 18:17:48 2008 +0100
@@ -20,7 +20,7 @@
 import mde.setup.initFunctions;
 import mde.input.joystick;
 import mde.lookup.Options;
-import mde.gl.basic;
+import mde.gl.draw;
 import imde = mde.imde;
 
 import tango.util.log.Log : Log, Logger;