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