1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "GUI/BsGUIPanel.h"
4#include "GUI/BsGUIElement.h"
5#include "GUI/BsGUISpace.h"
6#include "Math/BsMath.h"
7#include "Math/BsVector2I.h"
8#include "Profiling/BsProfilerCPU.h"
9
10namespace bs
11{
12 GUIPanel::GUIPanel(INT16 depth, UINT16 depthRangeMin, UINT16 depthRangeMax, const GUIDimensions& dimensions)
13 : GUILayout(dimensions), mDepthOffset(depth), mDepthRangeMin(depthRangeMin), mDepthRangeMax(depthRangeMax)
14 { }
15
16 void GUIPanel::setDepthRange(INT16 depth, UINT16 depthRangeMin, UINT16 depthRangeMax)
17 {
18 mDepthOffset = depth;
19 mDepthRangeMin = depthRangeMin;
20 mDepthRangeMax = depthRangeMax;
21
22 _markLayoutAsDirty();
23 }
24
25 LayoutSizeRange GUIPanel::_calculateLayoutSizeRange() const
26 {
27 Vector2I optimalSize;
28 Vector2I minSize;
29
30 for (auto& child : mChildren)
31 {
32 if (!child->_isActive())
33 continue;
34
35 LayoutSizeRange sizeRange = child->_calculateLayoutSizeRange();
36
37 if (child->_getType() == GUIElementBase::Type::FixedSpace || child->_getType() == GUIElementBase::Type::FlexibleSpace)
38 {
39 sizeRange.optimal.x = sizeRange.optimal.y = 0;
40 sizeRange.min.x = sizeRange.min.y = 0;
41 }
42
43 UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
44 UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
45
46 Vector2I childMax;
47 childMax.x = child->_getDimensions().x + sizeRange.optimal.x + paddingX;
48 childMax.y = child->_getDimensions().y + sizeRange.optimal.y + paddingY;
49
50 optimalSize.x = std::max(optimalSize.x, childMax.x);
51 optimalSize.y = std::max(optimalSize.y, childMax.y);
52
53 childMax.x = child->_getDimensions().x + sizeRange.min.x + paddingX;
54 childMax.y = child->_getDimensions().y + sizeRange.min.y + paddingY;
55
56 minSize.x = std::max(minSize.x, childMax.x);
57 minSize.y = std::max(minSize.y, childMax.y);
58 }
59
60 LayoutSizeRange sizeRange = _getDimensions().calculateSizeRange(optimalSize);
61 sizeRange.min.x = std::max(sizeRange.min.x, minSize.x);
62 sizeRange.min.y = std::max(sizeRange.min.y, minSize.y);
63
64 return sizeRange;
65 }
66
67 LayoutSizeRange GUIPanel::_getElementSizeRange(const GUIElementBase* element) const
68 {
69 if (element->_getType() == GUIElementBase::Type::FixedSpace || element->_getType() == GUIElementBase::Type::FlexibleSpace)
70 {
71 LayoutSizeRange sizeRange = element->_getLayoutSizeRange();
72 sizeRange.optimal.x = 0;
73 sizeRange.optimal.y = 0;
74 sizeRange.min.x = 0;
75 sizeRange.min.y = 0;
76
77 return sizeRange;
78 }
79
80 return element->_getLayoutSizeRange();
81 }
82
83 void GUIPanel::_updateOptimalLayoutSizes()
84 {
85 // Update all children first, otherwise we can't determine our own optimal size
86 GUIElementBase::_updateOptimalLayoutSizes();
87
88 if (mChildren.size() != mChildSizeRanges.size())
89 mChildSizeRanges.resize(mChildren.size());
90
91 Vector2I optimalSize;
92 Vector2I minSize;
93
94 UINT32 childIdx = 0;
95 for (auto& child : mChildren)
96 {
97 LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
98
99 if (child->_isActive())
100 {
101 childSizeRange = _getElementSizeRange(child);
102
103 UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
104 UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
105
106 Vector2I childMax;
107 childMax.x = child->_getDimensions().x + childSizeRange.optimal.x + paddingX;
108 childMax.y = child->_getDimensions().y + childSizeRange.optimal.y + paddingY;
109
110 optimalSize.x = std::max(optimalSize.x, childMax.x);
111 optimalSize.y = std::max(optimalSize.y, childMax.y);
112
113 childMax.x = child->_getDimensions().x + childSizeRange.min.x + paddingX;
114 childMax.y = child->_getDimensions().y + childSizeRange.min.y + paddingY;
115
116 minSize.x = std::max(minSize.x, childMax.x);
117 minSize.y = std::max(minSize.y, childMax.y);
118 }
119 else
120 childSizeRange = LayoutSizeRange();
121
122 childIdx++;
123 }
124
125 mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
126 mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
127 mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
128 }
129
130 void GUIPanel::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
131 const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
132 {
133 assert(mChildren.size() == numElements);
134
135 // Panel always uses optimal sizes and explicit positions
136 UINT32 childIdx = 0;
137 for (auto& child : mChildren)
138 {
139 elementAreas[childIdx] = _getElementArea(layoutArea, child, sizeRanges[childIdx]);
140
141 childIdx++;
142 }
143 }
144
145 Rect2I GUIPanel::_getElementArea(const Rect2I& layoutArea, const GUIElementBase* element, const LayoutSizeRange& sizeRange) const
146 {
147 const GUIDimensions& dimensions = element->_getDimensions();
148
149 Rect2I area;
150
151 area.x = layoutArea.x + dimensions.x;
152 area.y = layoutArea.y + dimensions.y;
153
154 if (dimensions.fixedWidth())
155 area.width = (UINT32)sizeRange.optimal.x;
156 else
157 {
158 UINT32 modifiedWidth = (UINT32)std::max(0, (INT32)layoutArea.width - dimensions.x);
159
160 if (modifiedWidth > (UINT32)sizeRange.optimal.x)
161 {
162 if (sizeRange.max.x > 0)
163 modifiedWidth = std::min(modifiedWidth, (UINT32)sizeRange.max.x);
164 }
165 else if (modifiedWidth < (UINT32)sizeRange.optimal.x)
166 {
167 if (sizeRange.min.x > 0)
168 modifiedWidth = std::max(modifiedWidth, (UINT32)sizeRange.min.x);
169 }
170
171 area.width = modifiedWidth;
172 }
173
174 if (dimensions.fixedHeight())
175 area.height = (UINT32)sizeRange.optimal.y;
176 else
177 {
178 UINT32 modifiedHeight = (UINT32)std::max(0, (INT32)layoutArea.height - dimensions.y);
179
180 if (modifiedHeight > (UINT32)sizeRange.optimal.y)
181 {
182 if (sizeRange.max.y > 0)
183 modifiedHeight = std::min(modifiedHeight, (UINT32)sizeRange.max.y);
184 }
185 else if (modifiedHeight < (UINT32)sizeRange.optimal.y)
186 {
187 if (sizeRange.min.y > 0)
188 modifiedHeight = std::max(modifiedHeight, (UINT32)sizeRange.min.y);
189 }
190
191 area.height = modifiedHeight;
192 }
193
194 return area;
195 }
196
197 void GUIPanel::_updateDepthRange(GUILayoutData& data)
198 {
199 INT32 newPanelDepth = data.getPanelDepth() + mDepthOffset;
200 INT32 newPanelDepthRangeMin = newPanelDepth - mDepthRangeMin;
201 INT32 newPanelDepthRangeMax = newPanelDepth + mDepthRangeMax;
202
203 INT32* allDepths[3] = { &newPanelDepth, &newPanelDepthRangeMin, &newPanelDepthRangeMax };
204
205 for (auto& depth : allDepths)
206 {
207 INT32 minValue = std::max((INT32)data.getPanelDepth() - (INT32)data.depthRangeMin, (INT32)std::numeric_limits<INT16>::min());
208 *depth = std::max(*depth, minValue);
209
210 INT32 maxValue = std::min((INT32)data.getPanelDepth() + (INT32)data.depthRangeMax, (INT32)std::numeric_limits<INT16>::max());
211 *depth = std::min(*depth, maxValue);
212 }
213
214 data.setPanelDepth((INT16)newPanelDepth);
215
216 if (mDepthRangeMin != (UINT16)-1 || data.depthRangeMin != (UINT16)-1)
217 data.depthRangeMin = (UINT16)(newPanelDepth - newPanelDepthRangeMin);
218
219 if (mDepthRangeMax != (UINT16)-1 || data.depthRangeMax != (UINT16)-1)
220 data.depthRangeMax = (UINT16)(newPanelDepthRangeMax - newPanelDepth);
221 }
222
223 void GUIPanel::_updateLayoutInternal(const GUILayoutData& data)
224 {
225 GUILayoutData childData = data;
226 _updateDepthRange(childData);
227
228 UINT32 numElements = (UINT32)mChildren.size();
229 Rect2I* elementAreas = nullptr;
230
231 if (numElements > 0)
232 elementAreas = bs_stack_new<Rect2I>(numElements);
233
234 _getElementAreas(data.area, elementAreas, numElements, mChildSizeRanges, mSizeRange);
235
236 UINT32 childIdx = 0;
237
238 for (auto& child : mChildren)
239 {
240 if (child->_isActive())
241 {
242 childData.area = elementAreas[childIdx];
243
244 _updateChildLayout(child, childData);
245 }
246
247 childIdx++;
248 }
249
250 if (elementAreas != nullptr)
251 bs_stack_free(elementAreas);
252 }
253
254 void GUIPanel::_updateChildLayout(GUIElementBase* element, const GUILayoutData& data)
255 {
256 GUILayoutData childData = data;
257
258 childData.clipRect = data.area;
259 childData.clipRect.clip(data.clipRect);
260
261 element->_setLayoutData(childData);
262 element->_updateLayoutInternal(childData);
263 }
264
265 GUIPanel* GUIPanel::create(INT16 depth, UINT16 depthRangeMin, UINT16 depthRangeMax)
266 {
267 return bs_new<GUIPanel>(depth, depthRangeMin, depthRangeMax, GUIDimensions::create());
268 }
269
270 GUIPanel* GUIPanel::create(const GUIOptions& options)
271 {
272 return bs_new<GUIPanel>(0, -1, -1, GUIDimensions::create(options));
273 }
274
275 GUIPanel* GUIPanel::create(INT16 depth, UINT16 depthRangeMin, UINT16 depthRangeMax, const GUIOptions& options)
276 {
277 return bs_new<GUIPanel>(depth, depthRangeMin, depthRangeMax, GUIDimensions::create(options));
278 }
279}