Editor: UI panels and GameObject updates

This commit is contained in:
2026-03-25 01:23:08 +08:00
parent dc970d215b
commit c9e459c179
21 changed files with 651 additions and 184 deletions

View File

@@ -43,6 +43,7 @@ add_executable(${PROJECT_NAME} WIN32
src/panels/InspectorPanel.cpp
src/panels/ConsolePanel.cpp
src/panels/ProjectPanel.cpp
src/Layers/EditorLayer.cpp
${IMGUI_SOURCES}
)
@@ -61,7 +62,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
d3d12.lib
dxgi.lib
d3dcompiler.lib
${CMAKE_SOURCE_DIR}/engine/build/Release/XCEngine.lib
${CMAKE_CURRENT_SOURCE_DIR}/../engine/build/Release/XCEngine.lib
)
set_target_properties(${PROJECT_NAME} PROPERTIES

View File

@@ -1,4 +1,5 @@
#include "Application.h"
#include "Layers/EditorLayer.h"
#include <imgui_impl_win32.h>
#include <imgui_impl_dx12.h>
#include <imgui_internal.h>
@@ -45,14 +46,6 @@ bool Application::Initialize(HWND hwnd) {
m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
m_srvHeap->GetGPUDescriptorHandleForHeapStart());
m_menuBar = std::make_unique<MenuBar>();
m_hierarchyPanel = std::make_unique<HierarchyPanel>();
m_sceneViewPanel = std::make_unique<SceneViewPanel>();
m_gameViewPanel = std::make_unique<GameViewPanel>();
m_inspectorPanel = std::make_unique<InspectorPanel>();
m_consolePanel = std::make_unique<ConsolePanel>();
m_projectPanel = std::make_unique<ProjectPanel>();
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
std::wstring exeDirW(exePath);
@@ -66,12 +59,18 @@ bool Application::Initialize(HWND hwnd) {
exeDir.resize(len - 1);
WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr);
}
m_projectPanel->Initialize(exeDir);
m_editorLayer = new EditorLayer();
m_editorLayer->SetProjectPath(exeDir);
m_layerStack.pushLayer(std::unique_ptr<Core::Layer>(m_editorLayer));
m_layerStack.onAttach();
return true;
}
void Application::Shutdown() {
m_layerStack.onDetach();
ImGui_ImplDX12_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
@@ -93,8 +92,7 @@ void Application::Render() {
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
SetupDockspace();
RenderUI();
m_layerStack.onImGuiRender();
ImGui::Render();
@@ -243,63 +241,5 @@ void Application::CleanupRenderTarget() {
}
}
void Application::SetupDockspace() {
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("MainDockspace", nullptr, windowFlags);
ImGui::PopStyleVar();
ImGui::PopStyleVar(2);
ImGuiID dockspaceId = ImGui::GetID("MyDockspace");
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
static bool firstTime = true;
if (firstTime) {
firstTime = false;
ImGui::DockBuilderRemoveNode(dockspaceId);
ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
ImGuiID dockMain = dockspaceId;
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
ImGui::DockBuilderDockWindow("Scene", dockMain);
ImGui::DockBuilderDockWindow("Game", dockMain);
ImGui::DockBuilderDockWindow("Inspector", dockRight);
ImGui::DockBuilderDockWindow("Console", dockBottom);
ImGui::DockBuilderDockWindow("Project", dockBottom);
ImGui::DockBuilderFinish(dockspaceId);
}
ImGui::End();
}
void Application::RenderUI() {
m_menuBar->Render();
m_hierarchyPanel->Render();
m_sceneViewPanel->Render();
m_gameViewPanel->Render();
m_inspectorPanel->Render();
m_consolePanel->Render();
m_projectPanel->Render();
}
}
}

View File

