Mercurial > projects > mde
view mde/gui/widget/layout.d @ 37:052df9b2fe07
Allowed widget resizing, changed widget IDs and made Input catch any callback exceptions.
Enabled widget resizing.
Removed IRenderer's temporary drawBox method and added drawButton for ButtonWidget.
Made the Widget class abstract and added FixedWidget and SizableWidget classes.
Rewrote much of createWidget to use meta-code; changed widget IDs.
Made Input catch callback exceptions and report error messages.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Mon, 05 May 2008 14:47:25 +0100 |
parents | 6b4116e6355c |
children | 8c4c96f04e7f |
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 : WidgetDataException; /** Encapsulates a grid of Widgets. * * Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */ class GridLayoutWidget : Widget { this (IWindow wind, IWidget, int[] data) { // Get grid size and check data // Check sufficient data for rows, cols, and at least one widget: if (data.length < 3) throw new WidgetDataException; rows = data[0]; cols = data[1]; if (data.length != 2 + rows * cols) throw new WidgetDataException; /* data.length >= 3 so besides checking the length is correct, this tells us: * rows * cols >= 3 - 2 = 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). */ window = wind; // Get all sub-widgets subWidgets.length = rows*cols; foreach (i, inout subWidget; subWidgets) { subWidget = window.makeWidget (data[i+2], this); } } 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; } 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; } /* 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; } void setSize (int nw, int nh) { // Step 1: calculate the minimal row/column sizes. int mw, mh; // FIXME: use w,h directly? getMinimalSize (mw, mh); colW = colWMin; // start with these dimensions, and increase if necessary rowH = rowHMin; // 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; } if (nh <= mh) nh = mh; else { if (isHSizable) rowH[rowSizable] += nh - mh; else rowH[$-1] += nh - mh; } // Step 3: set each sub-widget's size. foreach (i,widget; subWidgets) widget.setSize (colW[i % cols], rowH[i / cols]); // Step 4: calculate the column and row positions colX.length = cols; rowY.length = rows; int spacing = window.renderer.layoutSpacing; int cum = 0; foreach (i, x; rowH) { rowY[i] = cum; cum += x + spacing; } h = cum - spacing; // set the total height assert (h == nh); // FIXME: remove and set w/h directly once this is asserted cum = 0; foreach (i, x; colW) { colX[i] = cum; cum += x + spacing; } w = cum - spacing; // total width assert (w == nw); } 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: 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]); // 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]; n = i / cols; // row if (rowHMin[n] < widgetH[i]) rowHMin[n] = widgetH[i]; } } protected: int cols, rows; // number of cells in grid int colSizable = -2;// 0..cols-1 means this column is resizable int rowSizable = -2;// -2 means not calculated yet, -1 means not resizable int[] colWMin; // minimal column width int[] rowHMin; // minimal row height int[] colW; // column width (widest widget) int[] rowH; // row height (highest widget in the row) 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 ] */ }