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 /// Find the nearest valid _logical_ UI size. 67 /// Given an input size, get the nearest valid size. 68 void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels); 69 70 /// Returns: `true` if the UI can accomodate several size in _logical_ space. 71 /// (be it by resizing the user area, or rescaling it). 72 /// Technically all sizes are supported with black borders or cropping in logical space, 73 /// but they don't have to be encouraged if the plugin declares no support for it. 74 bool isUIResizable(); 75 76 /// A shared image resizer to be used in `reflow()` of element. 77 /// Resizing using dplug:graphics use a lot of memory, 78 /// so it can be better if this is a shared resource. 79 /// It is lazily constructed. 80 /// See_also: `ImageResizer`. 81 /// Note: do not use this resizer concurrently (in `onDrawRaw`, `onDrawPBR`, etc.) 82 /// unless you have `flagDrawAloneRaw` or `flagDrawAlonePBR`. 83 /// Usually intended for `reflow()`. 84 ImageResizer* globalImageResizer(); 85 86 /// A shared threadpool, used to draw widgets concurrently. 87 /// NEW: A widget can opt to be drawn alone, and use the threadpool for its own drawing itself. 88 /// Can ONLY be called from `onDrawRaw` AND when the flag `flagDrawAloneRaw` is used, 89 /// or from `onDrawPBR` AND when the flag `flagDrawAlonePBR` is used. 90 ThreadPool* globalThreadPool(); 91 92 /// Returns a UI-wide profiler that records UI performance, as long as Dplug_ProfileUI version is 93 /// defined. Else, it is a null IProfiler that forgets everything. 94 /// For performance purpose, it is recommended: 95 /// 1. not to record profile if Dplug_ProfileUI is not defined, 96 /// 2. and undefine Dplug_ProfileUI if you're not looking for a bottleneck. 97 /// See_also: 98 IProfiler profiler(); 99 100 /// Store an user-defined pointer globally for the UI. This is useful to implement an optional extension to dplug:gui. 101 /// id 0..7 are reserved for future Dplug extensions. 102 /// id 8..15 are for vendor-specific extensions. 103 /// Warning: if you store an object here, keep in mind they won't get destroyed automatically. 104 void setUserPointer(int pointerID, void* userPointer); 105 106 /// Get an user-defined pointer stored globally for the UI. This is useful to implement an optional extension to dplug:gui. 107 /// id 0..7 are reserved for future Dplug extensions. 108 /// id 8..15 are for vendor-specific extensions. 109 void* getUserPointer(int pointerID); 110 111 /// Get root element of the hierarchy. 112 UIElement getRootElement(); 113 114 /// Get the first `UIElement` with the given ID, or `null`. This just checks for exact id matches, without anything fancy. 115 /// If you use `dplug:wren-support`, this is called by the `$` operator or the `UI.getElementById`. 116 UIElement getElementById(const(char)* id); 117 } 118 119 // Official dplug:gui optional extension. 120 enum UICONTEXT_POINTERID_WREN_SUPPORT = 0; /// Official dplug:gui Wren extension. Wren state needs to be stored globally for the UI. 121 122 // <wren-specific part> 123 // See Wiki for how to enable scripting. 124 125 /// For a UIElement-derived class, this UDA means its members need to be inspected for registering properties to the script engine. 126 struct ScriptExport 127 { 128 } 129 130 /// For a member of a @ScriptExport class, this UDA means the member can is a property to be modified by script (read and write). 131 struct ScriptProperty 132 { 133 } 134 135 // </wren-specific part> 136 137 /// UIContext contains the "globals" of the UI. 138 /// It also provides additional APIs for `UIElement`. 139 class UIContext : IUIContext 140 { 141 public: 142 nothrow: 143 @nogc: 144 this(GUIGraphics owner) 145 { 146 this._owner = owner; 147 dirtyListPBR = makeDirtyRectList(); 148 dirtyListRaw = makeDirtyRectList(); 149 _sortingscratchBuffer = makeVec!UIElement(); 150 151 version(Dplug_ProfileUI) 152 { 153 _profiler = createProfiler(); 154 } 155 } 156 157 ~this() 158 { 159 destroyProfiler(_profiler); 160 } 161 162 final override float getUIScale() 163 { 164 return _owner.getUIScale(); 165 } 166 167 final override float getUserScale() 168 { 169 return _owner.getUserScale(); 170 } 171 172 final override vec2i getDefaultUISizeInPixels() 173 { 174 return _owner.getDefaultUISizeInPixels(); 175 } 176 177 final override int getDefaultUIWidth() 178 { 179 return getDefaultUISizeInPixels().x; 180 } 181 182 final override int getDefaultUIHeight() 183 { 184 return getDefaultUISizeInPixels().y; 185 } 186 187 final override vec2i getUISizeInPixelsUser() 188 { 189 return _owner.getUISizeInPixelsUser(); 190 } 191 192 final override vec2i getUISizeInPixelsLogical() 193 { 194 return _owner.getUISizeInPixelsLogical(); 195 } 196 197 final override vec2i getUISizeInPixelsPhysical() 198 { 199 return _owner.getUISizeInPixelsLogical(); 200 } 201 202 final override bool requestUIResize(int widthLogicalPixels, int heightLogicalPixels) 203 { 204 return _owner.requestUIResize(widthLogicalPixels, heightLogicalPixels); 205 } 206 207 final override void getUINearestValidSize(int* widthLogicalPixels, int* heightLogicalPixels) 208 { 209 _owner.getUINearestValidSize(widthLogicalPixels, heightLogicalPixels); 210 } 211 212 final override bool isUIResizable() 213 { 214 return _owner.isUIResizable(); 215 } 216 217 final override ImageResizer* globalImageResizer() 218 { 219 return &_globalResizer; 220 } 221 222 final override ThreadPool* globalThreadPool() 223 { 224 return &_owner._threadPool; 225 } 226 227 final override IProfiler profiler() 228 { 229 return _profiler; 230 } 231 232 /// Last clicked element. 233 UIElement focused = null; 234 235 /// Currently dragged element. 236 UIElement dragged = null; 237 238 /// Currently mouse-over'd element. 239 UIElement mouseOver = null; 240 241 // This is the UI-global, disjointed list of rectangles that need updating at the PBR level. 242 // Every UIElement touched by those rectangles will have their `onDrawPBR` and `onDrawRaw` 243 // callbacks called successively. 244 DirtyRectList dirtyListPBR; 245 246 // This is the UI-global, disjointed list of rectangles that need updating at the Raw level. 247 // Every UIElement touched by those rectangles will have its `onDrawRaw` callback called. 248 DirtyRectList dirtyListRaw; 249 250 final void setMouseOver(UIElement elem) 251 { 252 UIElement old = this.mouseOver; 253 UIElement new_ = elem; 254 if (old is new_) 255 return; 256 257 if (old !is null) 258 old.onMouseExit(); 259 this.mouseOver = new_; 260 if (new_ !is null) 261 new_.onMouseEnter(); 262 } 263 264 final void setFocused(UIElement focused) 265 { 266 UIElement old = this.focused; 267 UIElement new_ = focused; 268 if (old is new_) 269 return; 270 271 this.focused = new_; 272 if (old !is null) 273 old.onFocusExit(); 274 if (new_ !is null) 275 new_.onFocusEnter(); 276 } 277 278 final void beginDragging(UIElement element) 279 { 280 // Stop an existing dragging operation. 281 stopDragging(); 282 283 version(futureMouseDrag) 284 { 285 setMouseOver(element); 286 assert(this.mouseOver is element); 287 } 288 dragged = element; 289 dragged.onBeginDrag(); 290 } 291 292 final void stopDragging() 293 { 294 if (dragged !is null) 295 { 296 version(futureMouseDrag) 297 { 298 assert(this.mouseOver is dragged); 299 } 300 dragged.onStopDrag(); 301 dragged = null; 302 } 303 } 304 305 final MouseCursor getCurrentMouseCursor() 306 { 307 MouseCursor cursor = MouseCursor.pointer; 308 309 if (!(mouseOver is null)) 310 { 311 cursor = mouseOver.cursorWhenMouseOver(); 312 } 313 314 if(!(dragged is null)) 315 { 316 cursor = dragged.cursorWhenDragged(); 317 } 318 319 return cursor; 320 } 321 322 final override void* getUserPointer(int pointerID) 323 { 324 return _userPointers[pointerID]; 325 } 326 327 final override void setUserPointer(int pointerID, void* userPointer) 328 { 329 _userPointers[pointerID] = userPointer; 330 } 331 332 final override UIElement getRootElement() 333 { 334 return _owner; 335 } 336 337 final override UIElement getElementById(const(char)* id) 338 { 339 if (id is null) 340 return null; 341 342 // special value "__ROOT__" 343 if (strcmp("__ROOT__", id) == 0) 344 return getRootElement(); 345 346 // search in whole UI hierarchy 347 return _owner.getElementById(id); 348 } 349 350 final ref Vec!UIElement sortingScratchBuffer() 351 { 352 return _sortingscratchBuffer; 353 } 354 355 private: 356 GUIGraphics _owner; 357 358 ImageResizer _globalResizer; 359 360 /// A UI-global scratch buffer used as intermediate buffer for sorting UIElement. 361 Vec!UIElement _sortingscratchBuffer; 362 363 /// Warning: if you store objects here, keep in mind they won't get destroyed automatically. 364 /// 16 user pointer in case you'd like to store things in UIContext as a Dplug extension. 365 /// id 0..7 are reserved for future Dplug extensions. 366 /// id 8..15 are for vendor-specific extensions. 367 void*[16] _userPointers; // Opaque pointer for Wren VM and things. 368 369 IProfiler _profiler; 370 } 371