Mercurial > projects > mde
view mde/gui/widget/Floating.d @ 96:dbf332403c6e
Improvements to FloatingAreaWidget: positioning, passing click events and draw order.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 06 Nov 2008 13:16:39 +0000 |
parents | 2a364c7d82c9 |
children | 30470bc19ca4 |
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"); } /** An area to contain floating widgets. * * The position of each sub-widget is set from dimension data, but not the size. * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to * know their positions. * * 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 : AParentWidget { this (IWidgetManager mgr, widgetID id, WidgetData data) { if (data.ints.length != 1) throw new WidgetDataException (this); subWidgets.length = data.strings.length; // widgets created from string data sWOrder.length = subWidgets.length; foreach (i,s; data.strings) { subWidgets[i] = mgr.makeWidget (s); sWOrder[i] = i; } sWCoords = mgr.dimData (id); if (sWCoords.length != subWidgets.length * 2) { // don't bother logging a warning; correct data will be saved anyway sWCoords.length = subWidgets.length * 2; // maybe some data kept } super (mgr, id, data); } bool saveChanges () { foreach (widget; subWidgets) widget.saveChanges (); mgr.setDimData (id, sWCoords); // save positions return true; } void setWidth (wdim nw, int) { w = nw; // check all floating widgets are visible foreach (i, widg; subWidgets) { wdim d; if (sWCoords[i] + (d = widg.width) > w) { if (d > w) sWCoords[i] = 0; else sWCoords[i] = w - d; } } } void setHeight (wdim nh, int) { h = nh; foreach (i, widg; subWidgets) { wdim d; size_t n = i + subWidgets.length; if (sWCoords[n] + (d = widg.height) > h) { if (d > h) sWCoords[n] = 0; else sWCoords[n] = h - d; } } } bool isWSizable () { return true; } bool isHSizable () { return true; } void setPosition (wdim x, wdim y) { super.setPosition (x,y); size_t n = subWidgets.length; foreach (i,widg; subWidgets) widg.setPosition (x+sWCoords[i], y+sWCoords[i+n]); } void draw () { super.draw; mgr.renderer.restrict (x,y, w,h); foreach (i; sWOrder) subWidgets[i].draw; } IChildWidget getWidget (wdim cx, wdim cy) { debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)"); size_t n = subWidgets.length; foreach_reverse (j,i; sWOrder) { wdim lx = cx - (x + sWCoords[i ]); wdim ly = cy - (y + sWCoords[i+n]); if (lx >= 0 && lx < subWidgets[i].width && ly >= 0 && ly < subWidgets[i].height) { sWOrder[j..$-1] = sWOrder[j+1..$].dup; sWOrder[$-1] = i; mgr.requestRedraw; return subWidgets[i]; } } return this; // no match } protected: wdim[] sWCoords; // coords for subwidgets, relative to this widget: [x1,x2,...,y1,y2,...] size_t[] sWOrder; // indexes for draw order (top widget at end of list) /+ /** 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) +/ }