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 | |
41 | using namespace std::placeholders; |
42 | |
43 | namespace 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> = 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 | } |