Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Source/Game-Lib/Game-Lib/ECS/Components/UI/BoundingRect.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ namespace ECS::Components::UI
public:
vec2 min;
vec2 max;

// 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.
// If not, this will be the same as min and max.
vec2 hoveredMin;
vec2 hoveredMax;
};
}
8 changes: 7 additions & 1 deletion Source/Game-Lib/Game-Lib/ECS/Components/UI/Canvas.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#pragma once
#include <Base/Types.h>

#include <Renderer/Descriptors/TextureDesc.h>

namespace ECS::Components::UI
{
struct Canvas
{
public:

std::string name;
Renderer::TextureID renderTexture;
};

struct CanvasRenderTargetTag {};
struct DirtyCanvasTag {};
}
6 changes: 6 additions & 0 deletions Source/Game-Lib/Game-Lib/ECS/Components/UI/PanelTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <Base/Math/Color.h>
#include <Base/Types.h>

#include <Renderer/Descriptors/TextureDesc.h>

#include <entt/fwd.hpp>
#include <optional>

namespace ECS::Components::UI
Expand All @@ -14,6 +17,7 @@ namespace ECS::Components::UI
struct SetFlags
{
u8 background : 1 = 0;
u8 backgroundRT : 1 = 0;
u8 foreground : 1 = 0;
u8 color : 1 = 0;
u8 cornerRadius : 1 = 0;
Expand All @@ -23,6 +27,8 @@ namespace ECS::Components::UI
SetFlags setFlags;

std::string background;
Renderer::TextureID backgroundRT;
entt::entity backgroundRTEntity;
std::string foreground;
Color color = Color::White;
f32 cornerRadius = 0.0f;
Expand Down
3 changes: 2 additions & 1 deletion Source/Game-Lib/Game-Lib/ECS/Components/UI/Widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace ECS::Components::UI
public:
WidgetType type;
WidgetFlags flags = WidgetFlags::Default;
u32 worldTransformIndex = std::numeric_limits<u32>().max();

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

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

struct WidgetRoot {};
struct DirtyWidgetTransform {};
struct DirtyWidgetData {};
struct DirtyWidgetFlags {};
struct DirtyWidgetClipper {};
struct DirtyWidgetWorldTransformIndex {};
struct DestroyWidget {};
}
6 changes: 6 additions & 0 deletions Source/Game-Lib/Game-Lib/ECS/Singletons/UISingleton.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,11 @@ namespace ECS::Singletons
entt::entity hoveredEntity = entt::null;
entt::entity focusedEntity = entt::null;
entt::entity cursorCanvasEntity = entt::null;

// Cursor canvas
Scripting::UI::Widget* cursorCanvas = nullptr;

