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