1 // Written in the D programming language.
2 
3 /**
4 This module contains common Plaform definitions.
5 
6 Platform is abstraction layer for application.
7 
8 
9 Synopsis:
10 
11 ----
12 import dlangui.platforms.common.platform;
13 
14 ----
15 
16 Copyright: Vadim Lopatin, 2014
17 License:   Boost License 1.0
18 Authors:   Vadim Lopatin, coolreader.org@gmail.com
19 */
20 module dlangui.platforms.common.platform;
21 
22 public import dlangui.core.config;
23 public import dlangui.core.events;
24 import dlangui.core.collections;
25 import dlangui.widgets.widget;
26 import dlangui.widgets.popup;
27 import dlangui.widgets.scrollbar;
28 import dlangui.graphics.drawbuf;
29 import dlangui.core.stdaction;
30 import dlangui.core.asyncsocket;
31 
32 static if (ENABLE_OPENGL) {
33     private import dlangui.graphics.gldrawbuf;
34 }
35 private import std.algorithm;
36 private import core.sync.mutex;
37 private import std.string;
38 
39 /// entry point - declare such function to use as main for dlangui app
40 extern(C) int UIAppMain(string[] args);
41 
42 
43 // specify debug=DebugMouseEvents for logging mouse handling
44 // specify debug=DebugRedraw for logging drawing and layouts handling
45 // specify debug=DebugKeys for logging of key events
46 
47 /// window creation flags
48 enum WindowFlag : uint {
49     /// window can be resized
50     Resizable = 1,
51     /// window should be shown in fullscreen mode
52     Fullscreen = 2,
53     /// modal window - grabs input focus
54     Modal = 4,
55     /// measure window size on window.show() - helps if you want scrollWindow but on show() you want to set window to mainWidget measured size
56     MeasureSize = 8,
57 }
58 
59 /// Window states
60 enum WindowState : int {
61     /// state is unknown (not supported by platform?), as well for using in setWindowState when only want to activate window or change its size/position
62     unspecified,
63     /// normal state
64     normal,
65     /// window is maximized
66     maximized,
67     /// window is maximized
68     minimized,
69     /// fullscreen mode (supported not on all platforms)
70     fullscreen,
71     /// application is paused (e.g. on Android)
72     paused,
73     /// window is hidden
74     hidden,
75     /// closed
76     closed,
77 }
78 
79 ///  Flags to set start window position on show
80 enum ShowPosition {
81     /// do nothing just show
82     dontCare,
83     /// window will be centered on parent window
84     parentWindowCenter
85 }
86 
87 /// Dialog display modes - used to configure dialogs should be showed as a popup or window
88 enum DialogDisplayMode : ulong {
89     /// show all types of dialogs in windows
90     allTypesOfDialogsInWindow = 0,
91     /// show file dialogs in popups
92     fileDialogInPopup = 1,
93     /// show message boxes in popups
94     messageBoxInPopup = 2,
95     /// show input boxes in popups
96     inputBoxInPopup = 4,
97     /// show settings dialogs in popups
98     settingsDialogInPopup = 8,
99     /// show user dialogs in popups - flag for user dialogs
100     userDialogInPopup = 16,
101     /// show all types of dialogs in popups
102     allTypesOfDialogsInPopup = fileDialogInPopup | messageBoxInPopup | inputBoxInPopup | settingsDialogInPopup | userDialogInPopup
103 }
104 
105 /// Sets what's should be done when window content is too big
106 enum WindowOrContentResizeMode {
107     /// widgets are shrink to fit in window
108     shrinkWidgets,
109     /// resize window when window is too small
110     resizeWindow,
111     /// add scrollbars to window
112     scrollWindow
113 }
114 
115 /// Window state signal listener
116 interface OnWindowStateHandler {
117     /// signal listener - called when state of window is changed
118     bool onWindowStateChange(Window window, WindowState winState, Rect rect);
119 }
120 
121 /// Window activate/deactivate signal listener
122 interface OnWindowActivityHandler {
123     /// signal listener - called when window activity is changed
124     bool onWindowActivityChange(Window window, bool isWindowActive);
125 }
126 
127 /// protected event list
128 /// references to posted messages can be stored here at least to keep live reference and avoid GC
129 /// as well, on some platforms it's easy to send id to message queue, but not pointer
130 class EventList {
131     protected Mutex _mutex;
132     protected Collection!CustomEvent _events;
133     this() {
134         _mutex = new Mutex();
135     }
136     ~this() {
137         destroy(_mutex);
138         _mutex = null;
139     }
140     /// puts event into queue, returns event's unique id
141     long put(CustomEvent event) {
142         _mutex.lock();
143         scope(exit) _mutex.unlock();
144         _events.pushBack(event);
145         return event.uniqueId;
146     }
147     /// return next event
148     CustomEvent get() {
149         _mutex.lock();
150         scope(exit) _mutex.unlock();
151         return _events.popFront();
152     }
153     /// return event by unique id
154     CustomEvent get(uint uniqueId) {
155         _mutex.lock();
156         scope(exit) _mutex.unlock();
157         for (int i = 0; i < _events.length; i++) {
158             if (_events[i].uniqueId == uniqueId) {
159                 return _events.remove(i);
160             }
161         }
162         // not found
163         return null;
164     }
165 }
166 
167 class TimerInfo {
168     static __gshared ulong nextId;
169 
170     this(Widget targetWidget, long intervalMillis) {
171         _id = ++nextId;
172         assert(intervalMillis >= 0 && intervalMillis < 7*24*60*60*1000L);
173         _targetWidget = targetWidget;
174         _interval = intervalMillis;
175         _nextTimestamp = currentTimeMillis + _interval;
176     }
177     /// cancel timer
178     void cancel() {
179         _targetWidget = null;
180     }
181     /// cancel timer
182     void notify() {
183         if (_targetWidget) {
184             _nextTimestamp = currentTimeMillis + _interval;
185             if (!_targetWidget.onTimer(_id)) {
186                 _targetWidget = null;
187             }
188         }
189     }
190     /// unique Id of timer
191     @property ulong id() { return _id; }
192     /// timer interval, milliseconds
193     @property long interval() { return _interval; }
194     /// next timestamp to invoke timer at, as per currentTimeMillis()
195     @property long nextTimestamp() { return _nextTimestamp; }
196     /// widget to route timer event to
197     @property Widget targetWidget() { return _targetWidget; }
198     /// return true if timer is not yet cancelled
199     @property bool valid() { return _targetWidget !is null; }
200 
201     protected ulong _id;
202     protected long _interval;
203     protected long _nextTimestamp;
204     protected Widget _targetWidget;
205 
206     override bool opEquals(Object obj) const {
207         TimerInfo b = cast(TimerInfo)obj;
208         if (!b)
209             return false;
210         return b._nextTimestamp == _nextTimestamp;
211     }
212     override int opCmp(Object obj) {
213         TimerInfo b = cast(TimerInfo)obj;
214         if (!b)
215             return false;
216         if (valid && !b.valid)
217             return -1;
218         if (!valid && b.valid)
219             return 1;
220         if (!valid && !b.valid)
221             return 0;
222         if (_nextTimestamp < b._nextTimestamp)
223             return -1;
224         if (_nextTimestamp > b._nextTimestamp)
225             return 1;
226         return 0;
227     }
228 }
229 
230 
231 /**
232  * Window abstraction layer. Widgets can be shown only inside window.
233  *
234  */
235 class Window : CustomEventTarget {
236     import dlangui.core.settings;
237 
238     protected int _dx;
239     protected int _dy;
240     protected uint _keyboardModifiers;
241     protected uint _backgroundColor;
242     protected Widget _mainWidget;
243     protected EventList _eventList;
244     protected uint _flags;
245     /// minimal good looking content width
246     protected int _minContentWidth;
247     /// minimal good looking content height
248     protected int _minContentHeight;
249 
250     // current content width calculated using _windowOrContentResizeMode flag, usually used in measure()
251     protected int _currentContentWidth;
252     // current content height calculated using _windowOrContentResizeMode flag, usually used in measure()
253     protected int _currentContentHeight;
254 
255     @property uint flags() { return _flags; }
256     @property uint backgroundColor() const { return _backgroundColor; }
257     @property void backgroundColor(uint color) { _backgroundColor = color; }
258     @property int width() const { return _dx; }
259     @property int height() const { return _dy; }
260     @property uint keyboardModifiers() const { return _keyboardModifiers; }
261     @property Widget mainWidget() { return _mainWidget; }
262     @property void mainWidget(Widget widget) {
263         if (_mainWidget !is null) {
264             _mainWidget.window = null;
265             destroy(_mainWidget);
266         }
267         _mainWidget = widget;
268         if (_mainWidget !is null)
269             _mainWidget.window = this;
270     }
271 
272     /// save window state to setting object
273     void saveWindowState(Setting setting) {
274         if (!setting)
275             return;
276         WindowState state = windowState;
277         Rect rect = windowRect;
278         if (state == WindowState.fullscreen 
279             || state == WindowState.minimized 
280             || state == WindowState.maximized
281             || state == WindowState.normal) {
282             //
283             setting.setInteger("windowState", state);
284             if (rect.right > 0 && rect.bottom > 0) {
285                 setting.setInteger("windowPositionLeft", rect.left);
286                 setting.setInteger("windowPositionTop", rect.top);
287                 setting.setInteger("windowWidth", rect.right);
288                 setting.setInteger("windowHeight", rect.bottom);
289             }
290         }
291     }
292 
293     /// restore window state from setting object
294     bool restoreWindowState(Setting setting) {
295         if (!setting)
296             return false;
297         WindowState state = cast(WindowState)setting.getInteger("windowState", WindowState.unspecified);
298         Rect rect;
299         rect.left = cast(int)setting.getInteger("windowPositionLeft", WindowState.unspecified);
300         rect.top = cast(int)setting.getInteger("windowPositionTop", 0);
301         int w = cast(int)setting.getInteger("windowWidth", 0);
302         int h = cast(int)setting.getInteger("windowHeight", 0);
303         if (w <= 0 || h <= 0)
304             return false;
305         rect.right = w;
306         rect.bottom = h;
307         if (correctWindowPositionOnScreen(rect) && (state == WindowState.fullscreen
308              || state == WindowState.minimized 
309              || state == WindowState.maximized
310              || state == WindowState.normal)) {
311              setWindowState(state, false, rect);
312              return true;
313         }
314         return false;
315     }
316 
317     /// check if window position inside screen bounds; try to correct if needed; returns true if position is ok.
318     bool correctWindowPositionOnScreen(ref Rect rect) {
319         // override to apply screen size bounds
320         return true;
321     }
322 
323     protected Rect _caretRect;
324     /// blinking caret position (empty rect if no blinking caret)
325     @property void caretRect(Rect rc) { _caretRect = rc; }
326     @property Rect caretRect() { return _caretRect; }
327 
328     protected bool _caretReplace;
329     /// blinking caret is in Replace mode if true, insert mode if false
330     @property void caretReplace(bool flg) { _caretReplace = flg; }
331     @property bool caretReplace() { return _caretReplace; }
332 
333     // window content resize mode
334     //protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.resizeWindow;
335     //protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.shrinkWidgets;
336     protected WindowOrContentResizeMode _windowOrContentResizeMode = WindowOrContentResizeMode.scrollWindow;
337 
338     @property WindowOrContentResizeMode windowOrContentResizeMode() {return _windowOrContentResizeMode; }
339     @property void windowOrContentResizeMode(WindowOrContentResizeMode newMode) {
340         _windowOrContentResizeMode = newMode;
341         if (_mainWidget) {
342             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
343             adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight);
344         }
345     }
346 
347     protected ShowPosition _showPosition = ShowPosition.parentWindowCenter;
348 
349     ///returns current window show position (don't care or parent center)
350     @property ShowPosition showPosition() {
351         return _showPosition;
352     }
353 
354     /// sets window position after show (don't care or parent center)
355     @property void showPosition(ShowPosition newPosition) {
356         _showPosition = newPosition;
357     }
358 
359     // Abstract methods : override in platform implementation
360 
361     /// show window
362     abstract void show();
363     /// returns window caption
364     abstract @property dstring windowCaption();
365     /// sets window caption
366     abstract @property void windowCaption(dstring caption);
367     /// sets window icon
368     abstract @property void windowIcon(DrawBufRef icon);
369     /// request window redraw
370     abstract void invalidate();
371     /// close window
372     abstract void close();
373     /// returns parent window
374     abstract @property Window parentWindow();
375 
376     protected WindowState _windowState = WindowState.normal;
377     /// returns current window state
378     @property WindowState windowState() {
379         return _windowState;
380     }
381 
382     protected Rect _windowRect = RECT_VALUE_IS_NOT_SET;
383     /// returns window rectangle on screen (includes window frame and title)
384     @property Rect windowRect() {
385         if (_windowRect != RECT_VALUE_IS_NOT_SET)
386             return _windowRect;
387         // fake window rectangle -- at position 0,0 and
388         return Rect(0, 0, _dx, _dy);
389     }
390     /// window state change signal
391     Signal!OnWindowStateHandler windowStateChanged;
392     /// update and signal window state and/or size/positon changes - for using in platform inplementations
393     protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
394         bool signalWindow = false;
395         if (newState != WindowState.unspecified && newState != _windowState) {
396             _windowState = newState;
397             //Log.d("Window ", windowCaption, " has new state - ", newState);
398             signalWindow = true;
399         }
400         if (newWindowRect != RECT_VALUE_IS_NOT_SET && newWindowRect != _windowRect) {
401             _windowRect = newWindowRect;
402             //Log.d("Window ", windowCaption, " rect changed - ", newWindowRect);
403             signalWindow = true;
404         }
405 
406         if (signalWindow && windowStateChanged.assigned)
407             windowStateChanged(this, newState, newWindowRect);
408     }
409 
410     /// change window state, position, or size; returns true if successful, false if not supported by platform
411     bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
412         // override for particular platforms
413         return false;
414     }
415     /// maximize window
416     bool maximizeWindow(bool activate = false) { return setWindowState(WindowState.maximized, activate); }
417     /// minimize window
418     bool minimizeWindow() { return setWindowState(WindowState.minimized); }
419     /// restore window if maximized/minimized/hidden
420     bool restoreWindow(bool activate = false) { return setWindowState(WindowState.normal, activate); }
421     /// restore window if maximized/minimized/hidden
422     bool hideWindow() { return setWindowState(WindowState.hidden); }
423     /// just activate window
424     bool activateWindow() { return setWindowState(WindowState.unspecified, true); }
425     /// change window position only
426     bool moveWindow(Point topLeft, bool activate = false) { return setWindowState(WindowState.unspecified, activate, Rect(topLeft.x, topLeft.y, int.min, int.min)); }
427     /// change window size only
428     bool resizeWindow(Point sz, bool activate = false) { return setWindowState(WindowState.unspecified, activate, Rect(int.min, int.min, sz.x, sz.y)); }
429     /// set window rectangle
430     bool moveAndResizeWindow(Rect rc, bool activate = false) { return setWindowState(WindowState.unspecified, activate, rc); }
431 
432     /// centers window on parent window, do nothing if there is no parent window
433     void centerOnParentWindow() {
434         if (parentWindow) {
435             Rect parentRect = parentWindow.windowRect();
436             Point newPos;
437             newPos.x = parentRect.left + (parentRect.right - _windowRect.right) / 2;
438             newPos.y = parentRect.top + (parentRect.bottom - _windowRect.bottom) / 2;
439             moveWindow(newPos);
440         }
441     }
442 
443     ///adjust window position during show()
444     protected void adjustPositionDuringShow() {
445         final switch (_showPosition) {
446             case ShowPosition.dontCare:
447                 return;
448             case ShowPosition.parentWindowCenter: {
449                 centerOnParentWindow();
450                 break;
451             }
452         }
453     }
454 
455     // things needed for WindowOrContentResizeMode.scrollWindow:
456     /// vertical scrollbar control
457     protected ScrollBar _vScrollBar = null;
458     /// horizontal scrollbar control
459     protected ScrollBar _hScrollBar = null;
460 
461     /// Sets the minimal content size and adjust window or content should be called from window show
462     void adjustWindowOrContentSize(int minContentWidth, int minContentHeight) {
463         _minContentWidth = minContentWidth;
464         _minContentHeight = minContentHeight;
465         if (_windowOrContentResizeMode == WindowOrContentResizeMode.resizeWindow)
466             resizeWindow(Point(minContentWidth, minContentHeight));
467         updateWindowOrContentSize();
468     }
469 
470     /// update current content size based on windowOrContentResizeMode flag, usually used when window is resized
471     void updateWindowOrContentSize() {
472         final switch (_windowOrContentResizeMode) {
473             case WindowOrContentResizeMode.shrinkWidgets: {
474                 _currentContentWidth = _windowRect.right;
475                 _currentContentHeight = _windowRect.bottom;
476                 return;
477             }
478             case WindowOrContentResizeMode.resizeWindow: {
479                     //maybe should not permit to resize when new window size is smaller than content size, now do nothing
480                 _currentContentWidth = _windowRect.right;
481                 _currentContentHeight = _windowRect.bottom;
482                 break;
483             }
484             case WindowOrContentResizeMode.scrollWindow: {
485                 if (_windowRect.right < _minContentWidth) {
486                     // create scrollbar
487                     _currentContentWidth = _minContentWidth;
488                     if (!_hScrollBar) {
489                         _hScrollBar = new ScrollBar(null,Orientation.Horizontal);
490                         _hScrollBar.scrollEvent = delegate bool (AbstractSlider source, ScrollEvent event) {
491                             if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased)
492                                 requestLayout();
493                             else if (event.action == ScrollAction.PageUp) {
494                                 source.position = max(0, source.position - _windowRect.right * 3 / 4);
495                                 requestLayout();
496                             } else if (event.action == ScrollAction.PageDown) {
497                                 source.position = min(source.maxValue - _windowRect.right, source.position + _windowRect.right *3 / 4);
498                                 requestLayout();
499                             } else if (event.action == ScrollAction.LineUp) {
500                                 source.position = max(0, source.position - _windowRect.right / 10);
501                                 requestLayout();
502                             } else if (event.action == ScrollAction.LineDown) {
503                                 source.position = min(source.maxValue - _windowRect.right, source.position + _windowRect.right / 10);
504                                 requestLayout();
505                             }
506                             return true;
507                         };
508                     }
509                     _hScrollBar.measure(_windowRect.right, _windowRect.bottom);
510                     if (windowRect().bottom < _minContentHeight)
511                         _hScrollBar.setRange(0, _minContentWidth + _hScrollBar.measuredHeight);
512                     else
513                         _hScrollBar.setRange(0, _minContentWidth);
514                     _hScrollBar.pageSize(_windowRect.right);
515                     _hScrollBar.position(0);
516                 }
517                 else {
518                     if (_hScrollBar) {
519                         destroy(_hScrollBar);
520                         _hScrollBar = null;
521                     }
522                     _currentContentWidth = _windowRect.right;
523                 }
524 
525                 if (windowRect().bottom < _minContentHeight) {
526                     // create scrollbar
527                     _currentContentHeight = _minContentHeight;
528                     if (!_vScrollBar) {
529                         _vScrollBar = new ScrollBar(null,Orientation.Vertical);
530                         _vScrollBar.scrollEvent = delegate bool (AbstractSlider source, ScrollEvent event) {
531                             if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased)
532                                 requestLayout();
533                             else if (event.action == ScrollAction.PageUp) {
534                                 source.position = max(0, source.position - _windowRect.bottom * 3 / 4);
535                                 requestLayout();
536                             } else if (event.action == ScrollAction.PageDown) {
537                                 source.position = min(source.maxValue - _windowRect.bottom, source.position + _windowRect.bottom *3 / 4);
538                                 requestLayout();
539                             } else if (event.action == ScrollAction.LineUp) {
540                                 source.position = max(0, source.position - _windowRect.bottom / 10);
541                                 requestLayout();
542                             } else if (event.action == ScrollAction.LineDown) {
543                                 source.position = min(source.maxValue - _windowRect.bottom, source.position + _windowRect.bottom / 10);
544                                 requestLayout();
545                             }
546                             return true;
547                         };
548                     }
549                     _vScrollBar.measure(_windowRect.right, _windowRect.bottom);
550                     if (_hScrollBar)
551                         _vScrollBar.setRange(0, _minContentHeight+_hScrollBar.measuredHeight);
552                     else
553                         _vScrollBar.setRange(0, _minContentHeight);
554                     _vScrollBar.pageSize(windowRect().bottom);
555                     _vScrollBar.position(0);
556 
557                     if (!_hScrollBar)
558                         _currentContentWidth = _windowRect.right - _vScrollBar.measuredWidth;
559                 }
560                 else {
561                     if (_vScrollBar) {
562                         destroy(_vScrollBar);
563                         _vScrollBar = null;
564                     }
565                     _currentContentHeight = _hScrollBar ? _windowRect.bottom - _hScrollBar.measuredHeight : _windowRect.bottom;
566                 }
567                 return;
568             }
569         }
570     }
571 
572 
573     /// requests layout for main widget and popups
574     void requestLayout() {
575         if (_mainWidget)
576             _mainWidget.requestLayout();
577         if (_hScrollBar)
578             _hScrollBar.requestLayout();
579         if (_vScrollBar)
580             _vScrollBar.requestLayout();
581         foreach(p; _popups)
582             p.requestLayout();
583         if (_tooltip.popup)
584             _tooltip.popup.requestLayout();
585     }
586     void measure() {
587         if (_hScrollBar)
588             _hScrollBar.measure(_dx, _dy);
589 
590         if (_vScrollBar)
591             _vScrollBar.measure(_dx, _dy);
592 
593         if (_mainWidget !is null) {
594             _mainWidget.measure(_currentContentWidth, _currentContentHeight);
595         }
596         foreach(p; _popups)
597             p.measure(_currentContentWidth, _currentContentHeight);
598         if (_tooltip.popup)
599             _tooltip.popup.measure(_currentContentWidth, _currentContentHeight);
600     }
601     void layout() {
602         if (_hScrollBar)
603             _hScrollBar.layout(Rect(0, _dy - _hScrollBar.measuredHeight, _vScrollBar ? _dx - _vScrollBar.measuredWidth : _dx, _dy));
604 
605         if (_vScrollBar)
606             _vScrollBar.layout(Rect(_dx - _vScrollBar.measuredWidth, 0, _dx, _hScrollBar ? _dy - _hScrollBar.measuredHeight : _dy));
607 
608         int deltaX = 0;
609         if (_hScrollBar)
610             deltaX = -_hScrollBar.position;
611 
612         int deltaY = 0;
613         if (_vScrollBar)
614             deltaY = -_vScrollBar.position;
615 
616         Rect rc = Rect(deltaX, deltaY, _currentContentWidth + deltaX, _currentContentHeight + deltaY);
617         if (_mainWidget !is null) {
618             _mainWidget.layout(rc);
619         }
620         foreach(p; _popups)
621             p.layout(rc);
622         if (_tooltip.popup)
623             _tooltip.popup.layout(rc);
624     }
625     void onResize(int width, int height) {
626         if (_dx == width && _dy == height)
627             return;
628         _dx = width;
629         _dy = height;
630         // fix window rect for platforms that don't set it yet
631         _windowRect.right = width;
632         _windowRect.bottom = height;
633         if (_mainWidget !is null) {
634             Log.d("onResize ", _dx, "x", _dy);
635             long measureStart = currentTimeMillis;
636             updateWindowOrContentSize();
637             measure();
638             //Log.d("measured size: ", _mainWidget.measuredWidth, "x", _mainWidget.measuredHeight);
639             long measureEnd = currentTimeMillis;
640             debug Log.d("resize: measure took ", measureEnd - measureStart, " ms");
641             layout();
642             long layoutEnd = currentTimeMillis;
643             debug Log.d("resize: layout took ", layoutEnd - measureEnd, " ms");
644             //Log.d("layout position: ", _mainWidget.pos);
645         }
646         update(true);
647     }
648 
649     protected PopupWidget[] _popups;
650 
651     protected static struct TooltipInfo {
652         PopupWidget popup;
653         ulong timerId;
654         Widget ownerWidget;
655         uint alignment;
656         int x;
657         int y;
658     }
659 
660     protected TooltipInfo _tooltip;
661 
662     /// schedule tooltip for widget be shown with specified delay
663     void scheduleTooltip(Widget ownerWidget, long delay, uint alignment = PopupAlign.Below, int x = 0, int y = 0) {
664         _tooltip.alignment = alignment;
665         _tooltip.x = x;
666         _tooltip.y = y;
667         _tooltip.ownerWidget = ownerWidget;
668         _tooltip.timerId = setTimer(ownerWidget, delay);
669     }
670 
671     /// call when tooltip timer is expired
672     private bool onTooltipTimer() {
673         _tooltip.timerId = 0;
674         if (isChild(_tooltip.ownerWidget)) {
675             Widget w = _tooltip.ownerWidget.createTooltip(_lastMouseX, _lastMouseY, _tooltip.alignment, _tooltip.x, _tooltip.y);
676             if (w)
677                 showTooltip(w, _tooltip.ownerWidget, _tooltip.alignment, _tooltip.x, _tooltip.y);
678         }
679         return false;
680     }
681 
682     /// called when user dragged file(s) to application window
683     void handleDroppedFiles(string[] filenames) {
684         //Log.d("handleDroppedFiles(", filenames, ")");
685         if (_onFilesDropped)
686             _onFilesDropped(filenames);
687     }
688 
689     protected void delegate(string[]) _onFilesDropped;
690     /// get handler for files dropped to app window
691     @property void delegate(string[]) onFilesDropped() { return _onFilesDropped; }
692     /// set handler for files dropped to app window
693     @property Window onFilesDropped(void delegate(string[]) handler) { _onFilesDropped = handler; return this; }
694 
695     protected bool delegate() _onCanClose;
696     /// get handler for closing of app (it must return true to allow immediate close, false to cancel close or close window later)
697     @property bool delegate() onCanClose() { return _onCanClose; }
698     /// set handler for closing of app (it must return true to allow immediate close, false to cancel close or close window later)
699     @property Window onCanClose(bool delegate() handler) { _onCanClose = handler; return this; }
700 
701     protected void delegate() _onClose;
702     /// get handler for closing of window
703     @property void delegate() onClose() { return _onClose; }
704     /// set handler for closing of window
705     @property Window onClose(void delegate() handler) { _onClose = handler; return this; }
706 
707     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
708     bool hasModalWindowsAbove() {
709         return platform.hasModalWindowsAbove(this);
710     }
711 
712     /// calls onCanClose handler if set to check if system may close window
713     bool handleCanClose() {
714         if (hasModalWindowsAbove())
715             return false;
716         if (!_onCanClose)
717             return true;
718         bool res = _onCanClose();
719         if (!res)
720             update(true); // redraw window if it was decided to not close immediately
721         return res;
722     }
723 
724 
725     /// hide tooltip if shown and cancel tooltip timer if set
726     void hideTooltip() {
727         if (_tooltip.popup) {
728             destroy(_tooltip.popup);
729             _tooltip.popup = null;
730             if (_mainWidget)
731                 _mainWidget.invalidate();
732         }
733         if (_tooltip.timerId)
734             cancelTimer(_tooltip.timerId);
735     }
736 
737     /// show tooltip immediately
738     PopupWidget showTooltip(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center, int x = 0, int y = 0) {
739         hideTooltip();
740         if (!content)
741             return null;
742         PopupWidget res = new PopupWidget(content, this);
743         res.anchor.widget = anchor !is null ? anchor : _mainWidget;
744         res.anchor.alignment = alignment;
745         res.anchor.x = x;
746         res.anchor.y = y;
747         _tooltip.popup = res;
748         return res;
749     }
750 
751     /// show new popup
752     PopupWidget showPopup(Widget content, Widget anchor = null, uint alignment = PopupAlign.Center, int x = 0, int y = 0) {
753         PopupWidget res = new PopupWidget(content, this);
754         res.anchor.widget = anchor !is null ? anchor : _mainWidget;
755         res.anchor.alignment = alignment;
756         res.anchor.x = x;
757         res.anchor.y = y;
758         _popups ~= res;
759         if (_mainWidget !is null) {
760             _mainWidget.requestLayout();
761         }
762         update(false);
763         return res;
764     }
765     /// remove popup
766     bool removePopup(PopupWidget popup) {
767         if (!popup)
768             return false;
769         for (int i = 0; i < _popups.length; i++) {
770             PopupWidget p = _popups[i];
771             if (p is popup) {
772                 for (int j = i; j < _popups.length - 1; j++)
773                     _popups[j] = _popups[j + 1];
774                 _popups.length--;
775                 p.onClose();
776                 destroy(p);
777                 // force redraw
778                 _mainWidget.invalidate();
779                 return true;
780             }
781         }
782         return false;
783     }
784 
785     /// returns last modal popup widget, or null if no modal popups opened
786     PopupWidget modalPopup() {
787         for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
788             if (_popups[i].flags & PopupFlags.Modal)
789                 return _popups[i];
790         }
791         return null;
792     }
793 
794     /// returns true if widget is child of either main widget, one of popups or window scrollbar
795     bool isChild(Widget w) {
796         if (_mainWidget !is null && _mainWidget.isChild(w))
797             return true;
798         foreach(p; _popups)
799             if (p.isChild(w))
800                 return true;
801         if (_tooltip.popup)
802             if (_tooltip.popup.isChild(w))
803                 return true;
804         if (_hScrollBar !is null && _hScrollBar.isChild(w))
805             return true;
806         if (_vScrollBar !is null && _vScrollBar.isChild(w))
807             return true;
808         return false;
809     }
810 
811     private long lastDrawTs;
812 
813     this() {
814         _eventList = new EventList();
815         _timerQueue = new TimerQueue();
816         _backgroundColor = 0xFFFFFF;
817         if (currentTheme)
818             _backgroundColor = currentTheme.customColor(STYLE_COLOR_WINDOW_BACKGROUND);
819     }
820     ~this() {
821         debug Log.d("Destroying window");
822         if (_onClose)
823             _onClose();
824         if (_tooltip.popup) {
825             destroy(_tooltip.popup);
826             _tooltip.popup = null;
827         }
828         foreach(p; _popups)
829             destroy(p);
830         _popups = null;
831         if (_mainWidget !is null) {
832             destroy(_mainWidget);
833             _mainWidget = null;
834         }
835         if (_hScrollBar) {
836             destroy(_hScrollBar);
837             _hScrollBar = null;
838         }
839         if (_vScrollBar) {
840             destroy(_vScrollBar);
841             _vScrollBar = null;
842         }
843         destroy(_eventList);
844         destroy(_timerQueue);
845         _eventList = null;
846     }
847 
848     /**
849     Allows queue destroy of widget.
850 
851     Sometimes when you have very complicated UI with dynamic create/destroy lists of widgets calling simple destroy()
852     on widget makes segmentation fault.
853 
854     Usually because you destroy widget that on some stage call another that tries to destroy widget that calls it.
855     When the control flow returns widget not exist and you have seg. fault.
856 
857     This function use internally $(LINK2 $(DDOX_ROOT_DIR)dlangui/core/events/QueueDestroyEvent.html, QueueDestroyEvent).
858     */
859     void queueWidgetDestroy(Widget widgetToDestroy)
860     {
861         QueueDestroyEvent ev = new QueueDestroyEvent(widgetToDestroy);
862         postEvent(ev);
863     }
864 
865     private void animate(Widget root, long interval) {
866         if (root is null)
867             return;
868         if (root.visibility != Visibility.Visible)
869             return;
870         for (int i = 0; i < root.childCount; i++)
871             animate(root.child(i), interval);
872         if (root.animating)
873             root.animate(interval);
874     }
875 
876     private void animate(long interval) {
877         animate(_mainWidget, interval);
878         foreach(p; _popups)
879             p.animate(interval);
880         if (_tooltip.popup)
881             _tooltip.popup.animate(interval);
882     }
883 
884     static immutable int PERFORMANCE_LOGGING_THRESHOLD_MS = 20;
885 
886     /// set when first draw is called: don't handle mouse/key input until draw (layout) is called
887     protected bool _firstDrawCalled = false;
888     void onDraw(DrawBuf buf) {
889         _firstDrawCalled = true;
890         static import std.datetime;
891         try {
892             bool needDraw = false;
893             bool needLayout = false;
894             bool animationActive = false;
895             checkUpdateNeeded(needDraw, needLayout, animationActive);
896             if (needLayout || animationActive)
897                 needDraw = true;
898             long ts = std.datetime.Clock.currStdTime;
899             if (animationActive && lastDrawTs != 0) {
900                 animate(ts - lastDrawTs);
901                 // layout required flag could be changed during animate - check again
902                 checkUpdateNeeded(needDraw, needLayout, animationActive);
903             }
904             lastDrawTs = ts;
905             if (needLayout) {
906                 long measureStart = currentTimeMillis;
907                 measure();
908                 long measureEnd = currentTimeMillis;
909                 if (measureEnd - measureStart > PERFORMANCE_LOGGING_THRESHOLD_MS) {
910                     debug(DebugRedraw) Log.d("measure took ", measureEnd - measureStart, " ms");
911                 }
912                 layout();
913                 long layoutEnd = currentTimeMillis;
914                 if (layoutEnd - measureEnd > PERFORMANCE_LOGGING_THRESHOLD_MS) {
915                     debug(DebugRedraw) Log.d("layout took ", layoutEnd - measureEnd, " ms");
916                 }
917                 //checkUpdateNeeded(needDraw, needLayout, animationActive);
918             }
919             long drawStart = currentTimeMillis;
920             // draw main widget
921             _mainWidget.onDraw(buf);
922 
923             PopupWidget modal = modalPopup();
924 
925             // draw popups
926             foreach(p; _popups) {
927                 if (p is modal) {
928                     // TODO: get shadow color from theme
929                     buf.fillRect(Rect(0, 0, buf.width, buf.height), 0xD0404040);
930                 }
931                 p.onDraw(buf);
932             }
933 
934             if (_tooltip.popup)
935                 _tooltip.popup.onDraw(buf);
936 
937             if (_hScrollBar)
938                 _hScrollBar.onDraw(buf);
939             if (_vScrollBar)
940                 _vScrollBar.onDraw(buf);
941             if (_hScrollBar && _vScrollBar)
942                 buf.fillRect(Rect(_vScrollBar.left, _hScrollBar.top, buf.width, buf.height), _backgroundColor);
943 
944             long drawEnd = currentTimeMillis;
945             debug(DebugRedraw) {
946                 if (drawEnd - drawStart > PERFORMANCE_LOGGING_THRESHOLD_MS)
947                     Log.d("draw took ", drawEnd - drawStart, " ms");
948             }
949             if (animationActive)
950                 scheduleAnimation();
951             _actionsUpdateRequested = false;
952         } catch (Exception e) {
953             Log.e("Exception inside winfow.onDraw: ", e);
954         }
955     }
956 
957     /// after drawing, call to schedule redraw if animation is active
958     void scheduleAnimation() {
959         // override if necessary
960     }
961 
962 
963     protected void setCaptureWidget(Widget w, MouseEvent event) {
964         _mouseCaptureWidget = w;
965         _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
966     }
967 
968     protected Widget _focusedWidget;
969     protected auto _focusStateToApply = State.Focused;
970     /// returns current focused widget
971     @property Widget focusedWidget() {
972         if (!isChild(_focusedWidget))
973             _focusedWidget = null;
974         return _focusedWidget;
975     }
976 
977     /// change focus to widget
978     Widget setFocus(Widget newFocus, FocusReason reason = FocusReason.Unspecified) {
979         if (!isChild(_focusedWidget))
980             _focusedWidget = null;
981         Widget oldFocus = _focusedWidget;
982         auto targetState = State.Focused;
983         if(reason == FocusReason.TabFocus)
984             targetState = State.Focused | State.KeyboardFocused;
985         _focusStateToApply = targetState;
986         if (oldFocus is newFocus)
987             return oldFocus;
988         if (oldFocus !is null) {
989             oldFocus.resetState(targetState);
990             if (oldFocus)
991                 oldFocus.focusGroupFocused(false);
992         }
993         if (newFocus is null || isChild(newFocus)) {
994             if (newFocus !is null) {
995                 // when calling, setState(focused), window.focusedWidget is still previously focused widget
996                 debug(DebugFocus) Log.d("new focus: ", newFocus.id);
997                 newFocus.setState(targetState);
998             }
999             _focusedWidget = newFocus;
1000             if (_focusedWidget)
1001                 _focusedWidget.focusGroupFocused(true);
1002             // after focus change, ask for actions update automatically
1003             //requestActionsUpdate();
1004         }
1005         return _focusedWidget;
1006     }
1007 
1008     protected Widget removeFocus() {
1009         if (!isChild(_focusedWidget))
1010             _focusedWidget = null;
1011         if (_focusedWidget) {
1012             _focusedWidget.resetState(_focusStateToApply);
1013             update();
1014         }
1015         return _focusedWidget;
1016     }
1017 
1018     protected Widget applyFocus() {
1019         if (!isChild(_focusedWidget))
1020             _focusedWidget = null;
1021         if (_focusedWidget) {
1022             _focusedWidget.setState(_focusStateToApply);
1023             update();
1024         }
1025         return _focusedWidget;
1026     }
1027 
1028     abstract @property bool isActive();
1029 
1030     /// window state change signal
1031     Signal!OnWindowActivityHandler windowActivityChanged;
1032 
1033     protected void handleWindowActivityChange(bool isWindowActive) {
1034         if (isWindowActive)
1035             applyFocus();
1036         else
1037             removeFocus();
1038         if (windowActivityChanged.assigned)
1039             windowActivityChanged(this, isWindowActive);
1040     }
1041 
1042     /// dispatch key event to widgets which have wantsKeyTracking == true
1043     protected bool dispatchKeyEvent(Widget root, KeyEvent event) {
1044         if (root.visibility != Visibility.Visible)
1045             return false;
1046         if (root.wantsKeyTracking) {
1047             if (root.onKeyEvent(event))
1048                 return true;
1049         }
1050         for (int i = 0; i < root.childCount; i++) {
1051             Widget w = root.child(i);
1052             if (dispatchKeyEvent(w, event))
1053                 return true;
1054         }
1055         return false;
1056     }
1057 
1058     /// dispatch keyboard event
1059     bool dispatchKeyEvent(KeyEvent event) {
1060         if (hasModalWindowsAbove() || !_firstDrawCalled)
1061             return false;
1062         bool res = false;
1063         hideTooltip();
1064         PopupWidget modal = modalPopup();
1065         if (event.action == KeyAction.KeyDown || event.action == KeyAction.KeyUp) {
1066             _keyboardModifiers = event.flags;
1067             if (event.keyCode == KeyCode.ALT || event.keyCode == KeyCode.LALT || event.keyCode == KeyCode.RALT) {
1068                 debug(DebugKeys) Log.d("ALT key: keyboardModifiers = ", _keyboardModifiers);
1069                 if (_mainWidget) {
1070                     _mainWidget.invalidate();
1071                     res = true;
1072                 }
1073             }
1074         }
1075         if (event.action == KeyAction.Text) {
1076             // filter text
1077             if (event.text.length < 1)
1078                 return res;
1079             dchar ch = event.text[0];
1080             if (ch < ' ' || ch == 0x7F) // filter out control symbols
1081                 return res;
1082         }
1083         Widget focus = focusedWidget;
1084         if (!modal || modal.isChild(focus)) {
1085             while (focus) {
1086                 if (focus.onKeyEvent(event))
1087                     return true; // processed by focused widget
1088                 if (focus.focusGroup)
1089                     break;
1090                 focus = focus.parent;
1091             }
1092         }
1093         if (modal) {
1094             if (dispatchKeyEvent(modal, event))
1095                 return res;
1096             return modal.onKeyEvent(event) || res;
1097         } else if (_mainWidget) {
1098             if (dispatchKeyEvent(_mainWidget, event))
1099                 return res;
1100             return _mainWidget.onKeyEvent(event) || res;
1101         }
1102         return res;
1103     }
1104 
1105     protected bool dispatchMouseEvent(Widget root, MouseEvent event, ref bool cursorIsSet) {
1106         // only route mouse events to visible widgets
1107         if (root.visibility != Visibility.Visible)
1108             return false;
1109         if (!root.isPointInside(event.x, event.y))
1110             return false;
1111         // offer event to children first
1112         for (int i = 0; i < root.childCount; i++) {
1113             Widget child = root.child(i);
1114             if (dispatchMouseEvent(child, event, cursorIsSet))
1115                 return true;
1116         }
1117         if (event.action == MouseAction.Move && !cursorIsSet) {
1118             uint cursorType = root.getCursorType(event.x, event.y);
1119             if (cursorType != CursorType.Parent) {
1120                 setCursorType(cursorType);
1121                 cursorIsSet = true;
1122             }
1123         }
1124         // if not processed by children, offer event to root
1125         if (sendAndCheckOverride(root, event)) {
1126             debug(DebugMouseEvents) Log.d("MouseEvent is processed");
1127             if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null && !event.doNotTrackButtonDown) {
1128                 debug(DebugMouseEvents) Log.d("Setting active widget");
1129                 setCaptureWidget(root, event);
1130             } else if (event.action == MouseAction.Move) {
1131                 addTracking(root);
1132             }
1133             return true;
1134         }
1135         return false;
1136     }
1137 
1138     /// widget which tracks Move events
1139     //protected Widget _mouseTrackingWidget;
1140     protected Widget[] _mouseTrackingWidgets;
1141     private void addTracking(Widget w) {
1142         for(int i = 0; i < _mouseTrackingWidgets.length; i++)
1143             if (w is _mouseTrackingWidgets[i])
1144                 return;
1145         //foreach(widget; _mouseTrackingWidgets)
1146         //    if (widget is w)
1147         //       return;
1148         //Log.d("addTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
1149         _mouseTrackingWidgets ~= w;
1150         //Log.d("addTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
1151     }
1152     private bool checkRemoveTracking(MouseEvent event) {
1153         bool res = false;
1154         for(int i = cast(int)_mouseTrackingWidgets.length - 1; i >=0; i--) {
1155             Widget w = _mouseTrackingWidgets[i];
1156             if (!isChild(w)) {
1157                 // std.algorithm.remove does not work for me
1158                 //_mouseTrackingWidgets.remove(i);
1159                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
1160                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
1161                 _mouseTrackingWidgets.length--;
1162                 continue;
1163             }
1164             if (event.action == MouseAction.Leave || !w.isPointInside(event.x, event.y)) {
1165                 // send Leave message
1166                 MouseEvent leaveEvent = new MouseEvent(event);
1167                 leaveEvent.changeAction(MouseAction.Leave);
1168                 res = w.onMouseEvent(leaveEvent) || res;
1169                 // std.algorithm.remove does not work for me
1170                 //Log.d("removeTracking ", w.id, " items before: ", _mouseTrackingWidgets.length);
1171                 //_mouseTrackingWidgets.remove(i);
1172                 //_mouseTrackingWidgets.length--;
1173                 for (int j = i; j < _mouseTrackingWidgets.length - 1; j++)
1174                     _mouseTrackingWidgets[j] = _mouseTrackingWidgets[j + 1];
1175                 _mouseTrackingWidgets.length--;
1176                 //Log.d("removeTracking ", w.id, " items after: ", _mouseTrackingWidgets.length);
1177             }
1178         }
1179         return res;
1180     }
1181 
1182     /// widget which tracks all events after processed ButtonDown
1183     protected Widget _mouseCaptureWidget;
1184     protected ushort _mouseCaptureButtons;
1185     protected bool _mouseCaptureFocusedOut;
1186     /// does current capture widget want to receive move events even if pointer left it
1187     protected bool _mouseCaptureFocusedOutTrackMovements;
1188 
1189     protected void clearMouseCapture() {
1190         _mouseCaptureWidget = null;
1191         _mouseCaptureFocusedOut = false;
1192         _mouseCaptureFocusedOutTrackMovements = false;
1193         _mouseCaptureButtons = 0;
1194     }
1195 
1196     protected bool dispatchCancel(MouseEvent event) {
1197         event.changeAction(MouseAction.Cancel);
1198         bool res = _mouseCaptureWidget.onMouseEvent(event);
1199         clearMouseCapture();
1200         return res;
1201     }
1202 
1203     protected bool sendAndCheckOverride(Widget widget, MouseEvent event) {
1204         if (!isChild(widget))
1205             return false;
1206         bool res = widget.onMouseEvent(event);
1207         if (event.trackingWidget !is null && _mouseCaptureWidget !is event.trackingWidget) {
1208             setCaptureWidget(event.trackingWidget, event);
1209         }
1210         return res;
1211     }
1212 
1213     /// returns true if mouse is currently captured
1214     bool isMouseCaptured() {
1215         return (_mouseCaptureWidget !is null && isChild(_mouseCaptureWidget));
1216     }
1217 
1218     /// dispatch action to main widget
1219     bool dispatchAction(const Action action, Widget sourceWidget = null) {
1220         // try to handle by source widget
1221         if(sourceWidget && isChild(sourceWidget)) {
1222             if (sourceWidget.handleAction(action))
1223                 return true;
1224             sourceWidget = sourceWidget.parent;
1225         }
1226         Widget focus = focusedWidget;
1227         // then offer action to focused widget
1228         if (focus && isChild(focus)) {
1229             if (focus.handleAction(action))
1230                 return true;
1231             focus = focus.parent;
1232         }
1233         // then offer to parent chain of source widget
1234         while (sourceWidget && isChild(sourceWidget)) {
1235             if (sourceWidget.handleAction(action))
1236                 return true;
1237             sourceWidget = sourceWidget.parent;
1238         }
1239         // then offer to parent chain of focused widget
1240         while (focus && isChild(focus)) {
1241             if (focus.handleAction(action))
1242                 return true;
1243             focus = focus.parent;
1244         }
1245         if (_mainWidget)
1246             return _mainWidget.handleAction(action);
1247         return false;
1248     }
1249 
1250     /// dispatch action to main widget
1251     bool dispatchActionStateRequest(const Action action, Widget sourceWidget = null) {
1252         // try to handle by source widget
1253         if(sourceWidget && isChild(sourceWidget)) {
1254             if (sourceWidget.handleActionStateRequest(action))
1255                 return true;
1256             sourceWidget = sourceWidget.parent;
1257         }
1258         Widget focus = focusedWidget;
1259         // then offer action to focused widget
1260         if (focus && isChild(focus)) {
1261             if (focus.handleActionStateRequest(action))
1262                 return true;
1263             focus = focus.parent;
1264         }
1265         // then offer to parent chain of source widget
1266         while (sourceWidget && isChild(sourceWidget)) {
1267             if (sourceWidget.handleActionStateRequest(action))
1268                 return true;
1269             sourceWidget = sourceWidget.parent;
1270         }
1271         // then offer to parent chain of focused widget
1272         while (focus && isChild(focus)) {
1273             if (focus.handleActionStateRequest(action))
1274                 return true;
1275             focus = focus.parent;
1276         }
1277         if (_mainWidget)
1278             return _mainWidget.handleActionStateRequest(action);
1279         return false;
1280     }
1281 
1282     /// handle theme change: e.g. reload some themed resources
1283     void dispatchThemeChanged() {
1284         if (_hScrollBar)
1285             _hScrollBar.onThemeChanged();
1286         if (_vScrollBar)
1287             _vScrollBar.onThemeChanged();
1288         if (_mainWidget)
1289             _mainWidget.onThemeChanged();
1290         // draw popups
1291         foreach(p; _popups) {
1292             p.onThemeChanged();
1293         }
1294         if (_tooltip.popup)
1295             _tooltip.popup.onThemeChanged();
1296         if (currentTheme) {
1297             _backgroundColor = currentTheme.customColor(STYLE_COLOR_WINDOW_BACKGROUND);
1298         }
1299         invalidate();
1300     }
1301 
1302 
1303     /// post event to handle in UI thread (this method can be used from background thread)
1304     void postEvent(CustomEvent event) {
1305         // override to post event into window message queue
1306         _eventList.put(event);
1307     }
1308 
1309     /// post task to execute in UI thread (this method can be used from background thread)
1310     void executeInUiThread(void delegate() runnable) {
1311         RunnableEvent event = new RunnableEvent(CUSTOM_RUNNABLE, null, runnable);
1312         postEvent(event);
1313     }
1314 
1315     /// Creates async socket
1316     AsyncSocket createAsyncSocket(AsyncSocketCallback callback) {
1317         AsyncClientConnection conn = new AsyncClientConnection(new AsyncSocketCallbackProxy(callback, &executeInUiThread));
1318         return conn;
1319     }
1320 
1321     /// remove event from queue by unique id if not yet dispatched (this method can be used from background thread)
1322     void cancelEvent(uint uniqueId) {
1323         CustomEvent ev = _eventList.get(uniqueId);
1324         if (ev) {
1325             //destroy(ev);
1326         }
1327     }
1328 
1329     /// remove event from queue by unique id if not yet dispatched and dispatch it
1330     void handlePostedEvent(uint uniqueId) {
1331         CustomEvent ev = _eventList.get(uniqueId);
1332         if (ev) {
1333             dispatchCustomEvent(ev);
1334         }
1335     }
1336 
1337     /// handle all events from queue, if any (call from UI thread only)
1338     void handlePostedEvents() {
1339         for(;;) {
1340             CustomEvent e = _eventList.get();
1341             if (!e)
1342                 break;
1343             dispatchCustomEvent(e);
1344         }
1345     }
1346 
1347     /// dispatch custom event
1348     bool dispatchCustomEvent(CustomEvent event) {
1349         if (event.destinationWidget) {
1350             if (!isChild(event.destinationWidget)) {
1351                 //Event is sent to widget which does not exist anymore
1352                 return false;
1353             }
1354             return event.destinationWidget.onEvent(event);
1355         } else {
1356             // no destination widget
1357             RunnableEvent runnable = cast(RunnableEvent)event;
1358             if (runnable) {
1359                 // handle runnable
1360                 runnable.run();
1361                 return true;
1362             }
1363         }
1364         return false;
1365     }
1366 
1367     private int _lastMouseX;
1368     private int _lastMouseY;
1369     /// dispatch mouse event to window content widgets
1370     bool dispatchMouseEvent(MouseEvent event) {
1371         if (hasModalWindowsAbove() || !_firstDrawCalled)
1372             return false;
1373         // ignore events if there is no root
1374         if (_mainWidget is null)
1375             return false;
1376 
1377         bool actualChange = true;
1378         if (event.action == MouseAction.Move) {
1379             actualChange = (_lastMouseX != event.x || _lastMouseY != event.y);
1380             _lastMouseX = event.x;
1381             _lastMouseY = event.y;
1382         }
1383         if (actualChange) hideTooltip();
1384 
1385         PopupWidget modal = modalPopup();
1386 
1387         // check if _mouseCaptureWidget and _mouseTrackingWidget still exist in child of root widget
1388         if (_mouseCaptureWidget !is null && (!isChild(_mouseCaptureWidget) || (modal && !modal.isChild(_mouseCaptureWidget)))) {
1389             clearMouseCapture();
1390         }
1391 
1392         debug(DebugMouseEvents) Log.d("dispatchMouseEvent ", event.action, "  (", event.x, ",", event.y, ")");
1393 
1394         bool res = false;
1395         ushort currentButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
1396         if (_mouseCaptureWidget !is null) {
1397             // try to forward message directly to active widget
1398             if (event.action == MouseAction.Move) {
1399                 debug(DebugMouseEvents) Log.d("dispatchMouseEvent: Move; buttons state=", currentButtons);
1400                 if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
1401                     if (currentButtons != _mouseCaptureButtons) {
1402                         debug(DebugMouseEvents) Log.d("dispatchMouseEvent: Move; buttons state changed from ", _mouseCaptureButtons, " to ", currentButtons, " - cancelling capture");
1403                         return dispatchCancel(event);
1404                     }
1405                     // point is no more inside of captured widget
1406                     if (!_mouseCaptureFocusedOut) {
1407                         // sending FocusOut message
1408                         event.changeAction(MouseAction.FocusOut);
1409                         _mouseCaptureFocusedOut = true;
1410                         _mouseCaptureButtons = currentButtons;
1411                         _mouseCaptureFocusedOutTrackMovements = sendAndCheckOverride(_mouseCaptureWidget, event);
1412                         return true;
1413                     } else if (_mouseCaptureFocusedOutTrackMovements) {
1414                         // pointer is outside, but we still need to track pointer
1415                         return sendAndCheckOverride(_mouseCaptureWidget, event);
1416                     }
1417                     // don't forward message
1418                     return true;
1419                 } else {
1420                     // point is inside widget
1421                     if (_mouseCaptureFocusedOut) {
1422                         _mouseCaptureFocusedOut = false;
1423                         if (currentButtons != _mouseCaptureButtons)
1424                             return dispatchCancel(event);
1425                            event.changeAction(MouseAction.FocusIn); // back in after focus out
1426                     }
1427                     return sendAndCheckOverride(_mouseCaptureWidget, event);
1428                 }
1429             } else if (event.action == MouseAction.Leave) {
1430                 if (!_mouseCaptureFocusedOut) {
1431                     // sending FocusOut message
1432                     event.changeAction(MouseAction.FocusOut);
1433                     _mouseCaptureFocusedOut = true;
1434                     _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton);
1435                     return sendAndCheckOverride(_mouseCaptureWidget, event);
1436                 } else {
1437                     debug(DebugMouseEvents) Log.d("dispatchMouseEvent: mouseCaptureFocusedOut + Leave - cancelling capture");
1438                     return dispatchCancel(event);
1439                 }
1440             } else if (event.action == MouseAction.ButtonDown || event.action == MouseAction.ButtonUp) {
1441                 if (!_mouseCaptureWidget.isPointInside(event.x, event.y)) {
1442                     if (currentButtons != _mouseCaptureButtons) {
1443                         debug(DebugMouseEvents) Log.d("dispatchMouseEvent: ButtonUp/ButtonDown; buttons state changed from ", _mouseCaptureButtons, " to ", currentButtons, " - cancelling capture");
1444                         return dispatchCancel(event);
1445                     }
1446                 }
1447             }
1448             // other messages
1449             res = sendAndCheckOverride(_mouseCaptureWidget, event);
1450             if (!currentButtons) {
1451                 // usable capturing - no more buttons pressed
1452                 debug(DebugMouseEvents) Log.d("unsetting active widget");
1453                 clearMouseCapture();
1454             }
1455             return res;
1456         }
1457         bool processed = false;
1458         if (event.action == MouseAction.Move || event.action == MouseAction.Leave) {
1459             processed = checkRemoveTracking(event);
1460         }
1461         bool cursorIsSet = false;
1462         if (!res) {
1463             bool insideOneOfPopups = false;
1464             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
1465                 auto p = _popups[i];
1466                 if (p.isPointInside(event.x, event.y)) {
1467                     if (p !is modal)
1468                         insideOneOfPopups = true;
1469                 }
1470                 if (p is modal)
1471                     break;
1472             }
1473             for (int i = cast(int)_popups.length - 1; i >= 0; i--) {
1474                 auto p = _popups[i];
1475                 if (p is modal)
1476                     break;
1477                 if (!insideOneOfPopups) {
1478                     if (p.onMouseEventOutside(event)) // stop loop when true is returned, but allow other main widget to handle event
1479                         break;
1480                 } else {
1481                     if (dispatchMouseEvent(p, event, cursorIsSet))
1482                         return true;
1483                 }
1484             }
1485             if (!modal) {
1486                 res = false;
1487                 if (_hScrollBar)
1488                     res = dispatchMouseEvent(_hScrollBar, event, cursorIsSet);
1489                 if (!res && _vScrollBar)
1490                     res = dispatchMouseEvent(_vScrollBar, event, cursorIsSet);
1491                 if (!res)
1492                     res = dispatchMouseEvent(_mainWidget, event, cursorIsSet);
1493             }
1494             else {
1495                 if (_hScrollBar)
1496                     res = dispatchMouseEvent(_hScrollBar, event, cursorIsSet);
1497                 if (!res && _vScrollBar)
1498                     res = dispatchMouseEvent(_vScrollBar, event, cursorIsSet);
1499                 if (!res)
1500                         res = dispatchMouseEvent(modal, event, cursorIsSet);
1501             }
1502         }
1503         return res || processed || _mainWidget.needDraw;
1504     }
1505 
1506     /// calls update actions recursively
1507     protected void dispatchWidgetUpdateActionStateRecursive(Widget root) {
1508         if (root is null)
1509             return;
1510         root.updateActionState(true);
1511         for (int i = 0; i < root.childCount; i++)
1512             dispatchWidgetUpdateActionStateRecursive(root.child(i));
1513     }
1514     /// checks content widgets for necessary redraw and/or layout
1515     protected void checkUpdateNeeded(Widget root, ref bool needDraw, ref bool needLayout, ref bool animationActive) {
1516         if (root is null)
1517             return;
1518         if (root.visibility != Visibility.Visible)
1519             return;
1520         needDraw = root.needDraw || needDraw;
1521         if (!needLayout) {
1522             needLayout = root.needLayout || needLayout;
1523             if (needLayout) {
1524                 debug(DebugRedraw) Log.d("need layout: ", root.classinfo.name, " id=", root.id);
1525             }
1526         }
1527         if (root.animating && root.visible)
1528             animationActive = true; // check animation only for visible widgets
1529         for (int i = 0; i < root.childCount; i++)
1530             checkUpdateNeeded(root.child(i), needDraw, needLayout, animationActive);
1531     }
1532     /// sets cursor type for window
1533     protected void setCursorType(uint cursorType) {
1534         // override to support different mouse cursors
1535     }
1536     /// update action states
1537     protected void dispatchWidgetUpdateActionStateRecursive() {
1538         if (_mainWidget !is null)
1539             dispatchWidgetUpdateActionStateRecursive(_mainWidget);
1540         foreach(p; _popups)
1541             dispatchWidgetUpdateActionStateRecursive(p);
1542     }
1543     /// checks content widgets for necessary redraw and/or layout
1544     bool checkUpdateNeeded(ref bool needDraw, ref bool needLayout, ref bool animationActive) {
1545         if (_actionsUpdateRequested) {
1546             // call update action check - as requested
1547             dispatchWidgetUpdateActionStateRecursive();
1548             _actionsUpdateRequested = false;
1549         }
1550         needDraw = needLayout = animationActive = false;
1551         if (_mainWidget is null)
1552             return false;
1553         checkUpdateNeeded(_mainWidget, needDraw, needLayout, animationActive);
1554         if (_hScrollBar)
1555             checkUpdateNeeded(_hScrollBar, needDraw, needLayout, animationActive);
1556         if (_vScrollBar)
1557             checkUpdateNeeded(_vScrollBar, needDraw, needLayout, animationActive);
1558         foreach(p; _popups)
1559             checkUpdateNeeded(p, needDraw, needLayout, animationActive);
1560         if (_tooltip.popup)
1561             checkUpdateNeeded(_tooltip.popup, needDraw, needLayout, animationActive);
1562         return needDraw || needLayout || animationActive;
1563     }
1564 
1565     protected bool _animationActive;
1566 
1567     @property bool isAnimationActive() {
1568         return _animationActive;
1569     }
1570 
1571     /// requests update for window (unless force is true, update will be performed only if layout, redraw or animation is required).
1572     void update(bool force = false) {
1573         if (_mainWidget is null)
1574             return;
1575         bool needDraw = false;
1576         bool needLayout = false;
1577         _animationActive = false;
1578         if (checkUpdateNeeded(needDraw, needLayout, _animationActive) || force) {
1579             debug(DebugRedraw) Log.d("Requesting update");
1580             invalidate();
1581         }
1582         debug(DebugRedraw) Log.d("checkUpdateNeeded returned needDraw=", needDraw, " needLayout=", needLayout, " animationActive=", _animationActive);
1583     }
1584 
1585     protected bool _actionsUpdateRequested = true;
1586 
1587     /// set action update request flag, will be cleared after redraw
1588     void requestActionsUpdate(bool immediateUpdate = false) {
1589         if (!immediateUpdate)
1590             _actionsUpdateRequested = true;
1591         else
1592             dispatchWidgetUpdateActionStateRecursive();
1593     }
1594 
1595     @property bool actionsUpdateRequested() {
1596         return _actionsUpdateRequested;
1597     }
1598 
1599     /// Show message box with specified title and message (title and message as UIString)
1600     void showMessageBox(UIString title, UIString message, const (Action)[] actions = [ACTION_OK], int defaultActionIndex = 0, bool delegate(const Action result) handler = null) {
1601         import dlangui.dialogs.msgbox;
1602         MessageBox dlg = new MessageBox(title, message, this, actions, defaultActionIndex, handler);
1603         dlg.show();
1604     }
1605 
1606     /// Show message box with specified title and message (title and message as dstring)
1607     void showMessageBox(dstring title, dstring message, const (Action)[] actions = [ACTION_OK], int defaultActionIndex = 0, bool delegate(const Action result) handler = null) {
1608         showMessageBox(UIString.fromRaw(title), UIString.fromRaw(message), actions, defaultActionIndex, handler);
1609     }
1610 
1611     static if (BACKEND_GUI) {
1612         void showInputBox(UIString title, UIString message, dstring initialText, void delegate(dstring result) handler) {
1613             import dlangui.dialogs.inputbox;
1614             InputBox dlg = new InputBox(title, message, this, initialText, handler);
1615             dlg.show();
1616         }
1617     }
1618 
1619     void showInputBox(dstring title, dstring message, dstring initialText, void delegate(dstring result) handler) {
1620         showInputBox(UIString.fromRaw(title), UIString.fromRaw(message), initialText, handler);
1621     }
1622 
1623     protected TimerQueue _timerQueue;
1624 
1625 
1626     /// schedule timer for interval in milliseconds - call window.onTimer when finished
1627     protected void scheduleSystemTimer(long intervalMillis) {
1628         //debug Log.d("override scheduleSystemTimer to support timers");
1629     }
1630 
1631     /// poll expired timers; returns true if update is needed
1632     bool pollTimers() {
1633         bool res = _timerQueue.notify();
1634         if (res)
1635             update(false);
1636         return res;
1637     }
1638 
1639     /// system timer interval expired - notify queue
1640     protected void onTimer() {
1641         //Log.d("window.onTimer");
1642         bool res = _timerQueue.notify();
1643         //Log.d("window.onTimer after notify");
1644         if (res) {
1645             // check if update needed and redraw if so
1646             //Log.d("before update");
1647             update(false);
1648             //Log.d("after update");
1649         }
1650         //Log.d("schedule next timer");
1651         long nextInterval = _timerQueue.nextIntervalMillis();
1652         if (nextInterval > 0) {
1653             scheduleSystemTimer(nextInterval);
1654         }
1655         //Log.d("schedule next timer done");
1656     }
1657 
1658     /// set timer for destination widget - destination.onTimer() will be called after interval expiration; returns timer id
1659     ulong setTimer(Widget destination, long intervalMillis) {
1660         if (!isChild(destination)) {
1661             Log.e("setTimer() is called not for child widget of window");
1662             return 0;
1663         }
1664         ulong res = _timerQueue.add(destination, intervalMillis);
1665         long nextInterval = _timerQueue.nextIntervalMillis();
1666         if (nextInterval > 0) {
1667             scheduleSystemTimer(intervalMillis);
1668         }
1669         return res;
1670     }
1671 
1672     /// cancel previously scheduled widget timer (for timerId pass value returned from setTimer)
1673     void cancelTimer(ulong timerId) {
1674         _timerQueue.cancelTimer(timerId);
1675     }
1676 
1677     /// timers queue
1678     private class TimerQueue {
1679         protected TimerInfo[] _queue;
1680         /// add new timer
1681         ulong add(Widget destination, long intervalMillis) {
1682             TimerInfo item = new TimerInfo(destination, intervalMillis);
1683             _queue ~= item;
1684             sort(_queue);
1685             return item.id;
1686         }
1687         /// cancel timer
1688         void cancelTimer(ulong timerId) {
1689             if (!_queue.length)
1690                 return;
1691             for (int i = cast(int)_queue.length - 1; i >= 0; i--) {
1692                 if (_queue[i].id == timerId) {
1693                     _queue[i].cancel();
1694                     break;
1695                 }
1696             }
1697         }
1698         /// returns interval if millis of next scheduled event or -1 if no events queued
1699         long nextIntervalMillis() {
1700             if (!_queue.length || !_queue[0].valid)
1701                 return -1;
1702             long delta = _queue[0].nextTimestamp - currentTimeMillis;
1703             if (delta < 1)
1704                 delta = 1;
1705             return delta;
1706         }
1707         private void cleanup() {
1708             if (!_queue.length)
1709                 return;
1710             sort(_queue);
1711             size_t newsize = _queue.length;
1712             for (int i = cast(int)_queue.length - 1; i >= 0; i--) {
1713                 if (!_queue[i].valid) {
1714                     newsize = i;
1715                 }
1716             }
1717             if (_queue.length > newsize)
1718                 _queue.length = newsize;
1719         }
1720         private TimerInfo[] expired() {
1721             if (!_queue.length)
1722                 return null;
1723             long ts = currentTimeMillis;
1724             TimerInfo[] res;
1725             for (int i = 0; i < _queue.length; i++) {
1726                 if (_queue[i].nextTimestamp <= ts)
1727                     res ~= _queue[i];
1728             }
1729             return res;
1730         }
1731         /// returns true if at least one widget was notified
1732         bool notify() {
1733             bool res = false;
1734             checkValidWidgets();
1735             TimerInfo[] list = expired();
1736             if (list) {
1737                 for (int i = 0; i < list.length; i++) {
1738                     if (_queue[i].id == _tooltip.timerId) {
1739                         // special case for tooltip timer
1740                         onTooltipTimer();
1741                         _queue[i].cancel();
1742                         res = true;
1743                     } else {
1744                         Widget w = _queue[i].targetWidget;
1745                         if (w && !isChild(w))
1746                             _queue[i].cancel();
1747                         else {
1748                             _queue[i].notify();
1749                             res = true;
1750                         }
1751                     }
1752                 }
1753             }
1754             cleanup();
1755             return res;
1756         }
1757         private void checkValidWidgets() {
1758             for (int i = 0; i < _queue.length; i++) {
1759                 Widget w = _queue[i].targetWidget;
1760                 if (w && !isChild(w))
1761                     _queue[i].cancel();
1762             }
1763             cleanup();
1764         }
1765     }
1766 
1767 
1768 }
1769 
1770 /**
1771  * Platform abstraction layer.
1772  *
1773  * Represents application.
1774  *
1775  *
1776  *
1777  */
1778 class Platform {
1779     static __gshared Platform _instance;
1780     static void setInstance(Platform instance) {
1781         if (_instance)
1782             destroy(_instance);
1783         _instance = instance;
1784     }
1785     @property static Platform instance() {
1786         return _instance;
1787     }
1788 
1789     /**
1790      * create window
1791      * Args:
1792      *         windowCaption = window caption text
1793      *         parent = parent Window, or null if no parent
1794      *         flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen
1795      *      width = window width
1796      *      height = window height
1797      *
1798      * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget
1799      */
1800     abstract Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0);
1801 
1802     static if (ENABLE_OPENGL) {
1803         /**
1804          * OpenGL context major version.
1805          * Note: if the version is invalid or not supported, this value will be set to supported one.
1806          */
1807         int GLVersionMajor = 3;
1808         /**
1809          * OpenGL context minor version.
1810          * Note: if the version is invalid or not supported, this value will be set to supported one.
1811          */
1812         int GLVersionMinor = 2;
1813     }
1814     /**
1815      * close window
1816      *
1817      * Closes window earlier created with createWindow()
1818      */
1819     abstract void closeWindow(Window w);
1820     /**
1821      * Starts application message loop.
1822      *
1823      * When returned from this method, application is shutting down.
1824      */
1825     abstract int enterMessageLoop();
1826     /// check has clipboard text
1827     abstract bool hasClipboardText(bool mouseBuffer = false);
1828     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1829     abstract dstring getClipboardText(bool mouseBuffer = false);
1830     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1831     abstract void setClipboardText(dstring text, bool mouseBuffer = false);
1832 
1833     /// calls request layout for all windows
1834     abstract void requestLayout();
1835 
1836     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
1837     bool hasModalWindowsAbove(Window w) {
1838         // override in platform specific class
1839         return false;
1840     }
1841 
1842 
1843     protected string _uiLanguage;
1844     /// returns currently selected UI language code
1845     @property string uiLanguage() {
1846         return _uiLanguage;
1847     }
1848     /// set UI language (e.g. "en", "fr", "ru") - will relayout content of all windows if language has been changed
1849     @property Platform uiLanguage(string langCode) {
1850         if (_uiLanguage.equal(langCode))
1851             return this;
1852         _uiLanguage = langCode;
1853 
1854         Log.v("Loading language file");
1855         if (langCode.equal("en"))
1856             i18n.load("en.ini"); //"ru.ini", "en.ini"
1857         else
1858             i18n.load(langCode ~ ".ini", "en.ini");
1859         Log.v("Calling onThemeChanged");
1860         onThemeChanged();
1861         requestLayout();
1862         return this;
1863     }
1864     protected string _themeId;
1865     @property string uiTheme() {
1866         return _themeId;
1867     }
1868     /// sets application UI theme - will relayout content of all windows if theme has been changed
1869     @property Platform uiTheme(string themeResourceId) {
1870         if (_themeId.equal(themeResourceId))
1871             return this;
1872         Log.v("uiTheme setting new theme ", themeResourceId);
1873         _themeId = themeResourceId;
1874         Theme theme = loadTheme(themeResourceId);
1875         if (!theme) {
1876             Log.e("Cannot load theme from resource ", themeResourceId, " - will use default theme");
1877             theme = createDefaultTheme();
1878         } else {
1879             Log.i("Applying loaded theme ", theme.id);
1880         }
1881         currentTheme = theme;
1882         onThemeChanged();
1883         requestLayout();
1884         return this;
1885     }
1886 
1887     /// to set uiLanguage and themeId to default (en, theme_default) if not set yet
1888     protected void setDefaultLanguageAndThemeIfNecessary() {
1889         if (!_uiLanguage) {
1890             Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI language");
1891             uiLanguage = "en";
1892         }
1893         if (!_themeId) {
1894             Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI theme");
1895             uiTheme = "theme_default";
1896         }
1897     }
1898 
1899     protected ulong _uiDialogDisplayMode = DialogDisplayMode.messageBoxInPopup | DialogDisplayMode.inputBoxInPopup;
1900     /// returns how dialogs should be displayed - as popup or window
1901     @property ulong uiDialogDisplayMode() {
1902         return _uiDialogDisplayMode;
1903     }
1904 
1905     // sets how dialogs should be displayed - as popup or window - use DialogDisplayMode enumeration
1906     @property Platform uiDialogDisplayMode(ulong newDialogDisplayMode) {
1907         _uiDialogDisplayMode = newDialogDisplayMode;
1908         return this;
1909     }
1910 
1911     protected string[] _resourceDirs;
1912     /// returns list of resource directories
1913     @property string[] resourceDirs() { return _resourceDirs; }
1914     /// set list of directories to load resources from
1915     @property Platform resourceDirs(string[] dirs) {
1916         // setup resource directories - will use only existing directories
1917         drawableCache.setResourcePaths(dirs);
1918         _resourceDirs = drawableCache.resourcePaths;
1919         // setup i18n - look for i18n directory inside one of passed directories
1920         i18n.findTranslationsDir(dirs);
1921         return this;
1922     }
1923 
1924     /// open url in external browser
1925     bool openURL(string url) {
1926         import std.process;
1927         browse(url);
1928         return true;
1929     }
1930 
1931     /// show directory or file in OS file manager (explorer, finder, etc...)
1932     bool showInFileManager(string pathName) {
1933         static import dlangui.core.filemanager;
1934         return dlangui.core.filemanager.showInFileManager(pathName);
1935     }
1936 
1937     /// handle theme change: e.g. reload some themed resources
1938     void onThemeChanged() {
1939         // override and call dispatchThemeChange for all windows
1940     }
1941 
1942     /// default icon for new created windows
1943     private string _defaultWindowIcon = "dlangui-logo1";
1944 
1945     /// sets default icon for new created windows
1946     @property void defaultWindowIcon(string newIcon) {
1947         _defaultWindowIcon = newIcon;
1948     }
1949 
1950     /// gets default icon for new created windows
1951     @property string defaultWindowIcon() {
1952         return _defaultWindowIcon;
1953     }
1954 
1955 
1956 }
1957 
1958 /// get current platform object instance
1959 @property Platform platform() {
1960     return Platform.instance;
1961 }
1962 
1963 static if (ENABLE_OPENGL) {
1964     private __gshared bool _OPENGL_ENABLED = true;
1965     /// check if hardware acceleration is enabled
1966     @property bool openglEnabled() { return _OPENGL_ENABLED; }
1967     /// call on app initialization if OpenGL support is detected
1968     void setOpenglEnabled(bool enabled = true) {
1969         _OPENGL_ENABLED = enabled;
1970         if (enabled)
1971             glyphDestroyCallback = &onGlyphDestroyedCallback;
1972         else
1973             glyphDestroyCallback = null;
1974     }
1975 } else {
1976     @property bool openglEnabled() { return false; }
1977     void setOpenglEnabled(bool enabled = true) {
1978         // ignore
1979     }
1980 }
1981 
1982 static if (BACKEND_CONSOLE) {
1983     // to remove import
1984     extern(C) int DLANGUImain(string[] args);
1985 } else {
1986     version (Windows) {
1987         // to remove import
1988         extern(Windows) int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
1989                                            char* lpCmdLine, int nCmdShow);
1990     } else {
1991         // to remove import
1992         extern(C) int DLANGUImain(string[] args);
1993     }
1994 }
1995 
1996 /// put "mixin APP_ENTRY_POINT;" to main module of your dlangui based app
1997 mixin template APP_ENTRY_POINT() {
1998     version (unittest) {
1999         // no main in unit tests
2000     } else {
2001         static if (BACKEND_CONSOLE) {
2002             int main(string[] args)
2003             {
2004                 return DLANGUImain(args);
2005             }
2006         } else {
2007             /// workaround for link issue when WinMain is located in library
2008             version(Windows) {
2009                 extern (Windows) int WinMain(void* hInstance, void* hPrevInstance,
2010                                                 char* lpCmdLine, int nCmdShow)
2011                 {
2012                     try {
2013                         int res = DLANGUIWinMain(hInstance, hPrevInstance,
2014                                                     lpCmdLine, nCmdShow);
2015                         return res;
2016                     } catch (Exception e) {
2017                         Log.e("Exception: ", e);
2018                         return 1;
2019                     }
2020                 }
2021             } else {
2022                 version (Android) {
2023                 } else {
2024                     int main(string[] args)
2025                     {
2026                         return DLANGUImain(args);
2027                     }
2028                 }
2029             }
2030         }
2031     }
2032 }
2033 
2034 /// initialize font manager on startup
2035 extern(C) bool initFontManager();
2036 /// initialize logging (for win32 - to file ui.log, for other platforms - stderr; log level is TRACE for debug builds, and WARN for release builds)
2037 extern(C) void initLogs();
2038 /// call this when all resources are supposed to be freed to report counts of non-freed resources by type
2039 extern(C) void releaseResourcesOnAppExit();
2040 /// call this on application initialization
2041 extern(C) void initResourceManagers();
2042 /// call this from shared static this()
2043 extern (C) void initSharedResourceManagers();
2044 
2045