Refactor editor shell host layers

This commit is contained in:
2026-03-26 23:52:05 +08:00
parent f87bc53875
commit 31675e00c8
18 changed files with 738 additions and 518 deletions

View File

@@ -103,6 +103,8 @@
- `MenuBar Edit` 已开始跟随 `Hierarchy / Project` 切换动作目标
- `Rename` 这类依赖 panel 内联状态的动作,开始通过 `EventBus` 请求而不是直接耦合 panel 实现
- `Edit` 动作解析与菜单绘制 / shortcut 分发已开始从 `MenuBar` 抽成共享 router
- `Hierarchy / Project` 的上下文菜单与创建弹窗也开始下沉到 shared action router
- `Project` 右键菜单目标已不再依赖 panel 内裸索引字段,而是改成 targeted popup state
### 5. Dock / Layout 层
@@ -131,6 +133,7 @@
- `EditorLayer` 不再保存一串分散的 panel 生命周期样板代码
- panel 的 attach / detach / render 顺序有了统一入口
- 后续继续拆 panel 或补 panel 时,不需要再改一大片壳层代码
- startup scene / dock attach / panel tree 组装已继续从 `EditorLayer` 收口到 `EditorWorkspace`
### 7. Application / ImGui Session 层
@@ -145,6 +148,9 @@
- ImGui 生命周期不再继续堆在 `Application.cpp`
- Win32 / DX12 backend API 不再散落在 `Application.cpp``main.cpp`
- 后续继续清理 backend 初始化边界时,有稳定落点
- Win32 window/message pump 已抽成 `Platform/Win32EditorHost.h`
- DX12 swapchain / render target / present / resize 已抽成 `Platform/D3D12WindowRenderer.h`
- scene title 拼装已抽成 `Core/EditorWindowTitle.h`
## 主要面板状态

View File

