Mercurial > projects > mde
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 } |