1 /**
2 * `UIContext` holds global state for the whole UI (current selected widget, etc...).
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.context;
9 
10 import core.stdc.string : strcmp;
11 
12 import dplug.core.vec;
13 import dplug.core.nogc;
14 import dplug.core.thread;
15 
16 import dplug.window.window;
17 
18 import dplug.graphics.font;
19 import dplug.graphics.mipmap;
20 import dplug.graphics.resizer;
21 
22 import dplug.gui.element;
23 import dplug.gui.boxlist;
24 import dplug.gui.graphics;
25 import dplug.gui.sizeconstraints;
26 import dplug.gui.profiler;
27 
28 
29 /// Work in progress. An ensemble of calls `UIElement` are allowed to make, that
30 /// concern the whole UI.
31 /// Whenever an API call makes sense globally for usage in an `UIelement`, it should be moved to `IUIContext`.
32 interface IUIContext
33 {
34 nothrow @nogc:
35 
36     /// Returns: Current number of physical pixels for one logical pixel.
37     /// There is currently no support for this in Dplug, so it is always 1.0f for now.
38     /// The OS _might_ upscale the UI without our knowledge though.
39     float getUIScale();
40 
41     /// Returns: Current number of user pixels for one logical pixel.
42     /// There is currently no user area resize in Dplug, so it is always 1.0f for now.
43     float getUserScale();
44 
45     /// Get default size of the UI, at creation time, in user pixels.
46     vec2i getDefaultUISizeInPixels();
47 
48     /// Get default width of the UI, at creation time, in user pixels.
49     int getDefaultUIWidth();
50 
51     /// Get default width of the UI, at creation time, in user pixels.
52     int getDefaultUIHeight();
53 
54     /// Get current size of the UI, in user pixels.
55     vec2i getUISizeInPixelsUser();
56 
57     /// Get current size of the UI, in logical pixels.
58     vec2i getUISizeInPixelsLogical();
59 
60     /// Get current size of the UI, in physical pixels.
61     vec2i getUISizeInPixelsPhysical();
62 
63     /// Trigger a resize of the plugin window. This isn't guaranteed to succeed.
64     bool requestUIResize(int widthLogicalPixels, int heightLogicalPixels);
65 
66     /// Find the nearest valid _logical_ UI size.
67     /// Given an input size, get the nearest valid size.
68     void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels);
69 
70     /// Returns: `true` if the UI can accomodate several size in _logical_ space.
71     ///          (be it by resizing the user area, or rescaling it).
72     /// Technically all sizes are supported with black borders or cropping in logical space,
73     /// but they don't have to be encouraged if the plugin declares no support for it.
74     bool isUIResizable();
75 
76     /// A shared image resizer to be used in `reflow()` of element.
77     /// Resizing using dplug:graphics use a lot of memory, 
78     /// so it can be better if this is a shared resource.
79     /// It is lazily constructed.
80     /// See_also: `ImageResizer`.
81     /// Note: do not use this resizer concurrently (in `onDrawRaw`, `onDrawPBR`, etc.)
82     ///       unless you have `flagDrawAloneRaw` or `flagDrawAlonePBR`.
83     ///       Usually intended for `reflow()`.
84     ImageResizer* globalImageResizer();
85 
86     /// A shared threadpool, used to draw widgets concurrently.
87     /// NEW: A widget can opt to be drawn alone, and use the threadpool for its own drawing itself.
88     /// Can ONLY be called from `onDrawRaw` AND when the flag `flagDrawAloneRaw` is used, 
89     ///                 or from `onDrawPBR` AND when the flag `flagDrawAlonePBR` is used.
90     ThreadPool* globalThreadPool();
91 
92     /// Returns a UI-wide profiler that records UI performance, as long as Dplug_ProfileUI version is 
93     /// defined. Else, it is a null IProfiler that forgets everything.
94     /// For performance purpose, it is recommended:
95     /// 1. not to record profile if Dplug_ProfileUI is not defined, 
96     /// 2. and undefine Dplug_ProfileUI if you're not looking for a bottleneck.
97     /// See_also: 
98     IProfiler profiler();
99 
100     /// Store an user-defined pointer globally for the UI. This is useful to implement an optional extension to dplug:gui.
101     /// id 0..7 are reserved for future Dplug extensions.
102     /// id 8..15 are for vendor-specific extensions.
103     /// Warning: if you store an object here, keep in mind they won't get destroyed automatically.
104     void setUserPointer(int pointerID, void* userPointer);
105 
106     /// Get an user-defined pointer stored globally for the UI. This is useful to implement an optional extension to dplug:gui.
107     /// id 0..7 are reserved for future Dplug extensions.
108     /// id 8..15 are for vendor-specific extensions.
109     void* getUserPointer(int pointerID);
110 
111     /// Get root element of the hierarchy.
112     UIElement getRootElement();
113 
114     /// Get the first `UIElement` with the given ID, or `null`. This just checks for exact id matches, without anything fancy.
115     /// If you use `dplug:wren-support`, this is called by the `$` operator or the `UI.getElementById`.
116     UIElement getElementById(const(char)* id);
117 }
118 
119 // Official dplug:gui optional extension.
120 enum UICONTEXT_POINTERID_WREN_SUPPORT = 0; /// Official dplug:gui Wren extension. Wren state needs to be stored globally for the UI.
121 
122 // <wren-specific part>
123 // See Wiki for how to enable scripting.
124 
125 /// For a UIElement-derived class, this UDA means its members need to be inspected for registering properties to the script engine.
126 struct ScriptExport
127 {
128 }
129 
130 /// For a member of a @ScriptExport class, this UDA means the member can is a property to be modified by script (read and write).
131 struct ScriptProperty
132 {
133 }
134 
135 // </wren-specific part>
136 
137 /// UIContext contains the "globals" of the UI.
138 /// It also provides additional APIs for `UIElement`.
139 class UIContext : IUIContext
140 {
141 public:
142 nothrow:
143 @nogc:
144     this(GUIGraphics owner)
145     {
146         this._owner = owner;
147         dirtyListPBR = makeDirtyRectList();
148         dirtyListRaw = makeDirtyRectList();
149         _sortingscratchBuffer = makeVec!UIElement();
150 
151         version(Dplug_ProfileUI)
152         {
153             _profiler = createProfiler();
154         }
155     }
156 
157     ~this()
158     {
159         destroyProfiler(_profiler);
160     }
161 
162     final override float getUIScale()
163     {
164         return _owner.getUIScale();
165     }
166 
167     final override float getUserScale()
168     {
169         return _owner.getUserScale();
170     }
171 
172     final override vec2i getDefaultUISizeInPixels()
173     {
174         return _owner.getDefaultUISizeInPixels();
175     }
176 
177     final override int getDefaultUIWidth()
178     {
179         return getDefaultUISizeInPixels().x;
180     }
181 
182     final override int getDefaultUIHeight()
183     {
184         return getDefaultUISizeInPixels().y;
185     }
186 
187     final override vec2i getUISizeInPixelsUser()
188     {
189         return _owner.getUISizeInPixelsUser();
190     }
191 
192     final override vec2i getUISizeInPixelsLogical()
193     {
194         return _owner.getUISizeInPixelsLogical();
195     }
196 
197     final override vec2i getUISizeInPixelsPhysical()
198     {
199         return _owner.getUISizeInPixelsLogical();
200     }
201 
202     final override bool requestUIResize(int widthLogicalPixels, int heightLogicalPixels)
203     {
204         return _owner.requestUIResize(widthLogicalPixels, heightLogicalPixels);
205     }
206 
207     final override void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels)
208     {
209         _owner.getUINearestValidSize(widthLogicalPixels, heightLogicalPixels);
210     }
211 
212     final override bool isUIResizable()
213     {
214         return _owner.isUIResizable();
215     }
216 
217     final override ImageResizer* globalImageResizer()
218     {
219         return &_globalResizer;
220     }
221 
222     final override ThreadPool* globalThreadPool()
223     {
224         return &_owner._threadPool;
225     }
226 
227     final override IProfiler profiler()
228     {
229         return _profiler;
230     }
231 
232     /// Last clicked element.
233     UIElement focused = null;
234 
235     /// Currently dragged element.
236     UIElement dragged = null;
237 
238     /// Currently mouse-over'd element.
239     UIElement mouseOver = null;
240 
241     // This is the UI-global, disjointed list of rectangles that need updating at the PBR level.
242     // Every UIElement touched by those rectangles will have their `onDrawPBR` and `onDrawRaw` 
243     // callbacks called successively.
244     DirtyRectList dirtyListPBR;
245 
246     // This is the UI-global, disjointed list of rectangles that need updating at the Raw level.
247     // Every UIElement touched by those rectangles will have its `onDrawRaw` callback called.
248     DirtyRectList dirtyListRaw;
249 
250     final void setMouseOver(UIElement elem)
251     {
252         UIElement old = this.mouseOver;
253         UIElement new_ = elem;
254         if (old is new_)
255             return;
256 
257         if (old !is null)
258             old.onMouseExit();
259         this.mouseOver = new_;
260         if (new_ !is null)
261             new_.onMouseEnter();
262     }
263 
264     final void setFocused(UIElement focused)
265     {
266         UIElement old = this.focused;
267         UIElement new_ = focused;
268         if (old is new_)
269             return;
270 
271         this.focused = new_;
272         if (old !is null)
273             old.onFocusExit();
274         if (new_ !is null)
275             new_.onFocusEnter();
276     }
277 
278     final void beginDragging(UIElement element)
279     {
280         // Stop an existing dragging operation.
281         stopDragging();
282 
283         version(futureMouseDrag)
284         {
285             setMouseOver(element);
286             assert(this.mouseOver is element);
287         }
288         dragged = element;
289         dragged.onBeginDrag();
290     }
291 
292     final void stopDragging()
293     {
294         if (dragged !is null)
295         {
296             version(futureMouseDrag)
297             {
298                 assert(this.mouseOver is dragged);
299             }
300             dragged.onStopDrag();
301             dragged = null;
302         }
303     }
304 
305     final MouseCursor getCurrentMouseCursor()
306     {
307         MouseCursor cursor = MouseCursor.pointer;
308 
309         if (!(mouseOver is null))
310         {
311             cursor = mouseOver.cursorWhenMouseOver();
312         }
313 
314         if(!(dragged is null))
315         {
316             cursor = dragged.cursorWhenDragged();
317         }        
318 
319         return cursor;
320     }
321 
322     final override void* getUserPointer(int pointerID)
323     {
324         return _userPointers[pointerID];
325     }
326 
327     final override void setUserPointer(int pointerID, void* userPointer)
328     {
329         _userPointers[pointerID] = userPointer;
330     }
331 
332     final override UIElement getRootElement()
333     {
334         return _owner;
335     }
336 
337     final override UIElement getElementById(const(char)* id)
338     {
339         if (id is null)
340             return null;
341 
342         // special value "__ROOT__"
343         if (strcmp("__ROOT__", id) == 0)
344             return getRootElement();
345 
346         // search in whole UI hierarchy
347         return _owner.getElementById(id);
348     }
349 
350     final ref Vec!UIElement sortingScratchBuffer()
351     {
352         return _sortingscratchBuffer;
353     }    
354 
355 private:
356     GUIGraphics _owner;
357 
358     ImageResizer _globalResizer;
359 
360     /// A UI-global scratch buffer used as intermediate buffer for sorting UIElement.
361     Vec!UIElement _sortingscratchBuffer;
362 
363     /// Warning: if you store objects here, keep in mind they won't get destroyed automatically.
364     /// 16 user pointer in case you'd like to store things in UIContext as a Dplug extension.
365     /// id 0..7 are reserved for future Dplug extensions.
366     /// id 8..15 are for vendor-specific extensions.
367     void*[16] _userPointers; // Opaque pointer for Wren VM and things.
368 
369     IProfiler _profiler;
370 }
371