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 moduledplug.gui.context;
9 10 importcore.stdc.string : strcmp;
11 12 importdplug.core.vec;
13 importdplug.core.nogc;
14 importdplug.core.thread;
15 16 importdplug.window.window;
17 18 importdplug.graphics.font;
19 importdplug.graphics.mipmap;
20 importdplug.graphics.resizer;
21 22 importdplug.gui.element;
23 importdplug.gui.boxlist;
24 importdplug.gui.graphics;
25 importdplug.gui.sizeconstraints;
26 importdplug.gui.profiler;
27 28 29 /// Work in progress. An ensemble of calls `UIElement` are allowed to make, that30 /// concern the whole UI.31 /// Whenever an API call makes sense globally for usage in an `UIelement`, it should be moved to `IUIContext`.32 interfaceIUIContext33 {
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 floatgetUIScale();
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 floatgetUserScale();
44 45 /// Get default size of the UI, at creation time, in user pixels.46 vec2igetDefaultUISizeInPixels();
47 48 /// Get default width of the UI, at creation time, in user pixels.49 intgetDefaultUIWidth();
50 51 /// Get default width of the UI, at creation time, in user pixels.52 intgetDefaultUIHeight();
53 54 /// Get current size of the UI, in user pixels.55 vec2igetUISizeInPixelsUser();
56 57 /// Get current size of the UI, in logical pixels.58 vec2igetUISizeInPixelsLogical();
59 60 /// Get current size of the UI, in physical pixels.61 vec2igetUISizeInPixelsPhysical();
62 63 /// Trigger a resize of the plugin window. This isn't guaranteed to succeed.64 boolrequestUIResize(intwidthLogicalPixels, intheightLogicalPixels);
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 voidrequestUIScreenshot();
70 71 /// Find the nearest valid _logical_ UI size.72 /// Given an input size, get the nearest valid size.73 voidgetUINearestValidSize(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 boolisUIResizable();
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 IProfilerprofiler();
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 voidsetUserPointer(intpointerID, 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(intpointerID);
115 116 /// Get root element of the hierarchy.117 UIElementgetRootElement();
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 UIElementgetElementById(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 immediately128 /// refocus that widget.129 voidlooseFocus();
130 }
131 132 // Official dplug:gui optional extension.133 enumUICONTEXT_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 structScriptExport140 {
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 structScriptProperty145 {
146 }
147 148 // </wren-specific part>149 150 /// UIContext contains the "globals" of the UI.151 /// It also provides additional APIs for `UIElement`.152 classUIContext : IUIContext153 {
154 public:
155 nothrow:
156 @nogc:
157 this(GUIGraphicsowner)
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 finaloverridefloatgetUIScale()
176 {
177 return_owner.getUIScale();
178 }
179 180 finaloverridefloatgetUserScale()
181 {
182 return_owner.getUserScale();
183 }
184 185 finaloverridevec2igetDefaultUISizeInPixels()
186 {
187 return_owner.getDefaultUISizeInPixels();
188 }
189 190 finaloverrideintgetDefaultUIWidth()
191 {
192 returngetDefaultUISizeInPixels().x;
193 }
194 195 finaloverrideintgetDefaultUIHeight()
196 {
197 returngetDefaultUISizeInPixels().y;
198 }
199 200 finaloverridevec2igetUISizeInPixelsUser()
201 {
202 return_owner.getUISizeInPixelsUser();
203 }
204 205 finaloverridevec2igetUISizeInPixelsLogical()
206 {
207 return_owner.getUISizeInPixelsLogical();
208 }
209 210 finaloverridevec2igetUISizeInPixelsPhysical()
211 {
212 return_owner.getUISizeInPixelsLogical();
213 }
214 215 finaloverrideboolrequestUIResize(intwidthLogicalPixels, intheightLogicalPixels)
216 {
217 return_owner.requestUIResize(widthLogicalPixels, heightLogicalPixels);
218 }
219 220 finaloverridevoidrequestUIScreenshot()
221 {
222 _owner.requestUIScreenshot();
223 }
224 225 finaloverridevoidgetUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels)
226 {
227 _owner.getUINearestValidSize(widthLogicalPixels, heightLogicalPixels);
228 }
229 230 finaloverrideboolisUIResizable()
231 {
232 return_owner.isUIResizable();
233 }
234 235 finaloverrideImageResizer* globalImageResizer()
236 {
237 return &_globalResizer;
238 }
239 240 finaloverrideThreadPool* globalThreadPool()
241 {
242 return &_owner._threadPool;
243 }
244 245 finaloverrideIProfilerprofiler()
246 {
247 return_profiler;
248 }
249 250 /// Last clicked element.251 UIElementfocused = null;
252 253 /// Currently dragged element.254 UIElementdragged = null;
255 256 /// Currently mouse-over'd element.257 UIElementmouseOver = 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 DirtyRectListdirtyListPBR;
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 DirtyRectListdirtyListRaw;
267 268 finalvoidsetMouseOver(UIElementelem)
269 {
270 UIElementold = this.mouseOver;
271 UIElementnew_ = elem;
272 if (oldisnew_)
273 return;
274 275 if (old !isnull)
276 old.onMouseExit();
277 this.mouseOver = new_;
278 if (new_ !isnull)
279 new_.onMouseEnter();
280 }
281 282 finalvoidsetFocused(UIElementfocused)
283 {
284 UIElementold = this.focused;
285 UIElementnew_ = focused;
286 if (oldisnew_)
287 return;
288 289 this.focused = new_;
290 if (old !isnull)
291 old.onFocusExit();
292 if (new_ !isnull)
293 new_.onFocusEnter();
294 }
295 296 finalvoidbeginDragging(UIElementelement)
297 {
298 // Stop an existing dragging operation.299 stopDragging();
300 301 version(legacyMouseDrag)
302 {}
303 else304 {
305 setMouseOver(element);
306 assert(this.mouseOveriselement);
307 }
308 dragged = element;
309 dragged.onBeginDrag();
310 }
311 312 finalvoidstopDragging()
313 {
314 if (dragged !isnull)
315 {
316 version(legacyMouseDrag)
317 {}
318 else319 {
320 assert(this.mouseOverisdragged);
321 }
322 dragged.onStopDrag();
323 dragged = null;
324 }
325 }
326 327 finalMouseCursorgetCurrentMouseCursor()
328 {
329 MouseCursorcursor = MouseCursor.pointer;
330 331 if (!(mouseOverisnull))
332 {
333 cursor = mouseOver.cursorWhenMouseOver();
334 }
335 336 if(!(draggedisnull))
337 {
338 cursor = dragged.cursorWhenDragged();
339 }
340 341 returncursor;
342 }
343 344 finaloverridevoid* getUserPointer(intpointerID)
345 {
346 return_userPointers[pointerID];
347 }
348 349 finaloverridevoidsetUserPointer(intpointerID, void* userPointer)
350 {
351 _userPointers[pointerID] = userPointer;
352 }
353 354 finaloverrideUIElementgetRootElement()
355 {
356 return_owner;
357 }
358 359 finaloverrideUIElementgetElementById(const(char)* id)
360 {
361 if (idisnull)
362 returnnull;
363 364 // special value "__ROOT__"365 if (strcmp("__ROOT__", id) == 0)
366 returngetRootElement();
367 368 // search in whole UI hierarchy369 return_owner.getElementById(id);
370 }
371 372 finalrefVec!UIElementsortingScratchBuffer()
373 {
374 return_sortingscratchBuffer;
375 }
376 377 finaloverridevoidlooseFocus()
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