@@ -5,19 +5,15 @@
#include <d3d12.h>
#include <dxgi1_6.h>
#include <XCEngine/Core/LayerStack.h>
#include "Theme.h"
#include "panels/Panel.h"
#include "panels/MenuBar.h"
#include "panels/HierarchyPanel.h"
#include "panels/SceneViewPanel.h"
#include "panels/GameViewPanel.h"
#include "panels/InspectorPanel.h"
#include "panels/ConsolePanel.h"
#include "panels/ProjectPanel.h"
namespace XCEngine {
namespace Editor {
class EditorLayer;
class Application {
public:
static Application& Get();
@@ -34,8 +30,6 @@ private:
bool CreateDevice();
bool CreateRenderTarget();
void CleanupRenderTarget();
void SetupDockspace();
void RenderUI();
HWND m_hwnd = nullptr;
int m_width = 1280;
@@ -54,13 +48,8 @@ private:
UINT m_rtvDescriptorSize = 0;
UINT m_frameIndex = 0;
std::unique_ptr<MenuBar> m_menuBar;
std::unique_ptr<HierarchyPanel> m_hierarchyPanel;
std::unique_ptr<SceneViewPanel> m_sceneViewPanel;
std::unique_ptr<GameViewPanel> m_gameViewPanel;
std::unique_ptr<InspectorPanel> m_inspectorPanel;
std::unique_ptr<ConsolePanel> m_consolePanel;
std::unique_ptr<ProjectPanel> m_projectPanel;
Core::LayerStack m_layerStack;
EditorLayer* m_editorLayer = nullptr;
};
}

View File

@@ -0,0 +1,112 @@
#include "EditorLayer.h"
#include "panels/MenuBar.h"
#include "panels/HierarchyPanel.h"
#include "panels/SceneViewPanel.h"
#include "panels/GameViewPanel.h"
#include "panels/InspectorPanel.h"
#include "panels/ConsolePanel.h"
#include "panels/ProjectPanel.h"
#include "Managers/SelectionManager.h"
#include "Managers/SceneManager.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace XCEngine {
namespace Editor {
EditorLayer::EditorLayer() : Layer("Editor") {}
void EditorLayer::onAttach() {
m_menuBar = std::make_unique<MenuBar>();
m_hierarchyPanel = std::make_unique<HierarchyPanel>();
m_sceneViewPanel = std::make_unique<SceneViewPanel>();
m_gameViewPanel = std::make_unique<GameViewPanel>();
m_inspectorPanel = std::make_unique<InspectorPanel>();
m_consolePanel = std::make_unique<ConsolePanel>();
m_projectPanel = std::make_unique<ProjectPanel>();
m_projectPanel->Initialize(m_projectPath);
auto& selection = SelectionManager::Get();
selection.OnSelectionChanged.Subscribe([](uint64_t id) {
});
}
void EditorLayer::onDetach() {
}
void EditorLayer::onUpdate(float dt) {
}
void EditorLayer::onEvent(void* event) {
}
void EditorLayer::onImGuiRender() {
setupDockspace();
renderAllPanels();
}
void EditorLayer::SetProjectPath(const std::string& path) {
m_projectPath = path;
}
void EditorLayer::setupDockspace() {
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("MainDockspace", nullptr, windowFlags);
ImGui::PopStyleVar();
ImGui::PopStyleVar(2);
ImGuiID dockspaceId = ImGui::GetID("MyDockspace");
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
static bool firstTime = true;
if (firstTime) {
firstTime = false;
ImGui::DockBuilderRemoveNode(dockspaceId);
ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
ImGuiID dockMain = dockspaceId;
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
ImGui::DockBuilderDockWindow("Scene", dockMain);
ImGui::DockBuilderDockWindow("Game", dockMain);
ImGui::DockBuilderDockWindow("Inspector", dockRight);
ImGui::DockBuilderDockWindow("Console", dockBottom);
ImGui::DockBuilderDockWindow("Project", dockBottom);
ImGui::DockBuilderFinish(dockspaceId);
}
ImGui::End();
}
void EditorLayer::renderAllPanels() {
m_menuBar->Render();
m_hierarchyPanel->Render();
m_sceneViewPanel->Render();
m_gameViewPanel->Render();
m_inspectorPanel->Render();
m_consolePanel->Render();
m_projectPanel->Render();
}
}
}

View File

@@ -0,0 +1,48 @@
#pragma once
#include <XCEngine/Core/Layer.h>
#include <XCEngine/Core/LayerStack.h>
#include <memory>
#include <string>
namespace XCEngine {
namespace Editor {
class MenuBar;
class HierarchyPanel;
class SceneViewPanel;
class GameViewPanel;
class InspectorPanel;
class ConsolePanel;
class ProjectPanel;
class EditorLayer : public Core::Layer {
public:
EditorLayer();
~EditorLayer() override = default;
void onAttach() override;
void onDetach() override;
void onUpdate(float dt) override;
void onEvent(void* event) override;
void onImGuiRender() override;
void SetProjectPath(const std::string& path);
private:
void setupDockspace();
void renderAllPanels();
std::string m_projectPath;
std::unique_ptr<MenuBar> m_menuBar;
std::unique_ptr<HierarchyPanel> m_hierarchyPanel;
std::unique_ptr<SceneViewPanel> m_sceneViewPanel;
std::unique_ptr<GameViewPanel> m_gameViewPanel;
std::unique_ptr<InspectorPanel> m_inspectorPanel;
std::unique_ptr<ConsolePanel> m_consolePanel;
std::unique_ptr<ProjectPanel> m_projectPanel;
};
}
}

View File

