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 "legacyMouseDrag" version identifier is NOT 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 "legacyMouseDrag" version identifier is NOT 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(legacyMouseDrag)
574         {}
575         else
576         {
577             bool wasDragging = (_context.dragged !is null);
578         }
579 
580         _context.stopDragging();
581 
582         version(legacyMouseDrag)
583         {}
584         else
585         {
586             // Enter widget below mouse if a dragged operation was stopped.
587             if (wasDragging)
588             {
589                 bool foundOver = mouseMove(x, y, 0, 0, mstate, false);
590                 if (!foundOver)
591                     _context.setMouseOver(null);
592             }
593         }
594     }
595 
596     // to be called at top-level when the mouse wheeled
597     final bool mouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate)
598     {
599         recomputeZOrderedChildren();
600 
601         // Test children that are displayed above this element first
602         foreach(child; _zOrderedChildren[])
603         {
604             if (child.zOrder >= zOrder)
605                 if (child.mouseWheel(x, y, wheelDeltaX, wheelDeltaY, mstate))
606                     return true;
607         }
608 
609         bool canBeMouseWheeled = _visibilityStatus; // cannot be mouse-wheeled if invisible
610         if (canBeMouseWheeled && contains(x - _position.min.x, y - _position.min.y))
611         {
612             if (onMouseWheel(x - _position.min.x, y - _position.min.y, wheelDeltaX, wheelDeltaY, mstate))
613                 return true;
614         }
615 
616         // Test children that are displayed below this element last
617         foreach(child; _zOrderedChildren[])
618         {
619             if (child.zOrder < zOrder)
620                 if (child.mouseWheel(x, y, wheelDeltaX, wheelDeltaY, mstate))
621                     return true;
622         }
623 
624         return false;
625     }
626 
627     // To be called when the mouse moved
628     // Returns: `true` if one child has taken the mouse-over role globally.
629     final bool mouseMove(int x, int y, int dx, int dy, MouseState mstate, bool alreadyFoundMouseOver)
630     {
631         recomputeZOrderedChildren();
632 
633         bool foundMouseOver = alreadyFoundMouseOver;
634 
635         // Test children that are displayed above this element first
636         foreach(child; _zOrderedChildren[])
637         {
638             if (child.zOrder >= zOrder)
639             {
640                 bool found = child.mouseMove(x, y, dx, dy, mstate, foundMouseOver);
641                 foundMouseOver = foundMouseOver || found;
642             }
643         }
644 
645         if (isDragged())
646         {
647             // EDIT MODE
648             // In debug mode, dragging with the right mouse button move elements around
649             // and dragging with shift  + right button resize elements around.
650             //
651             // Additionally, if CTRL is pressed, the increments are only -1 or +1 pixel.
652             // 
653             // You can see the _position rectangle thanks to `debugLog`.
654             bool draggingUsed = false;
655             debug
656             {
657                 if (mstate.rightButtonDown && mstate.shiftPressed)
658                 {
659                     if (mstate.ctrlPressed)
660                     {
661                         if (dx < -1) dx = -1;
662                         if (dx >  1) dx =  1;
663                         if (dy < -1) dy = -1;
664                         if (dy >  1) dy =  1;
665                     }
666                     int nx = _position.min.x;
667                     int ny = _position.min.y;
668                     int w = _position.width + dx;
669                     int h = _position.height + dy;
670                     if (w < 5) w = 5;
671                     if (h < 5) h = 5;
672                     position = box2i(nx, ny, nx + w, ny + h);
673                     draggingUsed = true;
674 
675 
676                 }
677                 else if (mstate.rightButtonDown)
678                 {
679                     if (mstate.ctrlPressed)
680                     {
681                         if (dx < -1) dx = -1;
682                         if (dx >  1) dx =  1;
683                         if (dy < -1) dy = -1;
684                         if (dy >  1) dy =  1;
685                     }
686                     int nx = _position.min.x + dx;
687                     int ny = _position.min.y + dy;
688                     if (nx < 0) nx = 0;
689                     if (ny < 0) ny = 0;
690                     position = box2i(nx, ny, nx + position.width, ny + position.height);
691                     draggingUsed = true;
692                 }
693 
694                 // Output the latest position
695                 // This is helpful when developing a plug-in UI.
696                 if (draggingUsed)
697                 {
698                     char[128] buf;
699                     snprintf(buf.ptr, 128, "position = box2i.rectangle(%d, %d, %d, %d)\n", _position.min.x, _position.min.y, _position.width, _position.height);
700                     debugLog(buf.ptr);
701                 }
702             }
703 
704             if (!draggingUsed)
705                 onMouseDrag(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
706         }
707 
708         // Can't be mouse over if not visible.
709         bool canBeMouseOver = _visibilityStatus;
710 
711         version(legacyMouseDrag)
712         {}
713         else
714         {
715             // If dragged, it already received `onMouseDrag`.
716             // if something else is dragged, it can be mouse over.
717             if (_context.dragged !is null)
718                 canBeMouseOver = false;
719         }
720 
721         if (canBeMouseOver && contains(x - _position.min.x, y - _position.min.y)) // FUTURE: something more fine-grained?
722         {
723             // Get the mouse-over crown if not taken
724             if (!foundMouseOver)
725             {
726                 foundMouseOver = true;
727                 _context.setMouseOver(this);
728 
729                 version(legacyMouseDrag)
730                 {}
731                 else
732                 {
733                     onMouseMove(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
734                 }
735             }
736 
737             version(legacyMouseDrag)
738             {
739                 onMouseMove(x - _position.min.x, y - _position.min.y, dx, dy, mstate);
740             }
741         }
742 
743         // Test children that are displayed below this element
744         foreach(child; _zOrderedChildren[])
745         {
746             if (child.zOrder < zOrder)
747             {
748                 bool found = child.mouseMove(x, y, dx, dy, mstate, foundMouseOver);
749                 foundMouseOver = foundMouseOver || found;
750             }
751         }
752         return foundMouseOver;
753     }
754 
755     // to be called at top-level when a key is pressed
756     final bool keyDown(Key key)
757     {
758         if (onKeyDown(key))
759             return true;
760 
761         foreach(child; _children[])
762         {
763             if (child.keyDown(key))
764                 return true;
765         }
766         return false;
767     }
768 
769     // to be called at top-level when a key is released
770     final bool keyUp(Key key)
771     {
772         if (onKeyUp(key))
773             return true;
774 
775         foreach(child; _children[])
776         {
777             if (child.keyUp(key))
778                 return true;
779         }
780         return false;
781     }
782 
783     // To be called at top-level periodically.
784     void animate(double dt, double time)
785     {
786         if (isAnimated)
787             onAnimate(dt, time);
788 
789         // For some rare widgets, it is important that children are animated
790         // _after_ their parent.
791         foreach(child; _children[])
792             child.animate(dt, time);
793     }
794 
795     final UIContext context()
796     {
797         return _context;
798     }
799 
800     /// A widget is "visible" when it has a true visibility flag, and its parent is itself visible.
801     /// Returns: Last computed visibility status.
802     final bool isVisible() pure const
803     {
804         return _visibilityStatus;
805     }
806 
807     /// Get visibility flag of the widget.
808     /// A widget might still be invisible, if one of its parent is not visible.
809     final bool visibility() pure const
810     {
811         return _visibleFlag;
812     }
813 
814     /// Change visibility flag of the widget. This show and hide all children of this UIElement,
815     /// regardless of their position on screen, invalidating their graphics if need be much
816     /// like a position change.
817     final void visibility(bool visible)
818     {
819         if (_visibleFlag == visible)
820             return; // Nothing to do, this wouldn't change any visibility status in sub-tree.
821 
822         _visibleFlag = visible;
823 
824         // Get parent visibility status.
825         bool parentVisibleStatus = (parent !is null) ? parent.isVisible() : true;
826 
827         // Recompute own visibility status.
828         recomputeVisibilityStatus(parentVisibleStatus);
829     }
830 
831     final int zOrder() pure const
832     {
833         return _zOrder;
834     }
835 
836     // TODO: how to depreciate that? Wren will stumble upon every deprecated fields unfortunately.
837     alias setZOrder = zOrder;
838 
839     final void zOrder(int zOrder)
840     {
841         if (_zOrder != zOrder)
842         {
843             setDirtyWhole();
844             _zOrder = zOrder;
845         }
846     }
847 
848     /// Mark this element as wholly dirty.
849     ///
850     /// Params:
851     ///     layer which layers need to be redrawn.
852     ///
853     /// Important: you _can_ call this from the audio thread, HOWEVER it is
854     ///            much more efficient to mark the widget dirty with an atomic 
855     ///            and call `setDirty` in animation callback.
856     void setDirtyWhole(UILayer layer = UILayer.guessFromFlags)
857     {
858         addDirtyRect(_position, layer);
859     }
860 
861     /// Mark a part of the element dirty.
862     /// This part must be a subrect of its _position.
863     ///
864     /// Params:
865     ///     rect = Position of the dirtied rectangle, in widget coordinates.
866     ///
867     /// Important: you could call this from the audio thread, however it is
868     ///            much more efficient to mark the widget dirty with an atomic 
869     ///            and call setDirty in animation callback.
870     void setDirty(box2i rect, UILayer layer = UILayer.guessFromFlags)
871     {
872         /// BUG: it is problematic to allow this from the audio thread,
873         /// because the access to _position isn't protected and it could 
874         /// create a race in case of concurrent reflow(). Puhsed rectangles
875         /// might be out of range, this is workarounded in GUIGraphics currently
876         /// for other reasons.
877         box2i translatedRect = rect.translate(_position.min);
878         assert(_position.contains(translatedRect));
879         addDirtyRect(translatedRect, layer);
880     }  
881 
882     /// Returns: Parent element. `null` if detached or root element.
883     final UIElement parent() pure nothrow @nogc
884     {
885         return _parent;
886     }
887 
888     /// Returns: Top-level parent. `null` if detached or root element.
889     final UIElement topLevelParent() pure nothrow @nogc
890     {
891         if (_parent is null)
892             return this;
893         else
894             return _parent.topLevelParent();
895     }
896 
897     /// Returns: `true` is this element is hovered by the mouse, and 
898     final bool isMouseOver() pure const
899     {
900         version(legacyMouseDrag)
901         {}
902         else
903         {
904             if (_context.mouseOver !is this)
905             {
906                 assert(_context.dragged !is this);
907             }
908         }
909 
910         return _context.mouseOver is this;
911     }
912 
913     final bool isDragged() pure const
914     {
915         version(legacyMouseDrag)
916         {}
917         else
918         {
919             if (_context.dragged is this)
920                 assert(isMouseOver());
921         }
922 
923         return _context.dragged is this;
924     }
925 
926     final bool isFocused() pure const
927     {
928         return _context.focused is this;
929     }
930 
931     final bool drawsToPBR() pure const
932     {
933         return (_flags & flagPBR) != 0;
934     }
935 
936     final bool drawsToRaw() pure const
937     {
938         return (_flags & flagRaw) != 0;
939     }
940 
941     final bool isAnimated() pure const
942     {
943         return (_flags & flagAnimated) != 0;
944     }
945 
946     final bool isDrawAloneRaw() pure const
947     {
948         return (_flags & flagDrawAloneRaw) != 0;
949     }
950 
951     final bool isDrawAlonePBR() pure const
952     {
953         return (_flags & flagDrawAlonePBR) != 0;
954     }
955 
956     /// Appends the Elements that should be drawn, in order.
957     /// You should empty it before calling this function.
958     /// Everything visible get into the draw list, but that doesn't mean they
959     /// will get drawn if they don't overlap with a dirty area.
960     final void getDrawLists(ref Vec!UIElement listRaw, ref Vec!UIElement listPBR)
961     {
962         if (_visibilityStatus)
963         {
964             if (drawsToRaw())
965                 listRaw.pushBack(this);
966 
967             if (drawsToPBR())
968                 listPBR.pushBack(this);
969 
970             // Note: if one widget is not visible, the whole sub-tree can be ignored for drawing.
971             // This is because invisibility is inherited without recourse.
972             foreach(child; _children[])
973                 child.getDrawLists(listRaw, listPBR);
974         }
975     }
976 
977     MouseCursor cursorWhenDragged() 
978     { 
979         return _cursorWhenDragged;
980     }
981 
982     void setCursorWhenDragged(MouseCursor mouseCursor)
983     {
984         _cursorWhenDragged = mouseCursor;
985     }
986 
987     MouseCursor cursorWhenMouseOver()
988     {
989         return _cursorWhenMouseOver;
990     }
991 
992     void setCursorWhenMouseOver(MouseCursor mouseCursor)
993     {
994         _cursorWhenMouseOver = mouseCursor;
995     }
996 
997     /// Get a user pointer. Allow dplug:gui extensions.
998     final void* getUserPointer(int pointerID)
999     {
1000         return _userPointers[pointerID];
1001     }
1002 
1003     /// Set a user pointer. Allow dplug:gui extensions.
1004     final void setUserPointer(int pointerID, void* userPointer)
1005     {
1006         _userPointers[pointerID] = userPointer;
1007     }
1008 
1009 protected:
1010 
1011     /// Raw layer draw method. This gives you 1 surface cropped by  _position for drawing.
1012     /// Note that you are not forced to draw to the surfaces at all.
1013     /// 
1014     /// `UIElement` are drawn by increasing z-order, or lexical order if lack thereof.
1015     /// Those elements who have non-overlapping `_position` are drawn in parallel.
1016     /// Hence you CAN'T draw outside `_position` and receive cropped surfaces.
1017     ///
1018     /// IMPORTANT: you MUST NOT draw outside `dirtyRects`. This allows more fine-grained updates.
1019     /// A `UIElement` that doesn't respect dirtyRects will have PAINFUL display problems.
1020     void onDrawRaw(ImageRef!RGBA rawMap, box2i[] dirtyRects)
1021     {
1022         // empty by default, meaning this UIElement does not draw on the Raw layer
1023     }
1024 
1025     /// PBR layer draw method. This gives you 3 surfaces cropped by  _position for drawing.
1026     /// Note that you are not forced to draw all to the surfaces at all, in which case the
1027     /// below `UIElement` will be displayed.
1028     /// 
1029     /// `UIElement` are drawn by increasing z-order, or lexical order if lack thereof.
1030     /// Those elements who have non-overlapping `_position` are drawn in parallel.
1031     /// Hence you CAN'T draw outside `_position` and receive cropped surfaces.
1032     /// `diffuseMap`, `depthMap` and `materialMap` are made to span _position exactly.
1033     ///
1034     /// IMPORTANT: you MUST NOT draw outside `dirtyRects`. This allows more fine-grained updates.
1035     /// A `UIElement` that doesn't respect dirtyRects will have PAINFUL display problems.
1036     void onDrawPBR(ImageRef!RGBA diffuseMap, ImageRef!L16 depthMap, ImageRef!RGBA materialMap, box2i[] dirtyRects)
1037     {
1038         // defaults to filling with a grey pattern
1039         RGBA darkGrey = RGBA(100, 100, 100, 0);
1040         RGBA lighterGrey = RGBA(150, 150, 150, 0);
1041 
1042         foreach(dirtyRect; dirtyRects)
1043         {
1044             for (int y = dirtyRect.min.y; y < dirtyRect.max.y; ++y)
1045             {
1046                 L16[] depthScan = depthMap.scanline(y);
1047                 RGBA[] diffuseScan = diffuseMap.scanline(y);
1048                 RGBA[] materialScan = materialMap.scanline(y);
1049                 for (int x = dirtyRect.min.x; x < dirtyRect.max.x; ++x)
1050                 {
1051                     diffuseScan.ptr[x] = ( (x >> 3) ^  (y >> 3) ) & 1 ? darkGrey : lighterGrey;
1052                     depthScan.ptr[x] = L16(defaultDepth);
1053                     materialScan.ptr[x] = RGBA(defaultRoughness, defaultMetalnessDielectric, defaultSpecular, 255);
1054                 }
1055             }
1056         }
1057     }
1058 
1059     /// Called periodically for every `UIElement`.
1060     /// Override this to create animations.
1061     /// Using setDirty there allows to redraw an element continuously (like a meter or an animated object).
1062     /// Warning: Summing `dt` will not lead to a time that increase like `time`.
1063     ///          `time` can go backwards if the window was reopen.
1064     ///          `time` is guaranteed to increase as fast as system time but is not synced to audio time.
1065     void onAnimate(double dt, double time)
1066     {
1067     }
1068 
1069     /// Parent element.
1070     /// Following this chain gets to the root element.
1071     UIElement _parent = null;
1072 
1073     /// Position is the graphical extent of the element, or something larger.
1074     /// An `UIElement` is not allowed though to draw further than its _position.
1075     /// For efficiency it's best to keep `_position` as small as feasible.
1076     /// This is an absolute "world" positioning data, that doesn't depend on the parent's position.
1077     box2i _position;
1078 
1079     /// The list of children UI elements.
1080     Vec!UIElement _children;
1081 
1082     /// Flags, for now immutable
1083     immutable(uint) _flags;
1084 
1085     /// Higher z-order = above other `UIElement`.
1086     /// By default, every `UIElement` have the same z-order.
1087     /// Because the sort is stable, tree traversal order is the default order (depth first).
1088     /// The children added last with `addChild` is considered above its siblings if you don't have legacyZOrder.
1089     int _zOrder = 0;
1090 
1091 private:
1092 
1093     /// Reference to owning context.
1094     UIContext _context;
1095 
1096     // <visibility privates>
1097 
1098     /// If _visibleFlag is false, neither the Element nor its children are drawn.
1099     /// Each UIElement starts its life being visible.
1100     bool _visibleFlag = true;
1101 
1102     /// Final visibility value, cached in order to set rectangles dirty.
1103     /// It is always up to date across the whole UI tree.
1104     bool _visibilityStatus = true;
1105 
1106     void recomputeVisibilityStatus(bool parentVisibilityStatus)
1107     {
1108         bool newVisibleStatus = _visibleFlag && parentVisibilityStatus;
1109 
1110         // has it changed in any way?
1111         if (newVisibleStatus != _visibilityStatus)
1112         {
1113             _visibilityStatus = newVisibleStatus;
1114 
1115             // Dirty the widget position
1116             setDirtyWhole();
1117 
1118             // Must inform children of the new status of parent.
1119             foreach(child; _children[])
1120                 child.recomputeVisibilityStatus(newVisibleStatus);
1121         }
1122     }
1123 
1124     // </visibility privates>
1125 
1126     /// Dirty rectangles buffer, cropped to _position.
1127     Vec!box2i _localRectsBuf;
1128 
1129     /// Sorted children in Z-lexical-order (sorted by Z, or else increasing index in _children).
1130     Vec!UIElement _zOrderedChildren;
1131 
1132     /// The mouse cursor to display when this element is being dragged
1133     MouseCursor _cursorWhenDragged = MouseCursor.pointer;
1134 
1135     /// The mouse cursor to display when this element is being moused over
1136     MouseCursor _cursorWhenMouseOver = MouseCursor.pointer;
1137 
1138     /// Identifier storage.
1139     char[maxUIElementIDLength+1] _idStorage;
1140 
1141     /// Warning: if you store objects here, keep in mind they won't get destroyed automatically.
1142     /// 4 user pointer in case you'd like to store things in UIElement as a Dplug extension.
1143     /// id 0..1 are reserved for Wren support.
1144     /// id 2..3 are reserved for future Dplug extensions.
1145     /// id 4..7 are for vendor-specific extensions.
1146     void*[8] _userPointers; // Opaque pointers for Wren VM and things.
1147 
1148     // Sort children in ascending z-order
1149     // Input: unsorted _children
1150     // Output: sorted _zOrderedChildren
1151     // This is not thread-safe.
1152     // Only one widget in the same UI can sort its children at once, since it uses
1153     // a UIContext buffer to do so.
1154     final void recomputeZOrderedChildren()
1155     {
1156         // Get a z-ordered list of childrens
1157         _zOrderedChildren.clearContents();
1158 
1159         /// See: https://github.com/AuburnSounds/Dplug/issues/652
1160         version(legacyZOrder)
1161         {
1162             foreach(child; _children[])
1163                 _zOrderedChildren.pushBack(child);
1164         }
1165         else
1166         {
1167             // Adding children in reverse, since children added last are considered having a higher Z order.
1168             foreach_reverse(child; _children[])
1169                 _zOrderedChildren.pushBack(child);
1170         }
1171 
1172         timSort!UIElement(_zOrderedChildren[],
1173                             context.sortingScratchBuffer(),
1174                             (a, b) nothrow @nogc 
1175                             {
1176                                 if (a.zOrder < b.zOrder) return 1;
1177                                 else if (a.zOrder > b.zOrder) return -1;
1178                                 else return 0;
1179                             });
1180 
1181     }
1182 
1183     final void addDirtyRect(box2i rect, UILayer layer)
1184     {
1185         final switch(layer)
1186         {
1187             case UILayer.guessFromFlags:
1188                 if (drawsToPBR())
1189                 {
1190                     // Note: even if one UIElement draws to both Raw and PBR layers, we are not 
1191                     // adding this rectangle in `dirtyListRaw` since the Raw layer is automatically
1192                     // updated when the PBR layer below is.
1193                     _context.dirtyListPBR.addRect(rect); 
1194                 }
1195                 else if (drawsToRaw())
1196                 {
1197                     _context.dirtyListRaw.addRect(rect);
1198                 }
1199                 break;
1200 
1201             case UILayer.rawOnly:
1202                 _context.dirtyListRaw.addRect(rect); 
1203                 break;
1204 
1205             case UILayer.allLayers:
1206                 // This will lead the Raw layer to be invalidated too
1207                 _context.dirtyListPBR.addRect(rect);
1208                 break;
1209         }
1210     }
1211 }
1212 
1213 version(legacyMouseOver)
1214 {
1215     static assert(false, "legacyMouseOver was removed in Dplug v13. Please see Release Notes.");
1216 }