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     /// If one `UIElement` was focused, loose that focus.
124     /// Allows to loose focus from a widget callback.
125     /// To be effective from a mouse click, you also need 
126     /// to return `Click.handledNoFocus`, since 
127     /// `Click.handled` and `Click.startDrag` would immediately
128     /// refocus that widget.
129     void looseFocus();
130 }
131 
132 // Official dplug:gui optional extension.
133 enum UICONTEXT_POINTERID_WREN_SUPPORT = 0; /// Official dplug:gui Wren extension. Wren state needs to be stored globally for the UI.
134 
135 // <wren-specific part>
136 // See Wiki for how to enable scripting.
137 
138 /// For a UIElement-derived class, this UDA means its members need to be inspected for registering properties to the script engine.
139 struct ScriptExport
140 {
141 }
142 
143 /// For a member of a @ScriptExport class, this UDA means the member can is a property to be modified by script (read and write).
144 struct ScriptProperty
145 {
146 }
147 
148 // </wren-specific part>
149 
150 /// UIContext contains the "globals" of the UI.
151 /// It also provides additional APIs for `UIElement`.
152 class UIContext : IUIContext
153 {
154 public:
155 nothrow:
156 @nogc:
157     this(GUIGraphics owner)
158     {
159         this._owner = owner;
160         dirtyListPBR = makeDirtyRectList();
161         dirtyListRaw = makeDirtyRectList();
162         _sortingscratchBuffer = makeVec!UIElement();
163 
164         version(Dplug_ProfileUI)
165         {
166             _profiler = createProfiler();
167         }
168     }
169 
170     ~this()
171     {
172         destroyProfiler(_profiler);
173     }
174 
175     final override float getUIScale()
176     {
177         return _owner.getUIScale();
178     }
179 
180     final override float getUserScale()
181     {
182         return _owner.getUserScale();
183     }
184 
185     final override vec2i getDefaultUISizeInPixels()
186     {
187         return _owner.getDefaultUISizeInPixels();
188     }
189 
190     final override int getDefaultUIWidth()
191     {
192         return getDefaultUISizeInPixels().x;
193     }
194 
195     final override int getDefaultUIHeight()
196     {
197         return getDefaultUISizeInPixels().y;
198     }
199 
200     final override vec2i getUISizeInPixelsUser()
201     {
202         return _owner.getUISizeInPixelsUser();
203     }
204 
205     final override vec2i getUISizeInPixelsLogical()
206     {
207         return _owner.getUISizeInPixelsLogical();
208     }
209 
210     final override vec2i getUISizeInPixelsPhysical()
211     {
212         return _owner.getUISizeInPixelsLogical();
213     }
214 
215     final override bool requestUIResize(int widthLogicalPixels, int heightLogicalPixels)
216     {
217         return _owner.requestUIResize(widthLogicalPixels, heightLogicalPixels);
218     }
219 
220     final override void requestUIScreenshot()
221     {
222         _owner.requestUIScreenshot();
223     }
224 
225     final override void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels)
226     {
227         _owner.getUINearestValidSize(widthLogicalPixels, heightLogicalPixels);
228     }
229 
230     final override bool isUIResizable()
231     {
232         return _owner.isUIResizable();
233     }
234 
235     final override ImageResizer* globalImageResizer()
236     {
237         return &_globalResizer;
238     }
239 
240     final override ThreadPool* globalThreadPool()
241     {
242         return &_owner._threadPool;
243     }
244 
245     final override IProfiler profiler()
246     {
247         return _profiler;
248     }
249 
250     /// Last clicked element.
251     UIElement focused = null;
252 
253     /// Currently dragged element.
254     UIElement dragged = null;
255 
256     /// Currently mouse-over'd element.
257     UIElement mouseOver = null;
258 
259     // This is the UI-global, disjointed list of rectangles that need updating at the PBR level.
260     // Every UIElement touched by those rectangles will have their `onDrawPBR` and `onDrawRaw` 
261     // callbacks called successively.
262     DirtyRectList dirtyListPBR;
263 
264     // This is the UI-global, disjointed list of rectangles that need updating at the Raw level.
265     // Every UIElement touched by those rectangles will have its `onDrawRaw` callback called.
266     DirtyRectList dirtyListRaw;
267 
268     final void setMouseOver(UIElement elem)
269     {
270         UIElement old = this.mouseOver;
271         UIElement new_ = elem;
272         if (old is new_)
273             return;
274 
275         if (old !is null)
276             old.onMouseExit();
277         this.mouseOver = new_;
278         if (new_ !is null)
279             new_.onMouseEnter();
280     }
281 
282     final void setFocused(UIElement focused)
283     {
284         UIElement old = this.focused;
285         UIElement new_ = focused;
286         if (old is new_)
287             return;
288 
289         this.focused = new_;
290         if (old !is null)
291             old.onFocusExit();
292         if (new_ !is null)
293             new_.onFocusEnter();
294     }
295 
296     final void beginDragging(UIElement element)
297     {
298         // Stop an existing dragging operation.
299         stopDragging();
300 
301         version(legacyMouseDrag)
302         {}
303         else
304         {
305             setMouseOver(element);
306             assert(this.mouseOver is element);
307         }
308         dragged = element;
309         dragged.onBeginDrag();
310     }
311 
312     final void stopDragging()
313     {
314         if (dragged !is null)
315         {
316             version(legacyMouseDrag)
317             {}
318             else
319             {
320                 assert(this.mouseOver is dragged);
321             }
322             dragged.onStopDrag();
323             dragged = null;
324         }
325     }
326 
327     final MouseCursor getCurrentMouseCursor()
328     {
329         MouseCursor cursor = MouseCursor.pointer;
330 
331         if (!(mouseOver is null))
332         {
333             cursor = mouseOver.cursorWhenMouseOver();
334         }
335 
336         if(!(dragged is null))
337         {
338             cursor = dragged.cursorWhenDragged();
339         }        
340 
341         return cursor;
342     }
343 
344     final override void* getUserPointer(int pointerID)
345     {
346         return _userPointers[pointerID];
347     }
348 
349     final override void setUserPointer(int pointerID, void* userPointer)
350     {
351         _userPointers[pointerID] = userPointer;
352     }
353 
354     final override UIElement getRootElement()
355     {
356         return _owner;
357     }
358 
359     final override UIElement getElementById(const(char)* id)
360     {
361         if (id is null)
362             return null;
363 
364         // special value "__ROOT__"
365         if (strcmp("__ROOT__", id) == 0)
366             return getRootElement();
367 
368         // search in whole UI hierarchy
369         return _owner.getElementById(id);
370     }
371 
372     final ref Vec!UIElement sortingScratchBuffer()
373     {
374         return _sortingscratchBuffer;
375     }
376 
377     final override void looseFocus()
378     {
379         setFocused(null);
380     }
381 
382 private:
383     GUIGraphics _owner;
384 
385     ImageResizer _globalResizer;
386 
387     /// A UI-global scratch buffer used as intermediate buffer for sorting UIElement.
388     Vec!UIElement _sortingscratchBuffer;
389 
390     /// Warning: if you store objects here, keep in mind they won't get destroyed automatically.
391     /// 16 user pointer in case you'd like to store things in UIContext as a Dplug extension.
392     /// id 0..7 are reserved for future Dplug extensions.
393     /// id 8..15 are for vendor-specific extensions.
394     void*[16] _userPointers; // Opaque pointer for Wren VM and things.
395 
396     IProfiler _profiler;
397 }
398