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*/;
24 import arsd
.simpledisplay
;
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",
100 return MouseHideTime
;
102 delegate (self
, uint nv
) {
103 if (MouseHideTime
!= nv
) {
104 if (MouseHideTime
== 0) egraMouseMoved();
106 conwriteln("mouse hiding time: ", nv
);
113 //==========================================================================
117 //==========================================================================
118 public int mshtime_dbg () {
119 if (MouseHideTime
> 0) {
120 auto ctt
= MonoTime
.currTime
;
121 auto mt
= (ctt
-lastMouseMove
).total
!"msecs";
129 //==========================================================================
133 //==========================================================================
134 public bool isMouseVisible () {
135 if (MouseHideTime
> 0) {
136 auto ctt
= MonoTime
.currTime
;
137 return ((ctt
-lastMouseMove
).total
!"msecs" < MouseHideTime
+500);
144 //==========================================================================
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;
163 //==========================================================================
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
));
186 //==========================================================================
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;
221 SubWindow aw
= getActiveSubWindow();
222 if (aw
!is null) aw
.releaseWidgetGrab();
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
;
238 while (w
.mPrev
!is null) w
= w
.mPrev
;
240 if (w
!is null) w
.mPrev
= nw
; else subwinLast
= nw
;
244 nw
.mInWinList
= true;
245 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
248 SubWindow aw
= getActiveSubWindow();
250 if (nw
.mType
== SubWindow
.Type
.OnTop || aw
is null) {
251 nw
.mPrev
= subwinLast
;
252 if (subwinLast
!is null) subwinLast
.mNext
= nw
;
254 nw
.mInWinList
= true;
255 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
258 if (aw
.mModal
&& !nw
.mModal
) return false; // can't insert normal windows while modal window is active
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();
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
;
281 nw
.mInWinList
= false;
282 if (law
!is null && law
!is getActiveSubWindow()) law
.releaseWidgetGrab();
287 //==========================================================================
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 //==========================================================================
302 //==========================================================================
303 public SubWindow
subWindowAt (in GxPoint p
) nothrow {
304 for (SubWindow w
= subwinLast
; w
!is null; w
= w
.mPrev
) {
307 if (p
.x
>= w
.winminx
&& p
.y
>= w
.winminy
&& p
.x
< w
.winminx
+w
.MinSizeX
&& p
.y
< w
.winminy
+w
.MinSizeY
) return w
;
309 if (p
.inside(w
.winrect
)) return w
;
317 public SubWindow
subWindowAt (int mx
, int my
) nothrow @trusted { return subWindowAt(GxPoint(mx
, my
)); }
320 //==========================================================================
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
;
344 //==========================================================================
348 //==========================================================================
349 public bool dispatchEvent (KeyEvent event
) {
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;
370 } else if (auto aw
= getActiveSubWindow()) {
371 res
= aw
.onKeyEvent(event
);
377 //==========================================================================
381 //==========================================================================
382 public bool dispatchEvent (MouseEvent event
) {
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;
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
;
408 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) subwinDrag
= null;
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
)) {
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();
434 if (msw
is null || msw
!is aw || msw
.minimised
) {
435 if (msw
is null ||
!msw
.onTop
) {
439 assert(msw
!is null);
441 if (msw
.onMouseEvent(event
)) {
451 //==========================================================================
455 //==========================================================================
456 public bool dispatchEvent (dchar ch
) {
457 if (ignoreSubWinChar
) { ignoreSubWinChar
= false; return (subwinLast
!is null); }
459 if (!isSubWinDragging
) {
460 if (auto aw
= getActiveSubWindow()) {
461 res
= aw
.onCharEvent(ch
);
468 //==========================================================================
472 //==========================================================================
473 public void paintSubWindows () {
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
;
484 void doDraw (SubWindow w
) {
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;
496 if (firstMin
is null) firstMin
= w
;
498 } else if (w
.mType
== SubWindow
.Type
.Normal
) {
499 if (firstNormal
is null) firstNormal
= w
;
501 } else if (w
.mType
== SubWindow
.Type
.OnTop
) {
502 if (firstTop
is null) firstTop
= w
;
504 } else if (w
.mType
== SubWindow
.Type
.OnBottom
) {
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
;
534 if (wdt
>= screenWidth
) {
535 x
= (screenWidth
-wdt
)/2;
537 x
= (msw
.winminx
+msw
.MinSizeX
)/2-wdt
/2;
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
{
558 SubWindow mPrev
, mNext
;
559 Type mType
= Type
.Normal
;
564 bool mAllowMinimise
= true;
565 bool mAllowDragMove
= true;
566 bool mNoTitle
= false;
570 int winminx
, winminy
;
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
;
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;
609 if (mRoot
is null) setRoot(new RootWidget(this)); else fixRoot();
614 mRoot
.rect
.pos
= GxPoint(0, 0);
615 mRoot
.rect
.size
= GxSize(clientWidth
, clientHeight
);
619 void finishConstruction () {
620 winrect
.size
.sanitize();
623 mRoot
.enter(&createWidgets
);
628 void setTitleFrom(T
:const(char)[]) (T atitle
) {
629 static if (is(T
== typeof(null))) {
640 winrect
.pos
= GxPoint(0, 0);
641 winrect
.size
= GxSize(0, 0);
642 finishConstruction();
645 this(T
:const(char)[]) (T atitle
) {
646 setTitleFrom(atitle
);
647 winrect
.pos
= GxPoint(0, 0);
648 winrect
.size
= GxSize(0, 0);
649 finishConstruction();
652 this(T
:const(char)[]) (T atitle
, in GxPoint apos
, in GxSize asize
) {
653 setTitleFrom(atitle
);
655 winrect
.size
= asize
;
656 finishConstruction();
659 this(T
:const(char)[]) (T atitle
, in GxSize asize
) {
660 setTitleFrom(atitle
);
661 winrect
.size
= asize
;
662 winrect
.size
.sanitize();
663 winrect
.pos
.x
= (screenWidth
-winrect
.width
)/2;
664 winrect
.pos
.y
= (screenHeight
-winrect
.height
)/2;
665 finishConstruction();
669 return (mRoot
!is null ? mRoot
.hasGrab() : false);
672 override void widgetChanged () nothrow {
673 if (mInWinList
&& !minimised
) {
676 } catch (Exception e
) {
682 // this doesn't perform relayouting
683 void setRoot (RootWidget w
) {
691 // this is called from constructor
692 void createStyle () {
695 // this is called from constructor
696 void createWidgets () {
699 // this is called from constructor
700 // you can call `addModal()` here, for example
701 void finishCreating () {
705 void relayout (bool resizeWindow
) {
706 if (mRoot
is null) return;
708 FuiFlexLayouter
!Widget lay
;
710 lay
.isValidBoxId
= delegate bool (Widget id
) { return (id
!is null); };
712 lay
.firstChild
= delegate Widget (Widget id
) { return id
.firstChild
; };
713 lay
.nextSibling
= delegate Widget (Widget id
) { return id
.nextSibling
; };
715 lay
.getMinSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.minSize
.w
: id
.minSize
.h
); };
716 lay
.getMaxSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.maxSize
.w
: id
.maxSize
.h
); };
717 lay
.getPrefSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.prefSize
.w
: id
.prefSize
.h
); };
719 lay
.isHorizBox
= delegate bool (Widget id
) { return (id
.childDir
== GxDir
.Horiz
); };
721 lay
.getFlex
= delegate int (Widget id
) { return id
.flex
; };
723 lay
.getSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.boxsize
.w
: id
.boxsize
.h
); };
724 lay
.setSize
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.boxsize
.w
= val
; else id
.boxsize
.h
= val
; };
726 lay
.getFinalSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.finalSize
.w
: id
.finalSize
.h
); };
727 lay
.setFinalSize
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.finalSize
.w
= val
; else id
.finalSize
.h
= val
; };
729 lay
.setFinalPos
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.finalPos
.x
= val
; else id
.finalPos
.y
= val
; };
731 if (winrect
.size
.empty
) {
732 if (maxWinSize
.empty
) maxWinSize
= GxSize(screenWidth
-decorationSizeX
, screenHeight
-decorationSizeY
);
736 if (winrect
.size
.w
> screenWidth
) winrect
.size
.w
= screenWidth
;
737 if (winrect
.size
.h
> screenHeight
) winrect
.size
.h
= screenHeight
;
740 mRoot
.maxSize
= maxWinSize
;
742 mRoot
.maxSize
= winrect
.size
-GxSize(decorationSizeX
, decorationSizeY
);
743 if (mRoot
.maxSize
.w
<= 0) mRoot
.maxSize
.w
= maxWinSize
.w
;
744 if (mRoot
.maxSize
.h
<= 0) mRoot
.maxSize
.h
= maxWinSize
.h
;
747 mRoot
.minSize
= minWinSize
;
748 if (mRoot
.minSize
.w
> screenWidth
-decorationSizeX
) mRoot
.minSize
.w
= screenWidth
-decorationSizeX
;
749 if (mRoot
.minSize
.h
> screenHeight
-decorationSizeY
) mRoot
.minSize
.h
= screenHeight
-decorationSizeY
;
751 if (!mNoTitle
&& mWinTitle
.length
) {
752 if (mRoot
.boxsize
.w
<= 0) mRoot
.boxsize
.w
= gxTextWidthUtf(mWinTitle
)+2;
754 mRoot
.prefSize
= mRoot
.boxsize
;
759 hsizes
.unsafeArrayClear();
761 vsizes
.unsafeArrayClear();
765 foreach (Widget w
; mRoot
.allDepth
) {
768 w
.hsizeIdNext
= null;
769 if (w
.hsizeId
.length
) {
770 foreach (ref Widget nw
; hsizes
) {
771 if (nw
.hsizeId
== w
.hsizeId
) {
777 if (w
.hsizeIdNext
is null) hsizes
.unsafeArrayAppend(w
);
780 w
.vsizeIdNext
= null;
781 if (w
.vsizeId
.length
) {
782 foreach (ref Widget nw
; vsizes
) {
783 if (nw
.vsizeId
== w
.vsizeId
) {
789 if (w
.vsizeIdNext
is null) vsizes
.unsafeArrayAppend(w
);
793 // fix horiz pref sizes
794 foreach (Widget sw
; hsizes
) {
796 for (Widget w
= sw
; w
!is null; w
= w
.hsizeIdNext
) if (vmax
< w
.prefSize
.w
) vmax
= w
.prefSize
.w
;
797 for (Widget w
= sw
; w
!is null; w
= w
.hsizeIdNext
) w
.prefSize
.w
= vmax
;
800 // fix vert pref sizes
801 foreach (Widget sw
; vsizes
) {
803 for (Widget w
= sw
; w
!is null; w
= w
.vsizeIdNext
) if (vmax
< w
.prefSize
.h
) vmax
= w
.prefSize
.h
;
804 for (Widget w
= sw
; w
!is null; w
= w
.vsizeIdNext
) w
.prefSize
.h
= vmax
;
809 winrect
.size
= mRoot
.boxsize
+GxSize(decorationSizeX
, decorationSizeY
);
810 if (winrect
.x1
>= screenWidth
) winrect
.pos
.x
-= winrect
.x1
-screenWidth
+1;
811 if (winrect
.y1
>= screenHeight
) winrect
.pos
.y
-= winrect
.y1
-screenHeight
+1;
812 if (winrect
.pos
.x
< 0) winrect
.pos
.x
= 0;
813 if (winrect
.pos
.y
< 0) winrect
.pos
.y
= 0;
815 foreach (Widget w
; mRoot
.allDepth
) {
816 w
.hsizeIdNext
= null;
817 w
.vsizeIdNext
= null;
823 void relayoutResize () { relayout(true); }
825 final @property Widget
rootWidget () pure nothrow @safe @nogc { pragma(inline
, true); return mRoot
; }
827 final @property int x0 () nothrow @safe { return (minimised ? winminx
: winrect
.pos
.x
); }
828 final @property int y0 () nothrow @safe { return (minimised ? winminy
: winrect
.pos
.y
); }
829 final @property int width () nothrow @safe { return (minimised ? MinSizeX
: winrect
.size
.w
); }
830 final @property int height () nothrow @safe { return (minimised ? MinSizeY
: winrect
.size
.h
); }
832 final @property void x0 (in int v
) {
834 if (winminx
!= v
) { winminx
= v
; if (mInWinList
) widgetChanged(); }
836 if (winrect
.pos
.x
!= v
) { winrect
.pos
.x
= v
; widgetChanged(); }
839 final @property void y0 (in int v
) {
841 if (winminy
!= v
) { winminy
= v
; if (mInWinList
) widgetChanged(); }
843 if (winrect
.pos
.y
!= v
) { winrect
.pos
.y
= v
; widgetChanged(); }
846 final @property void width (in int v
) {
850 if (winrect
.size
.w
!= v
) { winrect
.size
.w
= v
; widgetChanged(); }
853 final @property void height (in int v
) {
857 if (winrect
.size
.h
!= v
) { winrect
.size
.h
= v
; widgetChanged(); }
861 final void setPos (in int newx
, in int newy
) {
862 if (winrect
.pos
.x
!= newx || winrect
.pos
.y
!= newy
) {
863 winrect
.pos
.x
= newx
;
864 winrect
.pos
.y
= newy
;
869 final void setSize (in int awidth
, in int aheight
) {
870 bool changed
= false;
871 if (awidth
> 0 && awidth
!= winrect
.size
.w
) { changed
= true; winrect
.size
.w
= awidth
; }
872 if (aheight
> 0 && aheight
!= winrect
.size
.w
) { changed
= true; winrect
.size
.h
= aheight
; }
879 final void setClientSize (int awidth
, int aheight
) {
880 bool changed
= false;
881 if (awidth
> 0 && awidth
+decorationSizeX
!= winrect
.size
.w
) { changed
= true; winrect
.size
.w
= awidth
+decorationSizeX
; }
882 if (aheight
> 0 && aheight
+decorationSizeY
!= winrect
.size
.h
) { changed
= true; winrect
.size
.h
= aheight
+decorationSizeY
; }
889 final void centerWindow () {
890 immutable int newx
= (screenWidth
-winrect
.size
.w
)/2;
891 immutable int newy
= (screenHeight
-winrect
.size
.h
)/2;
892 if (winrect
.pos
.x
!= newx || winrect
.pos
.y
!= newy
) {
893 winrect
.pos
.x
= newx
;
894 winrect
.pos
.y
= newy
;
899 final @property SubWindow
prev () pure nothrow @safe @nogc { return mPrev
; }
900 final @property SubWindow
next () pure nothrow @safe @nogc { return mNext
; }
902 final @property Type
type () const pure nothrow @safe @nogc { return mType
; }
903 final @property bool onTop () const pure nothrow @safe @nogc { return (mType
== Type
.OnTop
); }
904 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType
== Type
.OnBottom
); }
906 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList
; }
908 final @property bool modal () const pure nothrow @safe @nogc { return mModal
; }
909 final @property bool closed () const pure nothrow @safe @nogc { return mClosed
; }
911 final @property bool inWindowList () const pure nothrow @safe @nogc { return mInWinList
; }
913 final @property bool active () const nothrow @trusted @nogc {
914 if (!mInWinList || mClosed || minimised
) return false;
915 return (getActiveSubWindow
is this);
918 @property int decorationSizeX () const nothrow @safe { return 2*2; }
919 @property int decorationSizeY () const nothrow @safe { immutable hgt
= gxTextHeightUtf
; return (hgt
< 10 ?
10 : hgt
+1)+4; }
921 @property int clientOffsetX () const nothrow @safe { return 2; }
922 @property int clientOffsetY () const nothrow @safe { immutable hgt
= gxTextHeightUtf
; return (hgt
< 10 ?
10 : hgt
+1)+2; }
924 final @property int clientWidth () const nothrow @safe { return winrect
.size
.w
-decorationSizeX
; }
925 final @property int clientHeight () const nothrow @safe { return winrect
.size
.h
-decorationSizeY
; }
927 protected void drawWidgets () {
929 if (mRoot
!is null) mRoot
.onPaint();
932 // draw window frame and background in "normal" state
933 protected void drawNormalDecoration () {
934 //immutable string act = (active ? null : "inactive");
935 gxDrawWindow(winrect
, (mNoTitle ?
null : mWinTitle
.length ? mWinTitle
.getData
: ""),
937 getColor("title-text"),
938 getColor("title-back"),
940 getColor("shadow-color"),
941 getInt("shadow-size", 0),
942 (getInt("shadow-dash", 0) > 0));
945 // draw window frame and background in "minimised" state
946 protected void drawWindowMinimised () {
947 //immutable string act = (active ? null : "inactive");
948 gxFillRect(winminx
, winminy
, MinSizeX
, MinSizeY
, getColor("back"));
949 gxDrawRect(winminx
, winminy
, MinSizeX
, MinSizeY
, getColor("frame"));
953 protected void drawDecoration () {
955 if (gxClipRect
.intersect(GxRect(winminx
, winminy
, MinSizeX
, MinSizeY
))) drawWindowMinimised();
958 drawNormalDecoration();
962 protected void drawDragRect () {
963 immutable uint clr
= getColor("drag-overlay-back");
964 if (gxIsTransparent(clr
)) return;
965 immutable bool dashed
= (getInt("drag-overlay-dash", 0) > 0);
969 gxDashRect(x0
, y0
, width
, height
, clr
);
971 gxFillRect(x0
, y0
, width
, height
, clr
);
977 void releaseWidgetGrab () {
978 if (mRoot
!is null) mRoot
.releaseGrab();
981 // event in our local coords
982 bool startMouseDrag (MouseEvent event
) {
983 if (!mAllowDragMove ||
!mInWinList
) return false;
986 subwinDragXSpot
= -event
.x
;
987 subwinDragYSpot
= -event
.y
;
992 bool startKeyboardDrag () {
993 if (!mAllowDragMove ||
!mInWinList
) return false;
996 subwinDragXSpot
= int.min
;
997 subwinDragYSpot
= int.min
;
1003 if (subwinDrag
is this) {
1004 releaseWidgetGrab();
1014 if (!minimised
) drawWidgets();
1019 bool onKeySink (KeyEvent event
) {
1023 bool onKeyBubble (KeyEvent event
) {
1024 // global window hotkeys
1025 if (event
.pressed
) {
1026 if (event
== "C-F5") { if (startKeyboardDrag()) return true; }
1027 if (/*event == "M-M" ||*/ event
== "M-S-M") { if (minimise()) return true; }
1032 bool onKeyEvent (KeyEvent event
) {
1033 if (closed
) return false;
1034 if (minimised
) return false;
1035 if (onKeySink(event
)) return true;
1036 if (mRoot
!is null && mRoot
.dispatchKey(event
)) return true;
1037 return onKeyBubble(event
);
1041 bool onMouseSink (MouseEvent event
) {
1043 if (subwinDrag
is null && event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
1044 if (event
.x
>= 0 && event
.y
>= 0 &&
1045 event
.x
< width
&& event
.y
< (!minimised ? gxTextHeightUtf
+2 : height
))
1047 startMouseDrag(event
);
1052 if (minimised
) return false;
1054 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.right
) {
1055 if (event
.x
>= 0 && event
.y
>= 0 &&
1056 event
.x
< winrect
.size
.w
&& event
.y
< gxTextHeightUtf
)
1066 bool onMouseBubble (MouseEvent event
) {
1070 bool onMouseEvent (MouseEvent event
) {
1071 if (!active
) return false;
1072 if (closed
) return false;
1075 event
.mouse2xy(mx
, my
);
1077 MouseEvent ev
= event
;
1080 if (onMouseSink(ev
)) return true;
1082 if (mRoot
!is null) {
1084 ev
.x
= mx
-(x0
+clientOffsetX
)-mRoot
.rect
.x0
;
1085 ev
.y
= my
-(y0
+clientOffsetY
)-mRoot
.rect
.y0
;
1086 if (mRoot
.dispatchMouse(ev
)) return true;
1092 return onMouseBubble(ev
);
1096 bool onCharSink (dchar ch
) {
1100 bool onCharBubble (dchar ch
) {
1104 bool onCharEvent (dchar ch
) {
1105 if (!active
) return false;
1106 if (closed
) return false;
1107 if (minimised
) return false;
1108 if (onCharSink(ch
)) return true;
1109 if (mRoot
!is null && mRoot
.dispatchChar(ch
)) return true;
1110 return onCharBubble(ch
);
1114 gxClipRect
.intersect(winrect
);
1117 final void setupClientClip () {
1119 gxClipRect
.intersect(GxRect(
1120 GxPoint(winrect
.pos
.x
+clientOffsetX
, winrect
.pos
.y
+clientOffsetY
),
1121 GxPoint(winrect
.pos
.x
+clientOffsetX
+clientWidth
-1, winrect
.pos
.y
+clientOffsetY
+clientHeight
-1)));
1126 if (removeSubWindow(this)) widgetChanged();
1129 protected bool addToSubwinList (bool asModal
, bool fromKeyboard
) {
1130 if (fromKeyboard
) ignoreSubWinChar
= true;
1131 if (mInWinList
) return true;
1133 if (insertSubWindow(this)) {
1140 void add (bool fromKeyboard
=false) { addToSubwinList(false, fromKeyboard
); }
1142 void addModal (bool fromKeyboard
=false) { addToSubwinList(true, fromKeyboard
); }
1144 // return `false` to reject
1145 protected bool minimiseQuery () {
1149 // return `false` to reject (this is "unminimise")
1150 protected bool restoreQuery () {
1154 void bringToFront () {
1155 if (mClosed ||
!mInWinList
) return;
1156 if (minimised
&& !restoreQuery()) return;
1157 auto aw
= getActiveSubWindow();
1162 lastHoverWindow
= null;
1166 if (aw
!is null && aw
.mModal
) return; // alas
1167 removeSubWindow(this);
1169 insertSubWindow(this);
1170 if (subwinDrag
!is this) subwinDrag
= null;
1174 final @property bool allowMinimise () pure const nothrow @safe @nogc { return mAllowMinimise
; }
1175 @property void allowMinimise (in bool v
) { mAllowMinimise
= v
; }
1177 final @property bool allowDragMove () pure const nothrow @safe @nogc { return mAllowDragMove
; }
1178 @property void allowDragMove (in bool v
) { mAllowDragMove
= v
; }
1180 final @property bool minimised () pure const nothrow @safe @nogc { return mMinimised
; }
1181 @property void minimised (in bool v
) {
1182 if (v
== mMinimised
) return;
1183 if (!mAllowMinimise
&& v
) return;
1184 if (v
) minimise(); else bringToFront();
1188 if (minimised
) return true;
1189 if (mClosed ||
!mAllowMinimise || mModal
) return false;
1191 if (minimiseQuery()) mMinimised
= true;
1194 assert(subwinLast
!is null);
1195 releaseWidgetGrab();
1196 findMinimisedPos(winminx
, winminy
);
1197 auto aw
= getActiveSubWindow();
1198 if (aw
is this) subwinDrag
= null;
1200 lastHoverWindow
= null;
1206 releaseWidgetGrab();
1214 enum MinMarginX = 3;
1215 enum MinMarginY = 3;
1218 static T
clampval(T
) (in T val
, in T min
, in T max
) pure nothrow @safe @nogc {
1219 pragma(inline
, true);
1220 return (val
< min ? min
: val
> max ? max
: val
);
1223 int MinSizeX () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-size-x", 16), 16, 64); }
1224 int MinSizeY () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-size-y", 16), 16, 64); }
1226 int MinMarginX () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-margin-x", 3), 1, 16); }
1227 int MinMarginY () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-margin-y", 3), 1, 16); }
1230 void findMinimisedPos (out int wx
, out int wy
) {
1231 static bool isOccupied (int x
, int y
) {
1232 for (SubWindow w
= subwinLast
; w
!is null; w
= w
.mPrev
) {
1233 if (w
.mInWinList
&& !w
.closed
&& w
.minimised
) {
1234 if (x
>= w
.winminx
&& y
>= w
.winminy
&& x
< w
.winminx
+w
.MinSizeX
&& y
< w
.winminy
+w
.MinSizeY
) return true;
1240 int txcount
= screenWidth
/(MinSizeX
+MinMarginX
);
1241 //int tycount = screenHeight/(MinSizeY+MinMarginY);
1242 if (txcount
< 1) txcount
= 1;
1243 //if (tycount < 1) tycount = 1;
1244 foreach (immutable int n
; 0..6/*5535*/) {
1245 int x
= (n
%txcount
)*(MinSizeX
+MinMarginX
)+1;
1246 int y
= screenHeight
-MinSizeY
-(n
/txcount
)*(MinSizeY
+MinMarginY
);
1247 //conwriteln("trying (", x, ",", y, ")");
1248 if (!isOccupied(x
, y
)) { wx
= x
; wy
= y
; /*conwriteln(" HIT!");*/ return; }