@@ -0,0 +1,77 @@
#pragma once
#include "EditorActions.h"
#include "Commands/EntityCommands.h"
#include "Core/EditorEvents.h"
#include "Core/EventBus.h"
#include "Core/IEditorContext.h"
namespace XCEngine {
namespace Editor {
namespace Actions {
inline void RequestEntityRename(IEditorContext& context, const ::XCEngine::Components::GameObject* gameObject) {
if (!gameObject) {
return;
}
context.GetEventBus().Publish(EntityRenameRequestedEvent{ gameObject->GetID() });
}
inline void DrawHierarchyCreateActions(IEditorContext& context, ::XCEngine::Components::GameObject* parent) {
DrawMenuAction(MakeCreateEmptyEntityAction(), [&]() {
Commands::CreateEmptyEntity(context, parent, "Create Entity", "GameObject");
});
DrawMenuSeparator();
DrawMenuAction(MakeCreateCameraEntityAction(), [&]() {
Commands::CreateCameraEntity(context, parent);
});
DrawMenuAction(MakeCreateLightEntityAction(), [&]() {
Commands::CreateLightEntity(context, parent);
});
DrawMenuSeparator();
DrawMenuAction(MakeCreateCubeEntityAction(), [&]() {
Commands::CreateEmptyEntity(context, parent, "Create Cube", "Cube");
});
DrawMenuAction(MakeCreateSphereEntityAction(), [&]() {
Commands::CreateEmptyEntity(context, parent, "Create Sphere", "Sphere");
});
DrawMenuAction(MakeCreatePlaneEntityAction(), [&]() {
Commands::CreateEmptyEntity(context, parent, "Create Plane", "Plane");
});
}
inline void DrawHierarchyContextActions(IEditorContext& context, ::XCEngine::Components::GameObject* gameObject) {
if (UI::DrawMenuScope("Create", [&]() {
DrawHierarchyCreateActions(context, gameObject);
})) {
}
DrawMenuAction(MakeCreateChildEntityAction(gameObject), [&]() {
Commands::CreateEmptyEntity(context, gameObject, "Create Child", "GameObject");
});
DrawMenuSeparator();
DrawMenuAction(MakeDetachEntityAction(gameObject), [&]() {
Commands::DetachEntity(context, gameObject);
});
DrawMenuAction(MakeRenameEntityAction(gameObject), [&]() {
RequestEntityRename(context, gameObject);
});
DrawMenuAction(MakeDeleteEntityAction(gameObject), [&]() {
Commands::DeleteEntity(context, gameObject->GetID());
});
DrawMenuSeparator();
DrawMenuAction(MakeCopyEntityAction(gameObject), [&]() {
Commands::CopyEntity(context, gameObject->GetID());
});
DrawMenuAction(MakePasteEntityAction(context), [&]() {
Commands::PasteEntity(context, gameObject->GetID());
});
DrawMenuAction(MakeDuplicateEntityAction(gameObject), [&]() {
Commands::DuplicateEntity(context, gameObject->GetID());
});
}
} // namespace Actions
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,83 @@
#pragma once
#include "EditorActions.h"
#include "Commands/ProjectCommands.h"
#include "Core/IEditorContext.h"
#include "Core/IProjectManager.h"
#include "UI/PopupState.h"
namespace XCEngine {
namespace Editor {
namespace Actions {
inline int FindProjectItemIndex(IProjectManager& projectManager, const AssetItemPtr& item) {
if (!item) {
return -1;
}
const auto& items = projectManager.GetCurrentItems();
for (size_t i = 0; i < items.size(); ++i) {
if (items[i] == item) {
return static_cast<int>(i);
}
if (items[i] && items[i]->fullPath == item->fullPath) {
return static_cast<int>(i);
}
}
return -1;
}
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item) {
auto& projectManager = context.GetProjectManager();
const int itemIndex = FindProjectItemIndex(projectManager, item);
DrawMenuAction(MakeOpenAssetAction(Commands::CanOpenAsset(item)), [&]() {
Commands::OpenAsset(context, item);
});
DrawMenuSeparator();
DrawMenuAction(MakeDeleteAssetAction(itemIndex >= 0), [&]() {
Commands::DeleteAsset(projectManager, itemIndex);
});
}
template <size_t BufferCapacity>
inline void DrawProjectEmptyContextActions(UI::TextInputPopupState<BufferCapacity>& createFolderDialog) {
DrawMenuAction(MakeCreateFolderAction(), [&]() {
createFolderDialog.RequestOpen("NewFolder");
});
}
template <size_t BufferCapacity>
inline void DrawProjectCreateFolderDialog(IEditorContext& context, UI::TextInputPopupState<BufferCapacity>& createFolderDialog) {
createFolderDialog.ConsumeOpenRequest("Create Folder");
if (!UI::BeginModalPopup("Create Folder")) {
return;
}
ImGui::InputText("Name", createFolderDialog.Buffer(), createFolderDialog.BufferSize());
ImGui::Separator();
switch (UI::DrawDialogActionRow("Create", "Cancel", !createFolderDialog.Empty())) {
case UI::DialogActionResult::Primary:
if (Commands::CreateFolder(context.GetProjectManager(), createFolderDialog.Buffer())) {
createFolderDialog.Clear();
ImGui::CloseCurrentPopup();
}
break;
case UI::DialogActionResult::Secondary:
createFolderDialog.Clear();
ImGui::CloseCurrentPopup();
break;
case UI::DialogActionResult::None:
break;
}
UI::EndPopup();
}
} // namespace Actions
} // namespace Editor
} // namespace XCEngine

View File

@@ -1,65 +1,20 @@
#include "Application.h"
#include "Core/EditorWindowTitle.h"
#include "Layers/EditorLayer.h"
#include "Core/EditorContext.h"
#include "Core/EditorConsoleSink.h"
#include "Core/EditorEvents.h"
#include "Core/EventBus.h"
#include "Platform/Win32Utf8.h"
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Debug/FileLogSink.h>
#include <XCEngine/Debug/ConsoleLogSink.h>
#include <imgui_internal.h>
#include <filesystem>
#include <stdio.h>
#include <windows.h>
#include <string>
namespace {
std::string WideToUtf8(const std::wstring& value) {
if (value.empty()) {
return {};
}
int len = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len <= 0) {
return {};
}
std::string result(len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], len, nullptr, nullptr);
return result;
}
std::wstring Utf8ToWide(const std::string& value) {
if (value.empty()) {
return {};
}
int len = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
if (len <= 0) {
return {};
}
std::wstring result(len - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], len);
return result;
}
std::string GetExecutableDirectoryUtf8() {
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
std::wstring exeDirW(exePath);
const size_t pos = exeDirW.find_last_of(L"\\/");
if (pos != std::wstring::npos) {
exeDirW = exeDirW.substr(0, pos);
}
return WideToUtf8(exeDirW);
}
std::string GetExecutableLogPath(const char* fileName) {
return GetExecutableDirectoryUtf8() + "\\" + fileName;
return XCEngine::Editor::Platform::GetExecutableDirectoryUtf8() + "\\" + fileName;
}
} // namespace
@@ -92,24 +47,17 @@ Application& Application::Get() {
}
bool Application::Initialize(HWND hwnd) {
// Set global exception filter first to catch any crashes
SetUnhandledExceptionFilter(GlobalExceptionFilter);
// Redirect stderr to log file to capture ImGui errors
{
const std::string stderrPath = GetExecutableLogPath("stderr.log");
freopen(stderrPath.c_str(), "w", stderr);
fprintf(stderr, "[TEST] stderr redirection test - this should appear in stderr.log\n");
fflush(stderr);
}
// Initialize logging first
Debug::Logger::Get().AddSink(std::make_unique<Debug::ConsoleLogSink>());
Debug::Logger::Get().AddSink(std::make_unique<Debug::EditorConsoleSink>());
// Get exe directory for log file path
const std::string exeDir = GetExecutableDirectoryUtf8();
const std::string exeDir = Platform::GetExecutableDirectoryUtf8();
std::string logPath = exeDir + "\\editor.log";
Debug::Logger::Get().AddSink(std::make_unique<Debug::FileLogSink>(logPath.c_str()));
Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting...");
@@ -117,14 +65,10 @@ bool Application::Initialize(HWND hwnd) {
m_hwnd = hwnd;
if (!CreateDevice()) {
if (!m_windowRenderer.Initialize(hwnd, 1280, 720)) {
MessageBoxW(hwnd, L"Failed to create D3D12 device", L"Error", MB_OK | MB_ICONERROR);
return false;
}
if (!CreateRenderTarget()) {
MessageBoxW(hwnd, L"Failed to create render target", L"Error", MB_OK | MB_ICONERROR);
return false;
}
m_editorContext = std::make_shared<EditorContext>();
m_editorContext->SetProjectPath(exeDir);
@@ -136,7 +80,7 @@ bool Application::Initialize(HWND hwnd) {
});
m_imguiSession.Initialize(m_editorContext->GetProjectPath());
m_imguiBackend.Initialize(hwnd, m_device, m_srvHeap);
m_imguiBackend.Initialize(hwnd, m_windowRenderer.GetDevice(), m_windowRenderer.GetSrvHeap());
m_editorLayer = new EditorLayer();
m_editorLayer->SetContext(m_editorContext);
@@ -156,17 +100,7 @@ void Application::Shutdown() {
m_imguiBackend.Shutdown();
m_imguiSession.Shutdown();
CleanupRenderTarget();
if (m_fence) m_fence->Release();
if (m_commandList) m_commandList->Release();
if (m_commandAllocator) m_commandAllocator->Release();
if (m_commandQueue) m_commandQueue->Release();
if (m_rtvHeap) m_rtvHeap->Release();
if (m_srvHeap) m_srvHeap->Release();
if (m_swapChain) m_swapChain->Release();
if (m_device) m_device->Release();
m_windowRenderer.Shutdown();
}
void Application::Render() {
@@ -176,46 +110,9 @@ void Application::Render() {
UpdateWindowTitle();
ImGui::Render();
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator, nullptr);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = m_renderTargets[m_frameIndex];
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_commandList->ResourceBarrier(1, &barrier);
float clearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f };
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
m_commandList->SetDescriptorHeaps(1, heaps);
m_imguiBackend.RenderDrawData(m_commandList);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
m_commandList->ResourceBarrier(1, &barrier);
m_commandList->Close();
ID3D12CommandList* cmdLists[] = { m_commandList };
m_commandQueue->ExecuteCommandLists(1, cmdLists);
m_swapChain->Present(1, 0);
m_fenceValue++;
m_commandQueue->Signal(m_fence, m_fenceValue);
if (m_fence->GetCompletedValue() < m_fenceValue) {
m_fence->SetEventOnCompletion(m_fenceValue, nullptr);
}
m_windowRenderer.Render(m_imguiBackend, clearColor);
}
void Application::UpdateWindowTitle() {
@@ -223,23 +120,7 @@ void Application::UpdateWindowTitle() {
return;
}
auto& sceneManager = m_editorContext->GetSceneManager();
std::string sceneName = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
if (sceneName.empty()) {
sceneName = "Untitled Scene";
}
if (sceneManager.IsSceneDirty()) {
sceneName += " *";
}
if (sceneManager.GetCurrentScenePath().empty()) {
sceneName += " (Unsaved)";
} else {
sceneName += " - ";
sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
}
const std::wstring title = Utf8ToWide(sceneName + " - XCEngine Editor");
const std::wstring title = Platform::Utf8ToWide(BuildEditorWindowTitle(*m_editorContext));
if (title != m_lastWindowTitle) {
SetWindowTextW(m_hwnd, title.c_str());
m_lastWindowTitle = title;
@@ -247,107 +128,7 @@ void Application::UpdateWindowTitle() {
}
void Application::OnResize(int width, int height) {
if (width <= 0 || height <= 0) return;
m_width = width;
m_height = height;
CleanupRenderTarget();
if (m_swapChain) {
DXGI_SWAP_CHAIN_DESC desc;
m_swapChain->GetDesc(&desc);
m_swapChain->ResizeBuffers(3, width, height, desc.BufferDesc.Format, desc.Flags);
}
CreateRenderTarget();
}
bool Application::CreateDevice() {
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
if (FAILED(hr)) {
return false;
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Priority = 0;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.NodeMask = 0;
hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
if (FAILED(hr)) return false;
m_commandList->Close();
hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
if (FAILED(hr)) return false;
IDXGIFactory4* factory = nullptr;
hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr)) return false;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = 3;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
IDXGISwapChain1* swapChain1 = nullptr;
hr = factory->CreateSwapChainForHwnd(m_commandQueue, m_hwnd, &swapChainDesc, nullptr, nullptr, &swapChain1);
factory->Release();
if (FAILED(hr)) return false;
hr = swapChain1->QueryInterface(IID_PPV_ARGS(&m_swapChain));
swapChain1->Release();
if (FAILED(hr)) return false;
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = 3;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = m_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap));
if (FAILED(hr)) return false;
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvDesc.NumDescriptors = 1;
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
if (FAILED(hr)) return false;
return true;
}
bool Application::CreateRenderTarget() {
if (!m_swapChain || !m_device || !m_rtvHeap) return false;
for (UINT i = 0; i < 3; i++) {
HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i]));
if (FAILED(hr)) return false;
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += i * m_rtvDescriptorSize;
m_device->CreateRenderTargetView(m_renderTargets[i], nullptr, rtvHandle);
}
return true;
}
void Application::CleanupRenderTarget() {
for (UINT i = 0; i < 3; i++) {
if (m_renderTargets[i]) {
m_renderTargets[i]->Release();
m_renderTargets[i] = nullptr;
}
}
m_windowRenderer.Resize(width, height);
}
}

