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