Skip to content

Commit 0a72568

Browse files
authored
World Attached UI (#51)
* Add support for UI attached to world positions * Add basic Nameplate widget * WIP OptionsContext and GameMenu * Fix issue with cursor canvas handling * Manage lifetime of Scripting::UI::Widget on our own and only push copies * Add HasParent function to TransformSystem2D * Update Engine submodule
1 parent eb59c9d commit 0a72568

36 files changed

+2321
-462
lines changed

Source/Game-Lib/Game-Lib/ECS/Components/UI/BoundingRect.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ namespace ECS::Components::UI
88
public:
99
vec2 min;
1010
vec2 max;
11+
12+
// If this widget is hovered and a child of a RenderTarget canvas, this will be min and max offset by the panel that canvas is displayed on.
13+
// If not, this will be the same as min and max.
14+
vec2 hoveredMin;
15+
vec2 hoveredMax;
1116
};
1217
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
#pragma once
22
#include <Base/Types.h>
33

4+
#include <Renderer/Descriptors/TextureDesc.h>
5+
46
namespace ECS::Components::UI
57
{
68
struct Canvas
79
{
810
public:
9-
11+
std::string name;
12+
Renderer::TextureID renderTexture;
1013
};
14+
15+
struct CanvasRenderTargetTag {};
16+
struct DirtyCanvasTag {};
1117
}

Source/Game-Lib/Game-Lib/ECS/Components/UI/PanelTemplate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include <Base/Math/Color.h>
55
#include <Base/Types.h>
66

7+
#include <Renderer/Descriptors/TextureDesc.h>
8+
9+
#include <entt/fwd.hpp>
710
#include <optional>
811

912
namespace ECS::Components::UI
@@ -14,6 +17,7 @@ namespace ECS::Components::UI
1417
struct SetFlags
1518
{
1619
u8 background : 1 = 0;
20+
u8 backgroundRT : 1 = 0;
1721
u8 foreground : 1 = 0;
1822
u8 color : 1 = 0;
1923
u8 cornerRadius : 1 = 0;
@@ -23,6 +27,8 @@ namespace ECS::Components::UI
2327
SetFlags setFlags;
2428

2529
std::string background;
30+
Renderer::TextureID backgroundRT;
31+
entt::entity backgroundRTEntity;
2632
std::string foreground;
2733
Color color = Color::White;
2834
f32 cornerRadius = 0.0f;

Source/Game-Lib/Game-Lib/ECS/Components/UI/Widget.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace ECS::Components::UI
3737
public:
3838
WidgetType type;
3939
WidgetFlags flags = WidgetFlags::Default;
40+
u32 worldTransformIndex = std::numeric_limits<u32>().max();
4041

4142
Scripting::UI::Widget* scriptWidget = nullptr;
4243

@@ -48,10 +49,10 @@ namespace ECS::Components::UI
4849
inline bool IsResizable() const { return IsInteractable() && (flags & WidgetFlags::Resizable) == WidgetFlags::Resizable; }
4950
};
5051

51-
struct WidgetRoot {};
5252
struct DirtyWidgetTransform {};
5353
struct DirtyWidgetData {};
5454
struct DirtyWidgetFlags {};
5555
struct DirtyWidgetClipper {};
56+
struct DirtyWidgetWorldTransformIndex {};
5657
struct DestroyWidget {};
5758
}

Source/Game-Lib/Game-Lib/ECS/Singletons/UISingleton.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,11 @@ namespace ECS::Singletons
3232
entt::entity hoveredEntity = entt::null;
3333
entt::entity focusedEntity = entt::null;
3434
entt::entity cursorCanvasEntity = entt::null;
35+
36+
// Cursor canvas
37+
Scripting::UI::Widget* cursorCanvas = nullptr;
38+
39+
// Script widgets, these are actually owned and need to be deleted
40+
std::vector<Scripting::UI::Widget*> scriptWidgets;
3541
};
3642
}

