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