changeset 34:6b4116e6355c

Work on the Gui: some of the framework for drag & drop. Also made Window an IWidget. Implemented getWidget(x,y) to find the widget under this location for IWidgets (but not Gui). Made Window an IWidget and made it work a little more similarly to widgets. Implemented callbacks on the Gui for mouse events (enabling drag & drop, etc.). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 02 May 2008 16:03:52 +0100
parents 6886402c1545
children 928db3c75ed3
files codeDoc/gui/GUI notes.txt codeDoc/jobs.txt data/conf/gui.mtt mde/gl/basic.d mde/gui/Gui.d mde/gui/IGui.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/Input.d mde/scheduler/init2.d
diffstat 13 files changed, 342 insertions(+), 155 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/gui/GUI notes.txt	Thu May 01 10:55:04 2008 +0100
+++ b/codeDoc/gui/GUI notes.txt	Fri May 02 16:03:52 2008 +0100
@@ -17,6 +17,12 @@
     ->  scripted widgets
 ->  Text rendering
     -> text library?
+->  Drag & drop
+    ->  click/drag start triggers a callback on the widget
+        ->  when button is released, callback:
+            ->  finds release location
+            ->  checks if this is a valid drop target
+            ->  if so, acts on it
 
 
 
--- a/codeDoc/jobs.txt	Thu May 01 10:55:04 2008 +0100
+++ b/codeDoc/jobs.txt	Fri May 02 16:03:52 2008 +0100
@@ -3,7 +3,6 @@
 
 
 In progress:
-FreeType implementing...
 
 
 
@@ -49,5 +48,6 @@
 
 
 Done (for git log message):
-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
+Implemented getWidget(x,y) to find the widget under this location for IWidgets (but not Gui).
+Made Window an IWidget and made it work a little more similarly to widgets.
+Implemented callbacks on the Gui for mouse events (enabling drag & drop, etc.).
--- a/data/conf/gui.mtt	Thu May 01 10:55:04 2008 +0100
+++ b/data/conf/gui.mtt	Fri May 02 16:03:52 2008 +0100
@@ -1,10 +1,10 @@
 {MT01}
 <char[]|Renderer="Simple">
 {W1}
-<int|x=0>
-<int|y=0>
+<int|x=30>
+<int|y=80>
 <int[][int]|widgetData=[0:[1003,200,200]]>
 {W2}
 <int|x=150>
 <int|y=200>
-<int[][int]|widgetData=[0:[1002,3,2,2,2,3,3,2,5],2:[1001,150,150],3:[1003,150,150],5:[1002,2,2,6,6,6,6],6:[1003,73,73]]>
+<int[][int]|widgetData=[0:[1002,1,3,2,3,5],2:[1001,150,150],3:[1003,150,150],5:[1002,2,1,6,7],6:[1003,73,73],7:[1003,73,73]]>
--- a/mde/gl/basic.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gl/basic.d	Fri May 02 16:03:52 2008 +0100
@@ -49,13 +49,6 @@
     glColor3f (r, g, b);
 }
 void drawBox (int x, int y, int w, int h) {
-    glBegin (GL_QUADS);
-    {
-        glVertex2i (x, y+h);
-        glVertex2i (x+w, y+h);
-        glVertex2i (x+w, y);
-        glVertex2i (x, y);
-    }
-    glEnd();
+    glRecti(x, y+h, x+w, y);
 }
 //END Drawing utils
--- a/mde/gui/Gui.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/Gui.d	Fri May 02 16:03:52 2008 +0100
@@ -22,6 +22,7 @@
 module mde.gui.Gui;
 
 import mde.gui.IGui;
+import mde.gui.widget.Ifaces;
 import mde.gui.widget.Window;
 import mde.gui.renderer.createRenderer;
 import mde.gui.exception;
@@ -46,9 +47,6 @@
 // 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
