1 /**
2 * `UIElement` is the base class of all widgets.
3 *
4 * Copyright: Copyright Auburn Sounds 2015 and later.
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Authors:   Guillaume Piolat
7 */
8 module dplug.gui.element;
9 
10 import core.stdc.stdio;
11 import core.stdc.string: strlen, strcmp;
12 
13 import std.math: round;
14 
15 public import dplug.math.vector;
16 public import dplug.math.box;
17 
18 public import dplug.graphics;
19 
20 public import dplug.window.window;
21 
22 public import dplug.core.sync;
23 public import dplug.core.vec;
24 public import dplug.core.nogc;
25 
26 public import dplug.gui.boxlist;
27 public import dplug.gui.context;
28 
29 /// Reasonable default value for the Depth channel.
30 enum ushort defaultDepth = 15000;
31 
32 /// Reasonable default value for the Roughness channel.
33 enum ubyte defaultRoughness = 128;
34 
35 /// Reasonable default value for the Specular channel ("everything is shiny").
36 enum ubyte defaultSpecular = 128;
37 
38 /// Reasonable dielectric default value for the Metalness channel.
39 enum ubyte defaultMetalnessDielectric = 25; // ~ 0.08
40 
41 /// Reasonable metal default value for the Metalness channel.
42 enum ubyte defaultMetalnessMetal = 255;
43 
44 /// Each UIElement class has flags which are used to lessen the number of empty virtual calls.
45 /// Such flags say which callbacks the `UIElement` need.
46 alias UIFlags = uint;
47 enum : UIFlags
48 {
49     /// This `UIElement` draws to the Raw layer and as such `onDrawRaw` should be called when dirtied.
50     /// When calling `setDirty(UILayer.guessFromFlags)`, the Raw layer alone will be invalidated.
51     flagRaw = 1,
52 
53     /// This `UIElement` draws to the PBR layer and as such `onDrawPBR` should be called when dirtied.
54     /// Important: `setDirty(UILayer.guessFromFlags)` will lead to BOTH `onDrawPBR` and `onDrawRaw` 
55     /// to be called sucessively.
56     flagPBR = 2,
57 
58     /// This `UIElement` is animated and as such the `onAnimate` callback should be called regularly.
59     flagAnimated = 4,
60 
61     /// Is not drawn in parallel with other widgets, when drawn to the Raw layer.
62     flagDrawAloneRaw = 8,
63 
64     /// Is not drawn in parallel with other widgets, when drawn to the PBR layer.
65     flagDrawAlonePBR = 16,
66 }
67 
68 /// Used by `setDirty` calls to figure which layer should be invalidated.
69 enum UILayer
70 {
71     /// Use the `UIElement` flags to figure which layers to invalidate.
72     /// This is what you want most of the time.
73     guessFromFlags,
74 
75     /// Only the Raw layer is invalidated.
76     /// This is what you want if your `UIElement` draw to both Raw and PBR layer, but this 
77     /// time you only want to udpate a fast Raw overlay (ie: any PBR widget that still need to be real-time)
78     rawOnly,
79 
80     /// This is only useful for the very first setDirty call, to mark the whole UI dirty.
81     /// For internal Dplug usage.
82     allLayers
83 }
84 
85 /// Result of `onMouseClick`, was the mouse click handled?
86 enum Click
87 {
88     handled,   /// click handled, no drag.            
89     startDrag, /// click handled AND it start new drag. (previously: true)
90     unhandled  /// click not handeld, pass it around.   (previously: false)
91 }
92 
93 /// The maximum length for an UIElement ID.
94 enum maxUIElementIDLength = 63;
95 
96 /// Returns: true if a valid UIlement unique identifier.
97 /// Chrome rules applies: can't be empty.
98 static bool isValidElementID(const(char)[] identifier) pure nothrow @nogc @safe
99 {
100     if (identifier.length == 0) return false;
101     if (identifier.length > maxUIElementIDLength) return false;
102     foreach (char ch; identifier)
103     {
104         if (ch == 0) // Note: Chrome does actually accept ID with spaces
105             return false;
106     }
107     return true;
108 }
109 
110 // An UIElement has 8 void* user pointers (4 reserved for Dplug + 4 for vendors).
111 // The first two are used by dplug:wren-support.
112 // Official dplug:wren-support optional extension.
113 enum UIELEMENT_POINTERID_WREN_EXPORTED_CLASS = 0; /// The cached Wren class of this UIElement.
114 enum UIELEMENT_POINTERID_WREN_VM_GENERATION  = 1; /// The Wren VM count, as it is restarted. Stored as void*, but is an uint.
115 
116 /// Base class of the UI widget hierarchy.
117 ///
118 /// MAYDO: a bunch of stuff in that class is intended specifically for the root element,
119 ///        there is probably a better design to find.
120 class UIElement
121 {
122 public:
123 nothrow:
124 @nogc:
125 
126     this(UIContext context, uint flags)
127     {
128         _context = context;
129         _flags = flags;
130         _localRectsBuf = makeVec!box2i();
131         _children = makeVec!UIElement();
132         _zOrderedChildren = makeVec!UIElement();
133         _idStorage[0] = '\0'; // defaults to no ID
134 
135         // Initially set to an empty position
136         assert(_position.empty()); 
137     }
138 
139     ~this()
140     {
141         foreach(child; _children[])
142             child.destroyFree();
143     }
144 
145     /// Set this element ID.
146     /// All UIElement can have a string as unique identifier, similar to HTML.
147     /// There is a maximum of 63 characters for this id though.
148     /// This ID is supposed to be unique. If it isn't, a search by ID will return `null`.
149     /// Chrome rules applies: can't contain any space characters.
150     final void setId(const(char)[] identifier) pure
151     {
152         if (!isValidElementID(identifier))
153         {
154             // Note: assigning an invalid ID is a silent error. The UIElement ends up with no ID.
155             _idStorage[0] = '\0';
156             return;
157         }
158         _idStorage[0..identifier.length] = identifier[0..$];
159         _idStorage[identifier.length] = '\0';
160     }
161 
162     /// Get this element ID.
163     /// All UIElement can have a string as unique identifier, similar to HTML.
164     /// Returns the empty string "" if there is no ID.
165     /// Note: this return an interior slice, and could be invalidated if the ID is reassigned.
166     final const(char)[] getId() pure
167     {
168         if (_idStorage[0] == '\0')
169             return "";
170         else
171         {
172             size_t len = strlen(_idStorage.ptr);
173             return _idStorage[0..len];
174         }
175     }
176 
177     /// Properties to access this element ID.
178     /// See_also: setId, getId.
179     final const(char)[] id() pure
180     {
181         return getId();
182     }
183     ///ditto
184     final void id(const(char)[] identifier)
185     {
186         setId(identifier);
187     }
188 
189     /// Returns: `true` if thie UIElement has an ID.
190     final bool hasId() pure
191     {
192         return _idStorage[0] != '\0';
193     }
194 
195     /// Search subtree for an UIElement with ID `id`. Undefined Behaviour if ID are not unique.
196     final UIElement getElementById(const(char)* id)
197     {
198         if (strcmp(id, _idStorage.ptr) == 0)
199             return this;
200 
201         foreach(c; _children)
202         {
203             UIElement r = c.getElementById(id);
204             if (r) return r;
205         }
206         return null;
207     }
208 
209     /// This method is called for each item in the drawlist that was visible and has a dirty Raw layer.
210     /// This is called after compositing, starting from the buffer output by the Compositor.
211     final void renderRaw(ImageRef!RGBA rawMap, in box2i[] areasToUpdate)
212     {
213         // We only consider the part of _position that is actually in the surface
214         box2i validPosition = _position.intersection(box2i(0, 0, rawMap.w, rawMap.h));
215 
216         // Note: _position can be outside the bounds of a window.
217 
218         if (validPosition.empty())
219             return; // nothing to draw here
220 
221         _localRectsBuf.clearContents();
222         {
223             foreach(rect; areasToUpdate)
224             {
225                 box2i inter = rect.intersection(validPosition);
226 
227                 if (!inter.empty) // don't consider empty rectangles
228                 {
229                     // Express the dirty rect in local coordinates for simplicity
230                     _localRectsBuf.pushBack( inter.translate(-validPosition.min) );
231                 }
232             }
233         }
234 
235         if (_localRectsBuf.length == 0)
236             return; // nothing to draw here
237 
238         // Crop the composited map to the valid part of _position
239         // Drawing outside of _position is disallowed by design.
240         ImageRef!RGBA rawMapCropped = rawMap.cropImageRef(validPosition);
241         assert(rawMapCropped.w != 0 && rawMapCropped.h != 0); // Should never be an empty area there
242         onDrawRaw(rawMapCropped, _localRectsBuf[]);
243     }
244 
245     /// Returns: true if was drawn, ie. the buffers have changed.
246     /// This method is called for each item in the drawlist that was visible and has a dirty PBR layer.
247     final void renderPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, in box2i[] areasToUpdate)
248     {
249         // we only consider the part of _position that is actually in the surface
250         box2i validPosition = _position.intersection(box2i(0, 0, diffuseMap.w, diffuseMap.h));
251 
252         // Note: _position can be outside the bounds of a window.
253 
254         if (validPosition.empty())
255             return; // nothing to draw here
256 
257         _localRectsBuf.clearContents();
258         {
259             foreach(rect; areasToUpdate)
260             {
261                 box2i inter = rect.intersection(validPosition);
262 
263                 if (!inter.empty) // don't consider empty rectangles
264                 {
265                     // Express the dirty rect in local coordinates for simplicity
266                     _localRectsBuf.pushBack( inter.translate(-validPosition.min) );
267                 }
268             }
269         }
270 
271         if (_localRectsBuf.length == 0)
272             return; // nothing to draw here
273 
274         // Crop the diffuse, material and depth to the valid part of _position
275         // Drawing outside of _position is disallowed by design.
276         ImageRef!RGBA diffuseMapCropped = diffuseMap.cropImageRef(validPosition);
277         ImageRef!L16 depthMapCropped = depthMap.cropImageRef(validPosition);
278         ImageRef!RGBA materialMapCropped = materialMap.cropImageRef(validPosition);
279 
280         // Should never be an empty area there
281         assert(diffuseMapCropped.w != 0 && diffuseMapCropped.h != 0);
282         onDrawPBR(diffuseMapCropped, depthMapCropped, materialMapCropped, _localRectsBuf[]);
283     }
284 
285     /// The goal of this method is to update positions of childrens. It is called whenever
286     /// _position changes.
287     ///
288     /// It is called after a widget position is changed.
289     /// Given information with `position` getter, the widget decides the position of its 
290     /// children, by calling their `position` setter (which will call `reflow` itself).
291     /// 
292     /// `reflow()` cannot be used to set the own position of a widget: it is always done
293     /// externally. You shouldn't call reflow yourself, instead use `position = x;`.
294     ///
295     /// Like in the DOM, children elements don't need to be inside position of their parent.
296     /// The _position field is indeed storing an absolute position.
297     ///
298     /// See_also: `position`, `_position`.
299     void reflow()
300     {
301         // default: do nothing
302     }
303 
304     /// Returns: Position of the element, that will be used for rendering. 
305     /// This getter is typically used in reflow() to adapt resource and children to the new position.
306     final box2i position()
307     {
308         return _position;
309     }
310 
311     /// Changes the position of the element.
312     /// This calls `reflow` if that position has changed.
313     /// IMPORTANT: As of today you are not allowed to assign a position outside the extent of the window.
314     ///            This is purely a Dplug limitation.
315     final void position(box2i p)
316     {
317         assert(p.isSorted());
318 
319         bool moved = (p != _position);
320         
321         // Make dirty rect in former and new positions.
322         if (moved)
323         {
324             setDirtyWhole();
325             _position = p;
326             setDirtyWhole();
327 
328             // New in Dplug v11: setting position now calls reflow() if position has changed.
329             reflow();
330 
331             // _position shouldn't be touched by `reflow` calls.
332             assert(p == _position);
333         }
334     }
335 
336     /// Changes the position of the element.
337     /// This calls `reflow` if that position has changed.
338     /// Note: Widget coordinates are always integer coordinates.
339     ///       The input rectangle is rounded to nearest integer.
340     final void position(box2f p)
341     {
342         int x1 = cast(int) round(p.min.x);
343         int y1 = cast(int) round(p.min.y);
344         int x2 = cast(int) round(p.max.x);
345         int y2 = cast(int) round(p.max.y);
346         box2i r = box2i(x1, y1, x2, y2);
347         position = r;
348     }
349 
350     /// Returns: The nth  child of this `UIElement`.
351     final UIElement child(int n)
352     {
353         return _children[n];
354     }
355 
356     /// Adds an `UIElement`
357     /// The addChild method is mandatory.
358     /// Such a child MUST be created through `dplug.core.nogc.mallocEmplace`.
359     /// Note: to display a newly added widget, use `position` setter.
360     final void addChild(UIElement element)
361     {
362         element._parent = this;
363         _children.pushBack(element);
364 
365         // Recompute visibility of that element.
366         bool parentVisible = isVisible();
367         element.recomputeVisibilityStatus(parentVisible);
368     }
369 
370     /// Removes a child (but does not destroy it, you take back the ownership of it).
371     /// Useful for creating dynamic UI's.
372     /// MAYDO: there are restrictions for where this is allowed. Find them.
373     final void removeChild(UIElement element)
374     {
375         int index= _children.indexOf(element);
376         if(index >= 0)
377         {
378             // Dirty where the UIElement has been removed
379             element.setDirtyWhole();
380 
381             _children.removeAndReplaceByLastElement(index);
382         }
383     }
384 
385     /// `onMouseClick` is called for every new click, whether or not you are in a 
386     /// dragging operation.
387     /// This function is meant to be overriden.
388     ///
389     /// Returns:
390     ///    If you return `Click.handled`, the click is considered processed 
391     ///    and will not pass to parent window.
392     ///
393     ///    If you return `Click.startDrag`, the click is considered processed. 
394     ///    Any existing dragging is stopped with `onStopDrag`, and a new drag operation
395     ///    is started. `onBeginDrag`/`onStopDrag` are also called. (was formerly: returning true)
396     ///
397     ///    If you return `Click.unhandled`, the click is unprocessed. 
398     ///    If may be passed down to the underlying parent window. 
399     ///    (was formerly: returning false)
400     ///
401     /// Warning: For this reason, check your widgets with several mouse buttons pressed 
402     /// at once.
403     Click onMouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate)
404     {
405         // By default, does not recognize that click.
406         return Click.unhandled; 
407     }
408 
409     /// Mouse wheel was turned.
410     /// This function is meant to be overriden.
411     /// It should return true if the wheel is handled.
412     bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate)
413     {
414         return false;
415     }
416 
417     /// Called when mouse move over this Element.
418     /// This function is meant to be overriden.
419     ///
420     /// Note: If "legacyMouseDrag" version identifier is used, 
421     /// this will be called even during a drag, beware.
422     void onMouseMove(int x, int y, int dx, int dy, MouseState mstate)
423     {
424     }
425 
426     /// Called when clicked with left/middle/right button
427     /// This function is meant to be overriden.
428     /// Between `onBeginDrag` and `onStopDrag`, `isDragged` will return `true`.
429     ///
430     /// Note: When a widget is dragged, and "futureMouseDrag" version identifier is used, 
431     /// then a dragged widget is always also isMouseOver.
432     ///
433     /// Example: you could call `beginParamEdit` from there or from `onMouseClick`. You probably 
434     /// have more context in `onMouseClick`.
435     void onBeginDrag()
436     {
437     }
438 
439     /// Called when mouse drag this Element.
440     /// This function is meant to be overriden.
441     ///
442     /// Example: if a mouse click started an edit f a plugin parameter with a drag, this will be a
443     /// preferred place to call `param.setFromGUI`.
444     void onMouseDrag(int x, int y, int dx, int dy, MouseState mstate)
445     {
446     }
447 
448     /// Called once a dragging operation is finished.
449     /// This function is meant to be overriden.
450     /// Between `onBeginDrag` and `onStopDrag`, `isDragged` will return `true`.
451     ///
452     /// Note: When a widget is dragged, and "futureMouseDrag" version identifier is used, 
453     /// then a dragged widget is always also isMouseOver.
454     ///
455     /// Example: if a mouse click started a modification of a plugin parameter, this will be a
456     /// preferred place to call `param.endParamEdit`.
457     void onStopDrag()
458     {
459     }
460 
461     /// Called when mouse enter this Element.
462     /// This function is meant to be overriden.
463     /// Between `onMouseEnter` and `onMouseExit`, `isMouseOver` will return `true`.
464     ///
465     /// Example: use this callback to call `setDirtyWhole` so that you can draw a highlight when 
466     /// the widget is pointed to.
467     void onMouseEnter()
468     {
469     }
470 
471     /// Called when mouse enter this Element.
472     /// This function is meant to be overriden.
473     /// Between `onMouseEnter` and `onMouseExit`, `isMouseOver` will return `true`.
474     ///
475     /// Example: use this callback to call `setDirtyWhole` so that you can draw a lack of highlight 
476     /// when the widget is pointed to.
477     void onMouseExit()
478     {
479     }
480 
481     /// Called when this Element is clicked and get the "focus" (ie. keyboard focus).
482     /// This function is meant to be overriden.
483     void onFocusEnter()
484     {
485     }
486 
487     /// Called when focus is lost because another Element was clicked.
488     /// This widget then loose the "focus" (ie. keyboard focus).
489     /// This function is meant to be overriden.
490     ///
491     /// Example: if a widget is foldable (like a popup menu), you can us this callback to close it
492     /// once the user click anywhere else.
493     void onFocusExit()
494     {
495     }
496 
497     /// Called when a key is pressed. This event bubbles down-up until being processed.
498     /// Return true if treating the message.
499     bool onKeyDown(Key key)
500     {
501         return false;
502     }
503 
504     /// Called when a key is pressed. This event bubbles down-up until being processed.
505     /// Return true if treating the message.
506     bool onKeyUp(Key key)
507     {
508         return false;
509     }
510 
511     /// Check if given point is within the widget. 
512     /// Override this to disambiguate clicks and mouse-over between widgets that 
513     /// would otherwise partially overlap.
514     /// 
515     /// `x` and `y` are given in local widget coordinates.
516     /// IMPORTANT: a widget CANNOT be clickable beyond its _position.
517     ///            For now, there is no good reason for that, but it could be useful
518     ///            in the future if we get acceleration structure for picking elements.
519     bool contains(int x, int y)
520     {
521         return (x < cast(uint)(_position.width ) )
522             && (y < cast(uint)(_position.height) );
523     }
524 
525     // to be called at top-level when the mouse clicked
526     final bool mouseClick(int x, int y, int button, bool isDoubleClick, MouseState mstate)
527     {
528         recomputeZOrderedChildren();
529 
530         // Test children that are displayed above this element first
531         foreach(child; _zOrderedChildren[])
532         {
533             if (child.zOrder >= zOrder)
534                 if (child.mouseClick(x, y, button, isDoubleClick, mstate))
535                     return true;
536         }
537 
538         // Test for collision with this element
539         bool canBeClicked = _visibilityStatus; // cannot be clicked if invisible
540         if (canBeClicked && contains(x - _position.min.x, y - _position.min.y))
541         {
542             Click click = onMouseClick(x - _position.min.x, y - _position.min.y, button, isDoubleClick, mstate);
543 
544             final switch(click)
545             {
546                 case Click.handled:
547                     _context.setFocused(this);
548                     return true;
549 
550                 case Click.startDrag:
551                     _context.beginDragging(this);
552                     goto case Click.handled;
553 
554                 case Click.unhandled:
555                     return false;
556             }
557         }
558 
559         // Test children that are displayed below this element last
560         foreach(child; _zOrderedChildren[])
561         {
562             if (child.zOrder < zOrder)
563                 if (child.mouseClick(x, y, button, isDoubleClick, mstate))
564                     return true;
565         }
566 
567         return false;
568     }
569 
570     // to be called at top-level when the mouse is released
571     final void mouseRelease(int x, int y, int button, MouseState mstate)
572     {
573         version(futureMouseDrag)
574         {
575             bool wasDragging = (_context.dragged !is null);
576         }
577 
578         _context.stopDragging();
579 
580         version(futureMouseDrag)
581         {
582             // Enter widget below mouse if a dragged operation was stopped.
583             if (wasDragging)
584             {
585                 bool foundOver = mouseMove(x, y, 0, 0, mstate, false);
586                 if (!foundOver)
587                     _context.setMouseOver(null);
588             }
589         }
590     }
591 
592     // to be called at top-level when the mouse wheeled
593     final bool mouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate)
594     {
595         recomputeZOrderedChildren();
596 
597         // Test children that are displayed above this element first
598         foreach(child; _zOrderedChildren[])
599         {
600             if (child.zOrder >= zOrder)
601                 if (child.mouseWheel(x, y, wheelDeltaX, wheelDeltaY, mstate))
602                     return true;
603         }
604 
605         bool canBeMouseWheeled = _visibilityStatus; // cannot be mouse-wheeled if invisible
606         if (canBeMouseWheeled && contains(x - _position.min.x, y - _position.min.y))
607         {
608             if (onMouseWheel(x - _position.min.x, y - _position.min.y, wheelDeltaX, wheelDeltaY, mstate))
609                 return true;
610         }
611 
612         // Test children that are displayed below this element last
613         foreach(child; _zOrderedChildren[])
614         {
615             if (child.zOrder < zOrder)
616                 if (child.mouseWheel(x, y, wheelDeltaX, wheelDeltaY, mstate))
617                     return true;
618         }
619 
620         return false;
621     }
622 
623     // To be called when the mouse moved
624     // Returns: `true` if one child has taken the mouse-over role globally.
625     final bool mouseMove(int x, int y, int dx, int dy, MouseState mstate, bool alreadyFoundMouseOver)
626     {
627         recomputeZOrderedChildren();
628 
629         bool foundMouseOver = alreadyFoundMouseOver;
630 
631         // Test children that are displayed above this element first
632         foreach(child; _zOrderedChildren[])
633         {
634             if (child.zOrder >= zOrder)
635             {
636                 bool found = child.mouseMove(x, y, dx, dy, mstate, foundMouseOver);
637                 foundMouseOver = foundMouseOver || found;
638             }
639         }
640 
641         if (isDragged())
642         {
643             // EDIT MODE
644             // In debug mode, dragging with the right mouse button move elements around
645             // and dragging with shift  + right button resize elements around.
646             //
647             // Additionally, if CTRL is pressed, the increments are only -1 or +1 pixel.
648             // 
649             // You can see the _position rectangle thanks to `debugLog`.
650             bool draggingUsed = false;
651             debug
652             {
653                 if (mstate.rightButtonDown && mstate.shiftPressed)
654                 {
655                     if (mstate.ctrlPressed)
656                     {
657                         if (dx < -1) dx = -1;
658                         if (dx >  1) dx =  1;
659                         if (dy < -1) dy = -1;
660                         if (dy >  1) dy =  1;
661                     }
662                     int nx = _position.min.x;
663                     int ny = _position.min.y;
664                     int w = _position.width + dx;
665                     int h = _position.height + dy;
666                     if (w < 5) w = 5;
667                     if (h < 5) h = 5;
668                     position = box2i(nx, ny, nx + w, ny + h);
669                     draggingUsed = true;
670 
671 
672                 }
673                 else if (mstate.rightButtonDown)
674                 {
675                     if (mstate.ctrlPressed)
676                     {
677                         if (dx < -1) dx = -1;
678                         if (dx >  1) dx =  1;
679                         if (dy < -1) dy = -1;
680                         if (dy >  1) dy =  1;
681                     }
682                     int nx = _position.min.x + dx;
683                     int ny = _position.min.y + dy;
684                     if (nx < 0) nx = 0;
685                     if (ny < 0) ny = 0;
686                     position = box2i(nx, ny, nx + position.width, ny + position.height);
687                     draggingUsed = true;
688                 }
689 
690                 // Output the latest position
691                 // This is helpful when developing a plug-in UI.
692                 if (draggingUsed)
693                 {
694                     char[128] buf;
695                     snprintf(buf.ptr, 128, "position = box2i.rectangle(%d, %d, %d, %d)\n", _position.min.x, _position.min.y, _position.width, _position.height);
696                     debugLog(buf.ptr);
697                 }
698             }
699 
700             if (!draggingUsed)
701                 onMouseDrag(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
702         }
703 
704         // Can't be mouse over if not visible.
705         bool canBeMouseOver = _visibilityStatus;
706 
707         version(futureMouseDrag)
708         {
709             // If dragged, it already received `onMouseDrag`.
710             // if something else is dragged, it can be mouse over.
711             if (_context.dragged !is null)
712                 canBeMouseOver = false;
713         }
714 
715         if (canBeMouseOver && contains(x - _position.min.x, y - _position.min.y)) // FUTURE: something more fine-grained?
716         {
717             // Get the mouse-over crown if not taken
718             if (!foundMouseOver)
719             {
720                 foundMouseOver = true;
721                 _context.setMouseOver(this);
722 
723                 version(futureMouseDrag)
724                 {
725                     onMouseMove(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
726                 }
727             }
728 
729             version(futureMouseDrag)
730             {}
731             else
732             {
733                 onMouseMove(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
734             }
735         }
736 
737         // Test children that are displayed below this element
738         foreach(child; _zOrderedChildren[])
739         {
740             if (child.zOrder < zOrder)
741             {
742                 bool found = child.mouseMove(x, y, dx, dy, mstate, foundMouseOver);
743                 foundMouseOver = foundMouseOver || found;
744             }
745         }
746         return foundMouseOver;
747     }
748 
749     // to be called at top-level when a key is pressed
750     final bool keyDown(Key key)
751     {
752         if (onKeyDown(key))
753             return true;
754 
755         foreach(child; _children[])
756         {
757             if (child.keyDown(key))
758                 return true;
759         }
760         return false;
761     }
762 
763     // to be called at top-level when a key is released
764     final bool keyUp(Key key)
765     {
766         if (onKeyUp(key))
767             return true;
768 
769         foreach(child; _children[])
770         {
771             if (child.keyUp(key))
772                 return true;
773         }
774         return false;
775     }
776 
777     // To be called at top-level periodically.
778     void animate(double dt, double time)
779     {
780         if (isAnimated)
781             onAnimate(dt, time);
782 
783         // For some rare widgets, it is important that children are animated
784         // _after_ their parent.
785         foreach(child; _children[])
786             child.animate(dt, time);
787     }
788 
789     final UIContext context()
790     {
791         return _context;
792     }
793 
794     /// A widget is "visible" when it has a true visibility flag, and its parent is itself visible.
795     /// Returns: Last computed visibility status.
796     final bool isVisible() pure const
797     {
798         return _visibilityStatus;
799     }
800 
801     /// Get visibility flag of the widget.
802     /// A widget might still be invisible, if one of its parent is not visible.
803     final bool visibility() pure const
804     {
805         return _visibleFlag;
806     }
807 
808     /// Change visibility flag of the widget. This show and hide all children of this UIElement,
809     /// regardless of their position on screen, invalidating their graphics if need be much
810     /// like a position change.
811     final void visibility(bool visible)
812     {
813         if (_visibleFlag == visible)
814             return; // Nothing to do, this wouldn't change any visibility status in sub-tree.
815 
816         _visibleFlag = visible;
817 
818         // Get parent visibility status.
819         bool parentVisibleStatus = (parent !is null) ? parent.isVisible() : true;
820 
821         // Recompute own visibility status.
822         recomputeVisibilityStatus(parentVisibleStatus);
823     }
824 
825     final int zOrder() pure const
826     {
827         return _zOrder;
828     }
829 
830     // TODO: how to deprecate that? Wren will stumble upon every deprecated fields unfortunately.
831     alias setZOrder = zOrder;
832 
833     final void zOrder(int zOrder)
834     {
835         if (_zOrder != zOrder)
836         {
837             setDirtyWhole();
838             _zOrder = zOrder;
839         }
840     }
841 
842     /// Mark this element as wholly dirty.
843     ///
844     /// Params:
845     ///     layer which layers need to be redrawn.
846     ///
847     /// Important: you _can_ call this from the audio thread, HOWEVER it is
848     ///            much more efficient to mark the widget dirty with an atomic 
849     ///            and call `setDirty` in animation callback.
850     void setDirtyWhole(UILayer layer = UILayer.guessFromFlags)
851     {
852         addDirtyRect(_position, layer);
853     }
854 
855     /// Mark a part of the element dirty.
856     /// This part must be a subrect of its _position.
857     ///
858     /// Params:
859     ///     rect = Position of the dirtied rectangle, in widget coordinates.
860     ///
861     /// Important: you could call this from the audio thread, however it is
862     ///            much more efficient to mark the widget dirty with an atomic 
863     ///            and call setDirty in animation callback.
864     void setDirty(box2i rect, UILayer layer = UILayer.guessFromFlags)
865     {
866         /// BUG: it is problematic to allow this from the audio thread,
867         /// because the access to _position isn't protected and it could 
868         /// create a race in case of concurrent reflow(). Puhsed rectangles
869         /// might be out of range, this is workarounded in GUIGraphics currently
870         /// for other reasons.
871         box2i translatedRect = rect.translate(_position.min);
872         assert(_position.contains(translatedRect));
873         addDirtyRect(translatedRect, layer);
874     }  
875 
876     /// Returns: Parent element. `null` if detached or root element.
877     final UIElement parent() pure nothrow @nogc
878     {
879         return _parent;
880     }
881 
882     /// Returns: Top-level parent. `null` if detached or root element.
883     final UIElement topLevelParent() pure nothrow @nogc
884     {
885         if (_parent is null)
886             return this;
887         else
888             return _parent.topLevelParent();
889     }
890 
891     /// Returns: `true` is this element is hovered by the mouse, and 
892     final bool isMouseOver() pure const
893     {
894         version(futureMouseDrag)
895         {
896             if (_context.mouseOver !is this)
897             {
898                 assert(_context.dragged !is this);
899             }
900         }
901 
902         return _context.mouseOver is this;
903     }
904 
905     final bool isDragged() pure const
906     {
907         version(futureMouseDrag)
908         {
909             if (_context.dragged is this)
910                 assert(isMouseOver());
911         }
912 
913         return _context.dragged is this;
914     }
915 
916     final bool isFocused() pure const
917     {
918         return _context.focused is this;
919     }
920 
921     final bool drawsToPBR() pure const
922     {
923         return (_flags & flagPBR) != 0;
924     }
925 
926     final bool drawsToRaw() pure const
927     {
928         return (_flags & flagRaw) != 0;
929     }
930 
931     final bool isAnimated() pure const
932     {
933         return (_flags & flagAnimated) != 0;
934     }
935 
936     final bool isDrawAloneRaw() pure const
937     {
938         return (_flags & flagDrawAloneRaw) != 0;
939     }
940 
941     final bool isDrawAlonePBR() pure const
942     {
943         return (_flags & flagDrawAlonePBR) != 0;
944     }
945 
946     /// Appends the Elements that should be drawn, in order.
947     /// You should empty it before calling this function.
948     /// Everything visible get into the draw list, but that doesn't mean they
949     /// will get drawn if they don't overlap with a dirty area.
950     final void getDrawLists(ref Vec!UIElement listRaw, ref Vec!UIElement listPBR)
951     {
952         if (_visibilityStatus)
953         {
954             if (drawsToRaw())
955                 listRaw.pushBack(this);
956 
957             if (drawsToPBR())
958                 listPBR.pushBack(this);
959 
960             // Note: if one widget is not visible, the whole sub-tree can be ignored for drawing.
961             // This is because invisibility is inherited without recourse.
962             foreach(child; _children[])
963                 child.getDrawLists(listRaw, listPBR);
964         }
965     }
966 
967     MouseCursor cursorWhenDragged() 
968     { 
969         return _cursorWhenDragged;
970     }
971 
972     void setCursorWhenDragged(MouseCursor mouseCursor)
973     {
974         _cursorWhenDragged = mouseCursor;
975     }
976 
977     MouseCursor cursorWhenMouseOver()
978     {
979         return _cursorWhenMouseOver;
980     }
981 
982     void setCursorWhenMouseOver(MouseCursor mouseCursor)
983     {
984         _cursorWhenMouseOver = mouseCursor;
985     }
986 
987     /// Get a user pointer. Allow dplug:gui extensions.
988     final void* getUserPointer(int pointerID)
989     {
990         return _userPointers[pointerID];
991     }
992 
993     /// Set a user pointer. Allow dplug:gui extensions.
994     final void setUserPointer(int pointerID, void* userPointer)
995     {
996         _userPointers[pointerID] = userPointer;
997     }
998 
999 protected:
1000 
1001     /// Raw layer draw method. This gives you 1 surface cropped by  _position for drawing.
1002     /// Note that you are not forced to draw to the surfaces at all.
1003     /// 
1004     /// `UIElement` are drawn by increasing z-order, or lexical order if lack thereof.
1005     /// Those elements who have non-overlapping `_position` are drawn in parallel.
1006     /// Hence you CAN'T draw outside `_position` and receive cropped surfaces.
1007     ///
1008     /// IMPORTANT: you MUST NOT draw outside `dirtyRects`. This allows more fine-grained updates.
1009     /// A `UIElement` that doesn't respect dirtyRects will have PAINFUL display problems.
1010     void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
1011     {
1012         // empty by default, meaning this UIElement does not draw on the Raw layer
1013     }
1014 
1015     /// PBR layer draw method. This gives you 3 surfaces cropped by  _position for drawing.
1016     /// Note that you are not forced to draw all to the surfaces at all, in which case the
1017     /// below `UIElement` will be displayed.
1018     /// 
1019     /// `UIElement` are drawn by increasing z-order, or lexical order if lack thereof.
1020     /// Those elements who have non-overlapping `_position` are drawn in parallel.
1021     /// Hence you CAN'T draw outside `_position` and receive cropped surfaces.
1022     /// `diffuseMap`, `depthMap` and `materialMap` are made to span _position exactly.
1023     ///
1024     /// IMPORTANT: you MUST NOT draw outside `dirtyRects`. This allows more fine-grained updates.
1025     /// A `UIElement` that doesn't respect dirtyRects will have PAINFUL display problems.
1026     void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects)
1027     {
1028         // defaults to filling with a grey pattern
1029         RGBA darkGrey = RGBA(100, 100, 100, 0);
1030         RGBA lighterGrey = RGBA(150, 150, 150, 0);
1031 
1032         foreach(dirtyRect; dirtyRects)
1033         {
1034             for (int y = dirtyRect.min.y; y < dirtyRect.max.y; ++y)
1035             {
1036                 L16[] depthScan = depthMap.scanline(y);
1037                 RGBA[] diffuseScan = diffuseMap.scanline(y);
1038                 RGBA[] materialScan = materialMap.scanline(y);
1039                 for (int x = dirtyRect.min.x; x < dirtyRect.max.x; ++x)
1040                 {
1041                     diffuseScan.ptr[x] = ( (x >> 3) ^  (y >> 3) ) & 1 ? darkGrey : lighterGrey;
1042                     depthScan.ptr[x] = L16(defaultDepth);
1043                     materialScan.ptr[x] = RGBA(defaultRoughness, defaultMetalnessDielectric, defaultSpecular, 255);
1044                 }
1045             }
1046         }
1047     }
1048 
1049     /// Called periodically for every `UIElement`.
1050     /// Override this to create animations.
1051     /// Using setDirty there allows to redraw an element continuously (like a meter or an animated object).
1052     /// Warning: Summing `dt` will not lead to a time that increase like `time`.
1053     ///          `time` can go backwards if the window was reopen.
1054     ///          `time` is guaranteed to increase as fast as system time but is not synced to audio time.
1055     void onAnimate(double dt, double time)
1056     {
1057     }
1058 
1059     /// Parent element.
1060     /// Following this chain gets to the root element.
1061     UIElement _parent = null;
1062 
1063     /// Position is the graphical extent of the element, or something larger.
1064     /// An `UIElement` is not allowed though to draw further than its _position.
1065     /// For efficiency it's best to keep `_position` as small as feasible.
1066     /// This is an absolute "world" positioning data, that doesn't depend on the parent's position.
1067     box2i _position;
1068 
1069     /// The list of children UI elements.
1070     Vec!UIElement _children;
1071 
1072     /// Flags, for now immutable
1073     immutable(uint) _flags;
1074 
1075     /// Higher z-order = above other `UIElement`.
1076     /// By default, every `UIElement` have the same z-order.
1077     /// Because the sort is stable, tree traversal order is the default order (depth first).
1078     /// The children added last with `addChild` is considered above its siblings if you don't have legacyZOrder.
1079     int _zOrder = 0;
1080 
1081 private:
1082 
1083     /// Reference to owning context.
1084     UIContext _context;
1085 
1086     // <visibility privates>
1087 
1088     /// If _visibleFlag is false, neither the Element nor its children are drawn.
1089     /// Each UIElement starts its life being visible.
1090     bool _visibleFlag = true;
1091 
1092     /// Final visibility value, cached in order to set rectangles dirty.
1093     /// It is always up to date across the whole UI tree.
1094     bool _visibilityStatus = true;
1095 
1096     void recomputeVisibilityStatus(bool parentVisibilityStatus)
1097     {
1098         bool newVisibleStatus = _visibleFlag && parentVisibilityStatus;
1099 
1100         // has it changed in any way?
1101         if (newVisibleStatus != _visibilityStatus)
1102         {
1103             _visibilityStatus = newVisibleStatus;
1104 
1105             // Dirty the widget position
1106             setDirtyWhole();
1107 
1108             // Must inform children of the new status of parent.
1109             foreach(child; _children[])
1110                 child.recomputeVisibilityStatus(newVisibleStatus);
1111         }
1112     }
1113 
1114     // </visibility privates>
1115 
1116     /// Dirty rectangles buffer, cropped to _position.
1117     Vec!box2i _localRectsBuf;
1118 
1119     /// Sorted children in Z-lexical-order (sorted by Z, or else increasing index in _children).
1120     Vec!UIElement _zOrderedChildren;
1121 
1122     /// The mouse cursor to display when this element is being dragged
1123     MouseCursor _cursorWhenDragged = MouseCursor.pointer;
1124 
1125     /// The mouse cursor to display when this element is being moused over
1126     MouseCursor _cursorWhenMouseOver = MouseCursor.pointer;
1127 
1128     /// Identifier storage.
1129     char[maxUIElementIDLength+1] _idStorage;
1130 
1131     /// Warning: if you store objects here, keep in mind they won't get destroyed automatically.
1132     /// 4 user pointer in case you'd like to store things in UIElement as a Dplug extension.
1133     /// id 0..1 are reserved for Wren support.
1134     /// id 2..3 are reserved for future Dplug extensions.
1135     /// id 4..7 are for vendor-specific extensions.
1136     void*[8] _userPointers; // Opaque pointers for Wren VM and things.
1137 
1138     // Sort children in ascending z-order
1139     // Input: unsorted _children
1140     // Output: sorted _zOrderedChildren
1141     // This is not thread-safe.
1142     // Only one widget in the same UI can sort its children at once, since it uses
1143     // a UIContext buffer to do so.
1144     final void recomputeZOrderedChildren()
1145     {
1146         // Get a z-ordered list of childrens
1147         _zOrderedChildren.clearContents();
1148 
1149         /// See: https://github.com/AuburnSounds/Dplug/issues/652
1150         version(legacyZOrder)
1151         {
1152             foreach(child; _children[])
1153                 _zOrderedChildren.pushBack(child);
1154         }
1155         else
1156         {
1157             // Adding children in reverse, since children added last are considered having a higher Z order.
1158             foreach_reverse(child; _children[])
1159                 _zOrderedChildren.pushBack(child);
1160         }
1161 
1162         timSort!UIElement(_zOrderedChildren[],
1163                             context.sortingScratchBuffer(),
1164                             (a, b) nothrow @nogc 
1165                             {
1166                                 if (a.zOrder < b.zOrder) return 1;
1167                                 else if (a.zOrder > b.zOrder) return -1;
1168                                 else return 0;
1169                             });
1170 
1171     }
1172 
1173     final void addDirtyRect(box2i rect, UILayer layer)
1174     {
1175         final switch(layer)
1176         {
1177             case UILayer.guessFromFlags:
1178                 if (drawsToPBR())
1179                 {
1180                     // Note: even if one UIElement draws to both Raw and PBR layers, we are not 
1181                     // adding this rectangle in `dirtyListRaw` since the Raw layer is automatically
1182                     // updated when the PBR layer below is.
1183                     _context.dirtyListPBR.addRect(rect); 
1184                 }
1185                 else if (drawsToRaw())
1186                 {
1187                     _context.dirtyListRaw.addRect(rect);
1188                 }
1189                 break;
1190 
1191             case UILayer.rawOnly:
1192                 _context.dirtyListRaw.addRect(rect); 
1193                 break;
1194 
1195             case UILayer.allLayers:
1196                 // This will lead the Raw layer to be invalidated too
1197                 _context.dirtyListPBR.addRect(rect);
1198                 break;
1199         }
1200     }
1201 }
1202 
1203 version(legacyMouseOver)
1204 {
1205     static assert(false, "legacyMouseOver was removed in Dplug v13. Please see Release Notes.");
1206 }