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 }