egra: better selectors (they can be customised with return type now, and has proper...
[iv.d.git] / egra / gui / subwindows.d
blob124635fc211dfe1659560cf754ee2aa37babd34b
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.dynstring;
29 import iv.flexlay2;
30 import iv.strex;
31 import iv.unarray;
32 import iv.utfutil;
34 import iv.egra.gfx;
35 public import iv.egra.gui.style;
36 import iv.egra.gui.widgets : Widget, RootWidget;
39 // ////////////////////////////////////////////////////////////////////////// //
40 public __gshared SimpleWindow vbwin; // main window; MUST be set!
41 public __gshared bool vbfocused = false;
44 // ////////////////////////////////////////////////////////////////////////// //
45 __gshared public int lastMouseXUnscaled = 10000, lastMouseYUnscaled = 10000;
46 __gshared public uint lastMouseButton;
48 public int lastMouseX () nothrow @trusted @nogc { pragma(inline, true); return lastMouseXUnscaled/screenEffScale; }
49 public int lastMouseY () nothrow @trusted @nogc { pragma(inline, true); return lastMouseYUnscaled/screenEffScale; }
51 public bool lastMouseLeft () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.left) != 0); }
52 public bool lastMouseRight () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.right) != 0); }
53 public bool lastMouseMiddle () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.middle) != 0); }
56 // ////////////////////////////////////////////////////////////////////////// //
57 public class ScreenRebuildEvent {}
58 public class ScreenRepaintEvent {}
59 public class QuitEvent {}
60 public class CursorBlinkEvent {}
61 public class HideMouseEvent {}
63 __gshared ScreenRebuildEvent evScrRebuild;
64 __gshared ScreenRepaintEvent evScreenRepaint;
65 __gshared CursorBlinkEvent evCurBlink;
66 __gshared HideMouseEvent evHideMouse;
68 shared static this () {
69 evScrRebuild = new ScreenRebuildEvent();
70 evScreenRepaint = new ScreenRepaintEvent();
71 evCurBlink = new CursorBlinkEvent();
72 evHideMouse = new HideMouseEvent();
76 // ////////////////////////////////////////////////////////////////////////// //
77 public void postScreenRebuild () { if (vbwin !is null && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScrRebuild); }
78 public void postScreenRepaint () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScreenRepaint); }
79 public void postScreenRepaintDelayed () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postTimeout(evScreenRepaint, 35); }
81 public void postCurBlink (int timeout) {
82 if (timeout < 1) return;
83 if (vbwin !is null && !vbwin.eventQueued!CursorBlinkEvent) {
84 //conwriteln("curblink posted!");
85 if (timeout < 100) timeout = 100;
86 vbwin.postTimeout(evCurBlink, timeout);
87 //vbwin.postTimeout(evCurBlink, 500);
92 // ////////////////////////////////////////////////////////////////////////// //
93 __gshared MonoTime lastMouseMove;
94 __gshared uint MouseHideTime = 3000;
97 shared static this () {
98 conRegVar("mouse_hide_time", "mouse cursor hiding time (in milliseconds); 0 to not hide it",
99 delegate (self) {
100 return MouseHideTime;
102 delegate (self, uint nv) {
103 if (MouseHideTime != nv) {
104 if (MouseHideTime == 0) egraMouseMoved();
105 MouseHideTime = nv;
106 conwriteln("mouse hiding time: ", nv);
113 //==========================================================================
115 // mshtime_dbg
117 //==========================================================================
118 public int mshtime_dbg () {
119 if (MouseHideTime > 0) {
120 auto ctt = MonoTime.currTime;
121 auto mt = (ctt-lastMouseMove).total!"msecs";
122 return cast(int)mt;
123 } else {
124 return 0;
129 //==========================================================================
131 // isMouseVisible
133 //==========================================================================
134 public bool isMouseVisible () {
135 if (MouseHideTime > 0) {
136 auto ctt = MonoTime.currTime;
137 return ((ctt-lastMouseMove).total!"msecs" < MouseHideTime+500);
138 } else {
139 return true;
144 //==========================================================================
146 // mouseAlpha
148 //==========================================================================
149 public float mouseAlpha () {
150 if (MouseHideTime > 0) {
151 auto ctt = MonoTime.currTime;
152 auto msc = (ctt-lastMouseMove).total!"msecs";
153 if (msc >= MouseHideTime+500) return 0.0f;
154 if (msc < MouseHideTime) return 1.0f;
155 msc -= MouseHideTime;
156 return 1.0f-msc/500.0f;
157 } else {
158 return 1.0f;
163 //==========================================================================
165 // repostHideMouse
167 // returns `true` if mouse should be redrawn
169 //==========================================================================
170 public bool repostHideMouse () {
171 if (vbwin is null || vbwin.eventQueued!HideMouseEvent) return false;
172 if (MouseHideTime > 0) {
173 auto ctt = MonoTime.currTime;
174 auto tms = (ctt-lastMouseMove).total!"msecs";
175 if (tms >= MouseHideTime) {
176 if (tms >= MouseHideTime+500) return true; // hide it
177 vbwin.postTimeout(evHideMouse, 50);
178 return true; // fade it
180 vbwin.postTimeout(evHideMouse, cast(int)(MouseHideTime-tms));
182 return false;
186 //==========================================================================
188 // egraMouseMoved
190 //==========================================================================
191 public void egraMouseMoved () {
192 if (MouseHideTime > 0) {
193 lastMouseMove = MonoTime.currTime;
194 if (vbwin !is null && !vbwin.eventQueued!HideMouseEvent) vbwin.postTimeout(evHideMouse, MouseHideTime);
196 if (vArrowTextureId && isMouseVisible) postScreenRepaint();
200 // ////////////////////////////////////////////////////////////////////////// //
201 private __gshared SubWindow subwinLast;
202 private __gshared bool ignoreSubWinChar = false;
203 // make a package and move that to package
204 private __gshared SubWindow subwinDrag = null;
205 private __gshared int subwinDragXSpot, subwinDragYSpot;
206 private __gshared SubWindow lastHoverWindow = null;
209 public @property bool isSubWinDragging () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null); }
210 public @property bool isSubWinDraggingKeyboard () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null && subwinDragXSpot == int.min && subwinDragYSpot == int.min); }
213 //==========================================================================
215 // eguiLostGlobalFocus
217 //==========================================================================
218 public void eguiLostGlobalFocus () {
219 ignoreSubWinChar = false;
220 subwinDrag = null;
221 SubWindow aw = getActiveSubWindow();
222 if (aw !is null) aw.releaseWidgetGrab();
223 lastMouseButton = 0;
227 private bool insertSubWindow (SubWindow nw) {
228 if (nw is null || nw.mClosed) return false;
229 assert(nw.mPrev is null);
230 assert(nw.mNext is null);
231 assert(!nw.mInWinList);
232 lastHoverWindow = null; // just in case
233 nw.releaseWidgetGrab();
234 SubWindow law = getActiveSubWindow();
235 if (nw.mType == SubWindow.Type.OnBottom) {
236 SubWindow w = subwinLast;
237 if (w !is null) {
238 while (w.mPrev !is null) w = w.mPrev;
239 nw.mNext = w;
240 if (w !is null) w.mPrev = nw; else subwinLast = nw;
241 } else {
242 subwinLast = nw;
244 nw.mInWinList = true;
245 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
246 return true;
248 SubWindow aw = getActiveSubWindow();
249 assert(aw !is nw);
250 if (nw.mType == SubWindow.Type.OnTop || aw is null) {
251 nw.mPrev = subwinLast;
252 if (subwinLast !is null) subwinLast.mNext = nw;
253 subwinLast = nw;
254 nw.mInWinList = true;
255 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
256 return true;
258 if (aw.mModal && !nw.mModal) return false; // can't insert normal windows while modal window is active
259 // insert after aw
260 nw.mPrev = aw;
261 nw.mNext = aw.mNext;
262 aw.mNext = nw;
263 if (nw.mNext !is null) nw.mNext.mPrev = nw;
264 if (aw is subwinLast) subwinLast = nw;
265 nw.mInWinList = true;
266 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
267 return true;
271 private bool removeSubWindow (SubWindow nw) {
272 if (nw is null || !nw.mInWinList) return false;
273 lastHoverWindow = null; // just in case
274 nw.releaseWidgetGrab();
275 SubWindow law = getActiveSubWindow();
276 if (nw.mPrev !is null) nw.mPrev.mNext = nw.mNext;
277 if (nw.mNext !is null) nw.mNext.mPrev = nw.mPrev;
278 if (nw is subwinLast) subwinLast = nw.mPrev;
279 nw.mPrev = null;
280 nw.mNext = null;
281 nw.mInWinList = false;
282 if (law !is null && law !is getActiveSubWindow()) law.releaseWidgetGrab();
283 return true;
287 //==========================================================================
289 // mouse2xy
291 //==========================================================================
292 public void mouse2xy (MouseEvent event, out int mx, out int my) nothrow @trusted @nogc {
293 mx = event.x/screenEffScale;
294 my = event.y/screenEffScale;
298 //==========================================================================
300 // subWindowAt
302 //==========================================================================
303 public SubWindow subWindowAt (in GxPoint p) nothrow {
304 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
305 if (!w.closed) {
306 if (w.minimised) {
307 if (p.x >= w.winminx && p.y >= w.winminy && p.x < w.winminx+w.MinSizeX && p.y < w.winminy+w.MinSizeY) return w;
308 } else {
309 if (p.inside(w.winrect)) return w;
313 return null;
317 public SubWindow subWindowAt (int mx, int my) nothrow @trusted { return subWindowAt(GxPoint(mx, my)); }
320 //==========================================================================
322 // subWindowAt
324 //==========================================================================
325 public SubWindow subWindowAt (MouseEvent event) nothrow @trusted {
326 pragma(inline, true);
327 return subWindowAt(event.x/screenEffScale, event.y/screenEffScale);
331 //==========================================================================
333 // getActiveSubWindow
335 //==========================================================================
336 public SubWindow getActiveSubWindow () nothrow @trusted @nogc {
337 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
338 if (!w.mClosed && w.type != SubWindow.Type.OnTop && !w.minimised) return w;
340 return null;
344 //==========================================================================
346 // dispatchEvent
348 //==========================================================================
349 public bool dispatchEvent (KeyEvent event) {
350 bool res = false;
351 if (isSubWinDragging) {
352 if (isSubWinDraggingKeyboard) {
353 if (!event.pressed) return true;
354 if (event == "Left") subwinDrag.x0 = subwinDrag.x0-1;
355 else if (event == "Right") subwinDrag.x0 = subwinDrag.x0+1;
356 else if (event == "Up") subwinDrag.y0 = subwinDrag.y0-1;
357 else if (event == "Down") subwinDrag.y0 = subwinDrag.y0+1;
358 else if (event == "C-Left") subwinDrag.x0 = subwinDrag.x0-8;
359 else if (event == "C-Right") subwinDrag.x0 = subwinDrag.x0+8;
360 else if (event == "C-Up") subwinDrag.y0 = subwinDrag.y0-8;
361 else if (event == "C-Down") subwinDrag.y0 = subwinDrag.y0+8;
362 else if (event == "Home") subwinDrag.x0 = 0;
363 else if (event == "End") subwinDrag.x0 = screenWidth-subwinDrag.width;
364 else if (event == "PageUp") subwinDrag.y0 = 0;
365 else if (event == "PageDown") subwinDrag.y0 = screenHeight-subwinDrag.height;
366 else if (event == "Escape" || event == "Enter") subwinDrag = null;
367 postScreenRebuild();
368 return true;
370 } else if (auto aw = getActiveSubWindow()) {
371 res = aw.onKeyEvent(event);
373 return res;
377 //==========================================================================
379 // dispatchEvent
381 //==========================================================================
382 public bool dispatchEvent (MouseEvent event) {
383 scope(exit) {
384 if (event.type == MouseEventType.buttonPressed) lastMouseButton |= cast(uint)event.button;
385 else if (event.type == MouseEventType.buttonReleased) lastMouseButton &= ~cast(uint)event.button;
388 if (subwinLast is null) {
389 if (lastHoverWindow !is null) {
390 lastHoverWindow = null;
391 postScreenRebuild();
393 return false;
396 int mx = lastMouseX;
397 int my = lastMouseY;
399 // drag
400 if (isSubWinDragging) {
401 assert(subwinDrag !is null);
402 lastHoverWindow = subwinDrag;
403 subwinDrag.releaseWidgetGrab(); // just in case
404 if (!isSubWinDraggingKeyboard) {
405 subwinDrag.x0 = mx+subwinDragXSpot;
406 subwinDrag.y0 = my+subwinDragYSpot;
407 // stop drag?
408 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) subwinDrag = null;
409 postScreenRebuild();
411 return true;
414 SubWindow aw = getActiveSubWindow();
415 immutable bool curIsModal = (aw !is null && aw.mModal);
416 SubWindow msw = (curIsModal || aw.hasGrab ? aw : subWindowAt(event));
417 if (msw != lastHoverWindow) {
418 if ((msw !is null && msw.minimised) || (lastHoverWindow !is null && lastHoverWindow.minimised)) {
419 postScreenRebuild();
421 lastHoverWindow = msw;
424 // switch window by button press
425 if (event.type == MouseEventType.buttonReleased && msw !is aw && !curIsModal) {
426 if (msw !is null && msw.mType == SubWindow.Type.Normal) {
427 if (aw !is null) aw.releaseWidgetGrab();
428 msw.releaseWidgetGrab();
429 msw.bringToFront();
430 return true;
434 if (msw is null || msw !is aw || msw.minimised) {
435 if (msw is null || !msw.onTop) {
436 return false;
439 assert(msw !is null);
441 if (msw.onMouseEvent(event)) {
442 egraMouseMoved();
443 return true;
446 egraMouseMoved();
447 return false;
451 //==========================================================================
453 // dispatchEvent
455 //==========================================================================
456 public bool dispatchEvent (dchar ch) {
457 if (ignoreSubWinChar) { ignoreSubWinChar = false; return (subwinLast !is null); }
458 bool res = false;
459 if (!isSubWinDragging) {
460 if (auto aw = getActiveSubWindow()) {
461 res = aw.onCharEvent(ch);
464 return res;
468 //==========================================================================
470 // paintSubWindows
472 //==========================================================================
473 public void paintSubWindows () {
474 // get first window
475 SubWindow firstWin = subwinLast;
476 if (firstWin is null) return;
477 while (firstWin.mPrev !is null) firstWin = firstWin.mPrev;
479 SubWindow firstMin, firstNormal, firstTop;
480 SubWindow lastMin, lastNormal, lastTop;
482 //gxClipReset();
484 void doDraw (SubWindow w) {
485 if (w !is null) {
486 gxClipReset();
487 w.onPaint();
488 if (w is subwinDrag) w.drawDragRect();
492 // paint background windows
493 for (SubWindow w = firstWin; w !is null; w = w.mNext) {
494 if (w.mClosed) continue;
495 if (w.minimised) {
496 if (firstMin is null) firstMin = w;
497 lastMin = w;
498 } else if (w.mType == SubWindow.Type.Normal) {
499 if (firstNormal is null) firstNormal = w;
500 lastNormal = w;
501 } else if (w.mType == SubWindow.Type.OnTop) {
502 if (firstTop is null) firstTop = w;
503 lastTop = w;
504 } else if (w.mType == SubWindow.Type.OnBottom) {
505 doDraw(w);
509 // paint minimised windows
510 for (SubWindow w = firstMin; w !is null; w = w.mNext) {
511 if (!w.mClosed && w.minimised) doDraw(w);
512 if (w is lastMin) break;
515 // paint normal windows
516 for (SubWindow w = firstNormal; w !is null; w = w.mNext) {
517 if (!w.mClosed && !w.minimised && w.mType == SubWindow.Type.Normal) doDraw(w);
518 if (w is lastNormal) break;
521 // paint ontop windows
522 for (SubWindow w = firstTop; w !is null; w = w.mNext) {
523 if (!w.mClosed && !w.minimised && w.mType == SubWindow.Type.OnTop) doDraw(w);
524 if (w is lastTop) break;
527 // paint hint for minimised window
528 if (auto msw = /*subWindowAt(lastMouseX, lastMouseY)*/lastHoverWindow) {
529 if (!msw.mClosed && msw.minimised && msw.mWinTitle.length) {
530 auto wdt = gxTextWidthUtf(msw.mWinTitle)+2;
531 auto hgt = gxTextHeightUtf+2;
532 int y = msw.winminy-hgt;
533 int x;
534 if (wdt >= screenWidth) {
535 x = (screenWidth-wdt)/2;
536 } else {
537 x = (msw.winminx+msw.MinSizeX)/2-wdt/2;
538 if (x < 0) x = 0;
540 gxClipReset();
541 gxFillRect(x, y, wdt, hgt, gxRGB!(255, 255, 255));
542 gxDrawTextUtf(x+1, y+1, msw.mWinTitle, gxRGB!(0, 0, 0));
548 // ////////////////////////////////////////////////////////////////////////// //
549 public class SubWindow : EgraStyledClass {
550 protected:
551 enum Type {
552 Normal,
553 OnTop,
554 OnBottom,
557 protected:
558 SubWindow mPrev, mNext;
559 Type mType = Type.Normal;
560 bool mMinimised;
561 bool mInWinList;
562 bool mModal;
563 bool mClosed;
564 bool mAllowMinimise = true;
565 bool mAllowDragMove = true;
566 bool mNoTitle = false;
567 RootWidget mRoot;
569 protected:
570 int winminx, winminy;
571 GxRect winrect;
572 dynstring mWinTitle;
574 public:
575 GxSize minWinSize;
576 GxSize maxWinSize;
578 public:
579 override bool isMyModifier (const(char)[] str) nothrow @trusted @nogc {
580 if (str.length == 0) return !active;
581 if (mMinimised) return false;
582 if (strEquCI(str, "focused")) return active;
583 return false;
586 override bool isMyStyleClass (const(char)[] str) nothrow @trusted @nogc {
587 if (mMinimised && str.strEquCI("minimised")) return true;
588 return super.isMyStyleClass(str);
591 override string getCurrentMod () nothrow @trusted @nogc {
592 return (mMinimised ? "" : active ? "focused" : "");
595 override EgraStyledClass getParent () nothrow @trusted @nogc { return null; }
596 override EgraStyledClass getFirstChild () nothrow @trusted @nogc { return mRoot; }
598 override bool isMyClassName (const(char)[] str) nothrow @trusted @nogc {
599 if (str.length == 0) return true;
600 // sorry for this cast
601 for (TypeInfo_Class ti = cast(TypeInfo_Class)typeid(this); ti !is null; ti = ti.base) {
602 if (str.strEquCI(classShortName(ti))) return true;
604 return false;
607 final T querySelector(T:EgraStyledClass=Widget) (const(char)[] sel) { pragma(inline, true); return querySelectorInternal!T(sel); }
608 final auto querySelectorAll(T:EgraStyledClass=Widget) (const(char)[] sel) nothrow @safe @nogc { pragma(inline, true); return Iter!T(this, sel); }
610 protected:
611 void createRoot () {
612 if (mRoot is null) setRoot(new RootWidget(this)); else fixRoot();
615 void fixRoot () {
616 if (mRoot) {
617 mRoot.rect.pos = GxPoint(0, 0);
618 mRoot.rect.size = GxSize(clientWidth, clientHeight);
622 void finishConstruction () {
623 winrect.size.sanitize();
624 createRoot();
625 createStyle();
626 mRoot.enter(&createWidgets);
627 finishCreating();
630 protected:
631 void setTitleFrom(T:const(char)[]) (T atitle) {
632 static if (is(T == typeof(null))) {
633 mWinTitle.clear();
634 mNoTitle = true;
635 } else {
636 mWinTitle = atitle;
637 mNoTitle = false;
641 public:
642 this () {
643 winrect.pos = GxPoint(0, 0);
644 winrect.size = GxSize(0, 0);
645 finishConstruction();
648 this(T:const(char)[]) (T atitle) {
649 setTitleFrom(atitle);
650 winrect.pos = GxPoint(0, 0);
651 winrect.size = GxSize(0, 0);
652 finishConstruction();
655 this(T:const(char)[]) (T atitle, in GxPoint apos, in GxSize asize) {
656 setTitleFrom(atitle);
657 winrect.pos = apos;
658 winrect.size = asize;
659 finishConstruction();
662 this(T:const(char)[]) (T atitle, in GxSize asize) {
663 setTitleFrom(atitle);
664 winrect.size = asize;
665 winrect.size.sanitize();
666 winrect.pos.x = (screenWidth-winrect.width)/2;
667 winrect.pos.y = (screenHeight-winrect.height)/2;
668 finishConstruction();
671 bool hasGrab () {
672 return (mRoot !is null ? mRoot.hasGrab() : false);
675 override void widgetChanged () nothrow {
676 if (mInWinList && !minimised) {
677 try {
678 postScreenRebuild();
679 } catch (Exception e) {
680 // sorry
685 // this doesn't perform relayouting
686 void setRoot (RootWidget w) {
687 if (mRoot !is w) {
688 mRoot = w;
689 fixRoot();
690 widgetChanged();
694 // this is called from constructor
695 void createStyle () {
698 // this is called from constructor
699 void createWidgets () {
702 // this is called from constructor
703 // you can call `addModal()` here, for example
704 void finishCreating () {
705 add();
708 void relayout (bool resizeWindow) {
709 if (mRoot is null) return;
711 FuiFlexLayouter!Widget lay;
713 lay.isValidBoxId = delegate bool (Widget id) { return (id !is null); };
715 lay.firstChild = delegate Widget (Widget id) { return id.firstChild; };
716 lay.nextSibling = delegate Widget (Widget id) { return id.nextSibling; };
718 lay.getMinSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.minSize.w : id.minSize.h); };
719 lay.getMaxSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.maxSize.w : id.maxSize.h); };
720 lay.getPrefSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.prefSize.w : id.prefSize.h); };
722 lay.isHorizBox = delegate bool (Widget id) { return (id.childDir == GxDir.Horiz); };
724 lay.getFlex = delegate int (Widget id) { return id.flex; };
726 lay.getSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.boxsize.w : id.boxsize.h); };
727 lay.setSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.boxsize.w = val; else id.boxsize.h = val; };
729 lay.getFinalSize = delegate int (Widget id, in bool horiz) { return (horiz ? id.finalSize.w : id.finalSize.h); };
730 lay.setFinalSize = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalSize.w = val; else id.finalSize.h = val; };
732 lay.setFinalPos = delegate void (Widget id, in bool horiz, int val) { if (horiz) id.finalPos.x = val; else id.finalPos.y = val; };
734 if (winrect.size.empty) {
735 if (maxWinSize.empty) maxWinSize = GxSize(screenWidth-decorationSizeX, screenHeight-decorationSizeY);
736 resizeWindow = true;
739 if (winrect.size.w > screenWidth) winrect.size.w = screenWidth;
740 if (winrect.size.h > screenHeight) winrect.size.h = screenHeight;
742 if (resizeWindow) {
743 mRoot.maxSize = maxWinSize;
744 } else {
745 mRoot.maxSize = winrect.size-GxSize(decorationSizeX, decorationSizeY);
746 if (mRoot.maxSize.w <= 0) mRoot.maxSize.w = maxWinSize.w;
747 if (mRoot.maxSize.h <= 0) mRoot.maxSize.h = maxWinSize.h;
750 mRoot.minSize = minWinSize;
751 if (mRoot.minSize.w > screenWidth-decorationSizeX) mRoot.minSize.w = screenWidth-decorationSizeX;
752 if (mRoot.minSize.h > screenHeight-decorationSizeY) mRoot.minSize.h = screenHeight-decorationSizeY;
754 if (!mNoTitle && mWinTitle.length) {
755 if (mRoot.boxsize.w <= 0) mRoot.boxsize.w = gxTextWidthUtf(mWinTitle)+2;
757 mRoot.prefSize = mRoot.boxsize;
759 Widget[] hsizes;
760 Widget[] vsizes;
761 scope(exit) {
762 hsizes.unsafeArrayClear();
763 delete hsizes;
764 vsizes.unsafeArrayClear();
765 delete vsizes;
768 foreach (Widget w; mRoot.allDepth) {
769 w.preLayout();
770 // hsize
771 w.hsizeIdNext = null;
772 if (w.hsizeId.length) {
773 foreach (ref Widget nw; hsizes) {
774 if (nw.hsizeId == w.hsizeId) {
775 w.hsizeIdNext = nw;
776 nw = w;
777 break;
780 if (w.hsizeIdNext is null) hsizes.unsafeArrayAppend(w);
782 // vsize
783 w.vsizeIdNext = null;
784 if (w.vsizeId.length) {
785 foreach (ref Widget nw; vsizes) {
786 if (nw.vsizeId == w.vsizeId) {
787 w.vsizeIdNext = nw;
788 nw = w;
789 break;
792 if (w.vsizeIdNext is null) vsizes.unsafeArrayAppend(w);
796 // fix horiz pref sizes
797 foreach (Widget sw; hsizes) {
798 int vmax = 0;
799 for (Widget w = sw; w !is null; w = w.hsizeIdNext) if (vmax < w.prefSize.w) vmax = w.prefSize.w;
800 for (Widget w = sw; w !is null; w = w.hsizeIdNext) w.prefSize.w = vmax;
803 // fix vert pref sizes
804 foreach (Widget sw; vsizes) {
805 int vmax = 0;
806 for (Widget w = sw; w !is null; w = w.vsizeIdNext) if (vmax < w.prefSize.h) vmax = w.prefSize.h;
807 for (Widget w = sw; w !is null; w = w.vsizeIdNext) w.prefSize.h = vmax;
810 lay.layout(mRoot);
812 winrect.size = mRoot.boxsize+GxSize(decorationSizeX, decorationSizeY);
813 if (winrect.x1 >= screenWidth) winrect.pos.x -= winrect.x1-screenWidth+1;
814 if (winrect.y1 >= screenHeight) winrect.pos.y -= winrect.y1-screenHeight+1;
815 if (winrect.pos.x < 0) winrect.pos.x = 0;
816 if (winrect.pos.y < 0) winrect.pos.y = 0;
818 foreach (Widget w; mRoot.allDepth) {
819 w.hsizeIdNext = null;
820 w.vsizeIdNext = null;
821 w.postLayout();
823 widgetChanged();
826 void relayoutResize () { relayout(true); }
828 final @property Widget rootWidget () pure nothrow @safe @nogc { pragma(inline, true); return mRoot; }
830 final @property int x0 () nothrow @safe { return (minimised ? winminx : winrect.pos.x); }
831 final @property int y0 () nothrow @safe { return (minimised ? winminy : winrect.pos.y); }
832 final @property int width () nothrow @safe { return (minimised ? MinSizeX : winrect.size.w); }
833 final @property int height () nothrow @safe { return (minimised ? MinSizeY : winrect.size.h); }
835 final @property void x0 (in int v) {
836 if (minimised) {
837 if (winminx != v) { winminx = v; if (mInWinList) postScreenRebuild(); }
838 } else {
839 if (winrect.pos.x != v) { winrect.pos.x = v; widgetChanged(); }
842 final @property void y0 (in int v) {
843 if (minimised) {
844 if (winminy != v) { winminy = v; if (mInWinList) postScreenRebuild(); }
845 } else {
846 if (winrect.pos.y != v) { winrect.pos.y = v; widgetChanged(); }
849 final @property void width (in int v) {
850 if (minimised) {
851 if (winrect.size.w != v) { winrect.size.w = v; postScreenRebuild(); }
852 } else {
853 if (winrect.size.w != v) { winrect.size.w = v; widgetChanged(); }
856 final @property void height (in int v) {
857 if (minimised) {
858 if (winrect.size.h != v) { winrect.size.h = v; postScreenRebuild(); }
859 } else {
860 if (winrect.size.h != v) { winrect.size.h = v; widgetChanged(); }
864 final void setPos (in int newx, in int newy) {
865 if (winrect.pos.x != newx || winrect.pos.y != newy) {
866 winrect.pos.x = newx;
867 winrect.pos.y = newy;
868 widgetChanged();
872 final void setSize (in int awidth, in int aheight) {
873 bool changed = false;
874 if (awidth > 0 && awidth != winrect.size.w) { changed = true; winrect.size.w = awidth; }
875 if (aheight > 0 && aheight != winrect.size.w) { changed = true; winrect.size.h = aheight; }
876 if (changed) {
877 fixRoot();
878 widgetChanged();
882 final void setClientSize (int awidth, int aheight) {
883 bool changed = false;
884 if (awidth > 0 && awidth+decorationSizeX != winrect.size.w) { changed = true; winrect.size.w = awidth+decorationSizeX; }
885 if (aheight > 0 && aheight+decorationSizeY != winrect.size.h) { changed = true; winrect.size.h = aheight+decorationSizeY; }
886 if (changed) {
887 fixRoot();
888 widgetChanged();
892 final void centerWindow () {
893 immutable int newx = (screenWidth-winrect.size.w)/2;
894 immutable int newy = (screenHeight-winrect.size.h)/2;
895 if (winrect.pos.x != newx || winrect.pos.y != newy) {
896 winrect.pos.x = newx;
897 winrect.pos.y = newy;
898 widgetChanged();
902 final @property SubWindow prev () pure nothrow @safe @nogc { return mPrev; }
903 final @property SubWindow next () pure nothrow @safe @nogc { return mNext; }
905 final @property Type type () const pure nothrow @safe @nogc { return mType; }
906 final @property bool onTop () const pure nothrow @safe @nogc { return (mType == Type.OnTop); }
907 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType == Type.OnBottom); }
909 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList; }
911 final @property bool modal () const pure nothrow @safe @nogc { return mModal; }
912 final @property bool closed () const pure nothrow @safe @nogc { return mClosed; }
914 final @property bool inWindowList () const pure nothrow @safe @nogc { return mInWinList; }
916 final @property bool active () const nothrow @trusted @nogc {
917 if (!mInWinList || mClosed || minimised) return false;
918 return (getActiveSubWindow is this);
921 @property int decorationSizeX () const nothrow @safe { return 2*2; }
922 @property int decorationSizeY () const nothrow @safe { immutable hgt = gxTextHeightUtf; return (hgt < 10 ? 10 : hgt+1)+4; }
924 @property int clientOffsetX () const nothrow @safe { return 2; }
925 @property int clientOffsetY () const nothrow @safe { immutable hgt = gxTextHeightUtf; return (hgt < 10 ? 10 : hgt+1)+2; }
927 final @property int clientWidth () const nothrow @safe { return winrect.size.w-decorationSizeX; }
928 final @property int clientHeight () const nothrow @safe { return winrect.size.h-decorationSizeY; }
930 protected void drawWidgets () {
931 setupClientClip();
932 if (mRoot !is null) mRoot.onPaint();
935 // draw window frame and background in "normal" state
936 protected void drawNormalDecoration () {
937 //immutable string act = (active ? null : "inactive");
938 gxDrawWindow(winrect, (mNoTitle ? null : mWinTitle.length ? mWinTitle.getData : ""),
939 getColor("frame"),
940 getColor("title-text"),
941 getColor("title-back"),
942 getColor("back"),
943 getColor("shadow-color"),
944 getInt("shadow-size", 0),
945 (getInt("shadow-dash", 0) > 0));
948 // draw window frame and background in "minimised" state
949 protected void drawWindowMinimised () {
950 //immutable string act = (active ? null : "inactive");
951 gxFillRect(winminx, winminy, MinSizeX, MinSizeY, getColor("back"));
952 gxDrawRect(winminx, winminy, MinSizeX, MinSizeY, getColor("frame"));
955 // this changes clip
956 protected void drawDecoration () {
957 if (minimised) {
958 if (gxClipRect.intersect(GxRect(winminx, winminy, MinSizeX, MinSizeY))) drawWindowMinimised();
959 } else {
960 setupClip();
961 drawNormalDecoration();
965 protected void drawDragRect () {
966 immutable uint clr = getColor("drag-overlay-back");
967 if (gxIsTransparent(clr)) return;
968 immutable bool dashed = (getInt("drag-overlay-dash", 0) > 0);
969 gxWithSavedClip {
970 gxClipReset();
971 if (dashed) {
972 gxDashRect(x0, y0, width, height, clr);
973 } else {
974 gxFillRect(x0, y0, width, height, clr);
980 void releaseWidgetGrab () {
981 if (mRoot !is null) mRoot.releaseGrab();
984 // event in our local coords
985 bool startMouseDrag (MouseEvent event) {
986 if (!mAllowDragMove || !mInWinList) return false;
987 releaseWidgetGrab();
988 subwinDrag = this;
989 subwinDragXSpot = -event.x;
990 subwinDragYSpot = -event.y;
991 widgetChanged();
992 return true;
995 bool startKeyboardDrag () {
996 if (!mAllowDragMove || !mInWinList) return false;
997 releaseWidgetGrab();
998 subwinDrag = this;
999 subwinDragXSpot = int.min;
1000 subwinDragYSpot = int.min;
1001 widgetChanged();
1002 return true;
1005 void stopDrag () {
1006 if (subwinDrag is this) {
1007 releaseWidgetGrab();
1008 subwinDrag = null;
1009 widgetChanged();
1013 void onPaint () {
1014 if (closed) return;
1015 gxWithSavedClip {
1016 drawDecoration();
1017 if (!minimised) drawWidgets();
1022 bool onKeySink (KeyEvent event) {
1023 return false;
1026 bool onKeyBubble (KeyEvent event) {
1027 // global window hotkeys
1028 if (event.pressed) {
1029 if (event == "C-F5") { if (startKeyboardDrag()) return true; }
1030 if (/*event == "M-M" ||*/ event == "M-S-M") { if (minimise()) return true; }
1032 return false;
1035 bool onKeyEvent (KeyEvent event) {
1036 if (closed) return false;
1037 if (minimised) return false;
1038 if (onKeySink(event)) return true;
1039 if (mRoot !is null && mRoot.dispatchKey(event)) return true;
1040 return onKeyBubble(event);
1044 bool onMouseSink (MouseEvent event) {
1045 // start drag?
1046 if (subwinDrag is null && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
1047 if (event.x >= 0 && event.y >= 0 &&
1048 event.x < width && event.y < (!minimised ? gxTextHeightUtf+2 : height))
1050 startMouseDrag(event);
1051 return true;
1055 if (minimised) return false;
1057 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.right) {
1058 if (event.x >= 0 && event.y >= 0 &&
1059 event.x < winrect.size.w && event.y < gxTextHeightUtf)
1061 minimise();
1062 return true;
1066 return false;
1069 bool onMouseBubble (MouseEvent event) {
1070 return false;
1073 bool onMouseEvent (MouseEvent event) {
1074 if (!active) return false;
1075 if (closed) return false;
1077 int mx, my;
1078 event.mouse2xy(mx, my);
1080 MouseEvent ev = event;
1081 ev.x = mx-x0;
1082 ev.y = my-y0;
1083 if (onMouseSink(ev)) return true;
1085 if (mRoot !is null) {
1086 ev = event;
1087 ev.x = mx-(x0+clientOffsetX)-mRoot.rect.x0;
1088 ev.y = my-(y0+clientOffsetY)-mRoot.rect.y0;
1089 if (mRoot.dispatchMouse(ev)) return true;
1092 ev = event;
1093 ev.x = mx-x0;
1094 ev.y = my-y0;
1095 return onMouseBubble(ev);
1099 bool onCharSink (dchar ch) {
1100 return false;
1103 bool onCharBubble (dchar ch) {
1104 return false;
1107 bool onCharEvent (dchar ch) {
1108 if (!active) return false;
1109 if (closed) return false;
1110 if (minimised) return false;
1111 if (onCharSink(ch)) return true;
1112 if (mRoot !is null && mRoot.dispatchChar(ch)) return true;
1113 return onCharBubble(ch);
1116 void setupClip () {
1117 gxClipRect.intersect(winrect);
1120 final void setupClientClip () {
1121 setupClip();
1122 gxClipRect.intersect(GxRect(
1123 GxPoint(winrect.pos.x+clientOffsetX, winrect.pos.y+clientOffsetY),
1124 GxPoint(winrect.pos.x+clientOffsetX+clientWidth-1, winrect.pos.y+clientOffsetY+clientHeight-1)));
1127 void close () {
1128 if (!mClosed) {
1129 mClosed = true;
1130 if (removeSubWindow(this)) postScreenRebuild();
1134 protected bool addToSubwinList (bool asModal, bool fromKeyboard) {
1135 if (fromKeyboard) ignoreSubWinChar = true;
1136 if (mInWinList) return true;
1137 mModal = asModal;
1138 if (insertSubWindow(this)) {
1139 widgetChanged();
1140 postScreenRebuild();
1141 return true;
1143 return false;
1146 void add (bool fromKeyboard=false) { addToSubwinList(false, fromKeyboard); }
1148 void addModal (bool fromKeyboard=false) { addToSubwinList(true, fromKeyboard); }
1150 // return `false` to reject
1151 protected bool minimiseQuery () {
1152 return true;
1155 // return `false` to reject (this is "unminimise")
1156 protected bool restoreQuery () {
1157 return true;
1160 void bringToFront () {
1161 if (mClosed || !mInWinList) return;
1162 if (minimised && !restoreQuery()) return;
1163 auto aw = getActiveSubWindow();
1164 if (aw is this) {
1165 if (minimised) {
1166 mMinimised = false;
1167 lastHoverWindow = null;
1168 widgetChanged();
1170 return;
1172 if (aw !is null && aw.mModal) return; // alas
1173 removeSubWindow(this);
1174 mMinimised = false;
1175 insertSubWindow(this);
1176 if (subwinDrag !is this) subwinDrag = null;
1177 widgetChanged();
1180 final @property bool allowMinimise () pure const nothrow @safe @nogc { return mAllowMinimise; }
1181 @property void allowMinimise (in bool v) { mAllowMinimise = v; }
1183 final @property bool allowDragMove () pure const nothrow @safe @nogc { return mAllowDragMove; }
1184 @property void allowDragMove (in bool v) { mAllowDragMove = v; }
1186 final @property bool minimised () pure const nothrow @safe @nogc { return mMinimised; }
1187 @property void minimised (in bool v) {
1188 if (v == mMinimised) return;
1189 if (!mAllowMinimise && v) return;
1190 if (v) minimise(); else bringToFront();
1193 bool minimise () {
1194 if (minimised) return true;
1195 if (mClosed || !mAllowMinimise || mModal) return false;
1196 if (!mInWinList) {
1197 if (minimiseQuery()) mMinimised = true;
1198 return true;
1200 assert(subwinLast !is null);
1201 releaseWidgetGrab();
1202 findMinimisedPos(winminx, winminy);
1203 auto aw = getActiveSubWindow();
1204 if (aw is this) subwinDrag = null;
1205 mMinimised = true;
1206 lastHoverWindow = null;
1207 postScreenRebuild(); // because minimised widgets will not post this
1208 return true;
1211 void restore () {
1212 releaseWidgetGrab();
1213 bringToFront();
1216 public:
1217 static T clampval(T) (in T val, in T min, in T max) pure nothrow @safe @nogc {
1218 pragma(inline, true);
1219 return (val < min ? min : val > max ? max : val);
1222 int MinSizeX () nothrow @safe { immutable bool omm = mMinimised; mMinimised = true; scope(exit) mMinimised = omm; return clampval(getInt("icon-size-x", 16), 16, 64); }
1223 int MinSizeY () nothrow @safe { immutable bool omm = mMinimised; mMinimised = true; scope(exit) mMinimised = omm; return clampval(getInt("icon-size-y", 16), 16, 64); }
1225 int MinMarginX () nothrow @safe { immutable bool omm = mMinimised; mMinimised = true; scope(exit) mMinimised = omm; return clampval(getInt("icon-margin-x", 3), 1, 16); }
1226 int MinMarginY () nothrow @safe { immutable bool omm = mMinimised; mMinimised = true; scope(exit) mMinimised = omm; return clampval(getInt("icon-margin-y", 3), 1, 16); }
1228 protected:
1229 void findMinimisedPos (out int wx, out int wy) {
1230 static bool isOccupied (int x, int y) {
1231 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
1232 if (w.mInWinList && !w.closed && w.minimised) {
1233 if (x >= w.winminx && y >= w.winminy && x < w.winminx+w.MinSizeX && y < w.winminy+w.MinSizeY) return true;
1236 return false;
1239 int txcount = screenWidth/(MinSizeX+MinMarginX);
1240 //int tycount = screenHeight/(MinSizeY+MinMarginY);
1241 if (txcount < 1) txcount = 1;
1242 //if (tycount < 1) tycount = 1;
1243 foreach (immutable int n; 0..6/*5535*/) {
1244 int x = (n%txcount)*(MinSizeX+MinMarginX)+1;
1245 int y = screenHeight-MinSizeY-(n/txcount)*(MinSizeY+MinMarginY);
1246 //conwriteln("trying (", x, ",", y, ")");
1247 if (!isOccupied(x, y)) { wx = x; wy = y; /*conwriteln(" HIT!");*/ return; }