@@ -62,8 +60,8 @@
         IReader reader;
         try {
             reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, null, true);
-            reader.dataSecCreator = delegate mt.IDataSection(mt.ID) {
-                return new Window;
+            reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
+                return new Window (id);
             };
             reader.read;
         } catch (mt.MTException e) {
@@ -105,18 +103,35 @@
     * 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();
+        foreach_reverse (w; windows)    // Draw, starting with back-most window.
+            w.draw;
     }
     
-    /** Send an input event.
+    /** For mouse click events.
     *
-    * I.e. send all mouse click events to all active GUIs, which check the coordinates and forward
-    * to any relevent windows. */
+    * Sends the event on to the relevant windows and all click callbacks. */
     void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
-        foreach (w; windows)
-            w.clickEvent (cx,cy,b,state);
+        // NOTE: buttons receive the up-event even when drag-callbacks are in place.
+        foreach (dg; clickCallbacks)
+            dg (cx, cy, b, state);
+        
+        foreach (w; windows) {
+            IWidget widg = w.getWidget (cx,cy);
+            if (widg !is null) {
+                widg.clickEvent (cx,cy,b,state);
+                return;     // only pass to first window
+            }
+        }
     }
+    
+    /** For mouse motion events.
+    *
+    * Sends the event on to all motion callbacks. */
+    void motionEvent (ushort cx, ushort cy) {
+        foreach (dg; motionCallbacks)
+            dg (cx, cy);
+    }
+    
     //END Methods for external use
     
     //BEGIN IGui methods
@@ -126,9 +141,23 @@
     } body {
         return rend;
     }
+    
+    void addClickCallback (void delegate(ushort, ushort, ubyte, bool) dg) {
+        clickCallbacks[dg.ptr] = dg;
+    }
+    void addMotionCallback (void delegate(ushort, ushort) dg) {
+        motionCallbacks[dg.ptr] = dg;
+    }
+    void removeCallbacks (void* frame) {
+        clickCallbacks.remove(frame);
+        motionCallbacks.remove(frame);
+    }
     //END IGui methods
     
 private:
-    Window[] windows;
+    Window[] windows;   // Windows. First window is "on top", others may be obscured.
     IRenderer rend;
+    // callbacks indexed by their frame pointers:
+    void delegate(ushort cx, ushort cy, ubyte b, bool state) [void*] clickCallbacks;
+    void delegate(ushort cx, ushort cy) [void*] motionCallbacks;
 }
--- a/mde/gui/IGui.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/IGui.d	Fri May 02 16:03:52 2008 +0100
@@ -13,16 +13,23 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
+module mde.gui.IGui;
+
+public import mde.gui.renderer.IRenderer;
+
 /** 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 ();
+    
+    /** Add a mouse click callback: delegate will be called for all mouse click events recieved. */
+    void addClickCallback (void delegate (ushort cx, ushort cy, ubyte b, bool state) dg);
+    /** Add a mouse motion callback: delegate will be called for all motion events recieved. */
+    void addMotionCallback (void delegate (ushort cx, ushort cy) dg);
+    /** Remove all event callbacks with _frame pointer frame. */
+    void removeCallbacks (void* frame);
 }
--- a/mde/gui/widget/Ifaces.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Fri May 02 16:03:52 2008 +0100
@@ -17,11 +17,12 @@
 module mde.gui.widget.Ifaces;
 
 public import mde.gui.renderer.IRenderer;
