try to detect html content in plain text
[chiroptera.git] / egui / subwindows.d
blob7be852aa69afa34dee84d917e810f41b83f83822
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;
30 import egui.widgets : Widget;
33 // ////////////////////////////////////////////////////////////////////////// //
34 public __gshared SimpleWindow vbwin; // main window; MUST be set!
35 public __gshared bool vbfocused = false;
38 // ////////////////////////////////////////////////////////////////////////// //
39 __gshared public int lastMouseXUnscaled = 10000, lastMouseYUnscaled = 10000;
40 __gshared /*MouseButton*/public int lastMouseButton;
42 public int lastMouseX () nothrow @trusted @nogc { pragma(inline, true); return lastMouseXUnscaled/screenEffScale; }
43 public int lastMouseY () nothrow @trusted @nogc { pragma(inline, true); return lastMouseYUnscaled/screenEffScale; }
45 public bool lastMouseLeft () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.left) != 0); }
46 public bool lastMouseRight () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.right) != 0); }
47 public bool lastMouseMiddle () nothrow @trusted @nogc { pragma(inline, true); return ((lastMouseButton&MouseButton.middle) != 0); }
50 // ////////////////////////////////////////////////////////////////////////// //
51 public class ScreenRebuildEvent {}
52 public class ScreenRepaintEvent {}
53 public class QuitEvent {}
54 public class CursorBlinkEvent {}
55 public class HideMouseEvent {}
57 __gshared ScreenRebuildEvent evScrRebuild;
58 __gshared ScreenRepaintEvent evScreenRepaint;
59 __gshared CursorBlinkEvent evCurBlink;
60 __gshared HideMouseEvent evHideMouse;
62 shared static this () {
63 evScrRebuild = new ScreenRebuildEvent();
64 evScreenRepaint = new ScreenRepaintEvent();
65 evCurBlink = new CursorBlinkEvent();
66 evHideMouse = new HideMouseEvent();
70 // ////////////////////////////////////////////////////////////////////////// //
71 public void postScreenRebuild () { if (vbwin !is null && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScrRebuild); }
72 public void postScreenRepaint () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postEvent(evScreenRepaint); }
73 public void postScreenRepaintDelayed () { if (vbwin !is null && !vbwin.eventQueued!ScreenRepaintEvent && !vbwin.eventQueued!ScreenRebuildEvent) vbwin.postTimeout(evScreenRepaint, 35); }
75 public void postCurBlink () {
76 if (vbwin !is null && !vbwin.eventQueued!CursorBlinkEvent) {
77 //conwriteln("curblink posted!");
78 vbwin.postTimeout(evCurBlink, 100);
79 //vbwin.postTimeout(evCurBlink, 500);
84 // ////////////////////////////////////////////////////////////////////////// //
85 __gshared MonoTime lastMouseMove;
86 __gshared uint MouseHideTime = 3000;
89 shared static this () {
90 conRegVar("mouse_hide_time", "mouse cursor hiding time (in milliseconds); 0 to not hide it",
91 delegate (self) {
92 return MouseHideTime;
94 delegate (self, uint nv) {
95 if (MouseHideTime != nv) {
96 if (MouseHideTime == 0) mouseMoved();
97 MouseHideTime = nv;
98 conwriteln("mouse hiding time: ", nv);
105 //==========================================================================
107 // mshtime_dbg
109 //==========================================================================
110 public int mshtime_dbg () {
111 if (MouseHideTime > 0) {
112 auto ctt = MonoTime.currTime;
113 auto mt = (ctt-lastMouseMove).total!"msecs";
114 return cast(int)mt;
115 } else {
116 return 0;
121 //==========================================================================
123 // isMouseVisible
125 //==========================================================================
126 public bool isMouseVisible () {
127 if (MouseHideTime > 0) {
128 auto ctt = MonoTime.currTime;
129 return ((ctt-lastMouseMove).total!"msecs" < MouseHideTime+500);
130 } else {
131 return true;
136 //==========================================================================
138 // mouseAlpha
140 //==========================================================================
141 public float mouseAlpha () {
142 if (MouseHideTime > 0) {
143 auto ctt = MonoTime.currTime;
144 auto msc = (ctt-lastMouseMove).total!"msecs";
145 if (msc >= MouseHideTime+500) return 0.0f;
146 if (msc < MouseHideTime) return 1.0f;
147 msc -= MouseHideTime;
148 return 1.0f-msc/500.0f;
149 } else {
150 return 1.0f;
155 //==========================================================================
157 // repostHideMouse
159 // returns `true` if mouse should be redrawn
161 //==========================================================================
162 public bool repostHideMouse () {
163 if (vbwin is null || vbwin.eventQueued!HideMouseEvent) return false;
164 if (MouseHideTime > 0) {
165 auto ctt = MonoTime.currTime;
166 auto tms = (ctt-lastMouseMove).total!"msecs";
167 if (tms >= MouseHideTime) {
168 if (tms >= MouseHideTime+500) return true; // hide it
169 vbwin.postTimeout(evHideMouse, 50);
170 return true; // fade it
172 vbwin.postTimeout(evHideMouse, cast(int)(MouseHideTime-tms));
174 return false;
178 //==========================================================================
180 // mouseMoved
182 //==========================================================================
183 public void mouseMoved () {
184 if (MouseHideTime > 0) {
185 lastMouseMove = MonoTime.currTime;
186 if (vbwin !is null && !vbwin.eventQueued!HideMouseEvent) vbwin.postTimeout(evHideMouse, MouseHideTime);
191 //==========================================================================
193 // drawTextCursor
195 //==========================================================================
196 public void drawTextCursor (bool active, int x, int y, int hgt=-666) {
197 if (hgt == -666) hgt = gxTextHeightUtf;
198 if (hgt < 1) return;
199 if (active) {
200 auto ctt = (MonoTime.currTime.ticks*1000/MonoTime.ticksPerSecond)/100;
201 int doty = ctt%(hgt*2-1);
202 if (doty >= hgt) doty = hgt*2-doty-1;
203 gxVLine(x, y, hgt, (ctt%10 < 5 ? gxRGB!(255, 255, 255) : gxRGB!(200, 200, 200)));
204 gxPutPixel(x, y+doty, gxRGB!(0, 255, 255));
205 postCurBlink();
206 } else {
207 gxVLine(x, y, hgt, gxRGB!(170, 170, 170));
212 // ////////////////////////////////////////////////////////////////////////// //
213 private __gshared SubWindow subwinLast;
214 private __gshared bool ignoreSubWinChar = false;
215 // make a package and move that to package
216 private __gshared SubWindow subwinDrag = null;
217 private __gshared int subwinDragXSpot, subwinDragYSpot;
220 public @property bool isSubWinDragging () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null); }
221 public @property bool isSubWinDraggingKeyboard () nothrow @trusted @nogc { pragma(inline, true); return (subwinDrag !is null && subwinDragXSpot == int.min && subwinDragYSpot == int.min); }
224 //==========================================================================
226 // eguiLostGlobalFocus
228 //==========================================================================
229 public void eguiLostGlobalFocus () {
230 ignoreSubWinChar = false;
231 subwinDrag = null;
235 private bool insertSubWindow (SubWindow nw) nothrow @trusted @nogc {
236 if (nw is null || nw.mClosed) return false;
237 assert(nw.mPrev is null);
238 assert(nw.mNext is null);
239 assert(!nw.mInWinList);
240 if (nw.mType == SubWindow.Type.OnBottom) {
241 SubWindow w = subwinLast;
242 if (w !is null) {
243 while (w.mPrev !is null) w = w.mPrev;
244 nw.mNext = w;
245 if (w !is null) w.mPrev = nw; else subwinLast = nw;
246 } else {
247 subwinLast = nw;
249 nw.mInWinList = true;
250 return true;
252 SubWindow aw = getActiveSubWindow();
253 assert(aw !is nw);
254 if (nw.mType == SubWindow.Type.OnTop || aw is null) {
255 nw.mPrev = subwinLast;
256 if (subwinLast !is null) subwinLast.mNext = nw;
257 subwinLast = nw;
258 nw.mInWinList = true;
259 return true;
261 if (aw.mModal && !nw.mModal) return false; // can't insert normal windows while modal window is active
262 // insert after aw
263 nw.mPrev = aw;
264 nw.mNext = aw.mNext;
265 aw.mNext = nw;
266 if (nw.mNext !is null) nw.mNext.mPrev = nw;
267 if (aw is subwinLast) subwinLast = nw;
268 nw.mInWinList = true;
269 return true;
273 private bool removeSubWindow (SubWindow nw) nothrow @trusted @nogc {
274 if (nw is null || !nw.mInWinList) return false;
275 if (nw.mPrev !is null) nw.mPrev.mNext = nw.mNext;
276 if (nw.mNext !is null) nw.mNext.mPrev = nw.mPrev;
277 if (nw is subwinLast) subwinLast = nw.mPrev;
278 nw.mPrev = null;
279 nw.mNext = null;
280 nw.mInWinList = false;
281 return true;
285 //==========================================================================
287 // mouse2xy
289 //==========================================================================
290 public void mouse2xy (MouseEvent event, out int mx, out int my) nothrow @trusted @nogc {
291 mx = event.x/screenEffScale;
292 my = event.y/screenEffScale;
296 //==========================================================================
298 // subWindowAt
300 //==========================================================================
301 public SubWindow subWindowAt (int mx, int my) nothrow @trusted @nogc {
302 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
303 if (!w.closed) {
304 if (w.mMinimized) {
305 if (mx >= w.winminx && my >= w.winminy && mx < w.winminx+w.MinSizeX && my < w.winminy+w.MinSizeY) return w;
306 } else {
307 if (mx >= w.winx && my >= w.winy && mx < w.winx+w.winw && my < w.winy+w.winh) return w;
311 return null;
315 //==========================================================================
317 // subWindowAt
319 //==========================================================================
320 public SubWindow subWindowAt (MouseEvent event) nothrow @trusted @nogc {
321 pragma(inline, true);
322 return subWindowAt(event.x/screenEffScale, event.y/screenEffScale);
326 //==========================================================================
328 // getActiveSubWindow
330 //==========================================================================
331 public SubWindow getActiveSubWindow () nothrow @trusted @nogc {
332 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
333 if (!w.mClosed && w.type != SubWindow.Type.OnTop && !w.mMinimized) return w;
335 return null;
339 //==========================================================================
341 // dispatchEvent
343 //==========================================================================
344 public bool dispatchEvent (KeyEvent event) {
345 bool res = false;
346 if (isSubWinDragging) {
347 if (isSubWinDraggingKeyboard) {
348 if (!event.pressed) return true;
349 if (event == "Left") subwinDrag.x0 = subwinDrag.x0-1;
350 else if (event == "Right") subwinDrag.x0 = subwinDrag.x0+1;
351 else if (event == "Up") subwinDrag.y0 = subwinDrag.y0-1;
352 else if (event == "Down") subwinDrag.y0 = subwinDrag.y0+1;
353 else if (event == "C-Left") subwinDrag.x0 = subwinDrag.x0-8;
354 else if (event == "C-Right") subwinDrag.x0 = subwinDrag.x0+8;
355 else if (event == "C-Up") subwinDrag.y0 = subwinDrag.y0-8;
356 else if (event == "C-Down") subwinDrag.y0 = subwinDrag.y0+8;
357 else if (event == "Home") subwinDrag.x0 = 0;
358 else if (event == "End") subwinDrag.x0 = screenWidth-subwinDrag.width;
359 else if (event == "PageUp") subwinDrag.y0 = 0;
360 else if (event == "PageDown") subwinDrag.y0 = screenHeight-subwinDrag.height;
361 else if (event == "Escape" || event == "Enter") subwinDrag = null;
362 postScreenRebuild();
363 return true;
365 } else if (auto aw = getActiveSubWindow()) {
366 res = aw.onKey(event);
367 if (res) postScreenRebuild();
369 return res;
373 //==========================================================================
375 // dispatchEvent
377 //==========================================================================
378 public bool dispatchEvent (MouseEvent event) {
379 __gshared SubWindow lastHover = null;
381 if (subwinLast is null) { postScreenRepaint(); return false; }
383 int mx = lastMouseX;
384 int my = lastMouseY;
385 auto aw = getActiveSubWindow();
386 auto msw = subWindowAt(event);
387 scope(exit) {
388 if (msw !is lastHover) {
389 lastHover = msw;
390 postScreenRebuild();
393 bool curIsModal = (aw !is null && aw.mModal);
395 // switch window by button press
396 if (event.type == MouseEventType.buttonReleased && msw !is aw && !curIsModal) {
397 if (msw !is null && msw.mType == SubWindow.Type.Normal) {
398 msw.bringToFront();
399 postScreenRepaint();
400 return true;
404 // drag
405 if (isSubWinDragging) {
406 if (!isSubWinDraggingKeyboard) {
407 subwinDrag.x0 = mx+subwinDragXSpot;
408 subwinDrag.y0 = my+subwinDragYSpot;
409 // stop drag?
410 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) subwinDrag = null;
411 postScreenRebuild();
412 } else {
413 postScreenRepaint();
415 return true;
418 if (msw is null || msw !is aw || msw.mMinimized) {
419 if (msw is null || !msw.onTop) {
420 postScreenRepaint();
421 return false;
424 assert(msw !is null);
426 if (msw.onMouse(event)) {
427 postScreenRebuild();
428 return true;
431 postScreenRepaint();
432 return false;
436 //==========================================================================
438 // dispatchEvent
440 //==========================================================================
441 public bool dispatchEvent (dchar ch) {
442 if (ignoreSubWinChar) { ignoreSubWinChar = false; return (subwinLast !is null); }
443 bool res = false;
444 if (!isSubWinDragging) {
445 if (auto aw = getActiveSubWindow()) {
446 res = aw.onChar(ch);
447 if (res) postScreenRebuild();
450 return res;
454 //==========================================================================
456 // paintSubWindows
458 //==========================================================================
459 public void paintSubWindows () {
460 // get first window
461 SubWindow firstWin = subwinLast;
462 if (firstWin is null) return;
463 while (firstWin.mPrev !is null) firstWin = firstWin.mPrev;
465 SubWindow firstMin, firstNormal, firstTop;
466 SubWindow lastMin, lastNormal, lastTop;
468 void doDraw (SubWindow w) {
469 if (w !is null) {
470 gxClipReset();
471 w.onPaint();
472 if (w is subwinDrag) { gxClipReset(); gxFillRect(w.x0, w.y0, w.width, w.height, gxRGBA!(255, 127, 0, 176)); }
476 // paint background windows
477 for (SubWindow w = firstWin; w !is null; w = w.mNext) {
478 if (w.mClosed) continue;
479 if (w.mMinimized) {
480 if (firstMin is null) firstMin = w;
481 lastMin = w;
482 } else if (w.mType == SubWindow.Type.Normal) {
483 if (firstNormal is null) firstNormal = w;
484 lastNormal = w;
485 } else if (w.mType == SubWindow.Type.OnTop) {
486 if (firstTop is null) firstTop = w;
487 lastTop = w;
488 } else if (w.mType == SubWindow.Type.OnBottom) {
489 doDraw(w);
493 // paint minimized windows
494 for (SubWindow w = firstMin; w !is null; w = w.mNext) {
495 if (!w.mClosed && w.mMinimized) doDraw(w);
496 if (w is lastMin) break;
499 // paint normal windows
500 for (SubWindow w = firstNormal; w !is null; w = w.mNext) {
501 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.Normal) doDraw(w);
502 if (w is lastNormal) break;
505 // paint ontop windows
506 for (SubWindow w = firstTop; w !is null; w = w.mNext) {
507 if (!w.mClosed && !w.mMinimized && w.mType == SubWindow.Type.OnTop) doDraw(w);
508 if (w is lastTop) break;
511 // paint hint for minimized window
512 if (auto msw = subWindowAt(lastMouseX, lastMouseY)) {
513 if (!msw.mClosed && msw.mMinimized && msw.title.length) {
514 auto wdt = gxTextWidthUtf(msw.title)+2;
515 auto hgt = gxTextHeightUtf+2;
516 int y = msw.winminy-hgt;
517 int x;
518 if (wdt >= screenWidth) {
519 x = (screenWidth-wdt)/2;
520 } else {
521 x = (msw.winminx+msw.MinSizeX)/2-wdt/2;
522 if (x < 0) x = 0;
524 gxClipReset();
525 gxFillRect(x, y, wdt, hgt, gxRGB!(255, 255, 255));
526 gxDrawTextUtf(x+1, y+1, msw.title, gxRGB!(0, 0, 0));
532 // ////////////////////////////////////////////////////////////////////////// //
533 public abstract class SubWindow {
534 protected:
535 enum Type {
536 Normal,
537 OnTop,
538 OnBottom,
541 protected:
542 SubWindow mPrev, mNext;
543 Type mType = Type.Normal;
544 bool mMinimized;
545 bool mInWinList;
546 bool mModal;
547 bool mClosed;
548 int awidx; // active widget index
549 Widget[] widgets;
551 public:
552 int winminx, winminy;
553 int winx, winy, winw, winh;
554 string title;
555 uint clrWinFrame = gxRGB!(255, 255, 255);
556 uint clrWinTitleBack = gxRGB!(255, 255, 255);
557 uint clrWinTitle = gxRGB!(0, 0, 0);
558 uint clrWinBack = gxRGB!(0, 0, 180);
559 uint clrWinText = gxRGB!(255, 255, 255);
561 public:
562 this () {}
564 this (string atitle, int ax, int ay, int aw, int ah) {
565 title = atitle;
566 winx = ax;
567 winy = ay;
568 winw = aw;
569 winh = ah;
572 this (string atitle, int aw, int ah) {
573 title = atitle;
574 winx = (screenWidth-aw)/2;
575 winy = (screenHeight-ah)/2;
576 winw = aw;
577 winh = ah;
580 final @property int x0 () const pure nothrow @safe @nogc { return (mMinimized ? winminx : winx); }
581 final @property int y0 () const pure nothrow @safe @nogc { return (mMinimized ? winminy : winy); }
582 final @property int width () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeX : winw); }
583 final @property int height () const pure nothrow @safe @nogc { return (mMinimized ? MinSizeY : winh); }
585 final @property void x0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminx = v; else winx = v; }
586 final @property void y0 (int v) pure nothrow @safe @nogc { if (mMinimized) winminy = v; else winy = v; }
588 final void setSize (int awidth, int aheight) nothrow @trusted @nogc {
589 if (awidth > 0) winw = awidth;
590 if (aheight > 0) winh = aheight;
593 final void setClientSize (int awidth, int aheight) nothrow @trusted {
594 if (awidth > 0) winw = awidth+decorationSizeX;
595 if (aheight > 0) winh = aheight+decorationSizeY;
598 final void centerWindow () nothrow @trusted @nogc {
599 winx = (screenWidth-winw)/2;
600 winy = (screenHeight-winh)/2;
603 final @property SubWindow prev () pure nothrow @safe @nogc { return mPrev; }
604 final @property SubWindow next () pure nothrow @safe @nogc { return mNext; }
606 final @property Type type () const pure nothrow @safe @nogc { return mType; }
607 final @property bool onTop () const pure nothrow @safe @nogc { return (mType == Type.OnTop); }
608 final @property bool onBottom () const pure nothrow @safe @nogc { return (mType == Type.OnBottom); }
610 final @property bool inWinList () const pure nothrow @safe @nogc { return mInWinList; }
612 final @property bool modal () const pure nothrow @safe @nogc { return mModal; }
613 final @property bool closed () const pure nothrow @safe @nogc { return mClosed; }
615 final @property bool active () const nothrow @trusted @nogc {
616 if (!mInWinList || mClosed || mMinimized) return false;
617 return (getActiveSubWindow is this);
620 @property int decorationSizeX () const nothrow @safe { return 2*2; }
621 @property int decorationSizeY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1+2; }
623 @property int clientOffsetX () const nothrow @safe { return 2; }
624 @property int clientOffsetY () const nothrow @safe { return (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+2)+1; }
626 final @property int clientWidth () const nothrow @safe { return winw-decorationSizeX; }
627 final @property int clientHeight () const nothrow @safe { return winh-decorationSizeY; }
629 void addWidget (Widget w) {
630 if (w is null) return;
631 if (w.parent is this) return;
632 if (w.parent !is null) throw new Exception("can't add widget owned by another window");
633 w.parent = this;
634 widgets ~= w;
637 final @property void activeWidget (Widget w) nothrow @trusted @nogc {
638 if (w is null) return;
639 foreach (immutable idx, Widget ww; widgets[]) {
640 if (w is ww) {
641 awidx = cast(int)idx;
642 return;
647 final @property Widget activeWidget () nothrow @trusted @nogc { return (awidx >= 0 && awidx < widgets.length ? widgets.ptr[awidx] : null); }
649 final int widgetAtXYIndex (int x, int y) nothrow @trusted {
650 foreach_reverse (immutable widx, Widget w; widgets) {
651 if (x >= w.x0 && y >= w.y0 && x <= w.x1 && y <= w.y1) return cast(int)widx;
653 return -1;
656 final Widget widgetAtXY (int x, int y) nothrow @trusted {
657 auto idx = widgetAtXYIndex(x, y);
658 return (idx >= 0 && idx < widgets.length ? widgets[idx] : null);
661 void doShiftTab () {
662 if (widgets.length > 0) {
663 foreach (immutable _; 0..widgets.length) {
664 if (awidx < 0) awidx = 0; else if (awidx >= widgets.length) awidx = cast(int)widgets.length;
665 --awidx;
666 if (awidx < 0) awidx = cast(int)(widgets.length-1);
667 if (widgets[awidx].tabStop) break;
672 void doTab () {
673 if (widgets.length > 0) {
674 foreach (immutable _; 0..widgets.length) {
675 if (awidx < 0) awidx = 0; else if (awidx >= widgets.length) awidx = cast(int)widgets.length;
676 ++awidx;
677 if (awidx >= widgets.length) awidx = 0;
678 if (widgets[awidx].tabStop) break;
683 protected void drawWidgets () {
684 foreach (Widget w; widgets) w.onPaint();
687 // draw window frame and background in "normal" state
688 protected void drawWindowNormal () {
689 setupClip();
690 gxDrawWindow(title, clrWinFrame, clrWinTitle, clrWinTitleBack, clrWinBack);
693 // draw window frame and background in "minimized" state
694 protected void drawWindowMinimized () {
695 gxClipRect.x0 = winminx;
696 gxClipRect.y0 = winminy;
697 gxClipRect.x1 = winminx+MinSizeX-1;
698 gxClipRect.y1 = winminy+MinSizeY-1;
699 gxFillRect(winminx, winminy, MinSizeX, MinSizeY, clrWinBack);
700 gxDrawRect(winminx, winminy, MinSizeX, MinSizeY, clrWinFrame);
703 void startMouseDrag (MouseEvent event) {
704 int mx, my;
705 event.mouse2xy(mx, my);
706 subwinDrag = this;
707 subwinDragXSpot = x0-mx;
708 subwinDragYSpot = y0-my;
709 postScreenRebuild();
712 void startKeyboardDrag () {
713 subwinDrag = this;
714 subwinDragXSpot = int.min;
715 subwinDragYSpot = int.min;
716 postScreenRebuild();
719 void stopDrag () {
720 if (subwinDrag is this) {
721 subwinDrag = null;
722 postScreenRebuild();
726 void onPaint () {
727 if (closed) return;
728 if (!mMinimized) {
729 drawWindowNormal();
730 drawWidgets();
731 } else {
732 drawWindowMinimized();
736 bool onKey (KeyEvent event) {
737 if (closed) return false;
738 if (mMinimized) return false;
739 if (awidx >= 0 && awidx < widgets.length) {
740 if (widgets[awidx].onKey(event)) return true;
742 if (event.pressed) {
743 if (event == "C-F5") { startKeyboardDrag(); return true; }
744 if (event == "Tab") { doTab(); return true; }
745 if (event == "S-Tab") { doShiftTab(); return true; }
746 if (event == "M-M" && !mModal) { minimize(); return true; }
748 foreach_reverse (immutable aidx, Widget w; widgets) {
749 if (aidx != awidx && w.onKey(event)) return true;
751 return false;
754 bool onMouse (MouseEvent event) {
755 if (!active) return false;
756 if (closed) return false;
758 int mx, my;
759 event.mouse2xy(mx, my);
761 // start drag?
762 if (subwinDrag is null && event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
763 if (mx >= x0 && my >= y0 && mx < x0+width && my < (!mMinimized ? y0+gxTextHeightUtf+2 : height)) {
764 startMouseDrag(event);
765 return true;
769 if (mMinimized) return false;
771 if (mx >= winx && my >= winy && mx < winx+winw && my < winy+gxTextHeightUtf) {
772 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.right) {
773 if (!mModal && mType == Type.Normal) minimize();
774 return true;
777 if (awidx >= 0 && awidx < widgets.length) {
778 auto msw = widgetAtXY(mx, my);
779 if (msw !is null && msw.onMouse(event)) return true;
781 return false;
784 bool onChar (dchar ch) {
785 if (!active) return false;
786 if (closed) return false;
787 if (mMinimized) return false;
788 if (awidx >= 0 && awidx < widgets.length) {
789 if (widgets[awidx].onChar(ch)) return true;
790 foreach_reverse (immutable aidx, Widget w; widgets) {
791 if (aidx != awidx && w.onChar(ch)) return true;
794 return false;
797 void setupClip () {
798 gxClipReset();
799 gxClipRect.x0 = winx;
800 gxClipRect.x1 = winx+winw-1;
801 gxClipRect.y0 = winy;
802 gxClipRect.y1 = winy+winh-1;
805 final void setupClientClip () {
806 setupClip();
807 gxClipRect.intersect(GxRect(
808 GxPoint(winx+clientOffsetX, winy+clientOffsetY),
809 GxPoint(winx+clientOffsetX+clientWidth-1, winy+clientOffsetY+clientHeight-1)));
812 void close () {
813 mClosed = true;
814 if (removeSubWindow(this)) postScreenRebuild();
817 protected bool addToSubwinList (bool asModal, bool fromKeyboard) {
818 if (fromKeyboard) ignoreSubWinChar = true;
819 if (mInWinList) return true;
820 mModal = asModal;
821 if (insertSubWindow(this)) {
822 postScreenRebuild();
823 return true;
825 return false;
828 void add (bool fromKeyboard=false) { addToSubwinList(false, fromKeyboard); }
830 void addModal (bool fromKeyboard=false) { addToSubwinList(true, fromKeyboard); }
832 void bringToFront () {
833 if (mClosed || !mInWinList) return;
834 auto aw = getActiveSubWindow();
835 if (aw is this) { mMinimized = false; return; }
836 if (aw !is null && aw.mModal) return; // alas
837 removeSubWindow(this);
838 mMinimized = false;
839 insertSubWindow(this);
840 if (subwinDrag !is this) subwinDrag = null;
841 postScreenRebuild();
844 @property bool minimized () const { return mMinimized; }
846 @property void minimized (bool v) {
847 if (v == mMinimized) return;
848 if (v) minimize(); else bringToFront();
851 void minimize () {
852 if (mClosed || mMinimized) return;
853 if (!mInWinList) { mMinimized = true; return; }
854 if (mModal) return;
855 assert(subwinLast !is null);
856 findMinimizedPos(winminx, winminy);
857 auto aw = getActiveSubWindow();
858 if (aw is this) subwinDrag = null;
859 mMinimized = true;
860 postScreenRebuild();
863 void restore () {
864 bringToFront();
867 public:
868 enum MinSizeX = 16;
869 enum MinSizeY = 16;
870 enum MinMarginX = 3;
871 enum MinMarginY = 3;
873 protected:
874 static findMinimizedPos (out int wx, out int wy) {
875 static bool isOccupied (int x, int y) {
876 for (SubWindow w = subwinLast; w !is null; w = w.mPrev) {
877 if (w.mInWinList && !w.closed && w.mMinimized) {
878 if (x >= w.winminx && y >= w.winminy && x < w.winminx+MinSizeX && y < w.winminy+MinSizeY) return true;
881 return false;
884 int txcount = screenWidth/(MinSizeX+MinMarginX);
885 //int tycount = screenHeight/(MinSizeY+MinMarginY);
886 if (txcount < 1) txcount = 1;
887 //if (tycount < 1) tycount = 1;
888 foreach (immutable int n; 0..6/*5535*/) {
889 int x = (n%txcount)*(MinSizeX+MinMarginX)+1;
890 int y = screenHeight-MinSizeY-(n/txcount)*(MinSizeY+MinMarginY);
891 //conwriteln("trying (", x, ",", y, ")");
892 if (!isOccupied(x, y)) { wx = x; wy = y; /*conwriteln(" HIT!");*/ return; }