egra: less wordy widget hierarchy creation
[iv.d.git] / egra / gui / subwindows.d
blob3b46a488897a41c732a869be95f8a53a01fddced
1 /*
2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.egra.gui.subwindows /*is aliced*/;
20 private:
22 import core.time;
24 import arsd.simpledisplay;
26 import iv.alice;
27 import iv.cmdcon;
28 import iv.flexlay2;
29 import iv.strex;
30 import iv.utfutil;
32 import iv.egra.gfx;
33 public import iv.egra.gui.style;
34 import iv.egra.gui.widgets : Widget, RootWidget;
37 // ////////////////////////////////////////////////////////////////////////// //
38 public __gshared SimpleWindow vbwin; // main window; MUST be set!
39 public __gshared bool vbfocused = false;
42 // ////////////////////////////////////////////////////////////////////////// //
43 __gshared public int lastMouseXUnscaled = 10000, lastMouseYUnscaled = 10000;
44 __gshared /*MouseButton*/public int lastMouseButton;
46 public int lastMouseX () nothrow @trusted @nogc { pragma(inline, true); return lastMouseXUnscaled/screenEffScale; }
47 public int lastMouseY () nothrow @trusted @nogc { pragma(inline, true); return lastMouseYUnscaled/screenEffScale; }
49 public bool lastMouseLeft () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.left) != 0); }
50 public bool lastMouseRight () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.right) != 0); }
51 public bool lastMouseMiddle () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.middle) != 0); }
54 // ////////////////////////////////////////////////////////////////////////// //
55 public class ScreenRebuildEvent {}
56 public class ScreenRepaintEvent {}
57 public class QuitEvent {}
58 public class CursorBlinkEvent {}
59 public class HideMouseEvent {}
61 __gshared ScreenRebuildEvent evScrRebuild;
62 __gshared ScreenRepaintEvent evScreenRepaint;
63 __gshared CursorBlinkEvent evCurBlink;
64 __gshared HideMouseEvent evHideMouse;
66 shared static this () {
67 evScrRebuild = new ScreenRebuildEvent();
68 evScreenRepaint = new ScreenRepaintEvent();
69 evCurBlink = new CursorBlinkEvent();
70 evHideMouse = new HideMouseEvent();
74 // ////////////////////////////////////////////////////////////////////////// //
75 public void postScreenRebuild () { if (vbwin !is null && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScrRebuild); }
76 public void postScreenRepaint () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScreenRepaint); }
77 public void postScreenRepaintDelayed () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postTimeout(evScreenRepaint, 35); }
79 public void postCurBlink () {
80 if (vbwin !is null && !vbwin.eventQueued!CursorBlinkEvent) {
81 //conwriteln("curblink posted!");
82 vbwin.postTimeout(evCurBlink, 100);
83 //vbwin.postTimeout(evCurBlink, 500);
88 // ////////////////////////////////////////////////////////////////////////// //
89 __gshared MonoTime lastMouseMove;
90 __gshared uint MouseHideTime = 3000;
93 shared static this () {
94 conRegVar("mouse_hide_time", "mouse cursor hiding time (in milliseconds); 0 to not hide it",
95 delegate (self) {
96 return MouseHideTime;
98 delegate (self, uint nv) {
99 if (MouseHideTime != nv) {
100 if (MouseHideTime == 0) mouseMoved();
101 MouseHideTime = nv;
102 conwriteln("mouse hiding time: ", nv);
109 //==========================================================================
111 // mshtime_dbg
113 //==========================================================================
114 public int mshtime_dbg () {
115 if (MouseHideTime > 0) {
116 auto ctt = MonoTime.currTime;
117 auto mt = (ctt-lastMouseMove).total!"msecs";
118 return cast(int)mt;
119 } else {
120 return 0;
125 //==========================================================================
127 // isMouseVisible
129 //==========================================================================
130 public bool isMouseVisible () {
131 if (MouseHideTime > 0) {
132 auto ctt = MonoTime.currTime;
133 return ((ctt-lastMouseMove).total!"msecs" < MouseHideTime+500);
134 } else {
135 return true;
140 //==========================================================================
142 // mouseAlpha
144 //==========================================================================
145 public float mouseAlpha () {
146 if (MouseHideTime > 0) {
147 auto ctt = MonoTime.currTime;
148 auto msc = (ctt-lastMouseMove).total!"msecs";
149 if (msc >= MouseHideTime+500) return 0.0f;
150 if (msc < MouseHideTime) return 1.0f;
151 msc -= MouseHideTime;
152 return 1.0f-msc/500.0f;
153 } else {
154 return 1.0f;
159 //==========================================================================
161 // repostHideMouse
163 // returns `true` if mouse should be redrawn
165 //==========================================================================
166 public bool repostHideMouse () {
167 if (vbwin is null || vbwin.eventQueued!HideMouseEvent) return false;
168 if (MouseHideTime > 0) {
169 auto ctt = MonoTime.currTime;
170 auto tms = (ctt-lastMouseMove).total!"msecs";
171 if (tms >= MouseHideTime) {
172 if (tms >= MouseHideTime+500) return true; // hide it
173 vbwin.postTimeout(evHideMouse, 50);
174 return true; // fade it
176 vbwin.postTimeout(evHideMouse, cast(int)(MouseHideTime-tms));
178 return false;
182 //==========================================================================
184 // mouseMoved
186 //==========================================================================
187 public void mouseMoved () {
188 if (MouseHideTime > 0) {
189 lastMouseMove = MonoTime.currTime;
190 if (vbwin !is null && !vbwin.eventQueued!HideMouseEvent) vbwin.postTimeout(evHideMouse, MouseHideTime);
195 //==========================================================================
197 // drawTextCursor
199 //==========================================================================
200 public void drawTextCursor (bool active, int x, int y, int hgt=-666) {
201 if (hgt == -666) hgt = gxTextHeightUtf;
202 if (hgt < 1) return;
203 if (active) {
204 auto ctt = (MonoTime.currTime.ticks*1000/MonoTime.ticksPerSecond)/100;
205 int doty = ctt%(hgt*2-1);
206 if (doty >= hgt) doty = hgt*2-doty-1;
207 gxVLine(x, y, hgt, (ctt%10 < 5 ? gxRGB!(255, 255, 255) : gxRGB!(200, 200, 200)));
208 gxPutPixel(x, y+doty, gxRGB!(0, 255, 255));
209 postCurBlink();
210 } else {
211 gxVLine(x, y, hgt, gxRGB!(170, 170, 170));
216 // ////////////////////////////////////////////////////////////////////////// //
217 private __gshared SubWindow subwinLast;
218 private __gshared bool ignoreSubWinChar = false;
219 // make a package and move that to package
220 private __gshared SubWindow subwinDrag = null;
221 private __gshared int subwinDragXSpot, subwinDragYSpot;
224 public @property bool isSubWinDragging () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null); }
225 public @property bool isSubWinDraggingKeyboard () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null && subwinDragXSpot == int.min && subwinDragYSpot == int.min); }
228 //==========================================================================
230 // eguiLostGlobalFocus
232 //==========================================================================
233 public void eguiLostGlobalFocus () {
234 ignoreSubWinChar = false;
235 subwinDrag = null;
236 SubWindow aw = getActiveSubWindow();
237 if (aw !is null) aw.releaseWidgetGrab();
241 private bool insertSubWindow (SubWindow nw) {
242 if (nw is null || nw.mClosed) return false;
243 assert(nw.mPrev is null);
244 assert(nw.mNext is null);
245 assert(!nw.mInWinList);
246 nw.releaseWidgetGrab();
247 SubWindow law = getActiveSubWindow();
248 if (nw.mType == SubWindow.Type.OnBottom) {
249 SubWindow w = subwinLast;
250 if (w !is null) {
251 while (w.mPrev !is null) w = w.mPrev;
252 nw.mNext = w;
253 if (w !is null) w.mPrev = nw; else subwinLast = nw;
254 } else {
255 subwinLast = nw;
257 nw.mInWinList = true;
258 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
259 return true;
261 SubWindow aw = getActiveSubWindow();
262 assert(aw !is nw);
263 if (nw.mType == SubWindow.Type.OnTop || aw is null) {
264 nw.mPrev = subwinLast;
265 if (subwinLast !is null) subwinLast.mNext = nw;
266 subwinLast = nw;
267 nw.mInWinList = true;
268 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
269 return true;
271 if (aw.mModal && !nw.mModal) return false; // can't insert normal windows while modal window is active
272 // insert after aw
273 nw.mPrev = aw;
274 nw.mNext = aw.mNext;
275 aw.mNext = nw;
276 if (nw.mNext !is null) nw.mNext.mPrev = nw;
277 if (aw is subwinLast) subwinLast = nw;
278 nw.mInWinList = true;
279 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
280 return true;
284 private bool removeSubWindow (SubWindow nw) {
285 if (nw is null || !nw.mInWinList) return false;
286 nw.releaseWidgetGrab();
287 SubWindow law = getActiveSubWindow();
288 if (nw.mPrev !is null) nw.mPrev.mNext = nw.mNext;
289 if (nw.mNext !is null) nw.mNext.mPrev = nw.mPrev;
290 if (nw is subwinLast) subwinLast = nw.mPrev;
291 nw.mPrev = null;
292 nw.mNext = null;
293 nw.mInWinList = false;
294 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
295 return true;
299 //==========================================================================
301 // mouse2xy
303 //==========================================================================
304 public void mouse2xy (MouseEvent event, out int mx, out int my) nothrow @trusted @nogc {
305 mx = event.x/screenEffScale;
306 my = event.y/screenEffScale;
310 //==========================================================================
312 // subWindowAt
314 //==========================================================================
315 public SubWindow subWindowAt (in GxPoint p) nothrow @trusted @nogc {
316 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
317 if (!w.closed) {
318 if (w.mMinimized) {
319 if (p.x >= w.winminx && p.y >= w.winminy && p.x < w.winminx+w.MinSizeX && p.y < w.winminy+w.MinSizeY) return w;
320 } else {
321 if (p.inside(w.winrect)) return w;
325 return null;
329 public SubWindow subWindowAt (int mx, int my) nothrow @trusted @nogc { return subWindowAt(GxPoint(mx, my)); }
332 //==========================================================================
334 // subWindowAt
336 //==========================================================================
337 public SubWindow subWindowAt (MouseEvent event) nothrow @trusted @nogc {
338 pragma(inline, true);
339 return subWindowAt(event.x/screenEffScale, event.y/screenEffScale);
343 //==========================================================================
345 // getActiveSubWindow
347 //==========================================================================
348 public SubWindow getActiveSubWindow () nothrow @trusted @nogc {
349 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
350 if (!w.mClosed && w.type != SubWindow.Type.OnTop && !w.mMinimized) return w;
352 return null;
356 //==========================================================================
358 // dispatchEvent
360 //==========================================================================
361 public bool dispatchEvent (KeyEvent event) {
362 bool res = false;
363 if (isSubWinDragging) {
364 if (isSubWinDraggingKeyboard) {
365 if (!event.pressed) return true;
366 if (event == "Left") subwinDrag.x0 = subwinDrag.x0-1;
367 else if (event == "Right") subwinDrag.x0 = subwinDrag.x0+1;
368 else if (event == "Up") subwinDrag.y0 = subwinDrag.y0-1;
369 else if (event == "Down") subwinDrag.y0 = subwinDrag.y0+1;
370 else if (event == "C-Left") subwinDrag.x0 = subwinDrag.x0-8;
371 else if (event == "C-Right") subwinDrag.x0 = subwinDrag.x0+8;
372 else if (event == "C-Up") subwinDrag.y0 = subwinDrag.y0-8;
373 else if (event == "C-Down") subwinDrag.y0 = subwinDrag.y0+8;
374 else if (event == "Home") subwinDrag.x0 = 0;
375 else if (event == "End") subwinDrag.x0 = screenWidth-subwinDrag.width;
376 else if (event == "PageUp") subwinDrag.y0 = 0;
377 else if (event == "PageDown") subwinDrag.y0 = screenHeight-subwinDrag.height;
378 else if (event == "Escape" || event == "Enter") subwinDrag = null;
379 postScreenRebuild();
380 return true;
382 } else if (auto aw = getActiveSubWindow()) {
383 res = aw.onKeyEvent(event);
384 if (res) postScreenRebuild();
386 return res;
390 //==========================================================================
392 // dispatchEvent
394 //==========================================================================
395 public bool dispatchEvent (MouseEvent event) {
396 __gshared SubWindow lastHover = null;
398 if (subwinLast is null) { postScreenRepaint(); return false; }
400 int mx = lastMouseX;
401 int my = lastMouseY;
402 auto aw = getActiveSubWindow();
403 auto msw = subWindowAt(event);
404 scope(exit) {
405 if (msw !is lastHover) {
406 lastHover = msw;
407 postScreenRebuild();
410 bool curIsModal = (aw !is null && aw.mModal);
412 // switch window by button press
413 if (event.type == MouseEventType.buttonReleased && msw !is aw && !curIsModal) {
414 if (msw !is null && msw.mType == SubWindow.Type.Normal) {
415 if (aw !is null) aw.releaseWidgetGrab();
416 msw.releaseWidgetGrab();
417 msw.bringToFront();
418 postScreenRepaint();
419 return true;
423 // drag
424 if (isSubWinDragging) {
425 subwinDrag.releaseWidgetGrab(); // just in case
426 if (!isSubWinDraggingKeyboard) {
427 subwinDrag.x0 = mx+subwinDragXSpot;
428 subwinDrag.y0 = my+subwinDragYSpot;
429 // stop drag?
430 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) subwinDrag = null;
431 postScreenRebuild();
432 } else {
433 postScreenRepaint();
435 return true;
438 if (msw is null || msw !is aw || msw.mMinimized) {
439 if (msw is null || !msw.onTop) {
440 postScreenRepaint();
441 return false;
444 assert(msw !is null);
446 if (msw.onMouseEvent(event)) {
447 postScreenRebuild();
448 return true;
451 postScreenRepaint();
452 return false;
456 //==========================================================================
458 // dispatchEvent
460 //==========================================================================
461 public bool dispatchEvent (dchar ch) {
462 if (ignoreSubWinChar) { ignoreSubWinChar = false; return (subwinLast !is null); }
463 bool res = false;
464 if (!isSubWinDragging) {
465 if (auto aw = getActiveSubWindow()) {
466 res = aw.onCharEvent(ch);
467 if (res) postScreenRebuild();
470 return res;
474 //==========================================================================
476 // paintSubWindows
478 //==========================================================================
479 public void paintSubWindows () {
480 // get first window
481 SubWindow firstWin = subwinLast;
482 if (firstWin is null) return;
483 while (firstWin.mPrev !is null) firstWin = firstWin.mPrev;
485 SubWindow firstMin, firstNormal, firstTop;
486 SubWindow lastMin, lastNormal, lastTop;
488 //gxClipReset();
490 void doDraw (SubWindow w) {
491 if (w !is null) {
492 gxClipReset();
493 w.onPaint();
494 if (w is subwinDrag) { gxClipReset(); gxFillRect(w.x0, w.y0, w.width, w.height, gxRGBA!(255, 127, 0, 176)); }
498 // paint background windows
499 for (SubWindow w = firstWin; w !is null; w = w.mNext) {
500 if (w.mClosed) continue;
501 if (w.mMinimized) {
502 if (firstMin is null) firstMin = w;
503 lastMin = w;
504 } else if (w.mType == SubWindow.Type.Normal) {
505 if (firstNormal is null) firstNormal = w;
506 lastNormal = w;
507 } else if (w.mType == SubWindow.Type.OnTop) {
508 if (firstTop is null) firstTop = w;
509 lastTop = w;
510 } else if (w.mType == SubWindow.Type.OnBottom) {
511 doDraw(w);
515 // paint minimized windows
516 for (SubWindow w = firstMin; w !is null; w = w.mNext) {
517 if (!w.mClosed && w.mMinimized) doDraw(w);
518 if (w is lastMin) break;
521 // paint normal windows
522 for (SubWindow w = firstNormal; w !is null; w = w.mNext) {
523 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.Normal) doDraw(w);
524 if (w is lastNormal) break;
527 // paint ontop windows
528 for (SubWindow w = firstTop; w !is null; w = w.mNext) {
529 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.OnTop) doDraw(w);
530 if (w is lastTop) break;
533 // paint hint for minimized window
534 if (auto msw = subWindowAt(lastMouseX, lastMouseY)) {
535 if (!msw.mClosed && msw.mMinimized && msw.title.length) {
536 auto wdt = gxTextWidthUtf(msw.title)+2;
537 auto hgt = gxTextHeightUtf+2;
538 int y = msw.winminy-hgt;
539 int x;
540 if (wdt >= screenWidth) {
541 x = (screenWidth-wdt)/2;
542 } else {
543 x = (msw.winminx+msw.MinSizeX)/2-wdt/2;
544 if (x < 0) x = 0;
546 gxClipReset();
547 gxFillRect(x, y, wdt, hgt, gxRGB!(255, 255, 255));
548 gxDrawTextUtf(x+1, y+1, msw.title, gxRGB!(0, 0, 0));
554 // ////////////////////////////////////////////////////////////////////////// //
555 public class SubWindow {
556 protected:
557 enum Type {
558 Normal,
559 OnTop,
560 OnBottom,
563 protected:
564 SubWindow mPrev, mNext;
565 Type mType = Type.Normal;
566 bool mMinimized;
567 bool mInWinList;
568 bool mModal;
569 bool mClosed;
570 int awidx; // active widget index
571 RootWidget mRoot;
573 ColorStyle colorStyle;
575 public:
576 int winminx, winminy;
577 GxRect winrect;
578 string title;
580 GxSize minWinSize;
581 GxSize maxWinSize;
583 public:
584 // color getters
585 final uint getStyleColor (in Object obj, const(char)[] type, const(char)[] mod=null) {
586 pragma(inline, true);
587 ColorStyle st = colorStyle;
588 if (st is null) st = defaultColorStyle;
589 return st.findColor(this, obj, type, mod);
592 final uint getColor (const(char)[] type, const(char)[] mod) {
593 return getStyleColor(this, type, mod);
596 final uint getColor (const(char)[] type) {
597 return getStyleColor(this, type, (active ? null : "inactive"));
600 protected:
601 void createRoot () {
602 if (mRoot is null) setRoot(new RootWidget(this)); else fixRoot();
605 void fixRoot () {
606 if (mRoot) {
607 mRoot.rect.pos = GxPoint(0, 0);
608 mRoot.rect.size = GxSize(clientWidth, clientHeight);
612 void setStyle (ColorStyle stl) {
613 colorStyle = stl;
616 void finishConstruction () {
617 winrect.size.sanitize();
618 createRoot();
619 createStyle();
620 mRoot.enter(&createWidgets);
621 finishCreating();
624 public:
625 this () {
626 winrect.pos = GxPoint(0, 0);
627 winrect.size = GxSize(0, 0);
628 finishConstruction();
631 this (string atitle) {
632 title = atitle;
633 winrect.pos = GxPoint(0, 0);
634 winrect.size = GxSize(0, 0);
635 finishConstruction();
638 this (string atitle, in GxPoint apos, in GxSize asize) {
639 title = atitle;
640 winrect.pos = apos;
641 winrect.size = asize;
642 finishConstruction();
645 this (string atitle, in GxSize asize) {
646 title = atitle;
647 winrect.size = asize;
648 winrect.size.sanitize();
649 winrect.pos.x = (screenWidth-winrect.width)/2;
650 winrect.pos.y = (screenHeight-winrect.height)/2;
651 finishConstruction();
654 // this doesn't perform relayouting
655 void setRoot (RootWidget w) {
656 mRoot = w;
657 fixRoot();
660 // this is called from constructor
661 void createStyle () {
664 // this is called from constructor
665 void createWidgets () {
668 // this is called from constructor
669 // you can call `addModal()` here, for example
670 void finishCreating () {
671 add();
674 void relayout (bool resizeWindow) {
675 if (mRoot is null) return;
677 FuiFlexLayouter!Widget lay;
679 lay.isValidBoxId = delegate bool (Widget id) { return (id !is null); };
681 lay.firstChild = delegate Widget (Widget id) { return id.firstChild; };
682 lay.nextSibling = delegate Widget (Widget id) { return id.nextSibling; };
684 lay.getMinSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.minSize.w : id.minSize.h); };
685 lay.getMaxSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.maxSize.w : id.maxSize.h); };
686 lay.getPrefSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.prefSize.w : id.prefSize.h); };
688 lay.isHorizBox = delegate bool (Widget id) { return (id.childDir == GxDir.Horiz); };
690 lay.getFlex = delegate int (Widget id) { return id.flex; };
692 lay.getSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.boxsize.w : id.boxsize.h); };
693 lay.setSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.boxsize.w = val; else id.boxsize.h = val; };
695 lay.getFinalSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.finalSize.w : id.finalSize.h); };
696 lay.setFinalSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalSize.w = val; else id.finalSize.h = val; };
698 lay.setFinalPos = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalPos.x = val; else id.finalPos.y = val; };
700 if (winrect.size.empty) {
701 if (maxWinSize.empty) maxWinSize = GxSize(screenWidth-decorationSizeX, screenHeight-decorationSizeY);
702 resizeWindow = true;
705 if (winrect.size.w > screenWidth) winrect.size.w = screenWidth;
706 if (winrect.size.h > screenHeight) winrect.size.h = screenHeight;
708 if (resizeWindow) {
709 mRoot.maxSize = maxWinSize;
710 } else {
711 mRoot.maxSize = winrect.size-GxSize(decorationSizeX, decorationSizeY);
712 if (mRoot.maxSize.w <= 0) mRoot.maxSize.w = maxWinSize.w;
713 if (mRoot.maxSize.h <= 0) mRoot.maxSize.h = maxWinSize.h;
716 mRoot.minSize = minWinSize;
717 if (mRoot.minSize.w > screenWidth-decorationSizeX) mRoot.minSize.w = screenWidth-decorationSizeX;
718 if (mRoot.minSize.h > screenHeight-decorationSizeY) mRoot.minSize.h = screenHeight-decorationSizeY;
720 if (title.length) {
721 if (mRoot.boxsize.w <= 0) mRoot.boxsize.w = gxTextWidthUtf(title)+2;
723 mRoot.prefSize = mRoot.boxsize;
725 mRoot.forEachDepth((Widget w) { w.preLayout(); });
727 lay.layout(mRoot);
729 winrect.size = mRoot.boxsize+GxSize(decorationSizeX, decorationSizeY);
730 if (winrect.x1 >= screenWidth) winrect.pos.x -= winrect.x1-screenWidth+1;
731 if (winrect.y1 >= screenHeight) winrect.pos.y -= winrect.y1-screenHeight+1;
732 if (winrect.pos.x < 0) winrect.pos.x = 0;
733 if (winrect.pos.y < 0) winrect.pos.y = 0;
735 mRoot.forEachDepth((Widget w) { w.postLayout(); });
738 void relayoutResize () { relayout(true); }
740 // this clones the style
741 void appendStyle (const(char)[] str) {
742 if (colorStyle is null) {
743 colorStyle = new ColorStyle;
744 colorStyle.cloneFrom(defaultColorStyle);
746 colorStyle.parseStyle(str);
749 final @property Widget rootWidget () pure nothrow @safe @nogc { pragma(inline, true); return mRoot; }
751 final @property int x0 () const pure nothrow @safe @nogc { return (mMinimized ? winminx : winrect.pos.x); }
752 final @property int y0 () const pure nothrow @safe @nogc { return (mMinimized ? winminy : winrect.pos.y); }
753 final @property int width () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeX : winrect.size.w); }
754 final @property int height () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeY : winrect.size.h); }
756 final @property void x0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminx = v; else winrect.pos.x = v; }
757 final @property void y0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminy = v; else winrect.pos.y = v; }
758 final @property void width (int v) nothrow @safe @nogc { winrect.size.w = v; }
759 final @property void height (int v) nothrow @safe @nogc { winrect.size.h = v; }
761 final void setSize (int awidth, int aheight) {
762 bool changed = false;
763 if (awidth > 0 && awidth != winrect.size.w) { changed = true; winrect.size.w = awidth; }
764 if (aheight > 0 && aheight != winrect.size.w) { changed = true; winrect.size.h = aheight; }
765 if (changed) fixRoot();
768 final void setClientSize (int awidth, int aheight) {
769 bool changed = false;
770 if (awidth > 0 && awidth+decorationSizeX != winrect.size.w) { changed = true; winrect.size.w = awidth+decorationSizeX; }
771 if (aheight > 0 && aheight+decorationSizeY != winrect.size.h) { changed = true; winrect.size.h = aheight+decorationSizeY; }
772 if (changed) fixRoot();
775 final void centerWindow () nothrow @trusted @nogc {
776 winrect.pos.x = (screenWidth-winrect.size.w)/2;
777 winrect.pos.y = (screenHeight-winrect.size.h)/2;
780 final @property SubWindow prev () pure nothrow @safe @nogc { return mPrev; }
781 final @property SubWindow next () pure nothrow @safe @nogc { return mNext; }
783 final @property Type type () const pure nothrow @safe @nogc { return mType; }
784 final @property bool onTop () const pure nothrow @safe @nogc { return (mType == Type.OnTop); }
785 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType == Type.OnBottom); }
787 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList; }
789 final @property bool modal () const pure nothrow @safe @nogc { return mModal; }
790 final @property bool closed () const pure nothrow @safe @nogc { return mClosed; }
792 final @property bool active () const nothrow @trusted @nogc {
793 if (!mInWinList || mClosed || mMinimized) return false;
794 return (getActiveSubWindow is this);
797 @property int decorationSizeX () const nothrow @safe { return 2*2; }
798 @property int decorationSizeY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1+2; }
800 @property int clientOffsetX () const nothrow @safe { return 2; }
801 @property int clientOffsetY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1; }
803 final @property int clientWidth () const nothrow @safe { return winrect.size.w-decorationSizeX; }
804 final @property int clientHeight () const nothrow @safe { return winrect.size.h-decorationSizeY; }
806 protected void drawWidgets () {
807 setupClientClip();
808 if (mRoot !is null) mRoot.onPaint();
811 // draw window frame and background in "normal" state
812 protected void drawWindowNormal () {
813 setupClip();
814 immutable string act = (active ? null : "inactive");
815 gxDrawWindow(winrect, title,
816 getStyleColor(this, "frame", act),
817 getStyleColor(this, "title-text", act),
818 getStyleColor(this, "title-back", act),
819 getStyleColor(this, "back", act));
822 // draw window frame and background in "minimized" state
823 protected void drawWindowMinimized () {
824 gxClipRect.x0 = winminx;
825 gxClipRect.y0 = winminy;
826 gxClipRect.x1 = winminx+MinSizeX-1;
827 gxClipRect.y1 = winminy+MinSizeY-1;
828 immutable string act = (active ? null : "inactive");
829 gxFillRect(winminx, winminy, MinSizeX, MinSizeY, getStyleColor(this, "back", act));
830 gxDrawRect(winminx, winminy, MinSizeX, MinSizeY, getStyleColor(this, "frame", act));
833 void releaseWidgetGrab () {
834 if (mRoot !is null) mRoot.releaseGrab();
837 // event in our local coords
838 void startMouseDrag (MouseEvent event) {
839 releaseWidgetGrab();
840 subwinDrag = this;
841 subwinDragXSpot = -event.x;
842 subwinDragYSpot = -event.y;
843 postScreenRebuild();
846 void startKeyboardDrag () {
847 releaseWidgetGrab();
848 subwinDrag = this;
849 subwinDragXSpot = int.min;
850 subwinDragYSpot = int.min;
851 postScreenRebuild();
854 void stopDrag () {
855 if (subwinDrag is this) {
856 releaseWidgetGrab();
857 subwinDrag = null;
858 postScreenRebuild();
862 void onPaint () {
863 if (closed) return;
864 gxWithSavedClip {
865 if (!mMinimized) {
866 drawWindowNormal();
867 drawWidgets();
868 } else {
869 drawWindowMinimized();
875 bool onKeySink (KeyEvent event) {
876 return false;
879 bool onKeyBubble (KeyEvent event) {
880 // global window hotkeys
881 if (event.pressed) {
882 if (event == "C-F5") { startKeyboardDrag(); return true; }
883 if (event == "M-M" && !mModal) { minimize(); return true; }
885 return false;
888 bool onKeyEvent (KeyEvent event) {
889 if (closed) return false;
890 if (mMinimized) return false;
891 if (onKeySink(event)) return true;
892 if (mRoot !is null && mRoot.dispatchKey(event)) return true;
893 return onKeyBubble(event);
897 bool onMouseSink (MouseEvent event) {
898 // start drag?
899 if (subwinDrag is null && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
900 if (event.x >= 0 && event.y >= 0 &&
901 event.x < width && event.y < (!mMinimized ? gxTextHeightUtf+2 : height))
903 startMouseDrag(event);
904 return true;
908 if (mMinimized) return false;
910 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.right) {
911 if (event.x >= 0 && event.y >= 0 &&
912 event.x < winrect.size.w && event.y < gxTextHeightUtf)
914 if (!mModal && mType == Type.Normal) minimize();
915 return true;
919 return false;
922 bool onMouseBubble (MouseEvent event) {
923 return false;
926 bool onMouseEvent (MouseEvent event) {
927 if (!active) return false;
928 if (closed) return false;
930 int mx, my;
931 event.mouse2xy(mx, my);
933 MouseEvent ev = event;
934 ev.x = mx-x0;
935 ev.y = my-y0;
936 if (onMouseSink(ev)) return true;
938 if (mRoot !is null) {
939 ev = event;
940 ev.x = mx-(x0+clientOffsetX)-mRoot.rect.x0;
941 ev.y = my-(y0+clientOffsetY)-mRoot.rect.y0;
942 if (mRoot.dispatchMouse(ev)) return true;
945 ev = event;
946 ev.x = mx-x0;
947 ev.y = my-y0;
948 return onMouseBubble(ev);
952 bool onCharSink (dchar ch) {
953 return false;
956 bool onCharBubble (dchar ch) {
957 return false;
960 bool onCharEvent (dchar ch) {
961 if (!active) return false;
962 if (closed) return false;
963 if (mMinimized) return false;
964 if (onCharSink(ch)) return true;
965 if (mRoot !is null && mRoot.dispatchChar(ch)) return true;
966 return onCharBubble(ch);
969 void setupClip () {
970 gxClipRect.intersect(winrect);
973 final void setupClientClip () {
974 setupClip();
975 gxClipRect.intersect(GxRect(
976 GxPoint(winrect.pos.x+clientOffsetX, winrect.pos.y+clientOffsetY),
977 GxPoint(winrect.pos.x+clientOffsetX+clientWidth-1, winrect.pos.y+clientOffsetY+clientHeight-1)));
980 void close () {
981 mClosed = true;
982 if (removeSubWindow(this)) postScreenRebuild();
985 protected bool addToSubwinList (bool asModal, bool fromKeyboard) {
986 if (fromKeyboard) ignoreSubWinChar = true;
987 if (mInWinList) return true;
988 mModal = asModal;
989 if (insertSubWindow(this)) {
990 postScreenRebuild();
991 return true;
993 return false;
996 void add (bool fromKeyboard=false) { addToSubwinList(false, fromKeyboard); }
998 void addModal (bool fromKeyboard=false) { addToSubwinList(true, fromKeyboard); }
1000 void bringToFront () {
1001 if (mClosed || !mInWinList) return;
1002 auto aw = getActiveSubWindow();
1003 if (aw is this) { mMinimized = false; return; }
1004 if (aw !is null && aw.mModal) return; // alas
1005 removeSubWindow(this);
1006 mMinimized = false;
1007 insertSubWindow(this);
1008 if (subwinDrag !is this) subwinDrag = null;
1009 postScreenRebuild();
1012 @property bool minimized () const { return mMinimized; }
1014 @property void minimized (bool v) {
1015 if (v == mMinimized) return;
1016 if (v) minimize(); else bringToFront();
1019 void minimize () {
1020 if (mClosed || mMinimized) return;
1021 if (!mInWinList) { mMinimized = true; return; }
1022 if (mModal) return;
1023 assert(subwinLast !is null);
1024 releaseWidgetGrab();
1025 findMinimizedPos(winminx, winminy);
1026 auto aw = getActiveSubWindow();
1027 if (aw is this) subwinDrag = null;
1028 mMinimized = true;
1029 postScreenRebuild();
1032 void restore () {
1033 releaseWidgetGrab();
1034 bringToFront();
1037 public:
1038 enum MinSizeX = 16;
1039 enum MinSizeY = 16;
1040 enum MinMarginX = 3;
1041 enum MinMarginY = 3;
1043 protected:
1044 static findMinimizedPos (out int wx, out int wy) {
1045 static bool isOccupied (int x, int y) {
1046 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
1047 if (w.mInWinList && !w.closed && w.mMinimized) {
1048 if (x >= w.winminx && y >= w.winminy && x < w.winminx+MinSizeX && y < w.winminy+MinSizeY) return true;
1051 return false;
1054 int txcount = screenWidth/(MinSizeX+MinMarginX);
1055 //int tycount = screenHeight/(MinSizeY+MinMarginY);
1056 if (txcount < 1) txcount = 1;
1057 //if (tycount < 1) tycount = 1;
1058 foreach (immutable int n; 0..6/*5535*/) {
1059 int x = (n%txcount)*(MinSizeX+MinMarginX)+1;
1060 int y = screenHeight-MinSizeY-(n/txcount)*(MinSizeY+MinMarginY);
1061 //conwriteln("trying (", x, ",", y, ")");
1062 if (!isOccupied(x, y)) { wx = x; wy = y; /*conwriteln(" HIT!");*/ return; }