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