Mercurial > projects > mde
view mde/gui/widget/Floating.d @ 91:4d5d53e4f881
Shared alignment for dynamic content lists - finally implemented! Lots of smaller changes too.
Some debugging improvements.
When multiple .mtt files are read for merging, files with invalid headers are ignored and no error is thrown so long as at least one file os valid.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 16 Oct 2008 17:43:48 +0100 |
parents | b525ff28774b |
children | 085f2ca31914 |
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. Becoming a widget. */ module mde.gui.widget.Floating; import mde.gui.widget.Widget; import mde.gui.exception; /+import mde.gui.widget.createWidget; import mde.gui.IGui; import mde.gui.renderer.createRenderer; import mt = mde.mergetag.DataSet; import mde.mergetag.parse.parseTo : parseTo; import mde.mergetag.parse.parseFrom : parseFrom; +/ import tango.util.log.Log : Log, Logger; private Logger logger; static this () { logger = Log.getLogger ("mde.gui.widget.Floating"); } //FIXME - documentation /** 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). */ /** An area to contain floating widgets. * * The position of each sub-widget is set from data, but not the size. * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to * know their positions. Size setting is still under work FIXME. * * Data: Each string item is interpreted as a subwidget widgetID. * Ints supplied may consist of just the widget type or * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords). */ class FloatingAreaWidget : SizableWidget { this (IWidgetManager mgr, widgetID id, WidgetData data) { subWidgets.length = data.strings.length; foreach (i,s; data.strings) subWidgets[i] = mgr.makeWidget (s); sWCoords.length = subWidgets.length; if (data.ints.length != 1) { if (data.ints.length != 2*subWidgets.length + 1) { throw new WidgetDataException (this); } foreach (i, ref c; sWCoords) { c.x = cast(wdim) data.ints[i + 1]; c.y = cast(wdim) data.ints[i + 1 + sWCoords.length]; } } super (mgr, id, data); foreach (w; subWidgets) { //FIXME: set default size w.setWidth (w.minWidth, -1); w.setHeight (w.minHeight, -1); } } void setPosition (wdim x, wdim y) { super.setPosition (x,y); foreach (i,c; sWCoords) subWidgets[i].setPosition (x+c.x, y+c.y); } void draw () { super.draw; mgr.renderer.restrict (x,y, w,h); foreach (w; subWidgets) w.draw; } protected: IChildWidget[] subWidgets; // all subwidgets, framed or not wdimPair[] sWCoords; // coords for subwidgets, relative to this widget /+ /** 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"); // Save gui and create the renderer: gui_ = gui; rend = createRenderer (gui.rendererName); // Create the primary widget (and indirectly all sub-widgets), throwing on error: // Note: GridLayoutWidget's this relies on rend.layoutSpacing. widget = makeWidget (0); // primary widget always has ID 0. // This data is no longer needed by Window, although its sub-arrays may still be used, so // let the GC collect what it can: widgetData = null; widgetStrings = null; // get border sizes: border = rend.setSizable (isWSizable, isHSizable); // depends on widget // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty: if ((widget.adjust (mutableData)).length != 0) // adjust/set size, etc., depends on rend logger.warn ("Local widget position data is invalid!"); 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); // Calculate mw/mh and xw/yh (cached data): mw = widget.minWidth + border.l + border.r; mh = widget.minHeight + border.t + border.b; xw = x+w; yh = y+h; } //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); } char[] getWidgetString (int i) in { // widgetStrings is freed at same time as widgetData // but widgetData is guaranteed to be read assert (widgetData !is null, "getWidgetString called after widget creation finished"); } body { char[]* p; if (widgetStrings is null || (p = i in widgetStrings) is null ) throw new WindowLoadException ("Needed widgetStrings not set for Window"); return *p; } /** 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; /+ Doesn't this have no effect except when getCreationData throws, in which case the data + isn't used anyway? I'm sure it was added for a reason... FIXME and below widgetData[i] = null; // Make sure the same ID doesn't get used by a recursive call +/ widgetData[i] = widget.getCreationData; return i; } int addWidgetString (char[] str) { int i; if (widgetStrings is null) i = 0; else i = widgetStrings.keys[$-1] + 1; /+ See above. FIXME widgetStrings[i] = null; // Make sure the same ID doesn't get used by a recursive call +/ widgetStrings[i] = str; return i; } IGui gui () { return gui_; } void requestRedraw () { gui_.requestRedraw; } IRenderer renderer () in { assert (rend !is null, "Window.renderer: rend is null"); } body { 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; } wdim minWidth () { return mw; } wdim minHeight () { return mh; } void getCurrentSize (out wdim cw, out wdim ch) { cw = w; ch = h; } void setWidth (wdim nw, int dir) { if (nw < mw) w = mw; // clamp else w = nw; xw = x + w; widget.setWidth (w - border.l - border.r, dir); } void setHeight (wdim nh, int dir) { if (nh < mh) h = mh; // clamp else h = nh; yh = y + h; widget.setHeight (h - border.t - border.b, dir); } void setPosition (wdim nx, wdim 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 (); // necessary whenever the window is moved; setPosition is called after resizes and moves } IWidget getWidget (wdim cx, wdim 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 (wdabs cx, wdabs cy, ubyte b, bool state) { if (b == 1 && state == true) { resizeType = rend.getResizeType (cx-x, cy-y, w,h); if (resizeType != RESIZE_TYPE.NONE) { // Some type of resize // Set x/yDrag (unfortunately these need to be different for each edge) if (resizeType & RESIZE_TYPE.L) xDrag = w + cx; else if (resizeType & RESIZE_TYPE.R) xDrag = w - cx; if (resizeType & RESIZE_TYPE.T) yDrag = h + cy; else if (resizeType & RESIZE_TYPE.B) yDrag = h - cy; // Add the callbacks (they use resizeType which has already been set) 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.RESIZE_TYPE RESIZE_TYPE; //BEGIN Window moving and resizing void moveCallback (wdabs cx, wdabs cy) { setPosition (cx-xDrag, cy-yDrag); } void resizeCallback (wdabs cx, wdabs cy) { debug scope(failure) logger.trace ("resizeCallback: failure"); // This function is only called if some resize is going to happen. // x,y are used as parameters to setPosition as well as being affected by it; somewhat // pointless but fairly effective. if (resizeType & RESIZE_TYPE.L) { wdim xSize = xDrag - cx; if (xSize < mw) xSize = mw; // clamp x += w - xSize; setWidth (xSize, 1); } else if (resizeType & RESIZE_TYPE.R) { setWidth (xDrag + cx, -1); } if (resizeType & RESIZE_TYPE.T) { wdim ySize = yDrag - cy; if (ySize < mh) ySize = mh; y += h - ySize; setHeight (ySize, 1); } else if (resizeType & RESIZE_TYPE.B) { setHeight (yDrag + cy, -1); } // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the // resizing: setPosition (x, y); } bool endCallback (wdabs cx, wdabs 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); return true; // we've handled the up-click } return false; // we haven't handled it } wdim xDrag, yDrag; // where a drag starts relative to x and y IRenderer.RESIZE_TYPE 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) IGui gui_; // The gui managing this window IRenderer rend; // The window's renderer IWidget widget; // The primary widget in this window. wdim x = -1, y = -1; // Window position wdsize w,h; // Window size (calculated from Widgets) wdim xw, yh; // x+w, y+h (frequent use by clickEvent) wdim widgetX, widgetY; // Widget position (= window position plus BORDER_WIDTH) wdim mw = -1, mh = -1; // minimal size (negative means requires calculation) BorderDimensions border; // Total border size (move plus resize) +/ }