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