1 /** 2 `UIElement` is the base class of all widgets. 3 In the Dplug codebase, `UIElement` and "widgets" terms 4 are used indifferently. 5 6 Copyright: Copyright Auburn Sounds 2015 and later. 7 License: http://www.boost.org/LICENSE_1_0.txt, BSL-1.0 8 Authors: Guillaume Piolat 9 */ 10 module dplug.gui.element; 11 12 import core.stdc.stdio; 13 import core.stdc.string: strlen, strcmp; 14 15 import std.math: round; 16 17 public import dplug.math.vector; 18 public import dplug.math.box; 19 public import dplug.graphics; 20 public import dplug.window.window; 21 public import dplug.core.sync; 22 public import dplug.core.vec; 23 public import dplug.core.nogc; 24 public import dplug.gui.boxlist; 25 public import dplug.gui.context; 26 27 28 /** 29 `UIElement` (aka widgets) have various flags. 30 31 Note: For now the UIFlags are fixed over the lifetime of 32 a widget, but all of them could eventually 33 change at every frame. 34 */ 35 alias UIFlags = uint; 36 enum : UIFlags 37 { 38 /** 39 This `UIElement` draws to the Raw layer. 40 `onDrawRaw` will be called when widget is dirty. 41 When calling `setDirty(UILayer.guessFromFlags)`, 42 the Raw layer will be invalidated. 43 44 The Raw layer is "on top" of the PBR layer, and is 45 faster to update since it needs no PBR computation. 46 */ 47 flagRaw = 1, 48 49 /** 50 This `UIElement` draws to the PBR layer. 51 `onDrawPBR` will be called when widget is dirty. 52 53 When calling `setDirty(UILayer.guessFromFlags)`, the 54 PBR _and_ Raw layers will be invalidated, since the 55 Raw layer is composited over the result of PBR. 56 */ 57 flagPBR = 2, 58 59 /** 60 This `UIElement` is animated. 61 The `onAnimate` callback should be called regularly. 62 63 If you don't have this flag, only input events and 64 parameter changes may change the widget appearance. 65 */ 66 flagAnimated = 4, 67 68 /** 69 This `UIElement` cannot be drawn in parallel with 70 other widgets, when drawn in the Raw layer. 71 */ 72 flagDrawAloneRaw = 8, 73 74 /** 75 This `UIElement` cannot be drawn in parallel with 76 other widgets, when drawn in the PBR layer. 77 */ 78 flagDrawAlonePBR = 16, 79 } 80 81 82 /** 83 Reasonable default value for the Depth channel. 84 85 In the Depth channel: 86 0 means bottom, further from viewer. 87 `ushort.max` means top, closer from viewer. 88 */ 89 enum ushort defaultDepth = 15000; 90 91 92 /** 93 Reasonable default value for the Roughness channel. 94 95 In the Roughness channel: 96 0 means sharpest (more plastic) 97 255 means smoothest surface (more polished) 98 */ 99 enum ubyte defaultRoughness = 128; 100 101 102 /** 103 Reasonable default value for the Specular channel. 104 Since "everything is shiny" it' better to always have at 105 least a little of specular. 106 107 In the Specul channel: 108 0 means no specular reflection. 109 255 means specular reflection. 110 111 It changes the amount of specular highlights. 112 */ 113 enum ubyte defaultSpecular = 128; 114 115 116 /** 117 Reasonable dielectric and metal values for Metalness. 118 119 In the metalness channel: 120 0 means no environment reflection. 121 255 means more environment (skybox), metallic look. 122 */ 123 enum ubyte defaultMetalnessDielectric = 25; 124 enum ubyte defaultMetalnessMetal = 255; ///ditto 125 126 127 /** 128 Used by the `setDirty` calls to figure out which layers 129 should be invalidated (there are 2 layers, Raw and PBR). 130 */ 131 enum UILayer 132 { 133 /** 134 Use the `UIElement` own flags to figure out which 135 layers to invalidate. 136 137 This is what you want most of the time, and it the 138 default. 139 */ 140 guessFromFlags, 141 142 /** 143 Invalidate only the Raw layer but not the PBR layer. 144 This is useful for a rare `UIElement` which would 145 draw to both the Raw and PBR layers, but only the 146 Raw one is animated for performance reasons. 147 */ 148 rawOnly, 149 150 /** 151 Internal Dplug usage. 152 This is only useful for the very first setDirty call, 153 to mark the whole UI dirty. 154 */ 155 allLayers 156 } 157 158 159 /** 160 Result of `onMouseClick`. 161 */ 162 enum Click 163 { 164 /** 165 Click was handled, consume the click event. 166 No dragging was started. 167 Widget gets keyboard focus (`isFocused`). 168 */ 169 handled, 170 171 /** 172 Click was handled, consume the click event. 173 No dragging was started. 174 Do NOT get keyboard focus. 175 Useful to select another widget on click, or exit a 176 widget. 177 */ 178 handledNoFocus, 179 180 /** 181 Click handled, consume the click event. 182 Widget gets keyboard focus (`isFocused`). 183 184 A drag operation was started which means several 185 things: 186 - This widget keeps the keyboard and mouse focus 187 as long as the drag operation is active. 188 Even if the mouse is not inside the widget 189 anymore. 190 - The plugin window keeps the active focus as long 191 as the drag operation is active. Mouse events 192 keep being received even if mouse gets outside 193 the plugin window. 194 - `onStartDrag` and `onStopDrag` get called. 195 - `onMouseDrag` is called instead of `onMouseMove`. 196 197 It is a good practice to NOT start another drag 198 operation if one is already active (see Issue #822), 199 typically it currently happens with another mouse 200 button in the wild. 201 202 Note: there is no `onMouseRelease` in Dplug. The way 203 to catch the releasesi to start a drag, then catch 204 `onStopDrag`. 205 */ 206 startDrag, 207 208 /** 209 Click was not handled, pass the event around. 210 */ 211 unhandled 212 } 213 214 /** 215 The maximum length for an UIElement ID. 216 */ 217 enum maxUIElementIDLength = 63; 218 219 /** 220 Is this a valid `UIElement` identifier? 221 Returns: true if a valid UIlement unique identifier. 222 This is mapped on HTML ids. 223 */ 224 static bool isValidElementID(const(char)[] identifier) 225 pure nothrow @nogc @safe 226 { 227 if (identifier.length == 0) 228 return false; 229 if (identifier.length > maxUIElementIDLength) 230 return false; 231 foreach (char ch; identifier) 232 { 233 // Note: Chrome does actually accept ID with spaces 234 if (ch == 0) 235 return false; 236 } 237 return true; 238 } 239 240 /** 241 A `UIElement` has 8 `void*` user pointers (4 reserved 242 for Dplug + 4 for vendors). 243 The first two are used by `dplug:wren-support`. 244 245 user[UIELEMENT_POINTERID_WREN_EXPORTED_CLASS] 246 points to a cached Wren class of this UIElement. 247 248 user[UIELEMENT_POINTERID_WREN_VM_GENERATION] 249 is the Wren VM counter, is an `uint` not a pointer. 250 */ 251 enum UIELEMENT_POINTERID_WREN_EXPORTED_CLASS = 0; 252 enum UIELEMENT_POINTERID_WREN_VM_GENERATION = 1; ///ditto 253 254 /** 255 `UIElement` is the base class of the `dplug:gui` widget 256 hierarchy. It is called a "widget" in the Dplug lore. 257 258 MAYDO: a bunch of stuff in that class is intended 259 specifically for the root element, there is 260 perhaps a better design to find. 261 */ 262 class UIElement 263 { 264 // Summary of user APIs in `UIElement`: 265 // 266 // 1. Creation/destruction API 267 // - this 268 // - ~this 269 // 270 // 2. Widget positioning API 271 // - position 272 // 273 // 3. Children API 274 // - context 275 // - parent 276 // - topLevelParent 277 // - child 278 // - addChild 279 // - removeChild 280 // 281 // 4. Invalidation API 282 // - setDirtyWhole 283 // - setDirty 284 // 285 // 5. Widget visibility API 286 // - isVisible 287 // - visibility 288 // 289 // 6. Widget Z-order API 290 // - zOrder 291 // 292 // 7. Widget identifiers API 293 // - id 294 // - hasId 295 // - getElementById 296 // 297 // 8. Layout callback 298 // - reflow 299 // 300 // 9. Status API 301 // - isMouseOver 302 // - isDragged 303 // - isFocused 304 // - drawsToPBR 305 // - drawsToRaw 306 // - isAnimated 307 // - isDrawAloneRaw 308 // - isDrawAlonePBR 309 // 310 // 10. Mouse Cursor API 311 // - cursorWhenDragged 312 // - setCursorWhenDragged 313 // - cursorWhenMouseOver 314 // - setCursorWhenMouseOver 315 // 316 // 11. User Pointers API 317 // - getUserPointer 318 // - setUserPointer 319 // 320 // 12. Contains callback 321 // - contains 322 // 323 // 13. Mouse Event callbacks 324 // - onMouseEnter 325 // - onMouseExit 326 // - onMouseClick 327 // - onMouseWheel 328 // - onMouseMove 329 // 330 // 14. Drag Events callbacks 331 // - onBeginDrag 332 // - onStopDrag 333 // - onMouseDrag 334 // 335 // 15. Keyboard Events callbacks 336 // - onFocusEnter 337 // - onFocusExit 338 // - onKeyDown 339 // - onKeyUp 340 // 341 // 16. Drawing callbacks. 342 // - onDrawRaw 343 // - onDrawPBR 344 // 345 // 17. Animation callback 346 // - onAnimate 347 348 public: 349 nothrow: 350 @nogc: 351 352 // 353 // 1. Creation/destruction API. 354 // 355 356 /** 357 Create a `UIElement`. 358 359 When creating a custom widget, this should be called 360 with `super` as a first measure in order to have a 361 `UIContext`. 362 363 When created a widget has no position, it should be 364 positionned in your gui.d `this()` or `reflow()`. 365 366 Params: 367 context The global UI context of this UI. 368 flags Flags as defined in `UIFlags`. 369 */ 370 this(UIContext context, uint flags) 371 { 372 _context = context; 373 _flags = flags; 374 _localRectsBuf = makeVec!box2i(); 375 _children = makeVec!UIElement(); 376 _zOrderedChildren = makeVec!UIElement(); 377 _idStorage[0] = '\0'; // defaults to no ID 378 379 // Initially empty position 380 assert(_position.empty()); 381 } 382 383 /** 384 Destroy a `UIElement`. 385 Normally this happens naturally, since each widget 386 owns its children. 387 */ 388 ~this() 389 { 390 foreach(child; _children[]) 391 child.destroyFree(); 392 } 393 394 395 // 396 // 2. Widget positioning API. 397 // 398 399 /** 400 Get widget position in the window (absolute). 401 402 You can query this in `.reflow()` to position 403 children of this widget. 404 */ 405 final box2i position() 406 { 407 return _position; 408 } 409 410 /** 411 Set widget position in the window (absolute). 412 413 Params: 414 p New widget position rectangle, sorted, 415 rounded to nearest integer coordinates if 416 needed. 417 418 If the position changed: 419 - call `.setDirtyWhole()` to redraw the affected 420 underlying buffers. 421 - call `.reflow` to position children. 422 423 Warning: 424 Most widget won't crash with: 425 - empty position 426 - position partially or 100% outside the window 427 428 However, position outside the window will likely 429 lead to bad rendering. (See: onDrawRaw TODO) 430 */ 431 final void position(box2i p) 432 { 433 assert(p.isSorted()); 434 435 bool moved = (p != _position); 436 437 // Make dirty rect in former and new positions. 438 if (moved) 439 { 440 setDirtyWhole(); 441 _position = p; 442 setDirtyWhole(); 443 444 // New in Dplug v11: setting position now calls 445 // reflow() if position has changed. 446 reflow(); 447 448 // If you fail here, it's because your 449 // custom widget changed its own position in 450 // `.reflow`. 451 // But, `_position` shouldn't be touched by 452 // `.reflow()` calls. 453 assert(p == _position); 454 } 455 } 456 ///ditto 457 final void position(box2f p) 458 { 459 int x1 = cast(int) round(p.min.x); 460 int y1 = cast(int) round(p.min.y); 461 int x2 = cast(int) round(p.max.x); 462 int y2 = cast(int) round(p.max.y); 463 box2i r = box2i(x1, y1, x2, y2); 464 position = r; 465 } 466 467 468 // 469 // 3. Children API 470 // 471 472 /** 473 Get the UI context, which is an additional API for 474 widgets to use (though quite a bit of methods are 475 internals there). 476 */ 477 final UIContext context() 478 { 479 return _context; 480 } 481 482 /** 483 Get parent widget, if any. 484 Returns: Parent element. 485 `null` if detached or root element. 486 */ 487 final UIElement parent() pure nothrow @nogc 488 { 489 return _parent; 490 } 491 492 /** 493 Get top-level parent, if any. 494 Returns: Top-level parent. 495 `this` if detached or root element. 496 */ 497 final UIElement topLevelParent() pure nothrow @nogc 498 { 499 if (_parent is null) 500 return this; 501 else 502 return _parent.topLevelParent(); 503 } 504 505 /** 506 Returns: The nth child of this `UIElement`. 507 */ 508 final UIElement child(int n) 509 { 510 return _children[n]; 511 } 512 513 /** 514 Add a `UIElement` as child to another. 515 516 Such a child MUST be created through `mallocNew`. 517 Its ownership is given to is parent. 518 */ 519 final void addChild(UIElement element) 520 { 521 element._parent = this; 522 _children.pushBack(element); 523 524 // Recompute visibility of that element. 525 bool parentVisible = isVisible(); 526 element.recomputeVisibilityStatus(parentVisible); 527 } 528 529 /** 530 Removes a child from its parent. 531 Useful for creating dynamic UI's. 532 533 Warning: For now, it's way better to create all the 534 widgets in advance and use `.visibility` to make them 535 coexist. 536 537 FUTURE: there are restrictions and lots of races for 538 where this is actually allowed. Find them. 539 */ 540 final void removeChild(UIElement element) 541 { 542 int index = _children.indexOf(element); 543 if(index >= 0) 544 { 545 // Dirty where the UIElement has been removed 546 element.setDirtyWhole(); 547 548 _children.removeAndReplaceByLastElement(index); 549 } 550 } 551 552 553 554 // 555 // 4. Invalidation API. 556 // Explanation: provokes all widget redraws. 557 // 558 559 /** 560 Mark this element as "dirty" on its whole position. 561 562 This leads to a repaint of this widget and all other 563 widget parts in the neighbourhood. 564 565 Params: 566 layer Which layers need to be redrawn. 567 568 Important: While you _can_ call this from the audio 569 thread, it is much more efficient to mark 570 the widget dirty with an atomic and call 571 `.setDirty()/.setDirtyWhole()` in an 572 `.onAnimate()` callback. 573 */ 574 void setDirtyWhole(UILayer layer = 575 UILayer.guessFromFlags) 576 { 577 addDirtyRect(_position, layer); 578 } 579 580 /** 581 Mark a sub-part of the element "dirty". 582 583 This leads to a repaint of this widget's part, and 584 all other widget parts in the neighbourhood. 585 586 Params: 587 rect Position of the dirtied rectangle, 588 given in **local coordinates**. 589 layer Which layers need to be redrawn. 590 591 Warning: `rect` must be inside `.position()`, but 592 is given in widget (local) coordinates. 593 594 Important: While you _can_ call this from the audio 595 thread, it is much more efficient to mark 596 the widget dirty with an atomic and call 597 `.setDirty()/.setDirtyWhole()` in an 598 `.onAnimate()` callback. 599 */ 600 void setDirty(box2i rect, 601 UILayer layer = UILayer.guessFromFlags) 602 { 603 // BUG: it is actually problematic to allow this 604 // from the audio thread, because the access to 605 // _position isn't protected and it could create a 606 // race in case of concurrent reflow(). Pushed 607 // rectangles might be out of range, this is 608 // workarounded in GUIGraphics currently 609 /// for other reasons. 610 box2i translatedRect = rect.translate(_position.min); 611 assert(_position.contains(translatedRect)); 612 addDirtyRect(translatedRect, layer); 613 } 614 615 616 617 // 618 // 5. Widget visibility API 619 // Explanation: an invisible widget is not displayed 620 // nor considered for most events. 621 // Widgets start their life being visible. 622 // 623 624 /** 625 A widget is "visible" when it has a true visibility 626 flag, and its parent is itself visible. 627 628 Returns: Last computed visibility status. 629 */ 630 final bool isVisible() pure const 631 { 632 return _visibilityStatus; 633 } 634 635 /** 636 Get visibility flag of the widget. 637 638 This is only the visibility of this widget. 639 A widget might still be invisible, if one of its 640 parent is not visible. To know that, use the 641 `isVisible()` call. 642 */ 643 final bool visibility() pure const 644 { 645 return _visibleFlag; 646 } 647 648 /** 649 Change visibility flag of the widget. Show or hide 650 all children of this `UIElement`, regardless of 651 their position on screen, invalidating their 652 graphics if need be (much like a position change). 653 */ 654 final void visibility(bool visible) 655 { 656 if (_visibleFlag == visible) 657 { 658 // Nothing to do, this wouldn't change any 659 // visibility status in sub-tree. 660 return; 661 } 662 663 _visibleFlag = visible; 664 665 // Get parent visibility status. 666 bool parentVisibleStatus = true; 667 if (parent) 668 parentVisibleStatus = parent.isVisible(); 669 670 // Recompute own visibility status. 671 recomputeVisibilityStatus(parentVisibleStatus); 672 } 673 674 675 // 676 // 6. Widget Z-order API 677 // 678 679 680 /** 681 Set/get widget Z-order (default = 0). 682 683 However, keep in mind the Raw layer is always on top 684 of anything PBR. 685 686 Order of draw (lower is earlier): 687 [ Raw widget with zOrder=10] 688 [ Raw widget with zOrder=-4] 689 [ PBR widget with zOrder=2] 690 [ PBR widget with zOrder=0] 691 692 The higher the Z-order, the later it is composited. 693 In case of identical Z-order, the widget that comes 694 after in the children tree is drawn after. 695 696 */ 697 final int zOrder() pure const 698 { 699 return _zOrder; 700 } 701 ///ditto 702 final void zOrder(int zOrder) 703 { 704 if (_zOrder != zOrder) 705 { 706 setDirtyWhole(); 707 _zOrder = zOrder; 708 } 709 } 710 711 // TODO: how to depreciate that? Wren will stumble upon 712 // every deprecated fields unfortunately. 713 alias setZOrder = zOrder; 714 715 716 717 // 718 // 7. Widget identifiers API 719 // 720 721 /** 722 Set widget ID. 723 724 All UIElement may have a string as unique identifier 725 like in HTML. 726 727 This identifier is supposed to be unique. If it 728 isn't, a search by ID will be Undefined Behaviour. 729 730 Params: 731 identifier A valid HTML-like identifier. 732 Can't contain spaces or null characters. 733 Must be below a maxium of 63 characters. 734 */ 735 final void setId(const(char)[] identifier) pure 736 { 737 if (!isValidElementID(identifier)) 738 { 739 // Note: assigning an invalid ID is a silent 740 // error. The UIElement ends up with no ID. 741 _idStorage[0] = '\0'; 742 return; 743 } 744 _idStorage[0..identifier.length] = identifier[0..$]; 745 _idStorage[identifier.length] = '\0'; 746 } 747 ///ditto 748 final void id(const(char)[] identifier) 749 { 750 setId(identifier); 751 } 752 753 /** 754 Get widget ID. 755 756 All UIElement may have a string as unique identifier 757 like in HTML 758 759 Returns: The widget identifier, or `""` if no ID. 760 The result is an interior slice, that is 761 invalidated if the ID is reassigned. 762 */ 763 final const(char)[] getId() pure 764 { 765 if (_idStorage[0] == '\0') 766 return ""; 767 else 768 { 769 size_t len = strlen(_idStorage.ptr); 770 return _idStorage[0..len]; 771 } 772 } 773 ///ditto 774 final const(char)[] id() pure 775 { 776 return getId(); 777 } 778 779 /** 780 Has this widget an identifier? 781 782 Returns: `true` if this `UIElement` has an ID. 783 */ 784 final bool hasId() pure 785 { 786 return _idStorage[0] != '\0'; 787 } 788 789 /** 790 Search subtree for an UIElement with ID `id`. 791 Undefined Behaviour if ID are not unique. 792 */ 793 final UIElement getElementById(const(char)* id) 794 { 795 if (strcmp(id, _idStorage.ptr) == 0) 796 return this; 797 798 foreach(c; _children) 799 { 800 UIElement r = c.getElementById(id); 801 if (r) return r; 802 } 803 return null; 804 } 805 806 // 807 // 8. Layout callback. 808 // 809 810 /** 811 The `.reflow()` callback is called whenver the 812 `.position` of a widget changes. 813 814 You MUST NOT call `reflow()` yourself, to do that 815 use `.position = someRect`. 816 817 However but you can override it and if you want a 818 resizeable UI you will have at least one `.reflow()` 819 override in your `gui.d`. 820 821 The role of this method is to update positions of 822 children, hence you can implement any kind of 823 descending layout. 824 825 Inside this call, getting own position with 826 `.position()` is encouraged. 827 828 Pseudo-code: 829 830 override void reflow() 831 { 832 box2i p = this.position(); 833 child[n].position = something(p) 834 } 835 836 Note: Widget positions are absolute. As such, 837 children don't need to be inside position of 838 their parent at all. 839 840 See_also: `position` 841 */ 842 void reflow() 843 { 844 // By default: do nothing, do not position children 845 } 846 847 848 // 849 // 9. Status API. 850 // Typically used to change the display of a widget. 851 // 852 853 /** 854 Widget hovered by mouse? 855 856 Between `onMouseEnter` and `onMouseExit`, 857 `isMouseOver` will return `true`. 858 */ 859 final bool isMouseOver() pure const 860 { 861 version(legacyMouseDrag) 862 {} 863 else 864 { 865 if (_context.mouseOver !is this) 866 { 867 // in newer mouse drag behaviour 868 // the dragged item is always also mouseOver 869 assert(_context.dragged !is this); 870 } 871 } 872 873 return _context.mouseOver is this; 874 } 875 876 /** 877 Widget dragged by mouse? 878 879 Between `.onBeginDrag()` and `.onStopDrag()`, 880 `isDragged` returns `true`. 881 882 */ 883 final bool isDragged() pure const 884 { 885 version(legacyMouseDrag) 886 {} 887 else 888 { 889 if (_context.dragged is this) 890 assert(isMouseOver()); 891 } 892 893 return _context.dragged is this; 894 } 895 896 /** 897 Widget has keyboard focused? (last clicked) 898 */ 899 final bool isFocused() pure const 900 { 901 return _context.focused is this; 902 } 903 904 /** 905 Widget draws on the PBR layer? 906 */ 907 final bool drawsToPBR() pure const 908 { 909 return (_flags & flagPBR) != 0; 910 } 911 912 /** 913 Widget draws on the Raw layer? 914 */ 915 final bool drawsToRaw() pure const 916 { 917 return (_flags & flagRaw) != 0; 918 } 919 920 /** 921 Is widget animated? (onAnimate called) 922 */ 923 final bool isAnimated() pure const 924 { 925 return (_flags & flagAnimated) != 0; 926 } 927 928 /** 929 Should widget be drawn alone in Raw layer? 930 */ 931 final bool isDrawAloneRaw() pure const 932 { 933 return (_flags & flagDrawAloneRaw) != 0; 934 } 935 936 /** 937 Should widget be drawn alone in PBR layer? 938 */ 939 final bool isDrawAlonePBR() pure const 940 { 941 return (_flags & flagDrawAlonePBR) != 0; 942 } 943 944 945 // 946 // 10. Mouse Cursor API 947 // FUTURE: need redo/clarify this API. 948 // 949 950 /// 951 MouseCursor cursorWhenDragged() 952 { 953 return _cursorWhenDragged; 954 } 955 956 /// 957 void setCursorWhenDragged(MouseCursor mouseCursor) 958 { 959 _cursorWhenDragged = mouseCursor; 960 } 961 962 /// 963 MouseCursor cursorWhenMouseOver() 964 { 965 return _cursorWhenMouseOver; 966 } 967 968 /// 969 void setCursorWhenMouseOver(MouseCursor mouseCursor) 970 { 971 _cursorWhenMouseOver = mouseCursor; 972 } 973 974 975 // 976 // 11. User Pointers API 977 // 978 979 /** 980 Set/Get a user pointer. 981 This allow `dplug:gui` extensions. 982 */ 983 final void* getUserPointer(int pointerID) 984 { 985 return _userPointers[pointerID]; 986 } 987 ///ditto 988 final void setUserPointer(int pointerID, void* user) 989 { 990 _userPointers[pointerID] = user; 991 } 992 993 // 994 // 12. Contains callback. 995 // 996 997 /** 998 Check if given point is considered in the widget, 999 for clicks, mouse moves, etc. 1000 This function is meant to be overridden. 1001 1002 It is meant for widgets that aren't rectangles, and 1003 is relatively rare in practice. 1004 1005 It can be useful to disambiguate clicks and 1006 mouse-over between widgets that would otherwise 1007 overlap (you can also use Z-order to solve that). 1008 1009 Params: 1010 x X position in local coordinates. 1011 x Y position in local coordinates. 1012 1013 Returns: `true` if point `(x, y)` is inside widget. 1014 1015 Note: 1016 This is unusual, but it seems a widget could be 1017 clickable beyond its `.position()`. It won't be 1018 able to draw there, though. 1019 So it's advised to not exceed `_position`. 1020 */ 1021 bool contains(int x, int y) 1022 { 1023 // FUTURE: should be onContains to disambiguate 1024 1025 // By default: true if (x, y) inside _position. 1026 return (x < cast(uint)(_position.width ) ) 1027 && (y < cast(uint)(_position.height) ); 1028 } 1029 1030 1031 // 1032 // 13. Mouse Events callbacks. 1033 // All of the following function can be (optionally) 1034 // overridden. 1035 // 1036 1037 /** 1038 Called when mouse enter or exits a widget. 1039 This function is meant to be overridden. 1040 1041 Typically used to call `.setDirtyWhole` in order to 1042 display/hide a mouse highlight if clickable. 1043 */ 1044 void onMouseEnter() 1045 { 1046 } 1047 ///ditto 1048 void onMouseExit() 1049 { 1050 } 1051 1052 /** 1053 `.onMouseClick()` is called for every new click. 1054 1055 This function is meant to be overridden. 1056 1057 Params: 1058 x Mouse X position in local coordinates. 1059 y Mouse Y position in local coordinates. 1060 isDoubleClick `true` if double-click. 1061 mstate General mouse state. 1062 1063 Returns: What do with the click event. This is the 1064 only place where you can start a drag operation. 1065 1066 Warning: Called Whether or not you are in a dragging 1067 operation! For this reason, check your widgets 1068 with several mouse buttons pressed at once. 1069 1070 See_also: `Click` 1071 */ 1072 Click onMouseClick(int x, int y, int button, 1073 bool isDoubleClick, 1074 MouseState mstate) 1075 { 1076 // By default, do nothing. 1077 return Click.unhandled; 1078 } 1079 1080 /** 1081 Mouse wheel was turned. 1082 This function is meant to be overridden. 1083 1084 Params: 1085 x Mouse X position in local coordinates. 1086 y Mouse Y position in local coordinates. 1087 wheelDeltaX Amount of mouse X wheel (rare). 1088 wheelDeltaY Amount of mouse Y wheel. 1089 mstate General mouse state. 1090 1091 Returns: `true` if the wheel event was handlded, 1092 else propagated. 1093 */ 1094 bool onMouseWheel(int x, int y, 1095 int wheelDeltaX, int wheelDeltaY, 1096 MouseState mstate) 1097 { 1098 // By default, do nothing. 1099 return false; 1100 } 1101 1102 /** 1103 Called when the mouse moves over this widget area. 1104 This function is meant to be overridden. 1105 1106 Params: 1107 x Mouse X position in local coordinates. 1108 y Mouse Y position in local coordinates. 1109 dx Mouse X relative displacement. 1110 dy Mouse Y relative displacement. 1111 mstate General mouse state. 1112 1113 Warning: If `legacyMouseDrag` version identifier is 1114 used, this will be called even during a drag. 1115 Else this cannot happen in a drag. 1116 */ 1117 void onMouseMove(int x, int y, 1118 int dx, int dy, 1119 MouseState mstate) 1120 { 1121 // By default: do nothing 1122 } 1123 1124 1125 // 1126 // 14. Drag Events callbacks. 1127 // All of the following function can be (optionally) 1128 // overridden. 1129 // 1130 // A drag operation is started by a click, and last as 1131 // long as no mouse buttons is released. 1132 // There cannot be concurrent drags, but right now 1133 // `onMouseClick` will still be called during a drag. 1134 // 1135 1136 /** 1137 Called when a drag operation starts or ends. 1138 This function is meant to be overridden. 1139 1140 Typically: 1141 - Call `beginParamEdit` from `.onBeginDrag()` or 1142 better, `onMouseClick` (more context in 1143 `.onMouseClick()`). 1144 1145 - Call `endParamEdit()` from `.onStopDrag()`. 1146 - Call `.setDirtyWhole()` to account from drag 1147 state being seen. 1148 - `.onStopDrag()` is only way to catch mouse 1149 button release events. 1150 - etc... 1151 */ 1152 void onBeginDrag() 1153 { 1154 // Do nothing. 1155 // Often, onMouseClick is the right place for what 1156 // you may do here. 1157 } 1158 ///ditto 1159 void onStopDrag() 1160 { 1161 // Do nothing. 1162 // Often the place to do `.endParamEdit`. 1163 } 1164 1165 /** 1166 Called when the mouse moves while dragging this 1167 widget. 1168 1169 Typically used for knobs and sliders widgets. 1170 Mouse movement will be the place to call 1171 `param.setFromGUI()`. 1172 1173 Params: 1174 x Mouse X position in local coordinates. 1175 y Mouse Y position in local coordinates. 1176 dx Mouse X relative displacement. 1177 dy Mouse Y relative displacement. 1178 mstate General mouse state. 1179 */ 1180 void onMouseDrag(int x, int y, int dx, int dy, 1181 MouseState mstate) 1182 { 1183 } 1184 1185 1186 // 1187 // 15. Keyboard Events Callback 1188 // 1189 1190 /** 1191 Called when this widget is clicked and get the 1192 "focus" (ie. meaning the keyboard focus). 1193 This function is meant to be overridden. 1194 */ 1195 void onFocusEnter() 1196 { 1197 } 1198 1199 /** 1200 This widget lost the keyboard focus. 1201 This function is meant to be overridden. 1202 1203 Typically used to close a pop-up widget. 1204 1205 Called when keyboard focus is lost (typically 1206 because another widget was clicked, or mouse clicked 1207 outside the window). 1208 */ 1209 void onFocusExit() 1210 { 1211 } 1212 1213 /** 1214 Called when a key is pressed/released. 1215 Functiosn meant to be overridden. 1216 1217 Key events bubbles towards the top until being 1218 processed, eventually the DAW will get it. 1219 1220 Returns: `true` if event handled. 1221 */ 1222 bool onKeyDown(Key key) 1223 { 1224 return false; 1225 } 1226 ///ditto 1227 bool onKeyUp(Key key) 1228 { 1229 return false; 1230 } 1231 1232 1233 protected: 1234 1235 // 1236 // 16. Drawing callbacks. 1237 // 1238 1239 /** 1240 Raw layer draw method. 1241 This function is meant to be overridden. 1242 1243 `UIElement` are drawn on the Raw layer by increasing 1244 z-order, or lexical order if lack thereof. 1245 1246 The widgets who have non-overlapping positions are 1247 drawn in parallel if their flags allow it. 1248 1249 One MUST NOT draw outsides the given `dirtyRects`. 1250 This allows fast and fine-grained updates. 1251 A `UIElement` that doesn't respect dirtyRects WILL 1252 have bad rendering with surrounding updates. 1253 1254 Params: 1255 rawMap Raw RGBA pixels (input and output), 1256 cropped to widget position. 1257 Blending allowed. 1258 dirtyRects Where to draw in this rawMap. 1259 1260 TODO: Not sure if dirtyRects are widget-space or 1261 cropped-space. 1262 */ 1263 void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects) 1264 { 1265 // By default: invisible 1266 } 1267 1268 /** 1269 PBR layer draw method. 1270 This function is meant to be overridden. 1271 1272 `UIElement` are drawn on the Raw layer by increasing 1273 z-order, or lexical order if lack thereof. 1274 However, all PBR stuff is composited and computed 1275 before Raw is drawn on top of that (cached) result. 1276 1277 The widgets who have non-overlapping positions are 1278 drawn in parallel if their flags allow it. 1279 1280 One MUST NOT draw outsides the given `dirtyRects`. 1281 This allows fast and fine-grained updates. 1282 A `UIElement` that doesn't respect dirtyRects WILL 1283 have bad rendering with surrounding updates. 1284 1285 Params: 1286 diffuseMap Contain 4 channels: 1287 Red, Green, Blue, Emissive. 1288 depthMap One channel of 16-bit depth. 1289 materialMap Contain 3 channels: 1290 Roughness, Metalness, Specular, and 1291 a unused 4th channel. 1292 dirtyRects Where to draw in these maps. 1293 1294 TODO: Not sure if dirtyRects are widget-space or 1295 cropped-space. 1296 */ 1297 void onDrawPBR(ImageRef!RGBA diffuse, 1298 ImageRef!L16 depth, 1299 ImageRef!RGBA material, 1300 box2i[] dirtyRects) 1301 { 1302 // By default: checkerboard pattern 1303 RGBA darkGrey = RGBA(100, 100, 100, 0); 1304 RGBA lighterGrey = RGBA(150, 150, 150, 0); 1305 1306 // This is a typical draw function: 1307 // for each r in dirtyRects 1308 // crop inputs by r 1309 // show something in it 1310 // You don't have to fill everything though. 1311 foreach(r; dirtyRects) 1312 { 1313 for (int y = r.min.y; y < r.max.y; ++y) 1314 { 1315 L16[] depthScan = depth.scanline(y); 1316 RGBA[] diffuseScan = diffuse.scanline(y); 1317 RGBA[] materialScan = material.scanline(y); 1318 for (int x = r.min.x; x < r.max.x; ++x) 1319 { 1320 RGBA col = ((x>>3)^(y>>3))&1 ? darkGrey 1321 : lighterGrey; 1322 diffuseScan.ptr[x] = col; 1323 depthScan.ptr[x] = L16(defaultDepth); 1324 RGBA m = RGBA(defaultRoughness, 1325 defaultMetalnessDielectric, 1326 defaultSpecular, 1327 255); 1328 materialScan.ptr[x] = m; 1329 } 1330 } 1331 } 1332 } 1333 1334 // 1335 // 17. Animation callback. 1336 // 1337 1338 /** 1339 Called periodically for every `UIElement` that has 1340 `flagAnimated`. 1341 Override this to create animations. 1342 1343 Using `.setDirty()` there allows to redraw a widget 1344 continuously (like a meter or an animated object). 1345 This is typically used to poll DSP state from the 1346 UI. 1347 1348 Warning: Summing `dt` will not lead to a time that 1349 increase like `time`. 1350 `time` may go backwards if the window was 1351 reopen after a while being closed (???) 1352 `time` is guaranteed to increase as fast as 1353 system time but is not synced to audio 1354 time. 1355 1356 Note: `.onAnimate` is called even if the widget is 1357 not visible! (use `.isVisible()` to know). 1358 */ 1359 void onAnimate(double dt, double time) 1360 { 1361 } 1362 1363 1364 1365 public: 1366 1367 // 1368 // Private Dplug API, used by graphics.d mostly. 1369 // (internal) 1370 // Normally you can ignore all of this as a user. 1371 // 1372 1373 /* 1374 This method is called for each item in the drawlist 1375 that was visible and has a dirty Raw layer. 1376 This is called after compositing, starting from the 1377 buffer output by the Compositor. 1378 */ 1379 final void renderRaw(ImageRef!RGBA rawMap, 1380 in box2i[] areasToUpdate) 1381 { 1382 // We only consider the part of _position that is 1383 // actually in the surface. 1384 // Indeed, `_position` should not be outside the 1385 // bounds of a window (most widgets will support 1386 // that), but most widgets will render that badly. 1387 box2i surPos = box2i(0, 0, rawMap.w, rawMap.h); 1388 box2i vPos = _position.intersection(surPos); 1389 1390 // Widget inside the window? 1391 if (vPos.empty()) 1392 return; 1393 1394 // Create a list of dirty rectangles for this widget 1395 // in local coordinates, for the Raw layer. 1396 _localRectsBuf.clearContents(); 1397 foreach(rect; areasToUpdate) 1398 { 1399 box2i inter = rect.intersection(vPos); 1400 if (!inter.empty) 1401 { 1402 box2i tr = inter.translate(-vPos.min); 1403 _localRectsBuf.pushBack(tr); 1404 } 1405 } 1406 1407 // Any dirty part in widget? 1408 if (_localRectsBuf.length == 0) 1409 return; 1410 1411 // Crop output image to valid part of _position. 1412 // Drawing outside of _position is thus not doable. 1413 ImageRef!RGBA rawCrop = rawMap.cropImageRef(vPos); 1414 assert(rawCrop.w != 0 && rawCrop.h != 0); 1415 1416 // Call repaint function 1417 onDrawRaw(rawCrop, _localRectsBuf[]); 1418 } 1419 1420 /* 1421 This method is called for each item in the drawlist 1422 that is visible and has a dirty PBR layer. 1423 */ 1424 final void renderPBR(ImageRef!RGBA diffuse, 1425 ImageRef!L16 depth, 1426 ImageRef!RGBA material, 1427 in box2i[] areasToUpdate) 1428 { 1429 int W = diffuse.w; 1430 int H = diffuse.h; 1431 1432 // We only consider the part of _position that is 1433 // actually in the surface. 1434 // Indeed, `_position` should not be outside the 1435 // bounds of a window (most widgets will support 1436 // that), but most widgets will render that badly. 1437 box2i surPos = box2i(0, 0, W, H); 1438 box2i vPos = _position.intersection(surPos); 1439 1440 // Widget inside the window? 1441 if (vPos.empty()) 1442 return; 1443 1444 // Create a list of dirty rectangles for this widget 1445 // in local coordinates, for the PBR layer. 1446 _localRectsBuf.clearContents(); 1447 foreach(rect; areasToUpdate) 1448 { 1449 box2i inter = rect.intersection(vPos); 1450 if (!inter.empty) 1451 { 1452 box2i tr = inter.translate(-vPos.min); 1453 _localRectsBuf.pushBack(tr); 1454 } 1455 } 1456 1457 // Any dirty part in widget? 1458 if (_localRectsBuf.length == 0) 1459 return; 1460 1461 // Crop output image to valid part of _position. 1462 // Drawing outside of _position is thus not doable. 1463 ImageRef!RGBA cDiffuse = diffuse.cropImageRef(vPos); 1464 ImageRef!L16 cDepth = depth.cropImageRef(vPos); 1465 ImageRef!RGBA cMaterial=material.cropImageRef(vPos); 1466 1467 assert(cDiffuse.w != 0 && cDiffuse.h != 0); 1468 1469 // Call repaint function 1470 onDrawPBR(cDiffuse, cDepth, cMaterial, 1471 _localRectsBuf[]); 1472 } 1473 1474 // to be called at top-level when the mouse clicked 1475 final bool mouseClick(int x, int y, int button, 1476 bool isDoubleClick, MouseState mstate) 1477 { 1478 recomputeZOrderedChildren(); 1479 1480 // Test children that are displayed above this 1481 // element first 1482 foreach(child; _zOrderedChildren[]) 1483 { 1484 if (child.zOrder >= zOrder) 1485 if (child.mouseClick(x, y, button, 1486 isDoubleClick, mstate)) 1487 return true; 1488 } 1489 1490 // Test for collision with this element 1491 int px = _position.min.x; 1492 int py = _position.min.y; 1493 if (_visibilityStatus && contains(x - px, y - py)) 1494 { 1495 Click click = onMouseClick(x - px, 1496 y - py, button, isDoubleClick, mstate); 1497 1498 final switch(click) 1499 { 1500 case Click.handled: 1501 _context.setFocused(this); 1502 return true; 1503 1504 case Click.handledNoFocus: 1505 return true; 1506 1507 case Click.startDrag: 1508 _context.beginDragging(this); 1509 goto case Click.handled; 1510 1511 case Click.unhandled: 1512 return false; 1513 } 1514 } 1515 1516 foreach(child; _zOrderedChildren[]) 1517 { 1518 if (child.zOrder < zOrder) 1519 if (child.mouseClick(x, y, button, 1520 isDoubleClick, mstate)) 1521 return true; 1522 } 1523 1524 return false; 1525 } 1526 1527 // to be called at top-level when the mouse is released 1528 final void mouseRelease(int x, int y, 1529 int button, MouseState mstate) 1530 { 1531 version(legacyMouseDrag) 1532 {} 1533 else 1534 { 1535 bool wasDragging = (_context.dragged !is null); 1536 } 1537 1538 _context.stopDragging(); 1539 1540 version(legacyMouseDrag) 1541 {} 1542 else 1543 { 1544 // Enter widget below mouse if a dragged 1545 // operation was stopped. 1546 if (wasDragging) 1547 { 1548 bool ok = mouseMove(x, y, 0, 0, mstate, 1549 false); 1550 if (!ok) 1551 _context.setMouseOver(null); 1552 } 1553 } 1554 } 1555 1556 // to be called at top-level when the mouse wheeled 1557 final bool mouseWheel(int x, int y, 1558 int wheelDeltaX, int wheelDeltaY, 1559 MouseState mstate) 1560 { 1561 recomputeZOrderedChildren(); 1562 1563 // Test children that are displayed above this 1564 // element first 1565 foreach(child; _zOrderedChildren[]) 1566 { 1567 if (child.zOrder >= zOrder) 1568 if (child.mouseWheel(x, y, wheelDeltaX, 1569 wheelDeltaY, mstate)) 1570 return true; 1571 } 1572 1573 int dx = x - _position.min.x; 1574 int dy = y - _position.min.y; 1575 1576 // cannot be mouse-wheeled if invisible 1577 bool canBeMouseWheeled = _visibilityStatus; 1578 if (canBeMouseWheeled && contains(dx, dy)) 1579 { 1580 if (onMouseWheel(dx, dy, wheelDeltaX, 1581 wheelDeltaY, mstate)) 1582 return true; 1583 } 1584 1585 // Test children that are displayed below this 1586 // element last 1587 foreach(child; _zOrderedChildren[]) 1588 { 1589 if (child.zOrder < zOrder) 1590 if (child.mouseWheel(x, y, wheelDeltaX, 1591 wheelDeltaY, mstate)) 1592 return true; 1593 } 1594 1595 return false; 1596 } 1597 1598 // To be called when the mouse moved 1599 final bool mouseMove(int x, int y, int dx, int dy, 1600 MouseState mstate, bool alreadyFoundMouseOver) 1601 { 1602 recomputeZOrderedChildren(); 1603 1604 // "found" is whether we have found the hovered 1605 // thing (mouseOver) 1606 bool found = alreadyFoundMouseOver; 1607 1608 // Test children that are displayed above this 1609 // element first 1610 foreach(child; _zOrderedChildren[]) 1611 { 1612 if (child.zOrder >= zOrder) 1613 { 1614 bool here = child.mouseMove(x, y, dx, dy, 1615 mstate, found); 1616 found = found || here; 1617 } 1618 } 1619 1620 if (isDragged()) 1621 { 1622 // EDIT MODE 1623 // With version `Dplug_RightClickMoveWidgets`, 1624 // dragging with the right mouse button move 1625 // elements around. 1626 // Dragging with shift + right button resize 1627 // elements around. 1628 // 1629 // Additionally, if CTRL is pressed, the 1630 // increments are only -1 or +1 pixel. 1631 // 1632 // You can see the _position rectangle thanks to 1633 // `debugLog`. 1634 bool draggingUsed = false; 1635 version(Dplug_RightClickMoveWidgets) 1636 { 1637 if (mstate.rightButtonDown 1638 && mstate.shiftPressed) 1639 { 1640 if (mstate.ctrlPressed) 1641 { 1642 if (dx < -1) dx = -1; 1643 if (dx > 1) dx = 1; 1644 if (dy < -1) dy = -1; 1645 if (dy > 1) dy = 1; 1646 } 1647 int nx = _position.min.x; 1648 int ny = _position.min.y; 1649 int w = _position.width + dx; 1650 int h = _position.height + dy; 1651 if (w < 5) w = 5; 1652 if (h < 5) h = 5; 1653 position = box2i(nx, ny, nx+w, ny+h); 1654 draggingUsed = true; 1655 } 1656 else if (mstate.rightButtonDown) 1657 { 1658 if (mstate.ctrlPressed) 1659 { 1660 if (dx < -1) dx = -1; 1661 if (dx > 1) dx = 1; 1662 if (dy < -1) dy = -1; 1663 if (dy > 1) dy = 1; 1664 } 1665 int nx = _position.min.x + dx; 1666 int ny = _position.min.y + dy; 1667 if (nx < 0) nx = 0; 1668 if (ny < 0) ny = 0; 1669 position = box2i(nx, ny, 1670 nx + position.width, 1671 ny + position.height); 1672 draggingUsed = true; 1673 } 1674 1675 if (draggingUsed) 1676 { 1677 char[128] buf; 1678 snprintf(buf.ptr, 128, 1679 "rectangle(%d, %d, %d, %d)\n", 1680 _position.min.x, _position.min.y, 1681 _position.width, _position.height); 1682 debugLog(buf.ptr); 1683 } 1684 } 1685 1686 if (!draggingUsed) 1687 onMouseDrag(x - _position.min.x, 1688 y - _position.min.y, 1689 dx, dy, mstate); 1690 } 1691 1692 // Can't be mouse over if not visible. 1693 bool canBeMouseOver = _visibilityStatus; 1694 1695 version(legacyMouseDrag) 1696 {} 1697 else 1698 { 1699 // If dragged, already received `onMouseDrag`. 1700 // if something else dragged, cannot be hovered. 1701 if (_context.dragged !is null) 1702 canBeMouseOver = false; 1703 } 1704 1705 // FUTURE: something more fine-grained? 1706 if (canBeMouseOver && contains(x - _position.min.x, 1707 y - _position.min.y)) 1708 { 1709 // Get the mouse-over crown if not taken 1710 if (!found) 1711 { 1712 found = true; 1713 _context.setMouseOver(this); 1714 1715 version(legacyMouseDrag) 1716 {} 1717 else 1718 { 1719 onMouseMove(x - _position.min.x, 1720 y - _position.min.y, 1721 dx, dy, mstate); 1722 } 1723 } 1724 1725 version(legacyMouseDrag) 1726 { 1727 onMouseMove(x - _position.min.x, 1728 y - _position.min.y, 1729 dx, dy, mstate); 1730 } 1731 } 1732 1733 // Test children that are displayed below this 1734 foreach(child; _zOrderedChildren[]) 1735 { 1736 if (child.zOrder < zOrder) 1737 { 1738 bool hit; 1739 hit = child.mouseMove(x, y, dx, dy, mstate, 1740 found); 1741 found = found || hit; 1742 } 1743 } 1744 return found; 1745 } 1746 1747 // to be called at top-level when a key is pressed 1748 final bool keyDown(Key key) 1749 { 1750 if (onKeyDown(key)) 1751 return true; 1752 1753 foreach(child; _children[]) 1754 { 1755 if (child.keyDown(key)) 1756 return true; 1757 } 1758 return false; 1759 } 1760 1761 // to be called at top-level when a key is released 1762 final bool keyUp(Key key) 1763 { 1764 if (onKeyUp(key)) 1765 return true; 1766 1767 foreach(child; _children[]) 1768 { 1769 if (child.keyUp(key)) 1770 return true; 1771 } 1772 return false; 1773 } 1774 1775 // To be called at top-level periodically. 1776 // TODO why this isn't final? 1777 void animate(double dt, double time) 1778 { 1779 if (isAnimated) 1780 onAnimate(dt, time); 1781 1782 // For some rare widgets, it is important that 1783 // children are animated 1784 // _after_ their parent. 1785 foreach(child; _children[]) 1786 child.animate(dt, time); 1787 } 1788 1789 // Appends the Elements that should be drawn, in order. 1790 // You should empty it before calling this function. 1791 // Everything visible get into the draw list, but that 1792 // doesn't mean they will get drawn if they don't 1793 // overlap with a dirty area. 1794 final void getDrawLists(ref Vec!UIElement listRaw, 1795 ref Vec!UIElement listPBR) 1796 { 1797 if (_visibilityStatus) 1798 { 1799 if (drawsToRaw()) 1800 listRaw.pushBack(this); 1801 1802 if (drawsToPBR()) 1803 listPBR.pushBack(this); 1804 1805 // Note: if one widget is not visible, the whole 1806 // sub-tree can be ignored for drawing. 1807 // This is because invisibility is inherited 1808 // without recourse. 1809 foreach(child; _children[]) 1810 child.getDrawLists(listRaw, listPBR); 1811 } 1812 } 1813 1814 // Parent element. 1815 // Following this chain gets to the root element. 1816 UIElement _parent = null; 1817 1818 // Position is the graphical extent of the element, or 1819 // something larger. 1820 // An `UIElement` is not allowed though to draw further 1821 // than its _position. For efficiency it's best to keep 1822 // `_position` as small as feasible. 1823 // This is an absolute "world" positioning data, that 1824 // doesn't depend on the parent's position. 1825 box2i _position; 1826 1827 // The list of children UI elements. 1828 Vec!UIElement _children; 1829 1830 // Flags, for now immutable 1831 immutable(uint) _flags; 1832 1833 // Higher z-order = above other `UIElement`. 1834 // By default, every `UIElement` have the same z-order. 1835 // Because the sort is stable, tree traversal order is 1836 // the default order (depth first). 1837 // The children added last with `addChild` is considered 1838 // above its siblings if you don't have legacyZOrder. 1839 int _zOrder = 0; 1840 1841 private: 1842 1843 // Reference to owning context. 1844 UIContext _context; 1845 1846 // <visibility privates> 1847 1848 // If _visibleFlag is false, neither the Element nor its 1849 // children are drawn. 1850 bool _visibleFlag = true; 1851 1852 // Final visibility value, cached in order to set 1853 // rectangles dirty. 1854 // It is always up to date across the whole UI tree. 1855 bool _visibilityStatus = true; 1856 1857 void recomputeVisibilityStatus(bool parentStatus) 1858 { 1859 bool newStatus = _visibleFlag && parentStatus; 1860 1861 // has it changed in any way? 1862 if (newStatus != _visibilityStatus) 1863 { 1864 _visibilityStatus = newStatus; 1865 1866 // Dirty the widget position 1867 setDirtyWhole(); 1868 1869 // Inform children of the new parent status 1870 foreach(child; _children[]) 1871 child.recomputeVisibilityStatus(newStatus); 1872 } 1873 } 1874 1875 // </visibility privates> 1876 1877 // Dirty rectangles buffer, cropped to _position. 1878 // Technically would only need that as a temporary 1879 // array in TLS, but well. 1880 Vec!box2i _localRectsBuf; 1881 1882 // Sorted children. 1883 Vec!UIElement _zOrderedChildren; 1884 1885 // Cursor to display when widget is being dragged 1886 MouseCursor _cursorWhenDragged = MouseCursor.pointer; 1887 1888 // Cursor to display when widget is mouseover 1889 MouseCursor _cursorWhenMouseOver = MouseCursor.pointer; 1890 1891 // Identifier storage. 1892 char[maxUIElementIDLength+1] _idStorage; 1893 1894 // Warning: if you store objects here, keep in mind 1895 // they won't get destroyed automatically. 1896 // 4 user pointer in case you'd like to store things in 1897 // `UIElement` as a Dplug extension. 1898 // id 0..1 are reserved for Wren support. 1899 // id 2..3 are reserved for future Dplug extensions. 1900 // id 4..7 are for vendor-specific extensions. 1901 void*[8] _userPointers; // User pointers 1902 1903 // Sort children in ascending z-order 1904 // Input: unsorted _children 1905 // Output: sorted _zOrderedChildren 1906 // This is not thread-safe. 1907 // Only one widget in the same UI can sort its children 1908 // at once, since it uses 1909 // a UIContext buffer to do so. 1910 final void recomputeZOrderedChildren() 1911 { 1912 // Get a z-ordered list of childrens 1913 _zOrderedChildren.clearContents(); 1914 1915 version(legacyZOrder) 1916 { 1917 // See: Dplug Issue #652 1918 foreach(child; _children[]) 1919 _zOrderedChildren.pushBack(child); 1920 } 1921 else 1922 { 1923 // Adding children in reverse, since children 1924 // added last are considered having a higher 1925 // Z-order. 1926 foreach_reverse(child; _children[]) 1927 _zOrderedChildren.pushBack(child); 1928 } 1929 1930 timSort!UIElement(_zOrderedChildren[], 1931 context.sortingScratchBuffer(), 1932 (a, b) nothrow @nogc 1933 { 1934 if (a.zOrder < b.zOrder) return 1; 1935 else if (a.zOrder > b.zOrder) return -1; 1936 else return 0; 1937 }); 1938 1939 } 1940 1941 final void addDirtyRect(box2i rect, UILayer layer) 1942 { 1943 final switch(layer) 1944 { 1945 case UILayer.guessFromFlags: 1946 if (drawsToPBR()) 1947 { 1948 // Note: even if one UIElement draws to 1949 // both Raw and PBR layers, we are not 1950 // adding this rect in `dirtyListRaw` 1951 // since the Raw layer is automatically 1952 // updated when the PBR layer below is. 1953 _context.dirtyListPBR.addRect(rect); 1954 } 1955 else if (drawsToRaw()) 1956 { 1957 _context.dirtyListRaw.addRect(rect); 1958 } 1959 break; 1960 1961 case UILayer.rawOnly: 1962 _context.dirtyListRaw.addRect(rect); 1963 break; 1964 1965 case UILayer.allLayers: 1966 // This will lead the Raw layer to be 1967 // invalidated too 1968 _context.dirtyListPBR.addRect(rect); 1969 break; 1970 } 1971 } 1972 } 1973 1974 version(legacyMouseOver) 1975 { 1976 // legacyMouseOver was removed in Dplug v13. 1977 // Please see Release Notes. 1978 static assert(false); 1979 }