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