View File

@@ -1,13 +1,12 @@
#pragma once
#include "Platform/D3D12WindowRenderer.h"
#include "UI/ImGuiBackendBridge.h"
#include "UI/ImGuiSession.h"
#include <memory>
#include <string>
#include <imgui.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <windows.h>
#include <XCEngine/Core/LayerStack.h>
@@ -32,32 +31,14 @@ public:
private:
Application() = default;
~Application() = default;
bool CreateDevice();
bool CreateRenderTarget();
void CleanupRenderTarget();
void UpdateWindowTitle();
HWND m_hwnd = nullptr;
int m_width = 1280;
int m_height = 720;
ID3D12Device* m_device = nullptr;
ID3D12CommandQueue* m_commandQueue = nullptr;
ID3D12CommandAllocator* m_commandAllocator = nullptr;
ID3D12GraphicsCommandList* m_commandList = nullptr;
IDXGISwapChain3* m_swapChain = nullptr;
ID3D12DescriptorHeap* m_rtvHeap = nullptr;
ID3D12DescriptorHeap* m_srvHeap = nullptr;
ID3D12Resource* m_renderTargets[3] = {};
ID3D12Fence* m_fence = nullptr;
UINT64 m_fenceValue = 0;
UINT m_rtvDescriptorSize = 0;
UINT m_frameIndex = 0;
Core::LayerStack m_layerStack;
EditorLayer* m_editorLayer = nullptr;
std::shared_ptr<IEditorContext> m_editorContext;
Platform::D3D12WindowRenderer m_windowRenderer;
UI::ImGuiBackendBridge m_imguiBackend;
UI::ImGuiSession m_imguiSession;
uint64_t m_exitRequestedHandlerId = 0;

