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/BsGUIWidget.h"
4#include "GUI/BsGUIManager.h"
5#include "GUI/BsGUISkin.h"
6#include "GUI/BsGUILabel.h"
7#include "GUI/BsGUIPanel.h"
8#include "GUI/BsGUINavGroup.h"
9#include "Math/BsVector2I.h"
10#include "Components/BsCCamera.h"
11#include "RenderAPI/BsViewport.h"
12#include "Scene/BsSceneObject.h"
13#include "Resources/BsBuiltinResources.h"
14
15namespace bs
16{
17 GUIWidget::GUIWidget(const SPtr<Camera>& camera)
18 : mCamera(camera), mPanel(nullptr), mDepth(128), mIsActive(true), mPosition(BsZero), mRotation(BsIdentity)
19 , mScale(Vector3::ONE), mTransform(BsIdentity), mCachedRTId(0), mWidgetIsDirty(false)
20 {
21 construct(camera);
22 }
23
24 GUIWidget::GUIWidget(const HCamera& camera)
25 : mCamera(camera->_getCamera()), mPanel(nullptr), mDepth(128), mIsActive(true), mPosition(BsZero)
26 , mRotation(BsIdentity), mScale(Vector3::ONE), mTransform(BsIdentity), mCachedRTId(0), mWidgetIsDirty(false)
27 {
28 construct(mCamera);
29 }
30
31 void GUIWidget::construct(const SPtr<Camera>& camera)
32 {
33 if (mCamera != nullptr)
34 {
35 SPtr<RenderTarget> target = mCamera->getViewport()->getTarget();
36
37 if (target != nullptr)
38 mCachedRTId = target->getInternalID();
39 }
40
41 mDefaultNavGroup = GUINavGroup::create();
42
43 GUIManager::instance().registerWidget(this);
44
45 mPanel = GUIPanel::create();
46 mPanel->_changeParentWidget(this);
47 updateRootPanel();
48 }
49
50 GUIWidget::~GUIWidget()
51 {
52 _destroy();
53 }
54
55 SPtr<GUIWidget> GUIWidget::create(const SPtr<Camera>& camera)
56 {
57 return bs_shared_ptr(new (bs_alloc<GUIWidget>()) GUIWidget(camera));
58 }
59
60 SPtr<GUIWidget> GUIWidget::create(const HCamera& camera)
61 {
62 return bs_shared_ptr(new (bs_alloc<GUIWidget>()) GUIWidget(camera));
63 }
64
65 void GUIWidget::_destroy()
66 {
67 if (mPanel != nullptr)
68 {
69 GUILayout::destroy(mPanel);
70 mPanel = nullptr;
71 }
72
73 if (mCamera != nullptr)
74 {
75 GUIManager::instance().unregisterWidget(this);
76 mCamera = nullptr;
77 }
78
79 mElements.clear();
80 mDirtyContents.clear();
81 }
82
83 void GUIWidget::setDepth(UINT8 depth)
84 {
85 mDepth = depth;
86 mWidgetIsDirty = true;
87
88 updateRootPanel();
89 }
90
91 Viewport* GUIWidget::getTarget() const
92 {
93 if(mCamera != nullptr)
94 return mCamera->getViewport().get();
95
96 return nullptr;
97 }
98
99 void GUIWidget::_updateTransform(const HSceneObject& parent)
100 {
101 // If the widgets parent scene object moved, we need to mark it as dirty
102 // as the GUIManager batching relies on object positions, so it needs to be updated.
103 const float diffEpsilon = 0.0001f;
104
105 const Transform& tfrm = parent->getTransform();
106 Vector3 position = tfrm.getPosition();
107 Quaternion rotation = tfrm.getRotation();
108 Vector3 scale = tfrm.getScale();
109
110 if(!mWidgetIsDirty)
111 {
112 if(!Math::approxEquals(mPosition, position, diffEpsilon))
113 mWidgetIsDirty = true;
114 else
115 {
116 if(!Math::approxEquals(mRotation, rotation, diffEpsilon))
117 mWidgetIsDirty = true;
118 else
119 {
120 if(Math::approxEquals(mScale, scale))
121 mWidgetIsDirty = true;
122 }
123 }
124 }
125
126 mPosition = position;
127 mRotation = rotation;
128 mScale = scale;
129 mTransform = parent->getWorldMatrix();
130 }
131
132 void GUIWidget::_updateRT()
133 {
134 SPtr<RenderTarget> rt;
135 UINT64 newRTId = 0;
136 if(mCamera != nullptr)
137 {
138 rt = mCamera->getViewport()->getTarget();
139 if (rt != nullptr)
140 newRTId = rt->getInternalID();
141 }
142
143 if(mCachedRTId != newRTId)
144 {
145 mCachedRTId = newRTId;
146 updateRootPanel();
147 }
148 }
149
150 void GUIWidget::_updateLayout()
151 {
152 // Check if render target size changed and update if needed
153 // Note: Purposely not relying to the RenderTarget::onResized callback, as it will trigger /before/ Input events.
154 // These events might trigger a resize, meaning the size would be delayed one frame, resulting in a visual artifact
155 // where the GUI doesn't match the target size.
156 Viewport* target = getTarget();
157 if (target != nullptr)
158 {
159 Rect2I area = target->getPixelArea();
160 UINT32 width = area.width;
161 UINT32 height = area.height;
162
163 const Rect2I& panelArea = mPanel->_getLayoutData().area;
164 if(panelArea.width != width || panelArea.height != height)
165 {
166 updateRootPanel();
167 onOwnerTargetResized();
168 }
169 }
170
171 bs_frame_mark();
172
173 // Determine dirty contents and layouts
174 FrameStack<GUIElementBase*> todo;
175 todo.push(mPanel);
176
177 while (!todo.empty())
178 {
179 GUIElementBase* currentElem = todo.top();
180 todo.pop();
181
182 if (currentElem->_isDirty())
183 {
184 GUIElementBase* updateParent = currentElem->_getUpdateParent();
185 assert(updateParent != nullptr || currentElem == mPanel);
186
187 if (updateParent != nullptr)
188 _updateLayout(updateParent);
189 else // Must be root panel
190 _updateLayout(mPanel);
191 }
192 else
193 {
194 UINT32 numChildren = currentElem->_getNumChildren();
195 for (UINT32 i = 0; i < numChildren; i++)
196 todo.push(currentElem->_getChild(i));
197 }
198 }
199
200 bs_frame_clear();
201 }
202
203 void GUIWidget::_updateLayout(GUIElementBase* elem)
204 {
205 GUIElementBase* parent = elem->_getParent();
206 bool isPanelOptimized = parent != nullptr && parent->_getType() == GUIElementBase::Type::Panel;
207
208 GUIElementBase* updateParent = nullptr;
209
210 if (isPanelOptimized)
211 updateParent = parent;
212 else
213 updateParent = elem;
214
215 // For GUIPanel we can do a an optimization and update only the element in question instead
216 // of all the children
217 if (isPanelOptimized)
218 {
219 GUIPanel* panel = static_cast<GUIPanel*>(updateParent);
220
221 GUIElementBase* dirtyElement = elem;
222 dirtyElement->_updateOptimalLayoutSizes();
223
224 LayoutSizeRange elementSizeRange = panel->_getElementSizeRange(dirtyElement);
225 Rect2I elementArea = panel->_getElementArea(panel->_getLayoutData().area, dirtyElement, elementSizeRange);
226
227 GUILayoutData childLayoutData = panel->_getLayoutData();
228 panel->_updateDepthRange(childLayoutData);
229 childLayoutData.area = elementArea;
230
231 panel->_updateChildLayout(dirtyElement, childLayoutData);
232 }
233 else
234 {
235 GUILayoutData childLayoutData = updateParent->_getLayoutData();
236 updateParent->_updateLayout(childLayoutData);
237 }
238
239 // Mark dirty contents
240 bs_frame_mark();
241 {
242 FrameStack<GUIElementBase*> todo;
243 todo.push(elem);
244
245 while (!todo.empty())
246 {
247 GUIElementBase* currentElem = todo.top();
248 todo.pop();
249
250 if (currentElem->_getType() == GUIElementBase::Type::Element)
251 mDirtyContents.insert(static_cast<GUIElement*>(currentElem));
252
253 currentElem->_markAsClean();
254
255 UINT32 numChildren = currentElem->_getNumChildren();
256 for (UINT32 i = 0; i < numChildren; i++)
257 todo.push(currentElem->_getChild(i));
258 }
259 }
260 bs_frame_clear();
261 }
262
263 void GUIWidget::_registerElement(GUIElementBase* elem)
264 {
265 assert(elem != nullptr && !elem->_isDestroyed());
266
267 if (elem->_getType() == GUIElementBase::Type::Element)
268 {
269 mElements.push_back(static_cast<GUIElement*>(elem));
270 mWidgetIsDirty = true;
271 }
272 }
273
274 void GUIWidget::_unregisterElement(GUIElementBase* elem)
275 {
276 assert(elem != nullptr);
277
278 auto iterFind = std::find(begin(mElements), end(mElements), elem);
279
280 if (iterFind != mElements.end())
281 {
282 mElements.erase(iterFind);
283 mWidgetIsDirty = true;
284 }
285
286 if (elem->_getType() == GUIElementBase::Type::Element)
287 mDirtyContents.erase(static_cast<GUIElement*>(elem));
288 }
289
290 void GUIWidget::_markMeshDirty(GUIElementBase* elem)
291 {
292 mWidgetIsDirty = true;
293 }
294
295 void GUIWidget::_markContentDirty(GUIElementBase* elem)
296 {
297 if (elem->_getType() == GUIElementBase::Type::Element)
298 mDirtyContents.insert(static_cast<GUIElement*>(elem));
299 }
300
301 void GUIWidget::setSkin(const HGUISkin& skin)
302 {
303 mSkin = skin;
304
305 for(auto& element : mElements)
306 element->_refreshStyle();
307 }
308
309 const GUISkin& GUIWidget::getSkin() const
310 {
311 if(mSkin.isLoaded())
312 return *mSkin;
313 else
314 return *BuiltinResources::instance().getGUISkin();
315 }
316
317 void GUIWidget::setCamera(const SPtr<Camera>& camera)
318 {
319 SPtr<Camera> newCamera = camera;
320 if(newCamera != nullptr)
321 {
322 if (newCamera->getViewport()->getTarget() == nullptr)
323 newCamera = nullptr;
324 }
325
326 if (mCamera == newCamera)
327 return;
328
329 GUIManager::instance().unregisterWidget(this);
330 mCamera = newCamera;
331 GUIManager::instance().registerWidget(this);
332
333 updateRootPanel();
334 }
335
336 void GUIWidget::setIsActive(bool active)
337 {
338 mIsActive = active;
339 }
340
341 bool GUIWidget::isDirty(bool cleanIfDirty)
342 {
343 if (!mIsActive)
344 return false;
345
346 const bool dirty = mWidgetIsDirty || !mDirtyContents.empty();
347
348 if(cleanIfDirty && dirty)
349 {
350 mWidgetIsDirty = false;
351
352 // Update render contents recursively because updates can cause child GUI elements to become dirty
353 while(!mDirtyContents.empty())
354 {
355 mDirtyContentsTemp.swap(mDirtyContents);
356
357 for (auto& dirtyElement : mDirtyContentsTemp)
358 dirtyElement->_updateRenderElements();
359
360 mDirtyContentsTemp.clear();
361 }
362
363 updateBounds();
364 }
365
366 return dirty;
367 }
368
369 bool GUIWidget::inBounds(const Vector2I& position) const
370 {
371 Viewport* target = getTarget();
372 if (target == nullptr)
373 return false;
374
375 // Technically GUI widget bounds can be larger than the viewport, so make sure we clip to viewport first
376 if(!target->getPixelArea().contains(position))
377 return false;
378
379 Vector3 vecPos((float)position.x, (float)position.y, 0.0f);
380 vecPos = mTransform.inverse().multiplyAffine(vecPos);
381
382 Vector2I localPos(Math::roundToInt(vecPos.x), Math::roundToInt(vecPos.y));
383 return mBounds.contains(localPos);
384 }
385
386 void GUIWidget::updateBounds() const
387 {
388 if(mElements.size() > 0)
389 mBounds = mElements[0]->_getClippedBounds();
390
391 for(auto& elem : mElements)
392 {
393 Rect2I elemBounds = elem->_getClippedBounds();
394 mBounds.encapsulate(elemBounds);
395 }
396 }
397
398 void GUIWidget::ownerWindowFocusChanged()
399 {
400 onOwnerWindowFocusChanged();
401 }
402
403 void GUIWidget::updateRootPanel()
404 {
405 Viewport* target = getTarget();
406 if (target == nullptr)
407 return;
408
409 Rect2I area = target->getPixelArea();
410 UINT32 width = area.width;
411 UINT32 height = area.height;
412
413 GUILayoutData layoutData;
414 layoutData.area.width = width;
415 layoutData.area.height = height;
416 layoutData.clipRect = Rect2I(0, 0, width, height);
417 layoutData.setWidgetDepth(mDepth);
418
419 mPanel->setWidth(width);
420 mPanel->setHeight(height);
421
422 mPanel->_setLayoutData(layoutData);
423 mPanel->_markLayoutAsDirty();
424 }
425}