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