Mercurial > projects > mde
view mde/gui/widget/Floating.d @ 92:085f2ca31914
Shared alignments supported in more complex cases.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Tue, 21 Oct 2008 09:57:19 +0100 |
parents | 4d5d53e4f881 |
children | 2a364c7d82c9 |
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 : ParentWidget { this (IWidgetManager mgr, widgetID id, WidgetData data) { subWidgets.length = data.strings.length; foreach (i,s; data.strings) subWidgets[i] = mgr.makeWidget (s); foreach (w; subWidgets) w.finalize; 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); } } bool isWSizable () { return true; } bool isHSizable () { return true; } 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: 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) +/ }