@@ -48,7 +48,9 @@ EditorSceneManager::ClipboardData EditorSceneManager::CopyEntityRecursive(const
data.name = entity->GetName();
if (auto* transform = entity->GetComponent<::XCEngine::Components::TransformComponent>()) {
// Transform 数据会被复制
data.localPosition = transform->GetLocalPosition();
data.localRotation = transform->GetLocalRotation();
data.localScale = transform->GetLocalScale();
}
for (auto* child : entity->GetChildren()) {
@@ -75,6 +77,12 @@ void EditorSceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) {
::XCEngine::Components::GameObject* newEntity = m_scene->CreateGameObject(data.name, parentObj);
if (auto* transform = newEntity->GetComponent<::XCEngine::Components::TransformComponent>()) {
transform->SetLocalPosition(data.localPosition);
transform->SetLocalRotation(data.localRotation);
transform->SetLocalScale(data.localScale);
}
if (parentObj == nullptr) {
m_rootEntities.push_back(newEntity);
}

View File

@@ -6,6 +6,8 @@
#include <optional>
#include <XCEngine/Core/Event.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +63,9 @@ private:
struct ClipboardData {
std::string name;
std::vector<std::unique_ptr<::XCEngine::Components::Component>> components;
Math::Vector3 localPosition = Math::Vector3::Zero();
Math::Quaternion localRotation = Math::Quaternion::Identity();
Math::Vector3 localScale = Math::Vector3::One();
std::vector<ClipboardData> children;
};

View File

@@ -1,6 +1,7 @@
#pragma once
#include <unordered_set>
#include <vector>
#include <algorithm>
#include <XCEngine/Core/Event.h>
#include <XCEngine/Components/GameObject.h>
@@ -15,26 +16,65 @@ public:
return instance;
}
::XCEngine::Components::GameObject* GetSelectedEntity() const { return m_selectedEntity; }
void SetSelectedEntity(::XCEngine::Components::GameObject* entity) {
m_selectedEntity = entity;
if (m_selectedEntities.empty()) {
m_selectedEntities.push_back(entity);
} else {
m_selectedEntities[0] = entity;
m_selectedEntities.resize(1);
}
OnSelectionChanged.Invoke(entity ? entity->GetID() : 0);
}
void ClearSelection() {
SetSelectedEntity(nullptr);
void SetSelectedEntities(const std::vector<::XCEngine::Components::GameObject*>& entities) {
m_selectedEntities = entities;
if (!entities.empty()) {
OnSelectionChanged.Invoke(entities.back()->GetID());
} else {
OnSelectionChanged.Invoke(0);
}
}
void AddToSelection(::XCEngine::Components::GameObject* entity) {
if (entity && !IsSelected(entity)) {
m_selectedEntities.push_back(entity);
OnSelectionChanged.Invoke(entity->GetID());
}
}
void RemoveFromSelection(::XCEngine::Components::GameObject* entity) {
auto it = std::find(m_selectedEntities.begin(), m_selectedEntities.end(), entity);
if (it != m_selectedEntities.end()) {
m_selectedEntities.erase(it);
OnSelectionChanged.Invoke(entity ? entity->GetID() : 0);
}
}
void ClearSelection() {
m_selectedEntities.clear();
OnSelectionChanged.Invoke(0);
}
::XCEngine::Components::GameObject* GetSelectedEntity() const {
return m_selectedEntities.empty() ? nullptr : m_selectedEntities.back();
}
const std::vector<::XCEngine::Components::GameObject*>& GetSelectedEntities() const {
return m_selectedEntities;
}
bool HasSelection() const { return !m_selectedEntities.empty(); }
size_t GetSelectionCount() const { return m_selectedEntities.size(); }
bool IsSelected(::XCEngine::Components::GameObject* entity) const {
return m_selectedEntity == entity;
return std::find(m_selectedEntities.begin(), m_selectedEntities.end(), entity) != m_selectedEntities.end();
}
::XCEngine::Core::Event<uint64_t> OnSelectionChanged;
private:
SelectionManager() = default;
::XCEngine::Components::GameObject* m_selectedEntity = nullptr;
std::vector<::XCEngine::Components::GameObject*> m_selectedEntities;
};
}

View File

@@ -93,4 +93,4 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return 0;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
}

View File

