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/BsGUIManager.h"
4#include "GUI/BsGUIWidget.h"
5#include "GUI/BsGUIElement.h"
6#include "Image/BsSpriteTexture.h"
7#include "Utility/BsTime.h"
8#include "Scene/BsSceneObject.h"
9#include "Material/BsMaterial.h"
10#include "Mesh/BsMeshData.h"
11#include "RenderAPI/BsVertexDataDesc.h"
12#include "Mesh/BsMesh.h"
13#include "Managers/BsRenderWindowManager.h"
14#include "Platform/BsPlatform.h"
15#include "Math/BsRect2I.h"
16#include "BsCoreApplication.h"
17#include "Error/BsException.h"
18#include "Input/BsInput.h"
19#include "GUI/BsGUIInputCaret.h"
20#include "GUI/BsGUIInputSelection.h"
21#include "GUI/BsGUIContextMenu.h"
22#include "GUI/BsDragAndDropManager.h"
23#include "GUI/BsGUIDropDownBoxManager.h"
24#include "GUI/BsGUIPanel.h"
25#include "GUI/BsGUINavGroup.h"
26#include "Profiling/BsProfilerCPU.h"
27#include "Input/BsVirtualInput.h"
28#include "Platform/BsCursor.h"
29#include "CoreThread/BsCoreThread.h"
30#include "Renderer/BsRendererManager.h"
31#include "Renderer/BsRenderer.h"
32#include "Renderer/BsCamera.h"
33#include "GUI/BsGUITooltipManager.h"
34#include "Renderer/BsRendererUtility.h"
35#include "Image/BsTexture.h"
36#include "RenderAPI/BsRenderTexture.h"
37#include "RenderAPI/BsSamplerState.h"
38#include "Managers/BsRenderStateManager.h"
39#include "Resources/BsBuiltinResources.h"
40
41using namespace std::placeholders;
42
43namespace bs
44{
45 struct GUIGroupElement
46 {
47 GUIGroupElement()
48 { }
49
50 GUIGroupElement(GUIElement* _element, UINT32 _renderElement)
51 :element(_element), renderElement(_renderElement)
52 { }
53
54 GUIElement* element;
55 UINT32 renderElement;
56 };
57
58 struct GUIMaterialGroup
59 {
60 SpriteMaterial* material;
61 SpriteMaterialInfo matInfo;
62 GUIMeshType meshType;
63 UINT32 numVertices;
64 UINT32 numIndices;
65 UINT32 depth;
66 UINT32 minDepth;
67 Rect2I bounds;
68 Vector<GUIGroupElement> elements;
69 };
70
71 const UINT32 GUIManager::DRAG_DISTANCE = 3;
72 const float GUIManager::TOOLTIP_HOVER_TIME = 1.0f;
73
74 GUIManager::GUIManager()
75 : mCoreDirty(false), mActiveMouseButton(GUIMouseButton::Left), mShowTooltip(false), mTooltipElementHoverStart(0.0f)
76 , mInputCaret(nullptr), mInputSelection(nullptr), mSeparateMeshesByWidget(true), mDragState(DragState::NoDrag)
77 , mCaretColor(1.0f, 0.6588f, 0.0f), mCaretBlinkInterval(0.5f), mCaretLastBlinkTime(0.0f), mIsCaretOn(false)
78 , mActiveCursor(CursorType::Arrow), mTextSelectionColor(0.0f, 114/255.0f, 188/255.0f)
79 {
80 // Note: Hidden dependency. GUI must receive input events before other systems, in order so it can mark them as used
81 // if required. e.g. clicking on a context menu should mark the event as used so that other non-GUI systems know
82 // that they probably should not process such event themselves.
83 mOnPointerMovedConn = gInput().onPointerMoved.connect(std::bind(&GUIManager::onPointerMoved, this, _1));
84 mOnPointerPressedConn = gInput().onPointerPressed.connect(std::bind(&GUIManager::onPointerPressed, this, _1));
85 mOnPointerReleasedConn = gInput().onPointerReleased.connect(std::bind(&GUIManager::onPointerReleased, this, _1));
86 mOnPointerDoubleClick = gInput().onPointerDoubleClick.connect(std::bind(&GUIManager::onPointerDoubleClick, this, _1));
87 mOnTextInputConn = gInput().onCharInput.connect(std::bind(&GUIManager::onTextInput, this, _1));
88 mOnInputCommandConn = gInput().onInputCommand.connect(std::bind(&GUIManager::onInputCommandEntered, this, _1));
89 mOnVirtualButtonDown = VirtualInput::instance().onButtonDown.connect(std::bind(&GUIManager::onVirtualButtonDown, this, _1, _2));
90
91 mWindowGainedFocusConn = RenderWindowManager::instance().onFocusGained.connect(std::bind(&GUIManager::onWindowFocusGained, this, _1));
92 mWindowLostFocusConn = RenderWindowManager::instance().onFocusLost.connect(std::bind(&GUIManager::onWindowFocusLost, this, _1));
93 mMouseLeftWindowConn = RenderWindowManager::instance().onMouseLeftWindow.connect(std::bind(&GUIManager::onMouseLeftWindow, this, _1));
94
95 mInputCaret = bs_new<GUIInputCaret>();
96 mInputSelection = bs_new<GUIInputSelection>();
97
98 DragAndDropManager::startUp();
99 mDragEndedConn = DragAndDropManager::instance().onDragEnded.connect(std::bind(&GUIManager::onMouseDragEnded, this, _1, _2));
100
101 GUIDropDownBoxManager::startUp();
102 GUITooltipManager::startUp();
103
104 mTriangleVertexDesc = bs_shared_ptr_new<VertexDataDesc>();
105 mTriangleVertexDesc->addVertElem(VET_FLOAT2, VES_POSITION);
106 mTriangleVertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD);
107
108 mLineVertexDesc = bs_shared_ptr_new<VertexDataDesc>();
109 mLineVertexDesc->addVertElem(VET_FLOAT2, VES_POSITION);
110
111 // Need to defer this call because I want to make sure all managers are initialized first
112 deferredCall(std::bind(&GUIManager::updateCaretTexture, this));
113 deferredCall(std::bind(&GUIManager::updateTextSelectionTexture, this));
114
115 mRenderer = RendererExtension::create<ct::GUIRenderer>(nullptr);
116 }
117
118 GUIManager::~GUIManager()
119 {
120 GUITooltipManager::shutDown();
121 GUIDropDownBoxManager::shutDown();
122 DragAndDropManager::shutDown();
123
124 // Make a copy of widgets, since destroying them will remove them from mWidgets and
125 // we can't iterate over an array thats getting modified
126 Vector<WidgetInfo> widgetCopy = mWidgets;
127 for(auto& widget : widgetCopy)
128 widget.widget->_destroy();
129
130 // Ensure everything queued get destroyed
131 processDestroyQueue();
132
133 mOnPointerPressedConn.disconnect();
134 mOnPointerReleasedConn.disconnect();
135 mOnPointerMovedConn.disconnect();
136 mOnPointerDoubleClick.disconnect();
137 mOnTextInputConn.disconnect();
138 mOnInputCommandConn.disconnect();
139 mOnVirtualButtonDown.disconnect();
140
141 mDragEndedConn.disconnect();
142
143 mWindowGainedFocusConn.disconnect();
144 mWindowLostFocusConn.disconnect();
145
146 mMouseLeftWindowConn.disconnect();
147
148 bs_delete(mInputCaret);
149 bs_delete(mInputSelection);
150
151 assert(mCachedGUIData.size() == 0);
152 }
153
154 void GUIManager::destroyCore(ct::GUIRenderer* core)
155 {
156 bs_delete(core);
157 }
158
159 void GUIManager::registerWidget(GUIWidget* widget)
160 {
161 const Viewport* renderTarget = widget->getTarget();
162 if (renderTarget == nullptr)
163 return;
164
165 mWidgets.push_back(WidgetInfo(widget));
166 auto findIter = mCachedGUIData.find(renderTarget);
167
168 if(findIter == end(mCachedGUIData))
169 mCachedGUIData[renderTarget] = GUIRenderData();
170
171 GUIRenderData& windowData = mCachedGUIData[renderTarget];
172 windowData.widgets.push_back(widget);
173 windowData.isDirty = true;
174 }
175
176 void GUIManager::unregisterWidget(GUIWidget* widget)
177 {
178 {
179 auto findIter = std::find_if(begin(mWidgets), end(mWidgets), [=] (const WidgetInfo& x) { return x.widget == widget; } );
180
181 if(findIter != mWidgets.end())
182 mWidgets.erase(findIter);
183 }
184
185 for(auto& entry : mElementsInFocus)
186 {
187 if (entry.widget == widget)
188 entry.widget = nullptr;
189 }
190
191 for (auto& entry : mElementsUnderPointer)
192 {
193 if (entry.widget == widget)
194 entry.widget = nullptr;
195 }
196
197 for (auto& entry : mActiveElements)
198 {
199 if (entry.widget == widget)
200 entry.widget = nullptr;
201 }
202
203 const Viewport* renderTarget = widget->getTarget();
204 GUIRenderData& renderData = mCachedGUIData[renderTarget];
205
206 {
207 auto findIter = std::find(begin(renderData.widgets), end(renderData.widgets), widget);
208
209 if(findIter != end(renderData.widgets))
210 renderData.widgets.erase(findIter);
211 }
212
213 if(renderData.widgets.size() == 0)
214 {
215 mCachedGUIData.erase(renderTarget);
216 mCoreDirty = true;
217 }
218 else
219 renderData.isDirty = true;
220 }
221
222 void GUIManager::update()
223 {
224 DragAndDropManager::instance()._update();
225
226 // Show tooltip if needed
227 if (mShowTooltip)
228 {
229 float diff = gTime().getTime() - mTooltipElementHoverStart;
230 if (diff >= TOOLTIP_HOVER_TIME || gInput().isButtonHeld(BC_LCONTROL) || gInput().isButtonHeld(BC_RCONTROL))
231 {
232 for(auto& entry : mElementsUnderPointer)
233 {
234 const String& tooltipText = entry.element->_getTooltip();
235 GUIWidget* parentWidget = entry.element->_getParentWidget();
236
237 if (!tooltipText.empty() && parentWidget != nullptr)
238 {
239 const RenderWindow* window = getWidgetWindow(*parentWidget);
240 if (window != nullptr)
241 {
242 Vector2I windowPos = window->screenToWindowPos(gInput().getPointerPosition());
243
244 GUITooltipManager::instance().show(*parentWidget, windowPos, tooltipText);
245 break;
246 }
247 }
248 }
249
250 mShowTooltip = false;
251 }
252 }
253
254 // Update layouts
255 gProfilerCPU().beginSample("UpdateLayout");
256 for(auto& widgetInfo : mWidgets)
257 {
258 widgetInfo.widget->_updateLayout();
259 }
260 gProfilerCPU().endSample("UpdateLayout");
261
262 // Destroy all queued elements (and loop in case any new ones get queued during destruction)
263 do
264 {
265 mNewElementsUnderPointer.clear();
266 for (auto& elementInfo : mElementsUnderPointer)
267 {
268 if (!elementInfo.element->_isDestroyed())
269 mNewElementsUnderPointer.push_back(elementInfo);
270 }
271
272 mElementsUnderPointer.swap(mNewElementsUnderPointer);
273
274 mNewActiveElements.clear();
275 for (auto& elementInfo : mActiveElements)
276 {
277 if (!elementInfo.element->_isDestroyed())
278 mNewActiveElements.push_back(elementInfo);
279 }
280
281 mActiveElements.swap(mNewActiveElements);
282
283 mNewElementsInFocus.clear();
284
285 for (auto& elementInfo : mElementsInFocus)
286 {
287 if (!elementInfo.element->_isDestroyed())
288 mNewElementsInFocus.push_back(elementInfo);
289 }
290
291 mElementsInFocus.swap(mNewElementsInFocus);
292
293 if(mForcedClearFocus)
294 {
295 // Clear focus on all elements that aren't part of the forced focus list (in case they are already in focus)
296 mCommandEvent.setType(GUICommandEventType::FocusLost);
297
298 for (auto iter = mElementsInFocus.begin(); iter != mElementsInFocus.end();)
299 {
300 const ElementFocusInfo& elementInfo = *iter;
301
302 const auto iterFind = std::find_if(begin(mForcedFocusElements), end(mForcedFocusElements),
303 [&elementInfo](const ElementForcedFocusInfo& x)
304 {
305 return x.focus && x.element == elementInfo.element;
306 });
307
308 if (iterFind == mForcedFocusElements.end())
309 {
310 sendCommandEvent(elementInfo.element, mCommandEvent);
311 iter = mElementsInFocus.erase(iter);
312 }
313 else
314 ++iter;
315 }
316
317 mForcedClearFocus = false;
318 }
319
320 for (auto& focusElementInfo : mForcedFocusElements)
321 {
322 if (focusElementInfo.element->_isDestroyed())
323 continue;
324
325 const auto iterFind = std::find_if(mElementsInFocus.begin(), mElementsInFocus.end(),
326 [&](const ElementFocusInfo& x) { return x.element == focusElementInfo.element; });
327
328 if (focusElementInfo.focus)
329 {
330 // Gain focus unless already in focus
331 if (iterFind == mElementsInFocus.end())
332 {
333 mElementsInFocus.push_back(ElementFocusInfo(focusElementInfo.element,
334 focusElementInfo.element->_getParentWidget(), false));
335
336 mCommandEvent = GUICommandEvent();
337 mCommandEvent.setType(GUICommandEventType::FocusGained);
338
339 sendCommandEvent(focusElementInfo.element, mCommandEvent);
340 }
341 }
342 else
343 {
344 // Force clear focus
345 if(iterFind != mElementsInFocus.end())
346 {
347 mCommandEvent = GUICommandEvent();
348 mCommandEvent.setType(GUICommandEventType::FocusLost);
349
350 sendCommandEvent(iterFind->element, mCommandEvent);
351 bs_swap_and_erase(mElementsInFocus, iterFind);
352 }
353 }
354 }
355
356 mForcedFocusElements.clear();
357
358 } while (processDestroyQueueIteration());
359
360 // Blink caret
361 float curTime = gTime().getTime();
362
363 if ((curTime - mCaretLastBlinkTime) >= mCaretBlinkInterval)
364 {
365 mCaretLastBlinkTime = curTime;
366 mIsCaretOn = !mIsCaretOn;
367
368 mCommandEvent = GUICommandEvent();
369 mCommandEvent.setType(GUICommandEventType::Redraw);
370
371 for (auto& elementInfo : mElementsInFocus)
372 {
373 sendCommandEvent(elementInfo.element, mCommandEvent);
374 }
375 }
376
377 PROFILE_CALL(updateMeshes(), "UpdateMeshes");
378
379 // Send potentially updated meshes to core for rendering
380 if (mCoreDirty)
381 {
382 UnorderedMap<SPtr<ct::Camera>, Vector<GUICoreRenderData>> corePerCameraData;
383
384 for (auto& viewportData : mCachedGUIData)
385 {
386 const GUIRenderData& renderData = viewportData.second;
387
388 SPtr<Camera> camera;
389 for (auto& widget : viewportData.second.widgets)
390 {
391 camera = widget->getCamera();
392 if (camera != nullptr)
393 break;
394 }
395
396 if (camera == nullptr)
397 continue;
398
399 auto insertedData = corePerCameraData.insert(std::make_pair(camera->getCore(), Vector<GUICoreRenderData>()));
400 Vector<GUICoreRenderData>& cameraData = insertedData.first->second;
401
402 for (auto& entry : renderData.cachedMeshes)
403 {
404 const SPtr<Mesh>& mesh = entry.isLine ? renderData.lineMesh : renderData.triangleMesh;
405 if(!mesh)
406 continue;
407
408 cameraData.push_back(GUICoreRenderData());
409 GUICoreRenderData& newEntry = cameraData.back();
410
411 SPtr<ct::Texture> textureCore;
412 if (entry.matInfo.texture.isLoaded())
413 textureCore = entry.matInfo.texture->getCore();
414 else
415 textureCore = nullptr;
416
417 SPtr<ct::SpriteTexture> spriteTextureCore;
418 if (entry.matInfo.spriteTexture.isLoaded())
419 spriteTextureCore = entry.matInfo.spriteTexture->getCore();
420 else
421 spriteTextureCore = nullptr;
422
423 newEntry.material = entry.material;
424 newEntry.mesh = mesh->getCore();
425 newEntry.texture = textureCore;
426 newEntry.spriteTexture = spriteTextureCore;
427 newEntry.tint = entry.matInfo.tint;
428 newEntry.animationStartTime = entry.matInfo.animationStartTime;
429 newEntry.worldTransform = entry.widget->getWorldTfrm();
430 newEntry.additionalData = entry.matInfo.additionalData;
431
432 newEntry.subMesh.indexOffset = entry.indexOffset;
433 newEntry.subMesh.indexCount = entry.indexCount;
434 newEntry.subMesh.drawOp = entry.isLine ? DOT_LINE_LIST : DOT_TRIANGLE_LIST;
435 }
436 }
437
438 gCoreThread().queueCommand(std::bind(&ct::GUIRenderer::updateData, mRenderer.get(), corePerCameraData));
439
440 mCoreDirty = false;
441 }
442
443 gCoreThread().queueCommand(std::bind(&ct::GUIRenderer::update, mRenderer.get(), gTime().getTime()));
444 }
445
446 void GUIManager::processDestroyQueue()
447 {
448 // Loop until everything empties
449 while (processDestroyQueueIteration())
450 { }
451 }
452
453 void GUIManager::updateMeshes()
454 {
455 for(auto& cachedMeshData : mCachedGUIData)
456 {
457 GUIRenderData& renderData = cachedMeshData.second;
458
459 // Check if anything is dirty. If nothing is we can skip the update
460 bool isDirty = renderData.isDirty;
461 renderData.isDirty = false;
462
463 for(auto& widget : renderData.widgets)
464 {
465 if (widget->isDirty(true))
466 {
467 isDirty = true;
468 }
469 }
470
471 if(!isDirty)
472 continue;
473
474 mCoreDirty = true;
475
476 bs_frame_mark();
477 {
478 // Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
479 auto elemComp = [](const GUIGroupElement& a, const GUIGroupElement& b)
480 {
481 UINT32 aDepth = a.element->_getRenderElementDepth(a.renderElement);
482 UINT32 bDepth = b.element->_getRenderElementDepth(b.renderElement);
483
484 // Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
485 // requires all elements to be unique
486 return (aDepth > bDepth) ||
487 (aDepth == bDepth && a.element > b.element) ||
488 (aDepth == bDepth && a.element == b.element && a.renderElement > b.renderElement);
489 };
490
491 FrameSet<GUIGroupElement, std::function<bool(const GUIGroupElement&, const GUIGroupElement&)>> allElements(elemComp);
492
493 for (auto& widget : renderData.widgets)
494 {
495 const Vector<GUIElement*>& elements = widget->getElements();
496
497 for (auto& element : elements)
498 {
499 if (!element->_isVisible())
500 continue;
501
502 UINT32 numRenderElems = element->_getNumRenderElements();
503 for (UINT32 i = 0; i < numRenderElems; i++)
504 {
505 allElements.insert(GUIGroupElement(element, i));
506 }
507 }
508 }
509
510 // Group the elements in such a way so that we end up with a smallest amount of
511 // meshes, without breaking back to front rendering order
512 FrameUnorderedMap<UINT64, FrameVector<GUIMaterialGroup>> materialGroups;
513 for (auto& elem : allElements)
514 {
515 GUIElement* guiElem = elem.element;
516 UINT32 renderElemIdx = elem.renderElement;
517 UINT32 elemDepth = guiElem->_getRenderElementDepth(renderElemIdx);
518
519 Rect2I tfrmedBounds = guiElem->_getClippedBounds();
520 tfrmedBounds.transform(guiElem->_getParentWidget()->getWorldTfrm());
521
522 SpriteMaterial* spriteMaterial = nullptr;
523 const SpriteMaterialInfo& matInfo = guiElem->_getMaterial(renderElemIdx, &spriteMaterial);
524 assert(spriteMaterial != nullptr);
525
526 UINT64 hash = spriteMaterial->getMergeHash(matInfo);
527 FrameVector<GUIMaterialGroup>& groupsPerMaterial = materialGroups[hash];
528
529 // Try to find a group this material will fit in:
530 // - Group that has a depth value same or one below elements depth will always be a match
531 // - Otherwise, we search higher depth values as well, but we only use them if no elements in between those depth values
532 // overlap the current elements bounds.
533 GUIMaterialGroup* foundGroup = nullptr;
534
535 if(spriteMaterial->allowBatching())
536 {
537 for (auto groupIter = groupsPerMaterial.rbegin(); groupIter != groupsPerMaterial.rend(); ++groupIter)
538 {
539 // If we separate meshes by widget, ignore any groups with widget parents other than mine
540 if (mSeparateMeshesByWidget)
541 {
542 if (groupIter->elements.size() > 0)
543 {
544 GUIElement* otherElem = groupIter->elements.begin()->element; // We only need to check the first element
545 if (otherElem->_getParentWidget() != guiElem->_getParentWidget())
546 continue;
547 }
548 }
549
550 GUIMaterialGroup& group = *groupIter;
551 if (group.depth == elemDepth)
552 {
553 foundGroup = &group;
554 break;
555 }
556 else
557 {
558 UINT32 startDepth = elemDepth;
559 UINT32 endDepth = group.depth;
560
561 Rect2I potentialGroupBounds = group.bounds;
562 potentialGroupBounds.encapsulate(tfrmedBounds);
563
564 bool foundOverlap = false;
565 for (auto& material : materialGroups)
566 {
567 for (auto& matGroup : material.second)
568 {
569 if (&matGroup == &group)
570 continue;
571
572 if ((matGroup.minDepth >= startDepth && matGroup.minDepth <= endDepth)
573 || (matGroup.depth >= startDepth && matGroup.depth <= endDepth))
574 {
575 if (matGroup.bounds.overlaps(potentialGroupBounds))
576 {
577 foundOverlap = true;
578 break;
579 }
580 }
581 }
582 }
583
584 if (!foundOverlap)
585 {
586 foundGroup = &group;
587 break;
588 }
589 }
590 }
591 }
592
593 if (foundGroup == nullptr)
594 {
595 groupsPerMaterial.push_back(GUIMaterialGroup());
596 foundGroup = &groupsPerMaterial[groupsPerMaterial.size() - 1];
597
598 foundGroup->depth = elemDepth;
599 foundGroup->minDepth = elemDepth;
600 foundGroup->bounds = tfrmedBounds;
601 foundGroup->elements.push_back(GUIGroupElement(guiElem, renderElemIdx));
602 foundGroup->matInfo = matInfo.clone();
603 foundGroup->material = spriteMaterial;
604
605 guiElem->_getMeshInfo(renderElemIdx, foundGroup->numVertices, foundGroup->numIndices, foundGroup->meshType);
606 }
607 else
608 {
609 foundGroup->bounds.encapsulate(tfrmedBounds);
610 foundGroup->elements.push_back(GUIGroupElement(guiElem, renderElemIdx));
611 foundGroup->minDepth = std::min(foundGroup->minDepth, elemDepth);
612
613 UINT32 numVertices;
614 UINT32 numIndices;
615 GUIMeshType meshType;
616 guiElem->_getMeshInfo(renderElemIdx, numVertices, numIndices, meshType);
617 assert(meshType == foundGroup->meshType); // It's expected that GUI element doesn't use same material for different mesh types so this should always be true
618
619 foundGroup->numVertices += numVertices;
620 foundGroup->numIndices += numIndices;
621
622 spriteMaterial->merge(foundGroup->matInfo, matInfo);
623 }
624 }
625
626 // Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
627 auto groupComp = [](GUIMaterialGroup* a, GUIMaterialGroup* b)
628 {
629 return (a->depth > b->depth) || (a->depth == b->depth && a > b);
630 // Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
631 // requires all elements to be unique
632 };
633
634 UINT32 numMeshes = 0;
635 UINT32 numIndices[2] = { 0, 0 };
636 UINT32 numVertices[2] = { 0, 0 };
637
638 FrameSet<GUIMaterialGroup*, std::function<bool(GUIMaterialGroup*, GUIMaterialGroup*)>> sortedGroups(groupComp);
639 for(auto& material : materialGroups)
640 {
641 for(auto& group : material.second)
642 {
643 sortedGroups.insert(&group);
644
645 UINT32 typeIdx = (UINT32)group.meshType;
646 numIndices[typeIdx] += group.numIndices;
647 numVertices[typeIdx] += group.numVertices;
648
649 numMeshes++;
650 }
651 }
652
653 renderData.triangleMesh = nullptr;
654 renderData.lineMesh = nullptr;
655
656 renderData.cachedMeshes.resize(numMeshes);
657
658 SPtr<MeshData> meshData[2];
659 SPtr<VertexDataDesc> vertexDesc[2] = { mTriangleVertexDesc, mLineVertexDesc };
660
661 UINT8* vertices[2] = { nullptr, nullptr };
662 UINT32* indices[2] = { nullptr, nullptr };
663
664 for(UINT32 i = 0; i < 2; i++)
665 {
666 if(numVertices[i] > 0 && numIndices[i] > 0)
667 {
668 meshData[i] = MeshData::create(numVertices[i], numIndices[i], vertexDesc[i]);
669
670 vertices[i] = meshData[i]->getElementData(VES_POSITION);
671 indices[i] = meshData[i]->getIndices32();
672 }
673 }
674
675 // Fill buffers for each group and update their meshes
676 UINT32 meshIdx = 0;
677 UINT32 vertexOffset[2] = { 0, 0 };
678 UINT32 indexOffset[2] = { 0, 0 };
679
680 for(auto& group : sortedGroups)
681 {
682 GUIWidget* widget;
683
684 if (group->elements.size() == 0)
685 widget = nullptr;
686 else
687 {
688 GUIElement* elem = group->elements.begin()->element;
689 widget = elem->_getParentWidget();
690 }
691
692 GUIMeshData& guiMeshData = renderData.cachedMeshes[meshIdx];
693 guiMeshData.matInfo = group->matInfo;
694 guiMeshData.material = group->material;
695 guiMeshData.widget = widget;
696 guiMeshData.isLine = group->meshType == GUIMeshType::Line;
697
698 UINT32 typeIdx = (UINT32)group->meshType;
699 guiMeshData.indexOffset = indexOffset[typeIdx];
700
701 UINT32 groupNumIndices = 0;
702 for(auto& matElement : group->elements)
703 {
704 matElement.element->_fillBuffer(
705 vertices[typeIdx], indices[typeIdx],
706 vertexOffset[typeIdx], indexOffset[typeIdx],
707 numVertices[typeIdx], numIndices[typeIdx], matElement.renderElement);
708
709 UINT32 elemNumVertices;
710 UINT32 elemNumIndices;
711 GUIMeshType meshType;
712 matElement.element->_getMeshInfo(matElement.renderElement, elemNumVertices, elemNumIndices, meshType);
713
714 UINT32 indexStart = indexOffset[typeIdx];
715 UINT32 indexEnd = indexStart + elemNumIndices;
716
717 for(UINT32 i = indexStart; i < indexEnd; i++)
718 indices[typeIdx][i] += vertexOffset[typeIdx];
719
720 indexOffset[typeIdx] += elemNumIndices;
721 vertexOffset[typeIdx] += elemNumVertices;
722
723 groupNumIndices += elemNumIndices;
724 }
725
726 guiMeshData.indexCount = groupNumIndices;
727
728 meshIdx++;
729 }
730
731 if(meshData[0])
732 renderData.triangleMesh = Mesh::_createPtr(meshData[0], MU_STATIC, DOT_TRIANGLE_LIST);
733
734 if(meshData[1])
735 renderData.lineMesh = Mesh::_createPtr(meshData[1], MU_STATIC, DOT_LINE_LIST);
736 }
737
738 bs_frame_clear();
739 }
740 }
741
742 void GUIManager::updateCaretTexture()
743 {
744 if(mCaretTexture == nullptr)
745 {
746 TEXTURE_DESC texDesc; // Default
747
748 HTexture newTex = Texture::create(texDesc);
749 mCaretTexture = SpriteTexture::create(newTex);
750 }
751
752 const HTexture& tex = mCaretTexture->getTexture();
753 SPtr<PixelData> data = tex->getProperties().allocBuffer(0, 0);
754
755 data->setColorAt(mCaretColor, 0, 0);
756 tex->writeData(data);
757 }
758
759 void GUIManager::updateTextSelectionTexture()
760 {
761 if(mTextSelectionTexture == nullptr)
762 {
763 TEXTURE_DESC texDesc; // Default
764
765 HTexture newTex = Texture::create(texDesc);
766 mTextSelectionTexture = SpriteTexture::create(newTex);
767 }
768
769 const HTexture& tex = mTextSelectionTexture->getTexture();
770 SPtr<PixelData> data = tex->getProperties().allocBuffer(0, 0);
771
772 data->setColorAt(mTextSelectionColor, 0, 0);
773 tex->writeData(data);
774 }
775
776 void GUIManager::onMouseDragEnded(const PointerEvent& event, DragCallbackInfo& dragInfo)
777 {
778 GUIMouseButton guiButton = buttonToGUIButton(event.button);
779
780 if(DragAndDropManager::instance().isDragInProgress() && guiButton == GUIMouseButton::Left)
781 {
782 for(auto& elementInfo : mElementsUnderPointer)
783 {
784 Vector2I localPos;
785
786 if(elementInfo.widget != nullptr)
787 localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
788
789 bool acceptDrop = true;
790 if(DragAndDropManager::instance().needsValidDropTarget())
791 {
792 acceptDrop = elementInfo.element->_acceptDragAndDrop(localPos, DragAndDropManager::instance().getDragTypeId());
793 }
794
795 if(acceptDrop)
796 {
797 mMouseEvent.setDragAndDropDroppedData(localPos, DragAndDropManager::instance().getDragTypeId(), DragAndDropManager::instance().getDragData());
798 dragInfo.processed = sendMouseEvent(elementInfo.element, mMouseEvent);
799
800 if(dragInfo.processed)
801 return;
802 }
803 }
804 }
805
806 dragInfo.processed = false;
807 }
808
809 void GUIManager::onPointerMoved(const PointerEvent& event)
810 {
811 if(event.isUsed())
812 return;
813
814 bool buttonStates[(int)GUIMouseButton::Count];
815 buttonStates[0] = event.buttonStates[0];
816 buttonStates[1] = event.buttonStates[1];
817 buttonStates[2] = event.buttonStates[2];
818
819 if(findElementUnderPointer(event.screenPos, buttonStates, event.shift, event.control, event.alt))
820 event.markAsUsed();
821
822 if(mDragState == DragState::HeldWithoutDrag)
823 {
824 UINT32 dist = mLastPointerClickPos.manhattanDist(event.screenPos);
825
826 if(dist > DRAG_DISTANCE)
827 {
828 for(auto& activeElement : mActiveElements)
829 {
830 Vector2I localPos = getWidgetRelativePos(activeElement.widget, event.screenPos);
831 Vector2I localDragStartPos = getWidgetRelativePos(activeElement.widget, mLastPointerClickPos);
832
833 mMouseEvent.setMouseDragStartData(localPos, localDragStartPos);
834 if(sendMouseEvent(activeElement.element, mMouseEvent))
835 event.markAsUsed();
836 }
837
838 mDragState = DragState::Dragging;
839 mDragStartPos = event.screenPos;
840 }
841 }
842
843 // If mouse is being held down send MouseDrag events
844 if(mDragState == DragState::Dragging)
845 {
846 for(auto& activeElement : mActiveElements)
847 {
848 if(mLastPointerScreenPos != event.screenPos)
849 {
850 Vector2I localPos = getWidgetRelativePos(activeElement.widget, event.screenPos);
851
852 mMouseEvent.setMouseDragData(localPos, event.screenPos - mDragStartPos);
853 if(sendMouseEvent(activeElement.element, mMouseEvent))
854 event.markAsUsed();
855 }
856 }
857
858 mLastPointerScreenPos = event.screenPos;
859
860 // Also if drag is in progress send DragAndDrop events
861 if(DragAndDropManager::instance().isDragInProgress())
862 {
863 bool acceptDrop = true;
864 for(auto& elementInfo : mElementsUnderPointer)
865 {
866 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
867
868 acceptDrop = true;
869 if(DragAndDropManager::instance().needsValidDropTarget())
870 {
871 acceptDrop = elementInfo.element->_acceptDragAndDrop(localPos, DragAndDropManager::instance().getDragTypeId());
872 }
873
874 if(acceptDrop)
875 {
876 mMouseEvent.setDragAndDropDraggedData(localPos, DragAndDropManager::instance().getDragTypeId(), DragAndDropManager::instance().getDragData());
877 if(sendMouseEvent(elementInfo.element, mMouseEvent))
878 {
879 event.markAsUsed();
880 break;
881 }
882 }
883 }
884
885 if(acceptDrop)
886 {
887 if(mActiveCursor != CursorType::ArrowDrag)
888 {
889 Cursor::instance().setCursor(CursorType::ArrowDrag);
890 mActiveCursor = CursorType::ArrowDrag;
891 }
892 }
893 else
894 {
895 if(mActiveCursor != CursorType::Deny)
896 {
897 Cursor::instance().setCursor(CursorType::Deny);
898 mActiveCursor = CursorType::Deny;
899 }
900 }
901 }
902 }
903 else // Otherwise, send MouseMove events if we are hovering over any element
904 {
905 if(mLastPointerScreenPos != event.screenPos)
906 {
907 bool moveProcessed = false;
908 bool hasCustomCursor = false;
909 for(auto& elementInfo : mElementsUnderPointer)
910 {
911 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
912
913 if(!moveProcessed)
914 {
915 // Send MouseMove event
916 mMouseEvent.setMouseMoveData(localPos);
917 moveProcessed = sendMouseEvent(elementInfo.element, mMouseEvent);
918
919 if(moveProcessed)
920 event.markAsUsed();
921 }
922
923 if (mDragState == DragState::NoDrag)
924 {
925 CursorType newCursor = CursorType::Arrow;
926 if(elementInfo.element->_hasCustomCursor(localPos, newCursor))
927 {
928 if(newCursor != mActiveCursor)
929 {
930 Cursor::instance().setCursor(newCursor);
931 mActiveCursor = newCursor;
932 }
933
934 hasCustomCursor = true;
935 }
936 }
937
938 if(moveProcessed)
939 break;
940 }
941
942 // While dragging we don't want to modify the cursor
943 if (mDragState == DragState::NoDrag)
944 {
945 if (!hasCustomCursor)
946 {
947 if (mActiveCursor != CursorType::Arrow)
948 {
949 Cursor::instance().setCursor(CursorType::Arrow);
950 mActiveCursor = CursorType::Arrow;
951 }
952 }
953 }
954 }
955
956 mLastPointerScreenPos = event.screenPos;
957
958 if(Math::abs(event.mouseWheelScrollAmount) > 0.00001f)
959 {
960 for(auto& elementInfo : mElementsUnderPointer)
961 {
962 mMouseEvent.setMouseWheelScrollData(event.mouseWheelScrollAmount);
963 if(sendMouseEvent(elementInfo.element, mMouseEvent))
964 {
965 event.markAsUsed();
966 break;
967 }
968 }
969 }
970 }
971 }
972
973 void GUIManager::onPointerReleased(const PointerEvent& event)
974 {
975 if(event.isUsed())
976 return;
977
978 bool buttonStates[(int)GUIMouseButton::Count];
979 buttonStates[0] = event.buttonStates[0];
980 buttonStates[1] = event.buttonStates[1];
981 buttonStates[2] = event.buttonStates[2];
982
983 if(findElementUnderPointer(event.screenPos, buttonStates, event.shift, event.control, event.alt))
984 event.markAsUsed();
985
986 mMouseEvent = GUIMouseEvent(buttonStates, event.shift, event.control, event.alt);
987
988 GUIMouseButton guiButton = buttonToGUIButton(event.button);
989
990 // Send MouseUp event only if we are over the active element (we don't want to accidentally trigger other elements).
991 // And only activate when a button that originally caused the active state is released, otherwise ignore it.
992 if(mActiveMouseButton == guiButton)
993 {
994 for(auto& elementInfo : mElementsUnderPointer)
995 {
996 auto iterFind2 = std::find_if(mActiveElements.begin(), mActiveElements.end(),
997 [&](const ElementInfo& x) { return x.element == elementInfo.element; });
998
999 if(iterFind2 != mActiveElements.end())
1000 {
1001 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
1002 mMouseEvent.setMouseUpData(localPos, guiButton);
1003
1004 if(sendMouseEvent(elementInfo.element, mMouseEvent))
1005 {
1006 event.markAsUsed();
1007 break;
1008 }
1009 }
1010 }
1011 }
1012
1013 // Send DragEnd event to whichever element is active
1014 bool acceptEndDrag = (mDragState == DragState::Dragging || mDragState == DragState::HeldWithoutDrag) && mActiveMouseButton == guiButton &&
1015 (guiButton == GUIMouseButton::Left);
1016
1017 if(acceptEndDrag)
1018 {
1019 if(mDragState == DragState::Dragging)
1020 {
1021 for(auto& activeElement : mActiveElements)
1022 {
1023 Vector2I localPos = getWidgetRelativePos(activeElement.widget, event.screenPos);
1024
1025 mMouseEvent.setMouseDragEndData(localPos);
1026 if(sendMouseEvent(activeElement.element, mMouseEvent))
1027 event.markAsUsed();
1028 }
1029 }
1030
1031 mDragState = DragState::NoDrag;
1032 }
1033
1034 if(mActiveMouseButton == guiButton)
1035 {
1036 mActiveElements.clear();
1037 mActiveMouseButton = GUIMouseButton::Left;
1038 }
1039
1040 if(mActiveCursor != CursorType::Arrow)
1041 {
1042 Cursor::instance().setCursor(CursorType::Arrow);
1043 mActiveCursor = CursorType::Arrow;
1044 }
1045 }
1046
1047 void GUIManager::onPointerPressed(const PointerEvent& event)
1048 {
1049 if(event.isUsed())
1050 return;
1051
1052 bool buttonStates[(int)GUIMouseButton::Count];
1053 buttonStates[0] = event.buttonStates[0];
1054 buttonStates[1] = event.buttonStates[1];
1055 buttonStates[2] = event.buttonStates[2];
1056
1057 if(findElementUnderPointer(event.screenPos, buttonStates, event.shift, event.control, event.alt))
1058 event.markAsUsed();
1059
1060 // Determine elements that gained focus
1061 mNewElementsInFocus.clear();
1062
1063 mCommandEvent = GUICommandEvent();
1064
1065 // Determine elements that gained focus
1066 for (auto& elementInfo : mElementsUnderPointer)
1067 {
1068 auto iterFind = std::find_if(begin(mElementsInFocus), end(mElementsInFocus),
1069 [=](const ElementFocusInfo& x) { return x.element == elementInfo.element; });
1070
1071 if (iterFind == mElementsInFocus.end())
1072 {
1073 bool processed = !elementInfo.element->getOptionFlags().isSet(GUIElementOption::ClickThrough);
1074 mNewElementsInFocus.push_back(ElementFocusInfo(elementInfo.element, elementInfo.widget, processed));
1075
1076 if (processed)
1077 break;
1078 }
1079 else
1080 {
1081 mNewElementsInFocus.push_back(*iterFind);
1082
1083 if (iterFind->usesFocus)
1084 break;
1085 }
1086 }
1087
1088 // Send focus loss events
1089 // Note: Focus loss must trigger before mouse press because things like input boxes often only confirm changes
1090 // made to them when focus is lost. So if the user is confirming some input via a press of the button focus loss
1091 // must trigger on the input box first to make sure its contents get saved.
1092 mCommandEvent.setType(GUICommandEventType::FocusLost);
1093
1094 for (auto& elementInfo : mElementsInFocus)
1095 {
1096 auto iterFind = std::find_if(begin(mNewElementsInFocus), end(mNewElementsInFocus),
1097 [=](const ElementFocusInfo& x) { return x.element == elementInfo.element; });
1098
1099 if (iterFind == mNewElementsInFocus.end())
1100 sendCommandEvent(elementInfo.element, mCommandEvent);
1101 }
1102
1103 // Send focus gain events
1104 mCommandEvent.setType(GUICommandEventType::FocusGained);
1105
1106 for(auto& elementInfo : mNewElementsInFocus)
1107 {
1108 auto iterFind = std::find_if(begin(mElementsInFocus), end(mElementsInFocus),
1109 [=](const ElementFocusInfo& x) { return x.element == elementInfo.element; });
1110
1111 if (iterFind == mElementsInFocus.end())
1112 sendCommandEvent(elementInfo.element, mCommandEvent);
1113 }
1114
1115 mElementsInFocus.swap(mNewElementsInFocus);
1116
1117 // Send mouse press event
1118 mMouseEvent = GUIMouseEvent(buttonStates, event.shift, event.control, event.alt);
1119 GUIMouseButton guiButton = buttonToGUIButton(event.button);
1120
1121 // We only check for mouse down if mouse isn't already being held down, and we are hovering over an element
1122 if(mActiveElements.size() == 0)
1123 {
1124 mNewActiveElements.clear();
1125 for(auto& elementInfo : mElementsUnderPointer)
1126 {
1127 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
1128 mMouseEvent.setMouseDownData(localPos, guiButton);
1129
1130 bool processed = sendMouseEvent(elementInfo.element, mMouseEvent);
1131
1132 if(guiButton == GUIMouseButton::Left)
1133 {
1134 mDragState = DragState::HeldWithoutDrag;
1135 mLastPointerClickPos = event.screenPos;
1136 }
1137
1138 mNewActiveElements.push_back(ElementInfo(elementInfo.element, elementInfo.widget));
1139 mActiveMouseButton = guiButton;
1140
1141 if(processed)
1142 {
1143 event.markAsUsed();
1144 break;
1145 }
1146 }
1147
1148 mActiveElements.swap(mNewActiveElements);
1149 }
1150
1151 // If right click try to open context menu
1152 if(buttonStates[2])
1153 {
1154 for(auto& elementInfo : mElementsUnderPointer)
1155 {
1156 SPtr<GUIContextMenu> menu = elementInfo.element->_getContextMenu();
1157
1158 if(menu != nullptr && elementInfo.widget != nullptr)
1159 {
1160 const RenderWindow* window = getWidgetWindow(*elementInfo.widget);
1161 if (window != nullptr)
1162 {
1163 Vector2I windowPos = window->screenToWindowPos(event.screenPos);
1164
1165 menu->open(windowPos, *elementInfo.widget);
1166 event.markAsUsed();
1167 break;
1168 }
1169 }
1170 }
1171 }
1172 }
1173
1174 void GUIManager::onPointerDoubleClick(const PointerEvent& event)
1175 {
1176 if(event.isUsed())
1177 return;
1178
1179 bool buttonStates[(int)GUIMouseButton::Count];
1180 buttonStates[0] = event.buttonStates[0];
1181 buttonStates[1] = event.buttonStates[1];
1182 buttonStates[2] = event.buttonStates[2];
1183
1184 if(findElementUnderPointer(event.screenPos, buttonStates, event.shift, event.control, event.alt))
1185 event.markAsUsed();
1186
1187 mMouseEvent = GUIMouseEvent(buttonStates, event.shift, event.control, event.alt);
1188
1189 GUIMouseButton guiButton = buttonToGUIButton(event.button);
1190
1191 // We only check for mouse down if we are hovering over an element
1192 for(auto& elementInfo : mElementsUnderPointer)
1193 {
1194 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, event.screenPos);
1195
1196 mMouseEvent.setMouseDoubleClickData(localPos, guiButton);
1197 if(sendMouseEvent(elementInfo.element, mMouseEvent))
1198 {
1199 event.markAsUsed();
1200 break;
1201 }
1202 }
1203 }
1204
1205 void GUIManager::onInputCommandEntered(InputCommandType commandType)
1206 {
1207 if(mElementsInFocus.empty())
1208 return;
1209
1210 hideTooltip();
1211
1212 // Tabs are handled by the GUI manager itself, while other events are passed to GUI elements
1213 if(commandType == InputCommandType::Tab)
1214 {
1215 tabFocusNext();
1216 return;
1217 }
1218
1219 mCommandEvent = GUICommandEvent();
1220
1221 switch(commandType)
1222 {
1223 case InputCommandType::Backspace:
1224 mCommandEvent.setType(GUICommandEventType::Backspace);
1225 break;
1226 case InputCommandType::Delete:
1227 mCommandEvent.setType(GUICommandEventType::Delete);
1228 break;
1229 case InputCommandType::Return:
1230 mCommandEvent.setType(GUICommandEventType::Return);
1231 break;
1232 case InputCommandType::Confirm:
1233 mCommandEvent.setType(GUICommandEventType::Confirm);
1234 break;
1235 case InputCommandType::Escape:
1236 mCommandEvent.setType(GUICommandEventType::Escape);
1237 break;
1238 case InputCommandType::CursorMoveLeft:
1239 mCommandEvent.setType(GUICommandEventType::MoveLeft);
1240 break;
1241 case InputCommandType::CursorMoveRight:
1242 mCommandEvent.setType(GUICommandEventType::MoveRight);
1243 break;
1244 case InputCommandType::CursorMoveUp:
1245 mCommandEvent.setType(GUICommandEventType::MoveUp);
1246 break;
1247 case InputCommandType::CursorMoveDown:
1248 mCommandEvent.setType(GUICommandEventType::MoveDown);
1249 break;
1250 case InputCommandType::SelectLeft:
1251 mCommandEvent.setType(GUICommandEventType::SelectLeft);
1252 break;
1253 case InputCommandType::SelectRight:
1254 mCommandEvent.setType(GUICommandEventType::SelectRight);
1255 break;
1256 case InputCommandType::SelectUp:
1257 mCommandEvent.setType(GUICommandEventType::SelectUp);
1258 break;
1259 case InputCommandType::SelectDown:
1260 mCommandEvent.setType(GUICommandEventType::SelectDown);
1261 break;
1262 default:
1263 break;
1264 }
1265
1266 for(auto& elementInfo : mElementsInFocus)
1267 sendCommandEvent(elementInfo.element, mCommandEvent);
1268 }
1269
1270 void GUIManager::onVirtualButtonDown(const VirtualButton& button, UINT32 deviceIdx)
1271 {
1272 hideTooltip();
1273 mVirtualButtonEvent.setButton(button);
1274
1275 for(auto& elementInFocus : mElementsInFocus)
1276 {
1277 bool processed = sendVirtualButtonEvent(elementInFocus.element, mVirtualButtonEvent);
1278
1279 if(processed)
1280 break;
1281 }
1282 }
1283
1284 bool GUIManager::findElementUnderPointer(const Vector2I& pointerScreenPos, bool buttonStates[3], bool shift, bool control, bool alt)
1285 {
1286 Vector<const RenderWindow*> widgetWindows;
1287 for(auto& widgetInfo : mWidgets)
1288 widgetWindows.push_back(getWidgetWindow(*widgetInfo.widget));
1289
1290#if BS_DEBUG_MODE
1291 // Checks if all referenced windows actually exist
1292 Vector<RenderWindow*> activeWindows = RenderWindowManager::instance().getRenderWindows();
1293 for(auto& window : widgetWindows)
1294 {
1295 if(window == nullptr)
1296 continue;
1297
1298 auto iterFind = std::find(begin(activeWindows), end(activeWindows), window);
1299
1300 if(iterFind == activeWindows.end())
1301 {
1302 BS_EXCEPT(InternalErrorException, "GUI manager has a reference to a window that doesn't exist. \
1303 Please detach all GUIWidgets from windows before destroying a window.");
1304 }
1305 }
1306#endif
1307
1308 mNewElementsUnderPointer.clear();
1309
1310 const RenderWindow* windowUnderPointer = nullptr;
1311 UnorderedSet<const RenderWindow*> uniqueWindows;
1312
1313 for(auto& window : widgetWindows)
1314 {
1315 if(window == nullptr)
1316 continue;
1317
1318 uniqueWindows.insert(window);
1319 }
1320
1321 RenderWindow* topMostModal = RenderWindowManager::instance().getTopMostModal();
1322 for(auto& window : uniqueWindows)
1323 {
1324 if(Platform::isPointOverWindow(*window, pointerScreenPos))
1325 {
1326 // If there's a top most modal window, it needs to be this one, otherwise we ignore input to that window
1327 if(topMostModal == nullptr || window == topMostModal)
1328 windowUnderPointer = window;
1329
1330 break;
1331 }
1332 }
1333
1334 if(windowUnderPointer != nullptr)
1335 {
1336 Vector2I windowPos = windowUnderPointer->screenToWindowPos(pointerScreenPos);
1337
1338 UINT32 widgetIdx = 0;
1339 for(auto& widgetInfo : mWidgets)
1340 {
1341 if(widgetWindows[widgetIdx] == nullptr)
1342 {
1343 widgetIdx++;
1344 continue;
1345 }
1346
1347 GUIWidget* widget = widgetInfo.widget;
1348 if(widgetWindows[widgetIdx] == windowUnderPointer
1349 && widget->inBounds(windowToBridgedCoords(widget->getTarget()->getTarget(), windowPos)))
1350 {
1351 const Vector<GUIElement*>& elements = widget->getElements();
1352 Vector2I localPos = getWidgetRelativePos(widget, pointerScreenPos);
1353
1354 // Elements with lowest depth (most to the front) get handled first
1355 for(auto iter = elements.begin(); iter != elements.end(); ++iter)
1356 {
1357 GUIElement* element = *iter;
1358
1359 if(element->_isVisible() && element->_isInBounds(localPos))
1360 {
1361 ElementInfoUnderPointer elementInfo(element, widget);
1362
1363 auto iterFind = std::find_if(mElementsUnderPointer.begin(), mElementsUnderPointer.end(),
1364 [=](const ElementInfoUnderPointer& x) { return x.element == element; });
1365
1366 if (iterFind != mElementsUnderPointer.end())
1367 {
1368 elementInfo.usesMouseOver = iterFind->usesMouseOver;
1369 elementInfo.receivedMouseOver = iterFind->receivedMouseOver;
1370 }
1371
1372 mNewElementsUnderPointer.push_back(elementInfo);
1373 }
1374 }
1375 }
1376
1377 widgetIdx++;
1378 }
1379 }
1380
1381 std::sort(mNewElementsUnderPointer.begin(), mNewElementsUnderPointer.end(),
1382 [](const ElementInfoUnderPointer& a, const ElementInfoUnderPointer& b)
1383 {
1384 return a.element->_getDepth() < b.element->_getDepth();
1385 });
1386
1387 // Send MouseOut and MouseOver events
1388 bool eventProcessed = false;
1389
1390 for (auto& elementInfo : mNewElementsUnderPointer)
1391 {
1392 GUIElement* element = elementInfo.element;
1393 GUIWidget* widget = elementInfo.widget;
1394
1395 if (elementInfo.receivedMouseOver)
1396 {
1397 elementInfo.isHovering = true;
1398 if (elementInfo.usesMouseOver)
1399 break;
1400
1401 continue;
1402 }
1403
1404 auto iterFind = std::find_if(mActiveElements.begin(), mActiveElements.end(),
1405 [&](const ElementInfo& x) { return x.element == element; });
1406
1407 // Send MouseOver event
1408 if (mActiveElements.size() == 0 || iterFind != mActiveElements.end())
1409 {
1410 Vector2I localPos = getWidgetRelativePos(widget, pointerScreenPos);
1411
1412 mMouseEvent = GUIMouseEvent(buttonStates, shift, control, alt);
1413
1414 mMouseEvent.setMouseOverData(localPos);
1415 elementInfo.receivedMouseOver = true;
1416 elementInfo.isHovering = true;
1417 if (sendMouseEvent(element, mMouseEvent))
1418 {
1419 eventProcessed = true;
1420 elementInfo.usesMouseOver = true;
1421 break;
1422 }
1423 }
1424 }
1425
1426 // Send DragAndDropLeft event - It is similar to MouseOut events but we send it to all
1427 // elements a user might hover over, while we send mouse over/out events only to active elements while dragging
1428 if (DragAndDropManager::instance().isDragInProgress())
1429 {
1430 for (auto& elementInfo : mElementsUnderPointer)
1431 {
1432 auto iterFind = std::find_if(mNewElementsUnderPointer.begin(), mNewElementsUnderPointer.end(),
1433 [=](const ElementInfoUnderPointer& x) { return x.element == elementInfo.element; });
1434
1435 if (iterFind == mNewElementsUnderPointer.end())
1436 {
1437 Vector2I localPos = getWidgetRelativePos(elementInfo.widget, pointerScreenPos);
1438
1439 mMouseEvent.setDragAndDropLeftData(localPos, DragAndDropManager::instance().getDragTypeId(), DragAndDropManager::instance().getDragData());
1440 if (sendMouseEvent(elementInfo.element, mMouseEvent))
1441 {
1442 eventProcessed = true;
1443 break;
1444 }
1445 }
1446 }
1447 }
1448
1449 for(auto& elementInfo : mElementsUnderPointer)
1450 {
1451 GUIElement* element = elementInfo.element;
1452 GUIWidget* widget = elementInfo.widget;
1453
1454 auto iterFind = std::find_if(mNewElementsUnderPointer.begin(), mNewElementsUnderPointer.end(),
1455 [=](const ElementInfoUnderPointer& x) { return x.element == element; });
1456
1457 if (!elementInfo.receivedMouseOver)
1458 continue;
1459
1460 if (iterFind == mNewElementsUnderPointer.end() || !iterFind->isHovering)
1461 {
1462 auto iterFind2 = std::find_if(mActiveElements.begin(), mActiveElements.end(),
1463 [=](const ElementInfo& x) { return x.element == element; });
1464
1465 // Send MouseOut event
1466 if(mActiveElements.size() == 0 || iterFind2 != mActiveElements.end())
1467 {
1468 Vector2I localPos = getWidgetRelativePos(widget, pointerScreenPos);
1469
1470 mMouseEvent.setMouseOutData(localPos);
1471 if (sendMouseEvent(element, mMouseEvent))
1472 {
1473 eventProcessed = true;
1474 break;
1475 }
1476 }
1477 }
1478 }
1479
1480 mElementsUnderPointer.swap(mNewElementsUnderPointer);
1481
1482 // Tooltip
1483 hideTooltip();
1484 if (mElementsUnderPointer.size() > 0)
1485 mShowTooltip = true;
1486
1487 mTooltipElementHoverStart = gTime().getTime();
1488
1489 return eventProcessed;
1490 }
1491
1492 void GUIManager::onTextInput(const TextInputEvent& event)
1493 {
1494 mTextInputEvent = GUITextInputEvent();
1495 mTextInputEvent.setData(event.textChar);
1496
1497 for(auto& elementInFocus : mElementsInFocus)
1498 {
1499 if(sendTextInputEvent(elementInFocus.element, mTextInputEvent))
1500 event.markAsUsed();
1501 }
1502 }
1503
1504 void GUIManager::onWindowFocusGained(RenderWindow& win)
1505 {
1506 for(auto& widgetInfo : mWidgets)
1507 {
1508 GUIWidget* widget = widgetInfo.widget;
1509 if(getWidgetWindow(*widget) == &win)
1510 widget->ownerWindowFocusChanged();
1511 }
1512 }
1513
1514 void GUIManager::onWindowFocusLost(RenderWindow& win)
1515 {
1516 for(auto& widgetInfo : mWidgets)
1517 {
1518 GUIWidget* widget = widgetInfo.widget;
1519 if(getWidgetWindow(*widget) == &win)
1520 widget->ownerWindowFocusChanged();
1521 }
1522
1523 mNewElementsInFocus.clear();
1524 for(auto& focusedElement : mElementsInFocus)
1525 {
1526 if (focusedElement.element->_isDestroyed())
1527 continue;
1528
1529 if (focusedElement.widget != nullptr && getWidgetWindow(*focusedElement.widget) == &win)
1530 {
1531 mCommandEvent = GUICommandEvent();
1532 mCommandEvent.setType(GUICommandEventType::FocusLost);
1533
1534 sendCommandEvent(focusedElement.element, mCommandEvent);
1535 }
1536 else
1537 mNewElementsInFocus.push_back(focusedElement);
1538 }
1539
1540 mElementsInFocus.swap(mNewElementsInFocus);
1541 }
1542
1543 // We stop getting mouse move events once it leaves the window, so make sure
1544 // nothing stays in hover state
1545 void GUIManager::onMouseLeftWindow(RenderWindow& win)
1546 {
1547 mNewElementsUnderPointer.clear();
1548
1549 for(auto& elementInfo : mElementsUnderPointer)
1550 {
1551 GUIElement* element = elementInfo.element;
1552 GUIWidget* widget = elementInfo.widget;
1553
1554 if (widget != nullptr && widget->getTarget()->getTarget().get() != &win)
1555 {
1556 mNewElementsUnderPointer.push_back(elementInfo);
1557 continue;
1558 }
1559
1560 auto iterFind = std::find_if(mActiveElements.begin(), mActiveElements.end(),
1561 [&](const ElementInfo& x) { return x.element == element; });
1562
1563 // Send MouseOut event
1564 if(mActiveElements.size() == 0 || iterFind != mActiveElements.end())
1565 {
1566 Vector2I localPos = getWidgetRelativePos(widget, Vector2I());
1567
1568 mMouseEvent.setMouseOutData(localPos);
1569 sendMouseEvent(element, mMouseEvent);
1570 }
1571 }
1572
1573 mElementsUnderPointer.swap(mNewElementsUnderPointer);
1574
1575 hideTooltip();
1576 if(mDragState != DragState::Dragging)
1577 {
1578 if(mActiveCursor != CursorType::Arrow)
1579 {
1580 Cursor::instance().setCursor(CursorType::Arrow);
1581 mActiveCursor = CursorType::Arrow;
1582 }
1583 }
1584 }
1585
1586 void GUIManager::hideTooltip()
1587 {
1588 GUITooltipManager::instance().hide();
1589 mShowTooltip = false;
1590 }
1591
1592 void GUIManager::queueForDestroy(GUIElement* element)
1593 {
1594 mScheduledForDestruction.push(element);
1595 }
1596
1597 void GUIManager::setFocus(GUIElement* element, bool focus, bool clear)
1598 {
1599 ElementForcedFocusInfo efi;
1600 efi.element = element;
1601 efi.focus = focus;
1602
1603 if(clear)
1604 {
1605 mForcedClearFocus = true;
1606 mForcedFocusElements.clear();
1607 }
1608
1609 mForcedFocusElements.push_back(efi);
1610 }
1611
1612 bool GUIManager::processDestroyQueueIteration()
1613 {
1614 Stack<GUIElement*> toDestroy = mScheduledForDestruction;
1615 mScheduledForDestruction = Stack<GUIElement*>();
1616
1617 while (!toDestroy.empty())
1618 {
1619 bs_delete(toDestroy.top());
1620 toDestroy.pop();
1621 }
1622
1623 return !mScheduledForDestruction.empty();
1624 }
1625
1626 void GUIManager::setInputBridge(const RenderTexture* renderTex, const GUIElement* element)
1627 {
1628 if(element == nullptr)
1629 mInputBridge.erase(renderTex);
1630 else
1631 mInputBridge[renderTex] = element;
1632 }
1633
1634 GUIMouseButton GUIManager::buttonToGUIButton(PointerEventButton pointerButton) const
1635 {
1636 if(pointerButton == PointerEventButton::Left)
1637 return GUIMouseButton::Left;
1638 else if(pointerButton == PointerEventButton::Middle)
1639 return GUIMouseButton::Middle;
1640 else if(pointerButton == PointerEventButton::Right)
1641 return GUIMouseButton::Right;
1642
1643 BS_EXCEPT(InvalidParametersException, "Provided button is not a GUI supported mouse button.");
1644 return GUIMouseButton::Left;
1645 }
1646
1647 Vector2I GUIManager::getWidgetRelativePos(const GUIWidget* widget, const Vector2I& screenPos) const
1648 {
1649 if (widget == nullptr)
1650 return screenPos;
1651
1652 const RenderWindow* window = getWidgetWindow(*widget);
1653 if(window == nullptr)
1654 return Vector2I();
1655
1656 Vector2I windowPos = window->screenToWindowPos(screenPos);
1657 windowPos = windowToBridgedCoords(widget->getTarget()->getTarget(), windowPos);
1658
1659 const Matrix4& worldTfrm = widget->getWorldTfrm();
1660
1661 Vector4 vecLocalPos = worldTfrm.inverse().multiplyAffine(Vector4((float)windowPos.x, (float)windowPos.y, 0.0f, 1.0f));
1662 Vector2I curLocalPos(Math::roundToInt(vecLocalPos.x), Math::roundToInt(vecLocalPos.y));
1663
1664 return curLocalPos;
1665 }
1666
1667 Vector2I GUIManager::windowToBridgedCoords(const SPtr<RenderTarget>& target, const Vector2I& windowPos) const
1668 {
1669 // This cast might not be valid (the render target could be a window), but we only really need to cast
1670 // so that mInputBridge map allows us to search through it - we don't access anything unless the target is bridged
1671 // (in which case we know it is a RenderTexture)
1672 const RenderTexture* renderTexture = static_cast<const RenderTexture*>(target.get());
1673 const RenderTargetProperties& rtProps = renderTexture->getProperties();
1674
1675 auto iterFind = mInputBridge.find(renderTexture);
1676 if(iterFind != mInputBridge.end()) // Widget input is bridged, which means we need to transform the coordinates
1677 {
1678 const GUIElement* bridgeElement = iterFind->second;
1679 const GUIWidget* parentWidget = bridgeElement->_getParentWidget();
1680 if (parentWidget == nullptr)
1681 return windowPos;
1682
1683 const Matrix4& worldTfrm = parentWidget->getWorldTfrm();
1684
1685 Vector4 vecLocalPos = worldTfrm.inverse().multiplyAffine(Vector4((float)windowPos.x, (float)windowPos.y, 0.0f, 1.0f));
1686 Rect2I bridgeBounds = bridgeElement->_getLayoutData().area;
1687
1688 // Find coordinates relative to the bridge element
1689 float x = vecLocalPos.x - (float)bridgeBounds.x;
1690 float y = vecLocalPos.y - (float)bridgeBounds.y;
1691
1692 float scaleX = bridgeBounds.width > 0 ? rtProps.width / (float)bridgeBounds.width : 0.0f;
1693 float scaleY = bridgeBounds.height > 0 ? rtProps.height / (float)bridgeBounds.height : 0.0f;
1694
1695 return Vector2I(Math::roundToInt(x * scaleX), Math::roundToInt(y * scaleY));
1696 }
1697
1698 return windowPos;
1699 }
1700
1701 const RenderWindow* GUIManager::getWidgetWindow(const GUIWidget& widget) const
1702 {
1703 const Viewport* viewport = widget.getTarget();
1704 if (viewport == nullptr)
1705 return nullptr;
1706
1707 SPtr<RenderTarget> target = viewport->getTarget();
1708 if (target == nullptr)
1709 return nullptr;
1710
1711 // This cast might not be valid (the render target could be a window), but we only really need to cast
1712 // so that mInputBridge map allows us to search through it - we don't access anything unless the target is bridged
1713 // (in which case we know it is a RenderTexture)
1714 const RenderTexture* renderTexture = static_cast<const RenderTexture*>(target.get());
1715
1716 auto iterFind = mInputBridge.find(renderTexture);
1717 if(iterFind != mInputBridge.end())
1718 {
1719 GUIWidget* parentWidget = iterFind->second->_getParentWidget();
1720 if (parentWidget == nullptr)
1721 return nullptr;
1722
1723 if(parentWidget != &widget)
1724 return getWidgetWindow(*parentWidget);
1725 }
1726
1727 Vector<RenderWindow*> renderWindows = RenderWindowManager::instance().getRenderWindows();
1728
1729 auto iterFindWin = std::find(renderWindows.begin(), renderWindows.end(), target.get());
1730 if(iterFindWin != renderWindows.end())
1731 return static_cast<RenderWindow*>(target.get());
1732
1733 return nullptr;
1734 }
1735
1736 SPtr<RenderWindow> GUIManager::getBridgeWindow(const SPtr<RenderTexture>& target) const
1737 {
1738 if (target == nullptr)
1739 return nullptr;
1740
1741 while (true)
1742 {
1743 auto iterFind = mInputBridge.find(target.get());
1744 if (iterFind == mInputBridge.end())
1745 return nullptr;
1746
1747 GUIWidget* parentWidget = iterFind->second->_getParentWidget();
1748 if (parentWidget == nullptr)
1749 return nullptr;
1750
1751 SPtr<RenderTarget> curTarget = parentWidget->getTarget()->getTarget();
1752 if (curTarget == nullptr)
1753 return nullptr;
1754
1755 if (curTarget == target)
1756 return nullptr;
1757
1758 if (curTarget->getProperties().isWindow)
1759 return std::static_pointer_cast<RenderWindow>(curTarget);
1760 }
1761
1762 return nullptr;
1763 }
1764
1765 void GUIManager::tabFocusFirst()
1766 {
1767 UINT32 nearestDist = std::numeric_limits<UINT32>::max();
1768 GUIElement* closestElement = nullptr;
1769
1770 // Find to top-left most element
1771 for (auto& widgetInfo : mWidgets)
1772 {
1773 const RenderWindow* window = getWidgetWindow(*widgetInfo.widget);
1774
1775 const Vector<GUIElement*>& elements = widgetInfo.widget->getElements();
1776 for (auto& element : elements)
1777 {
1778 const bool acceptsKeyFocus = element->getOptionFlags().isSet(GUIElementOption::AcceptsKeyFocus);
1779 if (!acceptsKeyFocus || element->_isDisabled() || !element->_isVisible())
1780 continue;
1781
1782 const Rect2I elemBounds = element->_getClippedBounds();
1783 const bool isFullyClipped = element->_getClippedBounds().width == 0 ||
1784 element->_getClippedBounds().height == 0;
1785
1786 if (isFullyClipped)
1787 continue;
1788
1789 Vector2I elementPos(elemBounds.x, elemBounds.y);
1790 Vector2I screenPos = window->windowToScreenPos(elementPos);
1791
1792 const UINT32 dist = screenPos.squaredLength();
1793 if (dist < nearestDist)
1794 {
1795 nearestDist = dist;
1796 closestElement = element;
1797 }
1798 }
1799 }
1800
1801 if (closestElement == nullptr)
1802 return;
1803
1804 // Don't use the element directly though, since its tab group could have explicit ordering
1805 const SPtr<GUINavGroup>& navGroup = closestElement->_getNavGroup();
1806 navGroup->focusFirst();
1807 }
1808
1809 void GUIManager::tabFocusNext()
1810 {
1811 for(auto& entry : mElementsInFocus)
1812 {
1813 const SPtr<GUINavGroup>& navGroup = entry.element->_getNavGroup();
1814 GUIElementOptions elementOptions = entry.element->getOptionFlags();
1815 if(elementOptions.isSet(GUIElementOption::AcceptsKeyFocus) && navGroup != nullptr)
1816 {
1817 navGroup->focusNext(entry.element);
1818 return;
1819 }
1820 }
1821
1822 // Nothing currently in focus
1823 tabFocusFirst();
1824 }
1825
1826 bool GUIManager::sendMouseEvent(GUIElement* element, const GUIMouseEvent& event)
1827 {
1828 if (element->_isDestroyed())
1829 return false;
1830
1831 return element->_mouseEvent(event);
1832 }
1833
1834 bool GUIManager::sendTextInputEvent(GUIElement* element, const GUITextInputEvent& event)
1835 {
1836 if (element->_isDestroyed())
1837 return false;
1838
1839 return element->_textInputEvent(event);
1840 }
1841
1842 bool GUIManager::sendCommandEvent(GUIElement* element, const GUICommandEvent& event)
1843 {
1844 if (element->_isDestroyed())
1845 return false;
1846
1847 return element->_commandEvent(event);
1848 }
1849
1850 bool GUIManager::sendVirtualButtonEvent(GUIElement* element, const GUIVirtualButtonEvent& event)
1851 {
1852 if (element->_isDestroyed())
1853 return false;
1854
1855 return element->_virtualButtonEvent(event);
1856 }
1857
1858 GUIManager& gGUIManager()
1859 {
1860 return GUIManager::instance();
1861 }
1862
1863 namespace ct
1864 {
1865 GUISpriteParamBlockDef gGUISpriteParamBlockDef;
1866
1867 GUIRenderer::GUIRenderer()
1868 :RendererExtension(RenderLocation::Overlay, 10)
1869 { }
1870
1871 void GUIRenderer::initialize(const Any& data)
1872 {
1873 SAMPLER_STATE_DESC ssDesc;
1874 ssDesc.magFilter = FO_POINT;
1875 ssDesc.minFilter = FO_POINT;
1876 ssDesc.mipFilter = FO_POINT;
1877
1878 mSamplerState = RenderStateManager::instance().createSamplerState(ssDesc);
1879 }
1880
1881 bool GUIRenderer::check(const Camera& camera)
1882 {
1883 auto iterFind = mPerCameraData.find(&camera);
1884 return iterFind != mPerCameraData.end();
1885 }
1886
1887 void GUIRenderer::render(const Camera& camera)
1888 {
1889 Vector<GUIManager::GUICoreRenderData>& renderData = mPerCameraData[&camera];
1890
1891 float invViewportWidth = 1.0f / (camera.getViewport()->getPixelArea().width * 0.5f);
1892 float invViewportHeight = 1.0f / (camera.getViewport()->getPixelArea().height * 0.5f);
1893 float viewflipYFlip = (gCaps().conventions.ndcYAxis == Conventions::Axis::Down) ? -1.0f : 1.0f;
1894
1895 for (auto& entry : renderData)
1896 {
1897 SPtr<GpuParamBlockBuffer> buffer = mParamBlocks[entry.bufferIdx];
1898
1899 gGUISpriteParamBlockDef.gInvViewportWidth.set(buffer, invViewportWidth);
1900 gGUISpriteParamBlockDef.gInvViewportHeight.set(buffer, invViewportHeight);
1901 gGUISpriteParamBlockDef.gViewportYFlip.set(buffer, viewflipYFlip);
1902
1903 float t = std::max(0.0f, mTime - entry.animationStartTime);
1904 if(entry.spriteTexture)
1905 {
1906 UINT32 row;
1907 UINT32 column;
1908 entry.spriteTexture->getAnimationFrame(t, row, column);
1909
1910 float invWidth = 1.0f / entry.spriteTexture->getAnimation().numColumns;
1911 float invHeight = 1.0f / entry.spriteTexture->getAnimation().numRows;
1912
1913 Vector4 sizeOffset(invWidth, invHeight, column * invWidth, row * invHeight);
1914 gGUISpriteParamBlockDef.gUVSizeOffset.set(buffer, sizeOffset);
1915 }
1916 else
1917 gGUISpriteParamBlockDef.gUVSizeOffset.set(buffer, Vector4(1.0f, 1.0f, 0.0f, 0.0f));
1918
1919 buffer->flushToGPU();
1920 }
1921
1922 for (auto& entry : renderData)
1923 {
1924 // TODO - I shouldn't be re-applying the entire material for each entry, instead just check which programs
1925 // changed, and apply only those + the modified constant buffers and/or texture.
1926
1927 SPtr<GpuParamBlockBuffer> buffer = mParamBlocks[entry.bufferIdx];
1928
1929 entry.material->render(entry.mesh, entry.subMesh, entry.texture, mSamplerState, buffer, entry.additionalData);
1930 }
1931 }
1932
1933 void GUIRenderer::update(float time)
1934 {
1935 mTime = time;
1936 }
1937
1938 void GUIRenderer::updateData(const UnorderedMap<SPtr<Camera>, Vector<GUIManager::GUICoreRenderData>>& newPerCameraData)
1939 {
1940 bs_frame_mark();
1941
1942 {
1943 mPerCameraData.clear();
1944 mReferencedCameras.clear();
1945
1946 for (auto& newCameraData : newPerCameraData)
1947 {
1948 SPtr<Camera> camera = newCameraData.first;
1949
1950 mPerCameraData.insert(std::make_pair(camera.get(), newCameraData.second));
1951 mReferencedCameras.insert(camera);
1952 }
1953
1954 // Allocate GPU buffers containing the material parameters
1955 UINT32 numBuffers = 0;
1956 for (auto& cameraData : mPerCameraData)
1957 numBuffers += (UINT32)cameraData.second.size();
1958
1959 UINT32 numAllocatedBuffers = (UINT32)mParamBlocks.size();
1960 if (numBuffers > numAllocatedBuffers)
1961 {
1962 mParamBlocks.resize(numBuffers);
1963
1964 for (UINT32 i = numAllocatedBuffers; i < numBuffers; i++)
1965 mParamBlocks[i] = gGUISpriteParamBlockDef.createBuffer();
1966 }
1967
1968 UINT32 curBufferIdx = 0;
1969 for (auto& cameraData : mPerCameraData)
1970 {
1971 for(auto& entry : cameraData.second)
1972 {
1973 SPtr<GpuParamBlockBuffer> buffer = mParamBlocks[curBufferIdx];
1974
1975 gGUISpriteParamBlockDef.gTint.set(buffer, entry.tint);
1976 gGUISpriteParamBlockDef.gWorldTransform.set(buffer, entry.worldTransform);
1977
1978 entry.bufferIdx = curBufferIdx;
1979 curBufferIdx++;
1980 }
1981 }
1982 }
1983
1984 bs_frame_clear();
1985 }
1986 }
1987}