comparison mde/gui/widget/Floating.d @ 80:ea58f277f487

Gui reorganization and changes; partial implementation of floating widgets. Moved contents of mde/gui/WidgetData.d elsewhere; new gui/WidgetDataSet.d and gui/types.d modules. Changes to widget/createWidget.d Partially implemented FloatingAreaWidget to provide an area for floating "window" widgets. New DebugWidget and some uses of it (e.g. bad widget data). Decoupled OptionChanges from Options.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 07 Aug 2008 11:25:27 +0100
parents mde/gui/widget/Window.d@25cb7420dc91
children ac1e3fd07275
comparison
equal deleted inserted replaced
79:61ea26abe4dd 80:ea58f277f487
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 /** The Window class. Becoming a widget. */
17 module mde.gui.widget.Floating;
18
19 import mde.gui.widget.Widget;
20 import mde.gui.exception;
21 /+import mde.gui.widget.createWidget;
22
23 import mde.gui.IGui;
24 import mde.gui.renderer.createRenderer;
25
26 import mt = mde.mergetag.DataSet;
27 import mde.mergetag.parse.parseTo : parseTo;
28 import mde.mergetag.parse.parseFrom : parseFrom;
29 +/
30 import tango.util.log.Log : Log, Logger;
31
32 private Logger logger;
33 static this () {
34 logger = Log.getLogger ("mde.gui.widget.Window");
35 }
36 //FIXME - documentation
37
38 /** GUI Window class
39 *
40 * A window class instance does two things: (1) specify a region of the screen upon which the window
41 * and its associated widgets are drawn, and (2) load, save, and generally manage all its widgets.
42 *
43 * Let the window load a table of widget data, of type int[][widgetID]. Each widget will, when
44 * created, be given its int[] of data, which this() must confirm is valid (or throw).
45 */
46 /** An area to contain floating widgets.
47 *
48 * The position of each sub-widget is set from data, but not the size.
49 * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to
50 * know their positions. Size setting is still under work FIXME.
51 *
52 * Data: Each string item is interpreted as a subwidget widgetID.
53 * Ints supplied may consist of just the widget type or
54 * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords).
55 */
56 class FloatingAreaWidget : SizableWidget, IParentWidget
57 {
58 this (IWidgetManager mgr, WidgetData data) {
59 subWidgets.length = data.strings.length;
60 foreach (i,s; data.strings)
61 subWidgets[i] = mgr.makeWidget (s, this);
62 sWCoords.length = subWidgets.length;
63
64 if (data.ints.length != 1) {
65 if (data.ints.length != 2*subWidgets.length + 1) {
66 throw new WidgetDataException;
67 }
68 foreach (i, ref c; sWCoords) {
69 c.x = cast(wdim) data.ints[i + 1];
70 c.y = cast(wdim) data.ints[i + 1 + sWCoords.length];
71 }
72 }
73
74 super (mgr, data);
75
76 foreach (w; subWidgets) {
77 //FIXME: set default size
78 w.setWidth (w.minWidth, -1);
79 w.setHeight (w.minHeight, -1);
80 }
81 }
82
83 void setPosition (wdim x, wdim y) {
84 super.setPosition (x,y);
85
86 foreach (i,c; sWCoords)
87 subWidgets[i].setPosition (x+c.x, y+c.y);
88 }
89
90 void draw () {
91 super.draw;
92
93 mgr.renderer.restrict (x,y, w,h);
94
95 foreach (w; subWidgets)
96 w.draw;
97 }
98
99 protected:
100 IChildWidget[] subWidgets; // all subwidgets, framed or not
101 wdimPair[] sWCoords; // coords for subwidgets, relative to this widget
102
103 /+
104 /** Call after loading is finished to setup the window and confirm that it's valid.
105 *
106 * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an
107 * exception occurs! */
108 void finalise (IGui gui)
109 in {
110 assert (gui !is null, "Window.finalise ("~name~"): gui is null");
111 } body {
112 // Check data was loaded:
113 if (widgetData is null)
114 throw new WindowLoadException ("No widget creation data");
115
116 // Save gui and create the renderer:
117 gui_ = gui;
118 rend = createRenderer (gui.rendererName);
119
120 // Create the primary widget (and indirectly all sub-widgets), throwing on error:
121 // Note: GridLayoutWidget's this relies on rend.layoutSpacing.
122 widget = makeWidget (0); // primary widget always has ID 0.
123 // This data is no longer needed by Window, although its sub-arrays may still be used, so
124 // let the GC collect what it can:
125 widgetData = null;
126 widgetStrings = null;
127
128 // get border sizes:
129 border = rend.setSizable (isWSizable, isHSizable); // depends on widget
130
131 // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty:
132 if ((widget.adjust (mutableData)).length != 0) // adjust/set size, etc., depends on rend
133 logger.warn ("Local widget position data is invalid!");
134 mutableData = null; // no longer needed
135
136 widget.getCurrentSize (w,h); // and get this size
137 w += border.l + border.r; // Adjust for border
138 h += border.t + border.b;
139
140 widgetX = x + border.l; // widget position
141 widgetY = y + border.t; // must be updated if the window is moved
142 widget.setPosition (widgetX, widgetY);
143
144 // Calculate mw/mh and xw/yh (cached data):
145 mw = widget.minWidth + border.l + border.r;
146 mh = widget.minHeight + border.t + border.b;
147
148 xw = x+w;
149 yh = y+h;
150 }
151 //END Methods for GUI
152
153 //BEGIN IWindow methods
154 /** Get/create a widget by ID.
155 *
156 * Should $(I only) be called internally and by sub-widgets! */
157 IWidget makeWidget (widgetID i)
158 in {
159 // widgetData is normally left to be garbage collected after widgets have been created:
160 assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null");
161 } body {
162 /* Each widget returned should be a unique object; if multiple widgets are requested with
163 * the same ID, a new widget is created each time. */
164
165 int[]* d = i in widgetData;
166 if (d is null)
167 throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
168
169 // Throws WidgetDataException (a WindowLoadException) if bad data:
170 return createWidget (this, *d);
171 }
172
173 char[] getWidgetString (int i)
174 in {
175 // widgetStrings is freed at same time as widgetData
176 // but widgetData is guaranteed to be read
177 assert (widgetData !is null, "getWidgetString called after widget creation finished");
178 } body {
179 char[]* p;
180 if (widgetStrings is null ||
181 (p = i in widgetStrings) is null )
182 throw new WindowLoadException ("Needed widgetStrings not set for Window");
183
184 return *p;
185 }
186
187 /** Add this widget's data to that to be saved, returning it's widgetID. */
188 widgetID addCreationData (IWidget widget)
189 {
190 widgetID i;
191 if (widgetData is null)
192 i = 0;
193 else
194 i = widgetData.keys[$-1] + 1;
195
196 /+ Doesn't this have no effect except when getCreationData throws, in which case the data
197 + isn't used anyway? I'm sure it was added for a reason... FIXME and below
198 widgetData[i] = null; // Make sure the same ID doesn't get used by a recursive call
199 +/
200 widgetData[i] = widget.getCreationData;
201
202 return i;
203 }
204
205 int addWidgetString (char[] str)
206 {
207 int i;
208 if (widgetStrings is null)
209 i = 0;
210 else
211 i = widgetStrings.keys[$-1] + 1;
212
213 /+ See above. FIXME
214 widgetStrings[i] = null; // Make sure the same ID doesn't get used by a recursive call
215 +/
216 widgetStrings[i] = str;
217
218 return i;
219 }
220
221 IGui gui () {
222 return gui_;
223 }
224
225 void requestRedraw () {
226 gui_.requestRedraw;
227 }
228
229 IRenderer renderer ()
230 in {
231 assert (rend !is null, "Window.renderer: rend is null");
232 } body {
233 return rend;
234 }
235 //END IWindow methods
236
237 //BEGIN IWidget methods
238 //FIXME: how many of these methods are actually needed/used?
239 int[] adjust (int[]) { // simply not relevant (never used)
240 return [];
241 }
242 int[] getCreationData () { // simply not relevant (never used)
243 return [];
244 }
245 int[] getMutableData () { // simply not relevant (never used)
246 return [];
247 }
248
249 bool isWSizable () {
250 return widget.isWSizable;
251 }
252 bool isHSizable () {
253 return widget.isHSizable;
254 }
255
256 wdim minWidth () {
257 return mw;
258 }
259 wdim minHeight () {
260 return mh;
261 }
262 void getCurrentSize (out wdim cw, out wdim ch) {
263 cw = w;
264 ch = h;
265 }
266
267 void setWidth (wdim nw, int dir) {
268 if (nw < mw) w = mw; // clamp
269 else w = nw;
270 xw = x + w;
271 widget.setWidth (w - border.l - border.r, dir);
272 }
273 void setHeight (wdim nh, int dir) {
274 if (nh < mh) h = mh; // clamp
275 else h = nh;
276 yh = y + h;
277 widget.setHeight (h - border.t - border.b, dir);
278 }
279
280 void setPosition (wdim nx, wdim ny) {
281 x = nx;
282 y = ny;
283
284 xw = x+w;
285 yh = y+h;
286
287 widgetX = x + border.l;
288 widgetY = y + border.t;
289
290 widget.setPosition (widgetX, widgetY);
291
292 gui_.requestRedraw (); // necessary whenever the window is moved; setPosition is called after resizes and moves
293 }
294
295 IWidget getWidget (wdim cx, wdim cy) {
296 if (cx < x || cx >= xw || cy < y || cy >= yh) // not over window
297 return null;
298 if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b)
299 // over the widget
300 return widget.getWidget (cx, cy);
301 else // over the window border
302 return this;
303 }
304 void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
305 if (b == 1 && state == true) {
306 resizeType = rend.getResizeType (cx-x, cy-y, w,h);
307
308 if (resizeType != RESIZE_TYPE.NONE) { // Some type of resize
309 // Set x/yDrag (unfortunately these need to be different for each edge)
310 if (resizeType & RESIZE_TYPE.L)
311 xDrag = w + cx;
312 else if (resizeType & RESIZE_TYPE.R)
313 xDrag = w - cx;
314
315 if (resizeType & RESIZE_TYPE.T)
316 yDrag = h + cy;
317 else if (resizeType & RESIZE_TYPE.B)
318 yDrag = h - cy;
319
320 // Add the callbacks (they use resizeType which has already been set)
321 gui_.addClickCallback (&endCallback);
322 gui_.addMotionCallback (&resizeCallback);
323 } else { // window is being moved
324 xDrag = cx - x;
325 yDrag = cy - y;
326
327 gui_.addClickCallback (&endCallback);
328 gui_.addMotionCallback (&moveCallback);
329 }
330 }
331 }
332
333 void draw () {
334 // background
335 rend.drawWindow (x,y, w,h);
336
337 // Tell the widget to draw itself:
338 widget.draw();
339 }
340 //END IWidget methods
341
342 private:
343 alias IRenderer.BorderDimensions BorderDimensions;
344 alias IRenderer.RESIZE_TYPE RESIZE_TYPE;
345
346 //BEGIN Window moving and resizing
347 void moveCallback (wdabs cx, wdabs cy) {
348 setPosition (cx-xDrag, cy-yDrag);
349 }
350 void resizeCallback (wdabs cx, wdabs cy) {
351 debug scope(failure)
352 logger.trace ("resizeCallback: failure");
353
354 // This function is only called if some resize is going to happen.
355 // x,y are used as parameters to setPosition as well as being affected by it; somewhat
356 // pointless but fairly effective.
357
358 if (resizeType & RESIZE_TYPE.L) {
359 wdim xSize = xDrag - cx;
360 if (xSize < mw) xSize = mw; // clamp
361 x += w - xSize;
362 setWidth (xSize, 1);
363 }
364 else if (resizeType & RESIZE_TYPE.R) {
365 setWidth (xDrag + cx, -1);
366 }
367 if (resizeType & RESIZE_TYPE.T) {
368 wdim ySize = yDrag - cy;
369 if (ySize < mh) ySize = mh;
370 y += h - ySize;
371 setHeight (ySize, 1);
372 }
373 else if (resizeType & RESIZE_TYPE.B) {
374 setHeight (yDrag + cy, -1);
375 }
376
377 // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the
378 // resizing:
379 setPosition (x, y);
380 }
381 bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) {
382 if (b == 1 && state == false) {
383 // The mouse shouldn't have moved since the motion callback
384 // was last called, so there's nothing else to do now.
385 gui_.removeCallbacks (cast(void*) this);
386
387 return true; // we've handled the up-click
388 }
389 return false; // we haven't handled it
390 }
391 wdim xDrag, yDrag; // where a drag starts relative to x and y
392 IRenderer.RESIZE_TYPE resizeType; // Type of current resize
393 //END Window moving and resizing
394
395 // Load/save data:
396 public char[] name; // The window's name (id from config file)
397 //bool edited = false; // True if any widgets have been edited (excluding scaling)
398
399 IGui gui_; // The gui managing this window
400 IRenderer rend; // The window's renderer
401
402 IWidget widget; // The primary widget in this window.
403
404 wdim x = -1, y = -1; // Window position
405 wdsize w,h; // Window size (calculated from Widgets)
406 wdim xw, yh; // x+w, y+h (frequent use by clickEvent)
407 wdim widgetX, widgetY; // Widget position (= window position plus BORDER_WIDTH)
408 wdim mw = -1, mh = -1; // minimal size (negative means requires calculation)
409
410 BorderDimensions border; // Total border size (move plus resize)
411 +/
412 }