added table for unsent messages
[chiroptera.git] / egui / subwindows.d
blob6f83bc817318a0085cc4750880f8d6fa2785a43f
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module egui.subwindows /*is aliced*/;
18 private:
20 import core.time;
22 import arsd.simpledisplay;
24 import iv.alice;
25 import iv.cmdcon;
26 import iv.strex;
27 import iv.utfutil;
29 import egfx;
31 import egui.widgets : Widget;
34 // ////////////////////////////////////////////////////////////////////////// //
35 public __gshared SimpleWindow vbwin; // main window; MUST be set!
36 public __gshared bool vbfocused = false;
39 // ////////////////////////////////////////////////////////////////////////// //
40 __gshared public int lastMouseXUnscaled = 10000, lastMouseYUnscaled = 10000;
41 __gshared /*MouseButton*/public int lastMouseButton;
43 __gshared public int lastMouseX () nothrow @trusted @nogc { pragma(inline, true); return lastMouseXUnscaled/vbufEffScale; }
44 __gshared public int lastMouseY () nothrow @trusted @nogc { pragma(inline, true); return lastMouseYUnscaled/vbufEffScale; }
46 __gshared public bool lastMouseLeft () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.left) != 0); }
47 __gshared public bool lastMouseRight () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.right) != 0); }
48 __gshared public bool lastMouseMiddle () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.middle) != 0); }
51 // ////////////////////////////////////////////////////////////////////////// //
52 public class ScreenRebuildEvent {}
53 public class ScreenRepaintEvent {}
54 public class QuitEvent {}
55 public class CursorBlinkEvent {}
56 public class HideMouseEvent {}
58 __gshared ScreenRebuildEvent evScrRebuild;
59 __gshared ScreenRepaintEvent evScreenRepaint;
60 __gshared CursorBlinkEvent evCurBlink;
61 __gshared HideMouseEvent evHideMouse;
63 shared static this () {
64 evScrRebuild = new ScreenRebuildEvent();
65 evScreenRepaint = new ScreenRepaintEvent();
66 evCurBlink = new CursorBlinkEvent();
67 evHideMouse = new HideMouseEvent();
71 // ////////////////////////////////////////////////////////////////////////// //
72 public void postScreenRebuild () { if (vbwin !is null && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScrRebuild); }
73 public void postScreenRepaint () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScreenRepaint); }
74 public void postScreenRepaintDelayed () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postTimeout(evScreenRepaint, 35); }
76 public void postCurBlink () {
77 if (vbwin !is null && !vbwin.eventQueued!CursorBlinkEvent) {
78 //conwriteln("curblink posted!");
79 vbwin.postTimeout(evCurBlink, 100);
80 //vbwin.postTimeout(evCurBlink, 500);
85 // ////////////////////////////////////////////////////////////////////////// //
86 __gshared MonoTime lastMouseMove;
87 __gshared uint MouseHideTime = 3000;
90 shared static this () {
91 conRegVar("mouse_hide_time", "mouse cursor hiding time (in milliseconds); 0 to not hide it",
92 delegate (self) {
93 return MouseHideTime;
95 delegate (self, uint nv) {
96 if (MouseHideTime != nv) {
97 if (MouseHideTime == 0) mouseMoved();
98 MouseHideTime = nv;
99 conwriteln("mouse hiding time: ", nv);
106 public int mshtime_dbg () {
107 if (MouseHideTime > 0) {
108 auto ctt = MonoTime.currTime;
109 auto mt = (ctt-lastMouseMove).total!"msecs";
110 return cast(int)mt;
111 } else {
112 return 0;
117 public bool isMouseVisible () {
118 if (MouseHideTime > 0) {
119 auto ctt = MonoTime.currTime;
120 return ((ctt-lastMouseMove).total!"msecs" < MouseHideTime+500);
121 } else {
122 return true;
127 public float mouseAlpha () {
128 if (MouseHideTime > 0) {
129 auto ctt = MonoTime.currTime;
130 auto msc = (ctt-lastMouseMove).total!"msecs";
131 if (msc >= MouseHideTime+500) return 0.0f;
132 if (msc < MouseHideTime) return 1.0f;
133 msc -= MouseHideTime;
134 return 1.0f-msc/500.0f;
135 } else {
136 return 1.0f;
141 // returns `true` if mouse should be redrawn
142 public bool repostHideMouse () {
143 if (vbwin is null || vbwin.eventQueued!HideMouseEvent) return false;
144 if (MouseHideTime > 0) {
145 auto ctt = MonoTime.currTime;
146 auto tms = (ctt-lastMouseMove).total!"msecs";
147 if (tms >= MouseHideTime) {
148 if (tms >= MouseHideTime+500) return true; // hide it
149 vbwin.postTimeout(evHideMouse, 50);
150 return true; // fade it
152 vbwin.postTimeout(evHideMouse, cast(int)(MouseHideTime-tms));
154 return false;
158 public void mouseMoved () {
159 if (MouseHideTime > 0) {
160 lastMouseMove = MonoTime.currTime;
161 if (vbwin !is null && !vbwin.eventQueued!HideMouseEvent) vbwin.postTimeout(evHideMouse, MouseHideTime);
166 // ////////////////////////////////////////////////////////////////////////// //
167 public void drawTextCursor (bool active, int x, int y, int hgt=-666) {
168 if (hgt == -666) hgt = gxTextHeightUtf;
169 if (hgt < 1) return;
170 if (active) {
171 auto ctt = (MonoTime.currTime.ticks*1000/MonoTime.ticksPerSecond)/100;
172 int doty = ctt%(hgt*2-1);
173 if (doty >= hgt) doty = hgt*2-doty-1;
174 gxVLine(x, y, hgt, (ctt%10 < 5 ? gxRGB!(255, 255, 255) : gxRGB!(200, 200, 200)));
175 gxPutPixel(x, y+doty, gxRGB!(0, 255, 255));
176 postCurBlink();
177 } else {
178 gxVLine(x, y, hgt, gxRGB!(170, 170, 170));
183 // ////////////////////////////////////////////////////////////////////////// //
184 private __gshared SubWindow subwinLast;
185 private __gshared bool ignoreSubWinChar = false;
186 // make a package and move that to package
187 private __gshared SubWindow subwinDrag = null;
188 private __gshared int subwinDragXSpot, subwinDragYSpot;
191 public @property bool isSubWinDragging () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null); }
192 public @property bool isSubWinDraggingKeyboard () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null && subwinDragXSpot == int.min && subwinDragYSpot == int.min); }
195 public void eguiLostGlobalFocus () {
196 ignoreSubWinChar = false;
197 subwinDrag = null;
201 private bool insertSubWindow (SubWindow nw) nothrow @trusted @nogc {
202 if (nw is null || nw.mClosed) return false;
203 assert(nw.mPrev is null);
204 assert(nw.mNext is null);
205 assert(!nw.mInWinList);
206 if (nw.mType == SubWindow.Type.OnBottom) {
207 SubWindow w = subwinLast;
208 if (w !is null) {
209 while (w.mPrev !is null) w = w.mPrev;
210 nw.mNext = w;
211 if (w !is null) w.mPrev = nw; else subwinLast = nw;
212 } else {
213 subwinLast = nw;
215 nw.mInWinList = true;
216 return true;
218 SubWindow aw = getActiveSubWindow();
219 assert(aw !is nw);
220 if (nw.mType == SubWindow.Type.OnTop || aw is null) {
221 nw.mPrev = subwinLast;
222 if (subwinLast !is null) subwinLast.mNext = nw;
223 subwinLast = nw;
224 nw.mInWinList = true;
225 return true;
227 if (aw.mModal && !nw.mModal) return false; // can't insert normal windows while modal window is active
228 // insert after aw
229 nw.mPrev = aw;
230 nw.mNext = aw.mNext;
231 aw.mNext = nw;
232 if (nw.mNext !is null) nw.mNext.mPrev = nw;
233 if (aw is subwinLast) subwinLast = nw;
234 nw.mInWinList = true;
235 return true;
239 private bool removeSubWindow (SubWindow nw) nothrow @trusted @nogc {
240 if (nw is null || !nw.mInWinList) return false;
241 if (nw.mPrev !is null) nw.mPrev.mNext = nw.mNext;
242 if (nw.mNext !is null) nw.mNext.mPrev = nw.mPrev;
243 if (nw is subwinLast) subwinLast = nw.mPrev;
244 nw.mPrev = null;
245 nw.mNext = null;
246 nw.mInWinList = false;
247 return true;
251 public void mouse2xy (MouseEvent event, out int mx, out int my) nothrow @trusted @nogc {
252 mx = event.x/vbufEffScale;
253 my = event.y/vbufEffScale;
257 public SubWindow subWindowAt (int mx, int my) nothrow @trusted @nogc {
258 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
259 if (!w.closed) {
260 if (w.mMinimized) {
261 if (mx >= w.winminx && my >= w.winminy && mx < w.winminx+w.MinSizeX && my < w.winminy+w.MinSizeY) return w;
262 } else {
263 if (mx >= w.winx && my >= w.winy && mx < w.winx+w.winw && my < w.winy+w.winh) return w;
267 return null;
271 public SubWindow subWindowAt (MouseEvent event) nothrow @trusted @nogc { return subWindowAt(event.x/vbufEffScale, event.y/vbufEffScale); }
274 public SubWindow getActiveSubWindow () nothrow @trusted @nogc {
275 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
276 if (!w.mClosed && w.type != SubWindow.Type.OnTop && !w.mMinimized) return w;
278 return null;
282 public bool dispatchEvent (KeyEvent event) {
283 bool res = false;
284 if (isSubWinDragging) {
285 if (isSubWinDraggingKeyboard) {
286 if (!event.pressed) return true;
287 if (event == "Left") subwinDrag.x0 = subwinDrag.x0-1;
288 else if (event == "Right") subwinDrag.x0 = subwinDrag.x0+1;
289 else if (event == "Up") subwinDrag.y0 = subwinDrag.y0-1;
290 else if (event == "Down") subwinDrag.y0 = subwinDrag.y0+1;
291 else if (event == "C-Left") subwinDrag.x0 = subwinDrag.x0-8;
292 else if (event == "C-Right") subwinDrag.x0 = subwinDrag.x0+8;
293 else if (event == "C-Up") subwinDrag.y0 = subwinDrag.y0-8;
294 else if (event == "C-Down") subwinDrag.y0 = subwinDrag.y0+8;
295 else if (event == "Home") subwinDrag.x0 = 0;
296 else if (event == "End") subwinDrag.x0 = VBufWidth-subwinDrag.width;
297 else if (event == "PageUp") subwinDrag.y0 = 0;
298 else if (event == "PageDown") subwinDrag.y0 = VBufHeight-subwinDrag.height;
299 else if (event == "Escape" || event == "Enter") subwinDrag = null;
300 postScreenRebuild();
301 return true;
303 } else if (auto aw = getActiveSubWindow()) {
304 res = aw.onKey(event);
305 if (res) postScreenRebuild();
307 return res;
311 public bool dispatchEvent (MouseEvent event) {
312 __gshared SubWindow lastHover = null;
314 if (subwinLast is null) { postScreenRepaint(); return false; }
316 int mx = lastMouseX;
317 int my = lastMouseY;
318 auto aw = getActiveSubWindow();
319 auto msw = subWindowAt(event);
320 scope(exit) {
321 if (msw !is lastHover) {
322 lastHover = msw;
323 postScreenRebuild();
326 bool curIsModal = (aw !is null && aw.mModal);
328 // switch window by button press
329 if (event.type == MouseEventType.buttonReleased && msw !is aw && !curIsModal) {
330 if (msw !is null && msw.mType == SubWindow.Type.Normal) {
331 msw.bringToFront();
332 postScreenRepaint();
333 return true;
337 // drag
338 if (isSubWinDragging) {
339 if (!isSubWinDraggingKeyboard) {
340 subwinDrag.x0 = mx+subwinDragXSpot;
341 subwinDrag.y0 = my+subwinDragYSpot;
342 // stop drag?
343 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) subwinDrag = null;
344 postScreenRebuild();
345 } else {
346 postScreenRepaint();
348 return true;
351 if (msw is null || msw !is aw || msw.mMinimized) {
352 if (msw is null || !msw.onTop) {
353 postScreenRepaint();
354 return false;
357 assert(msw !is null);
359 if (msw.onMouse(event)) {
360 postScreenRebuild();
361 return true;
364 postScreenRepaint();
365 return false;
369 public bool dispatchEvent (dchar ch) {
370 if (ignoreSubWinChar) { ignoreSubWinChar = false; return (subwinLast !is null); }
371 bool res = false;
372 if (!isSubWinDragging) {
373 if (auto aw = getActiveSubWindow()) {
374 res = aw.onChar(ch);
375 if (res) postScreenRebuild();
378 return res;
382 // ////////////////////////////////////////////////////////////////////////// //
383 public void paintSubWindows () {
384 // get first window
385 SubWindow firstWin = subwinLast;
386 if (firstWin is null) return;
387 while (firstWin.mPrev !is null) firstWin = firstWin.mPrev;
389 SubWindow firstMin, firstNormal, firstTop;
390 SubWindow lastMin, lastNormal, lastTop;
392 void doDraw (SubWindow w) {
393 if (w !is null) {
394 gxClipReset();
395 w.onPaint();
396 if (w is subwinDrag) { gxClipReset(); gxFillRect(w.x0, w.y0, w.width, w.height, gxRGBA!(255, 127, 0, 80)); }
400 // paint background windows
401 for (SubWindow w = firstWin; w !is null; w = w.mNext) {
402 if (w.mClosed) continue;
403 if (w.mMinimized) {
404 if (firstMin is null) firstMin = w;
405 lastMin = w;
406 } else if (w.mType == SubWindow.Type.Normal) {
407 if (firstNormal is null) firstNormal = w;
408 lastNormal = w;
409 } else if (w.mType == SubWindow.Type.OnTop) {
410 if (firstTop is null) firstTop = w;
411 lastTop = w;
412 } else if (w.mType == SubWindow.Type.OnBottom) {
413 doDraw(w);
417 // paint minimized windows
418 for (SubWindow w = firstMin; w !is null; w = w.mNext) {
419 if (!w.mClosed && w.mMinimized) doDraw(w);
420 if (w is lastMin) break;
423 // paint normal windows
424 for (SubWindow w = firstNormal; w !is null; w = w.mNext) {
425 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.Normal) doDraw(w);
426 if (w is lastNormal) break;
429 // paint ontop windows
430 for (SubWindow w = firstTop; w !is null; w = w.mNext) {
431 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.OnTop) doDraw(w);
432 if (w is lastTop) break;
435 // paint hint for minimized window
436 if (auto msw = subWindowAt(lastMouseX, lastMouseY)) {
437 if (!msw.mClosed && msw.mMinimized && msw.title.length) {
438 auto wdt = gxTextWidthUtf(msw.title)+2;
439 auto hgt = gxTextHeightUtf+2;
440 int y = msw.winminy-hgt;
441 int x;
442 if (wdt >= VBufWidth) {
443 x = (VBufWidth-wdt)/2;
444 } else {
445 x = (msw.winminx+msw.MinSizeX)/2-wdt/2;
446 if (x < 0) x = 0;
448 gxClipReset();
449 gxFillRect(x, y, wdt, hgt, gxRGB!(255, 255, 255));
450 gxDrawTextUtf(x+1, y+1, msw.title, gxRGB!(0, 0, 0));
456 // ////////////////////////////////////////////////////////////////////////// //
457 public abstract class SubWindow {
458 protected:
459 enum Type {
460 Normal,
461 OnTop,
462 OnBottom,
465 protected:
466 SubWindow mPrev, mNext;
467 Type mType = Type.Normal;
468 bool mMinimized;
469 bool mInWinList;
470 bool mModal;
471 bool mClosed;
472 int awidx; // active widget index
473 Widget[] widgets;
475 public:
476 int winminx, winminy;
477 int winx, winy, winw, winh;
478 string title;
479 uint clrWinFrame = gxRGB!(255, 255, 255);
480 uint clrWinTitleBack = gxRGB!(255, 255, 255);
481 uint clrWinTitle = gxRGB!(0, 0, 0);
482 uint clrWinBack = gxRGB!(0, 0, 180);
483 uint clrWinText = gxRGB!(255, 255, 255);
485 public:
486 this () {}
488 this (string atitle, int ax, int ay, int aw, int ah) {
489 title = atitle;
490 winx = ax;
491 winy = ay;
492 winw = aw;
493 winh = ah;
496 this (string atitle, int aw, int ah) {
497 title = atitle;
498 winx = (VBufWidth-aw)/2;
499 winy = (VBufHeight-ah)/2;
500 winw = aw;
501 winh = ah;
504 final @property int x0 () const pure nothrow @safe @nogc { return (mMinimized ? winminx : winx); }
505 final @property int y0 () const pure nothrow @safe @nogc { return (mMinimized ? winminy : winy); }
506 final @property int width () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeX : winw); }
507 final @property int height () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeY : winh); }
509 final @property void x0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminx = v; else winx = v; }
510 final @property void y0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminy = v; else winy = v; }
512 final @property SubWindow prev () pure nothrow @safe @nogc { return mPrev; }
513 final @property SubWindow next () pure nothrow @safe @nogc { return mNext; }
515 final @property Type type () const pure nothrow @safe @nogc { return mType; }
516 final @property bool onTop () const pure nothrow @safe @nogc { return (mType == Type.OnTop); }
517 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType == Type.OnBottom); }
519 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList; }
521 final @property bool modal () const pure nothrow @safe @nogc { return mModal; }
522 final @property bool closed () const pure nothrow @safe @nogc { return mClosed; }
524 final @property bool active () const nothrow @trusted @nogc {
525 if (!mInWinList || mClosed || mMinimized) return false;
526 return (getActiveSubWindow is this);
529 @property int decorationSizeX () const nothrow @safe { return 2*2; }
530 @property int decorationSizeY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1+2; }
532 @property int clientOffsetX () const nothrow @safe { return 2; }
533 @property int clientOffsetY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1; }
535 final @property int clientWidth () const nothrow @safe { return winw-decorationSizeX; }
536 final @property int clientHeight () const nothrow @safe { return winh-decorationSizeY; }
538 void addWidget (Widget w) {
539 if (w is null) return;
540 if (w.parent is this) return;
541 if (w.parent !is null) throw new Exception("can't add widget owned by another window");
542 w.parent = this;
543 widgets ~= w;
546 final @property void activeWidget (Widget w) nothrow @trusted @nogc {
547 if (w is null) return;
548 foreach (immutable idx, Widget ww; widgets[]) {
549 if (w is ww) {
550 awidx = cast(int)idx;
551 return;
556 final @property Widget activeWidget () nothrow @trusted @nogc { return (awidx >= 0 && awidx < widgets.length ? widgets.ptr[awidx] : null); }
558 final int widgetAtXYIndex (int x, int y) nothrow @trusted {
559 foreach_reverse (immutable widx, Widget w; widgets) {
560 if (x >= w.x0 && y >= w.y0 && x <= w.x1 && y <= w.y1) return cast(int)widx;
562 return -1;
565 final Widget widgetAtXY (int x, int y) nothrow @trusted {
566 auto idx = widgetAtXYIndex(x, y);
567 return (idx >= 0 && idx < widgets.length ? widgets[idx] : null);
570 void doShiftTab () {
571 if (widgets.length > 0) {
572 foreach (immutable _; 0..widgets.length) {
573 if (awidx < 0) awidx = 0; else if (awidx >= widgets.length) awidx = cast(int)widgets.length;
574 --awidx;
575 if (awidx < 0) awidx = cast(int)(widgets.length-1);
576 if (widgets[awidx].tabStop) break;
581 void doTab () {
582 if (widgets.length > 0) {
583 foreach (immutable _; 0..widgets.length) {
584 if (awidx < 0) awidx = 0; else if (awidx >= widgets.length) awidx = cast(int)widgets.length;
585 ++awidx;
586 if (awidx >= widgets.length) awidx = 0;
587 if (widgets[awidx].tabStop) break;
592 protected void drawWidgets () {
593 foreach (Widget w; widgets) w.onPaint();
596 // draw window frame and background in "normal" state
597 protected void drawWindowNormal () {
598 setupClip();
599 gxDrawWindow(title, clrWinFrame, clrWinTitle, clrWinTitleBack, clrWinBack);
602 // draw window frame and background in "minimized" state
603 protected void drawWindowMinimized () {
604 gxClipRect.x0 = winminx;
605 gxClipRect.y0 = winminy;
606 gxClipRect.x1 = winminx+MinSizeX-1;
607 gxClipRect.y1 = winminy+MinSizeY-1;
608 gxFillRect(winminx, winminy, MinSizeX, MinSizeY, clrWinBack);
609 gxDrawRect(winminx, winminy, MinSizeX, MinSizeY, clrWinFrame);
612 void startMouseDrag (MouseEvent event) {
613 int mx, my;
614 event.mouse2xy(mx, my);
615 subwinDrag = this;
616 subwinDragXSpot = x0-mx;
617 subwinDragYSpot = y0-my;
618 postScreenRebuild();
621 void startKeyboardDrag () {
622 subwinDrag = this;
623 subwinDragXSpot = int.min;
624 subwinDragYSpot = int.min;
625 postScreenRebuild();
628 void stopDrag () {
629 if (subwinDrag is this) {
630 subwinDrag = null;
631 postScreenRebuild();
635 void onPaint () {
636 if (closed) return;
637 if (!mMinimized) {
638 drawWindowNormal();
639 drawWidgets();
640 } else {
641 drawWindowMinimized();
645 bool onKey (KeyEvent event) {
646 if (closed) return false;
647 if (mMinimized) return false;
648 if (awidx >= 0 && awidx < widgets.length) {
649 if (widgets[awidx].onKey(event)) return true;
651 if (event.pressed) {
652 if (event == "C-F5") { startKeyboardDrag(); return true; }
653 if (event == "Tab") { doTab(); return true; }
654 if (event == "S-Tab") { doShiftTab(); return true; }
655 if (event == "M-M" && !mModal) { minimize(); return true; }
657 foreach_reverse (immutable aidx, Widget w; widgets) {
658 if (aidx != awidx && w.onKey(event)) return true;
660 return false;
663 bool onMouse (MouseEvent event) {
664 if (!active) return false;
665 if (closed) return false;
667 int mx, my;
668 event.mouse2xy(mx, my);
670 // start drag?
671 if (subwinDrag is null && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
672 if (mx >= x0 && my >= y0 && mx < x0+width && my < (!mMinimized ? y0+gxTextHeightUtf+2 : height)) {
673 startMouseDrag(event);
674 return true;
678 if (mMinimized) return false;
680 if (mx >= winx && my >= winy && mx < winx+winw && my < winy+gxTextHeightUtf) {
681 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.right) {
682 if (!mModal && mType == Type.Normal) minimize();
683 return true;
686 if (awidx >= 0 && awidx < widgets.length) {
687 auto msw = widgetAtXY(mx, my);
688 if (msw !is null && msw.onMouse(event)) return true;
690 return false;
693 bool onChar (dchar ch) {
694 if (!active) return false;
695 if (closed) return false;
696 if (mMinimized) return false;
697 if (awidx >= 0 && awidx < widgets.length) {
698 if (widgets[awidx].onChar(ch)) return true;
699 foreach_reverse (immutable aidx, Widget w; widgets) {
700 if (aidx != awidx && w.onChar(ch)) return true;
703 return false;
706 void setupClip () {
707 gxClipReset();
708 gxClipRect.x0 = winx;
709 gxClipRect.x1 = winx+winw-1;
710 gxClipRect.y0 = winy;
711 gxClipRect.y1 = winy+winh-1;
714 final void setupClientClip () {
715 setupClip();
716 gxClipRect.intersect(GxRect(
717 GxPoint(winx+clientOffsetX, winy+clientOffsetY),
718 GxPoint(winx+clientOffsetX+clientWidth-1, winy+clientOffsetY+clientHeight-1)));
721 void close () {
722 mClosed = true;
723 if (removeSubWindow(this)) postScreenRebuild();
726 protected bool addToSubwinList (bool asModal, bool fromKeyboard) {
727 if (fromKeyboard) ignoreSubWinChar = true;
728 if (mInWinList) return true;
729 mModal = asModal;
730 if (insertSubWindow(this)) {
731 postScreenRebuild();
732 return true;
734 return false;
737 void add (bool fromKeyboard=false) { addToSubwinList(false, fromKeyboard); }
739 void addModal (bool fromKeyboard=false) { addToSubwinList(true, fromKeyboard); }
741 void bringToFront () {
742 if (mClosed || !mInWinList) return;
743 auto aw = getActiveSubWindow();
744 if (aw is this) { mMinimized = false; return; }
745 if (aw !is null && aw.mModal) return; // alas
746 removeSubWindow(this);
747 mMinimized = false;
748 insertSubWindow(this);
749 if (subwinDrag !is this) subwinDrag = null;
750 postScreenRebuild();
753 @property bool minimized () const { return mMinimized; }
755 @property void minimized (bool v) {
756 if (v == mMinimized) return;
757 if (v) minimize(); else bringToFront();
760 void minimize () {
761 if (mClosed || mMinimized) return;
762 if (!mInWinList) { mMinimized = true; return; }
763 if (mModal) return;
764 assert(subwinLast !is null);
765 findMinimizedPos(winminx, winminy);
766 auto aw = getActiveSubWindow();
767 if (aw is this) subwinDrag = null;
768 mMinimized = true;
769 postScreenRebuild();
772 void restore () {
773 bringToFront();
776 public:
777 enum MinSizeX = 16;
778 enum MinSizeY = 16;
779 enum MinMarginX = 3;
780 enum MinMarginY = 3;
782 protected:
783 static findMinimizedPos (out int wx, out int wy) {
784 static bool isOccupied (int x, int y) {
785 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
786 if (w.mInWinList && !w.closed && w.mMinimized) {
787 if (x >= w.winminx && y >= w.winminy && x < w.winminx+MinSizeX && y < w.winminy+MinSizeY) return true;
790 return false;
793 int txcount = VBufWidth/(MinSizeX+MinMarginX);
794 //int tycount = VBufHeight/(MinSizeY+MinMarginY);
795 if (txcount < 1) txcount = 1;
796 //if (tycount < 1) tycount = 1;
797 foreach (immutable int n; 0..6/*5535*/) {
798 int x = (n%txcount)*(MinSizeX+MinMarginX)+1;
799 int y = VBufHeight-MinSizeY-(n/txcount)*(MinSizeY+MinMarginY);
800 //conwriteln("trying (", x, ",", y, ")");
801 if (!isOccupied(x, y)) { wx = x; wy = y; /*conwriteln(" HIT!");*/ return; }