@@ -1,6 +1,8 @@
#include "HierarchyPanel.h"
#include "Managers/SceneManager.h"
#include "Managers/SelectionManager.h"
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <imgui.h>
#include <cstring>
@@ -31,8 +33,8 @@ void HierarchyPanel::Render() {
ImGui::BeginChild("EntityList");
for (auto* entity : EditorSceneManager::Get().GetRootEntities()) {
RenderEntity(entity, filter);
for (auto* gameObject : EditorSceneManager::Get().GetRootEntities()) {
RenderEntity(gameObject, filter);
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) {
@@ -49,9 +51,9 @@ void HierarchyPanel::Render() {
ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1));
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
::XCEngine::Components::GameObject* sourceEntity = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceEntity && sourceEntity->GetParent() != nullptr) {
EditorSceneManager::Get().MoveEntity(sourceEntity->GetID(), 0);
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
EditorSceneManager::Get().MoveEntity(sourceGameObject->GetID(), 0);
}
}
ImGui::EndDragDropTarget();
@@ -67,26 +69,26 @@ void HierarchyPanel::RenderSearchBar() {
ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer));
}
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, const std::string& filter) {
if (!entity) return;
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return;
if (!filter.empty() && !PassesFilter(entity, filter)) {
if (!filter.empty() && !PassesFilter(gameObject, filter)) {
return;
}
ImGui::PushID(static_cast<int>(entity->GetID()));
ImGui::PushID(static_cast<int>(gameObject->GetID()));
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
if (entity->GetChildCount() == 0) {
if (gameObject->GetChildCount() == 0) {
flags |= ImGuiTreeNodeFlags_Leaf;
}
if (SelectionManager::Get().IsSelected(entity)) {
if (SelectionManager::Get().IsSelected(gameObject)) {
flags |= ImGuiTreeNodeFlags_Selected;
}
if (m_renaming && m_renamingEntity == entity) {
if (m_renaming && m_renamingEntity == gameObject) {
if (m_renameJustStarted) {
ImGui::SetKeyboardFocusHere();
m_renameJustStarted = false;
@@ -95,7 +97,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
if (strlen(m_renameBuffer) > 0) {
EditorSceneManager::Get().RenameEntity(entity->GetID(), m_renameBuffer);
EditorSceneManager::Get().RenameEntity(gameObject->GetID(), m_renameBuffer);
}
m_renaming = false;
m_renamingEntity = nullptr;
@@ -103,35 +105,42 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
if (strlen(m_renameBuffer) > 0) {
EditorSceneManager::Get().RenameEntity(entity->GetID(), m_renameBuffer);
EditorSceneManager::Get().RenameEntity(gameObject->GetID(), m_renameBuffer);
}
m_renaming = false;
m_renamingEntity = nullptr;
}
} else {
bool isOpen = ImGui::TreeNodeEx(entity->GetName().c_str(), flags);
bool isOpen = ImGui::TreeNodeEx((void*)gameObject->GetUUID(), flags, "%s", gameObject->GetName().c_str());
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
SelectionManager::Get().SetSelectedEntity(entity);
ImGuiIO& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (!SelectionManager::Get().IsSelected(gameObject)) {
SelectionManager::Get().AddToSelection(gameObject);
}
} else {
SelectionManager::Get().SetSelectedEntity(gameObject);
}
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_renaming = true;
m_renamingEntity = entity;
strcpy_s(m_renameBuffer, entity->GetName().c_str());
m_renamingEntity = gameObject;
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
m_renameJustStarted = true;
}
HandleDragDrop(entity);
HandleDragDrop(gameObject);
if (ImGui::BeginPopupContextItem("EntityContextMenu")) {
RenderContextMenu(entity);
RenderContextMenu(gameObject);
ImGui::EndPopup();
}
if (isOpen) {
for (size_t i = 0; i < entity->GetChildCount(); i++) {
RenderEntity(entity->GetChild(i), filter);
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
RenderEntity(gameObject->GetChild(i), filter);
}
ImGui::TreePop();
}
@@ -140,44 +149,54 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* entity, co
ImGui::PopID();
}
void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* entity) {
void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) {
auto& sceneManager = EditorSceneManager::Get();
auto& selectionManager = SelectionManager::Get();
if (ImGui::BeginMenu("Create")) {
RenderCreateMenu(entity);
RenderCreateMenu(gameObject);
ImGui::EndMenu();
}
if (gameObject != nullptr && ImGui::MenuItem("Create Child")) {
auto* child = sceneManager.CreateEntity("GameObject", gameObject);
selectionManager.SetSelectedEntity(child);
}
ImGui::Separator();
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
if (ImGui::MenuItem("Detach from Parent")) {
gameObject->DetachFromParent();
}
}
if (ImGui::MenuItem("Rename", "F2")) {
if (entity) {
if (gameObject) {
m_renaming = true;
m_renamingEntity = entity;
strcpy_s(m_renameBuffer, entity->GetName().c_str());
m_renamingEntity = gameObject;
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
m_renameJustStarted = true;
}
}
if (ImGui::MenuItem("Delete", "Delete")) {
sceneManager.DeleteEntity(entity->GetID());
sceneManager.DeleteEntity(gameObject->GetID());
}
ImGui::Separator();
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
sceneManager.CopyEntity(entity->GetID());
sceneManager.CopyEntity(gameObject->GetID());
}
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
sceneManager.PasteEntity(entity->GetID());
sceneManager.PasteEntity(gameObject->GetID());
}
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
uint64_t newId = sceneManager.DuplicateEntity(entity->GetID());
uint64_t newId = sceneManager.DuplicateEntity(gameObject->GetID());
if (newId != 0) {
// Selection handled via callback
}
}
}
@@ -225,24 +244,23 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent
}
}
void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* entity) {
void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) {
auto& sceneManager = EditorSceneManager::Get();
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
m_dragSource = entity;
ImGui::SetDragDropPayload("ENTITY_PTR", &entity, sizeof(::XCEngine::Components::GameObject*));
ImGui::Text("%s", entity->GetName().c_str());
ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*));
ImGui::Text("%s", gameObject->GetName().c_str());
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
::XCEngine::Components::GameObject* sourceEntity = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceEntity != entity && sourceEntity != nullptr) {
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject != gameObject && sourceGameObject != nullptr) {
bool isValidMove = true;
::XCEngine::Components::GameObject* checkParent = entity;
::XCEngine::Components::GameObject* checkParent = gameObject;
while (checkParent != nullptr) {
if (checkParent == sourceEntity) {
if (checkParent == sourceGameObject) {
isValidMove = false;
break;
}
@@ -250,7 +268,16 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* entity)
}
if (isValidMove) {
sceneManager.MoveEntity(sourceEntity->GetID(), entity->GetID());
auto* srcTransform = sourceGameObject->GetTransform();
Math::Vector3 worldPos = srcTransform->GetPosition();
Math::Quaternion worldRot = srcTransform->GetRotation();
Math::Vector3 worldScale = srcTransform->GetScale();
sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID());
srcTransform->SetPosition(worldPos);
srcTransform->SetRotation(worldRot);
srcTransform->SetScale(worldScale);
}
}
}
@@ -262,20 +289,20 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
auto& sceneManager = EditorSceneManager::Get();
auto& selectionManager = SelectionManager::Get();
::XCEngine::Components::GameObject* selectedEntity = selectionManager.GetSelectedEntity();
::XCEngine::Components::GameObject* selectedGameObject = selectionManager.GetSelectedEntity();
if (ImGui::IsWindowFocused()) {
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
if (selectedEntity != nullptr) {
sceneManager.DeleteEntity(selectedEntity->GetID());
if (selectedGameObject != nullptr) {
sceneManager.DeleteEntity(selectedGameObject->GetID());
}
}
if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
if (selectedEntity != nullptr) {
if (selectedGameObject != nullptr) {
m_renaming = true;
m_renamingEntity = selectedEntity;
strcpy_s(m_renameBuffer, selectedEntity->GetName().c_str());
m_renamingEntity = selectedGameObject;
strcpy_s(m_renameBuffer, selectedGameObject->GetName().c_str());
m_renameJustStarted = true;
}
}
@@ -283,35 +310,35 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
ImGuiIO& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
if (selectedEntity != nullptr) {
sceneManager.CopyEntity(selectedEntity->GetID());
if (selectedGameObject != nullptr) {
sceneManager.CopyEntity(selectedGameObject->GetID());
}
}
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
if (sceneManager.HasClipboardData()) {
sceneManager.PasteEntity(selectedEntity ? selectedEntity->GetID() : 0);
sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0);
}
}
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
if (selectedEntity != nullptr) {
sceneManager.DuplicateEntity(selectedEntity->GetID());
if (selectedGameObject != nullptr) {
sceneManager.DuplicateEntity(selectedGameObject->GetID());
}
}
}
}
}
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* entity, const std::string& filter) {
if (!entity) return false;
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return false;
if (entity->GetName().find(filter) != std::string::npos) {
if (gameObject->GetName().find(filter) != std::string::npos) {
return true;
}
for (size_t i = 0; i < entity->GetChildCount(); i++) {
if (PassesFilter(entity->GetChild(i), filter)) {
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
if (PassesFilter(gameObject->GetChild(i), filter)) {
return true;
}
}

View File

@@ -15,12 +15,12 @@ public:
private:
void RenderSearchBar();
void RenderEntity(::XCEngine::Components::GameObject* entity, const std::string& filter);
void RenderContextMenu(::XCEngine::Components::GameObject* entity);
void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
void RenderContextMenu(::XCEngine::Components::GameObject* gameObject);
void RenderCreateMenu(::XCEngine::Components::GameObject* parent);
void HandleDragDrop(::XCEngine::Components::GameObject* entity);
void HandleDragDrop(::XCEngine::Components::GameObject* gameObject);
void HandleKeyboardShortcuts();
bool PassesFilter(::XCEngine::Components::GameObject* entity, const std::string& filter);
bool PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
uint64_t m_selectionHandlerId = 0;
@@ -29,7 +29,6 @@ private:
::XCEngine::Components::GameObject* m_renamingEntity = nullptr;
char m_renameBuffer[256] = "";
bool m_renameJustStarted = false;
::XCEngine::Components::GameObject* m_dragSource = nullptr;
};
}

View File

@@ -10,6 +10,7 @@ namespace Editor {
InspectorPanel::InspectorPanel() : Panel("Inspector") {
m_selectionHandlerId = SelectionManager::Get().OnSelectionChanged.Subscribe([this](uint64_t) {
m_selectedGameObject = SelectionManager::Get().GetSelectedEntity();
});
}
@@ -20,10 +21,10 @@ InspectorPanel::~InspectorPanel() {
void InspectorPanel::Render() {
ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None);
::XCEngine::Components::GameObject* entity = SelectionManager::Get().GetSelectedEntity();
m_selectedGameObject = SelectionManager::Get().GetSelectedEntity();
if (entity) {
RenderEntity(entity);
if (m_selectedGameObject) {
RenderGameObject(m_selectedGameObject);
} else {
ImGui::Text("No object selected");
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an object in Hierarchy");
@@ -32,27 +33,80 @@ void InspectorPanel::Render() {
ImGui::End();
}
void InspectorPanel::RenderEntity(::XCEngine::Components::GameObject* entity) {
ImGui::Text("%s", entity->GetName().c_str());
ImGui::Separator();
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
char nameBuffer[256];
strcpy_s(nameBuffer, gameObject->GetName().c_str());
ImGui::InputText("##Name", nameBuffer, sizeof(nameBuffer));
if (ImGui::IsItemDeactivatedAfterEdit()) {
gameObject->SetName(nameBuffer);
}
auto components = entity->GetComponents<::XCEngine::Components::Component>();
ImGui::SameLine();
if (ImGui::Button("Add Component")) {
ImGui::OpenPopup("AddComponent");
}
RenderAddComponentPopup(gameObject);
auto components = gameObject->GetComponents<::XCEngine::Components::Component>();
for (auto* component : components) {
RenderComponent(component);
ImGui::Separator();
RenderComponent(component, gameObject);
}
}
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component) {
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
if (!ImGui::BeginPopup("AddComponent")) {
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 10.0f));
ImGui::SeparatorText("Components");
if (ImGui::MenuItem("Transform", nullptr, false, !gameObject->GetComponent<::XCEngine::Components::TransformComponent>())) {
gameObject->AddComponent<::XCEngine::Components::TransformComponent>();
ImGui::CloseCurrentPopup();
}
ImGui::SeparatorText("Other");
ImGui::TextDisabled("No more components available");
ImGui::PopStyleVar();
ImGui::EndPopup();
}
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
if (!component) return;
const char* name = component->GetName().c_str();
std::string headerId = std::string(name) + "##" + std::to_string(reinterpret_cast<uintptr_t>(component));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 2});
if (ImGui::CollapsingHeader(headerId.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
ImGuiTreeNodeFlags flags =
ImGuiTreeNodeFlags_DefaultOpen |
ImGuiTreeNodeFlags_Framed |
ImGuiTreeNodeFlags_SpanAvailWidth |
ImGuiTreeNodeFlags_FramePadding |
ImGuiTreeNodeFlags_AllowOverlap;
bool open = ImGui::TreeNodeEx((void*)typeid(*component).hash_code(), flags, "%s", name);
ImGui::PopStyleVar();
bool removeComponent = false;
if (ImGui::BeginPopupContextItem("ComponentSettings")) {
if (ImGui::MenuItem("Remove Component")) {
removeComponent = true;
}
ImGui::EndPopup();
}
if (removeComponent) {
RemoveComponentByType(component, gameObject);
return;
}
if (open) {
if (auto* transform = dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
@@ -71,7 +125,23 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
}
}
ImGui::Unindent(10.0f);
ImGui::TreePop();
}
}
void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
if (!component || !gameObject) return;
if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
return;
}
auto components = gameObject->GetComponents<::XCEngine::Components::Component>();
for (auto* comp : components) {
if (comp == component) {
gameObject->RemoveComponent<::XCEngine::Components::Component>();
break;
}
}
}

