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