Source/Game-Lib/Game-Lib/ECS/Systems/UI/HandleInput.cpp

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#include "Game-Lib/ECS/Components/Name.h"
44
#include "Game-Lib/ECS/Components/UI/BoundingRect.h"
5+
#include "Game-Lib/ECS/Components/UI/Canvas.h"
56
#include "Game-Lib/ECS/Components/UI/EventInputInfo.h"
7+
#include "Game-Lib/ECS/Components/UI/Panel.h"
68
#include "Game-Lib/ECS/Components/UI/Widget.h"
79
#include "Game-Lib/ECS/Singletons/UISingleton.h"
810
#include "Game-Lib/ECS/Util/UIUtil.h"
@@ -135,7 +137,7 @@ namespace ECS::Systems::UI
135137
if (eventInputInfo->onMouseUpEvent != -1)
136138
{
137139
auto& rect = registry.get<Components::UI::BoundingRect>(uiSingleton.clickedEntity);
138-
bool isWithin = IsWithin(mousePos, rect.min, rect.max);
140+
bool isWithin = IsWithin(mousePos, rect.hoveredMin, rect.hoveredMax);
139141
if (isWithin)
140142
{
141143
auto& widget = registry.get<Components::UI::Widget>(uiSingleton.clickedEntity);
@@ -243,7 +245,7 @@ namespace ECS::Systems::UI
243245
if (eventInputInfo->onMouseUpEvent != -1)
244246
{
245247
auto& rect = registry.get<Components::UI::BoundingRect>(uiSingleton.clickedEntity);
246-
bool isWithin = IsWithin(mousePos, rect.min, rect.max);
248+
bool isWithin = IsWithin(mousePos, rect.hoveredMin, rect.hoveredMax);
247249
if (isWithin)
248250
{
249251
auto& widget = registry.get<Components::UI::Widget>(uiSingleton.clickedEntity);
@@ -379,6 +381,75 @@ namespace ECS::Systems::UI
379381
});
380382
}
381383

384+
// This function is called on a canvas to find all hovered entities within it
385+
// It's recursive because we can have Panels with a RenderTarget canvas as a texture
386+
// When it finds one of those we need to offset the panel position and call this function again on the nested canvas
387+
void RecursivelyFindHoveredInCanvas(entt::registry& registry, entt::entity entity, const vec2& mousePos, std::map<u64, entt::entity>& allHoveredEntities, const vec2& parentMin, const vec2& parentMax)
388+
{
389+
auto& transform2DSystem = ECS::Transform2DSystem::Get(registry);
390+
//auto& boundingRect = registry.get<Components::UI::BoundingRect>(entity);
391+
//bool isWithin = IsWithin(mousePos, boundingRect.min, boundingRect.max);
392+
393+
// Loop over children recursively (depth first)
394+
transform2DSystem.IterateChildrenRecursiveDepth(entity, [&](auto childEntity)
395+
{
396+
auto& widget = registry.get<Components::UI::Widget>(childEntity);
397+
398+
if (!widget.IsVisible())
399+
return false;
400+
401+
if (!widget.IsInteractable())
402+
return true;
403+
404+
if (widget.type == Components::UI::WidgetType::Canvas) // For now we don't let canvas consume input
405+
return true;
406+
407+
auto* rect = registry.try_get<Components::UI::BoundingRect>(childEntity);
408+
if (rect == nullptr)
409+
{
410+
return true;
411+
}
412+
413+
// Offset the rect by the parents position
414+
vec2 min = rect->min + parentMin;
415+
vec2 max = rect->max + parentMin;
416+
417+
// Cap it so we can't go outside the parent max and interact with clipped children
418+
max = glm::min(max, parentMax);
419+
420+
// Update hoveredMin and hoveredMax
421+
rect->hoveredMin = min;
422+
rect->hoveredMax = max;
423+
424+
bool isWithin = IsWithin(mousePos, min, max);
425+
426+
if (isWithin)
427+
{
428+
Components::Transform2D& transform = registry.get<Components::Transform2D>(childEntity);
429+
430+
vec2 middlePoint = (min + max) * 0.5f;
431+
432+
u16 numParents = std::numeric_limits<u16>::max() - static_cast<u16>(transform.GetHierarchyDepth());
433+
u16 layer = std::numeric_limits<u16>::max() - static_cast<u16>(transform.GetLayer());
434+
u32 distanceToMouse = static_cast<u32>(glm::distance(middlePoint, mousePos)); // Distance in pixels
435+
436+
u64 key = (static_cast<u64>(numParents) << 48) | (static_cast<u64>(layer) << 32) | distanceToMouse;
437+
allHoveredEntities[key] = childEntity;
438+
}
439+
440+
if (widget.type == Components::UI::WidgetType::Panel)
441+
{
442+
auto& panelTemplate = registry.get<Components::UI::PanelTemplate>(childEntity);
443+
if (panelTemplate.setFlags.backgroundRT)
444+
{
445+
RecursivelyFindHoveredInCanvas(registry, panelTemplate.backgroundRTEntity, mousePos, allHoveredEntities, min, max);
446+
}
447+
}
448+
449+
return true;
450+
});
451+
}
452+
382453
void HandleInput::Update(entt::registry& registry, f32 deltaTime)
383454
{
384455
auto& ctx = registry.ctx();
@@ -404,9 +475,10 @@ namespace ECS::Systems::UI
404475
// Loop over widget roots
405476
if (!inputManager->IsCursorVirtual())
406477
{
407-
registry.view<Components::UI::WidgetRoot>().each([&](auto entity)
478+
registry.view<Components::UI::Canvas>(entt::exclude<Components::UI::CanvasRenderTargetTag>).each([&](auto entity, auto& canvas)
408479
{
409-
// Loop over children recursively (depth first)
480+
RecursivelyFindHoveredInCanvas(registry, entity, mousePos, uiSingleton.allHoveredEntities, vec2(0,0), renderSize);
481+
/*// Loop over children recursively (depth first)
410482
transform2DSystem.IterateChildrenRecursiveDepth(entity, [&](auto childEntity)
411483
{
412484
auto& widget = registry.get<Components::UI::Widget>(childEntity);
@@ -442,7 +514,7 @@ namespace ECS::Systems::UI
442514
uiSingleton.allHoveredEntities[key] = childEntity;
443515
}
444516
return true;
445-
});
517+
});*/
446518
});
447519
}
448520

Source/Game-Lib/Game-Lib/ECS/Util/Transform2D.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ ECS::Transform2DSystem& ECS::Transform2DSystem::Get(entt::registry& registry)
2626
}
2727
}
2828

29-
void ECS::Transform2DSystem::Clear()
29+
void ECS::Transform2DSystem::ClearQueue()
3030
{
3131
TransformQueueItem temp;
3232
while (elements.try_dequeue(temp))
@@ -300,6 +300,24 @@ void ECS::Transform2DSystem::ClearParent(entt::entity entity)
300300
}
301301
}
302302

