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