Mercurial > projects > mde
view mde/gui/widget/Window.d @ 40:b28d7adc786b
Made GUI more robust to mutable data changes and improved much of GridLayoutWidget's code.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 08 May 2008 16:05:51 +0100 |
parents | 5132301e9ed7 |
children | b3a6ca4516b4 |
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/>. */ /** The Window class. Hopefully eventually this will become a widget to make things a bit more * generic. */ module mde.gui.widget.Window; import mde.gui.widget.Ifaces; import mde.gui.widget.createWidget; import mde.gui.IGui; import mde.gui.exception; import mt = mde.mergetag.DataSet; import tango.scrapple.text.convert.parseTo : parseTo; import tango.scrapple.text.convert.parseFrom : parseFrom; import tango.util.log.Log : Log, Logger; private Logger logger; static this () { logger = Log.getLogger ("mde.gui.widget.Window"); } /** GUI Window class * * A window class instance does two things: (1) specify a region of the screen upon which the window * and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets. * * Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when * created, be given its int[] of data, which this() must confirm is valid (or throw). */ class Window : mt.IDataSection, IWindow { //BEGIN Methods for GUI this (char[] id) { name = id; } /** Call after loading is finished to setup the window and confirm that it's valid. * * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an * exception occurs! */ void finalise (IGui gui) in { assert (gui !is null, "Window.finalise ("~name~"): gui is null"); } body { // Check data was loaded: if (widgetData is null) throw new WindowLoadException ("No widget creation data"); gui_ = gui; rend = gui.renderer; // Get border sizes border = rend.getBorder (BORDER_TYPES.WINDOW_TOTAL); resize = rend.getBorder (BORDER_TYPES.WINDOW_RESIZE); // Create the primary widget (and indirectly all sub-widgets), throwing on error: 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 widget.getCurrentSize (w,h); // and get this size w += border.l + border.r; // Adjust for border h += border.t + border.b; widgetX = x + border.l; // widget position widgetY = y + border.t; // must be updated if the window is moved widget.setPosition (widgetX, widgetY); xw = x+w; yh = y+h; } //BEGIN Mergetag code void addTag (char[] tp, mt.ID id, char[] dt) { // Priority is HIGH_LOW, so don't overwrite data which has already been loaded. if (tp == INTAA) { if (id == WDGD && widgetData == null) { widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt); } } else if (tp == INTA) { if (id == MD && mutableData == null) { mutableData = parseTo!(int[]) (dt); } } else if (tp == INT) { if (id == X && x == -1) { x = parseTo!(int) (dt); } else if (id == Y && y == -1) { y = parseTo!(int) (dt); } } } void writeAll (ItemDelg dlg) in { assert (widgetData is null, "Window.writeAll: widgetData !is null"); } body { /+ NOTE: currently editing is impossible... if (edited) { // only save the widget creation data if it's been adjusted: addCreationData (widget); // generate widget save data dlg (INTAA, WDGD, parseFrom!(int[][int]) (widgetData)); }+/ // Save mutable data: dlg (INTA, MD, parseFrom!(int[]) (widget.getMutableData)); // Save the window position: dlg (INT, X, parseFrom!(int) (x)); dlg (INT, Y, parseFrom!(int) (y)); } private static const { auto INTAA = "int[][int]"; auto INTA = "int[]"; auto INT = "int"; auto WDGD = "widgetData"; auto MD = "mutableData"; auto X = "x"; auto Y = "y"; } //END Mergetag code //END Methods for GUI //BEGIN IWindow methods /** Get/create a widget by ID. * * Should $(I only) be called internally and by sub-widgets! */ IWidget makeWidget (widgetID i) in { // widgetData is normally left to be garbage collected after widgets have been created: assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null"); } body { /* Each widget returned should be a unique object; if multiple widgets are requested with * the same ID, a new widget is created each time. */ int[]* d = i in widgetData; if (d is null) throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found"); // Throws WidgetDataException (a WindowLoadException) if bad data: return createWidget (this, *d); } /** Add this widget's data to that to be saved, returning it's widgetID. */ widgetID addCreationData (IWidget widget) { widgetID i; if (widgetData is null) i = 0; else i = widgetData.keys[$-1] + 1; widgetData[i] = null; // Make sure the same ID doesn't get used by a recursive call widgetData[i] = widget.getCreationData; return i; } IGui gui () { return gui_; } IRenderer renderer () { return rend; } //END IWindow methods //BEGIN IWidget methods //FIXME: how many of these methods are actually needed/used? int[] adjust (int[]) { // simply not relevant (never used) return []; } int[] getCreationData () { // simply not relevant (never used) return []; } int[] getMutableData () { // simply not relevant (never used) return []; } bool isWSizable () { return widget.isWSizable; } bool isHSizable () { return widget.isHSizable; } void getMinimalSize (out int wM, out int hM) { if (mh < 0) { // calculate if necessary widget.getMinimalSize (mw, mh); mw += border.l + border.r; mh += border.t + border.b; } wM = mw; hM = mh; } void getCurrentSize (out int cw, out int ch) { cw = w; ch = h; } void setSize (int nw, int nh) { getMinimalSize (w,h); if (nw > w) w = nw; // expand if new size is larger, but don't go smaller if (nh > h) h = nh; xw = x+w; yh = y+h; widget.setSize (w - border.l - border.r, h - border.t - border.b); gui_.requestRedraw (); // obviously necessary whenever the window's size is changed } void setPosition (int nx, int ny) { x = nx; y = ny; xw = x+w; yh = y+h; widgetX = x + border.l; widgetY = y + border.t; widget.setPosition (widgetX, widgetY); gui_.requestRedraw (); // obviously necessary whenever the window is moved } IWidget getWidget (int cx, int cy) { if (cx < x || cx >= xw || cy < y || cy >= yh) // not over window return null; if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b) // over the widget return widget.getWidget (cx, cy); else // over the window border return this; } void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { if (b == 1 && state == true) { if (cx < x + resize.l || cx >= xw - resize.r || cy < y + resize.t || cy >= yh - resize.b) { // window is being resized /* check for resizes (different to above; use whole border giving larger area for * diagonal resizes). */ resizeType = RESIZE.NONE; if (isWSizable) { if (cx < x + border.l) { xDrag = w + cx; resizeType = RESIZE.L; } else if (cx >= xw - border.r) { xDrag = w - cx; resizeType = RESIZE.R; } } if (isHSizable) { if (cy < y + border.t) { yDrag = h + cy; resizeType |= RESIZE.T; } else if (cy >= yh - border.b) { yDrag = h - cy; resizeType |= RESIZE.B; } } if (resizeType != RESIZE.NONE) { // only if some valid size is being done gui_.addClickCallback (&endCallback); gui_.addMotionCallback (&resizeCallback); } } else { // window is being moved xDrag = cx - x; yDrag = cy - y; gui_.addClickCallback (&endCallback); gui_.addMotionCallback (&moveCallback); } } } void draw () { // background rend.drawWindow (x,y, w,h); // Tell the widget to draw itself: widget.draw(); } //END IWidget methods private: alias IRenderer.BorderDimensions BorderDimensions; alias IRenderer.BORDER_TYPES BORDER_TYPES; //BEGIN Window moving and resizing void moveCallback (ushort cx, ushort cy) { setPosition (cx-xDrag, cy-yDrag); } void resizeCallback (ushort cx, ushort cy) { if (resizeType & RESIZE.L) { int mw, nw; getMinimalSize (mw, nw); // (only want mw) nw = xDrag - cx; if (nw < mw) nw = mw; // clamp mw = x + w - nw; // reuse setSize (nw, h); setPosition (mw, y); } else if (resizeType & RESIZE.R) { setSize (xDrag + cx, h); setPosition (x, y); } if (resizeType & RESIZE.T) { int mh, nh; getMinimalSize (nh, mh); nh = yDrag - cy; if (nh < mh) nh = mh; mh = y + h - nh; setSize (w, nh); setPosition (x, mh); } else if (resizeType & RESIZE.B) { setSize (w, yDrag + cy); setPosition (x, y); } } void endCallback (ushort cx, ushort cy, ubyte b, bool state) { if (b == 1 && state == false) { // The mouse shouldn't have moved since the motion callback // was last called, so there's nothing else to do now. gui_.removeCallbacks (cast(void*) this); } } int xDrag, yDrag; // where a drag starts relative to x and y enum RESIZE : ubyte { NONE = 0x0, L = 0x1, R = 0x2, T = 0x4, B = 0x8 } RESIZE resizeType; // Type of current resize //END Window moving and resizing // Load/save data: public char[] name; // The window's name (id from config file) //bool edited = false; // True if any widgets have been edited (excluding scaling) // Data used for saving and loading (null in between): int[][widgetID] widgetData = null;// Data for all widgets under this window int[] mutableData = null; // Widget's mutable data (adjusted sizes, etc.) IGui gui_; // The gui managing this window IRenderer rend; // The window's renderer IWidget widget; // The primary widget in this window. int x = -1, y = -1; // Window position int w,h; // Window size (calculated from Widgets) int xw, yh; // x+w, y+h (frequent use by clickEvent) int widgetX, widgetY; // Widget position (= window position plus BORDER_WIDTH) int mw = -1, mh = -1; // minimal size BorderDimensions border; // Total border size (move plus resize) BorderDimensions resize; // The outer resize part of the border }