# HG changeset patch # User Diggory Hardy # Date 1209740632 -3600 # Node ID 6b4116e6355c14992bc71fb03db1680c65f12645 # Parent 6886402c15459b931415abc1fc3acc74e4c7fb2f 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 diff -r 6886402c1545 -r 6b4116e6355c codeDoc/gui/GUI notes.txt --- 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 diff -r 6886402c1545 -r 6b4116e6355c codeDoc/jobs.txt --- 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.). diff -r 6886402c1545 -r 6b4116e6355c data/conf/gui.mtt --- 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} {W1} - - + + {W2} - + diff -r 6886402c1545 -r 6b4116e6355c mde/gl/basic.d --- 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 diff -r 6886402c1545 -r 6b4116e6355c mde/gui/Gui.d --- 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; } diff -r 6886402c1545 -r 6b4116e6355c mde/gui/IGui.d --- 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 . */ +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); } diff -r 6886402c1545 -r 6b4116e6355c mde/gui/widget/Ifaces.d --- 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 (); } diff -r 6886402c1545 -r 6b4116e6355c mde/gui/widget/Widget.d --- 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 diff -r 6886402c1545 -r 6b4116e6355c mde/gui/widget/Window.d --- 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) diff -r 6886402c1545 -r 6b4116e6355c mde/gui/widget/createWidget.d --- 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"); diff -r 6886402c1545 -r 6b4116e6355c mde/gui/widget/layout.d --- 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 ] */ } diff -r 6886402c1545 -r 6b4116e6355c mde/input/Input.d --- 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"); diff -r 6886402c1545 -r 6b4116e6355c mde/scheduler/init2.d --- 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;