+import mde.gui.IGui;
 
 /** 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
+interface IWindow : IWidget
 {
     /** Widget ID type. Each ID is unique under this window.
     *
@@ -31,9 +32,16 @@
     /** 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);
+    * been created, creates it using the Window's widget creation data.
+    *
+    * Widgets should never be used more than once (must have a unique parent and position!), and
+    * this function is only intended for widgets to get child-widgets, hence a warning is logged
+    * if a widget is asked for more than once. */
+    //NOTE: possibly revise: parent isn't actually used any more
+    IWidget makeWidget (widgetID i, IWidget parent);
+    
+    /** Get the managing Gui. */
+    IGui gui ();
     
     /+ Currently draw-on-event isn't used.
     /** Called by a sub-widget when a redraw is necessary (since drawing may sometimes be done on
@@ -47,11 +55,6 @@
     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
@@ -62,19 +65,37 @@
 *
 * A widget's constructor should have this prototype:
 * ----------------------------------
-* this (IWindow window, IParentWidget parent, int[] data);
+* this (IWindow window, IWidget 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
+interface IWidget
 {
-    /** Draw, starting from given x and y.
+    /** Calculate the minimum size the widget could be shrunk to, taking into account
+     * child-widgets. */
+    void getMinimumSize (out int w, out int h);
+    
+    /** Get the current size of the widget.
      *
-     * 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);
+     * 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);
+    
+    /** Set the current position (i.e. called on init and move). */
+    void setPosition (int x, int y);
+    
+    /** Recursively scan the widget tree to find the widget under (x,y).
+     *
+     * If called on a widget, that widget should assume the location is over itself, and so should
+     * either return itself or the result of calling getWidget on the appropriate child widget.
+     *
+     * In the case of Window this may not be the case; it should check and return null if not under
+     * (x,y).
+     *
+     * Note: use global coordinates (x,y) not coordinates relative to the widget. */
+    IWidget getWidget (int x, int y);
     
     /** Receive a mouse click event.
      *
@@ -84,13 +105,9 @@
      * 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.
+    /** Draw, using the stored values of x and y.
      *
-     * 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);
+     * 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 ();
 }
--- a/mde/gui/widget/Widget.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/widget/Widget.d	Fri May 02 16:03:52 2008 +0100
@@ -17,6 +17,7 @@
 module mde.gui.widget.Widget;
 
 public import mde.gui.widget.Ifaces;
+import mde.gui.IGui;
 import mde.gui.exception;
 
 import gl = mde.gl.basic;
@@ -30,14 +31,6 @@
 */
 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. */
@@ -46,8 +39,28 @@
         h = this.h;
     }
     
+    void setPosition (int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+    
+    /** Return self, since we don't have child widgets and the method wouldn't have been called
+    * unless the location was over us. Valid for all widgets without children. */
+    IWidget getWidget (int,int) {
+        return this;
+    }
+    
+    /** Dummy event method (widget doesn't respond to events) */
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {}
+    
+    /** Basic draw method: draw the background (all widgets should do this) */
+    void draw () {
+        window.renderer.drawWidgetBack (x,y, w,h);
+    }
+    
 protected:
     IWindow window;         // the enclosing window
+    int x, y;               // position
     int w, h;               // size
 }
 
