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 | |
10 | namespace 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 | } |