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*/;
22 import arsd
.simpledisplay
;
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",
96 delegate (self
, uint nv
) {
97 if (MouseHideTime
!= nv
) {
98 if (MouseHideTime
== 0) mouseMoved();
100 conwriteln("mouse hiding time: ", nv
);
107 //==========================================================================
111 //==========================================================================
112 public int mshtime_dbg () {
113 if (MouseHideTime
> 0) {
114 auto ctt
= MonoTime
.currTime
;
115 auto mt
= (ctt
-lastMouseMove
).total
!"msecs";
123 //==========================================================================
127 //==========================================================================
128 public bool isMouseVisible () {
129 if (MouseHideTime
> 0) {
130 auto ctt
= MonoTime
.currTime
;
131 return ((ctt
-lastMouseMove
).total
!"msecs" < MouseHideTime
+500);
138 //==========================================================================
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;
157 //==========================================================================
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
));
180 //==========================================================================
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 //==========================================================================
197 //==========================================================================
198 public void drawTextCursor (bool active
, int x
, int y
, int hgt
=-666) {
199 if (hgt
== -666) hgt
= gxTextHeightUtf
;
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));
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;
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
;
249 while (w
.mPrev
!is null) w
= w
.mPrev
;
251 if (w
!is null) w
.mPrev
= nw
; else subwinLast
= nw
;
255 nw
.mInWinList
= true;
256 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
259 SubWindow aw
= getActiveSubWindow();
261 if (nw
.mType
== SubWindow
.Type
.OnTop || aw
is null) {
262 nw
.mPrev
= subwinLast
;
263 if (subwinLast
!is null) subwinLast
.mNext
= nw
;
265 nw
.mInWinList
= true;
266 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
269 if (aw
.mModal
&& !nw
.mModal
) return false; // can't insert normal windows while modal window is active
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();
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
;
291 nw
.mInWinList
= false;
292 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
297 //==========================================================================
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 //==========================================================================
312 //==========================================================================
313 public SubWindow
subWindowAt (in GxPoint p
) nothrow @trusted @nogc {
314 for (SubWindow w
= subwinLast
; w
!is null; w
= w
.mPrev
) {
317 if (p
.x
>= w
.winminx
&& p
.y
>= w
.winminy
&& p
.x
< w
.winminx
+w
.MinSizeX
&& p
.y
< w
.winminy
+w
.MinSizeY
) return w
;
319 if (p
.inside(w
.winrect
)) return w
;
327 public SubWindow
subWindowAt (int mx
, int my
) nothrow @trusted @nogc { return subWindowAt(GxPoint(mx
, my
)); }
330 //==========================================================================
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
;
354 //==========================================================================
358 //==========================================================================
359 public bool dispatchEvent (KeyEvent event
) {
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;
380 } else if (auto aw
= getActiveSubWindow()) {
381 res
= aw
.onKeyEvent(event
);
382 if (res
) postScreenRebuild();
388 //==========================================================================
392 //==========================================================================
393 public bool dispatchEvent (MouseEvent event
) {
394 __gshared SubWindow lastHover
= null;
396 if (subwinLast
is null) { postScreenRepaint(); return false; }
400 auto aw
= getActiveSubWindow();
401 auto msw
= subWindowAt(event
);
403 if (msw
!is lastHover
) {
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();
422 if (isSubWinDragging
) {
423 subwinDrag
.releaseWidgetGrab(); // just in case
424 if (!isSubWinDraggingKeyboard
) {
425 subwinDrag
.x0
= mx
+subwinDragXSpot
;
426 subwinDrag
.y0
= my
+subwinDragYSpot
;
428 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) subwinDrag
= null;
436 if (msw
is null || msw
!is aw || msw
.mMinimized
) {
437 if (msw
is null ||
!msw
.onTop
) {
442 assert(msw
!is null);
444 if (msw
.onMouseEvent(event
)) {
454 //==========================================================================
458 //==========================================================================
459 public bool dispatchEvent (dchar ch
) {
460 if (ignoreSubWinChar
) { ignoreSubWinChar
= false; return (subwinLast
!is null); }
462 if (!isSubWinDragging
) {
463 if (auto aw
= getActiveSubWindow()) {
464 res
= aw
.onCharEvent(ch
);
465 if (res
) postScreenRebuild();
472 //==========================================================================
476 //==========================================================================
477 public void paintSubWindows () {
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
;
488 void doDraw (SubWindow w
) {
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;
500 if (firstMin
is null) firstMin
= w
;
502 } else if (w
.mType
== SubWindow
.Type
.Normal
) {
503 if (firstNormal
is null) firstNormal
= w
;
505 } else if (w
.mType
== SubWindow
.Type
.OnTop
) {
506 if (firstTop
is null) firstTop
= w
;
508 } else if (w
.mType
== SubWindow
.Type
.OnBottom
) {
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
;
538 if (wdt
>= screenWidth
) {
539 x
= (screenWidth
-wdt
)/2;
541 x
= (msw
.winminx
+msw
.MinSizeX
)/2-wdt
/2;
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
{
562 SubWindow mPrev
, mNext
;
563 Type mType
= Type
.Normal
;
568 int awidx
; // active widget index
571 ColorStyle colorStyle
;
574 int winminx
, winminy
;
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"));
600 if (mRoot
is null) setRoot(new RootWidget(this)); else fixRoot();
605 mRoot
.rect
.pos
= GxPoint(0, 0);
606 mRoot
.rect
.size
= GxSize(clientWidth
, clientHeight
);
610 void setStyle (ColorStyle stl
) {
614 void finishConstruction () {
615 winrect
.size
.sanitize();
624 winrect
.pos
= GxPoint(0, 0);
625 winrect
.size
= GxSize(0, 0);
626 finishConstruction();
629 this (string 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
) {
639 winrect
.size
= asize
;
640 finishConstruction();
643 this (string atitle
, in GxSize asize
) {
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
) {
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 () {
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
);
703 if (winrect
.size
.w
> screenWidth
) winrect
.size
.w
= screenWidth
;
704 if (winrect
.size
.h
> screenHeight
) winrect
.size
.h
= screenHeight
;
707 mRoot
.maxSize
= maxWinSize
;
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
;
719 if (mRoot
.boxsize
.w
<= 0) mRoot
.boxsize
.w
= gxTextWidthUtf(title
)+2;
721 mRoot
.prefSize
= mRoot
.boxsize
;
723 mRoot
.forEachDepth((Widget w
) { w
.preLayout(); });
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 () {
806 if (mRoot
!is null) mRoot
.onPaint();
809 // draw window frame and background in "normal" state
810 protected void drawWindowNormal () {
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
) {
839 subwinDragXSpot
= -event
.x
;
840 subwinDragYSpot
= -event
.y
;
844 void startKeyboardDrag () {
847 subwinDragXSpot
= int.min
;
848 subwinDragYSpot
= int.min
;
853 if (subwinDrag
is this) {
867 drawWindowMinimized();
873 bool onKeySink (KeyEvent event
) {
877 bool onKeyBubble (KeyEvent event
) {
878 // global window hotkeys
880 if (event
== "C-F5") { startKeyboardDrag(); return true; }
881 if (event
== "M-M" && !mModal
) { minimize(); return true; }
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
) {
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
);
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();
920 bool onMouseBubble (MouseEvent event
) {
924 bool onMouseEvent (MouseEvent event
) {
925 if (!active
) return false;
926 if (closed
) return false;
929 event
.mouse2xy(mx
, my
);
931 MouseEvent ev
= event
;
934 if (onMouseSink(ev
)) return true;
936 if (mRoot
!is null) {
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;
946 return onMouseBubble(ev
);
950 bool onCharSink (dchar ch
) {
954 bool onCharBubble (dchar ch
) {
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
);
968 gxClipRect
.intersect(winrect
);
971 final void setupClientClip () {
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)));
980 if (removeSubWindow(this)) postScreenRebuild();
983 protected bool addToSubwinList (bool asModal
, bool fromKeyboard
) {
984 if (fromKeyboard
) ignoreSubWinChar
= true;
985 if (mInWinList
) return true;
987 if (insertSubWindow(this)) {
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);
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();
1018 if (mClosed || mMinimized
) return;
1019 if (!mInWinList
) { mMinimized
= true; return; }
1021 assert(subwinLast
!is null);
1022 releaseWidgetGrab();
1023 findMinimizedPos(winminx
, winminy
);
1024 auto aw
= getActiveSubWindow();
1025 if (aw
is this) subwinDrag
= null;
1027 postScreenRebuild();
1031 releaseWidgetGrab();
1038 enum MinMarginX
= 3;
1039 enum MinMarginY
= 3;
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;
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; }