Mercurial > projects > mde
view mde/gui/widget/Floating.d @ 144:66c58e5b0062
Added a BoolContent-based collapsible widget.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Tue, 10 Feb 2009 12:57:09 +0000 |
parents | 29a524e7c858 |
children | 075705ad664a |
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.AParentWidget; import mde.gui.exception; import mde.content.IContent; 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 sub-widget widgetID to add as a floating window. * Ints consist of widget type followed by a border type (flags from IRenderer.Border.BTYPE) for * each sub-widget. */ class FloatingAreaWidget : AParentWidget { this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) { if (data.ints.length != 1 + data.strings.length) throw new WidgetDataException (this); super (mgr, parent, id); subWidgets.length = data.strings.length; // widgets created from string data sWOrder.length = subWidgets.length; sWData.length = subWidgets.length; foreach (i,s; data.strings) { subWidgets[i] = mgr.makeWidget (this, s, content); sWOrder[i] = i; sWData[i].borderType = cast(BTYPE) data.ints[i+1]; } wdim[] dd = mgr.dimData (id); if (dd.length == subWidgets.length * 4) { foreach (i, ref d; sWData) (&d.x)[0..4] = dd[i*4..i*4+4]; } } override bool setup (uint n, uint flags) { debug (mdeWidgets) logger.trace ("FloatingAreaWidget.setup"); foreach (i, ref d; sWData) with (d) { auto widg = subWidgets[i]; if (!widg.setup (n, flags) && !(flags & 1)) continue; // no changes; skip the rest d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable); mw = widg.minWidth + border.x1 + border.x2; mh = widg.minHeight + border.y1 + border.y2; if (w < mw || !widg.isWSizable) w = mw; if (h < mh || !widg.isHSizable) h = mh; widg.setWidth (w - border.x1 - border.x2, -1); widg.setHeight (h - border.y1 - border.y2, -1); } return n == 0; // floating area size is not changed } override bool saveChanges () { wdim[] dd = new wdim[sWData.length*4]; foreach (i, ref d; sWData) { subWidgets[i].saveChanges (); dd[4*i..4*i+4] = (&d.x)[0..4]; } mgr.dimData (id, dd); // save positions return true; } override void setWidth (wdim nw, int) { w = nw; // check all floating widgets are visible foreach (i, ref d; sWData) with (d) { if (x + w > this.w) x = (w > this.w) ? 0 : this.w - w; } } override void setHeight (wdim nh, int) { h = nh; foreach (i, ref d; sWData) with (d) { if (y + h > this.h) y = (h > this.h) ? 0 : this.h - h; } } override bool isWSizable () { return true; } override bool isHSizable () { return true; } override void setPosition (wdim nx, wdim ny) { x = nx; y = ny; size_t n = subWidgets.length; foreach (i, ref d; sWData) subWidgets[i].setPosition (x + d.x + d.border.x1, y + d.y + d.border.y1); } override void minWChange (IChildWidget widg, wdim nmw) { with (sWData[getWidgetIndex(widg)]) { mw = nmw + border.x1 + border.x2; if (mw > w || !widg.isWSizable) { w = mw; widg.setWidth (w - border.x1 - border.x2, -1); } if (x + w > this.w) x = (w > this.w) ? 0 : this.w - w; widg.setPosition (this.x + border.x1 + x,this.y + border.y1 + y); mgr.requestRedraw; } } override void minHChange (IChildWidget widg, wdim nmh) { with (sWData[getWidgetIndex(widg)]) { mh = nmh + border.y1 + border.y2; if (mh > h || !widg.isHSizable) { h = mh; widg.setHeight (h - border.y1 - border.y2, -1); } if (y + h > this.h) y = (h > this.h) ? 0 : this.h - h; widg.setPosition (this.x + border.x1 + x,this.y + border.y1 + y); mgr.requestRedraw; } } override void draw () { super.draw; mgr.renderer.restrict (x,y, w,h); foreach (i; sWOrder) with (sWData[i]) { mgr.renderer.drawWindow (&border, this.x + x, this.y + y, w, h); subWidgets[i].draw; } mgr.renderer.relax; } override IChildWidget getWidget (wdim cx, wdim cy) { debug scope (failure) logger.warn ("getWidget: failure; values: click; pos; width: {},{}; {},{}; {},{}", cx, cy, x, y, w, h); debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)"); foreach_reverse (j,i; sWOrder) with (sWData[i]) { wdim lx = cx - (this.x + x); wdim ly = cy - (this.y + y); if (lx >= 0 && lx < w && ly >= 0 && ly < h) { sWOrder[j..$-1] = sWOrder[j+1..$].dup; sWOrder[$-1] = i; mgr.requestRedraw; if (lx >= border.x1 && lx < w-border.x2 && ly >= border.y1 && ly < h-border.y2) return subWidgets[i].getWidget (cx,cy); event = i; return this; } } event = size_t.max; return this; // no match } override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) { if (event > subWidgets.length) return 0; if (b == 1 && state == true) { active = event; with (sWData[active]) { resizeType = border.getResize (cx - this.x - x, cy - this.y - y, w,h); alias border.RESIZE RESIZE; if (resizeType != RESIZE.NONE) { // Set x/yDrag (unfortunately these need to be different for each edge) if (resizeType & RESIZE.X1) xDrag = w + cx; else if (resizeType & RESIZE.X2) xDrag = w - cx; if (resizeType & RESIZE.Y1) yDrag = h + cy; else if (resizeType & RESIZE.Y2) yDrag = h - cy; mgr.addClickCallback (&endCallback); mgr.addMotionCallback (&resizeCallback); } else if (borderType & BTYPE.MOVE) { // window is being moved xDrag = cx - x; yDrag = cy - y; mgr.addClickCallback (&endCallback); mgr.addMotionCallback (&moveCallback); } } } return 0; } protected: void moveCallback (wdabs cx, wdabs cy) { with (sWData[active]) { x = cx-xDrag; y = cy-yDrag; if (x < 0) x = 0; else if (x + w > this.w) x = (w > this.w) ? 0 : this.w - w; if (y < 0) y = 0; else if (y + h > this.h) y = (h > this.h) ? 0 : this.h - h; subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1); } mgr.requestRedraw; } void resizeCallback (wdabs cx, wdabs cy) { with (sWData[active]) { if (resizeType & RESIZE.X1) { wdim ow = w; w = xDrag - cx; // new width if (w < mw) w = mw; // limit to min width x += ow - w; // move by difference if (x < 0) { // limit to left edge of area w += x; x = 0; } subWidgets[active].setWidth (w - border.x1 - border.x2, 1); } else if (resizeType & RESIZE.X2) { w = xDrag + cx; // new width if (w < mw) w = mw; // limit to min width if (x + w > this.w) // limit to right edge of area w = this.w - x; subWidgets[active].setWidth (w - border.x1 - border.x2, -1); } if (resizeType & RESIZE.Y1) { wdim oh = h; h = yDrag - cy; if (h < mh) h = mh; y += oh - h; if (y < 0) { h += y; y = 0; } subWidgets[active].setHeight (h - border.y1 - border.y2, 1); } else if (resizeType & RESIZE.Y2) { h = yDrag + cy; if (h < mh) h = mh; if (y + h > this.h) h = this.h - y; subWidgets[active].setHeight (h - border.y1 - border.y2, -1); } // Reposition widget and sub-widgets: subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1); } mgr.requestRedraw; } bool endCallback (wdabs, wdabs, ubyte b, bool state) { if (b == 1 && state == false) { // end of a move/resize mgr.removeCallbacks (cast(void*) this); return true; // we've handled the up-click } return false; // we haven't handled it } struct SWData { // NOTE: x,y,w,h must be first elements; search (&d.x) wdim x,y; // position (corner of border) wdim w,h; // size (including border) wdim mw,mh; BTYPE borderType; // what type of border to put around the widget IRenderer.Border border; } static assert (SWData.alignof == 4); // assumptions for optimization; search (&d.x) SWData[] sWData; size_t[] sWOrder; // indexes for draw order (top widget at end of list) // Click/drag information: alias IRenderer.Border.BTYPE BTYPE; alias IRenderer.Border.RESIZE RESIZE; size_t event = size_t.max; // Border with last click/release: size_t.max: main area (no subwidget), i: subwidget[i] size_t active = size_t.max; // Like event, but refers to widget being moved/resized wdim xDrag, yDrag; // where a drag starts relative to x and y RESIZE resizeType; // Type of current resize }