// Script widgets, these are actually owned and need to be deleted
std::vector<Scripting::UI::Widget*> scriptWidgets;
};
}
82 changes: 77 additions & 5 deletions Source/Game-Lib/Game-Lib/ECS/Systems/UI/HandleInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include "Game-Lib/ECS/Components/Name.h"
#include "Game-Lib/ECS/Components/UI/BoundingRect.h"
#include "Game-Lib/ECS/Components/UI/Canvas.h"
#include "Game-Lib/ECS/Components/UI/EventInputInfo.h"
#include "Game-Lib/ECS/Components/UI/Panel.h"
#include "Game-Lib/ECS/Components/UI/Widget.h"
#include "Game-Lib/ECS/Singletons/UISingleton.h"
#include "Game-Lib/ECS/Util/UIUtil.h"
Expand Down Expand Up @@ -135,7 +137,7 @@ namespace ECS::Systems::UI
if (eventInputInfo->onMouseUpEvent != -1)
{
auto& rect = registry.get<Components::UI::BoundingRect>(uiSingleton.clickedEntity);
bool isWithin = IsWithin(mousePos, rect.min, rect.max);
bool isWithin = IsWithin(mousePos, rect.hoveredMin, rect.hoveredMax);
if (isWithin)
{
auto& widget = registry.get<Components::UI::Widget>(uiSingleton.clickedEntity);
Expand Down Expand Up @@ -243,7 +245,7 @@ namespace ECS::Systems::UI
if (eventInputInfo->onMouseUpEvent != -1)
{
auto& rect = registry.get<Components::UI::BoundingRect>(uiSingleton.clickedEntity);
bool isWithin = IsWithin(mousePos, rect.min, rect.max);
bool isWithin = IsWithin(mousePos, rect.hoveredMin, rect.hoveredMax);
if (isWithin)
{
auto& widget = registry.get<Components::UI::Widget>(uiSingleton.clickedEntity);
Expand Down Expand Up @@ -379,6 +381,75 @@ namespace ECS::Systems::UI
});
}

// This function is called on a canvas to find all hovered entities within it
// It's recursive because we can have Panels with a RenderTarget canvas as a texture
// When it finds one of those we need to offset the panel position and call this function again on the nested canvas
void RecursivelyFindHoveredInCanvas(entt::registry& registry, entt::entity entity, const vec2& mousePos, std::map<u64, entt::entity>& allHoveredEntities, const vec2& parentMin, const vec2& parentMax)
{
auto& transform2DSystem = ECS::Transform2DSystem::Get(registry);
//auto& boundingRect = registry.get<Components::UI::BoundingRect>(entity);
//bool isWithin = IsWithin(mousePos, boundingRect.min, boundingRect.max);

// Loop over children recursively (depth first)
transform2DSystem.IterateChildrenRecursiveDepth(entity, [&](auto childEntity)
{
auto& widget = registry.get<Components::UI::Widget>(childEntity);

if (!widget.IsVisible())
return false;

if (!widget.IsInteractable())
return true;

if (widget.type == Components::UI::WidgetType::Canvas) // For now we don't let canvas consume input
return true;

auto* rect = registry.try_get<Components::UI::BoundingRect>(childEntity);
if (rect == nullptr)
{
return true;
}

// Offset the rect by the parents position
vec2 min = rect->min + parentMin;
vec2 max = rect->max + parentMin;

// Cap it so we can't go outside the parent max and interact with clipped children
max = glm::min(max, parentMax);

// Update hoveredMin and hoveredMax
rect->hoveredMin = min;
rect->hoveredMax = max;

bool isWithin = IsWithin(mousePos, min, max);

if (isWithin)
{
Components::Transform2D& transform = registry.get<Components::Transform2D>(childEntity);

vec2 middlePoint = (min + max) * 0.5f;

u16 numParents = std::numeric_limits<u16>::max() - static_cast<u16>(transform.GetHierarchyDepth());
u16 layer = std::numeric_limits<u16>::max() - static_cast<u16>(transform.GetLayer());
u32 distanceToMouse = static_cast<u32>(glm::distance(middlePoint, mousePos)); // Distance in pixels

u64 key = (static_cast<u64>(numParents) << 48) | (static_cast<u64>(layer) << 32) | distanceToMouse;
allHoveredEntities[key] = childEntity;
}

if (widget.type == Components::UI::WidgetType::Panel)
{
auto& panelTemplate = registry.get<Components::UI::PanelTemplate>(childEntity);
if (panelTemplate.setFlags.backgroundRT)
{
RecursivelyFindHoveredInCanvas(registry, panelTemplate.backgroundRTEntity, mousePos, allHoveredEntities, min, max);
}
}

return true;
});
}

void HandleInput::Update(entt::registry& registry, f32 deltaTime)
{
auto& ctx = registry.ctx();
Expand All @@ -404,9 +475,10 @@ namespace ECS::Systems::UI
// Loop over widget roots
if (!inputManager->IsCursorVirtual())
{
registry.view<Components::UI::WidgetRoot>().each([&](auto entity)
registry.view<Components::UI::Canvas>(entt::exclude<Components::UI::CanvasRenderTargetTag>).each([&](auto entity, auto& canvas)
{
// Loop over children recursively (depth first)
RecursivelyFindHoveredInCanvas(registry, entity, mousePos, uiSingleton.allHoveredEntities, vec2(0,0), renderSize);
/*// Loop over children recursively (depth first)
transform2DSystem.IterateChildrenRecursiveDepth(entity, [&](auto childEntity)
{
auto& widget = registry.get<Components::UI::Widget>(childEntity);
Expand Down Expand Up @@ -442,7 +514,7 @@ namespace ECS::Systems::UI
uiSingleton.allHoveredEntities[key] = childEntity;
}
return true;
});
});*/
});
}

Expand Down
20 changes: 19 additions & 1 deletion Source/Game-Lib/Game-Lib/ECS/Util/Transform2D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ ECS::Transform2DSystem& ECS::Transform2DSystem::Get(entt::registry& registry)
}
}

void ECS::Transform2DSystem::Clear()
void ECS::Transform2DSystem::ClearQueue()
{
TransformQueueItem temp;
while (elements.try_dequeue(temp))
Expand Down Expand Up @@ -300,6 +300,24 @@ void ECS::Transform2DSystem::ClearParent(entt::entity entity)
}
}

