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