# HG changeset patch # User Diggory Hardy # Date 1225969638 0 # Node ID 2a364c7d82c93121387089c011cc1f313d0b2914 # Parent 9520cc0448e54f227160c469cda65e22d03315d3 Boolean options can be adjusted from the gui now (using a very basic widget). Also some bug-fixes. Fixed a minor bug where layouts with the same id but without shared alignments would be messed up. Tracked down the "nothing trawn until a resize" bug (see jobs.txt). If widgets throw during creation they're now replaced by debug widgets. Function pointers are converted to delegates using a safer method. diff -r 9520cc0448e5 -r 2a364c7d82c9 codeDoc/jobs.txt --- a/codeDoc/jobs.txt Thu Oct 23 17:45:49 2008 +0100 +++ b/codeDoc/jobs.txt Thu Nov 06 11:07:18 2008 +0000 @@ -7,14 +7,12 @@ Bugs: -Sometimes nothing is drawn until a resize, and fonts are blocks? External bug? Info: doesn't happen when limited to one thread. +Sometimes nothing is drawn until a resize, and fonts are blocks. Cause: init-stages always appear to get divided between two threads. If Inpt, Font and SWnd are called by the main thread and not a sub-thread, the bug doesn't occur. A temporary fix is just to set maxThreads=1. A redesign of threading init stages could solve this, but doesn't seem worth the effort right now. 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 Data saving for widgetIDs with multiple instances? -3 Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file) 3 Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour. 3 glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point. 3 Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/FontOptions.mtt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/L10n/FontOptions.mtt Thu Nov 06 11:07:18 2008 +0000 @@ -0,0 +1,4 @@ +{MT01} +{en-GB} + + diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/MiscOptions.mtt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/L10n/MiscOptions.mtt Thu Nov 06 11:07:18 2008 +0000 @@ -0,0 +1,7 @@ +{MT01} +{en-GB} + + + + + diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/OptionsFont.mtt --- a/data/L10n/OptionsFont.mtt Thu Oct 23 17:45:49 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -{MT01} -{en-GB} - - diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/OptionsMisc.mtt --- a/data/L10n/OptionsMisc.mtt Thu Oct 23 17:45:49 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -{MT01} -{en-GB} - - - - - diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/OptionsVideo.mtt --- a/data/L10n/OptionsVideo.mtt Thu Oct 23 17:45:49 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -{MT01} -{en-GB} - - - - - - - - diff -r 9520cc0448e5 -r 2a364c7d82c9 data/L10n/VideoOptions.mtt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/L10n/VideoOptions.mtt Thu Nov 06 11:07:18 2008 +0000 @@ -0,0 +1,10 @@ +{MT01} +{en-GB} + + + + + + + + diff -r 9520cc0448e5 -r 2a364c7d82c9 data/conf/gui.mtt --- a/data/conf/gui.mtt Thu Oct 23 17:45:49 2008 +0100 +++ b/data/conf/gui.mtt Thu Nov 06 11:07:18 2008 +0000 @@ -7,12 +7,12 @@ - - + + - + diff -r 9520cc0448e5 -r 2a364c7d82c9 data/conf/options.mtt --- a/data/conf/options.mtt Thu Oct 23 17:45:49 2008 +0100 +++ b/data/conf/options.mtt Thu Nov 06 11:07:18 2008 +0000 @@ -16,7 +16,7 @@ + - diff -r 9520cc0448e5 -r 2a364c7d82c9 examples/guiDemo.d --- a/examples/guiDemo.d Thu Oct 23 17:45:49 2008 +0100 +++ b/examples/guiDemo.d Thu Nov 06 11:07:18 2008 +0000 @@ -67,7 +67,7 @@ * not allow running components simultaeneously with threads. * Note: probably drawing should start at the beginning of the loop and glFlush()/swapBuffers * be called at the end to optimise. */ - mainSchedule.add (SCHEDULE.DRAW, &Screen.draw); // Draw, per event only. + mainSchedule.add (SCHEDULE.DRAW, &Screen.draw).request = true; // Draw, per event and first frame only. mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true; //END Main loop setup diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/content/Content.d --- a/mde/gui/content/Content.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/content/Content.d Thu Nov 06 11:07:18 2008 +0000 @@ -94,14 +94,20 @@ class BoolContent : ValueContent { - this () {} - this (bool val) { + /** Create a content with _symbol name symbol. */ + this (char[] symbol, bool val = false) { + symb = symbol; v = val; } + /** Adds cb to the list of callback functions called when the value is changed. Returns this. */ + BoolContent addChangeCb (void delegate (char[] symbol,bool value) cb) { + cngCb ~= cb; + return this; + } + /// Get the text. char[] toString (uint i) { - debug logger.trace ("BoolContent.toString"); return (i == 0) ? v ? "true" : "false" : (i == 1) ? name_ : (i == 2) ? desc_ @@ -110,12 +116,17 @@ void opAssign (bool val) { v = val; + foreach (cb; cngCb) + cb(symb, val); } bool opCall () { return v; } - protected bool v; + bool v; //FIXME: should be protected but Options needs to set without calling callbacks +protected: + char[] symb; + void delegate (char[],bool)[] cngCb; // change callbacks } /** Text content. */ diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/content/options.d --- a/mde/gui/content/options.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/content/options.d Thu Nov 06 11:07:18 2008 +0000 @@ -25,12 +25,10 @@ import mde.lookup.Options; import mde.lookup.Translation; -debug { - import tango.util.log.Log : Log, Logger; - private Logger logger; - static this () { - logger = Log.getLogger ("mde.gui.content.options"); - } +import tango.util.log.Log : Log, Logger; +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.content.options"); } class OptionList diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/renderer/IRenderer.d --- a/mde/gui/renderer/IRenderer.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/renderer/IRenderer.d Thu Nov 06 11:07:18 2008 +0000 @@ -76,9 +76,7 @@ * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the * restriction. */ void restrict (wdim x, wdim y, wdim w, wdim h); - - /** See restrict. */ - void relax (); + void relax (); /// ditto /** Draw a window border plus background. */ void drawWindow (wdim x, wdim y, wdim w, wdim h); @@ -92,8 +90,16 @@ /** Draws a button frame, in if pushed == true. */ void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed); - /** Get a TextAdapter to draw some text. */ - TextAdapter getAdapter (char[] text, int colour); + /** Toggle buttons. + * + * These have a fixed size which getToggleSize returns. */ + wdimPair getToggleSize (); + void drawToggle (wdim x, wdim y, bool state, bool pushed); /// ditto + + /** Get a TextAdapter to draw some text. + * + * If no colour is passes, a default is used (white). */ + TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF); /** For drawing text - one instance per string. * diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/renderer/SimpleRenderer.d --- a/mde/gui/renderer/SimpleRenderer.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/renderer/SimpleRenderer.d Thu Nov 06 11:07:18 2008 +0000 @@ -119,6 +119,21 @@ gl.drawBox (x,y, w,h); } + wdimPair getToggleSize () { + wdimPair r; + r.x = 16; + r.y = 16; + return r; + } + void drawToggle (wdim x, wdim y, bool state, bool pushed) { + float c = pushed ? .7f : .5f; + if (state) + gl.setColor (0f, c, 0f); + else + gl.setColor (c, 0f, 0f); + gl.drawBox (x+2,y+2, 12,12); + } + TextAdapter getAdapter (char[] text, int col) { TextAdapter a; a.font = defaultFont; diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/Floating.d --- a/mde/gui/widget/Floating.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/Floating.d Thu Nov 06 11:07:18 2008 +0000 @@ -53,7 +53,7 @@ * Ints supplied may consist of just the widget type or * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords). */ -class FloatingAreaWidget : ParentWidget +class FloatingAreaWidget : AParentWidget { this (IWidgetManager mgr, widgetID id, WidgetData data) { subWidgets.length = data.strings.length; diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/Ifaces.d --- a/mde/gui/widget/Ifaces.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/Ifaces.d Thu Nov 06 11:07:18 2008 +0000 @@ -151,8 +151,8 @@ // NOTE - change? /** Called on all widgets after all widgets have been created in a deepest first order. * - * Must be called before any other methods on the widget, which means this cannot call sub- - * widgets' methods, but finalize can. */ + * finalize must be called before any other methods on the widget, which means this() cannot + * call sub-widgets' methods, but finalize() can. */ void prefinalize (); void finalize (); /// ditto diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/TextWidget.d --- a/mde/gui/widget/TextWidget.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/TextWidget.d Thu Nov 06 11:07:18 2008 +0000 @@ -32,18 +32,14 @@ } } -/// Basic text widget -class TextLabelWidget : Widget +/** Base text widget. + * + * Little use currently except to ease future additions. */ +class ATextWidget : AWidget { - /** Constructor for a widget containing [fixed] content. - * - * Widget uses the initialisation data: - * [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. */ + /** Set the adapter first: + * adapter = mgr.renderer.getAdapter ("string", 0xRRGGBB); */ this (IWidgetManager mgr, widgetID id, WidgetData data) { - WDCheck (data, 2, 1); - adapter = mgr.renderer.getAdapter (data.strings[0], data.ints[1]); adapter.getDimensions (mw, mh); super (mgr, id, data); } @@ -57,26 +53,37 @@ IRenderer.TextAdapter adapter; } + +/// Basic text widget +class TextLabelWidget : ATextWidget +{ + /** Constructor for a widget containing [fixed] content. + * + * Widget uses the initialisation data: + * [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 (IWidgetManager mgr, widgetID id, WidgetData data) { + WDCheck (data, 2, 1); + adapter = mgr.renderer.getAdapter (data.strings[0], data.ints[1]); + super (mgr, id, data); + } +} + /// Basic widget displaying a label from a content. -class ContentLabelWidget : Widget +class ContentLabelWidget : ATextWidget { this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { debug assert (c, "content is null (code error)"); WDCheck (data, 3, 0); content = c; + if (!content) throw new ContentException (); index = data.ints[1]; adapter = mgr.renderer.getAdapter (content.toString(index), data.ints[2]); - adapter.getDimensions (mw, mh); super (mgr, id,data); } - void draw () { - super.draw(); - adapter.draw (x,y); - } - protected: - IRenderer.TextAdapter adapter; IContent content; int index; } diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/Widget.d --- a/mde/gui/widget/Widget.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/Widget.d Thu Nov 06 11:07:18 2008 +0000 @@ -19,6 +19,9 @@ * This module contains some base widget classes suitable for widget classes to inherit. However, * inheriting one of them is by no means necessary for a widget so long as the IWidget interface * is implemented. + * + * Abstract widget classes have an 'A' prepended to the name, similar to the 'I' convention for + * interfaces. *************************************************************************************************/ module mde.gui.widget.Widget; @@ -41,11 +44,11 @@ * 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 : IChildWidget +abstract class AWidget : IChildWidget { //BEGIN Load and save // Base this() for child Widgets. - this (IWidgetManager mgr, widgetID id, WidgetData) { + protected this (IWidgetManager mgr, widgetID id, WidgetData) { this.mgr = mgr; this.id = id; } @@ -155,7 +158,7 @@ /************************************************************************************************* * An abstract base widget class for parent widgets. *************************************************************************************************/ -abstract class ParentWidget : Widget +abstract class AParentWidget : AWidget { this (IWidgetManager mgr, widgetID id, WidgetData data) { super (mgr, id, data); @@ -177,7 +180,7 @@ } /** A base for fixed-size widgets taking their size from the creation data. */ -class FixedWidget : Widget { +class FixedWidget : AWidget { // Check data.length is at least 3 before calling! /** Constructor for a fixed-size [blank] widget. * @@ -186,15 +189,13 @@ * where w, h is the fixed size. */ this (IWidgetManager mgr, widgetID id, WidgetData data) { super (mgr, id, data); - mw = cast(wdim) data.ints[1]; - mh = cast(wdim) data.ints[2]; - w = mw; - h = mh; + w = mw = cast(wdim) data.ints[1]; + h = mh = cast(wdim) data.ints[2]; } } /** A base for resizable widgets. */ -class SizableWidget : Widget { +class SizableWidget : AWidget { // Check data.length is at least 1 before calling! /// Constructor for a completely resizable [blank] widget. this (IWidgetManager mgr, widgetID id, WidgetData data) { @@ -204,3 +205,59 @@ bool isWSizable () { return true; } bool isHSizable () { return true; } } + +/** For pressable buttons. + * + * Normally overriding classes implement this, draw and activated. */ +abstract class AButtonWidget : AWidget +{ + protected this (IWidgetManager mgr, widgetID id, WidgetData data) { + super (mgr, id, data); + } + + /// May be over-ridden. Pushed is true if the button has been pushed and not released. + void draw () { + mgr.renderer.drawButton (x,y, w,h, pushed); + } + + /// Handles the down-click + void clickEvent (wdabs, wdabs, ubyte b, bool state) { + if (b == 1 && state == true) { + pushed = true; + mgr.requestRedraw; + mgr.addClickCallback (&clickWhilePushed); + mgr.addMotionCallback (&motionWhilePushed); + } + } + + /// Called when a mouse click event occurs while held; handles up-click + bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) { + if (b == 1 && state == false) { + if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event + activated(); + + pushed = false; + mgr.requestRedraw; + mgr.removeCallbacks (cast(void*) this); + + return true; + } + return false; + } + /// Called when a mouse motion event occurs while held; handles pushing in/out on hover + void motionWhilePushed (wdabs cx, wdabs cy) { + bool oldPushed = pushed; + if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true; + else pushed = false; + if (oldPushed != pushed) + mgr.requestRedraw; + } + + /// The action triggered when the button is clicked... + void activated (); + +protected: + bool pushed = false; /// True if button is pushed in (visually) +} + + diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/createWidget.d --- a/mde/gui/widget/createWidget.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/createWidget.d Thu Nov 06 11:07:18 2008 +0000 @@ -27,6 +27,7 @@ import mde.gui.widget.layout; import mde.gui.widget.miscWidgets; import mde.gui.widget.TextWidget; +import mde.gui.widget.miscContent; import mde.gui.widget.Floating; import tango.util.log.Log : Log, Logger; @@ -56,11 +57,15 @@ } int type = data.ints[0]; // type is first element of data - //pragma (msg, binarySearch ("type", WIDGETS)); - mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]); + try { + //pragma (msg, binarySearch ("type", WIDGETS)); + mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]); + // Not returned a new widget or thrown: + logger.error ("Bad widget type: {}; creating a debug widget instead",type); + } catch (Exception e) { + logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg); + } - // Not returned a new widget... - logger.error ("Bad widget type: {}; creating a debug widget instead.",type); return new DebugWidget (mgr, id, data); } @@ -77,6 +82,7 @@ private: /// Widget types. enum WIDGET_TYPE : int { + FUNCTION = 0x2000, // Function called instead of widget created (no "Widget" appended to fct name) TAKES_CONTENT = 0x4000, // Flag indicates widget's this should be passed an IContent reference. PARENT = 0x8000, // widget can have children; not used by code (except in data files) @@ -95,6 +101,10 @@ ContentLabel = TAKES_CONTENT | 0x20, TextLabel = 0x21, + // content editables: 0x30 + editContent = FUNCTION | TAKES_CONTENT | 0x30, + BoolContent = TAKES_CONTENT | 0x31, + GridLayout = TAKES_CONTENT | PARENT | 0x100, TrialContentLayout = PARENT | 0x110, @@ -111,6 +121,8 @@ "Button", "TextLabel", "ContentLabel", + "BoolContent", + "editContent", "TrialContentLayout", "FloatingArea", "GridLayout"]; @@ -128,7 +140,9 @@ foreach (c; consts) { ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ " debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n" ~ - " static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~ + " static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.FUNCTION)\n" ~ + " return " ~ c ~ " (mgr, id, data, content);\n" ~ + " else static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~ " return new " ~ c ~ "Widget (mgr, id, data, content);\n" ~ " else\n" ~ " return new " ~ c ~ "Widget (mgr, id, data);\n" ~ diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/layout.d --- a/mde/gui/widget/layout.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/layout.d Thu Nov 06 11:07:18 2008 +0000 @@ -106,7 +106,7 @@ cols = 1; if ((rows = optsList.list.length) > 0) { // Get all sub-widgets - subWidgets.length = rows*cols; + subWidgets.length = rows; foreach (i, c; optsList.list) { subWidgets[i] = mgr.makeWidget (data.strings[0], c); } @@ -139,7 +139,7 @@ * * The grid has no border but has spacing between widgets. *************************************************************************************************/ -abstract class GridWidget : ParentWidget +abstract class GridWidget : AParentWidget { //BEGIN Creation & saving /** Partial constructor for a grid layout widget. @@ -274,8 +274,7 @@ widget.draw (); } -private: - //BEGIN Cache calculation functions +package: /* Calculations which need to be run whenever a new sub-widget structure is set * (i.e. to produce cached data calculated from construction data). * Also need to be re-run if the renderer changes. @@ -320,9 +319,8 @@ row.sizable[i / cols] = true; } } - //END Cache calculation functions - +private: void setColWidth (myIt i, wdim w, int dir) { for (myIt j = 0; j < rows; ++j) { subWidgets[i + cols*j].setWidth (w, dir); @@ -385,7 +383,7 @@ * Cells are not directly interacted with, but minimal widths for each column are passed, and * callback functions are used to adjust the width of any column. *************************************************************************************************/ -class AlignColumns +package class AlignColumns { /** Instance returned will be shared with any other widgets of same widgetID. * @@ -395,6 +393,7 @@ if (p) { if (p.minWidth.length != columns) throw new GuiException ("AlignColumns: no. of columns varies between sharing widgets (code error)"); + //logger.trace ("Shared alignment for: "~id); return *p; } else { auto a = new AlignColumns (columns); @@ -417,35 +416,52 @@ * Widths should be set after calling, as on creation. */ void reset (myIt columns) { if (columns < 1) - throw new GuiException("AlignColumns: created with <1 column"); + throw new GuiException("AlignColumns: created with <1 column (code error)"); minWidth = new wdim[columns]; sizable = new bool[columns]; width = null; // enforce calling setWidths after this firstSizable = -1; lastSizable = -1; - spare = 0; } - /** Initialize widths as minimal widths. */ - void setWidths () { + /** Initialize widths, either from minWidths or from supplied list, checking validity. + * + * Also calculates first/lastSizable from sizable, overall minimal width and column positions. + */ + void setWidths (wdim[] data = null) { if (!width) { - width = minWidth.dup; - initCalc; - } - } - /** Initialize widths from supplied list, checking validity. */ - void setWidths (wdim[] data) { - if (!width) { - // Set to provided data: - width = data; - // And check sizes are valid: - foreach (i, m; minWidth) { - // if fixed width or width is less than minimum: - if (!sizable[i] || width[i] < m) { - width[i] = m; + if (data) { + debug assert (data.length == minWidth.length, "setWidths called with bad data length (code error)"); + width = data.dup; // data is shared by other widgets with same id so must be .dup'ed + // And check sizes are valid: + foreach (i, m; minWidth) { + if (!sizable[i] || width[i] < m) // if width is fixed or less than minimum + width[i] = m; + } + } else + width = minWidth.dup; + + /* Calculate the minimal width of all columns plus spacing. */ + mw = spacing * cast(wdim)(minWidth.length - 1); + foreach (imw; minWidth) + mw += imw; + + genPositions; + + foreach (i,s; sizable) { + if (s) { + firstSizable = i; + goto gotFirst; } } - initCalc; + return; // none resizable - don't search for lastSizable + gotFirst: + foreach_reverse (i,s; sizable) { + if (s) { + lastSizable = i; + return; // done + } + } } } @@ -462,18 +478,16 @@ /** Get the row/column of relative position l. * * returns: - * -i if in space to left of col i, or i if on col i, or -(num cols + 1) if in $(I spare) - * space to right of last column. */ + * -i if in space to left of col i, or i if on col i. */ myDiff getCell (wdim l) { + debug assert (width, "AlignColumns not initialized when getCell called (code error)"); myDiff i = minWidth.length - 1; // starting from right... while (l < pos[i]) { // decrement while left of this column debug assert (i > 0, "getCell: l < pos[0] (code error)"); --i; } // now (l >= pos[i]) - if (l >= pos[i] + width[i]) { // between columns or in spare space after last column - debug assert (i+1 < minWidth.length || - l < pos[i] + width[i] + spare, - "getCell: l >= total width (code error)"); + if (l >= pos[i] + width[i]) { // between columns + debug assert (i+1 < minWidth.length, "getCell: l >= total width (code error)"); return -i - 1; // note: i might be 0 so cannot just return -i } return i; @@ -483,6 +497,7 @@ * * nw should be at least the minimal width. */ wdim resizeWidth (wdim nw, int dir) { + debug assert (width, "AlignColumns not initialized when resizeWidth called (code error)"); if (nw < mw) { debug logger.warn ("Widget dimension set below minimal"); nw = mw; @@ -490,14 +505,13 @@ if (nw == w) return w; wdim diff = nw - w; - if (firstSizable == -1) { - spare += diff; - w = nw; - } else - adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir); + if (firstSizable == -1) + diff = adjustCellSizes (diff, minWidth.length-1, -1); + else + diff = adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir); debug if (nw != w) { - logger.trace ("resizeWidth to {} failed, new width: {}",nw,w); + logger.trace ("resizeWidth on {} to {} failed, new width: {}, diff {}, firstSizable {}, columns {}",cast(void*)this, nw,w, diff, firstSizable, minWidth.length); /+ Also print column widths & positions: logger.trace ("resizeWidth to {} failed! Column dimensions and positions:",nw); foreach (i,w; width) @@ -511,8 +525,8 @@ * This and resizeCols are for moving dividers between cells. */ bool findResizeCols (wdim l) { resizeU = -getCell (l); // potential start for upward-resizes - if (resizeU <= 0 || resizeU > minWidth.length) - return true; // not on a space between cells or in spare space after last cell + if (resizeU <= 0) + return true; // not on a space between cells resizeD = resizeU - 1; // potential start for downward-resizes while (!sizable[resizeU]) { // find first actually resizable column (upwards) @@ -548,33 +562,6 @@ } } - /** Intitialization triggered by setWidths. - * - * Calculates first/lastSizable from sizable, minimal width and positions. */ - private void initCalc () { - /* Calculate the minimal width of all columns plus spacing. */ - mw = spacing * cast(wdim)(minWidth.length - 1); - foreach (w; minWidth) - mw += w; - - genPositions; - foreach (i,s; sizable) { - if (s) { - firstSizable = i; - goto gotFirst; - } - } - return; // none resizable - don't search for lastSizable - gotFirst: - - foreach_reverse (i,s; sizable) { - if (s) { - lastSizable = i; - return; // done - } - } - } - /* Generate position infomation for each column and set w. */ private void genPositions () { pos.length = minWidth.length; @@ -632,16 +619,13 @@ dg(i, width[i], incr); // rd is remainder to decrease by - - bool it = true; // iterate (force first time) - while (it) { + do { i += incr; if (i < 0 || i >= minWidth.length) { // run out of next cells diff -= rd; // still had rd left to decrease break aCSwhile; // exception: Array index out of bounds } - it = !sizable[i]; // iterate again if row/col isn't resizable - } + } while (!sizable[i]) // iterate again if row/col isn't resizable } } // else no adjustment needed (diff == 0) @@ -664,14 +648,13 @@ /** Current width, relative position (for each column) * - * Treat as READ ONLY! */ + * Treat as READ ONLY outside this class! */ wdim[] width; // only adjusted within the class wdim[] pos; /// ditto - protected: +protected: myDiff resizeD, // resizeCols works down from this index (<0 if not resizing) resizeU; // and up from this index wdim spacing; // used by genPositions (which cannot access the layout class's data) - wdim spare; // fixed size only: extra blank space filler wdim w,mw; // current & minimal widths /* indicies of the first/last resizable column (negative if none are resizable). */ myDiff firstSizable = -1, lastSizable = -1; // set by calcFLSbl diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/miscContent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/widget/miscContent.d Thu Nov 06 11:07:18 2008 +0000 @@ -0,0 +1,73 @@ +/* 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 . */ + +/** Some content widgets. */ +module mde.gui.widget.miscContent; + +import mde.gui.widget.Widget; +import mde.gui.widget.TextWidget; +import mde.gui.exception; +import mde.gui.renderer.IRenderer; + +import mde.gui.content.Content; + +/// Chooses the most appropriate content editing widget +IChildWidget editContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { + if (cast(BoolContent) c) + return new BoolContentWidget(mgr,id,data,c); + else // generic uneditable option + return new DisplayContentWidget(mgr,id,data,c); +} + +/// Just displays the content +class DisplayContentWidget : ATextWidget +{ + this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { + WDCheck(data, 1); + content = c; + if (!content) throw new ContentException (); + adapter = mgr.renderer.getAdapter (content.toString(0)); + super (mgr, id, data); + } + +protected: + IContent content; +} + +/// Editable boolean widget +class BoolContentWidget : AButtonWidget +{ + this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { + WDCheck(data, 1); + content = cast(BoolContent) c; + if (!content) throw new ContentException (); + wdimPair s = mgr.renderer.getToggleSize; + w = mw = s.x; + h = mh = s.y; + super (mgr, id, data); + } + + void draw () { + mgr.renderer.drawToggle (x,y, content(), pushed); + } + + void activated () { + content = !content(); + } + +protected: + BoolContent content; +} + diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/gui/widget/miscWidgets.d --- a/mde/gui/widget/miscWidgets.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/gui/widget/miscWidgets.d Thu Nov 06 11:07:18 2008 +0000 @@ -76,48 +76,16 @@ } /// First interactible widget -class ButtonWidget : FixedWidget +class ButtonWidget : AButtonWidget { - 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 (IWidgetManager mgr, widgetID id, WidgetData data) { WDCheck (data, 3); + w = mw = cast(wdim) data.ints[1]; + h = mh = cast(wdim) data.ints[2]; super (mgr, id, data); } - void draw () { - mgr.renderer.drawButton (x,y, w,h, pushed); - } - - void clickEvent (wdabs, wdabs, ubyte b, bool state) { - if (b == 1 && state == true) { - pushed = true; - mgr.requestRedraw; - mgr.addClickCallback (&clickWhileHeld); - mgr.addMotionCallback (&motionWhileHeld); - } - } - // Called when a mouse motion/click event occurs while (held == true) - bool clickWhileHeld (wdabs cx, wdabs cy, ubyte b, bool state) { - if (b == 1 && state == false) { - if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event - Stdout ("Button clicked!").newline; - - pushed = false; - mgr.requestRedraw; - mgr.removeCallbacks (cast(void*) this); - - return true; - } - return false; - } - void motionWhileHeld (wdabs cx, wdabs cy) { - bool oldPushed = pushed; - if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true; - else pushed = false; - if (oldPushed != pushed) - mgr.requestRedraw; + void activated () { + Stdout ("Button clicked!").newline; } } diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/imde.d --- a/mde/imde.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/imde.d Thu Nov 06 11:07:18 2008 +0000 @@ -13,7 +13,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -/** This module is for interfacing with the mde.mde module and some global items. */ +/** This module is for interfacing with the mde.mde module (or other module containing main()) and + * some global items. */ module mde.imde; import mde.input.Input; diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/input/joystick.d --- a/mde/input/joystick.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/input/joystick.d Thu Nov 06 11:07:18 2008 +0000 @@ -54,6 +54,7 @@ StageState closeJoysticks () { foreach (js; joysticks) { // NOTE: This sometimes causes a SIGSEGV (Address boundary error) when init fails. + debug logger.trace ("Calling SDL_JoystickClose"); if(js !is null) SDL_JoystickClose(js); // only close if successfully opened } return StageState.INACTIVE; diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/lookup/Options.d --- a/mde/lookup/Options.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/lookup/Options.d Thu Nov 06 11:07:18 2008 +0000 @@ -81,7 +81,8 @@ // All supported types, for generic handling via templates. It should be possible to change // the supported types simply by changing this list now (untested). template store(A...) { alias A store; } - alias store!(int, double, char[]) TYPES;//FIXME removed bool + alias store!(bool, int, double, char[]) TYPES; + alias store!(int, double, char[]) TYPES2; //BEGIN Templates: internal private { // Get name of a type. Basically just stringof, but special handling for arrays. @@ -122,7 +123,7 @@ auto p = id in opts; if (p) { auto q = cast(BoolContent) (*p); - if (q) q = parseTo!(`~T.stringof~`) (dt); + if (q) q.v = parseTo!(`~T.stringof~`) (dt); } }`; } else @@ -136,15 +137,6 @@ const char[] addTagMixin = ifBlock; } - // For writeAll - template writeAllMixin(A...) { - static if (A.length) { - const char[] writeAllMixin = - `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]); - } else - const char[] writeAllMixin = ``; - } - // For list template listMixin(A...) { static if (A.length) { @@ -288,7 +280,7 @@ /** List the names of all options of a specific type. */ char[][] list () { char[][] ret; - mixin (listMixin!(TYPES)); + mixin (listMixin!(TYPES2)); return ret; } @@ -305,7 +297,7 @@ OptionChanges optionChanges; // all changes to options (for saving) // The "pointer lists", e.g. char[]*[ID] optscharA; - mixin (PLists!(TYPES)); //FIXME adds unused optsbool + mixin (PLists!(TYPES2)); //FIXME ValueContent[char[]] opts; // generic list of option values } @@ -313,10 +305,8 @@ void addTag (char[] tp, ID id, char[] dt) { mixin(addTagMixin!(TYPES).addTagMixin); } - - void writeAll (ItemDelg dlg) { - mixin(writeAllMixin!(TYPES)); - } + // Only OptionChanges writes stuff + void writeAll (ItemDelg dlg) {} //END Mergetag loading/saving code //END Non-static @@ -419,7 +409,7 @@ else static if (A[0] == ' ') const char[] createBCs = createBCs!(A[1..$]); else - const char[] createBCs = A[0..cIndex!(A)]~ " = new BoolContent (false);\n"~ + const char[] createBCs = A[0..cIndex!(A)]~ ` = (new BoolContent ("`~A[0..cIndex!(A)]~"\")).addChangeCb (&optionChanges.set);\n"~ createBCs!(A[cIndex!(A)+1..$]); } // for recursing on TYPES @@ -446,13 +436,13 @@ * Basic types are replaced with a ValueContent class to keep the option synchronized and * generalize use. */ template declVals(char[] A) { - const char[] declVals = declValsInternal!(A, TYPES,bool); + const char[] declVals = declValsInternal!(A, TYPES); } /** Produces the implementation code to go in the constuctor. */ template optionsThis(char[] A) { const char[] optionsThis = "optionChanges = new OptionChanges;\n" ~ - optionsThisInternal!(A,TYPES,bool); + optionsThisInternal!(A,TYPES); } /+ Needs too many custom parameters to be worth it? Plus makes class less readable. /** Produces the implementation code to go in the static constuctor. */ @@ -502,6 +492,16 @@ } else const char[] Vars = ``; } + // For set + template Set(A...) { + static if (A.length) { + const char[] Set= `void set (char[] s,`~A[0].stringof~` v) { + Options.changed = true; + `~TName!(A[0])~`s[s] = v; +}`~ Set!(A[1..$]); + } else + const char[] Set = ``; + } // For addTag template addTagMixin(T, A...) { @@ -529,6 +529,8 @@ // These store the actual values, but are never accessed directly except when initially added. // optsX store pointers to each item added along with the ID and are used for access. mixin(Vars!(TYPES)); + // set (char[] symbol, T value); (not templates but for each type T) + mixin(Set!(TYPES)); this () {} diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/mde.d --- a/mde/mde.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/mde.d Thu Nov 06 11:07:18 2008 +0000 @@ -75,7 +75,7 @@ * not allow running components simultaeneously with threads. * Note: probably drawing should start at the beginning of the loop and glFlush()/swapBuffers * be called at the end to optimise. */ - mainSchedule.add (SCHEDULE.DRAW, &Screen.draw); // Draw, per event only. + mainSchedule.add (SCHEDULE.DRAW, &Screen.draw).request = true; // Draw, per event and first frame only. mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true; //END Main loop setup diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/scheduler/Scheduler.d --- a/mde/scheduler/Scheduler.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/scheduler/Scheduler.d Thu Nov 06 11:07:18 2008 +0000 @@ -18,6 +18,7 @@ * This class implements most functionality a generic scheduler might want, however currently it * doesn't any uses where equivalent functionality couldn't be achived very easily anyway. */ module mde.scheduler.Scheduler; +import mde.util; public import tango.time.Time; @@ -80,10 +81,7 @@ * scheduler.get(15).frame = true; */ ScheduleFunc add (ID id, scheduleFct func) { - // Convert to a delegate. Maybe someday implicit casts will work... - scheduleDlg d; - d.funcptr = func; - return add (id, d); + return add (id, toDg(func)); } /** ditto */ ScheduleFunc add (ID id, scheduleDlg func) diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/setup/Init.d --- a/mde/setup/Init.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/setup/Init.d Thu Nov 06 11:07:18 2008 +0000 @@ -262,6 +262,14 @@ Mutex toRunM = new Mutex; // synchronization on toRun, numWorking Condition toRunC = new Condition(toRunM); // used by threads waiting for remaining stages' dependencies to be met + /* initThreadFct is now in a class to allow reliably identifying whcich instance is run in the main thread. + * The main thread gets index 0, other threads indexes 2,3,4,etc. (there is no 1). */ + class InitStageThread : Thread { + this (int n) { + debug threadNum = n; + super (&initThreadFct); + } + debug int threadNum; /* This is a threadable member function to run init stages. * Operation follows: * 1 Look for a stage to run: @@ -329,20 +337,20 @@ try { // FIXME - old stage start&finish trace messages - we don't have a name! static if (startup) { - debug logger.trace ("InitStage {}: starting init", stage.name); + debug logger.trace ("({}) InitStage {}: starting init", threadNum, stage.name); stage.state = (*stage).init(); // init is a property of a pointer (oh no!) } else { - debug logger.trace ("InitStage {}: starting cleanup", stage.name); + debug logger.trace ("({}) InitStage {}: starting cleanup", threadNum, stage.name); stage.state = stage.cleanup(); } - debug logger.trace ("InitStage {}: completed; state: {}", stage.name, stage.state); + debug logger.trace ("({}) InitStage {}: completed; state: {}", threadNum, stage.name, stage.state); } catch (InitStageException e) { - debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name); + debug logger.trace ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name); stage.state = e.state; doneInit = STATE.ABORT; break threadLoop; } catch (Exception e) { - debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name); + debug logger.trace ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name); doneInit = STATE.ABORT; break threadLoop; } @@ -355,19 +363,27 @@ toRunC.notifyAll(); // Most likely if we're exiting, we should make sure others aren't waiting. return; } + } // Start min(miscOpts.maxThreads,toRun.size)-1 threads: try { ThreadGroup g = new ThreadGroup; - for (size_t i = numWorking; i > 1; --i) - g.create (&initThreadFct); - initThreadFct(); // also run in current thread + InitStageThread x; + for (size_t i = numWorking; i > 1; --i) { + //g.create (&initThreadFct); + x = new InitStageThread (i); + x.start; + g.add (x); + } + x = new InitStageThread (0); + x.initThreadFct(); // also run in current thread g.joinAll (false); // don't rethrow exceptions - there SHOULD NOT be any } catch (ThreadException e) { logger.error ("Exception while using threads: "~e.msg); logger.error ("Disabling threads and attempting to continue."); miscOpts.set!(int)("NumThreads", 1); // count includes current thread - initThreadFct(); // try with just this thread + auto x = new InitStageThread (0); + x.initThreadFct(); // try with just this thread } // any other exception will be caught in main() and abort program if (doneInit & STATE.ABORT) diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/setup/InitStage.d --- a/mde/setup/InitStage.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/setup/InitStage.d Thu Nov 06 11:07:18 2008 +0000 @@ -19,6 +19,8 @@ module mde.setup.InitStage; public import mde.setup.exception; +import mde.util; + import tango.util.container.HashMap; import tango.util.log.Log : Log, Logger; @@ -55,10 +57,7 @@ } /// Add a stage to be initialized. void addInitStage (char[4] name, StageState function() init, StageState function() cleanup = null, char[4][] depends = null) { - StageState delegate() i,c; - i.funcptr = init; - c.funcptr = cleanup; - addInitStage (name, i, c, depends); + addInitStage (name, toDg(init), toDg(cleanup), depends); } /// Add a stage to be initialized. void addInitStage (char[4] name, StageState delegate() init, StageState delegate() cleanup = null, char[4][] depends = null) { diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/setup/Screen.d --- a/mde/setup/Screen.d Thu Oct 23 17:45:49 2008 +0100 +++ b/mde/setup/Screen.d Thu Nov 06 11:07:18 2008 +0000 @@ -56,7 +56,9 @@ /** Init function to initialize SDL. */ StageState init () { // init func // Initialise SDL - if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) { + debug logger.trace ("Calling SDL_Init"); + //FIXME: init SDL_INIT_TIMER? + if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER /+| SDL_INIT_EVENTTHREAD+/)) { logger.fatal ("SDL initialisation failed:"); char* msg = SDL_GetError (); logger.fatal (msg ? fromStringz(msg) : "no reason available"); @@ -67,6 +69,7 @@ } /** SDL shutdown */ StageState cleanup () { + debug logger.trace ("Calling SDL_Quit"); SDL_Quit(); return StageState.INACTIVE; } @@ -145,6 +148,7 @@ //NOTE: wrap mode may have an effect, but shouldn't be noticed... // Window-manager settings + debug logger.trace ("Calling SDL_WM_SetCaption"); SDL_WM_SetCaption (toStringz ("mde"), null); // SDL_WM_GrabInput (use later) //END Create window and initialize OpenGL @@ -213,6 +217,7 @@ } //debug logger.trace ("Setting video mode {}x{}, 32-bit, flags: {}", w,h,flags); + debug logger.trace ("Calling SDL_SetVideoMode"); if (SDL_SetVideoMode (w, h, 32, flags) is null) { logger.fatal ("Unable to set video mode:"); char* msg = SDL_GetError (); diff -r 9520cc0448e5 -r 2a364c7d82c9 mde/util.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/util.d Thu Nov 06 11:07:18 2008 +0000 @@ -0,0 +1,15 @@ +module mde.util; + +/// Not my code: http://www.dsource.org/projects/tango/ticket/1174#comment:7 +R delegate(T) toDg(R, T...)(R function(T) fp) { + if (!fp) return null; + struct dg { + R opCall(T t) { + return (cast(R function(T)) this) (t); + } + } + R delegate(T) t; + t.ptr = fp; + t.funcptr = &dg.opCall; + return t; +}