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