303+
bool ECS::Transform2DSystem::HasParent(entt::entity entity)
304+
{
305+
ECS::Components::Transform2D* transform = owner->try_get<ECS::Components::Transform2D>(entity);
306+
307+
if (!transform)
308+
{
309+
return false;
310+
}
311+
312+
ECS::Components::SceneNode2D* sceneNode = owner->try_get<ECS::Components::SceneNode2D>(entity);
313+
if (!sceneNode)
314+
{
315+
return false;
316+
}
317+
318+
return sceneNode->HasParent();
319+
}
320+
303321
ECS::Components::Transform2D* ECS::Components::Transform2D::GetParentTransform() const
304322
{
305323
if (ownerNode && ownerNode->parent && ownerNode->parent->transform)

Source/Game-Lib/Game-Lib/ECS/Util/Transform2D.h

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace ECS
1818
public:
1919
static Transform2DSystem& Get(entt::registry& registry);
2020

21-
void Clear();
21+
void ClearQueue();
2222

2323
//api with entityID alone. Can do world transforms by accessing the scene component
2424
void SetLocalPosition(entt::entity entity, const vec2& newPosition);
@@ -52,6 +52,7 @@ namespace ECS
5252
//connects an entity ID into a parent. Will create the required scene-node components on demand if needed
5353
void ParentEntityTo(entt::entity parent, entt::entity child);
5454
void ClearParent(entt::entity entity);
55+
bool HasParent(entt::entity entity);
5556

5657
//iterates the children of a given node. NOT recursive
5758
//callback is in the form SceneComponent* child
@@ -135,7 +136,7 @@ namespace ECS::Components
135136
vec2 anchorOffset = vec2(0, 0);
136137

137138
Transform2D* parentTransform = GetParentTransform();
138-
if (parentTransform)
139+
if (parentTransform && !ignoreParent)
139140
{
140141
anchorOffset = anchor * parentTransform->size;
141142
}
@@ -189,9 +190,20 @@ namespace ECS::Components
189190
Transform2D* GetParentTransform() const;
190191
u32 GetHierarchyDepth() const;
191192

193+
bool GetIgnoreParent() const
194+
{
195+
return ignoreParent;
196+
}
197+
198+
void SetIgnoreParent(bool ignore)
199+
{
200+
ignoreParent = ignore;
201+
}
202+
192203
struct SceneNode2D* ownerNode{ nullptr };
193204

194205
private:
206+
bool ignoreParent = false; // Gets set when the widget is childed to a worldspace position
195207
vec2 position = vec2(0.0f, 0.0f);
196208
quat rotation = quat(1.0f, 0.0f, 0.0f, 0.0f);
197209
vec2 scale = vec2(1.0f, 1.0f);
@@ -200,6 +212,8 @@ namespace ECS::Components
200212
vec2 size = vec2(1.0f, 1.0f);
201213
vec2 anchor = vec2(0.0f, 0.0f); // This is the point on the parent widget that we will anchor to
202214
vec2 relativePoint = vec2(0.0f, 0.0f); // This is the point on this widget that we will anchor to the parent
215+
216+
friend struct SceneNode2D;
203217
};
204218

205219
//scene node component that holds the information for parenting and children of a given entity or node
@@ -341,7 +355,7 @@ namespace ECS::Components
341355
//recalculates the matrix. If the scene-node has a parent, it gets transform root from it
342356
inline void RefreshMatrix()
343357
{
344-
if (parent)
358+
if (parent && !transform->ignoreParent)
345359
{
346360
matrix = Math::AffineMatrix::MatrixMul(parent->matrix, transform->GetLocalMatrix());
347361
}
@@ -417,11 +431,11 @@ void ECS::Transform2DSystem::IterateChildren(entt::entity entity, F&& callback)
417431
ECS::Components::SceneNode2D* c = node->firstChild;
418432
if (c)
419433
{
420-
callback(c);
434+
callback(c->ownerEntity);
421435
c = c->nextSibling;
422436
while (c != node->firstChild)
423437
{
424-
callback(c);
438+
callback(c->ownerEntity);
425439
c = c->nextSibling;
426440
}
427441
}

0 commit comments

Comments
 (0)