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 std.file; 11 12 import dplug.core.vec; 13 import dplug.core.nogc; 14 import dplug.core.sync; 15 16 import dplug.window.window; 17 18 import dplug.graphics.font; 19 import dplug.graphics.mipmap; 20 21 import dplug.gui.element; 22 import dplug.gui.boxlist; 23 24 25 /// UIContext contains the "globals" of the UI 26 /// - current focused element 27 /// - current dragged element 28 /// - images and fonts... 29 class UIContext 30 { 31 public: 32 nothrow: 33 @nogc: 34 this() 35 { 36 // create a dummy black skybox 37 // FUTURE: do not create it, support no-skybox in rendering 38 skybox = mallocNew!(Mipmap!RGBA)(10, 1024, 1024); 39 40 dirtyList = makeDirtyRectList(); 41 } 42 43 ~this() 44 { 45 skybox.destroyFree(); 46 } 47 48 /// Last clicked element. 49 UIElement focused = null; 50 51 /// Currently dragged element. 52 UIElement dragged = null; 53 54 /// UI global image used for environment reflections. 55 Mipmap!RGBA skybox; 56 57 // This is the global UI list of rectangles that need updating. 58 // This used to be a list of rectangles per UIElement, 59 // but this wasn't workable because of too many races and 60 // inefficiencies. 61 DirtyRectList dirtyList; 62 63 // Note: take ownership of image 64 // That image must have been built with `mallocEmplace` 65 void setSkybox(OwnedImage!RGBA image) 66 { 67 skybox.destroyFree(); 68 skybox = mallocNew!(Mipmap!RGBA)(12, image); 69 } 70 71 final void setFocused(UIElement focused) nothrow @nogc 72 { 73 this.focused = focused; 74 } 75 76 final void beginDragging(UIElement element) nothrow @nogc 77 { 78 stopDragging(); 79 dragged = element; 80 dragged.onBeginDrag(); 81 } 82 83 final void stopDragging() nothrow @nogc 84 { 85 if (dragged !is null) 86 { 87 dragged.onStopDrag(); 88 dragged = null; 89 } 90 } 91 } 92 93 DirtyRectList makeDirtyRectList() nothrow @nogc 94 { 95 return DirtyRectList(4); 96 } 97 98 struct DirtyRectList 99 { 100 public: 101 nothrow @nogc: 102 103 this(int dummy) 104 { 105 _dirtyRectMutex = makeMutex(); 106 _dirtyRects = makeVec!box2i(0); 107 } 108 109 @disable this(this); 110 111 bool isEmpty() nothrow @nogc 112 { 113 _dirtyRectMutex.lock(); 114 bool result = _dirtyRects.length == 0; 115 _dirtyRectMutex.unlock(); 116 return result; 117 } 118 119 /// Returns: Array of rectangles in the list, remove them from the list. 120 /// Needed to avoid races in repainting. 121 void pullAllRectangles(ref Vec!box2i result) nothrow @nogc 122 { 123 _dirtyRectMutex.lock(); 124 125 foreach(rect; _dirtyRects[]) 126 result.pushBack(rect); 127 128 _dirtyRects.clearContents(); 129 130 _dirtyRectMutex.unlock(); 131 } 132 133 /// Add a rect while keeping the no overlap invariant 134 void addRect(box2i rect) nothrow @nogc 135 { 136 assert(rect.isSorted); 137 138 if (!rect.empty) 139 { 140 _dirtyRectMutex.lock(); 141 scope(exit) _dirtyRectMutex.unlock(); 142 143 bool processed = false; 144 145 for (int i = 0; i < _dirtyRects.length; ++i) 146 { 147 box2i other = _dirtyRects[i]; 148 if (other.contains(rect)) 149 { 150 // If the rectangle candidate is inside an element of the list, discard it. 151 processed = true; 152 break; 153 } 154 else if (rect.contains(other)) // remove rect that it contains 155 { 156 // If the rectangle candidate contains an element of the list, this element need to go. 157 _dirtyRects[i] = _dirtyRects.popBack(); 158 i--; 159 } 160 else 161 { 162 box2i common = other.intersection(rect); 163 if (!common.empty()) 164 { 165 // compute other without common 166 box2i D, E, F, G; 167 boxSubtraction(other, common, D, E, F, G); 168 169 // remove other from list 170 _dirtyRects[i] = _dirtyRects.popBack(); 171 i--; 172 173 // push the sub parts at the end of the list 174 // this is guaranteed to be non-overlapping since the list was non-overlapping before 175 if (!D.empty) _dirtyRects.pushBack(D); 176 if (!E.empty) _dirtyRects.pushBack(E); 177 if (!F.empty) _dirtyRects.pushBack(F); 178 if (!G.empty) _dirtyRects.pushBack(G); 179 } 180 // else no intersection problem, the candidate rectangle will be pushed normally in the list 181 } 182 183 } 184 185 if (!processed) 186 _dirtyRects.pushBack(rect); 187 188 // Quadratic test, disabled 189 // assert(haveNoOverlap(_dirtyRects[])); 190 } 191 } 192 193 private: 194 /// The possibly overlapping areas that need updating. 195 Vec!box2i _dirtyRects; 196 197 /// This is protected by a mutex, because it is sometimes updated from the host. 198 /// Note: we cannot remove this mutex, as host parameter change call setDirtyWhole directly.ODO: we want to remove this lock, the host thread should never directly. 199 UncheckedMutex _dirtyRectMutex; 200 } 201