View File

@@ -0,0 +1,33 @@
#pragma once
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include <filesystem>
#include <string>
namespace XCEngine {
namespace Editor {
inline std::string BuildEditorWindowTitle(IEditorContext& context) {
auto& sceneManager = context.GetSceneManager();
std::string sceneName = sceneManager.HasActiveScene() ? sceneManager.GetCurrentSceneName() : "No Scene";
if (sceneName.empty()) {
sceneName = "Untitled Scene";
}
if (sceneManager.IsSceneDirty()) {
sceneName += " *";
}
if (sceneManager.GetCurrentScenePath().empty()) {
sceneName += " (Unsaved)";
} else {
sceneName += " - ";
sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
}
return sceneName + " - XCEngine Editor";
}
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,82 @@
#pragma once
#include "Commands/SceneCommands.h"
#include "Core/IEditorContext.h"
#include "Layout/DockLayoutController.h"
#include "panels/ConsolePanel.h"
#include "panels/GameViewPanel.h"
#include "panels/HierarchyPanel.h"
#include "panels/InspectorPanel.h"
#include "panels/MenuBar.h"
#include "panels/PanelCollection.h"
#include "panels/ProjectPanel.h"
#include "panels/SceneViewPanel.h"
#include <filesystem>
#include <memory>
#include <string>
namespace XCEngine {
namespace Editor {
class EditorWorkspace {
public:
void Attach(IEditorContext& context) {
m_panels.Clear();
m_panels.SetContext(&context);
m_panels.Emplace<MenuBar>();
m_panels.Emplace<HierarchyPanel>();
m_panels.Emplace<SceneViewPanel>();
m_panels.Emplace<GameViewPanel>();
m_panels.Emplace<InspectorPanel>();
m_panels.Emplace<ConsolePanel>();
m_projectPanel = &m_panels.Emplace<ProjectPanel>();
m_dockLayoutController = std::make_unique<DockLayoutController>();
m_projectPanel->Initialize(context.GetProjectPath());
Commands::LoadStartupScene(context);
m_dockLayoutController->Attach(context);
m_panels.AttachAll();
}
void Detach(IEditorContext& context) {
Commands::SaveDirtySceneWithFallback(context, BuildFallbackScenePath(context));
if (m_dockLayoutController) {
m_dockLayoutController->Detach();
m_dockLayoutController.reset();
}
m_panels.DetachAll();
m_panels.Clear();
m_projectPanel = nullptr;
}
void Update(float dt) {
m_panels.UpdateAll(dt);
}
void DispatchEvent(void* event) {
m_panels.DispatchEvent(event);
}
void Render() {
if (m_dockLayoutController) {
m_dockLayoutController->RenderDockspace();
}
m_panels.RenderAll();
}
private:
static std::string BuildFallbackScenePath(const IEditorContext& context) {
return (std::filesystem::path(context.GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
}
PanelCollection m_panels;
ProjectPanel* m_projectPanel = nullptr;
std::unique_ptr<DockLayoutController> m_dockLayoutController;
};
} // namespace Editor
} // namespace XCEngine

View File

@@ -1,30 +1,9 @@
#include "Commands/SceneCommands.h"
#include "EditorLayer.h"
#include "Layout/DockLayoutController.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 "Core/IEditorContext.h"
#include "Core/EditorContext.h"
#include "Core/IUndoManager.h"
#include <filesystem>
#include <imgui.h>
namespace XCEngine {
namespace Editor {
namespace {
std::string BuildFallbackScenePath(const IEditorContext& context) {
return (std::filesystem::path(context.GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
}
} // namespace
EditorLayer::EditorLayer() : Layer("Editor") {}
void EditorLayer::SetContext(std::shared_ptr<IEditorContext> context) {
@@ -36,48 +15,25 @@ void EditorLayer::onAttach() {
m_context = std::make_shared<EditorContext>();
}
m_panels.Clear();
m_panels.SetContext(m_context.get());
m_panels.Emplace<MenuBar>();
m_panels.Emplace<HierarchyPanel>();
m_panels.Emplace<SceneViewPanel>();
m_panels.Emplace<GameViewPanel>();
m_panels.Emplace<InspectorPanel>();
m_panels.Emplace<ConsolePanel>();
m_projectPanel = &m_panels.Emplace<ProjectPanel>();
m_dockLayoutController = std::make_unique<DockLayoutController>();
m_projectPanel->Initialize(m_context->GetProjectPath());
Commands::LoadStartupScene(*m_context);
m_dockLayoutController->Attach(*m_context);
m_panels.AttachAll();
m_workspace.Attach(*m_context);
}
void EditorLayer::onDetach() {
if (m_context) {
Commands::SaveDirtySceneWithFallback(*m_context, BuildFallbackScenePath(*m_context));
m_workspace.Detach(*m_context);
}
if (m_dockLayoutController) {
m_dockLayoutController->Detach();
}
m_panels.DetachAll();
m_panels.Clear();
m_projectPanel = nullptr;
}
void EditorLayer::onUpdate(float dt) {
m_panels.UpdateAll(dt);
m_workspace.Update(dt);
}
void EditorLayer::onEvent(void* event) {
m_panels.DispatchEvent(event);
m_workspace.DispatchEvent(event);
}
void EditorLayer::onImGuiRender() {
m_dockLayoutController->RenderDockspace();
m_panels.RenderAll();
m_workspace.Render();
}
}

View File

@@ -1,24 +1,15 @@
#pragma once
#include "panels/PanelCollection.h"
#include "Core/EditorWorkspace.h"
#include <XCEngine/Core/Layer.h>
#include <XCEngine/Core/LayerStack.h>
#include <memory>
#include <string>
namespace XCEngine {
namespace Editor {
class IEditorContext;
class MenuBar;
class HierarchyPanel;
class SceneViewPanel;
class GameViewPanel;
class InspectorPanel;
class ConsolePanel;
class ProjectPanel;
class DockLayoutController;
class EditorLayer : public Core::Layer {
public:
@@ -35,9 +26,7 @@ public:
private:
std::shared_ptr<IEditorContext> m_context;
std::unique_ptr<DockLayoutController> m_dockLayoutController;
PanelCollection m_panels;
ProjectPanel* m_projectPanel = nullptr;
EditorWorkspace m_workspace;
};
}

View File

@@ -0,0 +1,218 @@
#pragma once
#include "UI/ImGuiBackendBridge.h"
#include <d3d12.h>
#include <dxgi1_6.h>
#include <windows.h>
namespace XCEngine {
namespace Editor {
namespace Platform {
class D3D12WindowRenderer {
public:
bool Initialize(HWND hwnd, int width, int height) {
m_hwnd = hwnd;
m_width = width;
m_height = height;
return CreateDevice() && CreateRenderTarget();
}
void Shutdown() {
CleanupRenderTarget();
if (m_fence) m_fence->Release();
if (m_commandList) m_commandList->Release();
if (m_commandAllocator) m_commandAllocator->Release();
if (m_commandQueue) m_commandQueue->Release();
if (m_rtvHeap) m_rtvHeap->Release();
if (m_srvHeap) m_srvHeap->Release();
if (m_swapChain) m_swapChain->Release();
if (m_device) m_device->Release();
m_hwnd = nullptr;
m_width = 1280;
m_height = 720;
m_fenceValue = 0;
m_rtvDescriptorSize = 0;
m_frameIndex = 0;
}
void Resize(int width, int height) {
if (width <= 0 || height <= 0) {
return;
}
m_width = width;
m_height = height;
CleanupRenderTarget();
if (m_swapChain) {
DXGI_SWAP_CHAIN_DESC desc;
m_swapChain->GetDesc(&desc);
m_swapChain->ResizeBuffers(3, width, height, desc.BufferDesc.Format, desc.Flags);
}
CreateRenderTarget();
}
void Render(UI::ImGuiBackendBridge& imguiBackend, const float clearColor[4]) {
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator, nullptr);
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = m_renderTargets[m_frameIndex];
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
m_commandList->ResourceBarrier(1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
m_commandList->SetDescriptorHeaps(1, heaps);
imguiBackend.RenderDrawData(m_commandList);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
m_commandList->ResourceBarrier(1, &barrier);
m_commandList->Close();
ID3D12CommandList* commandLists[] = { m_commandList };
m_commandQueue->ExecuteCommandLists(1, commandLists);
m_swapChain->Present(1, 0);
++m_fenceValue;
m_commandQueue->Signal(m_fence, m_fenceValue);
if (m_fence->GetCompletedValue() < m_fenceValue) {
m_fence->SetEventOnCompletion(m_fenceValue, nullptr);
}
}
ID3D12Device* GetDevice() const {
return m_device;
}
ID3D12DescriptorHeap* GetSrvHeap() const {
return m_srvHeap;
}
private:
bool CreateDevice() {
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
if (FAILED(hr)) {
return false;
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Priority = 0;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.NodeMask = 0;
hr = m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
if (FAILED(hr)) return false;
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator, nullptr, IID_PPV_ARGS(&m_commandList));
if (FAILED(hr)) return false;
m_commandList->Close();
hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
if (FAILED(hr)) return false;
IDXGIFactory4* factory = nullptr;
hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr)) return false;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = 3;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
IDXGISwapChain1* swapChain1 = nullptr;
hr = factory->CreateSwapChainForHwnd(m_commandQueue, m_hwnd, &swapChainDesc, nullptr, nullptr, &swapChain1);
factory->Release();
if (FAILED(hr)) return false;
hr = swapChain1->QueryInterface(IID_PPV_ARGS(&m_swapChain));
swapChain1->Release();
if (FAILED(hr)) return false;
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = 3;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = m_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&m_rtvHeap));
if (FAILED(hr)) return false;
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvDesc.NumDescriptors = 1;
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
if (FAILED(hr)) return false;
return true;
}
bool CreateRenderTarget() {
if (!m_swapChain || !m_device || !m_rtvHeap) return false;
for (UINT i = 0; i < 3; ++i) {
HRESULT hr = m_swapChain->GetBuffer(i, IID_PPV_ARGS(&m_renderTargets[i]));
if (FAILED(hr)) return false;
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += i * m_rtvDescriptorSize;
m_device->CreateRenderTargetView(m_renderTargets[i], nullptr, rtvHandle);
}
return true;
}
void CleanupRenderTarget() {
for (UINT i = 0; i < 3; ++i) {
if (m_renderTargets[i]) {
m_renderTargets[i]->Release();
m_renderTargets[i] = nullptr;
}
}
}
HWND m_hwnd = nullptr;
int m_width = 1280;
int m_height = 720;
ID3D12Device* m_device = nullptr;
ID3D12CommandQueue* m_commandQueue = nullptr;
ID3D12CommandAllocator* m_commandAllocator = nullptr;
ID3D12GraphicsCommandList* m_commandList = nullptr;
IDXGISwapChain3* m_swapChain = nullptr;
ID3D12DescriptorHeap* m_rtvHeap = nullptr;
ID3D12DescriptorHeap* m_srvHeap = nullptr;
ID3D12Resource* m_renderTargets[3] = {};
ID3D12Fence* m_fence = nullptr;
UINT64 m_fenceValue = 0;
UINT m_rtvDescriptorSize = 0;
UINT m_frameIndex = 0;
};
} // namespace Platform
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,94 @@
#pragma once
#include "Application.h"
#include "UI/ImGuiBackendBridge.h"
#include <windows.h>
namespace XCEngine {
namespace Editor {
namespace Platform {
inline LRESULT WINAPI EditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (UI::ImGuiBackendBridge::HandleWindowMessage(hWnd, msg, wParam, lParam)) {
return true;
}
switch (msg) {
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
Application::Get().OnResize(static_cast<int>(LOWORD(lParam)), static_cast<int>(HIWORD(lParam)));
}
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU) {
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
inline int RunEditor(HINSTANCE hInstance, int nCmdShow) {
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = EditorWndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"XCVolumeRendererUI2";
if (!RegisterClassExW(&wc)) {
return 1;
}
HWND hwnd = CreateWindowExW(
0,
wc.lpszClassName,
L"XCEngine Editor",
WS_OVERLAPPEDWINDOW,
100,
100,
1280,
720,
nullptr,
nullptr,
hInstance,
nullptr);
if (!hwnd) {
UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
if (!Application::Get().Initialize(hwnd)) {
DestroyWindow(hwnd);
UnregisterClassW(wc.lpszClassName, wc.hInstance);
return 1;
}
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
continue;
}
Application::Get().Render();
}
Application::Get().Shutdown();
UnregisterClassW(wc.lpszClassName, wc.hInstance);
return static_cast<int>(msg.wParam);
}
} // namespace Platform
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,55 @@
#pragma once
#include <string>
#include <windows.h>
namespace XCEngine {
namespace Editor {
namespace Platform {
inline std::string WideToUtf8(const std::wstring& value) {
if (value.empty()) {
return {};
}
const int len = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len <= 0) {
return {};
}
std::string result(len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, &result[0], len, nullptr, nullptr);
return result;
}
inline std::wstring Utf8ToWide(const std::string& value) {
if (value.empty()) {
return {};
}
const int len = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
if (len <= 0) {
return {};
}
std::wstring result(len - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, &result[0], len);
return result;
}
inline std::string GetExecutableDirectoryUtf8() {
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(nullptr, exePath, MAX_PATH);
std::wstring exeDir(exePath);
const size_t pos = exeDir.find_last_of(L"\\/");
if (pos != std::wstring::npos) {
exeDir = exeDir.substr(0, pos);
}
return WideToUtf8(exeDir);
}
} // namespace Platform
} // namespace Editor
} // namespace XCEngine

View File

@@ -3,6 +3,7 @@
#include <imgui.h>
#include <cstring>
#include <utility>
namespace XCEngine {
namespace Editor {
@@ -35,6 +36,47 @@ private:
bool m_openRequested = false;
};
template <typename Target>
class TargetedPopupState {
public:
void RequestOpen(Target target) {
m_target = std::move(target);
m_hasTarget = true;
m_popup.RequestOpen();
}
void ConsumeOpenRequest(const char* popupId) {
m_popup.ConsumeOpenRequest(popupId);
}
bool HasPendingOpenRequest() const {
return m_popup.HasPendingOpenRequest();
}
bool HasTarget() const {
return m_hasTarget;
}
Target& TargetValue() {
return m_target;
}
const Target& TargetValue() const {
return m_target;
}
void Clear() {
m_popup.Clear();
m_target = Target{};
m_hasTarget = false;
}
private:
DeferredPopupState m_popup;
Target m_target{};
bool m_hasTarget = false;
};
template <size_t BufferCapacity>
class TextInputPopupState {
public:

View File

@@ -1,95 +1,5 @@
#include "Application.h"
#include "UI/ImGuiBackendBridge.h"
#include <imgui.h>
#include <windows.h>
#include <stdio.h>
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
#include "Platform/Win32EditorHost.h"
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
AllocConsole();
freopen("CONOUT$", "w", stdout);
printf("Starting UI application...\n");
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"XCVolumeRendererUI2";
if (!RegisterClassExW(&wc)) {
printf("Failed to register window class, error: %lu\n", GetLastError());
return 1;
}
printf("Window class registered.\n");
HWND hwnd = CreateWindowExW(
0, wc.lpszClassName, L"XCVolumeRenderer - Unity Style Editor",
WS_OVERLAPPEDWINDOW, 100, 100, 1280, 720,
nullptr, nullptr, wc.hInstance, nullptr
);
if (!hwnd) {
printf("Failed to create window, error: %lu\n", GetLastError());
return 1;
}
printf("Window created.\n");
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
printf("Window shown.\n");
printf("Initializing application...\n");
if (!XCEngine::Editor::Application::Get().Initialize(hwnd)) {
printf("Failed to initialize application!\n");
UnregisterClassW(wc.lpszClassName, wc.hInstance);
system("pause");
return 1;
}
printf("Application initialized successfully.\n");
MSG msg = {};
int frameCount = 0;
while (msg.message != WM_QUIT) {
if (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
} else {
XCEngine::Editor::Application::Get().Render();
frameCount++;
if (frameCount % 100 == 0) {
printf("Frame %d\n", frameCount);
}
}
}
printf("Shutting down...\n");
XCEngine::Editor::Application::Get().Shutdown();
UnregisterClassW(wc.lpszClassName, wc.hInstance);
printf("Press any key to exit...\n");
system("pause");
return 0;
}
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (XCEngine::Editor::UI::ImGuiBackendBridge::HandleWindowMessage(hWnd, msg, wParam, lParam))
return true;
switch (msg) {
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
XCEngine::Editor::Application::Get().OnResize((int)LOWORD(lParam), (int)HIWORD(lParam));
}
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU)
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
return XCEngine::Editor::Platform::RunEditor(hInstance, nCmdShow);
}

View File

@@ -1,16 +1,14 @@
#include "Actions/EditorActions.h"
#include "Actions/HierarchyActionRouter.h"
#include "Actions/ActionRouting.h"
#include "Commands/EntityCommands.h"
#include "HierarchyPanel.h"
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include "Core/ISelectionManager.h"
#include "Core/IUndoManager.h"
#include "Core/EditorEvents.h"
#include "Core/EventBus.h"
#include "UI/UI.h"
#include <imgui.h>
#include <cstring>
namespace XCEngine {
namespace Editor {
@@ -85,7 +83,7 @@ void HierarchyPanel::Render() {
}
if (UI::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
RenderCreateMenu(nullptr);
Actions::DrawHierarchyCreateActions(*m_context, nullptr);
UI::EndPopup();
}
@@ -199,7 +197,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
HandleDragDrop(gameObject);
if (UI::BeginPopupContextItem("EntityContextMenu")) {
RenderContextMenu(gameObject);
Actions::DrawHierarchyContextActions(*m_context, gameObject);
UI::EndPopup();
}
@@ -214,60 +212,6 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
ImGui::PopID();
}
void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) {
if (UI::DrawMenuScope("Create", [&]() {
RenderCreateMenu(gameObject);
})) {
}
Actions::DrawMenuAction(Actions::MakeCreateChildEntityAction(gameObject), [&]() {
Commands::CreateEmptyEntity(*m_context, gameObject, "Create Child", "GameObject");
});
Actions::DrawMenuSeparator();
Actions::DrawMenuAction(Actions::MakeDetachEntityAction(gameObject), [&]() {
Commands::DetachEntity(*m_context, gameObject);
});
Actions::DrawMenuAction(Actions::MakeRenameEntityAction(gameObject), [&]() {
BeginRename(gameObject);
});
Actions::DrawMenuAction(Actions::MakeDeleteEntityAction(gameObject), [&]() {
Commands::DeleteEntity(*m_context, gameObject->GetID());
});
Actions::DrawMenuSeparator();
Actions::DrawMenuAction(Actions::MakeCopyEntityAction(gameObject), [&]() {
Commands::CopyEntity(*m_context, gameObject->GetID());
});
Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() {
Commands::PasteEntity(*m_context, gameObject->GetID());
});
Actions::DrawMenuAction(Actions::MakeDuplicateEntityAction(gameObject), [&]() {
Commands::DuplicateEntity(*m_context, gameObject->GetID());
});
}
void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) {
Actions::DrawMenuAction(Actions::MakeCreateEmptyEntityAction(), [&]() {
Commands::CreateEmptyEntity(*m_context, parent, "Create Entity", "GameObject");
});
Actions::DrawMenuSeparator();
Actions::DrawMenuAction(Actions::MakeCreateCameraEntityAction(), [&]() {
Commands::CreateCameraEntity(*m_context, parent);
});
Actions::DrawMenuAction(Actions::MakeCreateLightEntityAction(), [&]() {
Commands::CreateLightEntity(*m_context, parent);
});
Actions::DrawMenuSeparator();
Actions::DrawMenuAction(Actions::MakeCreateCubeEntityAction(), [&]() {
Commands::CreateEmptyEntity(*m_context, parent, "Create Cube", "Cube");
});
Actions::DrawMenuAction(Actions::MakeCreateSphereEntityAction(), [&]() {
Commands::CreateEmptyEntity(*m_context, parent, "Create Sphere", "Sphere");
});
Actions::DrawMenuAction(Actions::MakeCreatePlaneEntityAction(), [&]() {
Commands::CreateEmptyEntity(*m_context, parent, "Create Plane", "Plane");
});
}
void HierarchyPanel::BeginRename(::XCEngine::Components::GameObject* gameObject) {
if (!gameObject) {
CancelRename();

View File

@@ -23,8 +23,6 @@ private:
void OnRenameRequested(const struct EntityRenameRequestedEvent& event);
void RenderSearchBar();
void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
void RenderContextMenu(::XCEngine::Components::GameObject* gameObject);
void RenderCreateMenu(::XCEngine::Components::GameObject* parent);
void BeginRename(::XCEngine::Components::GameObject* gameObject);
void CommitRename();
void CancelRename();

View File

@@ -1,5 +1,5 @@
#include "Actions/ActionRouting.h"
#include "Actions/EditorActions.h"
#include "Actions/ProjectActionRouter.h"
#include "Commands/ProjectCommands.h"
#include "ProjectPanel.h"
#include "Core/IEditorContext.h"
@@ -7,12 +7,13 @@
#include "Core/AssetItem.h"
#include "UI/UI.h"
#include <imgui.h>
#include <imgui_internal.h>
namespace XCEngine {
namespace Editor {
const char* DRAG_DROP_TYPE = "ASSET_ITEM";
namespace {
constexpr const char* kAssetDragDropType = "ASSET_ITEM";
}
ProjectPanel::ProjectPanel() : Panel("Project") {
}
@@ -23,7 +24,7 @@ void ProjectPanel::Initialize(const std::string& projectPath) {
void ProjectPanel::Render() {
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
if (payload && payload->IsDataType(DRAG_DROP_TYPE)) {
if (payload && payload->IsDataType(kAssetDragDropType)) {
m_draggingPath = (const char*)payload->Data;
} else if (!ImGui::IsMouseDown(0)) {
m_draggingPath.clear();
@@ -101,52 +102,28 @@ void ProjectPanel::Render() {
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
manager.SetSelectedIndex(-1);
}
m_itemContextMenu.ConsumeOpenRequest("ItemContextMenu");
if (UI::BeginPopup("ItemContextMenu")) {
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
auto& item = items[m_contextMenuIndex];
const bool canOpen = item->isFolder || item->type == "Scene";
Actions::DrawMenuAction(Actions::MakeOpenAssetAction(canOpen), [&]() {
Commands::OpenAsset(*m_context, item);
});
Actions::DrawMenuSeparator();
Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(), [&]() {
Commands::DeleteAsset(manager, m_contextMenuIndex);
m_contextMenuIndex = -1;
});
if (m_itemContextMenu.HasTarget()) {
Actions::DrawProjectAssetContextActions(*m_context, m_itemContextMenu.TargetValue());
}
UI::EndPopup();
}
if (!ImGui::IsPopupOpen("ItemContextMenu") && !m_itemContextMenu.HasPendingOpenRequest()) {
m_itemContextMenu.Clear();
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) {
ImGui::OpenPopup("EmptyContextMenu");
}
if (UI::BeginPopup("EmptyContextMenu")) {
Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() {
m_createFolderDialog.RequestOpen("NewFolder");
});
UI::EndPopup();
}
m_createFolderDialog.ConsumeOpenRequest("Create Folder");
if (UI::BeginModalPopup("Create Folder")) {
ImGui::InputText("Name", m_createFolderDialog.Buffer(), m_createFolderDialog.BufferSize());
ImGui::Separator();
const Actions::ActionBinding createAction = Actions::MakeConfirmCreateAction(!m_createFolderDialog.Empty());
const Actions::ActionBinding cancelAction = Actions::MakeCancelAction();
if (Actions::DrawButtonAction(createAction, UI::DialogActionButtonSize())) {
if (Commands::CreateFolder(manager, m_createFolderDialog.Buffer())) {
ImGui::CloseCurrentPopup();
}
}
ImGui::SameLine();
if (Actions::DrawButtonAction(cancelAction, UI::DialogActionButtonSize())) {
ImGui::CloseCurrentPopup();
}
Actions::DrawProjectEmptyContextActions(m_createFolderDialog);
UI::EndPopup();
}
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
}
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
@@ -169,16 +146,14 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
manager.SetSelectedIndex(index);
}
bool openContextMenu = false;
if (tile.contextRequested) {
manager.SetSelectedIndex(index);
m_contextMenuIndex = index;
openContextMenu = true;
m_itemContextMenu.RequestOpen(item);
}
if (item->isFolder) {
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(kAssetDragDropType)) {
const char* draggedPath = (const char*)payload->Data;
Commands::MoveAssetToFolder(manager, draggedPath, item);
}
@@ -188,7 +163,7 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
if (!item->fullPath.empty()) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1);
ImGui::SetDragDropPayload(kAssetDragDropType, item->fullPath.c_str(), item->fullPath.length() + 1);
ImVec2 previewMin = ImGui::GetMousePos();
const ImVec2 previewSize = UI::AssetDragPreviewSize();
@@ -204,10 +179,6 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
}
ImGui::PopID();
if (openContextMenu) {
ImGui::OpenPopup("ItemContextMenu");
}
}
}

View File

@@ -18,7 +18,7 @@ private:
char m_searchBuffer[256] = "";
UI::TextInputPopupState<256> m_createFolderDialog;
int m_contextMenuIndex = -1;
UI::TargetedPopupState<AssetItemPtr> m_itemContextMenu;
std::string m_draggingPath;
};