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