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 moduledplug.gui.element;
11 12 importcore.stdc.stdio;
13 importcore.stdc.string: strlen, strcmp;
14 15 importstd.math: round;
16 17 publicimportdplug.math.vector;
18 publicimportdplug.math.box;
19 publicimportdplug.graphics;
20 publicimportdplug.window.window;
21 publicimportdplug.core.sync;
22 publicimportdplug.core.vec;
23 publicimportdplug.core.nogc;
24 publicimportdplug.gui.boxlist;
25 publicimportdplug.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 aliasUIFlags = uint;
36 enum : UIFlags37 {
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 enumushortdefaultDepth = 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 enumubytedefaultRoughness = 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 enumubytedefaultSpecular = 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 enumubytedefaultMetalnessDielectric = 25;
124 enumubytedefaultMetalnessMetal = 255; ///ditto125 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 enumUILayer132 {
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 allLayers156 }
157 158 159 /**
160 Result of `onMouseClick`.
161 */162 enumClick163 {
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 unhandled212 }
213 214 /**
215 The maximum length for an UIElement ID.
216 */217 enummaxUIElementIDLength = 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 staticboolisValidElementID(const(char)[] identifier)
225 purenothrow @nogc @safe226 {
227 if (identifier.length == 0)
228 returnfalse;
229 if (identifier.length > maxUIElementIDLength)
230 returnfalse;
231 foreach (charch; identifier)
232 {
233 // Note: Chrome does actually accept ID with spaces234 if (ch == 0)
235 returnfalse;
236 }
237 returntrue;
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 enumUIELEMENT_POINTERID_WREN_EXPORTED_CLASS = 0;
252 enumUIELEMENT_POINTERID_WREN_VM_GENERATION = 1; ///ditto253 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 classUIElement263 {
264 // Summary of user APIs in `UIElement`:265 //266 // 1. Creation/destruction API267 // - this268 // - ~this269 //270 // 2. Widget positioning API271 // - position272 //273 // 3. Children API274 // - context275 // - parent276 // - topLevelParent277 // - child278 // - addChild279 // - removeChild280 //281 // 4. Invalidation API282 // - setDirtyWhole283 // - setDirty284 //285 // 5. Widget visibility API286 // - isVisible287 // - visibility288 //289 // 6. Widget Z-order API290 // - zOrder291 //292 // 7. Widget identifiers API293 // - id294 // - hasId295 // - getElementById296 //297 // 8. Layout callback298 // - reflow299 //300 // 9. Status API301 // - isMouseOver302 // - isDragged303 // - isFocused304 // - drawsToPBR305 // - drawsToRaw306 // - isAnimated307 // - isDrawAloneRaw308 // - isDrawAlonePBR309 //310 // 10. Mouse Cursor API311 // - cursorWhenDragged312 // - setCursorWhenDragged313 // - cursorWhenMouseOver314 // - setCursorWhenMouseOver315 //316 // 11. User Pointers API317 // - getUserPointer318 // - setUserPointer319 //320 // 12. Contains callback321 // - contains322 //323 // 13. Mouse Event callbacks324 // - onMouseEnter325 // - onMouseExit326 // - onMouseClick327 // - onMouseWheel328 // - onMouseMove329 //330 // 14. Drag Events callbacks331 // - onBeginDrag332 // - onStopDrag333 // - onMouseDrag334 //335 // 15. Keyboard Events callbacks336 // - onFocusEnter337 // - onFocusExit338 // - onKeyDown339 // - onKeyUp340 //341 // 16. Drawing callbacks.342 // - onDrawRaw343 // - onDrawPBR344 //345 // 17. Animation callback346 // - onAnimate347 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(UIContextcontext, uintflags)
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 ID378 379 // Initially empty position380 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 finalbox2iposition()
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 finalvoidposition(box2ip)
432 {
433 assert(p.isSorted());
434 435 boolmoved = (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 calls445 // reflow() if position has changed.446 reflow();
447 448 // If you fail here, it's because your449 // custom widget changed its own position in450 // `.reflow`.451 // But, `_position` shouldn't be touched by452 // `.reflow()` calls.453 assert(p == _position);
454 }
455 }
456 ///ditto457 finalvoidposition(box2fp)
458 {
459 intx1 = cast(int) round(p.min.x);
460 inty1 = cast(int) round(p.min.y);
461 intx2 = cast(int) round(p.max.x);
462 inty2 = cast(int) round(p.max.y);
463 box2ir = box2i(x1, y1, x2, y2);
464 position = r;
465 }
466 467 468 //469 // 3. Children API470 //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 finalUIContextcontext()
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 finalUIElementparent() purenothrow @nogc488 {
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 finalUIElementtopLevelParent() purenothrow @nogc498 {
499 if (_parentisnull)
500 returnthis;
501 else502 return_parent.topLevelParent();
503 }
504 505 /**
506 Returns: The nth child of this `UIElement`.
507 */508 finalUIElementchild(intn)
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 finalvoidaddChild(UIElementelement)
520 {
521 element._parent = this;
522 _children.pushBack(element);
523 524 // Recompute visibility of that element.525 boolparentVisible = 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 finalvoidremoveChild(UIElementelement)
541 {
542 intindex = _children.indexOf(element);
543 if(index >= 0)
544 {
545 // Dirty where the UIElement has been removed546 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 voidsetDirtyWhole(UILayerlayer =
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 voidsetDirty(box2irect,
601 UILayerlayer = UILayer.guessFromFlags)
602 {
603 // BUG: it is actually problematic to allow this604 // from the audio thread, because the access to605 // _position isn't protected and it could create a606 // race in case of concurrent reflow(). Pushed607 // rectangles might be out of range, this is608 // workarounded in GUIGraphics currently609 /// for other reasons.610 box2itranslatedRect = rect.translate(_position.min);
611 assert(_position.contains(translatedRect));
612 addDirtyRect(translatedRect, layer);
613 }
614 615 616 617 //618 // 5. Widget visibility API619 // Explanation: an invisible widget is not displayed620 // 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 finalboolisVisible() pureconst631 {
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 finalboolvisibility() pureconst644 {
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 finalvoidvisibility(boolvisible)
655 {
656 if (_visibleFlag == visible)
657 {
658 // Nothing to do, this wouldn't change any659 // visibility status in sub-tree.660 return;
661 }
662 663 _visibleFlag = visible;
664 665 // Get parent visibility status.666 boolparentVisibleStatus = 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 API677 //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 finalintzOrder() pureconst698 {
699 return_zOrder;
700 }
701 ///ditto702 finalvoidzOrder(intzOrder)
703 {
704 if (_zOrder != zOrder)
705 {
706 setDirtyWhole();
707 _zOrder = zOrder;
708 }
709 }
710 711 // TODO: how to depreciate that? Wren will stumble upon712 // every deprecated fields unfortunately.713 aliassetZOrder = zOrder;
714 715 716 717 //718 // 7. Widget identifiers API719 //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 finalvoidsetId(const(char)[] identifier) pure736 {
737 if (!isValidElementID(identifier))
738 {
739 // Note: assigning an invalid ID is a silent740 // 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 ///ditto748 finalvoidid(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 finalconst(char)[] getId() pure764 {
765 if (_idStorage[0] == '\0')
766 return"";
767 else768 {
769 size_tlen = strlen(_idStorage.ptr);
770 return_idStorage[0..len];
771 }
772 }
773 ///ditto774 finalconst(char)[] id() pure775 {
776 returngetId();
777 }
778 779 /**
780 Has this widget an identifier?
781 782 Returns: `true` if this `UIElement` has an ID.
783 */784 finalboolhasId() pure785 {
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 finalUIElementgetElementById(const(char)* id)
794 {
795 if (strcmp(id, _idStorage.ptr) == 0)
796 returnthis;
797 798 foreach(c; _children)
799 {
800 UIElementr = c.getElementById(id);
801 if (r) returnr;
802 }
803 returnnull;
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 voidreflow()
843 {
844 // By default: do nothing, do not position children845 }
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 finalboolisMouseOver() pureconst860 {
861 version(legacyMouseDrag)
862 {}
863 else864 {
865 if (_context.mouseOver !isthis)
866 {
867 // in newer mouse drag behaviour868 // the dragged item is always also mouseOver869 assert(_context.dragged !isthis);
870 }
871 }
872 873 return_context.mouseOveristhis;
874 }
875 876 /**
877 Widget dragged by mouse?
878 879 Between `.onBeginDrag()` and `.onStopDrag()`,
880 `isDragged` returns `true`.
881 882 */883 finalboolisDragged() pureconst884 {
885 version(legacyMouseDrag)
886 {}
887 else888 {
889 if (_context.draggedisthis)
890 assert(isMouseOver());
891 }
892 893 return_context.draggedisthis;
894 }
895 896 /**
897 Widget has keyboard focused? (last clicked)
898 */899 finalboolisFocused() pureconst900 {
901 return_context.focusedisthis;
902 }
903 904 /**
905 Widget draws on the PBR layer?
906 */907 finalbooldrawsToPBR() pureconst908 {
909 return (_flags & flagPBR) != 0;
910 }
911 912 /**
913 Widget draws on the Raw layer?
914 */915 finalbooldrawsToRaw() pureconst916 {
917 return (_flags & flagRaw) != 0;
918 }
919 920 /**
921 Is widget animated? (onAnimate called)
922 */923 finalboolisAnimated() pureconst924 {
925 return (_flags & flagAnimated) != 0;
926 }
927 928 /**
929 Should widget be drawn alone in Raw layer?
930 */931 finalboolisDrawAloneRaw() pureconst932 {
933 return (_flags & flagDrawAloneRaw) != 0;
934 }
935 936 /**
937 Should widget be drawn alone in PBR layer?
938 */939 finalboolisDrawAlonePBR() pureconst940 {
941 return (_flags & flagDrawAlonePBR) != 0;
942 }
943 944 945 //946 // 10. Mouse Cursor API947 // FUTURE: need redo/clarify this API.948 //949 950 ///951 MouseCursorcursorWhenDragged()
952 {
953 return_cursorWhenDragged;
954 }
955 956 ///957 voidsetCursorWhenDragged(MouseCursormouseCursor)
958 {
959 _cursorWhenDragged = mouseCursor;
960 }
961 962 ///963 MouseCursorcursorWhenMouseOver()
964 {
965 return_cursorWhenMouseOver;
966 }
967 968 ///969 voidsetCursorWhenMouseOver(MouseCursormouseCursor)
970 {
971 _cursorWhenMouseOver = mouseCursor;
972 }
973 974 975 //976 // 11. User Pointers API977 //978 979 /**
980 Set/Get a user pointer.
981 This allow `dplug:gui` extensions.
982 */983 finalvoid* getUserPointer(intpointerID)
984 {
985 return_userPointers[pointerID];
986 }
987 ///ditto988 finalvoidsetUserPointer(intpointerID, 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 boolcontains(intx, inty)
1022 {
1023 // FUTURE: should be onContains to disambiguate1024 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 voidonMouseEnter()
1045 {
1046 }
1047 ///ditto1048 voidonMouseExit()
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 ClickonMouseClick(intx, inty, intbutton,
1073 boolisDoubleClick,
1074 MouseStatemstate)
1075 {
1076 // By default, do nothing.1077 returnClick.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 boolonMouseWheel(intx, inty,
1095 intwheelDeltaX, intwheelDeltaY,
1096 MouseStatemstate)
1097 {
1098 // By default, do nothing.1099 returnfalse;
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 voidonMouseMove(intx, inty,
1118 intdx, intdy,
1119 MouseStatemstate)
1120 {
1121 // By default: do nothing1122 }
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 as1131 // long as no mouse buttons is released.1132 // There cannot be concurrent drags, but right now1133 // `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 voidonBeginDrag()
1153 {
1154 // Do nothing.1155 // Often, onMouseClick is the right place for what1156 // you may do here.1157 }
1158 ///ditto1159 voidonStopDrag()
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 voidonMouseDrag(intx, inty, intdx, intdy,
1181 MouseStatemstate)
1182 {
1183 }
1184 1185 1186 //1187 // 15. Keyboard Events Callback1188 //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 voidonFocusEnter()
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 voidonFocusExit()
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 boolonKeyDown(Keykey)
1223 {
1224 returnfalse;
1225 }
1226 ///ditto1227 boolonKeyUp(Keykey)
1228 {
1229 returnfalse;
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 voidonDrawRaw(ImageRef!RGBArawMap, box2i[] dirtyRects)
1264 {
1265 // By default: invisible1266 }
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 voidonDrawPBR(ImageRef!RGBAdiffuse,
1298 ImageRef!L16depth,
1299 ImageRef!RGBAmaterial,
1300 box2i[] dirtyRects)
1301 {
1302 // By default: checkerboard pattern1303 RGBAdarkGrey = RGBA(100, 100, 100, 0);
1304 RGBAlighterGrey = RGBA(150, 150, 150, 0);
1305 1306 // This is a typical draw function:1307 // for each r in dirtyRects1308 // crop inputs by r1309 // show something in it1310 // You don't have to fill everything though.1311 foreach(r; dirtyRects)
1312 {
1313 for (inty = 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 (intx = r.min.x; x < r.max.x; ++x)
1319 {
1320 RGBAcol = ((x>>3)^(y>>3))&1 ? darkGrey1321 : lighterGrey;
1322 diffuseScan.ptr[x] = col;
1323 depthScan.ptr[x] = L16(defaultDepth);
1324 RGBAm = 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 voidonAnimate(doubledt, doubletime)
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 finalvoidrenderRaw(ImageRef!RGBArawMap,
1380 inbox2i[] areasToUpdate)
1381 {
1382 // We only consider the part of _position that is1383 // actually in the surface.1384 // Indeed, `_position` should not be outside the1385 // bounds of a window (most widgets will support1386 // that), but most widgets will render that badly.1387 box2isurPos = box2i(0, 0, rawMap.w, rawMap.h);
1388 box2ivPos = _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 widget1395 // in local coordinates, for the Raw layer.1396 _localRectsBuf.clearContents();
1397 foreach(rect; areasToUpdate)
1398 {
1399 box2iinter = rect.intersection(vPos);
1400 if (!inter.empty)
1401 {
1402 box2itr = 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!RGBArawCrop = rawMap.cropImageRef(vPos);
1414 assert(rawCrop.w != 0 && rawCrop.h != 0);
1415 1416 // Call repaint function1417 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 finalvoidrenderPBR(ImageRef!RGBAdiffuse,
1425 ImageRef!L16depth,
1426 ImageRef!RGBAmaterial,
1427 inbox2i[] areasToUpdate)
1428 {
1429 intW = diffuse.w;
1430 intH = diffuse.h;
1431 1432 // We only consider the part of _position that is1433 // actually in the surface.1434 // Indeed, `_position` should not be outside the1435 // bounds of a window (most widgets will support1436 // that), but most widgets will render that badly.1437 box2isurPos = box2i(0, 0, W, H);
1438 box2ivPos = _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 widget1445 // in local coordinates, for the PBR layer.1446 _localRectsBuf.clearContents();
1447 foreach(rect; areasToUpdate)
1448 {
1449 box2iinter = rect.intersection(vPos);
1450 if (!inter.empty)
1451 {
1452 box2itr = 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!RGBAcDiffuse = diffuse.cropImageRef(vPos);
1464 ImageRef!L16cDepth = depth.cropImageRef(vPos);
1465 ImageRef!RGBAcMaterial=material.cropImageRef(vPos);
1466 1467 assert(cDiffuse.w != 0 && cDiffuse.h != 0);
1468 1469 // Call repaint function1470 onDrawPBR(cDiffuse, cDepth, cMaterial,
1471 _localRectsBuf[]);
1472 }
1473 1474 // to be called at top-level when the mouse clicked1475 finalboolmouseClick(intx, inty, intbutton,
1476 boolisDoubleClick, MouseStatemstate)
1477 {
1478 recomputeZOrderedChildren();
1479 1480 // Test children that are displayed above this1481 // element first1482 foreach(child; _zOrderedChildren[])
1483 {
1484 if (child.zOrder >= zOrder)
1485 if (child.mouseClick(x, y, button,
1486 isDoubleClick, mstate))
1487 returntrue;
1488 }
1489 1490 // Test for collision with this element1491 intpx = _position.min.x;
1492 intpy = _position.min.y;
1493 if (_visibilityStatus && contains(x - px, y - py))
1494 {
1495 Clickclick = onMouseClick(x - px,
1496 y - py, button, isDoubleClick, mstate);
1497 1498 finalswitch(click)
1499 {
1500 caseClick.handled:
1501 _context.setFocused(this);
1502 returntrue;
1503 1504 caseClick.handledNoFocus:
1505 returntrue;
1506 1507 caseClick.startDrag:
1508 _context.beginDragging(this);
1509 gotocaseClick.handled;
1510 1511 caseClick.unhandled:
1512 returnfalse;
1513 }
1514 }
1515 1516 foreach(child; _zOrderedChildren[])
1517 {
1518 if (child.zOrder < zOrder)
1519 if (child.mouseClick(x, y, button,
1520 isDoubleClick, mstate))
1521 returntrue;
1522 }
1523 1524 returnfalse;
1525 }
1526 1527 // to be called at top-level when the mouse is released1528 finalvoidmouseRelease(intx, inty,
1529 intbutton, MouseStatemstate)
1530 {
1531 version(legacyMouseDrag)
1532 {}
1533 else1534 {
1535 boolwasDragging = (_context.dragged !isnull);
1536 }
1537 1538 _context.stopDragging();
1539 1540 version(legacyMouseDrag)
1541 {}
1542 else1543 {
1544 // Enter widget below mouse if a dragged1545 // operation was stopped.1546 if (wasDragging)
1547 {
1548 boolok = 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 wheeled1557 finalboolmouseWheel(intx, inty,
1558 intwheelDeltaX, intwheelDeltaY,
1559 MouseStatemstate)
1560 {
1561 recomputeZOrderedChildren();
1562 1563 // Test children that are displayed above this1564 // element first1565 foreach(child; _zOrderedChildren[])
1566 {
1567 if (child.zOrder >= zOrder)
1568 if (child.mouseWheel(x, y, wheelDeltaX,
1569 wheelDeltaY, mstate))
1570 returntrue;
1571 }
1572 1573 intdx = x - _position.min.x;
1574 intdy = y - _position.min.y;
1575 1576 // cannot be mouse-wheeled if invisible1577 boolcanBeMouseWheeled = _visibilityStatus;
1578 if (canBeMouseWheeled && contains(dx, dy))
1579 {
1580 if (onMouseWheel(dx, dy, wheelDeltaX,
1581 wheelDeltaY, mstate))
1582 returntrue;
1583 }
1584 1585 // Test children that are displayed below this1586 // element last1587 foreach(child; _zOrderedChildren[])
1588 {
1589 if (child.zOrder < zOrder)
1590 if (child.mouseWheel(x, y, wheelDeltaX,
1591 wheelDeltaY, mstate))
1592 returntrue;
1593 }
1594 1595 returnfalse;
1596 }
1597 1598 // To be called when the mouse moved1599 finalboolmouseMove(intx, inty, intdx, intdy,
1600 MouseStatemstate, boolalreadyFoundMouseOver)
1601 {
1602 recomputeZOrderedChildren();
1603 1604 // "found" is whether we have found the hovered1605 // thing (mouseOver)1606 boolfound = alreadyFoundMouseOver;
1607 1608 // Test children that are displayed above this1609 // element first1610 foreach(child; _zOrderedChildren[])
1611 {
1612 if (child.zOrder >= zOrder)
1613 {
1614 boolhere = child.mouseMove(x, y, dx, dy,
1615 mstate, found);
1616 found = found || here;
1617 }
1618 }
1619 1620 if (isDragged())
1621 {
1622 // EDIT MODE1623 // With version `Dplug_RightClickMoveWidgets`,1624 // dragging with the right mouse button move1625 // elements around.1626 // Dragging with shift + right button resize1627 // elements around.1628 //1629 // Additionally, if CTRL is pressed, the1630 // increments are only -1 or +1 pixel.1631 //1632 // You can see the _position rectangle thanks to1633 // `debugLog`.1634 booldraggingUsed = false;
1635 version(Dplug_RightClickMoveWidgets)
1636 {
1637 if (mstate.rightButtonDown1638 && 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 intnx = _position.min.x;
1648 intny = _position.min.y;
1649 intw = _position.width + dx;
1650 inth = _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 elseif (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 intnx = _position.min.x + dx;
1666 intny = _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 boolcanBeMouseOver = _visibilityStatus;
1694 1695 version(legacyMouseDrag)
1696 {}
1697 else1698 {
1699 // If dragged, already received `onMouseDrag`.1700 // if something else dragged, cannot be hovered.1701 if (_context.dragged !isnull)
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 taken1710 if (!found)
1711 {
1712 found = true;
1713 _context.setMouseOver(this);
1714 1715 version(legacyMouseDrag)
1716 {}
1717 else1718 {
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 this1734 foreach(child; _zOrderedChildren[])
1735 {
1736 if (child.zOrder < zOrder)
1737 {
1738 boolhit;
1739 hit = child.mouseMove(x, y, dx, dy, mstate,
1740 found);
1741 found = found || hit;
1742 }
1743 }
1744 returnfound;
1745 }
1746 1747 // to be called at top-level when a key is pressed1748 finalboolkeyDown(Keykey)
1749 {
1750 if (onKeyDown(key))
1751 returntrue;
1752 1753 foreach(child; _children[])
1754 {
1755 if (child.keyDown(key))
1756 returntrue;
1757 }
1758 returnfalse;
1759 }
1760 1761 // to be called at top-level when a key is released1762 finalboolkeyUp(Keykey)
1763 {
1764 if (onKeyUp(key))
1765 returntrue;
1766 1767 foreach(child; _children[])
1768 {
1769 if (child.keyUp(key))
1770 returntrue;
1771 }
1772 returnfalse;
1773 }
1774 1775 // To be called at top-level periodically.1776 // TODO why this isn't final?1777 voidanimate(doubledt, doubletime)
1778 {
1779 if (isAnimated)
1780 onAnimate(dt, time);
1781 1782 // For some rare widgets, it is important that1783 // children are animated1784 // _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 that1792 // doesn't mean they will get drawn if they don't1793 // overlap with a dirty area.1794 finalvoidgetDrawLists(refVec!UIElementlistRaw,
1795 refVec!UIElementlistPBR)
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 whole1806 // sub-tree can be ignored for drawing.1807 // This is because invisibility is inherited1808 // 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, or1819 // something larger.1820 // An `UIElement` is not allowed though to draw further1821 // than its _position. For efficiency it's best to keep1822 // `_position` as small as feasible.1823 // This is an absolute "world" positioning data, that1824 // 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 immutable1831 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 is1836 // the default order (depth first).1837 // The children added last with `addChild` is considered1838 // 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 its1849 // children are drawn.1850 bool_visibleFlag = true;
1851 1852 // Final visibility value, cached in order to set1853 // rectangles dirty.1854 // It is always up to date across the whole UI tree.1855 bool_visibilityStatus = true;
1856 1857 voidrecomputeVisibilityStatus(boolparentStatus)
1858 {
1859 boolnewStatus = _visibleFlag && parentStatus;
1860 1861 // has it changed in any way?1862 if (newStatus != _visibilityStatus)
1863 {
1864 _visibilityStatus = newStatus;
1865 1866 // Dirty the widget position1867 setDirtyWhole();
1868 1869 // Inform children of the new parent status1870 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 temporary1879 // 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 dragged1886 MouseCursor_cursorWhenDragged = MouseCursor.pointer;
1887 1888 // Cursor to display when widget is mouseover1889 MouseCursor_cursorWhenMouseOver = MouseCursor.pointer;
1890 1891 // Identifier storage.1892 char[maxUIElementIDLength+1] _idStorage;
1893 1894 // Warning: if you store objects here, keep in mind1895 // they won't get destroyed automatically.1896 // 4 user pointer in case you'd like to store things in1897 // `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 pointers1902 1903 // Sort children in ascending z-order1904 // Input: unsorted _children1905 // Output: sorted _zOrderedChildren1906 // This is not thread-safe.1907 // Only one widget in the same UI can sort its children1908 // at once, since it uses1909 // a UIContext buffer to do so.1910 finalvoidrecomputeZOrderedChildren()
1911 {
1912 // Get a z-ordered list of childrens1913 _zOrderedChildren.clearContents();
1914 1915 version(legacyZOrder)
1916 {
1917 // See: Dplug Issue #6521918 foreach(child; _children[])
1919 _zOrderedChildren.pushBack(child);
1920 }
1921 else1922 {
1923 // Adding children in reverse, since children1924 // added last are considered having a higher1925 // Z-order.1926 foreach_reverse(child; _children[])
1927 _zOrderedChildren.pushBack(child);
1928 }
1929 1930 timSort!UIElement(_zOrderedChildren[],
1931 context.sortingScratchBuffer(),
1932 (a, b) nothrow @nogc1933 {
1934 if (a.zOrder < b.zOrder) return1;
1935 elseif (a.zOrder > b.zOrder) return -1;
1936 elsereturn0;
1937 });
1938 1939 }
1940 1941 finalvoidaddDirtyRect(box2irect, UILayerlayer)
1942 {
1943 finalswitch(layer)
1944 {
1945 caseUILayer.guessFromFlags:
1946 if (drawsToPBR())
1947 {
1948 // Note: even if one UIElement draws to1949 // both Raw and PBR layers, we are not1950 // adding this rect in `dirtyListRaw`1951 // since the Raw layer is automatically1952 // updated when the PBR layer below is.1953 _context.dirtyListPBR.addRect(rect);
1954 }
1955 elseif (drawsToRaw())
1956 {
1957 _context.dirtyListRaw.addRect(rect);
1958 }
1959 break;
1960 1961 caseUILayer.rawOnly:
1962 _context.dirtyListRaw.addRect(rect);
1963 break;
1964 1965 caseUILayer.allLayers:
1966 // This will lead the Raw layer to be1967 // invalidated too1968 _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 staticassert(false);
1979 }