View File

@@ -15,10 +15,13 @@ public:
void Render() override;
private:
void RenderEntity(::XCEngine::Components::GameObject* entity);
void RenderComponent(::XCEngine::Components::Component* component);
void RenderGameObject(::XCEngine::Components::GameObject* gameObject);
void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject);
void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
void RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
uint64_t m_selectionHandlerId = 0;
::XCEngine::Components::GameObject* m_selectedGameObject = nullptr;
};
}

View File

@@ -2,5 +2,6 @@
namespace XCEngine {
namespace Editor {
}
}
}

View File

@@ -1,27 +1,27 @@
#pragma once
#include <XCEngine/Core/Layer.h>
#include <string>
#include <imgui.h>
namespace XCEngine {
namespace Editor {
class Panel {
class Panel : public Core::Layer {
public:
Panel(const std::string& name) : m_name(name), m_isOpen(true) {}
Panel(const std::string& name) : Layer(name), m_isOpen(true) {}
virtual ~Panel() = default;
virtual void Render() = 0;
const std::string& GetName() const { return m_name; }
bool IsOpen() const { return m_isOpen; }
void SetOpen(bool open) { m_isOpen = open; }
void Toggle() { m_isOpen = !m_isOpen; }
void onImGuiRender() override { Render(); }
protected:
std::string m_name;
bool m_isOpen;
};
}
}
}

