Mercurial > projects > mde
comparison mde/gui/widget/WidgetManager.d @ 175:1cbde9807293
Compile/link-time fixes for ldc & non-debug builds.
Moved WidgetManager to widget/
Reverted IChildWidget to an interface, not an abstract class.
Introduced a work-around for a compiler problem. May not cover all cases.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Fri, 11 Sep 2009 20:56:53 +0200 |
parents | mde/gui/WidgetManager.d@a1ba9157510e |
children | af40e9679436 |
comparison
equal
deleted
inserted
replaced
174:3d58adc17d20 | 175:1cbde9807293 |
---|---|
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 /****************************************************************************** | |
17 * The widget manager; root of the widget tree. | |
18 * | |
19 * Rendering is handled separately by an IRenderer. | |
20 *****************************************************************************/ | |
21 module mde.gui.widget.WidgetManager; | |
22 | |
23 import mde.gui.WidgetDataSet; | |
24 import mde.gui.widget.Ifaces; | |
25 import mde.gui.renderer.createRenderer; | |
26 | |
27 import imde = mde.imde; | |
28 import mde.content.Content; | |
29 import mde.content.ServiceContent; | |
30 debug import mde.content.miscContent; // Debug menu | |
31 debug import mde.content.Debug; | |
32 | |
33 // Widgets to create: | |
34 import mde.gui.widget.layout; | |
35 import mde.gui.widget.miscWidgets; | |
36 import mde.gui.widget.TextWidget; | |
37 import mde.gui.widget.contentFunctions; | |
38 import mde.gui.widget.miscContent; | |
39 import mde.gui.widget.Floating; | |
40 import mde.gui.widget.ParentContent; | |
41 import mde.gui.widget.AParentWidget; | |
42 | |
43 public import tango.core.sync.Mutex; | |
44 import tango.util.log.Log : Log, Logger; | |
45 import tango.io.Console; // to print exception stack-trace | |
46 import tango.util.container.SortedMap; | |
47 | |
48 private Logger logger; | |
49 static this () { | |
50 logger = Log.getLogger ("mde.gui.WidgetManager"); | |
51 } | |
52 | |
53 /****************************************************************************** | |
54 * Methods in this class are only intended for use within the gui package, | |
55 * either by widgets (the IXXXWidget methods implementing from an interface in | |
56 * widgets.Ifaces.d) or by a derived class (back-end methods doing widget | |
57 * work). None of these methods are intended to be thread-safe when called | |
58 * concurrently on the same WidgetManager instance, but they should be thread- | |
59 * safe for calling on separate instances. | |
60 *****************************************************************************/ | |
61 abstract class AWidgetManager : IWidgetManager | |
62 { | |
63 //BEGIN Public methods, for use outside the widget package | |
64 /** Construct a new widget manager. | |
65 * | |
66 * Params: | |
67 * name = The file name of the config for this GUI (to identify multiple GUIs). */ | |
68 this (char[] name) { | |
69 auto p = "MiscOptions.l10n" in Content.allContent; | |
70 assert (p, "MiscOptions.l10n not created!"); | |
71 p.addCallback (&reloadStrings); | |
72 | |
73 serviceContent = ServiceContentList.createItems (name); | |
74 assert (cast (IServiceContent) Content.get ("menus.services."~name)); | |
75 | |
76 debug { // add a debug-mode menu | |
77 auto lWS = new EventContent ("menus.debug."~name~".logWidgetSize"); | |
78 lWS.addCallback (&logWidgetSize); | |
79 } | |
80 } | |
81 | |
82 /** A change callback on MiscOptions.l10n content to update widgets. | |
83 * | |
84 * Relies on another callback reloading translations to content first! */ | |
85 final void reloadStrings (IContent) { | |
86 synchronized(mutex) { | |
87 if (childRoot is null) return; | |
88 childRoot.setup (++setupN, 2); | |
89 childRoot.setWidth (w, -1); | |
90 childRoot.setHeight (h, -1); | |
91 childRoot.setPosition (0,0); | |
92 childContext.setup (setupN, 2); | |
93 //TODO: possibly childDragged? | |
94 requestRedraw; | |
95 } | |
96 } | |
97 | |
98 debug public void logWidgetSize (IContent) { | |
99 logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh); | |
100 logger.trace ("childRoot:"); | |
101 childRoot.logWidgetSize; | |
102 logger.trace ("childContext:"); | |
103 childContext.logWidgetSize; | |
104 if (childDragged !is null) { | |
105 logger.trace ("childDragged:"); | |
106 childDragged.logWidgetSize; | |
107 } | |
108 } | |
109 | |
110 | |
111 //BEGIN Public IWidget methods | |
112 override bool saveChanges () { | |
113 bool ret = childRoot.saveChanges; | |
114 ret |= childContext.saveChanges; | |
115 if (childDragged !is null) | |
116 ret |= childDragged.saveChanges; | |
117 return ret; | |
118 } | |
119 | |
120 /** Draw all widgets */ | |
121 override void draw () { | |
122 if (childRoot) | |
123 childRoot.draw; | |
124 drawPopup; | |
125 } | |
126 //END Public IWidget methods | |
127 //END Public methods, for use outside the widget package | |
128 | |
129 //BEGIN IWidget methods for widgets | |
130 public override bool dropContent (IContent content) { | |
131 return false; | |
132 } | |
133 //END IWidget methods for widgets | |
134 | |
135 //BEGIN IParentWidget methods | |
136 // If call reaches the widget manager there isn't any recursion. | |
137 //NOTE: should be override | |
138 final void recursionCheck (widgetID, IContent) {} | |
139 | |
140 override void minWChange (IChildWidget widget, wdim nmw) { | |
141 if (widget !is childRoot) { // Probably because widget is a popup widget | |
142 // This may get called from a CTOR, hence we can't check widget is one of childContext, etc. | |
143 if (widget.width < nmw) | |
144 widget.setWidth (nmw, -1); | |
145 return; | |
146 } | |
147 mw = nmw; | |
148 if (w < nmw) { | |
149 childRoot.setWidth (nmw, -1); | |
150 w = nmw; | |
151 } | |
152 childRoot.setPosition (0,0); | |
153 requestRedraw; | |
154 } | |
155 override void minHChange (IChildWidget widget, wdim nmh) { | |
156 if (widget !is childRoot) { | |
157 if (widget.height < nmh) | |
158 widget.setHeight (nmh, -1); | |
159 return; | |
160 } | |
161 mh = nmh; | |
162 if (h < nmh) { | |
163 childRoot.setHeight (nmh, -1); | |
164 h = nmh; | |
165 } | |
166 childRoot.setPosition (0,0); | |
167 requestRedraw; | |
168 } | |
169 //END IParentWidget methods | |
170 | |
171 //BEGIN IPopupParentWidget methods | |
172 override IPopupParentWidget getParentIPPW () { | |
173 return this; | |
174 } | |
175 | |
176 override void addChildIPPW (IPopupParentWidget ippw) { | |
177 requestRedraw; | |
178 if (ippw is childContext) { // special handling - a separate IPPW | |
179 contextActive = true; | |
180 return; | |
181 } | |
182 if (childIPPW) | |
183 childIPPW.removedIPPW; | |
184 childIPPW = ippw; | |
185 } | |
186 override bool removeChildIPPW (IPopupParentWidget ippw) { | |
187 if (ippw is childContext && contextActive) { | |
188 childContext.removedIPPW; | |
189 contextActive = false; | |
190 return true; | |
191 } | |
192 if (childIPPW !is ippw) return false; | |
193 childIPPW.removedIPPW; | |
194 childIPPW = null; | |
195 mAIPPW = MenuPosition.INACTIVE; | |
196 requestRedraw; | |
197 return true; | |
198 } | |
199 | |
200 override void menuActive (MenuPosition mA) { | |
201 mAIPPW = mA; | |
202 if (childIPPW) | |
203 childIPPW.menuActive = mA; | |
204 if (contextActive) | |
205 childContext.menuActive = mA; | |
206 } | |
207 override MenuPosition menuActive () { | |
208 return mAIPPW; | |
209 } | |
210 override MenuPosition parentMenuActive () { | |
211 return MenuPosition.INACTIVE; | |
212 } | |
213 | |
214 // Note: also triggered by non-popup widgets | |
215 override void menuDone () {} | |
216 | |
217 override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) { | |
218 IChildWidget ret; | |
219 // Don't bother with childDragged; it has no interaction | |
220 if (contextActive) { | |
221 ret = childContext.getPopupWidget (cx, cy, closePopup); | |
222 if (ret) return ret; | |
223 if (closePopup) { | |
224 childContext.removedIPPW; | |
225 contextActive = false; | |
226 requestRedraw; | |
227 } | |
228 } | |
229 if (childIPPW) { | |
230 ret = childIPPW.getPopupWidget (cx, cy, closePopup); | |
231 if (ret) return ret; | |
232 if (closePopup) { | |
233 removeChildIPPW (childIPPW); | |
234 } | |
235 } | |
236 return null; | |
237 } | |
238 | |
239 override void drawPopup () { | |
240 if (childIPPW) | |
241 childIPPW.drawPopup; | |
242 if (contextActive) | |
243 childContext.drawPopup(); | |
244 if (childDragged) | |
245 childDragged.draw(); | |
246 } | |
247 | |
248 debug override bool isChild (IPopupParentWidget ippw) { | |
249 if (contextActive && ippw is childContext) | |
250 return true; | |
251 return ippw is childIPPW; | |
252 } | |
253 | |
254 override void removedIPPW () {} // irrelevant | |
255 //END IPopupParentWidget methods | |
256 | |
257 //BEGIN IWidgetManager methods | |
258 override IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null) | |
259 { | |
260 debug assert (parent, "makeWidget: parent is null (code error)"); | |
261 debug scope (failure) | |
262 logger.warn ("Creating widget \""~id~"\" failed."); | |
263 | |
264 WidgetData data = curData[id]; | |
265 if (data.ints.length < 1) { | |
266 logger.error ("No int data; creating a debug widget"); | |
267 data.ints = [WIDGET_TYPE.Debug]; | |
268 } | |
269 int type = data.ints[0]; // type is first element of data | |
270 | |
271 try { | |
272 // Statically programmed binary search on type, returning a new widget or calling a | |
273 // function: | |
274 //pragma (msg, binarySearch ("type", WIDGETS)); | |
275 mixin (binarySearch ("type", WIDGETS)); | |
276 // Not returned a new widget: | |
277 logger.error ("Bad widget type: {}; creating a debug widget instead",type); | |
278 } catch (Exception e) { | |
279 logger.error ("Error creating widget; creating a debug widget instead. Exception printed to stderr."); | |
280 //TODO: find a standard way to output exceptions, and implement everywhere: | |
281 e.writeOut(delegate void(char[]s){ Cerr(s); }); | |
282 } | |
283 | |
284 return new DebugWidget (this, parent, id, data, content); | |
285 } | |
286 | |
287 override WidgetData widgetData (widgetID id) { | |
288 return curData[id]; | |
289 } | |
290 override void widgetData (widgetID id, WidgetData d) { | |
291 changes[id] = d; // also updates WidgetDataSet in data. | |
292 } | |
293 | |
294 override wdims dimData (widgetID id) { | |
295 return curData.dims (id); | |
296 } | |
297 override void dimData (widgetID id, wdims d) { | |
298 changes.setDims(id, d); // also updates WidgetDataSet in data. | |
299 } | |
300 | |
301 IRenderer renderer () { | |
302 assert (rend !is null, "WidgetManager.renderer: rend is null"); | |
303 return rend; | |
304 } | |
305 | |
306 MenuPosition positionPopup (IChildWidget parent, IChildWidget popup, MenuPosition position = MenuPosition.INACTIVE) { | |
307 debug assert (parent && popup, "positionPopup: null widget"); | |
308 debug if (Debug.logPopupPositioning()) | |
309 logger.trace ("Placing popup {} in relation to parent {}; input position: {}", popup, parent, position); | |
310 wdim w = popup.width, | |
311 h = popup.height, | |
312 x, y; | |
313 if (position & MenuPosition.ACTIVE) { | |
314 y = parent.yPos; // height flush with top | |
315 if (y+h > this.h) y += parent.height - h; // or bottom | |
316 if (position & MenuPosition.LEFT) { // previously left | |
317 x = parent.xPos - w; // on left | |
318 if (x < 0) { | |
319 x = parent.xPos + parent.width; // on right | |
320 position = MenuPosition.RIGHT; | |
321 } | |
322 } else { // previously right or above/below | |
323 x = parent.xPos + parent.width; // on right | |
324 position = MenuPosition.RIGHT; | |
325 if (x+w > this.w) { | |
326 x = parent.xPos - w; // or left | |
327 position = MenuPosition.LEFT; | |
328 } | |
329 } | |
330 } else { | |
331 wdim pw = parent.width; | |
332 if (popup.minWidth <= pw) | |
333 popup.setWidth (pw, -1); // neatness | |
334 x = parent.xPos; // align on left edge | |
335 if (x+w > this.w) x += pw - w; // align on right edge | |
336 y = parent.yPos + parent.height; // place below | |
337 if (y+h > this.h) y = parent.yPos - h; // or above | |
338 position = MenuPosition.ACTIVE; | |
339 } | |
340 if (x < 0) x = 0; // may be placed partially off-screen | |
341 if (y < 0) y = 0; | |
342 popup.setPosition (x, y); | |
343 debug if (Debug.logPopupPositioning()) | |
344 logger.trace ("Placed popup {} of size ({},{}) at ({},{}); output position: {}", popup, w,h, x,y, position); | |
345 return position; | |
346 } | |
347 | |
348 void requestRedraw () { | |
349 imde.mainSchedule.request(imde.SCHEDULE.DRAW); | |
350 } | |
351 //END IWidgetManager methods | |
352 | |
353 protected: | |
354 // These methods are called by derived classes to do the widget-management work | |
355 //BEGIN WidgetManagement methods | |
356 /** Second stage of widget loading. | |
357 * | |
358 * Widget data should be loaded before this is called. */ | |
359 final void createWidgets () { | |
360 // The renderer needs to be created on the first load, but not after this. | |
361 if (rend is null) | |
362 rend = createRenderer (rendName); | |
363 | |
364 debug (mdeWidgets) logger.trace ("Creating root widget..."); | |
365 childRoot = makeWidget (this, "root"); | |
366 underMouse = childRoot; // don't leave null due to a check | |
367 debug (mdeWidgets) logger.trace ("Setting up root widget..."); | |
368 childRoot.setup (0, 3); | |
369 | |
370 mw = childRoot.minWidth; | |
371 mh = childRoot.minHeight; | |
372 matchMinimalSize (); | |
373 | |
374 debug (mdeWidgets) logger.trace ("Setting size and position of root widget..."); | |
375 childRoot.setWidth (w, -1); | |
376 childRoot.setHeight (h, -1); | |
377 childRoot.setPosition (0,0); | |
378 debug (mdeWidgets) logger.trace ("Done creating root widget."); | |
379 | |
380 childContext = new PopupHandlerWidget (this, this, "contextHandler", "context", serviceContent); | |
381 childContext.setup (0,3); | |
382 debug (mdeWidgets) logger.trace ("Created context handler widget."); | |
383 | |
384 underMouse = childRoot; // must be something | |
385 } | |
386 | |
387 final void wmSizeEvent (int nw, int nh) { | |
388 w = cast(wdim) nw; | |
389 h = cast(wdim) nh; | |
390 matchMinimalSize; | |
391 | |
392 if (!childRoot) return; // if not created yet. | |
393 childRoot.setWidth (w, -1); | |
394 childRoot.setHeight (h, -1); | |
395 childRoot.setPosition (0,0); | |
396 debug logWidgetSize (null); | |
397 } | |
398 | |
399 /** For mouse click events. | |
400 * | |
401 * Sends the event on to the relevant windows and all click callbacks. */ | |
402 final void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) { | |
403 if (childRoot is null) return; | |
404 | |
405 // Update underMouse to get the widget clicked on | |
406 updateUnderMouse (cx, cy, state); | |
407 | |
408 // end of a drag? | |
409 if (dragStart !is null && b == dragButton && state == false) { | |
410 IChildWidget dS = dragStart; | |
411 dragStart = null; | |
412 childDragged = null; | |
413 requestRedraw; | |
414 if (dS.dragRelease (cx, cy, underMouse)) | |
415 return; | |
416 } | |
417 | |
418 // Disable keyboard input if on another widget: | |
419 if (keyFocus && keyFocus !is underMouse) { | |
420 keyFocus.keyFocusLost; | |
421 keyFocus = null; | |
422 setLetterCallback (null); | |
423 } | |
424 | |
425 // Finally, post the actual event: | |
426 if (b == 3 && state) { // right click - open context menu | |
427 Content contextContent = cast(Content) underMouse.content; | |
428 if (contextContent !is null) { | |
429 serviceContent.setContent (contextContent); | |
430 childContext.openMenu (underMouse, contextContent); | |
431 } | |
432 } else { // post other button presses to clickEvent | |
433 int ret = underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state); | |
434 if (ret & 1) { // keyboard input requested | |
435 keyFocus = underMouse; | |
436 setLetterCallback (&underMouse.keyEvent); | |
437 } | |
438 if (ret & 2 && dragStart is null) { // drag events requested | |
439 dragStart = underMouse; | |
440 dragButton = b; // currently we allow any button to be used for a drag, but.. ? | |
441 if (ret & 4) { | |
442 IContent c = underMouse.content(); | |
443 if (c) { // NOTE: creates a new widget, not optimal | |
444 childDragged = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c); | |
445 childDragged.setup (0, 3); | |
446 dragX = underMouse.xPos - cx; | |
447 dragY = underMouse.yPos - cy; | |
448 childDragged.setPosition (cx + dragX, cy + dragY); | |
449 } | |
450 } | |
451 } | |
452 } | |
453 } | |
454 | |
455 /** For mouse motion events. | |
456 * | |
457 * Lock on mutex before calling. Pass new mouse coordinates. */ | |
458 final void wmMouseMotion (wdabs cx, wdabs cy) { | |
459 updateUnderMouse (cx, cy, false); | |
460 | |
461 if (dragStart !is null) { | |
462 dragStart.dragMotion (cx, cy, underMouse); | |
463 if (childDragged !is null) { | |
464 childDragged.setPosition (cx + dragX, cy + dragY); | |
465 requestRedraw; | |
466 } | |
467 } | |
468 } | |
469 | |
470 // for internal use | |
471 private final void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) { | |
472 auto oUM = underMouse; | |
473 underMouse = getPopupWidget (cx, cy, closePopup); | |
474 if (underMouse is null) { | |
475 debug assert (childRoot.onSelf (cx, cy), "WidgetManager: childRoot doesn't cover whole area"); | |
476 underMouse = childRoot.getWidget (cx, cy); | |
477 } | |
478 debug assert (oUM && underMouse, "no widget under mouse: error"); | |
479 if (underMouse !is oUM) { | |
480 oUM.underMouse (false); | |
481 underMouse.underMouse (true); | |
482 debug if (Debug.logUnderMouse()) | |
483 logger.trace ("Widget under mouse: {}", underMouse); | |
484 } | |
485 } | |
486 | |
487 /** If possible, the screen-interaction derived class should override to | |
488 * make sure the window is at least (mw,mh) in size. In any case, this | |
489 * method MUST make sure w >= mw and h >= mh even if the window isn't this | |
490 * big. | |
491 * | |
492 * A resize may not be required when this is called, however. */ | |
493 void matchMinimalSize () { | |
494 if (w < mw) { | |
495 logger.warn ("Min width for gui, {}, not met: {}", mw, w); | |
496 w = mw; | |
497 } | |
498 if (h < mh) { | |
499 logger.warn ("Min height for gui, {}, not met: {}", mh, h); | |
500 h = mh; | |
501 } | |
502 } | |
503 | |
504 /// This should be overloaded to set a callback receiving keyboard input. | |
505 abstract void setLetterCallback(void delegate(ushort, char[])); | |
506 //END WidgetManagement methods | |
507 | |
508 //BEGIN makeWidget metacode | |
509 private static { | |
510 /// Widget types. Items match widget names without the "Widget" suffix. | |
511 enum WIDGET_TYPE : int { | |
512 FUNCTION = 0x2000, // Function called instead of widget created (no "Widget" appended to fct name) | |
513 TAKES_CONTENT = 0x4000, // Flag indicates widget's this should be passed an IContent reference. | |
514 | |
515 // Use widget names rather than usual capitals convention | |
516 Unnamed = 0x0, // Only for use by widgets not created with createWidget | |
517 | |
518 // blank: 0x1 | |
519 FixedBlank = 0x1, | |
520 SizableBlank = 0x2, | |
521 Debug = TAKES_CONTENT | 0xF, | |
522 | |
523 // popup widgets: 0x10 | |
524 PopupMenu = TAKES_CONTENT | 0x11, | |
525 | |
526 // labels: 0x20 | |
527 TextLabel = 0x21, | |
528 | |
529 // content functions: 0x30 | |
530 editContent = FUNCTION | TAKES_CONTENT | 0x30, | |
531 addContent = FUNCTION | 0x31, | |
532 popupListContent = FUNCTION | TAKES_CONTENT | 0x33, | |
533 | |
534 // content widgets: 0x40 | |
535 DisplayContent = TAKES_CONTENT | 0x40, | |
536 BoolContent = TAKES_CONTENT | 0x41, | |
537 AStringContent = TAKES_CONTENT | 0x42, | |
538 ButtonContent = TAKES_CONTENT | 0x43, | |
539 SliderContent = TAKES_CONTENT | 0x44, | |
540 | |
541 GridLayout = TAKES_CONTENT | 0x100, | |
542 ContentList = TAKES_CONTENT | 0x110, | |
543 | |
544 FloatingArea = TAKES_CONTENT | 0x200, | |
545 Border = TAKES_CONTENT | 0x204, | |
546 Switch = TAKES_CONTENT | 0x210, | |
547 Collapsible = TAKES_CONTENT | 0x214, | |
548 } | |
549 | |
550 // Only used for binarySearch algorithm generation; must be ordered by numerical values. | |
551 const char[][] WIDGETS = [ | |
552 "FixedBlank", | |
553 "SizableBlank", | |
554 "TextLabel", | |
555 "addContent", | |
556 "Debug", | |
557 "PopupMenu", | |
558 "DisplayContent", | |
559 "BoolContent", | |
560 "AStringContent", | |
561 "ButtonContent", | |
562 "SliderContent", | |
563 "GridLayout", | |
564 "ContentList", | |
565 "FloatingArea", | |
566 "Border", | |
567 "Switch", | |
568 "Collapsible", | |
569 "editContent", | |
570 "popupListContent"]; | |
571 | |
572 /* Generates a binary search algorithm for makeWidget. */ | |
573 char[] binarySearch (char[] var, char[][] consts) { | |
574 if (consts.length > 3) { | |
575 return `if (`~var~` <= WIDGET_TYPE.`~consts[$/2 - 1]~`) {` ~ | |
576 binarySearch (var, consts[0 .. $/2]) ~ | |
577 `} else {` ~ | |
578 binarySearch (var, consts[$/2 .. $]) ~ | |
579 `}`; | |
580 } else { | |
581 char[] ret; | |
582 foreach (c; consts) { | |
583 ret ~= `if (` ~ var ~ ` == WIDGET_TYPE.` ~ c ~ `) { | |
584 debug (mdeWidgets) logger.trace ("Creating new `~c~`."); | |
585 parent.recursionCheck (id, content); | |
586 static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.FUNCTION) | |
587 return `~c~` (this, parent, id, data, content); | |
588 else static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.TAKES_CONTENT) | |
589 return new `~c~`Widget (this, parent, id, data, content); | |
590 else | |
591 return new `~c~`Widget (this, parent, id, data); | |
592 } else `; | |
593 } | |
594 ret = ret[0..$-6]; // remove last else | |
595 return ret; | |
596 } | |
597 } | |
598 | |
599 debug { // check items in WIDGETS are listed in order | |
600 char[] WIDGETS_check () { | |
601 char[] ret; | |
602 for (int i = WIDGETS.length-2; i > 0; --i) { | |
603 ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1]; | |
604 if (i>1) ret ~= " || "; | |
605 } | |
606 return ret; | |
607 } | |
608 mixin ("static if ("~WIDGETS_check~") | |
609 static assert (false, \"WIDGETS is not in order!\");"); | |
610 } | |
611 } | |
612 //END makeWidget metacode | |
613 | |
614 protected: | |
615 // Main child widget: | |
616 IChildWidget childRoot; // Root of the main GUI widget tree | |
617 | |
618 // Dimensions and child set-up data (fit to childRoot): | |
619 wdim w,h; // current widget size; should be at least (mw,mh) even if not displayable | |
620 wdim mw,mh; // minimal area required by widgets | |
621 uint setupN; // n to pass to IChildWidget.setup | |
622 | |
623 // IPopupParentWidget stuff for childRoot: | |
624 MenuPosition mAIPPW; // IPPW variable | |
625 IPopupParentWidget childIPPW; // child IPPW, if any active | |
626 | |
627 IChildWidget keyFocus; // widget receiving keyboard input | |
628 IChildWidget underMouse; // widget under the mouse pointer; should never be null when childRoot is non-null | |
629 | |
630 | |
631 // Context menu: | |
632 // Essentially, we consider childContext a full child IPPW, but handle it separately from | |
633 // childIPPW. Instead of providing another ref. for this IPPW, shortcut by using this reference | |
634 // and the boolean contextActive: | |
635 scope PopupHandlerWidget childContext; // context menu popup (handler) | |
636 bool contextActive = false; // If true, consider childContext a child IPPW | |
637 scope IServiceContent serviceContent; // context menu content tree | |
638 | |
639 | |
640 // Drag-and-drop data: | |
641 //NOTE: could be wrapped with a PopupHandlerWidget, but can't set position then? | |
642 scope IChildWidget childDragged; // displays dragged content; no interaction | |
643 IChildWidget dragStart; // if non-null, this widget should receive motion and click-release events | |
644 int dragButton; // index of button in use for drag | |
645 wdrel dragX, dragY; // coordinates of dragged content relative to mouse | |
646 | |
647 | |
648 // Renderer: | |
649 char[] rendName; // Name of renderer; for saving and creating renderers | |
650 scope IRenderer rend; | |
651 | |
652 | |
653 // Data loaded/to save: | |
654 WidgetDataSet curData; // Current data | |
655 WidgetDataChanges changes; // Changes for the current design. | |
656 | |
657 Mutex mutex; // lock on methods for use outside the package. | |
658 } |