1 /++ 2 Contains all the classes that make up qui. 3 +/ 4 module qui.qui; 5 6 import utils.misc; 7 import utils.ds; 8 9 import std.datetime.stopwatch; 10 import std.conv : to; 11 import std.process; 12 13 import qui.termwrap; 14 import qui.utils; 15 16 /// default foreground color, white 17 enum Color DEFAULT_FG = Color.white; 18 /// default background color, black 19 enum Color DEFAULT_BG = Color.black; 20 /// default background color of overflowing layouts, red 21 enum Color DEFAULT_OVERFLOW_BG = Color.red; 22 /// default active widget cycling key, tab 23 enum dchar WIDGET_CYCLE_KEY = '\t'; 24 25 /// Colors 26 public alias Color = qui.termwrap.Color; 27 /// Availabe Keys (keyboard) for input 28 public alias Key = qui.termwrap.Event.Keyboard.Key; 29 /// Mouse Event 30 public alias MouseEvent = Event.Mouse; 31 /// Keyboard Event 32 public alias KeyboardEvent = Event.Keyboard; 33 34 /// MouseEvent function. Return true to drop event 35 alias MouseEventFuction = bool delegate(QWidget, MouseEvent); 36 /// KeyboardEvent function. Return true to drop event 37 alias KeyboardEventFunction = bool delegate(QWidget, KeyboardEvent, 38 bool); 39 /// ResizeEvent function. Return true to drop event 40 alias ResizeEventFunction = bool delegate(QWidget); 41 /// ScrollEvent function. Return true to drop event 42 alias ScrollEventFunction = bool delegate(QWidget); 43 /// ActivateEvent function. Return true to drop event 44 alias ActivateEventFunction = bool delegate(QWidget, bool); 45 /// TimerEvent function. Return true to drop event 46 alias TimerEventFunction = bool delegate(QWidget, uint); 47 /// Init function. Return true to drop event 48 alias InitFunction = bool delegate(QWidget); 49 /// UpdateEvent function. Return true to drop event 50 alias UpdateEventFunction = bool delegate(QWidget); 51 52 /// mask of events subscribed 53 enum EventMask : uint{ 54 /// mouse clicks/presses. 55 /// This value matches `MouseEvent.State.Click` 56 MousePress = 1, 57 /// mouse releases 58 /// This value matches `MouseEvent.State.Release` 59 MouseRelease = 1 << 1, 60 /// mouse move/hover. 61 /// This value matches `MouseEvent.State.Hover` 62 MouseHover = 1 << 2, 63 /// key presses. 64 /// This value matches `KeyboardEvent.State.Pressed` 65 KeyboardPress = 1 << 3, 66 /// key releases. 67 /// This value matches `KeyboardEvent.State.Released` 68 KeyboardRelease = 1 << 4, 69 /// widget scrolling. 70 Scroll = 1 << 5, 71 /// widget resize. 72 Resize = 1 << 6, 73 /// widget activated/deactivated. 74 Activate = 1 << 7, 75 /// timer 76 Timer = 1 << 8, 77 /// initialize 78 Initialize = 1 << 9, 79 /// draw itself. 80 Update = 1 << 10, 81 /// All mouse events 82 MouseAll = MousePress | MouseRelease | MouseHover, 83 /// All keyboard events 84 KeyboardAll = KeyboardPress | KeyboardRelease, 85 /// All keyboard and mouse events 86 InputAll = MouseAll | KeyboardAll, 87 } 88 89 /// A cell on terminal 90 struct Cell{ 91 /// character 92 dchar c; 93 /// foreground color 94 Color fg; 95 /// background colors 96 Color bg; 97 /// if colors are same with another Cell 98 bool colorsSame(Cell b){ 99 return this.fg == b.fg && this.bg == b.fg; 100 } 101 } 102 103 /// Display buffer 104 struct Viewport{ 105 private: 106 Cell[] _buffer; 107 /// where cursor is right now 108 uint _seekX, _seekY; 109 /// size of buffer (from `SIZE - offset`) 110 uint _width, _height; 111 /// actual width of the full buffer 112 uint _actualWidth; 113 // these are subtracted, only when seek is set, not after 114 uint _offsetX, _offsetY; 115 116 /// reset 117 void _reset(){ 118 _buffer = []; 119 _seekX = 0; 120 _seekY = 0; 121 _width = 0; 122 _height = 0; 123 _actualWidth = 0; 124 _offsetX = 0; 125 _offsetY = 0; 126 } 127 128 /// seek position in _buffer calculated from _seekX & _seekY 129 @property int _seek(){ 130 return (_seekX - _offsetX) + 131 ((_seekY - _offsetY) * _actualWidth); 132 } 133 /// set another Viewport so that it is a rectangular slice 134 /// of this 135 /// 136 /// Returns: true if done, false if not 137 void _getSlice(Viewport* sub, int x, int y, uint width, uint height){ 138 sub._reset(); 139 sub._actualWidth = _actualWidth; 140 if (width == 0 || height == 0) 141 return; 142 x -= _offsetX; 143 y -= _offsetY; 144 if (x > cast(int)_width || y > cast(int)_height || 145 x + width <= 0 || y + height <= 0) 146 return; 147 if (x < 0){ 148 sub._offsetX = -x; 149 width += x; 150 x = 0; 151 } 152 if (y < 0){ 153 sub._offsetY = -y; 154 height += y; 155 y = 0; 156 } 157 if (width + x > _width) 158 width = _width - x; 159 if (height + y > _height) 160 height = _height - y; 161 immutable uint buffStart = x + (y*_actualWidth), 162 buffEnd = buffStart + ((height-1)*_actualWidth) + width; 163 if (buffEnd > _buffer.length || buffStart >= buffEnd) 164 return; 165 sub._width = width; 166 sub._height = height; 167 sub._buffer = _buffer[buffStart .. buffEnd]; 168 } 169 public: 170 /// if x and y are at a position where writing can happen 171 bool isWritable(uint x, uint y){ 172 return x >= _offsetX && y >= _offsetY && 173 x < _width + _offsetX && y < _height + _offsetY; 174 } 175 /// move to a position. if x > width, moved to x=0 of next row 176 void moveTo(uint x, uint y){ 177 _seekX = x; 178 _seekY = y; 179 if (_seekX >= _width){ 180 _seekX = 0; 181 _seekY ++; 182 } 183 } 184 /// Writes a character at current position and move ahead 185 /// 186 /// Returns: false if outside writing area 187 bool write(dchar c, Color fg, Color bg){ 188 if (_seekX < _offsetX || _seekY < _offsetY){ 189 _seekX ++; 190 if (_seekX >= _width){ 191 _seekX = 0; 192 _seekY ++; 193 } 194 return false; 195 } 196 if (_seekX >= _width && _seekY >= _height) 197 return false; 198 if (_buffer.length > _seek) 199 _buffer[_seek] = Cell(c, fg, bg); 200 _seekX ++; 201 if (_seekX >= _width){ 202 _seekX = 0; 203 _seekY ++; 204 } 205 return true; 206 } 207 /// Writes a string. 208 /// 209 /// Returns: number of characters written 210 uint write(dstring s, Color fg, Color bg){ 211 uint r; 212 foreach (c; s){ 213 if (!write(c, fg, bg)) 214 break; 215 r ++; 216 } 217 return r; 218 } 219 /// Fills line, starting from current coordinates, 220 /// with maximum `max` number of chars, if `max>0` 221 /// 222 /// Returns: number of characters written 223 uint fillLine(dchar c, Color fg, Color bg, uint max=0){ 224 uint r = 0; 225 const uint currentY = _seekY; 226 bool status = true; 227 while (status && (max == 0 || r < max) && 228 _seekY == currentY){ 229 status = write(c, fg, bg); 230 r ++; 231 } 232 return r; 233 } 234 } 235 236 /// Base class for all widgets, including layouts and QTerminal 237 /// 238 /// Use this as parent-class for new widgets 239 abstract class QWidget{ 240 private: 241 /// position of this widget, relative to parent 242 uint _posX, _posY; 243 /// width of widget 244 uint _width; 245 /// height of widget 246 uint _height; 247 /// scroll 248 uint _scrollX, _scrollY; 249 /// viewport 250 Viewport _view; 251 /// if this widget is the active widget 252 bool _isActive = false; 253 /// whether this widget is requesting update 254 bool _requestingUpdate = true; 255 /// Whether to call resize before next update 256 bool _requestingResize = false; 257 /// the parent widget 258 QWidget _parent = null; 259 /// if it can make parent change scrolling 260 bool _canReqScroll = false; 261 /// if this widget is itself a scrollable container 262 bool _isScrollableContainer = false; 263 /// Events this widget is subscribed to, see `EventMask` 264 uint _eventSub = 0; 265 /// whether this widget should be drawn or not. 266 bool _show = true; 267 /// specifies ratio of height or width 268 uint _sizeRatio = 1; 269 270 /// custom onInit event 271 InitFunction _customInitEvent; 272 /// custom mouse event 273 MouseEventFuction _customMouseEvent; 274 /// custom keyboard event 275 KeyboardEventFunction _customKeyboardEvent; 276 /// custom resize event 277 ResizeEventFunction _customResizeEvent; 278 /// custom rescroll event 279 ScrollEventFunction _customScrollEvent; 280 /// custom onActivate event, 281 ActivateEventFunction _customActivateEvent; 282 /// custom onTimer event 283 TimerEventFunction _customTimerEvent; 284 /// custom upedateEvent 285 UpdateEventFunction _customUpdateEvent; 286 287 /// Called when it needs to request an update. 288 void _requestUpdate(){ 289 if (_requestingUpdate || !(_eventSub & EventMask.Update)) 290 return; 291 _requestingUpdate = true; 292 if (_parent) 293 _parent.requestUpdate(); 294 } 295 /// Called to request this widget to resize at next update 296 void _requestResize(){ 297 if (_requestingResize) 298 return; 299 _requestingResize = true; 300 if (_parent) 301 _parent.requestResize(); 302 } 303 /// Called to request cursor to be positioned at x,y 304 /// Will do nothing if not active widget 305 void _requestCursorPos(int x, int y){ 306 if (_isActive && _parent) 307 _parent.requestCursorPos(x < 0 ? 308 x : 309 _posX + x - _view._offsetX, 310 y < 0 ? 311 y : 312 _posY + y - _view._offsetY); 313 } 314 /// Called to request _scrollX to be adjusted 315 /// Returns: true if _scrollX was modified 316 bool _requestScrollX(uint x){ 317 if (_canReqScroll && _parent && _view._width < _width && 318 x < _width - _view._width) 319 return _parent.requestScrollX(x + _posX); 320 return false; 321 } 322 /// Called to request _scrollY to be adjusted 323 /// Returns: true if _scrollY was modified 324 bool _requestScrollY(uint y){ 325 if (_canReqScroll && _parent && _view._height < _height && 326 y < _height - _view._height) 327 return _parent.requestScrollY(y + _posY); 328 return false; 329 } 330 331 /// called by parent for initialize event 332 bool _initializeCall(){ 333 if (!(_eventSub & EventMask.Initialize)) 334 return false; 335 if (_customInitEvent && _customInitEvent(this)) 336 return true; 337 return this.initialize(); 338 } 339 /// called by parent for mouseEvent 340 bool _mouseEventCall(MouseEvent mouse){ 341 if (!(_eventSub & mouse.state)) 342 return false; 343 // mouse input comes relative to visible area 344 mouse.x = (mouse.x - cast(int)this._posX) + _view._offsetX; 345 mouse.y = (mouse.y - cast(int)this._posY) + _view._offsetY; 346 if (_customMouseEvent && _customMouseEvent(this, mouse)) 347 return true; 348 return this.mouseEvent(mouse); 349 } 350 /// called by parent for keyboardEvent 351 bool _keyboardEventCall(KeyboardEvent key, bool cycle){ 352 if (!(_eventSub & key.state)) 353 return false; 354 if (_customKeyboardEvent && 355 _customKeyboardEvent(this, key, cycle)) 356 return true; 357 return this.keyboardEvent(key, cycle); 358 } 359 /// called by parent for resizeEvent 360 bool _resizeEventCall(){ 361 _requestingResize = false; 362 _requestUpdate(); 363 if (!(_eventSub & EventMask.Resize)) 364 return false; 365 if (_customResizeEvent && _customResizeEvent(this)) 366 return true; 367 return this.resizeEvent(); 368 } 369 /// called by parent for scrollEvent 370 bool _scrollEventCall(){ 371 _requestUpdate(); 372 if (!(_eventSub & EventMask.Scroll)) 373 return false; 374 if (_customScrollEvent && _customScrollEvent(this)) 375 return true; 376 return this.scrollEvent(); 377 } 378 /// called by parent for activateEvent 379 bool _activateEventCall(bool isActive){ 380 if (!(_eventSub & EventMask.Activate)) 381 return false; 382 if (_isActive == isActive) 383 return false; 384 _isActive = isActive; 385 if (_customActivateEvent && 386 _customActivateEvent(this, isActive)) 387 return true; 388 return this.activateEvent(isActive); 389 } 390 /// called by parent for timerEvent 391 bool _timerEventCall(uint msecs){ 392 if (!(_eventSub & EventMask.Timer)) 393 return false; 394 if (_customTimerEvent && _customTimerEvent(this, msecs)) 395 return true; 396 return timerEvent(msecs); 397 } 398 /// called by parent for updateEvent 399 bool _updateEventCall(){ 400 if (!_requestingUpdate || !(_eventSub & EventMask.Update)) 401 return false; 402 _requestingUpdate = false; 403 _view.moveTo(_view._offsetX,_view._offsetY); 404 if (_customUpdateEvent && _customUpdateEvent(this)) 405 return true; 406 return this.updateEvent(); 407 } 408 protected: 409 /// minimum width 410 uint _minWidth; 411 /// maximum width 412 uint _maxWidth; 413 /// minimum height 414 uint _minHeight; 415 /// maximum height 416 uint _maxHeight; 417 418 /// viewport coordinates. (drawable area for widget) 419 final @property uint viewportX(){ 420 return _view._offsetX; 421 } 422 /// ditto 423 final @property uint viewportY(){ 424 return _view._offsetY; 425 } 426 /// viewport size. (drawable area for widget) 427 final @property uint viewportWidth(){ 428 return _view._width; 429 } 430 /// ditto 431 final @property uint viewportHeight(){ 432 return _view._height; 433 } 434 435 /// If a coordinate is within writing area, 436 /// and writing area actually exists 437 final bool isWritable(uint x, uint y){ 438 return _view.isWritable(x,y); 439 } 440 441 /// move seek for next write to terminal. 442 /// can only write in between: 443 /// `(_viewX .. _viewX + _viewWidth, 444 /// _viewY .. _viewX + _viewHeight)` 445 final void moveTo(uint newX, uint newY){ 446 _view.moveTo(newX, newY); 447 } 448 /// writes a character on terminal 449 /// 450 /// Returns: true if done, false if outside writing area 451 final bool write(dchar c, Color fg, Color bg){ 452 return _view.write(c, fg, bg); 453 } 454 /// writes a string to terminal. 455 /// if it does not fit in one line, it is wrapped 456 /// 457 /// Returns: number of characters written 458 final uint write(dstring s, Color fg, Color bg){ 459 return _view.write(s, fg, bg); 460 } 461 /// fill current line with a character. 462 /// `max` is ignored if `max==0` 463 /// 464 /// Returns: number of cells written 465 final uint fillLine(dchar c, Color fg, Color bg, uint max = 0){ 466 return _view.fillLine(c, fg, bg, max); 467 } 468 469 /// activate the passed widget if this is the correct widget 470 /// 471 /// Returns: if it was activated or not 472 bool searchAndActivateWidget(QWidget target) { 473 if (this == target) { 474 this._isActive = true; 475 this._activateEventCall(true); 476 return true; 477 } 478 return false; 479 } 480 481 /// called by itself, to update events subscribed to 482 final void eventSubscribe(uint newSub){ 483 _eventSub = newSub; 484 if (_parent) 485 _parent.eventSubscribe(); 486 } 487 /// called by children when they want to subscribe to events 488 void eventSubscribe(){} 489 490 /// to set cursor position on terminal. 491 /// only works if this is active widget. 492 /// set x or y or both to negative to hide cursor 493 void requestCursorPos(int x, int y){ 494 _requestCursorPos(x, y); 495 } 496 497 /// called to request to scrollX 498 bool requestScrollX(uint x){ 499 return _requestScrollX(x); 500 } 501 /// called to request to scrollY 502 bool requestScrollY(uint y){ 503 return _requestScrollY(y); 504 } 505 506 /// Called after UI has been run 507 bool initialize(){ 508 return false; 509 } 510 /// Called when mouse is clicked with cursor on this widget. 511 bool mouseEvent(MouseEvent mouse){ 512 return false; 513 } 514 /// Called when key is pressed and this widget is active. 515 /// 516 /// `cycle` indicates if widget cycling should happen, if this 517 /// widget has child widgets 518 bool keyboardEvent(KeyboardEvent key, bool cycle){ 519 return false; 520 } 521 /// Called when widget size is changed, 522 bool resizeEvent(){ 523 return false; 524 } 525 /// Called when the widget is rescrolled, ~but size not changed.~ 526 bool scrollEvent(){ 527 return false; 528 } 529 /// called right after this widget is activated, or de-activated 530 bool activateEvent(bool isActive){ 531 return false; 532 } 533 /// called often. 534 /// 535 /// `msecs` is the msecs since last timerEvent, not accurate 536 bool timerEvent(uint msecs){ 537 return false; 538 } 539 /// Called when this widget should re-draw itself 540 bool updateEvent(){ 541 return false; 542 } 543 public: 544 /// To request parent to trigger an update event 545 void requestUpdate(){ 546 _requestUpdate(); 547 } 548 /// To request parent to trigger a resize event 549 void requestResize(){ 550 _requestResize(); 551 } 552 /// custom initialize event 553 final @property InitFunction onInitEvent(InitFunction func){ 554 return _customInitEvent = func; 555 } 556 /// custom mouse event 557 final @property MouseEventFuction onMouseEvent( 558 MouseEventFuction func){ 559 return _customMouseEvent = func; 560 } 561 /// custom keyboard event 562 final @property KeyboardEventFunction onKeyboardEvent( 563 KeyboardEventFunction func){ 564 return _customKeyboardEvent = func; 565 } 566 /// custom resize event 567 final @property ResizeEventFunction onResizeEvent( 568 ResizeEventFunction func){ 569 return _customResizeEvent = func; 570 } 571 /// custom scroll event 572 final @property ScrollEventFunction onScrollEvent( 573 ScrollEventFunction func){ 574 return _customScrollEvent = func; 575 } 576 /// custom activate event 577 final @property ActivateEventFunction onActivateEvent( 578 ActivateEventFunction func){ 579 return _customActivateEvent = func; 580 } 581 /// custom timer event 582 final @property TimerEventFunction onTimerEvent( 583 TimerEventFunction func){ 584 return _customTimerEvent = func; 585 } 586 /// Returns: true if this widget is the current active widget 587 final @property bool isActive(){ 588 return _isActive; 589 } 590 /// Returns: true if this widget is a container that supports 591 /// scrolling 592 final @property bool isScrollableContainer(){ 593 return _isScrollableContainer; 594 } 595 /// Returns: EventMask of subscribed events 596 final @property uint eventSub(){ 597 return _eventSub; 598 } 599 /// ratio of height or width 600 final @property uint sizeRatio(){ 601 return _sizeRatio; 602 } 603 /// ditto 604 final @property uint sizeRatio(uint newRatio){ 605 _sizeRatio = newRatio; 606 _requestResize(); 607 return _sizeRatio; 608 } 609 /// if widget is visible. 610 final @property bool show(){ 611 return _show; 612 } 613 /// ditto 614 final @property bool show(bool visibility){ 615 _show = visibility; 616 _requestResize(); 617 return _show; 618 } 619 /// horizontal scroll. 620 final @property uint scrollX(){ 621 return _scrollX; 622 } 623 /// ditto 624 final @property uint scrollX(uint newVal){ 625 _requestScrollX(newVal); 626 return _scrollX; 627 } 628 /// vertical scroll. 629 final @property uint scrollY(){ 630 return _scrollY; 631 } 632 /// ditto 633 final @property uint scrollY(uint newVal){ 634 _requestScrollY(newVal); 635 return _scrollY; 636 } 637 /// width of widget 638 final @property uint width(){ 639 return _width; 640 } 641 /// ditto 642 @property uint width(uint value){ 643 _minWidth = value; 644 _maxWidth = value; 645 _requestResize(); 646 return value; 647 } 648 /// height of widget 649 final @property uint height(){ 650 return _height; 651 } 652 /// ditto 653 @property uint height(uint value){ 654 _minHeight = value; 655 _maxHeight = value; 656 _requestResize(); 657 return value; 658 } 659 /// minimum width 660 @property uint minWidth(){ 661 return _minWidth; 662 } 663 /// ditto 664 @property uint minWidth(uint value){ 665 _requestResize(); 666 return _minWidth = value; 667 } 668 /// minimum height 669 @property uint minHeight(){ 670 return _minHeight; 671 } 672 /// ditto 673 @property uint minHeight(uint value){ 674 _requestResize(); 675 return _minHeight = value; 676 } 677 /// maximum width 678 @property uint maxWidth(){ 679 return _maxWidth; 680 } 681 /// ditto 682 @property uint maxWidth(uint value){ 683 _requestResize(); 684 return _maxWidth = value; 685 } 686 /// maximum height 687 @property uint maxHeight(){ 688 return _maxHeight; 689 } 690 /// ditto 691 @property uint maxHeight(uint value){ 692 _requestResize(); 693 return _maxHeight = value; 694 } 695 } 696 697 /// Used to place widgets in an order (i.e vertical or horizontal) 698 class QLayout : QWidget{ 699 private: 700 /// widgets 701 QWidget[] _widgets; 702 /// layout type, horizontal or vertical 703 QLayout.Type _type; 704 /// index of active widget. -1 if none. 705 int _activeWidgetIndex = -1; 706 /// if it is overflowing 707 bool _isOverflowing = false; 708 /// Color to fill with in unoccupied space 709 Color _fillColor; 710 /// Color to fill with when overflowing 711 Color _overflowColor = DEFAULT_OVERFLOW_BG; 712 713 /// gets height/width of a widget using: 714 /// it's sizeRatio and min/max-height/width 715 uint _calculateWidgetSize(QWidget widget, uint ratioTotal, 716 uint totalSpace, ref bool free){ 717 immutable uint calculatedSize = 718 cast(uint)(widget.sizeRatio * totalSpace / ratioTotal); 719 if (_type == QLayout.Type.Horizontal){ 720 free = widget.minWidth == 0 && widget.maxWidth == 0; 721 return getLimitedSize(calculatedSize, widget.minWidth, 722 widget.maxWidth); 723 } 724 free = widget.minHeight == 0 && widget.maxHeight == 0; 725 return getLimitedSize(calculatedSize, widget.minHeight, 726 widget.maxHeight); 727 } 728 729 /// recalculates the size of every widget inside layout 730 void _recalculateWidgetsSize(){ 731 FIFOStack!QWidget widgetStack = new FIFOStack!QWidget; 732 uint totalRatio = 0; 733 uint totalSpace = _type == QLayout.Type.Horizontal ? 734 _width : _height; 735 bool free = false; 736 foreach (widget; _widgets){ 737 if (!widget.show) 738 continue; 739 totalRatio += widget.sizeRatio; 740 widget._height = getLimitedSize(_height, 741 widget.minHeight, widget.maxHeight); 742 743 widget._width = getLimitedSize(_width, 744 widget.minWidth, widget.maxWidth); 745 widgetStack.push(widget); 746 } 747 // do widgets with size limits 748 /// totalRatio, and space used of widgets with limits 749 uint limitWRatio, limitWSize; 750 for (int i = 0; i < widgetStack.count; i ++){ 751 QWidget widget = widgetStack.pop; 752 immutable uint space = _calculateWidgetSize(widget, 753 totalRatio, totalSpace, free); 754 if (free){ 755 widgetStack.push(widget); 756 continue; 757 } 758 if (_type == QLayout.Type.Horizontal) 759 widget._width = space; 760 else 761 widget._height = space; 762 limitWRatio += widget.sizeRatio; 763 limitWSize += space; 764 } 765 totalSpace -= limitWSize; 766 totalRatio -= limitWRatio; 767 while (widgetStack.count){ 768 QWidget widget = widgetStack.pop; 769 immutable uint space = _calculateWidgetSize(widget, 770 totalRatio, totalSpace, free); 771 if (_type == QLayout.Type.Horizontal) 772 widget._width = space; 773 else 774 widget._height = space; 775 totalRatio -= widget.sizeRatio; 776 totalSpace -= space; 777 } 778 .destroy(widgetStack); 779 } 780 781 /// find the next widget to activate 782 /// Returns: index, or -1 if no one wanna be active 783 int _nextActiveWidget(){ 784 for (int i = _activeWidgetIndex + 1; i < _widgets.length; 785 i ++){ 786 if ((_widgets[i].eventSub & EventMask.KeyboardAll) && 787 _widgets[i].show) 788 return i; 789 } 790 return -1; 791 } 792 protected: 793 override void eventSubscribe(){ 794 _eventSub = 0; 795 foreach (widget; _widgets) 796 _eventSub |= widget.eventSub; 797 798 // if children can become active, then need activate too 799 if (_eventSub & EventMask.KeyboardAll) 800 _eventSub |= EventMask.Activate; 801 802 _eventSub |= EventMask.Scroll; 803 804 if (_parent) 805 _parent.eventSubscribe(); 806 } 807 808 /// Recalculates size and position for all visible widgets 809 override bool resizeEvent(){ 810 _recalculateWidgetsSize(); // resize everything 811 // if parent is scrollable container, and there are no size 812 // limits, then grow as needed 813 if (minHeight + maxHeight + minWidth + maxWidth == 0 && 814 _parent && _parent.isScrollableContainer){ 815 _width = 0; 816 _height = 0; 817 if (_type == Type.Horizontal){ 818 foreach (widget; _widgets){ 819 if (_height < widget.height) 820 _height = widget.height; 821 _width += widget.width; 822 } 823 }else{ 824 foreach (widget; _widgets){ 825 if (_width < widget.width) 826 _width = widget.width; 827 _height += widget.height; 828 } 829 } 830 } 831 // now reposition everything 832 /// space taken by widgets before 833 uint previousSpace = 0; 834 uint w, h; 835 foreach(widget; _widgets){ 836 if (!widget.show) 837 continue; 838 if (_type == QLayout.Type.Horizontal){ 839 widget._posY = 0; 840 widget._posX = previousSpace; 841 previousSpace += widget.width; 842 w += widget.width; 843 if (widget.height > h) 844 h = widget.height; 845 }else{ 846 widget._posX = 0; 847 widget._posY = previousSpace; 848 previousSpace += widget.height; 849 h += widget.height; 850 if (widget.width > w) 851 w = widget.width; 852 } 853 } 854 _isOverflowing = w > _width || h > _height; 855 if (_isOverflowing){ 856 foreach (widget; _widgets) 857 widget._view._reset(); 858 }else{ 859 foreach (i, widget; _widgets){ 860 _view._getSlice(&(widget._view), widget._posX, 861 widget._posY, widget.width, widget.height); 862 widget._resizeEventCall(); 863 } 864 } 865 return true; 866 } 867 868 /// Resize event 869 override bool scrollEvent(){ 870 if (_isOverflowing){ 871 foreach (widget; _widgets) 872 widget._view._reset(); 873 return false; 874 } 875 foreach (i, widget; _widgets){ 876 _view._getSlice(&(widget._view), widget._posX, 877 widget._posY, widget.width, widget.height); 878 widget._scrollEventCall(); 879 } 880 return true; 881 } 882 883 /// Redirects the mouseEvent to the appropriate widget 884 override public bool mouseEvent(MouseEvent mouse){ 885 if (_isOverflowing) 886 return false; 887 int index; 888 if (_type == Type.Horizontal){ 889 foreach (i, w; _widgets){ 890 if (w.show && w._posX <= mouse.x && 891 w._posX + w.width > mouse.x){ 892 index = cast(int)i; 893 break; 894 } 895 } 896 }else{ 897 foreach (i, w; _widgets){ 898 if (w.show && w._posY <= mouse.y && 899 w._posY + w.height > mouse.y){ 900 index = cast(int)i; 901 break; 902 } 903 } 904 } 905 if (index > -1){ 906 if (mouse.state != MouseEvent.State.Hover && 907 index != _activeWidgetIndex && 908 _widgets[index].eventSub & 909 EventMask.KeyboardAll){ 910 if (_activeWidgetIndex > -1) 911 _widgets[_activeWidgetIndex]._activateEventCall( 912 false); 913 _widgets[index]._activateEventCall(true); 914 _activeWidgetIndex = index; 915 } 916 return _widgets[index]._mouseEventCall(mouse); 917 } 918 return false; 919 } 920 921 /// Redirects the keyboardEvent to appropriate widget 922 override public bool keyboardEvent(KeyboardEvent key, 923 bool cycle){ 924 if (_isOverflowing) 925 return false; 926 if (_activeWidgetIndex > -1 && 927 _widgets[_activeWidgetIndex]._keyboardEventCall( 928 key, cycle)) 929 return true; 930 931 if (!cycle) 932 return false; 933 immutable int next = _nextActiveWidget(); 934 935 if (_activeWidgetIndex > -1 && next != _activeWidgetIndex) 936 _widgets[_activeWidgetIndex]._activateEventCall(false); 937 938 if (next == -1) 939 return false; 940 _activeWidgetIndex = next; 941 _widgets[_activeWidgetIndex]._activateEventCall(true); 942 return true; 943 } 944 945 /// initialise 946 override bool initialize(){ 947 foreach (widget; _widgets){ 948 widget._view._reset(); 949 widget._initializeCall(); 950 } 951 return true; 952 } 953 954 /// timer 955 override bool timerEvent(uint msecs){ 956 foreach (widget; _widgets) 957 widget._timerEventCall(msecs); 958 return true; 959 } 960 961 /// activate event 962 override bool activateEvent(bool isActive){ 963 if (isActive){ 964 _activeWidgetIndex = -1; 965 _activeWidgetIndex = _nextActiveWidget(); 966 } 967 if (_activeWidgetIndex > -1) 968 _widgets[_activeWidgetIndex]._activateEventCall( 969 isActive); 970 return true; 971 } 972 973 /// called by parent widget to update 974 override bool updateEvent(){ 975 if (_isOverflowing){ 976 foreach (y; viewportY .. viewportY + viewportHeight){ 977 moveTo(viewportX, y); 978 fillLine(' ', DEFAULT_FG, _overflowColor); 979 } 980 return false; 981 } 982 if (_type == Type.Horizontal){ 983 foreach(i, widget; _widgets){ 984 if (widget.show && widget._requestingUpdate) 985 widget._updateEventCall(); 986 foreach (y; widget.height .. 987 viewportY + viewportHeight){ 988 moveTo(widget._posX, y); 989 fillLine(' ', DEFAULT_FG, _fillColor, 990 widget.width); 991 } 992 } 993 }else{ 994 foreach(i, widget; _widgets){ 995 if (widget.show && widget._requestingUpdate) 996 widget._updateEventCall(); 997 if (widget.width == _width) 998 continue; 999 foreach (y; widget._posY .. 1000 widget._posY + widget.height){ 1001 moveTo(widget._posX + widget.width, y); 1002 fillLine(' ', DEFAULT_FG, _fillColor); 1003 } 1004 } 1005 } 1006 return true; 1007 } 1008 1009 /// activate the passed widget if it's in the current layout 1010 /// Returns: true if it was activated or not 1011 override bool searchAndActivateWidget(QWidget target){ 1012 immutable int lastActiveWidgetIndex = _activeWidgetIndex; 1013 1014 // search and activate recursively 1015 _activeWidgetIndex = -1; 1016 foreach (index, widget; _widgets) { 1017 if ((widget.eventSub & EventMask.KeyboardAll) && 1018 widget.show && 1019 widget.searchAndActivateWidget(target)){ 1020 _activeWidgetIndex = cast(int)index; 1021 break; 1022 } 1023 } 1024 1025 // and then manipulate the current layout 1026 if (lastActiveWidgetIndex != _activeWidgetIndex && 1027 lastActiveWidgetIndex > -1) 1028 _widgets[lastActiveWidgetIndex]._activateEventCall( 1029 false); 1030 return _activeWidgetIndex != -1; 1031 } 1032 1033 public: 1034 /// Layout type 1035 enum Type{ 1036 Vertical, 1037 Horizontal, 1038 } 1039 /// constructor 1040 this(QLayout.Type type){ 1041 _type = type; 1042 this._fillColor = DEFAULT_BG; 1043 } 1044 /// destructor, kills children 1045 ~this(){ 1046 foreach (child; _widgets) 1047 .destroy(child); 1048 } 1049 /// Color for unoccupied space 1050 @property Color fillColor(){ 1051 return _fillColor; 1052 } 1053 /// ditto 1054 @property Color fillColor(Color newColor){ 1055 return _fillColor = newColor; 1056 } 1057 /// Color to fill with when out of space 1058 @property Color overflowColor(){ 1059 return _overflowColor; 1060 } 1061 /// ditto 1062 @property Color overflowColor(Color newColor){ 1063 return _overflowColor = newColor; 1064 } 1065 /// adds a widget 1066 /// 1067 /// `allowScrollControl` specifies if the widget will be able 1068 /// to make scrolling requests 1069 void addWidget(QWidget widget, bool allowScrollControl = false){ 1070 widget._parent = this; 1071 widget._canReqScroll = allowScrollControl; 1072 _widgets ~= widget; 1073 eventSubscribe(); 1074 } 1075 /// ditto 1076 void addWidget(QWidget[] widgets){ 1077 foreach (i, widget; widgets){ 1078 widget._parent = this; 1079 } 1080 _widgets ~= widgets; 1081 eventSubscribe(); 1082 } 1083 } 1084 1085 /// A Scrollable Container 1086 class ScrollContainer : QWidget{ 1087 private: 1088 /// offset in _widget before adding scrolling to it 1089 uint _offX, _offY; 1090 protected: 1091 /// widget 1092 QWidget _widget; 1093 /// if scrollbar is to be shown 1094 bool _scrollbarV, _scrollbarH; 1095 /// if page down/up button should scroll 1096 bool _pgDnUp = false; 1097 /// if mouse wheel should scroll 1098 bool _mouseWheel = false; 1099 /// height and width available to widget 1100 uint _drawAreaHeight, _drawAreaWidth; 1101 /// Scrollbar colors 1102 Color _sbarBg = DEFAULT_BG, _sbarFg = DEFAULT_FG; 1103 1104 override void eventSubscribe(){ 1105 _eventSub = EventMask.Resize | EventMask.Scroll; 1106 if (_widget) 1107 _eventSub |= _widget.eventSub; 1108 if (_pgDnUp) 1109 _eventSub |= EventMask.KeyboardPress; 1110 if (_mouseWheel) 1111 _eventSub |= EventMask.MouseAll; 1112 if (_scrollbarH || _scrollbarV) 1113 _eventSub |= EventMask.Update; 1114 if (_parent) 1115 _parent.eventSubscribe(); 1116 } 1117 1118 /// re-assings display buffer based on _subScrollX/Y, 1119 /// and calls scrollEvent on child if `callScrollEvents` 1120 final void rescroll(bool callScrollEvent = true){ 1121 if (!_widget) 1122 return; 1123 _widget._view._reset(); 1124 if (_width == 0 || _height == 0) 1125 return; 1126 uint w = _drawAreaWidth, h = _drawAreaHeight; 1127 if (w > _widget.width) 1128 w = _widget.width; 1129 if (h > _widget.height) 1130 h = _widget.height; 1131 _view._getSlice(&(_widget._view), 0, 0, w, h); 1132 _widget._view._offsetX = _offX + _widget.scrollX; 1133 _widget._view._offsetY = _offY + _widget.scrollY; 1134 if (callScrollEvent) 1135 _widget._scrollEventCall(); 1136 } 1137 1138 override bool requestScrollX(uint x){ 1139 if (!_widget) 1140 return false; 1141 if (_widget.width <= _drawAreaWidth) 1142 x = 0; 1143 else if (x > _widget.width - _drawAreaWidth) 1144 x = _widget.width - _drawAreaWidth; 1145 1146 if (_widget.scrollX == x) 1147 return false; 1148 _widget._scrollX = x; 1149 rescroll(); 1150 return true; 1151 } 1152 override bool requestScrollY(uint y){ 1153 if (!_widget) 1154 return false; 1155 if (_widget.height <= _drawAreaHeight) 1156 y = 0; 1157 else if (y > _widget.height - _drawAreaHeight) 1158 y = _widget.height - _drawAreaHeight; 1159 1160 if (_widget.scrollY == y) 1161 return false; 1162 _widget._scrollY = y; 1163 rescroll(); 1164 return true; 1165 } 1166 1167 override bool resizeEvent(){ 1168 _offX = _view._offsetX; 1169 _offY = _view._offsetY; 1170 _drawAreaHeight = _height - (1*(_height>0 && _scrollbarV)); 1171 _drawAreaWidth = _width - (1*(_width>0 && _scrollbarV)); 1172 if (!_widget) 1173 return false; 1174 if (_scrollbarH || _scrollbarV) 1175 requestUpdate(); 1176 // try to size widget to fit 1177 if (_height > 0 && _width > 0){ 1178 _widget._width = getLimitedSize(_drawAreaWidth, 1179 _widget.minWidth, _widget.maxWidth); 1180 _widget._height = getLimitedSize(_drawAreaHeight, 1181 _widget.minHeight, _widget.maxHeight); 1182 } 1183 rescroll(false); 1184 _widget._resizeEventCall(); 1185 1186 return true; 1187 } 1188 1189 override bool scrollEvent(){ 1190 _offX = _view._offsetX; 1191 _offY = _view._offsetY; 1192 if (_scrollbarH || _scrollbarV) 1193 requestUpdate(); 1194 rescroll(); 1195 1196 return true; 1197 } 1198 1199 override bool keyboardEvent(KeyboardEvent key, bool cycle){ 1200 if (!_widget) 1201 return false; 1202 if (_widget.isActive && _widget._keyboardEventCall(key, 1203 cycle)) 1204 return true; 1205 1206 if (!cycle && _pgDnUp && 1207 key.state == KeyboardEvent.State.Pressed){ 1208 if (key.key == Key.PageUp){ 1209 return requestScrollY( 1210 _drawAreaHeight > _widget.scrollY ? 0 : 1211 _widget.scrollY - _drawAreaHeight); 1212 } 1213 if (key.key == Key.PageDown){ 1214 return requestScrollY( 1215 _drawAreaHeight + _widget.scrollY > 1216 _widget.height ? 1217 _widget.height - _drawAreaHeight : 1218 _widget.scrollY + _drawAreaHeight); 1219 } 1220 } 1221 _widget._activateEventCall(false); 1222 return false; 1223 } 1224 1225 override bool mouseEvent(MouseEvent mouse){ 1226 if (!_widget) 1227 return false; 1228 if (_widget._mouseEventCall(mouse)){ 1229 _widget._activateEventCall(true); 1230 return true; 1231 } 1232 _widget._activateEventCall(false); 1233 if (_mouseWheel && _drawAreaHeight < _widget.height){ 1234 if (mouse.button == mouse.Button.ScrollUp){ 1235 if (_widget.scrollY) 1236 return requestScrollY(_widget.scrollY - 1); 1237 } 1238 if (mouse.button == mouse.Button.ScrollDown){ 1239 return requestScrollY(_widget.scrollY + 1); 1240 } 1241 } 1242 return false; 1243 } 1244 1245 override bool updateEvent(){ 1246 if (!_widget) 1247 return false; 1248 if (_widget.show) 1249 _widget._updateEventCall(); 1250 // TODO: fill unoccupied space, if any 1251 drawScrollbars(); 1252 1253 return true; 1254 } 1255 1256 /// draws scrollbars, very basic stuff 1257 void drawScrollbars(){ 1258 if (!_widget || _width == 0 || _height == 0) 1259 return; 1260 static const dchar verticalLine = '│', horizontalLine = '─'; 1261 static const dchar block = '█'; 1262 if (_scrollbarH && _scrollbarV){ 1263 moveTo(_width - 1, _height - 1); 1264 write('┘', _sbarFg, _sbarBg); 1265 } 1266 if (_scrollbarH){ 1267 moveTo(0, _drawAreaHeight); 1268 fillLine(horizontalLine, _sbarFg, _sbarBg, 1269 _drawAreaWidth); 1270 const int maxScroll = _widget.width - _drawAreaWidth; 1271 if (maxScroll > 0){ 1272 const uint barPos = 1273 (_widget.scrollX * _drawAreaWidth) / maxScroll; 1274 moveTo(barPos, _drawAreaHeight); 1275 write(block, _sbarFg, _sbarBg); 1276 } 1277 } 1278 if (_scrollbarV){ 1279 foreach (y; 0 .. _drawAreaHeight){ 1280 moveTo(_drawAreaWidth, y); 1281 write(verticalLine, _sbarFg, _sbarBg); 1282 } 1283 const int maxScroll = _widget.height - _drawAreaHeight; 1284 if (maxScroll > 0){ 1285 const uint barPos = 1286 (_widget.scrollY * _drawAreaHeight) / maxScroll; 1287 moveTo(_drawAreaWidth, barPos); 1288 write(block, _sbarFg, _sbarBg); 1289 } 1290 } 1291 } 1292 public: 1293 /// constructor 1294 this(){ 1295 this._isScrollableContainer = true; 1296 this._scrollbarV = true; 1297 this._scrollbarH = true; 1298 } 1299 ~this(){ 1300 if (_widget) 1301 .destroy(_widget); 1302 } 1303 1304 /// Scrollbar foreground color 1305 @property Color scrollbarForeground(){ 1306 return _sbarFg; 1307 } 1308 /// ditto 1309 @property Color scrollbarForeground(Color newVal){ 1310 _requestUpdate(); 1311 return _sbarFg = newVal; 1312 } 1313 /// Scrollbar background color 1314 @property Color scrollbarBackground(){ 1315 return _sbarBg; 1316 } 1317 /// ditto 1318 @property Color scrollbarBackground(Color newVal){ 1319 _requestUpdate(); 1320 return _sbarBg = newVal; 1321 } 1322 1323 /// Sets the child widget. 1324 /// 1325 /// Returns: false if already has a child 1326 bool setWidget(QWidget child){ 1327 if (_widget) 1328 return false; 1329 _widget = child; 1330 _widget._parent = this; 1331 _widget._canReqScroll = true; 1332 _widget._posX = 0; 1333 _widget._posY = 0; 1334 eventSubscribe(); 1335 return true; 1336 } 1337 1338 override void requestResize(){ 1339 // just do a the resize within itself 1340 _resizeEventCall(); 1341 } 1342 1343 /// Whether to scroll on page up/down keys 1344 @property bool scrollOnPageUpDown(){ 1345 return _pgDnUp; 1346 } 1347 /// ditto 1348 @property bool scrollOnPageUpDown(bool newVal){ 1349 _pgDnUp = newVal; 1350 eventSubscribe(); 1351 return _pgDnUp; 1352 } 1353 1354 /// Whether to scroll on mouse scroll wheel 1355 @property bool scrollOnMouseWheel(){ 1356 return _mouseWheel; 1357 } 1358 /// ditto 1359 @property bool scrollOnMouseWheel(bool newVal){ 1360 _mouseWheel = newVal; 1361 eventSubscribe(); 1362 return _mouseWheel; 1363 } 1364 1365 /// Whether to show vertical scrollbar. 1366 /// 1367 /// Modifying this will request update 1368 @property bool scrollbarV(){ 1369 return _scrollbarV; 1370 } 1371 /// ditto 1372 @property bool scrollbarV(bool newVal){ 1373 if (newVal != _scrollbarV){ 1374 _scrollbarV = newVal; 1375 rescroll(); 1376 } 1377 return _scrollbarV; 1378 } 1379 /// Whether to show horizontal scrollbar. 1380 /// 1381 /// Modifying this will request update 1382 @property bool scrollbarH(){ 1383 return _scrollbarH; 1384 } 1385 /// ditto 1386 @property bool scrollbarH(bool newVal){ 1387 if (newVal != _scrollbarH){ 1388 _scrollbarH = newVal; 1389 rescroll(); 1390 } 1391 return _scrollbarH; 1392 } 1393 } 1394 1395 /// Terminal 1396 class QTerminal : QLayout{ 1397 private: 1398 /// To actually access the terminal 1399 TermWrapper _termWrap; 1400 /// set to false to stop UI loop in run() 1401 bool _isRunning; 1402 /// the key used for cycling active widget 1403 dchar _activeWidgetCycleKey = WIDGET_CYCLE_KEY; 1404 /// whether to stop UI loop on Interrupt 1405 bool _stopOnInterrupt = true; 1406 /// cursor position 1407 int _cursorX = -1, _cursorY = -1; 1408 1409 /// Reads InputEvent and calls appropriate functions 1410 void _readEvent(Event event){ 1411 if (event.type == Event.Type.HangupInterrupt){ 1412 if (_stopOnInterrupt) 1413 _isRunning = false; 1414 else{ // otherwise read it as a Ctrl+C 1415 KeyboardEvent keyEvent; 1416 keyEvent.key = KeyboardEvent.CtrlKeys.CtrlC; 1417 this._keyboardEventCall(keyEvent, false); 1418 } 1419 }else if (event.type == Event.Type.Keyboard){ 1420 KeyboardEvent kPress = event.keyboard; 1421 this._keyboardEventCall(kPress, false); 1422 }else if (event.type == Event.Type.Mouse){ 1423 this._mouseEventCall(event.mouse); 1424 }else if (event.type == Event.Type.Resize){ 1425 this._resizeEventCall(); 1426 } 1427 } 1428 1429 /// writes _view to _termWrap 1430 void _flushBuffer(){ 1431 if (_view._buffer.length == 0) 1432 return; 1433 Cell prev = _view._buffer[0]; 1434 _termWrap.color(prev.fg, prev.bg); 1435 uint x, y; 1436 foreach (cell; _view._buffer){ 1437 if (!prev.colorsSame(cell)) 1438 _termWrap.color(cell.fg, cell.bg); 1439 prev = cell; 1440 _termWrap.put(x, y, cell.c); 1441 x ++; 1442 if (x == _width){ 1443 x = 0; 1444 y ++; 1445 } 1446 } 1447 } 1448 1449 protected: 1450 1451 override void eventSubscribe(){ 1452 // ignore what children want, this needs all events 1453 // so custom event handlers can be set up 1454 _eventSub = uint.max; 1455 } 1456 1457 override void requestCursorPos(int x, int y){ 1458 _cursorX = x; 1459 _cursorY = y; 1460 } 1461 1462 override bool resizeEvent(){ 1463 _height = _termWrap.height; 1464 _width = _termWrap.width; 1465 _view._buffer.length = _width * _height; 1466 _view._width = _width; 1467 _view._actualWidth = _width; 1468 _view._height = _height; 1469 super.resizeEvent(); 1470 return true; 1471 } 1472 1473 override bool keyboardEvent(KeyboardEvent key, bool cycle){ 1474 cycle = key.state == KeyboardEvent.State.Pressed && 1475 key.key == _activeWidgetCycleKey; 1476 return super.keyboardEvent(key, cycle); 1477 } 1478 1479 override bool updateEvent(){ 1480 // resize if needed 1481 if (_requestingResize) 1482 this._resizeEventCall(); 1483 _cursorX = -1; 1484 _cursorY = -1; 1485 // no, this is not a mistake, dont change this to 1486 // updateEventCall again! 1487 super.updateEvent(); 1488 // flush _view._buffer to _termWrap 1489 _flushBuffer(); 1490 // check if need to show/hide cursor 1491 if (_cursorX < 0 || _cursorY < 0) 1492 _termWrap.cursorVisible = false; 1493 else{ 1494 _termWrap.moveCursor(_cursorX, _cursorY); 1495 _termWrap.cursorVisible = true; 1496 } 1497 _termWrap.flush(); 1498 return true; 1499 } 1500 1501 public: 1502 /// time to wait between timer events (milliseconds) 1503 ushort timerMsecs; 1504 /// constructor 1505 this(QLayout.Type displayType = QLayout.Type.Vertical, 1506 ushort timerDuration = 500){ 1507 // fix for issue #18 (resizing on alacritty borked) 1508 if (environment["TERM"] == "alacritty") 1509 environment["TERM"] = "xterm"; 1510 super(displayType); 1511 timerMsecs = timerDuration; 1512 1513 _termWrap = new TermWrapper(); 1514 // so it can make other widgets active on mouse events 1515 this._isActive = true; 1516 } 1517 ~this(){ 1518 .destroy(_termWrap); 1519 } 1520 1521 /// stops UI loop. **not instant** 1522 /// if it is in-between event functions, it will complete 1523 /// those first 1524 void terminate(){ 1525 _isRunning = false; 1526 } 1527 1528 /// whether to stop UI loop on HangupInterrupt (Ctrl+C) 1529 @property bool terminateOnHangup(){ 1530 return _stopOnInterrupt; 1531 } 1532 /// ditto 1533 @property bool terminateOnHangup(bool newVal){ 1534 return _stopOnInterrupt = newVal; 1535 } 1536 1537 /// starts the UI loop 1538 void run(){ 1539 _initializeCall(); 1540 _resizeEventCall(); 1541 _updateEventCall(); 1542 _isRunning = true; 1543 StopWatch sw = StopWatch(AutoStart.yes); 1544 while (_isRunning){ 1545 int timeout = cast(int) 1546 (timerMsecs - sw.peek.total!"msecs"); 1547 Event event; 1548 while (_termWrap.getEvent(timeout, event) > 0){ 1549 _readEvent(event); 1550 timeout = cast(int) 1551 (timerMsecs - sw.peek.total!"msecs"); 1552 _updateEventCall(); 1553 } 1554 if (sw.peek.total!"msecs" >= timerMsecs){ 1555 _timerEventCall(cast(uint)sw.peek.total!"msecs"); 1556 sw.reset; 1557 sw.start; 1558 _updateEventCall(); 1559 } 1560 } 1561 } 1562 1563 /// search the passed widget recursively and activate it 1564 /// 1565 /// Returns: true if the widget was made active, false if not 1566 bool activateWidget(QWidget target) { 1567 return this.searchAndActivateWidget(target); 1568 } 1569 1570 /// Changes the key used to cycle between active widgets. 1571 void setActiveWidgetCycleKey(dchar key){ 1572 _activeWidgetCycleKey = key; 1573 } 1574 }