# HG changeset patch # User Diggory Hardy # Date 1222900671 -3600 # Node ID b525ff28774b4aec4227c27b2e59ac1e9e0a0090 # Parent 97e6dce0803700646765fcdcba9ddd3912bed3af Widgets generated dynamically from a list can now be standard widgets selected from data files. Started on allowing alignment to be shared between instances of a layout widget in a dynamic list (to allow column alignment of list's rows). diff -r 97e6dce08037 -r b525ff28774b codeDoc/jobs.txt --- a/codeDoc/jobs.txt Mon Sep 29 18:27:17 2008 +0100 +++ b/codeDoc/jobs.txt Wed Oct 01 23:37:51 2008 +0100 @@ -3,6 +3,7 @@ In progress: +Layout alignment sharing for instances of same widgetID. @@ -13,6 +14,7 @@ To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): Also see todo.txt and FIXME/NOTE comment marks. +4 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. diff -r 97e6dce08037 -r b525ff28774b codeDoc/todo.txt --- a/codeDoc/todo.txt Mon Sep 29 18:27:17 2008 +0100 +++ b/codeDoc/todo.txt Wed Oct 01 23:37:51 2008 +0100 @@ -4,43 +4,22 @@ GUI: -> Widgets: - -> rethink how widgets are created and receive creation data, so that they don't have to be created by the Window - -> scripted widgets - -> decent rendering/theme system - -> lists from content lists --> Content: - -> lists +-> rethink how widgets are created and receive creation data, so that they don't have to be created by the Window +-> scripted widgets +-> decent rendering/theme system +-> lists from content lists -+ shows current preference: +Scratchpad area for ideas: + Redesign: --> requirements - -> widgets can receive int and string data types -> possibilities - -> per-widget merging (i.e. separate tag(s) for each widget's data)? - -> if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resulting are not created - -> if design changes in a base file, while the old design was modified in a derived file, will the result be sane? - -> if a locally modified gui is updated upstream (so the base files change), should: - -> the local modifications persist? - -> the local modifications be lost? - -> the widgets be merged? - -> potentially gives the best of both worlds - -> should original widgets used as sub-widgets of modified widgets: - -> use original data (from base file), so they can be updated? - -> have data copied, also impacting unmodified portions of window using same widget? - -> have data copied under a new ID? - -> the user be asked? - -> requires copy of old data... - +> widget data per-gui instead of per-window? - -> identical sub-widget could be used in multiple windows more easily, when data file is hand edited - -> in-gui editor will probably copy data under a new ID anyway - +> Maybe change to: - -> One section per version, containing widget data - -> can inherit from other sections - -> Which section (version of gui design) to use saved in header - -> Either/or: - -> Windows defined in header by primary widget - +> Windows become ordinary sub-widgets or some parent widget, gui becomes a parent widget - +> widget data no longer has "construction" and "mutable" variations - +> widgets know their IDs and update their data whenever necessary, via the gui, into a new data set, which is saved upon exit \ No newline at end of file + -> How widgets get their sub-widgets: + -> (current) Ask manager to create widget from manager's data with ID from widget's data. + -> seems to work well; allows widgets some control over creation of children + -> now extended to support instancing & passing content; seems to fulfill requirements + -> (alternate) Pass widget a list of pointers to all sub-widgets + -> manager creates all static (from data files) widets + -> other widgets can create dynamic widgets as or to pass to sub-widgets + -> widget data needs a portion which is explicitly IDs for subwidgets, or data needs reforming into a tree diff -r 97e6dce08037 -r b525ff28774b data/conf/gui.mtt --- a/data/conf/gui.mtt Mon Sep 29 18:27:17 2008 +0100 +++ b/data/conf/gui.mtt Wed Oct 01 23:37:51 2008 +0100 @@ -2,12 +2,15 @@ {Working} - + - + - + + + + {Basic} diff -r 97e6dce08037 -r b525ff28774b mde/gui/WidgetManager.d --- a/mde/gui/WidgetManager.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/WidgetManager.d Wed Oct 01 23:37:51 2008 +0100 @@ -190,6 +190,7 @@ import mde.gui.exception; import mde.gui.widget.Ifaces; +import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module import mde.gui.widget.createWidget; import mde.file.mergetag.Reader; @@ -376,10 +377,17 @@ } - /** Create a widget by ID. */ - IChildWidget makeWidget (widgetID id, IParentWidget parent = null) { + /** Create a widget by ID. + * + * A widget instance is created from data found under ID. Multiple instances may be created. + * FIXME - data conflicts when saving? + * + * An IContent may be passed. This could contain many things, e.g. some basic data, a widget, + * multiple sub-IContents. It is only passed to the widget by createWidget if it's enumeration + * given in that module has the flag TAKES_CONTENT. */ + IChildWidget makeWidget (widgetID id, IContent content = null) { debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"'); - return createWidget (this, curData[id], parent); + return createWidget (this, curData[id], content); } /** For making changes. */ diff -r 97e6dce08037 -r b525ff28774b mde/gui/content/Content.d --- a/mde/gui/content/Content.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/content/Content.d Wed Oct 01 23:37:51 2008 +0100 @@ -26,6 +26,13 @@ * * Currently Content instances can only have their data set on creation. * Each Content class should provide a method to get it's content, e.g. the method text(). + * + * Extensions to content: + * + * These extensions require that a content can notify any dependants of changes. + * + * Use as a state switch (one option from an enumeration). E.g. a button/selection box could set a + * state, and a tabbed box could show a tab based on this. Or could represent an option. */ // Don't include dimension/drawing stuff because it's renderer specific and content should not be! // NOTE: an interface or a class? diff -r 97e6dce08037 -r b525ff28774b mde/gui/widget/Floating.d --- a/mde/gui/widget/Floating.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/widget/Floating.d Wed Oct 01 23:37:51 2008 +0100 @@ -53,12 +53,12 @@ * 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 : SizableWidget, IParentWidget +class FloatingAreaWidget : SizableWidget { this (IWidgetManager mgr, WidgetData data) { subWidgets.length = data.strings.length; foreach (i,s; data.strings) - subWidgets[i] = mgr.makeWidget (s, this); + subWidgets[i] = mgr.makeWidget (s); sWCoords.length = subWidgets.length; if (data.ints.length != 1) { diff -r 97e6dce08037 -r b525ff28774b mde/gui/widget/Ifaces.d --- a/mde/gui/widget/Ifaces.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/widget/Ifaces.d Wed Oct 01 23:37:51 2008 +0100 @@ -25,6 +25,7 @@ public import mde.gui.types; public import mde.gui.renderer.IRenderer; +import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module /************************************************************************************************* @@ -46,21 +47,19 @@ * * This class handles widget rendering, input, loading and saving. *************************************************************************************************/ -interface IWidgetManager : IParentWidget +interface IWidgetManager : IWidget { // Loading/saving: /** Create a widget by ID. * * Params: * id = Identifier, within data files, of the data for the widget. - * parent = Reference to the widget's parent, passed if the widget supports recieving it. + * content = An IContent may be passed to some widgets on creation. * * Creates a widget, using the widget data with index id. Widget data is loaded from files, * and per design (multiple gui layouts, called designs, may exist; data is per design). - * - * Note: this method is only for widgets with an identifier; generic widgets instanciated in - * lists are created directly and have no WidgetData of their own. */ - IChildWidget makeWidget (widgetID id, IParentWidget parent = null); + */ + IChildWidget makeWidget (widgetID id, IContent content = null); /** Record some changes, for saving. */ void setData (widgetID id, WidgetData); @@ -106,22 +105,11 @@ /************************************************************************************************* - * Interface for parent widgets, including the gui manager. - * - * A widget may call these methods on its parent, and on the gui manager. - *************************************************************************************************/ -interface IParentWidget : IWidget -{ - // NOTE: Likely some day this interface will really be used. - // NOTE: What widget is NOT going to implement this? It will probably be inherited. -} - - -/************************************************************************************************* * Interface for (child) widgets, i.e. all widgets other than the manager. * * A widget is a region of a GUI window which handles rendering and user-interaction for itself - * and is able to communicate with its manager and parent/child widgets as necessary. + * and is able to communicate with its manager and child widgets as necessary. (Passing widgets + * a reference to their parent has not been found useful.) * * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the * createWidget module, and have a constructor of the following form. It should also update it's @@ -135,6 +123,9 @@ * + [widgetID, x, y] * + where x is ... and y is ... +/ * this (IWidgetManager mgr, WidgetData data); + * + * /// The CTOR may take an IContent reference: + * this (IWidgetManager mgr, WidgetData data, IContent content); * ---------------------------------- * Where mgr is the widget manager and data is * initialisation data. The method should throw a WidgetDataException (created without diff -r 97e6dce08037 -r b525ff28774b mde/gui/widget/TextWidget.d --- a/mde/gui/widget/TextWidget.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/widget/TextWidget.d Wed Oct 01 23:37:51 2008 +0100 @@ -26,42 +26,8 @@ import mde.font.font; -import tango.io.Stdout; - -/// Adapter to ease use of ContentText -struct ContentAdapter(ContentT : IContent) { - void set (char[] cID, int col) { - if (font is null) font = FontStyle.get("default"); - - static if (is(ContentT == ContentText)) { - content = getContentText (cID); - } else static if (is(ContentT == ContentInt)) { - content = getContentInt (cID); - } else static assert (false, "Unsupported content type"); - colour = Colour (cast(ubyte) (col >> 16u), - cast(ubyte) (col >> 8u), - cast(ubyte) col ); - } - - void getDimensions (out wdsize w, out wdsize h) { - font.updateBlock (content.toString, textCache); - w = cast(wdim) textCache.w; - h = cast(wdim) textCache.h; - } - - void draw (wdabs x, wdabs y) { - font.textBlock (x,y, content.toString, textCache, colour); - } - - ContentT content; - TextBlock textCache; - Colour colour; - static FontStyle font; -} - /// Basic text widget -// FIXME (content, creation for different types) -class ContentWidget(ContentT : IContent) : Widget +class TextLabelWidget : Widget { /** Constructor for a widget containing [fixed] content. * @@ -71,26 +37,29 @@ * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */ this (IWidgetManager mgr, WidgetData data) { WDCheck (data, 2, 1); - text.set (data.strings[0], data.ints[1]); - text.getDimensions (mw, mh); + if (font is null) font = FontStyle.get("default"); + font.updateBlock (data.strings[0], textCache); + mw = cast(wdim) textCache.w; + mh = cast(wdim) textCache.h; + colour = Colour (data.ints[1]); super (mgr,data); } void draw () { super.draw(); - text.draw (x,y); + font.textBlock (x,y, text, textCache, colour); } protected: - ContentAdapter!(ContentT) text; + char[] text; + Colour colour; + TextBlock textCache; + static FontStyle font; } -alias ContentWidget!(ContentText) TextWidget; -alias ContentWidget!(ContentInt) IntWidget; - -/// Adapter to ease use of ContentOptionWidget -struct ContentOptionAdapter { +/// Adapter to ease use of ContentLabelWidget +struct ContentLabelAdapter { void set (IContent c, int col) { if (font is null) font = FontStyle.get("default"); @@ -116,21 +85,21 @@ static FontStyle font; } -/// Basic text widget -class ContentOptionWidget : Widget +/// Basic widget displaying a label from a content. +class ContentLabelWidget : Widget { this (IWidgetManager mgr, WidgetData data, IContent c) { WDCheck (data, 2, 0); - content.set (c, data.ints[1]); - content.getDimensions (mw, mh); + adapter.set (c, data.ints[1]); + adapter.getDimensions (mw, mh); super (mgr,data); } void draw () { super.draw(); - content.draw (x,y); + adapter.draw (x,y); } protected: - ContentOptionAdapter content; + ContentLabelAdapter adapter; } diff -r 97e6dce08037 -r b525ff28774b mde/gui/widget/createWidget.d --- a/mde/gui/widget/createWidget.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/widget/createWidget.d Wed Oct 01 23:37:51 2008 +0100 @@ -13,11 +13,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -/// GUI Widget module. +/** This module contains code to create a widget based on an enumeration value passed at runtime. + * + * It could be a part of the WidgetLoader.makeWidget function, but having it here makes things + * tidier. */ module mde.gui.widget.createWidget; import mde.gui.widget.Ifaces; import mde.gui.exception : WidgetDataException; +import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module // Widgets to create: import mde.gui.widget.layout; @@ -38,11 +42,11 @@ * Widget created of type data.ints[0] (see enum WIDGET_TYPES), with one of the following CTORs: * --- * this (IWidgetManager mgr, WidgetData data); - * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_PARENT): - * this (IWidgetManager mgr, WidgetData data, IParentWidget parent); + * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_CONTENT): + * this (IWidgetManager mgr, WidgetData data, IContent content); * --- *************************************************************************************************/ -IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IParentWidget parent) +IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IContent content) in { assert (mgr !is null, "createWidget: mgr is null"); } body { @@ -73,8 +77,8 @@ private: /// Widget types. enum WIDGET_TYPE : int { - TAKES_PARENT = 0x4000, // CTOR takes reference to its parent - PARENT = 0x8000, // widget can have children + 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) // Use widget names rather than usual capitals convention Unnamed = 0x0, // Only for use by widgets not created with createWidget @@ -87,11 +91,11 @@ // buttons: 0x10 Button = 0x10, - // content: 0x20 - Text = 0x21, - Int = 0x22, + // labels: 0x20 + ContentLabel = TAKES_CONTENT | 0x20, + TextLabel = 0x21, - GridLayout = PARENT | 0x100, + GridLayout = TAKES_CONTENT | PARENT | 0x100, TrialContentLayout = PARENT | 0x110, FloatingArea = PARENT | 0x200, @@ -105,11 +109,11 @@ "SizableBlank", "Debug", "Button", - "Text", - "Int", - "GridLayout", + "TextLabel", + "ContentLabel", "TrialContentLayout", - "FloatingArea"]; + "FloatingArea", + "GridLayout"]; /* Generates a binary search algorithm. */ char[] binarySearch (char[] var, char[][] consts) { @@ -124,8 +128,8 @@ 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_PARENT)\n" ~ - " return new " ~ c ~ "Widget (mgr, data, parent);\n" ~ + " static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~ + " return new " ~ c ~ "Widget (mgr, data, content);\n" ~ " else\n" ~ " return new " ~ c ~ "Widget (mgr, data);\n" ~ "} else "; @@ -134,3 +138,16 @@ return ret; } } + +debug { // check items in WIDGETS are listed in order + char[] WIDGETS_check () { + char[] ret; + for (int i = WIDGETS.length-2; i > 0; --i) { + ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1]; + if (i>1) ret ~= " || "; + } + return ret; + } + mixin ("static if ("~WIDGETS_check~") + static assert (false, \"WIDGETS is not in order!\");"); +} \ No newline at end of file diff -r 97e6dce08037 -r b525ff28774b mde/gui/widget/layout.d --- a/mde/gui/widget/layout.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/gui/widget/layout.d Wed Oct 01 23:37:51 2008 +0100 @@ -21,6 +21,7 @@ import mde.gui.widget.TextWidget; import mde.gui.content.options; +import mde.gui.content.Content; debug { import tango.util.log.Log : Log, Logger; @@ -47,8 +48,10 @@ * Widget uses the initialisation data: * [widgetID, r, c, w11, w12, ..., w1c, ..., wr1, ..., wrc] * where r and c are the number of rows and columns, and wij is the ID (from parent Window's - * list) for the widget in row i and column j. The number of parameters must be r*c + 3. */ - this (IWidgetManager mgr, WidgetData data) { + * list) for the widget in row i and column j. The number of parameters must be r*c + 3. + * + * The content parameter is passed on to all children accepting an IContent. */ + this (IWidgetManager mgr, WidgetData data, IContent content) { // Get grid size and check data // Check sufficient data for rows, cols, and possibly row/col widths. if (data.ints.length < 3) throw new WidgetDataException; @@ -66,17 +69,17 @@ // Get all sub-widgets subWidgets.length = rows*cols; foreach (i, ref subWidget; subWidgets) { - subWidget = mgr.makeWidget (data.strings[i]); + subWidget = mgr.makeWidget (data.strings[i], content); } super (mgr, data); if (data.ints.length == 3 + rows + cols) { - col.setCheck (cast(wdim[]) data.ints[3..cols+3]); - row.setCheck (cast(wdim[]) data.ints[cols+3..$]); + col.setWidths (cast(wdim[]) data.ints[3..cols+3]); + row.setWidths (cast(wdim[]) data.ints[cols+3..$]); } else { - col.dupMin; - row.dupMin; + col.setWidths; + row.setWidths; } adjustCache; } @@ -105,7 +108,7 @@ this (IWidgetManager mgr, WidgetData data) { debug scope (failure) logger.warn ("TrialContentLayoutWidget: failure"); - WDCheck (data, 2); + WDCheck (data, 1, 1); OptionList optsList = OptionList.trial(); rows = optsList.list.length; @@ -113,16 +116,14 @@ // Get all sub-widgets subWidgets.length = rows*cols; - WidgetData COWData; - COWData.ints = [0, data.ints[1]]; foreach (i, c; optsList.list) { - subWidgets[i] = new ContentOptionWidget (mgr, COWData, c); + subWidgets[i] = mgr.makeWidget (data.strings[0], c); } super (mgr, data); // Set col/row widths to minimals. - col.dupMin; - row.dupMin; + col.setWidths; + row.setWidths; adjustCache; } @@ -150,14 +151,14 @@ * before calling this super constructor. (If it's necessary to call super(...) first, * the call to genCachedConstructionData can be moved to the derived this() methods.) * - * Derived constructors should call either dupMin or setCheck on col and row, and then call + * Derived constructors should call setWidths on col and row, and then call * adjustCache, after calling this. */ protected this (IWidgetManager mgr, WidgetData data) { super (mgr, data); - // Needn't be set before genCachedConstructionData is called: - col.setColWidth = &setColWidth; - row.setColWidth = &setRowHeight; + // Create cell aligners with appropriate col/row adjustment function + col = (new AlignColumns (cols)).addSetCallback (&setColWidth); + row = (new AlignColumns (rows)).addSetCallback (&setRowHeight); // Calculate cached construction data genCachedConstructionData; @@ -165,8 +166,7 @@ /** Generates cached mutable data. * - * Should be called by adjust() after setting col and row widths (currently via dupMin or - * setCheck). */ + * Should be called by adjust() after calling setWidths. */ void adjustCache () { // Generate cached mutable data // Calculate column and row locations: @@ -374,173 +374,245 @@ * [ 2 3 ] */ IChildWidget[] subWidgets; - /* Widths, positions, etc., either of columns or of rows - * - * The purpose of this struct is mostly to unify functionality which must work the same on both - * horizontal and vertical cell placement. - * - * Most notation corresponds to horizontal layout (columns), simply for easy of naming. */ - struct CellDimensions { - wdim[] pos, // relative position (cumulative width[i-1] plus spacing) - width, // current widths - minWidth; // minimal widths (set by genCachedConstructionData) - bool[] sizable; // true if col is resizable (set by genCachedConstructionData) - myDiff firstSizable, // first col which is resizable, negative if none - lastSizable; // as above, but last (set by genCachedConstructionData) - myDiff resizeD, // resize 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) - /* This is a delegate to a enclosing class's function, since: - * a different implementation is needed for cols or rows - * we're unable to access enclosing class members directly */ - void delegate (myIt,wdim,int) setColWidth; // set width of a column, with resize direction + AlignColumns col, row; +} + +/// Position information on top of widths. +//FIXME - merge classes back together? +class AlignColumns : AlignWidths +{ + /// See AlignWidths.this + this (myIt columns) { + super (columns); + } + + /** Add a callback to be called to notify changes in a column's width. + * + * All callbacks added are called on a width change so that multiple objects may share a + * CellAlign object. */ + typeof(this) addSetCallback (void delegate (myIt,wdim,int) setCW) { + assert (setCW, "CellAlign.this: setCW is null (code error)"); + setWidthCb ~= setCW; + return this; + } + + /** Generate position infomation and return total width of columns. */ + wdim genPositions () { + pos.length = minWidth.length; - void dupMin () { - width = minWidth.dup; + wdim x = 0; + foreach (i, w; width) { + pos[i] = x; + x += w + spacing; + } + return x - spacing; + } + + // Get the row/column a click occured in + // Returns -i if in space to left of col i, or i if on col i + myDiff getCell (wdim l) { + 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 + debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)"); + return -i - 1; // note: i might be 0 so cannot just return -i } - void setCheck (wdim[] data) { - // 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; + return i; + } + + // Calculate resizeU/resizeD, and return true if unable to resize. + bool findResize (wdim l) { + resizeU = -getCell (l); // potential start for upward-resizes + 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) + ++resizeU; + if (resizeU >= minWidth.length) { // cannot resize + resizeU = -1; + return true; + } + } + + while (!sizable[resizeD]) { // find first actually resizable column (downwards) + --resizeD; + if (resizeD < 0) { // cannot resize + resizeU = -1; // resizeU is tested to check whether resizes are possible + return true; + } } - // Generate position infomation and return total width (i.e. widget width/height) - wdim genPositions () { - pos.length = minWidth.length; - - wdim x = 0; - foreach (i, w; width) { - pos[i] = x; - x += w + spacing; - } - return x - spacing; + return false; // can resize + } + + /* Adjust the total size of rows/columns (including spacing) by diff. + * + * Params: + * diff = amount to increase/decrease the total size + * start= index for col/row to start resizing on + * incr = direction to resize in (added to index each step). Must be either -1 or +1. + * + * Returns: + * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. + * + * Note: Check variable used for start is valid before calling! If a non-sizable column's + * index is passed, this should get increased (if diff > 0) but not decreased. + */ + wdim adjustCellSizes (wdim diff, myDiff start, int incr) + in { + assert (width, "CellAlign.adjustCellSizes: width is null (code error)"); + // Most likely if passed negative when sizing is disabled: + assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); + debug assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); + } body { + debug scope(failure) + logger.trace ("adjustCellSizes: failure"); + myDiff i = start; + if (diff > 0) { // increase size of first resizable cell + width[i] += diff; + foreach (dg; setWidthCb) + dg(i, width[i], incr); } - - // Get the row/column a click occured in - // Returns -i if in space to left of col i, or i if on col i - myDiff getCell (wdim l) { - 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 - debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)"); - return -i - 1; // note: i might be 0 so cannot just return -i - } - return i; - } - - // Calculate resizeU/resizeD, and return true if unable to resize. - bool findResize (wdim l) { - resizeU = -getCell (l); // potential start for upward-resizes - 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) - ++resizeU; - if (resizeU >= minWidth.length) { // cannot resize - resizeU = -1; - return true; + else if (diff < 0) { // decrease + wdim rd = diff; // running diff + aCSwhile: + while (true) { + width[i] += rd; // decrease this cell's size (but may be too much) + rd = width[i] - minWidth[i]; + if (rd >= 0) { // OK; we're done + foreach (dg; setWidthCb) + dg(i, width[i], incr); + break; // we hit the mark exactly: diff is correct } - } - - while (!sizable[resizeD]) { // find first actually resizable column (downwards) - --resizeD; - if (resizeD < 0) { // cannot resize - resizeU = -1; // resizeU is tested to check whether resizes are possible - return true; + + // else we decreased it too much! + width[i] = minWidth[i]; + foreach (dg; setWidthCb) + dg(i, width[i], incr); + // rd is remainder to decrease by + + + bool it = true; // iterate (force first time) + while (it) { + 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 } } - - return false; // can resize } + // else no adjustment needed (diff == 0) - /* Adjust the total size of rows/columns (including spacing) by diff. - * - * Params: - * diff = amount to increase/decrease the total size - * start= index for col/row to start resizing on - * incr = direction to resize in (added to index each step). Must be either -1 or +1. - * - * Returns: - * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. - * - * Note: Check variable used for start is valid before calling! If a non-sizable column's - * index is passed, this should get increased (if diff > 0) but not decreased. - */ - wdim adjustCellSizes (wdim diff, myDiff start, int incr) - in { - // Could occur if constructor doesn't call dupMin/setCheck (code error): - assert (width !is null, "adjustCellSizes: width is null"); - // Most likely if passed negative when sizing is disabled: - assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); - assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); - assert (setColWidth !is null, "adjustCellSizes: setColWidth is null"); - } body { - debug scope(failure) - logger.trace ("adjustCellSizes: failure"); - myDiff i = start; - if (diff > 0) { // increase size of first resizable cell - width[i] += diff; - setColWidth (i, width[i], incr); - } - else if (diff < 0) { // decrease - wdim rd = diff; // running diff - aCSwhile: - while (true) { - width[i] += rd; // decrease this cell's size (but may be too much) - rd = width[i] - minWidth[i]; - if (rd >= 0) { // OK; we're done - setColWidth (i, width[i], incr); // set new width - break; // we hit the mark exactly: diff is correct - } - - // else we decreased it too much! - width[i] = minWidth[i]; - setColWidth (i, width[i], incr); - // rd is remainder to decrease by - - - bool it = true; // iterate (force first time) - while (it) { - 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 - } - } - } - // else no adjustment needed (diff == 0) - - genPositions; - return diff; - } + genPositions; + return diff; + } + + void resize (wdim diff) + { + if (resizeU <= 0) return; - void resize (wdim diff) { - if (resizeU <= 0) return; - - // do shrinking first (in case we hit the minimum) - if (diff >= 0) { - diff = -adjustCellSizes (-diff, resizeU, 1); - adjustCellSizes (diff, resizeD, -1); - } else { - diff = -adjustCellSizes (diff, resizeD, -1); - adjustCellSizes (diff, resizeU, 1); - } + // do shrinking first (in case we hit the minimum) + if (diff >= 0) { + diff = -adjustCellSizes (-diff, resizeU, 1); + adjustCellSizes (diff, resizeD, -1); + } else { + diff = -adjustCellSizes (diff, resizeD, -1); + adjustCellSizes (diff, resizeU, 1); } } - CellDimensions col, row; + +private: + wdim[] pos; // relative position (cumulative width[i-1] plus spacing) + wdim spacing; // used by genPositions (which cannot access the layout class's data) + // Callbacks used to actually adjust a column's width: + void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction +} + +/************************************************************************************************** + * Alignment device + * + * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the + * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to + * ease descriptions, a horizontal context (column widths) is assumed. + * + * Cells should be of type IChildWidget. + * + * FIXME: + * 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. + *************************************************************************************************/ +//FIXME: how to set cell positions after resizes? +/+ FIXME - remove position information from here, removing need of horizontal alignment of cells + into columns. On resizing, don't permanently adjust sizes until callback ends (mouse button + released). Until then, always readjust from current "permanent" size. +/ +class AlignWidths +{ + /** Create an instance. After creation, the number of columns can only be changed by calling + * reset. + * + * After creation, minimal widths should be set for all columns (minWidth) and + * setWidths must be called before other functions are used. */ + this (myIt columns) { + reset (columns); + } + + /** Reset all column information (only keep set callbacks). + * + * Widths should be set after calling, as on creation. */ + void reset (myIt columns) { + if (columns < 1) + throw new GuiException("AlignWidths: created with <1 column"); + minWidth = new wdim[columns]; + width = null; // enforce calling setWidths after this + } - // Index types. Note that in some cases they need to hold negative values. - // int is used for resizing direction (although ptrdiff_t would be more appropriate), - // since the value must always be -1 or +1 and int is smaller on X86_64. - alias size_t myIt; - alias ptrdiff_t myDiff; + /** Initialize widths as minimal widths. */ + void setWidths () { + width = minWidth.dup; + } + /** Initialize widths from supplied list, checking validity. */ + void setWidths (wdim[] data) { + // 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; + } + + /** Minimal width for each column. + * + * Initialized to zero. Each class using this AlignWidths should, for each column, increase + * this value to the maximum of the minimal widths (in other words, set + * minWidth[i] = max(minWidth[i], cell.minWidth) for each cell in column i). */ + wdim[] minWidth; // minimal widths (set by genCachedConstructionData) + + /** For each column i, sizable[i] is true if that column is resizable. + * firstSizable and lastSizable are the indicies of the first/last resizable column (negative + * if none are resizable). + * + * Set along with minWidth before calling setWidths. */ + bool[] sizable; // set by genCachedConstructionData + /// ditto + myDiff firstSizable, lastSizable; // set by genCachedConstructionData + + /** Current width for each column. + * + * Treat as READ ONLY! */ + wdim[] width; // only adjusted within the class +protected: + myDiff resizeD, // resize down from this index (<0 if not resizing) + resizeU; // and up from this index } + +// Index types. Note that in some cases they need to hold negative values. +// int is used for resizing direction (although ptrdiff_t would be more appropriate), +// since the value must always be -1 or +1. +alias size_t myIt; +alias ptrdiff_t myDiff; diff -r 97e6dce08037 -r b525ff28774b mde/types/Colour.d --- a/mde/types/Colour.d Mon Sep 29 18:27:17 2008 +0100 +++ b/mde/types/Colour.d Wed Oct 01 23:37:51 2008 +0100 @@ -46,5 +46,13 @@ c.b = cast(float) b / 255f; return c; } + /// Construct from 0xRRGGBB + Colour opCall (int col) { + Colour c; + c.r = cast(float) (col >> 16u) / 255f; + c.g = cast(float) (col >> 8u) / 255f; + c.b = cast(float) col / 255f; + return c; + } } }