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;
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
); }
612 if (mRoot
is null) setRoot(new RootWidget(this)); else fixRoot();
617 mRoot
.rect
.pos
= GxPoint(0, 0);
618 mRoot
.rect
.size
= GxSize(clientWidth
, clientHeight
);
622 void finishConstruction () {
623 winrect
.size
.sanitize();
626 mRoot
.enter(&createWidgets
);
632 winrect
.pos
= GxPoint(0, 0);
633 winrect
.size
= GxSize(0, 0);
634 finishConstruction();
637 this (const(char)[] atitle
) {
639 winrect
.pos
= GxPoint(0, 0);
640 winrect
.size
= GxSize(0, 0);
641 finishConstruction();
644 this (const(char)[] atitle
, in GxPoint apos
, in GxSize asize
) {
647 winrect
.size
= asize
;
648 finishConstruction();
651 this (const(char)[] atitle
, in GxSize asize
) {
653 winrect
.size
= asize
;
654 winrect
.size
.sanitize();
655 winrect
.pos
.x
= (screenWidth
-winrect
.width
)/2;
656 winrect
.pos
.y
= (screenHeight
-winrect
.height
)/2;
657 finishConstruction();
660 @property void caption (const(char)[] atitle
) {
661 if (atitle
is null) {
670 @property dynstring
caption () const nothrow @nogc { return dynstring(mWinTitle
); }
673 return (mRoot
!is null ? mRoot
.hasGrab() : false);
676 override void widgetChanged () nothrow {
677 if (mInWinList
&& !minimised
) {
680 } catch (Exception e
) {
686 // this doesn't perform relayouting
687 void setRoot (RootWidget w
) {
695 // this is called from constructor
696 void createStyle () {
699 // this is called from constructor
700 void createWidgets () {
703 // this is called from constructor
704 // you can call `addModal()` here, for example
705 void finishCreating () {
709 void relayout (bool resizeWindow
=false) {
710 if (mRoot
is null) return;
712 FuiFlexLayouter
!Widget lay
;
714 lay
.isValidBoxId
= delegate bool (Widget id
) { return (id
!is null); };
716 lay
.firstChild
= delegate Widget (Widget id
) { return id
.firstChild
; };
717 lay
.nextSibling
= delegate Widget (Widget id
) { return id
.nextSibling
; };
719 lay
.getMinSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.minSize
.w
: id
.minSize
.h
); };
720 lay
.getMaxSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.maxSize
.w
: id
.maxSize
.h
); };
721 lay
.getPrefSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.prefSize
.w
: id
.prefSize
.h
); };
723 lay
.isHorizBox
= delegate bool (Widget id
) { return (id
.childDir
== GxDir
.Horiz
); };
725 lay
.getFlex
= delegate int (Widget id
) { return id
.flex
; };
727 lay
.getSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.boxsize
.w
: id
.boxsize
.h
); };
728 lay
.setSize
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.boxsize
.w
= val
; else id
.boxsize
.h
= val
; };
730 lay
.getFinalSize
= delegate int (Widget id
, in bool horiz
) { return (horiz ? id
.finalSize
.w
: id
.finalSize
.h
); };
731 lay
.setFinalSize
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.finalSize
.w
= val
; else id
.finalSize
.h
= val
; };
733 lay
.setFinalPos
= delegate void (Widget id
, in bool horiz
, int val
) { if (horiz
) id
.finalPos
.x
= val
; else id
.finalPos
.y
= val
; };
735 if (winrect
.size
.empty
) {
736 if (maxWinSize
.empty
) maxWinSize
= GxSize(screenWidth
-16-decorationSizeX
, screenHeight
-16-decorationSizeY
);
740 if (winrect
.size
.w
> screenWidth
) winrect
.size
.w
= screenWidth
;
741 if (winrect
.size
.h
> screenHeight
) winrect
.size
.h
= screenHeight
;
744 mRoot
.maxSize
= maxWinSize
;
745 mRoot
.minSize
= minWinSize
;
746 if (mRoot
.minSize
.w
> screenWidth
-decorationSizeX
) mRoot
.minSize
.w
= screenWidth
-decorationSizeX
;
747 if (mRoot
.minSize
.h
> screenHeight
-decorationSizeY
) mRoot
.minSize
.h
= screenHeight
-decorationSizeY
;
749 // cannot resize window, so set fixed sizes
750 mRoot
.maxSize
= winrect
.size
-GxSize(decorationSizeX
, decorationSizeY
);
751 if (mRoot
.maxSize
.w
<= 0) mRoot
.maxSize
.w
= maxWinSize
.w
;
752 if (mRoot
.maxSize
.h
<= 0) mRoot
.maxSize
.h
= maxWinSize
.h
;
753 mRoot
.minSize
= mRoot
.maxSize
;
754 mRoot
.prefSize
= mRoot
.maxSize
;
755 mRoot
.boxsize
= mRoot
.maxSize
;
758 if (!mNoTitle
&& mWinTitle
.length
) {
759 if (mRoot
.boxsize
.w
<= 0) mRoot
.boxsize
.w
= gxTextWidthUtf(mWinTitle
)+2;
761 mRoot
.prefSize
= mRoot
.boxsize
;
766 hsizes
.unsafeArrayClear();
768 vsizes
.unsafeArrayClear();
772 foreach (Widget w
; mRoot
.allDepth
) {
775 w
.hsizeIdNext
= null;
776 if (w
.hsizeId
.length
) {
777 foreach (ref Widget nw
; hsizes
) {
778 if (nw
.hsizeId
== w
.hsizeId
) {
784 if (w
.hsizeIdNext
is null) hsizes
.unsafeArrayAppend(w
);
787 w
.vsizeIdNext
= null;
788 if (w
.vsizeId
.length
) {
789 foreach (ref Widget nw
; vsizes
) {
790 if (nw
.vsizeId
== w
.vsizeId
) {
796 if (w
.vsizeIdNext
is null) vsizes
.unsafeArrayAppend(w
);
800 // fix horiz pref sizes
801 foreach (Widget sw
; hsizes
) {
803 for (Widget w
= sw
; w
!is null; w
= w
.hsizeIdNext
) if (vmax
< w
.prefSize
.w
) vmax
= w
.prefSize
.w
;
804 for (Widget w
= sw
; w
!is null; w
= w
.hsizeIdNext
) w
.prefSize
.w
= vmax
;
807 // fix vert pref sizes
808 foreach (Widget sw
; vsizes
) {
810 for (Widget w
= sw
; w
!is null; w
= w
.vsizeIdNext
) if (vmax
< w
.prefSize
.h
) vmax
= w
.prefSize
.h
;
811 for (Widget w
= sw
; w
!is null; w
= w
.vsizeIdNext
) w
.prefSize
.h
= vmax
;
816 winrect
.size
= mRoot
.boxsize
+GxSize(decorationSizeX
, decorationSizeY
);
817 if (winrect
.x1
>= screenWidth
) winrect
.pos
.x
-= winrect
.x1
-screenWidth
+1;
818 if (winrect
.y1
>= screenHeight
) winrect
.pos
.y
-= winrect
.y1
-screenHeight
+1;
819 if (winrect
.pos
.x
< 0) winrect
.pos
.x
= 0;
820 if (winrect
.pos
.y
< 0) winrect
.pos
.y
= 0;
822 foreach (Widget w
; mRoot
.allDepth
) {
823 w
.hsizeIdNext
= null;
824 w
.vsizeIdNext
= null;
830 void relayoutResize () { relayout(true); }
832 final @property Widget
rootWidget () pure nothrow @safe @nogc { pragma(inline
, true); return mRoot
; }
834 final @property int x0 () nothrow @safe { return (minimised ? winminx
: winrect
.pos
.x
); }
835 final @property int y0 () nothrow @safe { return (minimised ? winminy
: winrect
.pos
.y
); }
836 final @property int width () nothrow @safe { return (minimised ? MinSizeX
: winrect
.size
.w
); }
837 final @property int height () nothrow @safe { return (minimised ? MinSizeY
: winrect
.size
.h
); }
839 final @property void x0 (in int v
) {
841 if (winminx
!= v
) { winminx
= v
; if (mInWinList
) postScreenRebuild(); }
843 if (winrect
.pos
.x
!= v
) { winrect
.pos
.x
= v
; widgetChanged(); }
846 final @property void y0 (in int v
) {
848 if (winminy
!= v
) { winminy
= v
; if (mInWinList
) postScreenRebuild(); }
850 if (winrect
.pos
.y
!= v
) { winrect
.pos
.y
= v
; widgetChanged(); }
853 final @property void width (in int v
) { pragma(inline
, true); setSize(v
, winrect
.size
.h
); }
854 final @property void height (in int v
) { pragma(inline
, true); setSize(winrect
.size
.w
, v
); }
856 final void setPos (in int newx
, in int newy
) {
857 if (winrect
.pos
.x
!= newx || winrect
.pos
.y
!= newy
) {
858 winrect
.pos
.x
= newx
;
859 winrect
.pos
.y
= newy
;
864 final void setSize (in int awidth
, in int aheight
) {
865 bool changed
= false;
866 if (awidth
> 0 && awidth
!= winrect
.size
.w
) {
868 immutable bool chsz
= (mRoot
!is null && mRoot
.width
== clientWidth
);
869 winrect
.size
.w
= awidth
;
870 if (chsz || mRoot
.width
> clientWidth
) mRoot
.width
= clientWidth
;
872 if (aheight
> 0 && aheight
!= winrect
.size
.w
) {
874 immutable bool chsz
= (mRoot
!is null && mRoot
.height
== clientHeight
);
875 winrect
.size
.h
= aheight
;
876 if (chsz || mRoot
.height
> clientHeight
) mRoot
.height
= clientHeight
;
884 final void setClientSize (int awidth
, int aheight
) {
885 setSize((awidth
> 0 ? awidth
+decorationSizeX
: awidth
), (aheight
> 0 ? aheight
+decorationSizeY
: aheight
));
888 final void centerWindow () {
889 immutable int newx
= (screenWidth
-winrect
.size
.w
)/2;
890 immutable int newy
= (screenHeight
-winrect
.size
.h
)/2;
891 if (winrect
.pos
.x
!= newx || winrect
.pos
.y
!= newy
) {
892 winrect
.pos
.x
= newx
;
893 winrect
.pos
.y
= newy
;
898 final @property SubWindow
prev () pure nothrow @safe @nogc { return mPrev
; }
899 final @property SubWindow
next () pure nothrow @safe @nogc { return mNext
; }
901 final @property Type
type () const pure nothrow @safe @nogc { return mType
; }
902 final @property bool onTop () const pure nothrow @safe @nogc { return (mType
== Type
.OnTop
); }
903 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType
== Type
.OnBottom
); }
905 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList
; }
907 final @property bool modal () const pure nothrow @safe @nogc { return mModal
; }
908 final @property bool closed () const pure nothrow @safe @nogc { return mClosed
; }
910 final @property bool inWindowList () const pure nothrow @safe @nogc { return mInWinList
; }
912 final @property bool active () const nothrow @trusted @nogc {
913 if (!mInWinList || mClosed || minimised
) return false;
914 return (getActiveSubWindow
is this);
917 @property int decorationSizeX () const nothrow @safe { return 2*2; }
918 @property int decorationSizeY () const nothrow @safe { immutable hgt
= gxTextHeightUtf
; return (hgt
< 10 ?
10 : hgt
+1)+4; }
920 @property int clientOffsetX () const nothrow @safe { return 2; }
921 @property int clientOffsetY () const nothrow @safe { immutable hgt
= gxTextHeightUtf
; return (hgt
< 10 ?
10 : hgt
+1)+2; }
923 final @property int clientWidth () const nothrow @safe { pragma(inline
, true); return winrect
.size
.w
-decorationSizeX
; }
924 final @property int clientHeight () const nothrow @safe { pragma(inline
, true); return winrect
.size
.h
-decorationSizeY
; }
926 final @property void clientWidth (in int v
) { pragma(inline
, true); setClientSize(v
, clientHeight
); }
927 final @property void clientHeight (in int v
) { pragma(inline
, true); setClientSize(clientWidth
, v
); }
929 final @property Widget
focusedWidget () {
930 if (mRoot
is null) return null;
931 Widget w
= mRoot
.focusedWidget
;
932 return (w
!is null && w
.canAcceptFocus ? w
: null);
935 protected void drawWidgets () {
937 if (mRoot
!is null) mRoot
.onPaint();
940 // draw window frame and background in "normal" state
941 protected void drawNormalDecoration () {
942 //immutable string act = (active ? null : "inactive");
943 gxDrawWindow(winrect
, (mNoTitle ?
null : mWinTitle
.length ? mWinTitle
.getData
: ""),
945 getColor("title-text"),
946 getColor("title-back"),
948 getColor("shadow-color"),
949 getInt("shadow-size", 0),
950 (getInt("shadow-dash", 0) > 0));
953 // draw window frame and background in "minimised" state
954 protected void drawWindowMinimised () {
955 //immutable string act = (active ? null : "inactive");
956 gxFillRect(winminx
, winminy
, MinSizeX
, MinSizeY
, getColor("back"));
957 gxDrawRect(winminx
, winminy
, MinSizeX
, MinSizeY
, getColor("frame"));
961 protected void drawDecoration () {
963 if (gxClipRect
.intersect(GxRect(winminx
, winminy
, MinSizeX
, MinSizeY
))) drawWindowMinimised();
966 drawNormalDecoration();
970 protected void drawDragRect () {
971 immutable uint clr
= getColor("drag-overlay-back");
972 if (gxIsTransparent(clr
)) return;
973 immutable bool dashed
= (getInt("drag-overlay-dash", 0) > 0);
977 gxDashRect(x0
, y0
, width
, height
, clr
);
979 gxFillRect(x0
, y0
, width
, height
, clr
);
985 void releaseWidgetGrab () {
986 if (mRoot
!is null) mRoot
.releaseGrab();
989 // event in our local coords
990 bool startMouseDrag (MouseEvent event
) {
991 if (!mAllowDragMove ||
!mInWinList
) return false;
994 subwinDragXSpot
= -event
.x
;
995 subwinDragYSpot
= -event
.y
;
1000 bool startKeyboardDrag () {
1001 if (!mAllowDragMove ||
!mInWinList
) return false;
1002 releaseWidgetGrab();
1004 subwinDragXSpot
= int.min
;
1005 subwinDragYSpot
= int.min
;
1011 if (subwinDrag
is this) {
1012 releaseWidgetGrab();
1022 if (!minimised
) drawWidgets();
1027 bool onKeySink (KeyEvent event
) {
1031 bool onKeyBubble (KeyEvent event
) {
1032 // global window hotkeys
1033 if (event
.pressed
) {
1034 if (event
== "C-F5") { if (startKeyboardDrag()) return true; }
1035 if (/*event == "M-M" ||*/ event
== "M-S-M") { if (minimise()) return true; }
1040 bool onKeyEvent (KeyEvent event
) {
1041 if (closed
) return false;
1042 if (minimised
) return false;
1043 if (onKeySink(event
)) return true;
1044 if (mRoot
!is null && mRoot
.dispatchKey(event
)) return true;
1045 return onKeyBubble(event
);
1049 bool onMouseSink (MouseEvent event
) {
1051 if (subwinDrag
is null && event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
1052 if (event
.x
>= 0 && event
.y
>= 0 &&
1053 event
.x
< width
&& event
.y
< (!minimised ? gxTextHeightUtf
+2 : height
))
1055 startMouseDrag(event
);
1060 if (minimised
) return false;
1062 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.right
) {
1063 if (event
.x
>= 0 && event
.y
>= 0 &&
1064 event
.x
< winrect
.size
.w
&& event
.y
< gxTextHeightUtf
)
1074 bool onMouseBubble (MouseEvent event
) {
1078 bool onMouseEvent (MouseEvent event
) {
1079 if (!active
) return false;
1080 if (closed
) return false;
1083 event
.mouse2xy(mx
, my
);
1085 MouseEvent ev
= event
;
1088 if (onMouseSink(ev
)) return true;
1090 if (mRoot
!is null) {
1092 ev
.x
= mx
-(x0
+clientOffsetX
)-mRoot
.rect
.x0
;
1093 ev
.y
= my
-(y0
+clientOffsetY
)-mRoot
.rect
.y0
;
1094 if (mRoot
.dispatchMouse(ev
)) return true;
1100 return onMouseBubble(ev
);
1104 bool onCharSink (dchar ch
) {
1108 bool onCharBubble (dchar ch
) {
1112 bool onCharEvent (dchar ch
) {
1113 if (!active
) return false;
1114 if (closed
) return false;
1115 if (minimised
) return false;
1116 if (onCharSink(ch
)) return true;
1117 if (mRoot
!is null && mRoot
.dispatchChar(ch
)) return true;
1118 return onCharBubble(ch
);
1122 gxClipRect
.intersect(winrect
);
1125 final void setupClientClip () {
1127 gxClipRect
.intersect(GxRect(
1128 GxPoint(winrect
.pos
.x
+clientOffsetX
, winrect
.pos
.y
+clientOffsetY
),
1129 GxPoint(winrect
.pos
.x
+clientOffsetX
+clientWidth
-1, winrect
.pos
.y
+clientOffsetY
+clientHeight
-1)));
1135 if (removeSubWindow(this)) postScreenRebuild();
1139 protected bool addToSubwinList (bool asModal
, bool fromKeyboard
) {
1140 if (fromKeyboard
) ignoreSubWinChar
= true;
1141 if (mInWinList
) return true;
1143 if (insertSubWindow(this)) {
1144 if (mRoot
!is null) mRoot
.fixFocus();
1146 postScreenRebuild();
1152 void add (bool fromKeyboard
=false) { addToSubwinList(false, fromKeyboard
); }
1154 void addModal (bool fromKeyboard
=false) { addToSubwinList(true, fromKeyboard
); }
1156 // return `false` to reject
1157 protected bool minimiseQuery () {
1161 // return `false` to reject (this is "unminimise")
1162 protected bool restoreQuery () {
1166 void bringToFront () {
1167 if (mClosed ||
!mInWinList
) return;
1168 if (minimised
&& !restoreQuery()) return;
1169 auto aw
= getActiveSubWindow();
1173 lastHoverWindow
= null;
1178 if (aw
!is null && aw
.mModal
) return; // alas
1179 removeSubWindow(this);
1181 insertSubWindow(this);
1182 if (subwinDrag
!is this) subwinDrag
= null;
1186 final @property bool allowMinimise () pure const nothrow @safe @nogc { return mAllowMinimise
; }
1187 @property void allowMinimise (in bool v
) { mAllowMinimise
= v
; }
1189 final @property bool allowDragMove () pure const nothrow @safe @nogc { return mAllowDragMove
; }
1190 @property void allowDragMove (in bool v
) { mAllowDragMove
= v
; }
1192 final @property bool minimised () pure const nothrow @safe @nogc { return mMinimised
; }
1193 @property void minimised (in bool v
) {
1194 if (v
== mMinimised
) return;
1195 if (!mAllowMinimise
&& v
) return;
1196 if (v
) minimise(); else bringToFront();
1200 if (minimised
) return true;
1201 if (mClosed ||
!mAllowMinimise || mModal
) return false;
1203 if (minimiseQuery()) mMinimised
= true;
1206 assert(subwinLast
!is null);
1207 releaseWidgetGrab();
1208 findMinimisedPos(winminx
, winminy
);
1209 auto aw
= getActiveSubWindow();
1210 if (aw
is this) subwinDrag
= null;
1212 lastHoverWindow
= null;
1213 postScreenRebuild(); // because minimised widgets will not post this
1218 releaseWidgetGrab();
1223 static T
clampval(T
) (in T val
, in T min
, in T max
) pure nothrow @safe @nogc {
1224 pragma(inline
, true);
1225 return (val
< min ? min
: val
> max ? max
: val
);
1228 int MinSizeX () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-size-x", 16), 16, 64); }
1229 int MinSizeY () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-size-y", 16), 16, 64); }
1231 int MinMarginX () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-margin-x", 3), 1, 16); }
1232 int MinMarginY () nothrow @safe { immutable bool omm
= mMinimised
; mMinimised
= true; scope(exit
) mMinimised
= omm
; return clampval(getInt("icon-margin-y", 3), 1, 16); }
1235 void findMinimisedPos (out int wx
, out int wy
) {
1236 static bool isOccupied (int x
, int y
) {
1237 for (SubWindow w
= subwinLast
; w
!is null; w
= w
.mPrev
) {
1238 if (w
.mInWinList
&& !w
.closed
&& w
.minimised
) {
1239 if (x
>= w
.winminx
&& y
>= w
.winminy
&& x
< w
.winminx
+w
.MinSizeX
&& y
< w
.winminy
+w
.MinSizeY
) return true;
1245 int txcount
= screenWidth
/(MinSizeX
+MinMarginX
);
1246 //int tycount = screenHeight/(MinSizeY+MinMarginY);
1247 if (txcount
< 1) txcount
= 1;
1248 //if (tycount < 1) tycount = 1;
1249 foreach (immutable int n
; 0..6/*5535*/) {
1250 int x
= (n
%txcount
)*(MinSizeX
+MinMarginX
)+1;
1251 int y
= screenHeight
-MinSizeY
-(n
/txcount
)*(MinSizeY
+MinMarginY
);
1252 //conwriteln("trying (", x, ",", y, ")");
1253 if (!isOccupied(x
, y
)) { wx
= x
; wy
= y
; /*conwriteln(" HIT!");*/ return; }