bool ECS::Transform2DSystem::HasParent(entt::entity entity)
{
ECS::Components::Transform2D* transform = owner->try_get<ECS::Components::Transform2D>(entity);

if (!transform)
{
return false;
}

ECS::Components::SceneNode2D* sceneNode = owner->try_get<ECS::Components::SceneNode2D>(entity);
if (!sceneNode)
{
return false;
}

return sceneNode->HasParent();
}

ECS::Components::Transform2D* ECS::Components::Transform2D::GetParentTransform() const
{
if (ownerNode && ownerNode->parent && ownerNode->parent->transform)
Expand Down
24 changes: 19 additions & 5 deletions Source/Game-Lib/Game-Lib/ECS/Util/Transform2D.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace ECS
public:
static Transform2DSystem& Get(entt::registry& registry);

void Clear();
void ClearQueue();

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

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

Transform2D* parentTransform = GetParentTransform();
if (parentTransform)
if (parentTransform && !ignoreParent)
{
anchorOffset = anchor * parentTransform->size;
}
Expand Down Expand Up @@ -189,9 +190,20 @@ namespace ECS::Components
Transform2D* GetParentTransform() const;
u32 GetHierarchyDepth() const;

bool GetIgnoreParent() const
{
return ignoreParent;
}

void SetIgnoreParent(bool ignore)
{
ignoreParent = ignore;
}

struct SceneNode2D* ownerNode{ nullptr };

private:
bool ignoreParent = false; // Gets set when the widget is childed to a worldspace position
vec2 position = vec2(0.0f, 0.0f);
quat rotation = quat(1.0f, 0.0f, 0.0f, 0.0f);
vec2 scale = vec2(1.0f, 1.0f);
Expand All @@ -200,6 +212,8 @@ namespace ECS::Components
vec2 size = vec2(1.0f, 1.0f);
vec2 anchor = vec2(0.0f, 0.0f); // This is the point on the parent widget that we will anchor to
vec2 relativePoint = vec2(0.0f, 0.0f); // This is the point on this widget that we will anchor to the parent

friend struct SceneNode2D;
};

//scene node component that holds the information for parenting and children of a given entity or node
Expand Down Expand Up @@ -341,7 +355,7 @@ namespace ECS::Components
//recalculates the matrix. If the scene-node has a parent, it gets transform root from it
inline void RefreshMatrix()
{
if (parent)
if (parent && !transform->ignoreParent)
{
matrix = Math::AffineMatrix::MatrixMul(parent->matrix, transform->GetLocalMatrix());
}
Expand Down Expand Up @@ -417,11 +431,11 @@ void ECS::Transform2DSystem::IterateChildren(entt::entity entity, F&& callback)
ECS::Components::SceneNode2D* c = node->firstChild;
if (c)
{
callback(c);
callback(c->ownerEntity);
c = c->nextSibling;
while (c != node->firstChild)
{
callback(c);
callback(c->ownerEntity);
c = c->nextSibling;
}
}
Expand Down
Loading
Loading