added progress bar widget, and progress window
[chiroptera.git] / egui / subwindows.d
blobdbd0474bf918e885cdb24461618e43dea76fecb7
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module egui.subwindows /*is aliced*/;
18 private:
20 import core.time;
22 import arsd.simpledisplay;
24 import iv.alice;
25 import iv.cmdcon;
26 import iv.flexlay2;
27 import iv.strex;
28 import iv.utfutil;
30 import egfx;
31 public import egui.style;
32 import egui.widgets : Widget, RootWidget;
35 // ////////////////////////////////////////////////////////////////////////// //
36 public __gshared SimpleWindow vbwin; // main window; MUST be set!
37 public __gshared bool vbfocused = false;
40 // ////////////////////////////////////////////////////////////////////////// //
41 __gshared public int lastMouseXUnscaled = 10000, lastMouseYUnscaled = 10000;
42 __gshared /*MouseButton*/public int lastMouseButton;
44 public int lastMouseX () nothrow @trusted @nogc { pragma(inline, true); return lastMouseXUnscaled/screenEffScale; }
45 public int lastMouseY () nothrow @trusted @nogc { pragma(inline, true); return lastMouseYUnscaled/screenEffScale; }
47 public bool lastMouseLeft () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.left) != 0); }
48 public bool lastMouseRight () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.right) != 0); }
49 public bool lastMouseMiddle () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.middle) != 0); }
52 // ////////////////////////////////////////////////////////////////////////// //
53 public class ScreenRebuildEvent {}
54 public class ScreenRepaintEvent {}
55 public class QuitEvent {}
56 public class CursorBlinkEvent {}
57 public class HideMouseEvent {}
59 __gshared ScreenRebuildEvent evScrRebuild;
60 __gshared ScreenRepaintEvent evScreenRepaint;
61 __gshared CursorBlinkEvent evCurBlink;
62 __gshared HideMouseEvent evHideMouse;
64 shared static this () {
65 evScrRebuild = new ScreenRebuildEvent();
66 evScreenRepaint = new ScreenRepaintEvent();
67 evCurBlink = new CursorBlinkEvent();
68 evHideMouse = new HideMouseEvent();
72 // ////////////////////////////////////////////////////////////////////////// //
73 public void postScreenRebuild () { if (vbwin !is null && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScrRebuild); }
74 public void postScreenRepaint () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScreenRepaint); }
75 public void postScreenRepaintDelayed () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postTimeout(evScreenRepaint, 35); }
77 public void postCurBlink () {
78 if (vbwin !is null && !vbwin.eventQueued!CursorBlinkEvent) {
79 //conwriteln("curblink posted!");
80 vbwin.postTimeout(evCurBlink, 100);
81 //vbwin.postTimeout(evCurBlink, 500);
86 // ////////////////////////////////////////////////////////////////////////// //
87 __gshared MonoTime lastMouseMove;
88 __gshared uint MouseHideTime = 3000;
91 shared static this () {
92 conRegVar("mouse_hide_time", "mouse cursor hiding time (in milliseconds); 0 to not hide it",
93 delegate (self) {
94 return MouseHideTime;
96 delegate (self, uint nv) {
97 if (MouseHideTime != nv) {
98 if (MouseHideTime == 0) mouseMoved();
99 MouseHideTime = nv;
100 conwriteln("mouse hiding time: ", nv);
107 //==========================================================================
109 // mshtime_dbg
111 //==========================================================================
112 public int mshtime_dbg () {
113 if (MouseHideTime > 0) {
114 auto ctt = MonoTime.currTime;
115 auto mt = (ctt-lastMouseMove).total!"msecs";
116 return cast(int)mt;
117 } else {
118 return 0;
123 //==========================================================================
125 // isMouseVisible
127 //==========================================================================
128 public bool isMouseVisible () {
129 if (MouseHideTime > 0) {
130 auto ctt = MonoTime.currTime;
131 return ((ctt-lastMouseMove).total!"msecs" < MouseHideTime+500);
132 } else {
133 return true;
138 //==========================================================================
140 // mouseAlpha
142 //==========================================================================
143 public float mouseAlpha () {
144 if (MouseHideTime > 0) {
145 auto ctt = MonoTime.currTime;
146 auto msc = (ctt-lastMouseMove).total!"msecs";
147 if (msc >= MouseHideTime+500) return 0.0f;
148 if (msc < MouseHideTime) return 1.0f;
149 msc -= MouseHideTime;
150 return 1.0f-msc/500.0f;
151 } else {
152 return 1.0f;
157 //==========================================================================
159 // repostHideMouse
161 // returns `true` if mouse should be redrawn
163 //==========================================================================
164 public bool repostHideMouse () {
165 if (vbwin is null || vbwin.eventQueued!HideMouseEvent) return false;
166 if (MouseHideTime > 0) {
167 auto ctt = MonoTime.currTime;
168 auto tms = (ctt-lastMouseMove).total!"msecs";
169 if (tms >= MouseHideTime) {
170 if (tms >= MouseHideTime+500) return true; // hide it
171 vbwin.postTimeout(evHideMouse, 50);
172 return true; // fade it
174 vbwin.postTimeout(evHideMouse, cast(int)(MouseHideTime-tms));
176 return false;
180 //==========================================================================
182 // mouseMoved
184 //==========================================================================
185 public void mouseMoved () {
186 if (MouseHideTime > 0) {
187 lastMouseMove = MonoTime.currTime;
188 if (vbwin !is null && !vbwin.eventQueued!HideMouseEvent) vbwin.postTimeout(evHideMouse, MouseHideTime);
193 //==========================================================================
195 // drawTextCursor
197 //==========================================================================
198 public void drawTextCursor (bool active, int x, int y, int hgt=-666) {
199 if (hgt == -666) hgt = gxTextHeightUtf;
200 if (hgt < 1) return;
201 if (active) {
202 auto ctt = (MonoTime.currTime.ticks*1000/MonoTime.ticksPerSecond)/100;
203 int doty = ctt%(hgt*2-1);
204 if (doty >= hgt) doty = hgt*2-doty-1;
205 gxVLine(x, y, hgt, (ctt%10 < 5 ? gxRGB!(255, 255, 255) : gxRGB!(200, 200, 200)));
206 gxPutPixel(x, y+doty, gxRGB!(0, 255, 255));
207 postCurBlink();
208 } else {
209 gxVLine(x, y, hgt, gxRGB!(170, 170, 170));
214 // ////////////////////////////////////////////////////////////////////////// //
215 private __gshared SubWindow subwinLast;
216 private __gshared bool ignoreSubWinChar = false;
217 // make a package and move that to package
218 private __gshared SubWindow subwinDrag = null;
219 private __gshared int subwinDragXSpot, subwinDragYSpot;
222 public @property bool isSubWinDragging () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null); }
223 public @property bool isSubWinDraggingKeyboard () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null && subwinDragXSpot == int.min && subwinDragYSpot == int.min); }
226 //==========================================================================
228 // eguiLostGlobalFocus
230 //==========================================================================
231 public void eguiLostGlobalFocus () {
232 ignoreSubWinChar = false;
233 subwinDrag = null;
234 SubWindow aw = getActiveSubWindow();
235 if (aw !is null) aw.releaseWidgetGrab();
239 private bool insertSubWindow (SubWindow nw) {
240 if (nw is null || nw.mClosed) return false;
241 assert(nw.mPrev is null);
242 assert(nw.mNext is null);
243 assert(!nw.mInWinList);
244 nw.releaseWidgetGrab();
245 SubWindow law = getActiveSubWindow();
246 if (nw.mType == SubWindow.Type.OnBottom) {
247 SubWindow w = subwinLast;
248 if (w !is null) {
249 while (w.mPrev !is null) w = w.mPrev;
250 nw.mNext = w;
251 if (w !is null) w.mPrev = nw; else subwinLast = nw;
252 } else {
253 subwinLast = nw;
255 nw.mInWinList = true;
256 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
257 return true;
259 SubWindow aw = getActiveSubWindow();
260 assert(aw !is nw);
261 if (nw.mType == SubWindow.Type.OnTop || aw is null) {
262 nw.mPrev = subwinLast;
263 if (subwinLast !is null) subwinLast.mNext = nw;
264 subwinLast = nw;
265 nw.mInWinList = true;
266 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
267 return true;
269 if (aw.mModal && !nw.mModal) return false; // can't insert normal windows while modal window is active
270 // insert after aw
271 nw.mPrev = aw;
272 nw.mNext = aw.mNext;
273 aw.mNext = nw;
274 if (nw.mNext !is null) nw.mNext.mPrev = nw;
275 if (aw is subwinLast) subwinLast = nw;
276 nw.mInWinList = true;
277 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
278 return true;
282 private bool removeSubWindow (SubWindow nw) {
283 if (nw is null || !nw.mInWinList) return false;
284 nw.releaseWidgetGrab();
285 SubWindow law = getActiveSubWindow();
286 if (nw.mPrev !is null) nw.mPrev.mNext = nw.mNext;
287 if (nw.mNext !is null) nw.mNext.mPrev = nw.mPrev;
288 if (nw is subwinLast) subwinLast = nw.mPrev;
289 nw.mPrev = null;
290 nw.mNext = null;
291 nw.mInWinList = false;
292 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
293 return true;
297 //==========================================================================
299 // mouse2xy
301 //==========================================================================
302 public void mouse2xy (MouseEvent event, out int mx, out int my) nothrow @trusted @nogc {
303 mx = event.x/screenEffScale;
304 my = event.y/screenEffScale;
308 //==========================================================================
310 // subWindowAt
312 //==========================================================================
313 public SubWindow subWindowAt (in GxPoint p) nothrow @trusted @nogc {
314 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
315 if (!w.closed) {
316 if (w.mMinimized) {
317 if (p.x >= w.winminx && p.y >= w.winminy && p.x < w.winminx+w.MinSizeX && p.y < w.winminy+w.MinSizeY) return w;
318 } else {
319 if (p.inside(w.winrect)) return w;
323 return null;
327 public SubWindow subWindowAt (int mx, int my) nothrow @trusted @nogc { return subWindowAt(GxPoint(mx, my)); }
330 //==========================================================================
332 // subWindowAt
334 //==========================================================================
335 public SubWindow subWindowAt (MouseEvent event) nothrow @trusted @nogc {
336 pragma(inline, true);
337 return subWindowAt(event.x/screenEffScale, event.y/screenEffScale);
341 //==========================================================================
343 // getActiveSubWindow
345 //==========================================================================
346 public SubWindow getActiveSubWindow () nothrow @trusted @nogc {
347 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
348 if (!w.mClosed && w.type != SubWindow.Type.OnTop && !w.mMinimized) return w;
350 return null;
354 //==========================================================================
356 // dispatchEvent
358 //==========================================================================
359 public bool dispatchEvent (KeyEvent event) {
360 bool res = false;
361 if (isSubWinDragging) {
362 if (isSubWinDraggingKeyboard) {
363 if (!event.pressed) return true;
364 if (event == "Left") subwinDrag.x0 = subwinDrag.x0-1;
365 else if (event == "Right") subwinDrag.x0 = subwinDrag.x0+1;
366 else if (event == "Up") subwinDrag.y0 = subwinDrag.y0-1;
367 else if (event == "Down") subwinDrag.y0 = subwinDrag.y0+1;
368 else if (event == "C-Left") subwinDrag.x0 = subwinDrag.x0-8;
369 else if (event == "C-Right") subwinDrag.x0 = subwinDrag.x0+8;
370 else if (event == "C-Up") subwinDrag.y0 = subwinDrag.y0-8;
371 else if (event == "C-Down") subwinDrag.y0 = subwinDrag.y0+8;
372 else if (event == "Home") subwinDrag.x0 = 0;
373 else if (event == "End") subwinDrag.x0 = screenWidth-subwinDrag.width;
374 else if (event == "PageUp") subwinDrag.y0 = 0;
375 else if (event == "PageDown") subwinDrag.y0 = screenHeight-subwinDrag.height;
376 else if (event == "Escape" || event == "Enter") subwinDrag = null;
377 postScreenRebuild();
378 return true;
380 } else if (auto aw = getActiveSubWindow()) {
381 res = aw.onKeyEvent(event);
382 if (res) postScreenRebuild();
384 return res;
388 //==========================================================================
390 // dispatchEvent
392 //==========================================================================
393 public bool dispatchEvent (MouseEvent event) {
394 __gshared SubWindow lastHover = null;
396 if (subwinLast is null) { postScreenRepaint(); return false; }
398 int mx = lastMouseX;
399 int my = lastMouseY;
400 auto aw = getActiveSubWindow();
401 auto msw = subWindowAt(event);
402 scope(exit) {
403 if (msw !is lastHover) {
404 lastHover = msw;
405 postScreenRebuild();
408 bool curIsModal = (aw !is null && aw.mModal);
410 // switch window by button press
411 if (event.type == MouseEventType.buttonReleased && msw !is aw && !curIsModal) {
412 if (msw !is null && msw.mType == SubWindow.Type.Normal) {
413 if (aw !is null) aw.releaseWidgetGrab();
414 msw.releaseWidgetGrab();
415 msw.bringToFront();
416 postScreenRepaint();
417 return true;
421 // drag
422 if (isSubWinDragging) {
423 subwinDrag.releaseWidgetGrab(); // just in case
424 if (!isSubWinDraggingKeyboard) {
425 subwinDrag.x0 = mx+subwinDragXSpot;
426 subwinDrag.y0 = my+subwinDragYSpot;
427 // stop drag?
428 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) subwinDrag = null;
429 postScreenRebuild();
430 } else {
431 postScreenRepaint();
433 return true;
436 if (msw is null || msw !is aw || msw.mMinimized) {
437 if (msw is null || !msw.onTop) {
438 postScreenRepaint();
439 return false;
442 assert(msw !is null);
444 if (msw.onMouseEvent(event)) {
445 postScreenRebuild();
446 return true;
449 postScreenRepaint();
450 return false;
454 //==========================================================================
456 // dispatchEvent
458 //==========================================================================
459 public bool dispatchEvent (dchar ch) {
460 if (ignoreSubWinChar) { ignoreSubWinChar = false; return (subwinLast !is null); }
461 bool res = false;
462 if (!isSubWinDragging) {
463 if (auto aw = getActiveSubWindow()) {
464 res = aw.onCharEvent(ch);
465 if (res) postScreenRebuild();
468 return res;
472 //==========================================================================
474 // paintSubWindows
476 //==========================================================================
477 public void paintSubWindows () {
478 // get first window
479 SubWindow firstWin = subwinLast;
480 if (firstWin is null) return;
481 while (firstWin.mPrev !is null) firstWin = firstWin.mPrev;
483 SubWindow firstMin, firstNormal, firstTop;
484 SubWindow lastMin, lastNormal, lastTop;
486 //gxClipReset();
488 void doDraw (SubWindow w) {
489 if (w !is null) {
490 gxClipReset();
491 w.onPaint();
492 if (w is subwinDrag) { gxClipReset(); gxFillRect(w.x0, w.y0, w.width, w.height, gxRGBA!(255, 127, 0, 176)); }
496 // paint background windows
497 for (SubWindow w = firstWin; w !is null; w = w.mNext) {
498 if (w.mClosed) continue;
499 if (w.mMinimized) {
500 if (firstMin is null) firstMin = w;
501 lastMin = w;
502 } else if (w.mType == SubWindow.Type.Normal) {
503 if (firstNormal is null) firstNormal = w;
504 lastNormal = w;
505 } else if (w.mType == SubWindow.Type.OnTop) {
506 if (firstTop is null) firstTop = w;
507 lastTop = w;
508 } else if (w.mType == SubWindow.Type.OnBottom) {
509 doDraw(w);
513 // paint minimized windows
514 for (SubWindow w = firstMin; w !is null; w = w.mNext) {
515 if (!w.mClosed && w.mMinimized) doDraw(w);
516 if (w is lastMin) break;
519 // paint normal windows
520 for (SubWindow w = firstNormal; w !is null; w = w.mNext) {
521 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.Normal) doDraw(w);
522 if (w is lastNormal) break;
525 // paint ontop windows
526 for (SubWindow w = firstTop; w !is null; w = w.mNext) {
527 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.OnTop) doDraw(w);
528 if (w is lastTop) break;
531 // paint hint for minimized window
532 if (auto msw = subWindowAt(lastMouseX, lastMouseY)) {
533 if (!msw.mClosed && msw.mMinimized && msw.title.length) {
534 auto wdt = gxTextWidthUtf(msw.title)+2;
535 auto hgt = gxTextHeightUtf+2;
536 int y = msw.winminy-hgt;
537 int x;
538 if (wdt >= screenWidth) {
539 x = (screenWidth-wdt)/2;
540 } else {
541 x = (msw.winminx+msw.MinSizeX)/2-wdt/2;
542 if (x < 0) x = 0;
544 gxClipReset();
545 gxFillRect(x, y, wdt, hgt, gxRGB!(255, 255, 255));
546 gxDrawTextUtf(x+1, y+1, msw.title, gxRGB!(0, 0, 0));
552 // ////////////////////////////////////////////////////////////////////////// //
553 public class SubWindow {
554 protected:
555 enum Type {
556 Normal,
557 OnTop,
558 OnBottom,
561 protected:
562 SubWindow mPrev, mNext;
563 Type mType = Type.Normal;
564 bool mMinimized;
565 bool mInWinList;
566 bool mModal;
567 bool mClosed;
568 int awidx; // active widget index
569 RootWidget mRoot;
571 ColorStyle colorStyle;
573 public:
574 int winminx, winminy;
575 GxRect winrect;
576 string title;
578 GxSize minWinSize;
579 GxSize maxWinSize;
581 public:
582 // color getters
583 final uint getStyleColor (in Object obj, const(char)[] type, const(char)[] mod=null) {
584 pragma(inline, true);
585 ColorStyle st = colorStyle;
586 if (st is null) st = defaultColorStyle;
587 return st.findColor(this, obj, type, mod);
590 final uint getColor (const(char)[] type, const(char)[] mod) {
591 return getStyleColor(this, type, mod);
594 final uint getColor (const(char)[] type) {
595 return getStyleColor(this, type, (active ? null : "inactive"));
598 protected:
599 void createRoot () {
600 if (mRoot is null) setRoot(new RootWidget(this)); else fixRoot();
603 void fixRoot () {
604 if (mRoot) {
605 mRoot.rect.pos = GxPoint(0, 0);
606 mRoot.rect.size = GxSize(clientWidth, clientHeight);
610 void setStyle (ColorStyle stl) {
611 colorStyle = stl;
614 void finishConstruction () {
615 winrect.size.sanitize();
616 createRoot();
617 createStyle();
618 createWidgets();
619 finishCreating();
622 public:
623 this () {
624 winrect.pos = GxPoint(0, 0);
625 winrect.size = GxSize(0, 0);
626 finishConstruction();
629 this (string atitle) {
630 title = atitle;
631 winrect.pos = GxPoint(0, 0);
632 winrect.size = GxSize(0, 0);
633 finishConstruction();
636 this (string atitle, in GxPoint apos, in GxSize asize) {
637 title = atitle;
638 winrect.pos = apos;
639 winrect.size = asize;
640 finishConstruction();
643 this (string atitle, in GxSize asize) {
644 title = atitle;
645 winrect.size = asize;
646 winrect.size.sanitize();
647 winrect.pos.x = (screenWidth-winrect.width)/2;
648 winrect.pos.y = (screenHeight-winrect.height)/2;
649 finishConstruction();
652 // this doesn't perform relayouting
653 void setRoot (RootWidget w) {
654 mRoot = w;
655 fixRoot();
658 // this is called from constructor
659 void createStyle () {
662 // this is called from constructor
663 void createWidgets () {
666 // this is called from constructor
667 // you can call `addModal()` here, for example
668 void finishCreating () {
669 add();
672 void relayout (bool resizeWindow) {
673 if (mRoot is null) return;
675 FuiFlexLayouter!Widget lay;
677 lay.isValidBoxId = delegate bool (Widget id) { return (id !is null); };
679 lay.firstChild = delegate Widget (Widget id) { return id.firstChild; };
680 lay.nextSibling = delegate Widget (Widget id) { return id.nextSibling; };
682 lay.getMinSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.minSize.w : id.minSize.h); };
683 lay.getMaxSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.maxSize.w : id.maxSize.h); };
684 lay.getPrefSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.prefSize.w : id.prefSize.h); };
686 lay.isHorizBox = delegate bool (Widget id) { return (id.childDir == GxDir.Horiz); };
688 lay.getFlex = delegate int (Widget id) { return id.flex; };
690 lay.getSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.boxsize.w : id.boxsize.h); };
691 lay.setSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.boxsize.w = val; else id.boxsize.h = val; };
693 lay.getFinalSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.finalSize.w : id.finalSize.h); };
694 lay.setFinalSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalSize.w = val; else id.finalSize.h = val; };
696 lay.setFinalPos = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalPos.x = val; else id.finalPos.y = val; };
698 if (winrect.size.empty) {
699 if (maxWinSize.empty) maxWinSize = GxSize(screenWidth-decorationSizeX, screenHeight-decorationSizeY);
700 resizeWindow = true;
703 if (winrect.size.w > screenWidth) winrect.size.w = screenWidth;
704 if (winrect.size.h > screenHeight) winrect.size.h = screenHeight;
706 if (resizeWindow) {
707 mRoot.maxSize = maxWinSize;
708 } else {
709 mRoot.maxSize = winrect.size-GxSize(decorationSizeX, decorationSizeY);
710 if (mRoot.maxSize.w <= 0) mRoot.maxSize.w = maxWinSize.w;
711 if (mRoot.maxSize.h <= 0) mRoot.maxSize.h = maxWinSize.h;
714 mRoot.minSize = minWinSize;
715 if (mRoot.minSize.w > screenWidth-decorationSizeX) mRoot.minSize.w = screenWidth-decorationSizeX;
716 if (mRoot.minSize.h > screenHeight-decorationSizeY) mRoot.minSize.h = screenHeight-decorationSizeY;
718 if (title.length) {
719 if (mRoot.boxsize.w <= 0) mRoot.boxsize.w = gxTextWidthUtf(title)+2;
721 mRoot.prefSize = mRoot.boxsize;
723 mRoot.forEachDepth((Widget w) { w.preLayout(); });
725 lay.layout(mRoot);
727 winrect.size = mRoot.boxsize+GxSize(decorationSizeX, decorationSizeY);
728 if (winrect.x1 >= screenWidth) winrect.pos.x -= winrect.x1-screenWidth+1;
729 if (winrect.y1 >= screenHeight) winrect.pos.y -= winrect.y1-screenHeight+1;
730 if (winrect.pos.x < 0) winrect.pos.x = 0;
731 if (winrect.pos.y < 0) winrect.pos.y = 0;
733 mRoot.forEachDepth((Widget w) { w.postLayout(); });
736 void relayoutResize () { relayout(true); }
738 // this clones the style
739 void appendStyle (const(char)[] str) {
740 if (colorStyle is null) {
741 colorStyle = new ColorStyle;
742 colorStyle.cloneFrom(defaultColorStyle);
744 colorStyle.parseStyle(str);
747 final @property Widget rootWidget () pure nothrow @safe @nogc { pragma(inline, true); return mRoot; }
749 final @property int x0 () const pure nothrow @safe @nogc { return (mMinimized ? winminx : winrect.pos.x); }
750 final @property int y0 () const pure nothrow @safe @nogc { return (mMinimized ? winminy : winrect.pos.y); }
751 final @property int width () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeX : winrect.size.w); }
752 final @property int height () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeY : winrect.size.h); }
754 final @property void x0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminx = v; else winrect.pos.x = v; }
755 final @property void y0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminy = v; else winrect.pos.y = v; }
756 final @property void width (int v) nothrow @safe @nogc { winrect.size.w = v; }
757 final @property void height (int v) nothrow @safe @nogc { winrect.size.h = v; }
759 final void setSize (int awidth, int aheight) {
760 bool changed = false;
761 if (awidth > 0 && awidth != winrect.size.w) { changed = true; winrect.size.w = awidth; }
762 if (aheight > 0 && aheight != winrect.size.w) { changed = true; winrect.size.h = aheight; }
763 if (changed) fixRoot();
766 final void setClientSize (int awidth, int aheight) {
767 bool changed = false;
768 if (awidth > 0 && awidth+decorationSizeX != winrect.size.w) { changed = true; winrect.size.w = awidth+decorationSizeX; }
769 if (aheight > 0 && aheight+decorationSizeY != winrect.size.h) { changed = true; winrect.size.h = aheight+decorationSizeY; }
770 if (changed) fixRoot();
773 final void centerWindow () nothrow @trusted @nogc {
774 winrect.pos.x = (screenWidth-winrect.size.w)/2;
775 winrect.pos.y = (screenHeight-winrect.size.h)/2;
778 final @property SubWindow prev () pure nothrow @safe @nogc { return mPrev; }
779 final @property SubWindow next () pure nothrow @safe @nogc { return mNext; }
781 final @property Type type () const pure nothrow @safe @nogc { return mType; }
782 final @property bool onTop () const pure nothrow @safe @nogc { return (mType == Type.OnTop); }
783 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType == Type.OnBottom); }
785 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList; }
787 final @property bool modal () const pure nothrow @safe @nogc { return mModal; }
788 final @property bool closed () const pure nothrow @safe @nogc { return mClosed; }
790 final @property bool active () const nothrow @trusted @nogc {
791 if (!mInWinList || mClosed || mMinimized) return false;
792 return (getActiveSubWindow is this);
795 @property int decorationSizeX () const nothrow @safe { return 2*2; }
796 @property int decorationSizeY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1+2; }
798 @property int clientOffsetX () const nothrow @safe { return 2; }
799 @property int clientOffsetY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1; }
801 final @property int clientWidth () const nothrow @safe { return winrect.size.w-decorationSizeX; }
802 final @property int clientHeight () const nothrow @safe { return winrect.size.h-decorationSizeY; }
804 protected void drawWidgets () {
805 setupClientClip();
806 if (mRoot !is null) mRoot.onPaint();
809 // draw window frame and background in "normal" state
810 protected void drawWindowNormal () {
811 setupClip();
812 immutable string act = (active ? null : "inactive");
813 gxDrawWindow(winrect, title,
814 getStyleColor(this, "frame", act),
815 getStyleColor(this, "title-text", act),
816 getStyleColor(this, "title-back", act),
817 getStyleColor(this, "back", act));
820 // draw window frame and background in "minimized" state
821 protected void drawWindowMinimized () {
822 gxClipRect.x0 = winminx;
823 gxClipRect.y0 = winminy;
824 gxClipRect.x1 = winminx+MinSizeX-1;
825 gxClipRect.y1 = winminy+MinSizeY-1;
826 immutable string act = (active ? null : "inactive");
827 gxFillRect(winminx, winminy, MinSizeX, MinSizeY, getStyleColor(this, "back", act));
828 gxDrawRect(winminx, winminy, MinSizeX, MinSizeY, getStyleColor(this, "frame", act));
831 void releaseWidgetGrab () {
832 if (mRoot !is null) mRoot.releaseGrab();
835 // event in our local coords
836 void startMouseDrag (MouseEvent event) {
837 releaseWidgetGrab();
838 subwinDrag = this;
839 subwinDragXSpot = -event.x;
840 subwinDragYSpot = -event.y;
841 postScreenRebuild();
844 void startKeyboardDrag () {
845 releaseWidgetGrab();
846 subwinDrag = this;
847 subwinDragXSpot = int.min;
848 subwinDragYSpot = int.min;
849 postScreenRebuild();
852 void stopDrag () {
853 if (subwinDrag is this) {
854 releaseWidgetGrab();
855 subwinDrag = null;
856 postScreenRebuild();
860 void onPaint () {
861 if (closed) return;
862 gxWithSavedClip {
863 if (!mMinimized) {
864 drawWindowNormal();
865 drawWidgets();
866 } else {
867 drawWindowMinimized();
873 bool onKeySink (KeyEvent event) {
874 return false;
877 bool onKeyBubble (KeyEvent event) {
878 // global window hotkeys
879 if (event.pressed) {
880 if (event == "C-F5") { startKeyboardDrag(); return true; }
881 if (event == "M-M" && !mModal) { minimize(); return true; }
883 return false;
886 bool onKeyEvent (KeyEvent event) {
887 if (closed) return false;
888 if (mMinimized) return false;
889 if (onKeySink(event)) return true;
890 if (mRoot !is null && mRoot.dispatchKey(event)) return true;
891 return onKeyBubble(event);
895 bool onMouseSink (MouseEvent event) {
896 // start drag?
897 if (subwinDrag is null && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
898 if (event.x >= 0 && event.y >= 0 &&
899 event.x < width && event.y < (!mMinimized ? gxTextHeightUtf+2 : height))
901 startMouseDrag(event);
902 return true;
906 if (mMinimized) return false;
908 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.right) {
909 if (event.x >= 0 && event.y >= 0 &&
910 event.x < winrect.size.w && event.y < gxTextHeightUtf)
912 if (!mModal && mType == Type.Normal) minimize();
913 return true;
917 return false;
920 bool onMouseBubble (MouseEvent event) {
921 return false;
924 bool onMouseEvent (MouseEvent event) {
925 if (!active) return false;
926 if (closed) return false;
928 int mx, my;
929 event.mouse2xy(mx, my);
931 MouseEvent ev = event;
932 ev.x = mx-x0;
933 ev.y = my-y0;
934 if (onMouseSink(ev)) return true;
936 if (mRoot !is null) {
937 ev = event;
938 ev.x = mx-(x0+clientOffsetX)-mRoot.rect.x0;
939 ev.y = my-(y0+clientOffsetY)-mRoot.rect.y0;
940 if (mRoot.dispatchMouse(ev)) return true;
943 ev = event;
944 ev.x = mx-x0;
945 ev.y = my-y0;
946 return onMouseBubble(ev);
950 bool onCharSink (dchar ch) {
951 return false;
954 bool onCharBubble (dchar ch) {
955 return false;
958 bool onCharEvent (dchar ch) {
959 if (!active) return false;
960 if (closed) return false;
961 if (mMinimized) return false;
962 if (onCharSink(ch)) return true;
963 if (mRoot !is null && mRoot.dispatchChar(ch)) return true;
964 return onCharBubble(ch);
967 void setupClip () {
968 gxClipRect.intersect(winrect);
971 final void setupClientClip () {
972 setupClip();
973 gxClipRect.intersect(GxRect(
974 GxPoint(winrect.pos.x+clientOffsetX, winrect.pos.y+clientOffsetY),
975 GxPoint(winrect.pos.x+clientOffsetX+clientWidth-1, winrect.pos.y+clientOffsetY+clientHeight-1)));
978 void close () {
979 mClosed = true;
980 if (removeSubWindow(this)) postScreenRebuild();
983 protected bool addToSubwinList (bool asModal, bool fromKeyboard) {
984 if (fromKeyboard) ignoreSubWinChar = true;
985 if (mInWinList) return true;
986 mModal = asModal;
987 if (insertSubWindow(this)) {
988 postScreenRebuild();
989 return true;
991 return false;
994 void add (bool fromKeyboard=false) { addToSubwinList(false, fromKeyboard); }
996 void addModal (bool fromKeyboard=false) { addToSubwinList(true, fromKeyboard); }
998 void bringToFront () {
999 if (mClosed || !mInWinList) return;
1000 auto aw = getActiveSubWindow();
1001 if (aw is this) { mMinimized = false; return; }
1002 if (aw !is null && aw.mModal) return; // alas
1003 removeSubWindow(this);
1004 mMinimized = false;
1005 insertSubWindow(this);
1006 if (subwinDrag !is this) subwinDrag = null;
1007 postScreenRebuild();
1010 @property bool minimized () const { return mMinimized; }
1012 @property void minimized (bool v) {
1013 if (v == mMinimized) return;
1014 if (v) minimize(); else bringToFront();
1017 void minimize () {
1018 if (mClosed || mMinimized) return;
1019 if (!mInWinList) { mMinimized = true; return; }
1020 if (mModal) return;
1021 assert(subwinLast !is null);
1022 releaseWidgetGrab();
1023 findMinimizedPos(winminx, winminy);
1024 auto aw = getActiveSubWindow();
1025 if (aw is this) subwinDrag = null;
1026 mMinimized = true;
1027 postScreenRebuild();
1030 void restore () {
1031 releaseWidgetGrab();
1032 bringToFront();
1035 public:
1036 enum MinSizeX = 16;
1037 enum MinSizeY = 16;
1038 enum MinMarginX = 3;
1039 enum MinMarginY = 3;
1041 protected:
1042 static findMinimizedPos (out int wx, out int wy) {
1043 static bool isOccupied (int x, int y) {
1044 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
1045 if (w.mInWinList && !w.closed && w.mMinimized) {
1046 if (x >= w.winminx && y >= w.winminy && x < w.winminx+MinSizeX && y < w.winminy+MinSizeY) return true;
1049 return false;
1052 int txcount = screenWidth/(MinSizeX+MinMarginX);
1053 //int tycount = screenHeight/(MinSizeY+MinMarginY);
1054 if (txcount < 1) txcount = 1;
1055 //if (tycount < 1) tycount = 1;
1056 foreach (immutable int n; 0..6/*5535*/) {
1057 int x = (n%txcount)*(MinSizeX+MinMarginX)+1;
1058 int y = screenHeight-MinSizeY-(n/txcount)*(MinSizeY+MinMarginY);
1059 //conwriteln("trying (", x, ",", y, ")");
1060 if (!isOccupied(x, y)) { wx = x; wy = y; /*conwriteln(" HIT!");*/ return; }