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(futureMouseDrag) 294 { 295 setMouseOver(element); 296 assert(this.mouseOver is element); 297 } 298 dragged = element; 299 dragged.onBeginDrag(); 300 } 301 302 final void stopDragging() 303 { 304 if (dragged !is null) 305 { 306 version(futureMouseDrag) 307 { 308 assert(this.mouseOver is dragged); 309 } 310 dragged.onStopDrag(); 311 dragged = null; 312 } 313 } 314 315 final MouseCursor getCurrentMouseCursor() 316 { 317 MouseCursor cursor = MouseCursor.pointer; 318 319 if (!(mouseOver is null)) 320 { 321 cursor = mouseOver.cursorWhenMouseOver(); 322 } 323 324 if(!(dragged is null)) 325 { 326 cursor = dragged.cursorWhenDragged(); 327 } 328 329 return cursor; 330 } 331 332 final override void* getUserPointer(int pointerID) 333 { 334 return _userPointers[pointerID]; 335 } 336 337 final override void setUserPointer(int pointerID, void* userPointer) 338 { 339 _userPointers[pointerID] = userPointer; 340 } 341 342 final override UIElement getRootElement() 343 { 344 return _owner; 345 } 346 347 final override UIElement getElementById(const(char)* id) 348 { 349 if (id is null) 350 return null; 351 352 // special value "__ROOT__" 353 if (strcmp("__ROOT__", id) == 0) 354 return getRootElement(); 355 356 // search in whole UI hierarchy 357 return _owner.getElementById(id); 358 } 359 360 final ref Vec!UIElement sortingScratchBuffer() 361 { 362 return _sortingscratchBuffer; 363 } 364 365 private: 366 GUIGraphics _owner; 367 368 ImageResizer _globalResizer; 369 370 /// A UI-global scratch buffer used as intermediate buffer for sorting UIElement. 371 Vec!UIElement _sortingscratchBuffer; 372 373 /// Warning: if you store objects here, keep in mind they won't get destroyed automatically. 374 /// 16 user pointer in case you'd like to store things in UIContext as a Dplug extension. 375 /// id 0..7 are reserved for future Dplug extensions. 376 /// id 8..15 are for vendor-specific extensions. 377 void*[16] _userPointers; // Opaque pointer for Wren VM and things. 378 379 IProfiler _profiler; 380 } 381