@@ -55,7 +68,7 @@
 /// Draws a box. That's it.
 class BoxWidget : Widget
 {
-    this (IWindow wind, IParentWidget, int[] data) {
+    this (IWindow wind, IWidget, int[] data) {
         if (data.length != 2) throw new WidgetDataException;
         
         window = wind;
@@ -63,7 +76,7 @@
         w = data[0];
         h = data[1];
     }
-    void draw (int x, int y) {
+    void draw () {
         gl.setColor(1f,0f,0f);
         window.renderer.drawBox (x,y, w,h);
     }
@@ -72,9 +85,11 @@
 /// First interactible widget
 class ButtonWidget : Widget
 {
-    bool pushed = false;// true if button is pushed in
+    bool pushed = false;    // true if button is pushed in (visually)
+    // pushed is not the same as the button being clicked but not yet released.
+    // it is whether the mouse is over the button after being clicked.
     
-    this (IWindow wind, IParentWidget, int[] data) {
+    this (IWindow wind, IWidget, int[] data) {
         if (data.length != 2) throw new WidgetDataException;
         
         window = wind;
@@ -83,7 +98,7 @@
         h = data[1];
     }
     
-    void draw (int x, int y) {
+    void draw () {
         if (pushed)
             gl.setColor (1f, 0f, 1f);
         else
@@ -97,7 +112,23 @@
     }
     
     void clickEvent (ushort, ushort, ubyte b, bool state) {
-        if (b == 1) pushed = state; // very basic
+        if (b == 1 && state == true) {
+            pushed = true;
+            window.gui.addClickCallback (&clickWhileHeld);
+            window.gui.addMotionCallback (&motionWhileHeld);
+        }
+    }
+    // Called when a mouse motion/click event occurs while (held == true)
+    void clickWhileHeld (ushort cx, ushort cy, ubyte b, bool state) {
+        if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
+            Stdout ("Button clicked!").newline;
+        
+        pushed = false;
+        window.gui.removeCallbacks (cast(void*) this);
+    }
+    void motionWhileHeld (ushort cx, ushort cy) {
+        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
+        else pushed = false;
     }
 }
 //END Widgets
--- a/mde/gui/widget/Window.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/widget/Window.d	Fri May 02 16:03:52 2008 +0100
@@ -28,6 +28,13 @@
 // not yet implemented:
 //import tango.scrapple.text.convert.parseFrom : parseFrom;
 
+import tango.util.log.Log : Log, Logger;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.widget.Window");
+}
+
 /** GUI Window class
  *
  * A window class instance does two things: (1) specify a region of the screen upon which the window
@@ -39,23 +46,31 @@
 class Window : mt.IDataSection, IWindow
 {
     //BEGIN Methods for GUI
+    this (char[] id) {
+        name = id;
+    }
+    
     /** 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) {
+    void finalise (IGui gui)
+    in {
+        assert (gui !is null, "Window.finalise ("~name~"): gui is null");
+    } body {
         // Check data was loaded:
         if (widgetData is null) throw new WindowLoadException ("No widget data");
         
-        // Create the renderer:
+        gui_ = gui;
         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.
+        widget = makeWidget (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.setPosition (widgetX, widgetY);
         
         widget.getCurrentSize (w,h);// Find the initial size
         w += rend.windowBorder * 2;       // Adjust for border
@@ -64,23 +79,6 @@
         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]") {
@@ -104,17 +102,21 @@
     /** Get/create a widget by ID.
      *
      * Should $(I only) be called internally and by sub-widgets! */
-    IWidget getWidget (widgetID i, IParentWidget parent)
+    IWidget makeWidget (widgetID i, IWidget parent)
     in {
         // widgetData is normally left to be garbage collected after widgets have been created:
-        assert (widgetData !is null, "getWidget: widgetData is null");
+        assert (widgetData !is null, "Window.makeWidget ("~name~"): 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
+        if (p !is null) {   // yes
+            char[128] tmp;
+            logger.warn (logger.format (tmp, "Window.makeWidget ("~name~"): widget {} has multiple uses!", i));
+            return *p;
+        }
+        else {              // no
             int[]* d = i in widgetData;
-            if (d is null) throw new WindowLoadException ("Widget not found");
+            if (d is null) throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
             
             // Throws WidgetDataException (a WindowLoadException) if bad data:
             IWidget widg = createWidget (this, parent, *d);
@@ -123,6 +125,10 @@
         }
     }
     
+    IGui gui () {
+        return gui_;
+    }
+    
     /+void requestRedraw () {
     }+/
     
@@ -131,15 +137,62 @@
     }
     //END IWindow methods
     
-    //BEGIN IParentWidget methods
-    //END IParentWidget methods
+    //BEGIN IWidget methods
+    void getMinimumSize (out int w, out int h) {
+        widget.getMinimumSize (x,y);
+        w += rend.windowBorder * 2;       // Adjust for border
+        h += rend.windowBorder * 2;
+    }
+    void getCurrentSize (out int cw, out int ch) {
+        cw = w;
+        ch = h;
+    }
+    
+    void setPosition (int x, int y) {
+        /+ Note: this is currently unused. Maybe only use it internally?
+        this.x = x;
+        this.y = y;
+        
+        widgetX = x + rend.windowBorder;
+        widgetY = y + rend.windowBorder;
+        
+        widget.setPosition (widgetX, widgetY);
+        +/
+    }
+    
+    IWidget getWidget (int cx, int cy) {
+        if (cx < x || cx >= xw || cy < y || cy >= yh)   // not over window
+            return null;
+        if (cx >= widgetX && cx < xw-rend.windowBorder && cy >= widgetY && cy < yh-rend.windowBorder)
+                                                        // over the widget
+            return widget.getWidget (cx, cy);
+        else                                            // over the window border
+            return this;
+    }
+    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+        //if (cx >= x && cx < xw && cy >= y && cy < yh) { // click on window?
+        // FIXME: repositioning?
+    }
+    
+    void draw () {
+        // background
+        rend.drawWindow (x,y, w,h);
+        
+        // Tell the widget to draw itself:
+        widget.draw();
+    }
+    //END IWidget methods
     
 private:
+    char[] name;                    // The window's name (id from config file)
+    IGui gui_;                      // The gui managing this window
+    
     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
+    // FIXME: revise which parameters are stored once Gui knows window position
     int x,y;                        // Window position
     int w,h;                        // Window size (calculated from Widgets)
     int xw, yh;                     // x+w, y+h (frequent use by clickEvent)
--- a/mde/gui/widget/createWidget.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Fri May 02 16:03:52 2008 +0100
@@ -30,7 +30,7 @@
 
 /** 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)
+IWidget createWidget (IWindow window, IWidget parent, int[] data)
 in {
     assert (window !is null, "createWidget: window is null");
     assert (parent !is null, "createWidget: parent is null");
--- a/mde/gui/widget/layout.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/gui/widget/layout.d	Fri May 02 16:03:52 2008 +0100
@@ -22,7 +22,7 @@
 /// Encapsulates a grid of Widgets
 class GridWidget : Widget
 {
-    this (IWindow wind, IParentWidget, int[] data) {
+    this (IWindow wind, IWidget, int[] data) {
         // Get grid size
         if (data.length < 2) throw new WidgetDataException;
         rows = data[0];
@@ -35,7 +35,7 @@
         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);
+            subWidget = window.makeWidget (data[i+2], this);
         }
         
         getMinimumSize (w,h);   // Calculate the size (current size is not saved)
@@ -57,10 +57,10 @@
         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];
+            uint n = i / cols;  // row
+            if (rowH[n] < widgetH[i]) rowH[n] = widgetH[i];
+            n = i % cols;       // column
+            if (colW[n] < widgetW[i]) colW[n] = widgetW[i];
         }
         
         // rowY / colX
@@ -82,41 +82,50 @@
         w = cum - spacing;      // total width
     }
     
-    void draw (int x, int y) {
-        super.draw (x,y);
+    void setPosition (int x, int y) {
+        this.x = x;
+        this.y = y;
         
-        foreach (i,widget; subWidgets) {
-            widget.draw (x + colX[i % cols], y + rowY[i / cols]);
-        }
+        foreach (i,widget; subWidgets)
+            widget.setPosition (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 relevant widget.
+    IWidget getWidget (int cx, int cy) {
+        if (rows*cols == 0) return this;    // special case
+        
+        int lx = cx - x, ly = cy - y;       // use coords relative to this widget
         
         // 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
+        int i = cols - 1;                   // starting from right...
+        while (lx < colX[i]) {              // decrement while left of this column
+            if (i == 0) return this;        // left of first column
             --i;
-        }                           // now (cx >= colX[i])
-        if (cx >= colX[i] + colW[i]) return;    // between columns
+        }                                   // now (lx >= colX[i])
+        if (lx >= colX[i] + colW[i]) return this;   // between columns
         
         // Find the row;
         int j = rows - 1;
-        while (cy < rowY[j]) {
-            if (j == 0) return;
+        while (ly < rowY[j]) {
+            if (j == 0) return this;
             --j;
         }
-        if (cy >= rowY[j] + rowH[j]) return;
+        if (ly >= rowY[j] + rowH[j]) return this;
         
         // 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];
+        lx -= colX[i];
+        ly -= rowY[j];
         IWidget widg = subWidgets[i + j*cols];
         widg.getCurrentSize (i,j);
-        if (cx < i && cy < j)
-            widg.clickEvent (cx, cy, b, state);
+        if (lx < i && ly < j)
+            return widg.getWidget (cx, cy);
+    }
+    
+    void draw () {
+        super.draw ();
+        
+        foreach (widget; subWidgets)
+            widget.draw ();
     }
     
 protected:
@@ -126,6 +135,6 @@
     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 ] */
+    /* SubWidget order:    [ 0 1 ]
+    *                      [ 2 3 ] */
 }
--- a/mde/input/Input.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/input/Input.d	Fri May 02 16:03:52 2008 +0100
@@ -30,7 +30,44 @@
 
 import tango.util.log.Log : Log, Logger;
 
-/// Class encapsulating all input functionality.
+/** Class encapsulating all input functionality.
+ *
+ * The following methods are provided for Gui mouse input:
+ * ---
+ * void getMouseScreenPos (out uint x, out uint y);
+ * void addMouseClickCallback (MouseClickCallback dg);
+ * void addMouseMotionCallback (MouseMotionCallback dg);
+ * ---
+ *
+ * The following methods are provided for mouse (and joystick ball) relative motion input:
+ * ---
+ * void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0);
+ * void addRelMotionCallback (inputID id, RelMotionCallback dg);
+ * ---
+ *
+ * The following methods are provided for joystick axis input:
+ * ---
+ * short getAxis (inputID id);
+ * real getAxis1 (inputID id);
+ * void addAxisCallback (inputID id, AxisCallback dg);
+ * ---
+ *
+ * The following methods are provided for keyboard, joystick and mouse button input:
+ * ---
+ * bool getButton (inputID id);
+ * void addButtonCallback (inputID id, ButtonCallback dg)
+ * ---
+ *
+ * The following methods are provided for setup & posting events:
+ * ---
+ * bool opCall (ref SDL_Event event);
+ * void frameReset ();
+ * void loadConfig (char[] profile = "Default");
+ * ---
+ ***************************************************/
+// FIXME: remove getMouseScreenPos (no use)?
+// FIXME: add an Axis1Callback similar to getAxis1? Or remove getAxis1 and provide a conversion
+// function?
 class Input
 {
     /// Typedef for all indexes (type is uint).
@@ -38,7 +75,8 @@
     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;
+    alias void delegate(ushort, ushort, ubyte, bool)    MouseClickCallback;
+    alias void delegate(ushort, ushort)                 MouseMotionCallback;
 
     /** Get key status at this ID.
     *
@@ -73,8 +111,7 @@
     * 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.
-    */
+    * positions. */
     void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) {
         RelPair* rp = id in relMotion;
         if (rp) {
@@ -85,7 +122,8 @@
     *
     * Window managers only support one mouse, so there will only be one screen coordinate.
     * Unlike nearly everything else, this is not configurable.
-    */
+    *
+    * Also see addMouseMotionCallback. */
     void getMouseScreenPos (out uint x, out uint y) {
         x = mouse_x;	y = mouse_y;
     }
@@ -94,16 +132,14 @@
 
     /** Adds a callback delegate for key events (both DOWN and UP) with this ID.
     *
-    * Delegate receives event status.
-    */
+    * 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).
-    */
+    * Delegate receives event status (as per what getAxis returns). */
     void addAxisCallback (inputID id, AxisCallback dg) {
         axisCallbacks[id] ~= dg;
     }
@@ -115,8 +151,7 @@
     *
     * (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.)
-    */
+    * function to get new screen coordinates.) */
     void addRelMotionCallback (inputID id, RelMotionCallback dg) {
         relMotionCallbacks[id] ~= dg;
     }
@@ -129,11 +164,18 @@
     *
     * 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.
-    */
+    * callback gets called. */
     void addMouseClickCallback (MouseClickCallback dg) {
         mouseClickCallbacks ~= dg;
     }
+    
+    /** Adds a callback delegate for all mouse motion events.
+    *
+    * Really just for graphical user interfaces. Use addRelMotionCallback for relative motion (for
+    * manipulating 3D views, etc.). */
+    void addMouseMotionCallback (MouseMotionCallback dg) {
+        mouseMotionCallbacks ~= dg;
+    }
 
     /** Feed an SDL_Event struct (only uses if it's a key, mouse or joystick event).
     *
@@ -141,8 +183,7 @@
     * 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.
-    */
+    * be fine. */
     bool opCall (ref SDL_Event event) {
         /* Non-config events.
         *
@@ -163,6 +204,9 @@
             case SDL_MOUSEMOTION:
                 mouse_x = event.motion.x - 1;
                 mouse_y = event.motion.y - 1;
+                
+                foreach (dg; mouseMotionCallbacks)
+                    dg (event.motion.x - 1, event.motion.y - 1);
                 break;
             
             default:
@@ -170,8 +214,7 @@
         
         /* 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.
-        */
+        * A message should already have been logged by loadConfig anyway. */
         if (!config) return false;
         
         switch (event.type) {
@@ -286,8 +329,7 @@
     /** 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).
-    */
+    * been read (e.g. just before updating the input). */
     void frameReset () {
         foreach (rp; relMotion) {
             rp.x = rp.y = 0.0;
@@ -297,8 +339,7 @@
     /** Loads all configs, activating the requested id.
     *
     * Throws: ConfigLoadException if unable to load any configs or the requested config id wasn't
-    *   found.
-    */
+    *   found. */
     void loadConfig (char[] profile = "Default") {
         Config.load("input");	// FIXME: filename
         Config* c_p = profile in Config.configs;
@@ -342,22 +383,22 @@
     AxisCallback[][inputID]	axisCallbacks;
     RelMotionCallback[][inputID] relMotionCallbacks;
     MouseClickCallback[]	mouseClickCallbacks;
+    MouseMotionCallback[]	mouseMotionCallbacks;
         
     //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).
+    * 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.
-    */
+    * 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.
-    */
+    * 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
@@ -389,8 +430,7 @@
     
     /* These are the codes allowing the config to specify event functions.
     *
-    * They are organised as defined in doc/input_ID_assignments.
-    */
+    * They are organised as defined in doc/input_ID_assignments. */
     enum ES_B : uint {
         OUT	= 0x1000u,
     }
@@ -488,8 +528,7 @@
     *	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).
-    */
+    * currently conf/input.mtt). */
     debug (mdeUnitTest) unittest {
         Input ut = new Input();
         ut.loadConfig ("UnitTest");
--- a/mde/scheduler/init2.d	Thu May 01 10:55:04 2008 +0100
+++ b/mde/scheduler/init2.d	Fri May 02 16:03:52 2008 +0100
@@ -69,7 +69,10 @@
                 global.run = false;
             }
         } );
+        
+        // Aught to be added by the gui, but it doesn't know if input exists then.
         global.input.addMouseClickCallback(&gui.clickEvent);
+        global.input.addMouseMotionCallback(&gui.motionEvent);
     } catch (Exception e) {
         logger.fatal ("initInput failed: " ~ e.msg);
         setInitFailure;