# HG changeset patch # User Diggory Hardy # Date 1210259151 -3600 # Node ID b28d7adc786b61a4de7108adb25c04cabb848192 # Parent 5132301e9ed7ab6b745ae27f6fce36a93c9b2021 Made GUI more robust to mutable data changes and improved much of GridLayoutWidget's code. committer: Diggory Hardy diff -r 5132301e9ed7 -r b28d7adc786b codeDoc/jobs.txt --- a/codeDoc/jobs.txt Wed May 07 13:07:03 2008 +0100 +++ b/codeDoc/jobs.txt Thu May 08 16:05:51 2008 +0100 @@ -3,6 +3,8 @@ In progress: +Make a widget with multiple sizable rows/cols. +Implement row/col sizing. diff -r 5132301e9ed7 -r b28d7adc786b data/conf/gui.mtt --- a/data/conf/gui.mtt Wed May 07 13:07:03 2008 +0100 +++ b/data/conf/gui.mtt Thu May 08 16:05:51 2008 +0100 @@ -7,4 +7,4 @@ {W2} - + diff -r 5132301e9ed7 -r b28d7adc786b mde/gui/exception.d --- a/mde/gui/exception.d Wed May 07 13:07:03 2008 +0100 +++ b/mde/gui/exception.d Thu May 08 16:05:51 2008 +0100 @@ -47,14 +47,3 @@ super (msg); } } - -/// Thrown when a Widget class's adjust() is called with invalid data. -class MutableDataException : WindowLoadException -{ - this () { // Default, by Widget class's this - super ("Bad widget mutable data"); - } - this (char[] msg) { // From createWidget - super (msg); - } -} diff -r 5132301e9ed7 -r b28d7adc786b mde/gui/widget/Ifaces.d --- a/mde/gui/widget/Ifaces.d Wed May 07 13:07:03 2008 +0100 +++ b/mde/gui/widget/Ifaces.d Thu May 08 16:05:51 2008 +0100 @@ -75,17 +75,27 @@ //BEGIN Load and save /** Called after creating widgets to adjust size & other mutable attributes from saved data. * + * As for setSize, setPosition should be called afterwards. + * * Each widget should call adjust on each of its sub-widgets in turn with data, each time * replacing data by the return value of the call. It should then take its own mutable data * from the beginning of the array and return the remainder of the array. * - * Throws: on error, throw a MutableDataException. */ + * Adjust should handle errors gracefully by reverting to default values and not throwing. + * This is because the creation data and the user's mutable data may be stored separately and + * become out-of-sync during an update. */ int[] adjust (int[] data); - /** Output data suitible for recreating the widget (data to be passed to this()). */ + /** Output data suitible for recreating the widget (data to be passed to this()). + * + * Creation data is data only changed when the gui is edited. */ int[] getCreationData (); /** Output data containing the widget's current adjustments (data to be passed to adjust()). + * + * Mutable data is data which can be changed during normal gui use, such as the size of + * resizible widgets or current tab of a tab widget. + * * Should be a concatenation of each sub-widget's mutable data and the widget's own. */ int[] getMutableData (); //END Load and save diff -r 5132301e9ed7 -r b28d7adc786b mde/gui/widget/Window.d --- a/mde/gui/widget/Window.d Wed May 07 13:07:03 2008 +0100 +++ b/mde/gui/widget/Window.d Thu May 08 16:05:51 2008 +0100 @@ -58,8 +58,8 @@ assert (gui !is null, "Window.finalise ("~name~"): gui is null"); } body { // Check data was loaded: - if (widgetData is null || mutableData is null) - throw new WindowLoadException ("No widget/mutable data"); + if (widgetData is null) + throw new WindowLoadException ("No widget creation data"); gui_ = gui; rend = gui.renderer; @@ -72,6 +72,7 @@ widget = makeWidget (0); // primary widget always has ID 0. widgetData = null; // data is no longer needed: allow GC to collect (cannot safely delete) + // Note: this should return an empty array, but nothing much should happen if it's not empty: widget.adjust (mutableData); // adjust/set size, etc. mutableData = null; // no longer needed @@ -111,7 +112,7 @@ } body { /+ NOTE: currently editing is impossible... if (edited) { // only save the widget creation data if it's been adjusted: - addSaveData (widget); // generate widget save data + addCreationData (widget); // generate widget save data dlg (INTAA, WDGD, parseFrom!(int[][int]) (widgetData)); }+/ // Save mutable data: diff -r 5132301e9ed7 -r b28d7adc786b mde/gui/widget/layout.d --- a/mde/gui/widget/layout.d Wed May 07 13:07:03 2008 +0100 +++ b/mde/gui/widget/layout.d Thu May 08 16:05:51 2008 +0100 @@ -21,7 +21,12 @@ /** Encapsulates a grid of Widgets. * -* Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */ +* Currently there is no support for changing number of cells, sub-widgets or sub-widget properties +* (namely isW/HSizable and minimal size) after this() has run. +* +* Since a grid with either dimension zero is not useful, there must be at least one sub-widget. +* +* The grid has no border but has spacing between widgets. */ class GridLayoutWidget : Widget { this (IWindow wind, int[] data) { @@ -43,26 +48,43 @@ foreach (i, inout subWidget; subWidgets) { subWidget = window.makeWidget (data[i+3]); } + + // Calculate cached construction data + genCachedConstructionData; } int[] adjust (int[] data) { // Give all sub-widgets their data: foreach (widget; subWidgets) data = widget.adjust (data); - if (data.length < rows + cols) throw new MutableDataException; /* We basically short-cut setSize by loading previous col/row sizes and doing the final - * calculations. There isn't checks that the data is valid/up-to-date... worst case is too - * small overlapping widgets or huge ones? + * calculations. * Note: if setSize gets called afterwards, it should have same dimensions and so not do * anything. */ - colW = data[0..cols]; - rowH = data[cols..rows+cols]; - setColRowSizes; + + int lenUsed = 0; + if (data.length < rows + cols) { // data error; use defaults + colW = colWMin.dup; + rowH = rowHMin.dup; + } else { // sufficient data + lenUsed = rows+cols; + colW = data[0..cols]; + rowH = data[cols..lenUsed]; + + // Check row sizes are valid: + //NOTE: this could be made optional + foreach (i, inout w; colW) + if (w < colWMin[i]) w = colWMin[i]; + foreach (i, inout h; rowH) + if (h < rowHMin[i]) h = rowHMin[i]; + } + + genCachedMutableData; w = colW[$-1] + colX[$-1]; h = rowY[$-1] + rowH[$-1]; - return data[rows+cols..$]; + return data[lenUsed..$]; } int[] getCreationData () { @@ -86,113 +108,28 @@ } bool isWSizable () { - if (colSizable == -2) { // check whether any columns are resizable - for1: - for (uint i = 0; i < cols; ++i) { // for each column - for (uint j = 0; j < subWidgets.length; j += cols) // for each row - if (!subWidgets[i+j].isWSizable) // column not resizable - continue for1; // continue the outer for loop - - // column is resizable if we get to here - colSizable = i; - goto break1; // use goto in lieu of for...else - } - - // if we get here, no resizable column was found - colSizable = -1; - - break1:; - } - - if (colSizable >= 0) return true; - else return false; + return (sizableCols.length != 0); } bool isHSizable () { - if (rowSizable == -2) { // check whether any columns are resizable - for2: - for (uint i = 0; i < subWidgets.length; i += cols) { // for each row - for (uint j = 0; j < cols; ++j) // for each column - if (!subWidgets[i+j].isHSizable) - continue for2; - - rowSizable = i / cols; // the current row - goto break2; - } - - rowSizable = -1; - - break2:; - } - - if (rowSizable >= 0) return true; - else return false; + return (sizableRows.length != 0); } /* Calculates the minimal size from all rows and columns of widgets. */ void getMinimalSize (out int mw, out int mh) { - // If rowHMin & colWMin are null, calculate them. They are set null whenever the contents - // or the contents' minimal size change, as well as when this widget is created. - if (rowHMin is null) - genMinRowColSizes; - - // Calculate the size, starting with the spacing: - mh = window.renderer.layoutSpacing; // use temporarily - mw = mh * (cols - 1); - mh *= (rows - 1); - - foreach (x; colWMin) // add the column/row's dimensions - mw += x; - foreach (x; rowHMin) - mh += x; + mw = this.mw; + mh = this.mh; } void setSize (int nw, int nh) { - /* For each of width and height, there are several cases: - * [new value is] more than old value - * -> enlarge any row/column - * same as old value - * -> do nothing - * more than min but less than current value - * -> find an enlarged row/col and reduce size - * -> repeat if necessary - * minimal value or less - * -> clamp to min and use min col/row sizes - */ - // FIXME: implement! - - // Step 1: calculate the minimal row/column sizes. - alias w mw; // no need for extra vars, just use these - alias h mh; - getMinimalSize (mw, mh); - colW = colWMin.dup; // start with these dimensions, and increase if necessary - rowH = rowHMin.dup; // duplicate, because we may want to resize without recalculating *Min + // Step 1: calculate the row/column sizes. + setSizeImpl!(true) (nw); + setSizeImpl!(false) (nh); - // Step 2: clamp nw/nh or expand a column/row to achieve the required size - if (nw <= mw) nw = mw; // clamp to minimal size - else { - if (isWSizable) // calculates colSizable; true if any is resizable - colW[colSizable] += nw - mw; // new width - else // no resizable column; so force the last one - colW[$-1] += nw - mw; - } + // Step 2: calculate the row/column offsets (positions) and set the sub-widgets sizes. + genCachedMutableData; - if (nh <= mh) nh = mh; - else { - if (isHSizable) - rowH[rowSizable] += nh - mh; - else - rowH[$-1] += nh - mh; - } - - w = nw; - h = nh; - - // Step 3: set each sub-widget's size. - // Step 4: calculate the column and row positions - setColRowSizes; - - // Step 5: position needs resetting + // Step 3: position needs to be set // Currently this happens by specifying that setPosition should be run after setSize. } @@ -243,26 +180,62 @@ } private: - void genMinRowColSizes () { - // Find the sizes of all subWidgets - int[] widgetW = new int[subWidgets.length]; // dimensions - int[] widgetH = new int[subWidgets.length]; - foreach (i,widget; subWidgets) - widget.getMinimalSize (widgetW[i],widgetH[i]); + /* Calculations which need to be run whenever a new sub-widget structure is set + * (i.e. to produce cached data calculated from construction data). */ + void genCachedConstructionData () { + // Calculate the minimal column and row sizes: + colWMin = new int[cols]; // set length, making sure the arrays are initialised to zero + rowHMin = new int[rows]; + int ww, wh; // sub-widget minimal sizes + foreach (i,widget; subWidgets) { + widget.getMinimalSize (ww, wh); - // Find the minimal row heights and column widths (non cumulative) - colWMin = new int[cols]; // set length - rowHMin = new int[rows]; - for (uint i = 0; i < subWidgets.length; ++i) { - uint n; - n = i % cols; // column - if (colWMin[n] < widgetW[i]) colWMin[n] = widgetW[i]; + // Increase dimensions if current minimal size is larger: + uint n = i % cols; // column + if (colWMin[n] < ww) colWMin[n] = ww; n = i / cols; // row - if (rowHMin[n] < widgetH[i]) rowHMin[n] = widgetH[i]; + if (rowHMin[n] < wh) rowHMin[n] = wh; + } + + + // Calculate the overall minimal size, starting with the spacing: + mh = window.renderer.layoutSpacing; // use temporarily + mw = mh * (cols - 1); + mh *= (rows - 1); + + foreach (x; colWMin) // add the column/row's dimensions + mw += x; + foreach (x; rowHMin) + mh += x; + + + // Find which cols/rows are resizable: + sizableCols = null; // clear these because we append to them + sizableRows = null; + + forCols: + for (uint i = 0; i < cols; ++i) { // for each column + for (uint j = 0; j < subWidgets.length; j += cols) // for each row + if (!subWidgets[i+j].isWSizable) // column not resizable + continue forCols; // continue the outer for loop + + // column is resizable if we get to here + sizableCols ~= i; + } + + forRows: + for (uint i = 0; i < subWidgets.length; i += cols) { // for each row + for (uint j = 0; j < cols; ++j) // for each column + if (!subWidgets[i+j].isHSizable) + continue forRows; + + sizableRows ~= i / cols; // the current row } } - void setColRowSizes () { + /* Calculations which need to be run whenever resizing occurs (or deeper alterations) + * (i.e. to produce cached data calculated from construction and mutable data). */ + void genCachedMutableData () { // Calculate column and row locations: colX.length = cols; rowY.length = rows; @@ -285,21 +258,79 @@ widget.setSize (colW[i % cols], rowH[i / cols]); } -protected: - int cols, rows; // number of cells in grid + /* setSize generalised for either dimension; w/h is renamed d, col/row renamed cell. */ + void setSizeImpl(bool W) (int nd) { + static if (W) { + alias w d; + alias mw md; + alias colW cellD; + alias colWMin cellDMin; + alias sizableCols sizableCells; + } else { + alias h d; + alias mh md; + alias rowH cellD; + alias rowHMin cellDMin; + alias sizableRows sizableCells; + } + // Could occur if adjust isn't called first, but this would be a code error: + assert (cellD !is null, "setSizeImpl: cellD is null"); + + /* For each of width and height, there are several cases: + * [new value is] more than old value + * -> enlarge any row/column + * same as old value + * -> do nothing + * more than min but less than current value + * -> find an enlarged row/col and reduce size + * -> repeat if necessary + * minimal value or less + * -> clamp to min and use min col/row sizes + */ + if (nd > d) { // expand (d < nd) + if (sizableCells.length) { // check there is a resizable col/row + cellD[sizableCells[$-1]] += nd - d; // new size + d = nd; + } + } else if (nd < d) { + if (nd > md) { // shrink (md < nd < d) + int toReduce = nd - d; // negative + foreach_reverse (cell; sizableCells) { + cellD[cell] += toReduce; + toReduce = cellD[cell] - cellDMin[cell]; + if (toReduce < 0) // reduced too far + cellD[cell] = cellDMin[cell]; + else // ok; cellD >= cellDMin + break; + } + d = nd; + } else { // clamp (nd <= md) + cellD = cellDMin.dup; // duplicate so future edits don't affect cellDMin + d = md; + } + } // third possibility: nd = d + } - int colSizable = -2;// 0..cols-1 means this column is resizable - int rowSizable = -2;// -2 means not calculated yet, -1 means not resizable +protected: + // Construction data (saved): + int cols, rows; // number of cells in grid + IWidget[] subWidgets; // all widgets in the grid (by row): + /* SubWidget order: [ 0 1 ] + * [ 2 3 ] */ + // Cached data calculated from construction data: int[] colWMin; // minimal column width int[] rowHMin; // minimal row height + int mw, mh; // minimal dimensions + + int[] sizableCols; // all resizable columns / rows, empty if not resizable + int[] sizableRows; // resizing is done from last entry + + // Mutable data (saved): int[] colW; // column width (widest widget) int[] rowH; // row height (highest widget in the row) + // Cached data calculated from construction and mutable data: int[] colX; // cumulative colW[i-1] + padding (add x to get column's left x-coord) int[] rowY; // cumulative rowH[i-1] + padding - - IWidget[] subWidgets; // all widgets in the grid (by row): - /* SubWidget order: [ 0 1 ] - * [ 2 3 ] */ }