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 * close window
1849 *
1850 * Closes window earlier created with createWindow()
1851 */
1852 abstract void closeWindow(Window w);
1853 /**
1854 * Starts application message loop.
1855 *
1856 * When returned from this method, application is shutting down.
1857 */
1858 abstract int enterMessageLoop();
1859 /// check has clipboard text
1860 abstract bool hasClipboardText(bool mouseBuffer = false);
1861 /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1862 abstract dstring getClipboardText(bool mouseBuffer = false);
1863 /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1864 abstract void setClipboardText(dstring text, bool mouseBuffer = false);
1865
1866 /// calls request layout for all windows
1867 abstract void requestLayout();
1868
1869 /// 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
1870 bool hasModalWindowsAbove(Window w) {
1871 // override in platform specific class
1872 return false;
1873 }
1874
1875
1876 protected string _uiLanguage;
1877 /// returns currently selected UI language code
1878 @property string uiLanguage() {
1879 return _uiLanguage;
1880 }
1881 /// set UI language (e.g. "en", "fr", "ru") - will relayout content of all windows if language has been changed
1882 @property Platform uiLanguage(string langCode) {
1883 if (_uiLanguage.equal(langCode))
1884 return this;
1885 _uiLanguage = langCode;
1886
1887 Log.v("Loading language file");
1888 if (langCode.equal("en"))
1889 i18n.load("en.ini"); //"ru.ini", "en.ini"
1890 else
1891 i18n.load(langCode ~ ".ini", "en.ini");
1892 Log.v("Calling onThemeChanged");
1893 onThemeChanged();
1894 requestLayout();
1895 return this;
1896 }
1897 protected string _themeId;
1898 @property string uiTheme() {
1899 return _themeId;
1900 }
1901 /// sets application UI theme - will relayout content of all windows if theme has been changed
1902 @property Platform uiTheme(string themeResourceId) {
1903 if (_themeId.equal(themeResourceId))
1904 return this;
1905 Log.v("uiTheme setting new theme ", themeResourceId);
1906 _themeId = themeResourceId;
1907 Theme theme = loadTheme(themeResourceId);
1908 if (!theme) {
1909 Log.e("Cannot load theme from resource ", themeResourceId, " - will use default theme");
1910 theme = createDefaultTheme();
1911 } else {
1912 Log.i("Applying loaded theme ", theme.id);
1913 }
1914 currentTheme = theme;
1915 onThemeChanged();
1916 requestLayout();
1917 return this;
1918 }
1919
1920 /// to set uiLanguage and themeId to default (en, theme_default) if not set yet
1921 protected void setDefaultLanguageAndThemeIfNecessary() {
1922 if (!_uiLanguage) {
1923 Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI language");
1924 uiLanguage = "en";
1925 }
1926 if (!_themeId) {
1927 Log.v("setDefaultLanguageAndThemeIfNecessary : setting UI theme");
1928 uiTheme = "theme_default";
1929 }
1930 }
1931
1932 protected ulong _uiDialogDisplayMode = DialogDisplayMode.messageBoxInPopup | DialogDisplayMode.inputBoxInPopup;
1933 /// returns how dialogs should be displayed - as popup or window
1934 @property ulong uiDialogDisplayMode() {
1935 return _uiDialogDisplayMode;
1936 }
1937
1938 // sets how dialogs should be displayed - as popup or window - use DialogDisplayMode enumeration
1939 @property Platform uiDialogDisplayMode(ulong newDialogDisplayMode) {
1940 _uiDialogDisplayMode = newDialogDisplayMode;
1941 return this;
1942 }
1943
1944 protected string[] _resourceDirs;
1945 /// returns list of resource directories
1946 @property string[] resourceDirs() { return _resourceDirs; }
1947 /// set list of directories to load resources from
1948 @property Platform resourceDirs(string[] dirs) {
1949 // setup resource directories - will use only existing directories
1950 drawableCache.setResourcePaths(dirs);
1951 _resourceDirs = drawableCache.resourcePaths;
1952 // setup i18n - look for i18n directory inside one of passed directories
1953 i18n.findTranslationsDir(dirs);
1954 return this;
1955 }
1956
1957 /// open url in external browser
1958 bool openURL(string url) {
1959 import std.process;
1960 browse(url);
1961 return true;
1962 }
1963
1964 /// show directory or file in OS file manager (explorer, finder, etc...)
1965 bool showInFileManager(string pathName) {
1966 static import dlangui.core.filemanager;
1967 return dlangui.core.filemanager.showInFileManager(pathName);
1968 }
1969
1970 /// handle theme change: e.g. reload some themed resources
1971 void onThemeChanged() {
1972 // override and call dispatchThemeChange for all windows
1973 drawableCache.clear();
1974 static if (!WIDGET_STYLE_CONSOLE) {
1975 imageCache.checkpoint();
1976 imageCache.cleanup();
1977 }
1978 }
1979
1980 /// default icon for new created windows
1981 private string _defaultWindowIcon = "dlangui-logo1";
1982
1983 /// sets default icon for new created windows
1984 @property void defaultWindowIcon(string newIcon) {
1985 _defaultWindowIcon = newIcon;
1986 }
1987
1988 /// gets default icon for new created windows
1989 @property string defaultWindowIcon() {
1990 return _defaultWindowIcon;
1991 }
1992
1993 private IconProviderBase _iconProvider;
1994 @property IconProviderBase iconProvider() {
1995 if (_iconProvider is null) {
1996 try {
1997 _iconProvider = new NativeIconProvider();
1998 } catch(Exception e) {
1999 Log.e("Error while creating icon provider.", e);
2000 Log.d("Could not create native icon provider, fallbacking to the dummy one");
2001 _iconProvider = new DummyIconProvider();
2002 }
2003 }
2004 return _iconProvider;
2005 }
2006
2007 @property IconProviderBase iconProvider(IconProviderBase provider)
2008 {
2009 _iconProvider = provider;
2010 return _iconProvider;
2011 }
2012
2013 ~this()
2014 {
2015 if(_iconProvider) {
2016 destroy(_iconProvider);
2017 }
2018 }
2019 }
2020
2021 /// get current platform object instance
2022 @property Platform platform() {
2023 return Platform.instance;
2024 }
2025
2026 static if (ENABLE_OPENGL) {
2027 private __gshared bool _OPENGL_ENABLED = true;
2028 /// check if hardware acceleration is enabled
2029 @property bool openglEnabled() { return _OPENGL_ENABLED; }
2030 /// call on app initialization if OpenGL support is detected
2031 void setOpenglEnabled(bool enabled = true) {
2032 _OPENGL_ENABLED = enabled;
2033 if (enabled)
2034 glyphDestroyCallback = &onGlyphDestroyedCallback;
2035 else
2036 glyphDestroyCallback = null;
2037 }
2038 } else {
2039 @property bool openglEnabled() { return false; }
2040 void setOpenglEnabled(bool enabled = true) {
2041 // ignore
2042 }
2043 }
2044
2045 static if (BACKEND_CONSOLE) {
2046 // to remove import
2047 extern(C) int DLANGUImain(string[] args);
2048 } else {
2049 version (Windows) {
2050 // to remove import
2051 extern(Windows) int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
2052 char* lpCmdLine, int nCmdShow);
2053 extern(Windows)
2054 int DLANGUIWinMainProfile(string[] args);
2055 } else {
2056 // to remove import
2057 extern(C) int DLANGUImain(string[] args);
2058 }
2059 }
2060
2061 /// put "mixin APP_ENTRY_POINT;" to main module of your dlangui based app
2062 mixin template APP_ENTRY_POINT() {
2063 version (unittest) {
2064 // no main in unit tests
2065 } else {
2066 static if (BACKEND_CONSOLE) {
2067 int main(string[] args)
2068 {
2069 return DLANGUImain(args);
2070 }
2071 } else {
2072 /// workaround for link issue when WinMain is located in library
2073 version(Windows) {
2074 version (ENABLE_PROFILING) {
2075 int main(string[] args)
2076 {
2077 return DLANGUIWinMainProfile(args);
2078 }
2079 } else {
2080 extern (Windows) int WinMain(void* hInstance, void* hPrevInstance,
2081 char* lpCmdLine, int nCmdShow)
2082 {
2083 try {
2084 int res = DLANGUIWinMain(hInstance, hPrevInstance,
2085 lpCmdLine, nCmdShow);
2086 return res;
2087 } catch (Exception e) {
2088 Log.e("Exception: ", e);
2089 return 1;
2090 }
2091 }
2092 }
2093 } else {
2094 version (Android) {
2095 } else {
2096 int main(string[] args)
2097 {
2098 return DLANGUImain(args);
2099 }
2100 }
2101 }
2102 }
2103 }
2104 }
2105
2106 /// initialize font manager on startup
2107 extern(C) bool initFontManager();
2108 /// initialize logging (for win32 - to file ui.log, for other platforms - stderr; log level is TRACE for debug builds, and WARN for release builds)
2109 extern(C) void initLogs();
2110 /// call this when all resources are supposed to be freed to report counts of non-freed resources by type
2111 extern(C) void releaseResourcesOnAppExit();
2112 /// call this on application initialization
2113 extern(C) void initResourceManagers();
2114 /// call this from shared static this()
2115 extern (C) void initSharedResourceManagers();
2116
2117