diff --git a/ui/src/Core/Event.h b/ui/src/Core/Event.h new file mode 100644 index 00000000..2ab193b5 --- /dev/null +++ b/ui/src/Core/Event.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace UI { + +template +class Event { +public: + using HandlerID = size_t; + using Handler = std::function; + + HandlerID Subscribe(Handler handler) { + HandlerID id = m_nextId++; + m_handlers.emplace_back(id, std::move(handler)); + return id; + } + + void Unsubscribe(HandlerID id) { + m_handlers.erase( + std::remove_if(m_handlers.begin(), m_handlers.end(), + [id](const auto& pair) { return pair.first == id; }), + m_handlers.end() + ); + } + + void Invoke(Args... args) { + for (const auto& pair : m_handlers) { + pair.second(args...); + } + } + + void operator()(Args... args) { + Invoke(args...); + } + + void Clear() { + m_handlers.clear(); + } + +private: + HandlerID m_nextId = 0; + std::vector> m_handlers; +}; + +} \ No newline at end of file diff --git a/ui/src/Core/GameObject.h b/ui/src/Core/GameObject.h index 852208fc..f376a040 100644 --- a/ui/src/Core/GameObject.h +++ b/ui/src/Core/GameObject.h @@ -3,9 +3,15 @@ #include #include #include +#include +#include +#include namespace UI { +using EntityID = uint64_t; +constexpr EntityID INVALID_ENTITY = 0; + class Component { public: virtual ~Component() = default; @@ -29,16 +35,14 @@ public: std::string GetName() const override { return "Mesh Renderer"; } }; -class GameObject { -public: +struct Entity { + EntityID id = INVALID_ENTITY; std::string name; - std::vector children; + EntityID parent = INVALID_ENTITY; + std::vector children; std::vector> components; bool selected = false; - GameObject() = default; - explicit GameObject(const std::string& n) : name(n) {} - template T* AddComponent(Args&&... args) { auto comp = std::make_unique(std::forward(args)...); @@ -58,4 +62,40 @@ public: } }; +using ComponentInspectorFn = std::function; + +struct ComponentInspectorInfo { + std::string name; + ComponentInspectorFn renderFn; +}; + +class ComponentRegistry { +public: + static ComponentRegistry& Get() { + static ComponentRegistry instance; + return instance; + } + + template + void RegisterComponent(const std::string& name, ComponentInspectorFn inspectorFn) { + m_inspectors[name] = {name, inspectorFn}; + m_factories[name] = []() -> std::unique_ptr { + return std::make_unique(); + }; + } + + ComponentInspectorInfo* GetInspector(const std::string& name) { + auto it = m_inspectors.find(name); + if (it != m_inspectors.end()) { + return &it->second; + } + return nullptr; + } + +private: + ComponentRegistry() = default; + std::unordered_map m_inspectors; + std::unordered_map()>> m_factories; +}; + } \ No newline at end of file diff --git a/ui/src/Managers/SceneManager.cpp b/ui/src/Managers/SceneManager.cpp index 1f04250c..47fdadf7 100644 --- a/ui/src/Managers/SceneManager.cpp +++ b/ui/src/Managers/SceneManager.cpp @@ -2,46 +2,4 @@ namespace UI { -SceneManager& SceneManager::Get() { - static SceneManager instance; - return instance; -} - -void SceneManager::SetSelectedObject(GameObject* obj) { - if (m_selectedObject) { - m_selectedObject->selected = false; - } - m_selectedObject = obj; - if (m_selectedObject) { - m_selectedObject->selected = true; - } -} - -void SceneManager::CreateDemoScene() { - m_rootObjects.clear(); - - GameObject mainCamera("Main Camera"); - mainCamera.AddComponent(); - - GameObject directionalLight("Directional Light"); - - GameObject cube("Cube"); - cube.AddComponent(); - cube.AddComponent()->meshName = "Cube Mesh"; - - GameObject sphere("Sphere"); - sphere.AddComponent(); - sphere.AddComponent()->meshName = "Sphere Mesh"; - - GameObject player("Player"); - GameObject weapon("Weapon"); - player.children.push_back(std::move(weapon)); - - m_rootObjects.push_back(std::move(mainCamera)); - m_rootObjects.push_back(std::move(directionalLight)); - m_rootObjects.push_back(std::move(cube)); - m_rootObjects.push_back(std::move(sphere)); - m_rootObjects.push_back(std::move(player)); -} - } \ No newline at end of file diff --git a/ui/src/Managers/SceneManager.h b/ui/src/Managers/SceneManager.h index 24bc7e0b..2c19bed0 100644 --- a/ui/src/Managers/SceneManager.h +++ b/ui/src/Managers/SceneManager.h @@ -1,25 +1,119 @@ #pragma once #include "Core/GameObject.h" +#include "SelectionManager.h" +#include #include namespace UI { class SceneManager { public: - static SceneManager& Get(); + static SceneManager& Get() { + static SceneManager instance; + return instance; + } - std::vector& GetRootObjects() { return m_rootObjects; } - GameObject* GetSelectedObject() { return m_selectedObject; } - void SetSelectedObject(GameObject* obj); + EntityID CreateEntity(const std::string& name, EntityID parent = INVALID_ENTITY) { + EntityID id = m_nextEntityId++; + Entity entity; + entity.id = id; + entity.name = name; + entity.parent = parent; + m_entities[id] = std::move(entity); + + if (parent != INVALID_ENTITY) { + m_entities[parent].children.push_back(id); + } else { + m_rootEntities.push_back(id); + } + + OnEntityCreated.Invoke(id); + return id; + } - void CreateDemoScene(); + Entity* GetEntity(EntityID id) { + auto it = m_entities.find(id); + if (it != m_entities.end()) { + return &it->second; + } + return nullptr; + } + + const Entity* GetEntity(EntityID id) const { + auto it = m_entities.find(id); + if (it != m_entities.end()) { + return &it->second; + } + return nullptr; + } + + const std::vector& GetRootEntities() const { + return m_rootEntities; + } + + void DeleteEntity(EntityID id) { + auto it = m_entities.find(id); + if (it == m_entities.end()) return; + + Entity& entity = it->second; + + for (EntityID childId : entity.children) { + DeleteEntity(childId); + } + + if (entity.parent != INVALID_ENTITY) { + auto* parent = GetEntity(entity.parent); + if (parent) { + auto& siblings = parent->children; + siblings.erase(std::remove(siblings.begin(), siblings.end(), id), siblings.end()); + } + } else { + m_rootEntities.erase(std::remove(m_rootEntities.begin(), m_rootEntities.end(), id), m_rootEntities.end()); + } + + if (SelectionManager::Get().GetSelectedEntity() == id) { + SelectionManager::Get().ClearSelection(); + } + + m_entities.erase(it); + OnEntityDeleted.Invoke(id); + } + + void CreateDemoScene() { + m_entities.clear(); + m_rootEntities.clear(); + m_nextEntityId = 1; + + EntityID camera = CreateEntity("Main Camera"); + GetEntity(camera)->AddComponent(); + + EntityID light = CreateEntity("Directional Light"); + + EntityID cube = CreateEntity("Cube"); + GetEntity(cube)->AddComponent(); + GetEntity(cube)->AddComponent()->meshName = "Cube Mesh"; + + EntityID sphere = CreateEntity("Sphere"); + GetEntity(sphere)->AddComponent(); + GetEntity(sphere)->AddComponent()->meshName = "Sphere Mesh"; + + EntityID player = CreateEntity("Player"); + EntityID weapon = CreateEntity("Weapon", player); + + OnSceneChanged.Invoke(); + } + + Event OnEntityCreated; + Event OnEntityDeleted; + Event<> OnSceneChanged; private: SceneManager() = default; - std::vector m_rootObjects; - GameObject* m_selectedObject = nullptr; + EntityID m_nextEntityId = 1; + std::unordered_map m_entities; + std::vector m_rootEntities; }; } \ No newline at end of file diff --git a/ui/src/Managers/SelectionManager.h b/ui/src/Managers/SelectionManager.h new file mode 100644 index 00000000..fe17e7b0 --- /dev/null +++ b/ui/src/Managers/SelectionManager.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Core/GameObject.h" +#include "Core/Event.h" +#include + +namespace UI { + +class SelectionManager { +public: + static SelectionManager& Get() { + static SelectionManager instance; + return instance; + } + + EntityID GetSelectedEntity() const { return m_selectedEntity; } + + void SetSelectedEntity(EntityID id) { + m_selectedEntity = id; + OnSelectionChanged.Invoke(id); + } + + void ClearSelection() { + SetSelectedEntity(INVALID_ENTITY); + } + + bool IsSelected(EntityID id) const { + return m_selectedEntity == id; + } + + Event OnSelectionChanged; + +private: + SelectionManager() = default; + EntityID m_selectedEntity = INVALID_ENTITY; +}; + +} \ No newline at end of file diff --git a/ui/src/panels/HierarchyPanel.cpp b/ui/src/panels/HierarchyPanel.cpp index ef2a9e31..53dbd61f 100644 --- a/ui/src/panels/HierarchyPanel.cpp +++ b/ui/src/panels/HierarchyPanel.cpp @@ -1,44 +1,55 @@ #include "HierarchyPanel.h" #include "Managers/SceneManager.h" -#include "Core/GameObject.h" +#include "Managers/SelectionManager.h" #include namespace UI { HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") { SceneManager::Get().CreateDemoScene(); + + m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) { + }); +} + +HierarchyPanel::~HierarchyPanel() { + SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId); } void HierarchyPanel::Render() { ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None); - for (auto& obj : SceneManager::Get().GetRootObjects()) { - RenderGameObject(obj); + for (EntityID id : SceneManager::Get().GetRootEntities()) { + RenderEntity(id); } ImGui::End(); } -void HierarchyPanel::RenderGameObject(GameObject& obj) { +void HierarchyPanel::RenderEntity(EntityID id) { + auto& sceneManager = SceneManager::Get(); + const Entity* entity = sceneManager.GetEntity(id); + if (!entity) return; + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; - if (obj.children.empty()) { + if (entity->children.empty()) { flags |= ImGuiTreeNodeFlags_Leaf; } - if (obj.selected) { + if (SelectionManager::Get().IsSelected(id)) { flags |= ImGuiTreeNodeFlags_Selected; } - bool isOpen = ImGui::TreeNodeEx(obj.name.c_str(), flags); + bool isOpen = ImGui::TreeNodeEx(entity->name.c_str(), flags); if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { - SceneManager::Get().SetSelectedObject(&obj); + SelectionManager::Get().SetSelectedEntity(id); } if (isOpen) { - for (auto& child : obj.children) { - RenderGameObject(child); + for (EntityID childId : entity->children) { + RenderEntity(childId); } ImGui::TreePop(); } diff --git a/ui/src/panels/HierarchyPanel.h b/ui/src/panels/HierarchyPanel.h index 39ee9978..3c9c6a0d 100644 --- a/ui/src/panels/HierarchyPanel.h +++ b/ui/src/panels/HierarchyPanel.h @@ -1,16 +1,22 @@ #pragma once #include "Panel.h" +#include "Core/Event.h" +#include "Core/GameObject.h" namespace UI { class HierarchyPanel : public Panel { public: HierarchyPanel(); + ~HierarchyPanel(); + void Render() override; private: - void RenderGameObject(class GameObject& obj); + void RenderEntity(EntityID id); + + Event::HandlerID m_selectionHandlerId = 0; }; } \ No newline at end of file diff --git a/ui/src/panels/InspectorPanel.cpp b/ui/src/panels/InspectorPanel.cpp index 523302a8..14f4b560 100644 --- a/ui/src/panels/InspectorPanel.cpp +++ b/ui/src/panels/InspectorPanel.cpp @@ -1,31 +1,27 @@ #include "InspectorPanel.h" #include "Managers/SceneManager.h" -#include "Core/GameObject.h" +#include "Managers/SelectionManager.h" #include namespace UI { -InspectorPanel::InspectorPanel() : Panel("Inspector") {} +InspectorPanel::InspectorPanel() : Panel("Inspector") { + m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](EntityID) { + }); +} + +InspectorPanel::~InspectorPanel() { + SelectionManager::Get().OnSelectionChanged.Unsubscribe(m_selectionHandlerId); +} void InspectorPanel::Render() { ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None); - GameObject* selected = SceneManager::Get().GetSelectedObject(); + EntityID selectedId = SelectionManager::Get().GetSelectedEntity(); + Entity* entity = SceneManager::Get().GetEntity(selectedId); - if (selected) { - ImGui::Text("%s", selected->name.c_str()); - ImGui::Separator(); - - auto transform = selected->GetComponent(); - if (transform) { - RenderTransformSection(transform); - ImGui::Separator(); - } - - auto meshRenderer = selected->GetComponent(); - if (meshRenderer) { - RenderMeshRendererSection(meshRenderer); - } + if (entity) { + RenderEntity(entity); } else { ImGui::Text("No object selected"); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy"); @@ -34,42 +30,51 @@ void InspectorPanel::Render() { ImGui::End(); } -void InspectorPanel::RenderTransformSection(TransformComponent* transform) { - if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Indent(10.0f); - - ImGui::Text("Position"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180); - ImGui::DragFloat3("##Position", transform->position, 0.1f); - - ImGui::Text("Rotation"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180); - ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f); - - ImGui::Text("Scale"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180); - ImGui::DragFloat3("##Scale", transform->scale, 0.1f); - - ImGui::Unindent(10.0f); +void InspectorPanel::RenderEntity(Entity* entity) { + ImGui::Text("%s", entity->name.c_str()); + ImGui::Separator(); + + for (auto& component : entity->components) { + RenderComponent(component.get()); + ImGui::Separator(); } } -void InspectorPanel::RenderMeshRendererSection(MeshRendererComponent* meshRenderer) { - if (ImGui::CollapsingHeader("Mesh Renderer", ImGuiTreeNodeFlags_DefaultOpen)) { +void InspectorPanel::RenderComponent(Component* component) { + if (!component) return; + + const char* name = component->GetName().c_str(); + + if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Indent(10.0f); - ImGui::Text("Material"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180); - ImGui::InputText("##Material", meshRenderer->materialName.data(), meshRenderer->materialName.capacity()); - - ImGui::Text("Mesh"); - ImGui::SameLine(80); - ImGui::SetNextItemWidth(180); - ImGui::InputText("##Mesh", meshRenderer->meshName.data(), meshRenderer->meshName.capacity()); + if (auto* transform = dynamic_cast(component)) { + ImGui::Text("Position"); + ImGui::SameLine(80); + ImGui::SetNextItemWidth(180); + ImGui::DragFloat3("##Position", transform->position, 0.1f); + + ImGui::Text("Rotation"); + ImGui::SameLine(80); + ImGui::SetNextItemWidth(180); + ImGui::DragFloat3("##Rotation", transform->rotation, 1.0f); + + ImGui::Text("Scale"); + ImGui::SameLine(80); + ImGui::SetNextItemWidth(180); + ImGui::DragFloat3("##Scale", transform->scale, 0.1f); + } + else if (auto* meshRenderer = dynamic_cast(component)) { + ImGui::Text("Material"); + ImGui::SameLine(80); + ImGui::SetNextItemWidth(180); + ImGui::InputText("##Material", meshRenderer->materialName.data(), meshRenderer->materialName.capacity()); + + ImGui::Text("Mesh"); + ImGui::SameLine(80); + ImGui::SetNextItemWidth(180); + ImGui::InputText("##Mesh", meshRenderer->meshName.data(), meshRenderer->meshName.capacity()); + } ImGui::Unindent(10.0f); } diff --git a/ui/src/panels/InspectorPanel.h b/ui/src/panels/InspectorPanel.h index f2888b0c..74945608 100644 --- a/ui/src/panels/InspectorPanel.h +++ b/ui/src/panels/InspectorPanel.h @@ -1,17 +1,23 @@ #pragma once #include "Panel.h" +#include "Core/Event.h" +#include "Core/GameObject.h" namespace UI { class InspectorPanel : public Panel { public: InspectorPanel(); + ~InspectorPanel(); + void Render() override; private: - void RenderTransformSection(class TransformComponent* transform); - void RenderMeshRendererSection(class MeshRendererComponent* meshRenderer); + void RenderEntity(Entity* entity); + void RenderComponent(Component* component); + + Event::HandlerID m_selectionHandlerId = 0; }; } \ No newline at end of file