Mercurial > projects > mde
view mde/gui/widget/layout.d @ 41:b3a6ca4516b4
The renderer now controls which parts of the window border allow resizing.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Tue, 13 May 2008 12:02:36 +0100 |
parents | b28d7adc786b |
children | 8bf53e711cc7 |
line wrap: on
line source
/* 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 <http://www.gnu.org/licenses/>. */ /// Gui layout widgets. module mde.gui.widget.layout; import mde.gui.widget.Widget; import mde.gui.exception; /** Encapsulates a grid of Widgets. * * 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) { // Get grid size and check data // Check sufficient data for rows, cols, and at least one widget: if (data.length < 4) throw new WidgetDataException; super (wind, data); rows = data[1]; cols = data[2]; if (data.length != 3 + rows * cols) throw new WidgetDataException; /* data.length >= 3 so besides checking the length is correct, this tells us: * rows * cols >= 4 - 3 = 1 a free check! * The only thing not checked is whether both rows and cols are negative, which would * cause an exception when dynamic arrays are allocated later (and is unlikely). */ // Get all sub-widgets subWidgets.length = rows*cols; foreach (i, ref 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); /* We basically short-cut setSize by loading previous col/row sizes and doing the final * calculations. * Note: if setSize gets called afterwards, it should have same dimensions and so not do * anything. */ 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, ref w; colW) if (w < colWMin[i]) w = colWMin[i]; foreach (i, ref h; rowH) if (h < rowHMin[i]) h = rowHMin[i]; } genCachedMutableData; w = colW[$-1] + colX[$-1]; h = rowY[$-1] + rowH[$-1]; return data[lenUsed..$]; } int[] getCreationData () { int[] ret; ret.length = 3 + subWidgets.length; ret [0..3] = [widgetType, rows, cols]; // first data foreach (i,widget; subWidgets) // sub widgets ret[i+3] = window.addCreationData (widget); return ret; } int[] getMutableData () { int[] ret; foreach (widget; subWidgets) ret ~= widget.getMutableData; ret ~= colW ~ rowH; return ret; } bool isWSizable () { return (sizableCols.length != 0); } bool isHSizable () { return (sizableRows.length != 0); } /* Calculates the minimal size from all rows and columns of widgets. */ void getMinimalSize (out int mw, out int mh) { mw = this.mw; mh = this.mh; } void setSize (int nw, int nh) { // Step 1: calculate the row/column sizes. setSizeImpl!(true) (nw); setSizeImpl!(false) (nh); // Step 2: calculate the row/column offsets (positions) and set the sub-widgets sizes. genCachedMutableData; // Step 3: position needs to be set // Currently this happens by specifying that setPosition should be run after setSize. } void setPosition (int x, int y) { this.x = x; this.y = y; foreach (i,widget; subWidgets) widget.setPosition (x + colX[i % cols], y + rowY[i / cols]); } // Find the relevant widget. IWidget getWidget (int cx, int cy) { int lx = cx - x, ly = cy - y; // use coords relative to this widget // Find the column int i = cols - 1; // starting from right... while (lx < colX[i]) { // decrement while left of this column if (i == 0) return this; // left of first column --i; } // now (lx >= colX[i]) if (lx >= colX[i] + colW[i]) return this; // between columns // Find the row; int j = rows - 1; while (ly < rowY[j]) { if (j == 0) return this; --j; } if (ly >= rowY[j] + rowH[j]) return this; // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell) lx -= colX[i]; ly -= rowY[j]; IWidget widg = subWidgets[i + j*cols]; widg.getCurrentSize (i,j); if (lx < i && ly < j) return widg.getWidget (cx, cy); return this; // wasn't in cell } void draw () { super.draw (); foreach (widget; subWidgets) widget.draw (); } private: /* 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); // 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] < wh) rowHMin[n] = wh; } // Calculate the overall minimal size, starting with the spacing: mh = window.renderer.layoutSpacing; // use mh 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 } } /* 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; int spacing = window.renderer.layoutSpacing; int cum = 0; foreach (i, x; rowH) { rowY[i] = cum; cum += x + spacing; } cum = 0; foreach (i, x; colW) { colX[i] = cum; cum += x + spacing; } // Tell subwidgets their new sizes: foreach (i,widget; subWidgets) widget.setSize (colW[i % cols], rowH[i / cols]); } /* 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 } 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 }