Mercurial > projects > mde
view mde/gui/Widget.d @ 31:baa87e68d7dc
GUI now supports basic interactible widgets, widget colour and border are more unified, and some code cleanup.
Removed some circular dependencies which slipped in. As a result, the OpenGL code got separated into different files.
Enabled widgets to recieve events.
New IParentWidget interface allowing widgets to interact with their parents.
New Widget base class.
New WidgetDecoration class.
New ButtonWidget class responding to events (in a basic way).
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Tue, 29 Apr 2008 18:10:58 +0100 |
parents | 467c74d4804d |
children |
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 Widget module. module mde.gui.Widget; import mde.gui.Ifaces; import mde.gui.exception; import gl = mde.gl.basic; import tango.io.Stdout; //BEGIN createWidget /// Widget types. Start high so they can be reordered easily later. enum WIDGET_TYPE : int { BOX = 1001, GRID, BUTTON } /** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation * data [1..$]. */ IWidget createWidget (IWindow window, IParentWidget parent, int[] data) in { assert (window !is null, "createWidget: window is null"); assert (parent !is null, "createWidget: parent is null"); } body { if (data.length < 1) throw new WidgetDataException ("No widget data"); int type = data[0]; // type is first element of data data = data[1..$]; // the rest is passed to the Widget if (type == WIDGET_TYPE.BOX) return new BoxWidget (window, parent, data); else if (type == WIDGET_TYPE.GRID) return new GridWidget (window, parent, data); else if (type == WIDGET_TYPE.BUTTON) return new ButtonWidget (window, parent, data); else throw new WidgetDataException ("Bad widget type"); } //END createWidget /** A base widget class. Widgets need not inherit this (they only need implement IWidget), but this * class provides a useful basic implementation for widgets. * * Technically this class could be instantiated, but it wouldn't do anything, not even draw itself. */ class Widget : IWidget { /** Basic draw method: draw the background */ void draw (int x, int y) { deco.setColor(); gl.drawBox (x,y, w,h); } /** Dummy event method (ignore) */ void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {} /** Minimum size is zero. */ void getMinimumSize (out int w, out int h) {} // w,h initialised to 0 /** Current size. */ void getCurrentSize (out int w, out int h) { w = this.w; h = this.h; } WidgetDecoration decoration () { return deco; } protected: WidgetDecoration deco; // the widget's decoration int w, h; // size } //BEGIN Widgets /// Draws a box. That's it. class BoxWidget : Widget { this (IWindow, IParentWidget parent, int[] data) { if (data.length != 2) throw new WidgetDataException; deco = new WidgetDecoration (parent.decoration); w = data[0] + 2*deco.border; h = data[1] + 2*deco.border; } } /// Encapsulates a grid of Widgets class GridWidget : Widget { this (IWindow window, IParentWidget parent, int[] data) { // Get grid size if (data.length < 2) throw new WidgetDataException; rows = data[0]; cols = data[1]; deco = new WidgetDecoration (parent.decoration, TypeFlags.LAYOUT); // Get all sub-widgets // Check: correct data length and rows*cols >= 0 (know data.length - 2 >= 0). if (data.length != 2 + rows * cols) throw new WidgetDataException; subWidgets.length = rows*cols; foreach (i, inout subWidget; subWidgets) { subWidget = window.getWidget (data[i+2], this); } getMinimumSize (w,h); // Calculate the size (current size is not saved) } void draw (int x, int y) { deco.setColor; gl.drawBox (x,y, w,h); foreach (i,widget; subWidgets) { widget.draw (x + colX[i % cols], y + rowY[i / cols]); } } // Calculates from all rows and columns of widgets. void getMinimumSize (out int w, out int h) { if (rows*cols == 0) { // special case w = h = 2*deco.border; return; } // Find the sizes of all subWidgets int[] widgetW = new int[subWidgets.length]; // dimensions int[] widgetH = new int[subWidgets.length]; foreach (i,widget; subWidgets) widget.getCurrentSize (widgetW[i],widgetH[i]); // Find row heights and column widths (non cumulative) rowH.length = rows; colW.length = cols; //WARNING: code reliant on these being initialised to zero for (uint i = 0; i < subWidgets.length; ++i) { uint x = i / cols; // row if (rowH[x] < widgetH[i]) rowH[x] = widgetH[i]; x = i % cols; // column if (colW[x] < widgetW[i]) colW[x] = widgetW[i]; } // rowY / colX rowY.length = rows; colX.length = cols; int cum = deco.border; foreach (i, x; rowH) { rowY[i] = cum; cum += x + PADDING; } h = cum + deco.border - PADDING; // total height cum = deco.border; foreach (i, x; colW) { colX[i] = cum; cum += x + PADDING; } w = cum + deco.border - PADDING; // total width } // Pass event on to relevant widget. Simply return if not on a widget. void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { if (rows*cols == 0) return; // special case // Find the column int i = cols - 1; // starting from right... while (cx < colX[i]) { // decrement while left of this column if (i == 0) return; // left of first column --i; } // now (cx >= colX[i]) if (cx >= colX[i] + colW[i]) return; // between columns // Find the row; int j = rows - 1; while (cy < rowY[j]) { if (j == 0) return; --j; } if (cy >= rowY[j] + rowH[j]) return; // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell) cx -= colX[i]; cy -= rowY[j]; IWidget widg = subWidgets[i + j*cols]; widg.getCurrentSize (i,j); if (cx < i && cy < j) widg.clickEvent (cx, cy, b, state); } protected: const PADDING = 4; // padding between rows/cols int rows, cols; // number of cells in grid int[] rowH; // row height (highest widget in the row) int[] colW; // column width (widest widget) int[] rowY; // cumulative rowH[i-1] + border and padding int[] colX; // cumulative colW[i-1] + border and padding IWidget[] subWidgets; // all widgets in the grid (by row): /* SubWidget order: [ 2 3 ] * [ 0 1 ] */ } /// First interactible widget class ButtonWidget : Widget { bool pushed = false;// true if button is pushed in this (IWindow, IParentWidget parent, int[] data) { if (data.length != 2) throw new WidgetDataException; deco = new WidgetDecoration (parent.decoration); w = data[0] + 2*deco.border; h = data[1] + 2*deco.border; } void draw (int x, int y) { if (pushed) gl.setColor (1f, 0f, 1f); else gl.setColor (.6f, 0f, .6f); gl.drawBox (x,y, w,h); } void getMinimumSize (out int w, out int h) { w = this.w; // button is not resizable h = this.h; } void clickEvent (ushort, ushort, ubyte b, bool state) { if (b == 1) pushed = state; // very basic } } //END Widgets