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/BsGUIElementBase.h"
4#include "GUI/BsGUILayout.h"
5#include "GUI/BsGUIPanel.h"
6#include "GUI/BsGUISpace.h"
7#include "GUI/BsGUIElement.h"
8#include "Error/BsException.h"
9#include "GUI/BsGUIWidget.h"
10#include "BsGUIManager.h"
11
12namespace bs
13{
14 GUIElementBase::GUIElementBase(const GUIDimensions& dimensions)
15 : mDimensions(dimensions)
16 { }
17
18 GUIElementBase::~GUIElementBase()
19 {
20 destroyChildElements();
21 }
22
23 void GUIElementBase::setPosition(INT32 x, INT32 y)
24 {
25 mDimensions.x = x;
26 mDimensions.y = y;
27
28 // Note: I could call _markMeshAsDirty with a little more work. If parent is layout then this call can be ignored
29 // and if it's a panel, we can immediately change the position without a full layout rebuild.
30 _markLayoutAsDirty();
31 }
32
33 void GUIElementBase::setSize(UINT32 width, UINT32 height)
34 {
35 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
36
37 mDimensions.flags |= GUIDF_FixedWidth | GUIDF_OverWidth | GUIDF_FixedHeight | GUIDF_OverHeight;
38 mDimensions.minWidth = mDimensions.maxWidth = width;
39 mDimensions.minHeight = mDimensions.maxHeight = height;
40
41 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
42
43 if (isFixedBefore != isFixedAfter)
44 refreshChildUpdateParents();
45
46 _markLayoutAsDirty();
47 }
48
49 void GUIElementBase::setWidth(UINT32 width)
50 {
51 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
52
53 mDimensions.flags |= GUIDF_FixedWidth | GUIDF_OverWidth;
54 mDimensions.minWidth = mDimensions.maxWidth = width;
55
56 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
57
58 if (isFixedBefore != isFixedAfter)
59 refreshChildUpdateParents();
60
61 _markLayoutAsDirty();
62 }
63
64 void GUIElementBase::setFlexibleWidth(UINT32 minWidth, UINT32 maxWidth)
65 {
66 if (maxWidth < minWidth)
67 std::swap(minWidth, maxWidth);
68
69 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
70
71 mDimensions.flags |= GUIDF_OverWidth;
72 mDimensions.flags &= ~GUIDF_FixedWidth;
73 mDimensions.minWidth = minWidth;
74 mDimensions.maxWidth = maxWidth;
75
76 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
77
78 if (isFixedBefore != isFixedAfter)
79 refreshChildUpdateParents();
80
81 _markLayoutAsDirty();
82 }
83
84 void GUIElementBase::setHeight(UINT32 height)
85 {
86 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
87
88 mDimensions.flags |= GUIDF_FixedHeight | GUIDF_OverHeight;
89 mDimensions.minHeight = mDimensions.maxHeight = height;
90
91 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
92
93 if (isFixedBefore != isFixedAfter)
94 refreshChildUpdateParents();
95
96 _markLayoutAsDirty();
97 }
98
99 void GUIElementBase::setFlexibleHeight(UINT32 minHeight, UINT32 maxHeight)
100 {
101 if (maxHeight < minHeight)
102 std::swap(minHeight, maxHeight);
103
104 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
105
106 mDimensions.flags |= GUIDF_OverHeight;
107 mDimensions.flags &= ~GUIDF_FixedHeight;
108 mDimensions.minHeight = minHeight;
109 mDimensions.maxHeight = maxHeight;
110
111 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
112
113 if (isFixedBefore != isFixedAfter)
114 refreshChildUpdateParents();
115
116 _markLayoutAsDirty();
117 }
118
119 void GUIElementBase::resetDimensions()
120 {
121 bool isFixedBefore = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
122
123 mDimensions = GUIDimensions::create();
124
125 bool isFixedAfter = (mDimensions.flags & GUIDF_FixedWidth) != 0 && (mDimensions.flags & GUIDF_FixedHeight) != 0;
126
127 if (isFixedBefore != isFixedAfter)
128 refreshChildUpdateParents();
129
130 _markLayoutAsDirty();
131 }
132
133 Rect2I GUIElementBase::getBounds(GUIPanel* relativeTo)
134 {
135 if (relativeTo == nullptr)
136 relativeTo = mAnchorParent;
137
138 Rect2I anchorBounds;
139 if (relativeTo != nullptr)
140 anchorBounds = relativeTo->getGlobalBounds();
141
142 if (mUpdateParent != nullptr && mUpdateParent->_isDirty() && mParentWidget != nullptr)
143 mParentWidget->_updateLayout(mUpdateParent);
144
145 Rect2I bounds = mLayoutData.area;
146 bounds.x -= anchorBounds.x;
147 bounds.y -= anchorBounds.y;
148
149 return bounds;
150 }
151
152 void GUIElementBase::setBounds(const Rect2I& bounds)
153 {
154 setPosition(bounds.x, bounds.y);
155 setWidth(bounds.width);
156 setHeight(bounds.height);
157 }
158
159 Rect2I GUIElementBase::getGlobalBounds()
160 {
161 if (mUpdateParent != nullptr && mUpdateParent->_isDirty() && mParentWidget != nullptr)
162 mParentWidget->_updateLayout(mUpdateParent);
163
164 return mLayoutData.area;
165 }
166
167 Rect2I GUIElementBase::getScreenBounds() const
168 {
169 if (mUpdateParent != nullptr && mUpdateParent->_isDirty() && mParentWidget != nullptr)
170 mParentWidget->_updateLayout(mUpdateParent);
171
172 Rect2I area = mLayoutData.area;
173 if(mParentWidget)
174 {
175 const Matrix4& widgetTfrm = mParentWidget->getWorldTfrm();
176 Vector2I localPos(area.x, area.y);
177
178 const Vector4 widgetPosFlt = widgetTfrm.multiplyAffine(Vector4((float)localPos.x, (float)localPos.y, 0.0f, 1.0f));
179 const Vector2I widgetPos(Math::roundToInt(widgetPosFlt.x), Math::roundToInt(widgetPosFlt.y));
180
181 const RenderWindow* parentWindow = GUIManager::instance().getWidgetWindow(*mParentWidget);
182 if(parentWindow)
183 {
184 const Vector2I windowPos = parentWindow->windowToScreenPos(widgetPos);
185 area.x = windowPos.x;
186 area.y = windowPos.y;
187 }
188 else
189 {
190 area.x = widgetPos.x;
191 area.y = widgetPos.y;
192 }
193 }
194
195 return area;
196 }
197
198 Rect2I GUIElementBase::getVisibleBounds()
199 {
200 return getBounds();
201 }
202
203 void GUIElementBase::_markAsClean()
204 {
205 mFlags &= ~GUIElem_Dirty;
206 }
207
208 void GUIElementBase::_markLayoutAsDirty()
209 {
210 if(!_isVisible())
211 return;
212
213 if (mUpdateParent != nullptr)
214 mUpdateParent->mFlags |= GUIElem_Dirty;
215 else
216 mFlags |= GUIElem_Dirty;
217 }
218
219 void GUIElementBase::_markContentAsDirty()
220 {
221 if (!_isVisible())
222 return;
223
224 if (mParentWidget != nullptr)
225 mParentWidget->_markContentDirty(this);
226 }
227
228 void GUIElementBase::_markMeshAsDirty()
229 {
230 if(!_isVisible())
231 return;
232
233 if (mParentWidget != nullptr)
234 mParentWidget->_markMeshDirty(this);
235 }
236
237 void GUIElementBase::setVisible(bool visible)
238 {
239 // No visibility states matter if object is not active
240 if (!_isActive())
241 return;
242
243 bool visibleSelf = (mFlags & GUIElem_HiddenSelf) == 0;
244 if (visibleSelf != visible)
245 {
246 // If making an element visible make sure to mark layout as dirty, as we didn't track any dirty flags while the element was inactive
247 if (!visible)
248 {
249 mFlags |= GUIElem_HiddenSelf;
250 _setVisible(false);
251 }
252 else
253 {
254 mFlags &= ~GUIElem_HiddenSelf;
255
256 if (mParentElement == nullptr || mParentElement->_isVisible())
257 _setVisible(true);
258 }
259 }
260 }
261
262 void GUIElementBase::_setVisible(bool visible)
263 {
264 bool isVisible = (mFlags & GUIElem_Hidden) == 0;
265 if (isVisible == visible)
266 return;
267
268 if (!visible)
269 {
270 _markMeshAsDirty();
271
272 mFlags |= GUIElem_Hidden;
273
274 for (auto& child : mChildren)
275 child->_setVisible(false);
276 }
277 else
278 {
279 bool childVisibleSelf = (mFlags & GUIElem_HiddenSelf) == 0;
280 if (childVisibleSelf)
281 {
282 mFlags &= ~GUIElem_Hidden;
283 _markLayoutAsDirty();
284
285 for (auto& child : mChildren)
286 child->_setVisible(true);
287 }
288 }
289 }
290
291 void GUIElementBase::setActive(bool active)
292 {
293 static const UINT8 ACTIVE_FLAGS = GUIElem_InactiveSelf | GUIElem_HiddenSelf;
294
295 bool activeSelf = (mFlags & GUIElem_InactiveSelf) == 0;
296 if (activeSelf != active)
297 {
298 if (!active)
299 {
300 mFlags |= ACTIVE_FLAGS;
301
302 _setActive(false);
303 _setVisible(false);
304 }
305 else
306 {
307 mFlags &= ~ACTIVE_FLAGS;
308
309 if (mParentElement != nullptr)
310 {
311 if (mParentElement->_isActive())
312 {
313 _setActive(true);
314
315 if (mParentElement->_isVisible())
316 _setVisible(true);
317 }
318 }
319 else
320 {
321 _setActive(true);
322 _setVisible(true);
323 }
324 }
325 }
326 }
327
328 void GUIElementBase::_setActive(bool active)
329 {
330 bool isActive = (mFlags & GUIElem_Inactive) == 0;
331 if (isActive == active)
332 return;
333
334 if (!active)
335 {
336 _markLayoutAsDirty();
337
338 mFlags |= GUIElem_Inactive;
339
340 for (auto& child : mChildren)
341 child->_setActive(false);
342 }
343 else
344 {
345 bool childActiveSelf = (mFlags & GUIElem_InactiveSelf) == 0;
346 if (childActiveSelf)
347 {
348 mFlags &= ~GUIElem_Inactive;
349 _markLayoutAsDirty();
350
351 for (auto& child : mChildren)
352 child->_setActive(true);
353 }
354 }
355 }
356
357 void GUIElementBase::setDisabled(bool disabled)
358 {
359 bool disabledSelf = (mFlags & GUIElem_DisabledSelf) != 0;
360 if (disabledSelf != disabled)
361 {
362 if (!disabled)
363 mFlags &= ~GUIElem_DisabledSelf;
364 else
365 mFlags |= GUIElem_DisabledSelf;
366
367 _setDisabled(disabled);
368 }
369 }
370
371 void GUIElementBase::_setDisabled(bool disabled)
372 {
373 bool isDisabled = (mFlags & GUIElem_Disabled) != 0;
374 if (isDisabled == disabled)
375 return;
376
377 if (!disabled)
378 {
379 bool disabledSelf = (mFlags & GUIElem_DisabledSelf) != 0;
380 if (!disabledSelf)
381 {
382 mFlags &= ~GUIElem_Disabled;
383
384 for (auto& child : mChildren)
385 child->_setDisabled(false);
386 }
387 }
388 else
389 {
390 mFlags |= GUIElem_Disabled;
391
392 for (auto& child : mChildren)
393 child->_setDisabled(true);
394 }
395
396 if (_isVisible())
397 _markContentAsDirty();
398 }
399
400 void GUIElementBase::_updateLayout(const GUILayoutData& data)
401 {
402 _updateOptimalLayoutSizes(); // We calculate optimal sizes of all layouts as a pre-processing step, as they are requested often during update
403 _updateLayoutInternal(data);
404 }
405
406 void GUIElementBase::_updateOptimalLayoutSizes()
407 {
408 for(auto& child : mChildren)
409 {
410 child->_updateOptimalLayoutSizes();
411 }
412 }
413
414 void GUIElementBase::_updateLayoutInternal(const GUILayoutData& data)
415 {
416 for(auto& child : mChildren)
417 {
418 child->_updateLayoutInternal(data);
419 }
420 }
421
422 LayoutSizeRange GUIElementBase::_calculateLayoutSizeRange() const
423 {
424 const GUIDimensions& dimensions = _getDimensions();
425 return dimensions.calculateSizeRange(_getOptimalSize());
426 }
427
428 LayoutSizeRange GUIElementBase::_getLayoutSizeRange() const
429 {
430 return _calculateLayoutSizeRange();
431 }
432
433 void GUIElementBase::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
434 const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
435 {
436 assert(mChildren.size() == 0);
437 }
438
439 void GUIElementBase::_setParent(GUIElementBase* parent)
440 {
441 if(mParentElement != parent)
442 {
443 mParentElement = parent;
444 _updateAUParents();
445
446 if (parent != nullptr)
447 {
448 if (_getParentWidget() != parent->_getParentWidget())
449 _changeParentWidget(parent->_getParentWidget());
450 }
451 else
452 _changeParentWidget(nullptr);
453 }
454 }
455
456 void GUIElementBase::_registerChildElement(GUIElementBase* element)
457 {
458 assert(!element->_isDestroyed());
459
460 GUIElementBase* parentElement = element->_getParent();
461 if(parentElement != nullptr)
462 {
463 parentElement->_unregisterChildElement(element);
464 }
465
466 element->_setParent(this);
467 mChildren.push_back(element);
468
469 element->_setActive(_isActive());
470 element->_setVisible(_isVisible());
471 element->_setDisabled(_isDisabled());
472
473 // No need to mark ourselves as dirty. If we're part of the element's update chain, this will do it for us.
474 element->_markLayoutAsDirty();
475 }
476
477 void GUIElementBase::_unregisterChildElement(GUIElementBase* element)
478 {
479 bool foundElem = false;
480 for(auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
481 {
482 GUIElementBase* child = *iter;
483
484 if (child == element)
485 {
486 element->_markLayoutAsDirty();
487
488 mChildren.erase(iter);
489 element->_setParent(nullptr);
490 foundElem = true;
491
492 break;
493 }
494 }
495
496 if(!foundElem)
497 BS_EXCEPT(InvalidParametersException, "Provided element is not a part of this element.");
498 }
499
500 void GUIElementBase::destroyChildElements()
501 {
502 Vector<GUIElementBase*> childCopy = mChildren;
503 for (auto& child : childCopy)
504 {
505 if (child->_getType() == Type::Element)
506 {
507 const auto element = static_cast<GUIElement*>(child);
508 GUIElement::destroy(element);
509 }
510 else if (child->_getType() == Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
511 {
512 const auto layout = static_cast<GUILayout*>(child);
513 GUILayout::destroy(layout);
514 }
515 else if (child->_getType() == Type::FixedSpace)
516 {
517 const auto space = static_cast<GUIFixedSpace*>(child);
518 GUIFixedSpace::destroy(space);
519 }
520 else if (child->_getType() == Type::FlexibleSpace)
521 {
522 const auto space = static_cast<GUIFlexibleSpace*>(child);
523 GUIFlexibleSpace::destroy(space);
524 }
525 }
526
527 assert(mChildren.empty());
528 }
529
530 void GUIElementBase::_changeParentWidget(GUIWidget* widget)
531 {
532 assert(!_isDestroyed());
533
534 if (mParentWidget != widget)
535 {
536 if (mParentWidget != nullptr)
537 mParentWidget->_unregisterElement(this);
538
539 if (widget != nullptr)
540 widget->_registerElement(this);
541 }
542
543 mParentWidget = widget;
544
545 for(auto& child : mChildren)
546 {
547 child->_changeParentWidget(widget);
548 }
549
550 _markLayoutAsDirty();
551 }
552
553 void GUIElementBase::_updateAUParents()
554 {
555 GUIElementBase* updateParent = nullptr;
556 if (mParentElement != nullptr)
557 {
558 updateParent = mParentElement->findUpdateParent();
559
560 // If parent is a panel then we can do an optimization and only update
561 // one child instead of all of them, so change parent to that child.
562 if (updateParent != nullptr && updateParent->_getType() == GUIElementBase::Type::Panel)
563 {
564 GUIElementBase* optimizedUpdateParent = this;
565 while (optimizedUpdateParent->_getParent() != updateParent)
566 optimizedUpdateParent = optimizedUpdateParent->_getParent();
567
568 updateParent = optimizedUpdateParent;
569 }
570 }
571
572 GUIPanel* anchorParent = nullptr;
573 GUIElementBase* currentParent = mParentElement;
574 while (currentParent != nullptr)
575 {
576 if (currentParent->_getType() == Type::Panel)
577 {
578 anchorParent = static_cast<GUIPanel*>(currentParent);
579 break;
580 }
581
582 currentParent = currentParent->mParentElement;
583 }
584
585 setAnchorParent(anchorParent);
586 setUpdateParent(updateParent);
587 }
588
589 GUIElementBase* GUIElementBase::findUpdateParent()
590 {
591 GUIElementBase* currentElement = this;
592 while (currentElement != nullptr)
593 {
594 const GUIDimensions& parentDimensions = currentElement->_getDimensions();
595 bool boundsDependOnChildren = !parentDimensions.fixedHeight() || !parentDimensions.fixedWidth();
596
597 if (!boundsDependOnChildren)
598 return currentElement;
599
600 currentElement = currentElement->mParentElement;
601 }
602
603 return nullptr;
604 }
605
606 void GUIElementBase::refreshChildUpdateParents()
607 {
608 GUIElementBase* updateParent = findUpdateParent();
609
610 for (auto& child : mChildren)
611 {
612 GUIElementBase* childUpdateParent = updateParent;
613
614 // If parent is a panel then we can do an optimization and only update
615 // one child instead of all of them, so change parent to that child.
616 if (childUpdateParent != nullptr && childUpdateParent->_getType() == GUIElementBase::Type::Panel)
617 {
618 GUIElementBase* optimizedUpdateParent = child;
619 while (optimizedUpdateParent->_getParent() != childUpdateParent)
620 optimizedUpdateParent = optimizedUpdateParent->_getParent();
621
622 childUpdateParent = optimizedUpdateParent;
623 }
624
625 child->setUpdateParent(childUpdateParent);
626 }
627 }
628
629 void GUIElementBase::setAnchorParent(GUIPanel* anchorParent)
630 {
631 mAnchorParent = anchorParent;
632
633 if (_getType() == Type::Panel)
634 return;
635
636 for (auto& child : mChildren)
637 child->setAnchorParent(anchorParent);
638 }
639
640 void GUIElementBase::setUpdateParent(GUIElementBase* updateParent)
641 {
642 mUpdateParent = updateParent;
643
644 const GUIDimensions& dimensions = _getDimensions();
645 bool boundsDependOnChildren = !dimensions.fixedHeight() || !dimensions.fixedWidth();
646
647 if (!boundsDependOnChildren)
648 return;
649
650 for (auto& child : mChildren)
651 child->setUpdateParent(updateParent);
652 }
653}
654