View File

@@ -8,6 +8,7 @@
#include <unordered_map>
#include <istream>
#include <ostream>
#include <random>
namespace XCEngine {
namespace Components {
@@ -24,9 +25,12 @@ public:
~GameObject();
ID GetID() const { return m_id; }
uint64_t GetUUID() const { return m_uuid; }
const std::string& GetName() const { return m_name; }
void SetName(const std::string& name) { m_name = name; }
void DetachFromParent();
Scene* GetScene() const { return m_scene; }
TransformComponent* GetTransform() { return m_transform; }
@@ -83,6 +87,16 @@ public:
return result;
}
template<typename T>
void RemoveComponent() {
for (auto it = m_components.begin(); it != m_components.end(); ++it) {
if (T* casted = dynamic_cast<T*>(it->get())) {
m_components.erase(it);
return;
}
}
}
template<typename T>
T* GetComponentInChildren() {
T* comp = GetComponent<T>();
@@ -154,6 +168,7 @@ public:
private:
ID m_id = INVALID_ID;
uint64_t m_uuid = 0;
std::string m_name;
bool m_activeSelf = true;

View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
namespace XCEngine {
namespace Core {
class Layer {
public:
Layer(const std::string& name = "Layer") : m_name(name) {}
virtual ~Layer() = default;
virtual void onAttach() {}
virtual void onDetach() {}
virtual void onUpdate(float dt) {}
virtual void onEvent(void* event) {}
virtual void onImGuiRender() {}
const std::string& getName() const { return m_name; }
protected:
std::string m_name;
};
}
}

View File

@@ -0,0 +1,100 @@
#pragma once
#include "Layer.h"
#include <vector>
#include <algorithm>
#include <memory>
namespace XCEngine {
namespace Core {
class LayerStack {
public:
LayerStack() = default;
~LayerStack() = default;
void pushLayer(std::unique_ptr<Layer> layer);
void pushOverlay(std::unique_ptr<Layer> overlay);
void popLayer(Layer* layer);
void popOverlay(Layer* overlay);
void onUpdate(float dt);
void onEvent(void* event);
void onImGuiRender();
void onAttach();
void onDetach();
std::vector<std::unique_ptr<Layer>>::iterator begin() { return m_layers.begin(); }
std::vector<std::unique_ptr<Layer>>::iterator end() { return m_layers.end(); }
std::vector<std::unique_ptr<Layer>>::reverse_iterator rbegin() { return m_layers.rbegin(); }
std::vector<std::unique_ptr<Layer>>::reverse_iterator rend() { return m_layers.rend(); }
std::vector<std::unique_ptr<Layer>>::const_iterator cbegin() const { return m_layers.begin(); }
std::vector<std::unique_ptr<Layer>>::const_iterator cend() const { return m_layers.end(); }
std::vector<std::unique_ptr<Layer>>::const_reverse_iterator crbegin() const { return m_layers.crbegin(); }
std::vector<std::unique_ptr<Layer>>::const_reverse_iterator crend() const { return m_layers.crend(); }
private:
std::vector<std::unique_ptr<Layer>> m_layers;
unsigned int m_layerInsertIndex = 0;
};
inline void LayerStack::pushLayer(std::unique_ptr<Layer> layer) {
m_layers.emplace(m_layers.begin() + m_layerInsertIndex, std::move(layer));
m_layerInsertIndex++;
}
inline void LayerStack::pushOverlay(std::unique_ptr<Layer> overlay) {
m_layers.emplace_back(std::move(overlay));
}
inline void LayerStack::popLayer(Layer* layer) {
auto it = std::find_if(m_layers.begin(), m_layers.begin() + m_layerInsertIndex,
[layer](const std::unique_ptr<Layer>& ptr) { return ptr.get() == layer; });
if (it != m_layers.begin() + m_layerInsertIndex) {
(*it)->onDetach();
m_layers.erase(it);
m_layerInsertIndex--;
}
}
inline void LayerStack::popOverlay(Layer* overlay) {
auto it = std::find_if(m_layers.begin() + m_layerInsertIndex, m_layers.end(),
[overlay](const std::unique_ptr<Layer>& ptr) { return ptr.get() == overlay; });
if (it != m_layers.end()) {
(*it)->onDetach();
m_layers.erase(it);
}
}
inline void LayerStack::onUpdate(float dt) {
for (auto& layer : m_layers) {
layer->onUpdate(dt);
}
}
inline void LayerStack::onEvent(void* event) {
for (auto& layer : m_layers) {
layer->onEvent(event);
}
}
inline void LayerStack::onImGuiRender() {
for (auto& layer : m_layers) {
layer->onImGuiRender();
}
}
inline void LayerStack::onAttach() {
for (auto& layer : m_layers) {
layer->onAttach();
}
}
inline void LayerStack::onDetach() {
for (auto& layer : m_layers) {
layer->onDetach();
}
}
}
}

View File

@@ -10,6 +10,10 @@ GameObject::ID GameObject::s_nextID = 1;
GameObject::GameObject()
: m_name("GameObject") {
m_id = s_nextID++;
static std::random_device rd;
static std::mt19937_64 gen(rd());
static std::uniform_int_distribution<uint64_t> dis(1, UINT64_MAX);
m_uuid = dis(gen);
m_transform = new TransformComponent();
m_transform->m_gameObject = this;
}
@@ -17,6 +21,10 @@ GameObject::GameObject()
GameObject::GameObject(const std::string& name)
: m_name(name) {
m_id = s_nextID++;
static std::random_device rd;
static std::mt19937_64 gen(rd());
static std::uniform_int_distribution<uint64_t> dis(1, UINT64_MAX);
m_uuid = dis(gen);
m_transform = new TransformComponent();
m_transform->m_gameObject = this;
}
@@ -88,6 +96,27 @@ void GameObject::DetachChildren() {
m_children.clear();
}
void GameObject::DetachFromParent() {
if (m_parent) {
Math::Vector3 worldPos = GetTransform()->GetPosition();
Math::Quaternion worldRot = GetTransform()->GetRotation();
Math::Vector3 worldScale = GetTransform()->GetScale();
auto& siblings = m_parent->m_children;
siblings.erase(std::remove(siblings.begin(), siblings.end(), this), siblings.end());
m_parent = nullptr;
if (m_scene) {
m_scene->m_rootGameObjects.push_back(m_id);
}
GetTransform()->SetPosition(worldPos);
GetTransform()->SetRotation(worldRot);
GetTransform()->SetScale(worldScale);
GetTransform()->SetDirty();
}
}
void GameObject::SetActive(bool active) {
if (m_activeSelf != active) {
m_activeSelf = active;

55
imgui.ini Normal file
View File

@@ -0,0 +1,55 @@
[Window][Hierarchy]
Pos=0,24
Size=189,485
Collapsed=0
DockId=0x00000003,0
[Window][Scene]
Pos=191,24
Size=594,485
Collapsed=0
DockId=0x00000005,0
[Window][Game]
Pos=191,24
Size=594,485
Collapsed=0
DockId=0x00000005,1
[Window][Inspector]
Pos=787,24
Size=533,485
Collapsed=0
DockId=0x00000006,0
[Window][Console]
Pos=0,511
Size=1320,170
Collapsed=0
DockId=0x00000002,0
[Window][Project]
Pos=0,511
Size=1320,170
Collapsed=0
DockId=0x00000002,1
[Window][MainDockspace]
Pos=0,0
Size=1320,681
Collapsed=0
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Docking][Data]
DockSpace ID=0x8D32E0A4 Window=0x1C358F53 Pos=0,24 Size=1320,657 Split=Y
DockNode ID=0x00000001 Parent=0x8D32E0A4 SizeRef=1264,509 Split=X
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=189,509 Selected=0xBABDAE5E
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1073,509 Split=X Selected=0xE601B12F
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=538,485 CentralNode=1 Selected=0xE601B12F
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=533,485 Selected=0x36DC96AB
DockNode ID=0x00000002 Parent=0x8D32E0A4 SizeRef=1264,170 Selected=0x9C21DE82

BIN
minimal.ppm Normal file

Binary file not shown.