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