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