From 31675e00c86c28833a8e3938f70735345ab12421 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 26 Mar 2026 23:52:05 +0800 Subject: [PATCH] Refactor editor shell host layers --- docs/plan/Editor重构3.26.md | 6 + editor/src/Actions/HierarchyActionRouter.h | 77 +++++++ editor/src/Actions/ProjectActionRouter.h | 83 +++++++ editor/src/Application.cpp | 247 ++------------------- editor/src/Application.h | 25 +-- editor/src/Core/EditorWindowTitle.h | 33 +++ editor/src/Core/EditorWorkspace.h | 82 +++++++ editor/src/Layers/EditorLayer.cpp | 54 +---- editor/src/Layers/EditorLayer.h | 15 +- editor/src/Platform/D3D12WindowRenderer.h | 218 ++++++++++++++++++ editor/src/Platform/Win32EditorHost.h | 94 ++++++++ editor/src/Platform/Win32Utf8.h | 55 +++++ editor/src/UI/PopupState.h | 42 ++++ editor/src/main.cpp | 94 +------- editor/src/panels/HierarchyPanel.cpp | 62 +----- editor/src/panels/HierarchyPanel.h | 2 - editor/src/panels/ProjectPanel.cpp | 65 ++---- editor/src/panels/ProjectPanel.h | 2 +- 18 files changed, 738 insertions(+), 518 deletions(-) create mode 100644 editor/src/Actions/HierarchyActionRouter.h create mode 100644 editor/src/Actions/ProjectActionRouter.h create mode 100644 editor/src/Core/EditorWindowTitle.h create mode 100644 editor/src/Core/EditorWorkspace.h create mode 100644 editor/src/Platform/D3D12WindowRenderer.h create mode 100644 editor/src/Platform/Win32EditorHost.h create mode 100644 editor/src/Platform/Win32Utf8.h diff --git a/docs/plan/Editor重构3.26.md b/docs/plan/Editor重构3.26.md index c90fd53c..fc0e81bf 100644 --- a/docs/plan/Editor重构3.26.md +++ b/docs/plan/Editor重构3.26.md @@ -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` ## 主要面板状态 diff --git a/editor/src/Actions/HierarchyActionRouter.h b/editor/src/Actions/HierarchyActionRouter.h new file mode 100644 index 00000000..131a97be --- /dev/null +++ b/editor/src/Actions/HierarchyActionRouter.h @@ -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 diff --git a/editor/src/Actions/ProjectActionRouter.h b/editor/src/Actions/ProjectActionRouter.h new file mode 100644 index 00000000..af3ee0b7 --- /dev/null +++ b/editor/src/Actions/ProjectActionRouter.h @@ -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(i); + } + + if (items[i] && items[i]->fullPath == item->fullPath) { + return static_cast(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 +inline void DrawProjectEmptyContextActions(UI::TextInputPopupState& createFolderDialog) { + DrawMenuAction(MakeCreateFolderAction(), [&]() { + createFolderDialog.RequestOpen("NewFolder"); + }); +} + +template +inline void DrawProjectCreateFolderDialog(IEditorContext& context, UI::TextInputPopupState& 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 diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index aa3e98c7..565dfa5e 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -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 #include #include -#include -#include #include #include -#include 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::Logger::Get().AddSink(std::make_unique()); - - // 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(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(); 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); } } diff --git a/editor/src/Application.h b/editor/src/Application.h index 1369721d..2674562f 100644 --- a/editor/src/Application.h +++ b/editor/src/Application.h @@ -1,13 +1,12 @@ #pragma once +#include "Platform/D3D12WindowRenderer.h" #include "UI/ImGuiBackendBridge.h" #include "UI/ImGuiSession.h" #include #include -#include -#include -#include +#include #include @@ -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 m_editorContext; + Platform::D3D12WindowRenderer m_windowRenderer; UI::ImGuiBackendBridge m_imguiBackend; UI::ImGuiSession m_imguiSession; uint64_t m_exitRequestedHandlerId = 0; diff --git a/editor/src/Core/EditorWindowTitle.h b/editor/src/Core/EditorWindowTitle.h new file mode 100644 index 00000000..96c7c2ca --- /dev/null +++ b/editor/src/Core/EditorWindowTitle.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Core/IEditorContext.h" +#include "Core/ISceneManager.h" + +#include +#include + +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 diff --git a/editor/src/Core/EditorWorkspace.h b/editor/src/Core/EditorWorkspace.h new file mode 100644 index 00000000..f6ffe582 --- /dev/null +++ b/editor/src/Core/EditorWorkspace.h @@ -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 +#include +#include + +namespace XCEngine { +namespace Editor { + +class EditorWorkspace { +public: + void Attach(IEditorContext& context) { + m_panels.Clear(); + m_panels.SetContext(&context); + m_panels.Emplace(); + m_panels.Emplace(); + m_panels.Emplace(); + m_panels.Emplace(); + m_panels.Emplace(); + m_panels.Emplace(); + m_projectPanel = &m_panels.Emplace(); + m_dockLayoutController = std::make_unique(); + + 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 m_dockLayoutController; +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Layers/EditorLayer.cpp b/editor/src/Layers/EditorLayer.cpp index 24dd691a..fd87563a 100644 --- a/editor/src/Layers/EditorLayer.cpp +++ b/editor/src/Layers/EditorLayer.cpp @@ -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 -#include 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 context) { @@ -36,48 +15,25 @@ void EditorLayer::onAttach() { m_context = std::make_shared(); } - m_panels.Clear(); - m_panels.SetContext(m_context.get()); - m_panels.Emplace(); - m_panels.Emplace(); - m_panels.Emplace(); - m_panels.Emplace(); - m_panels.Emplace(); - m_panels.Emplace(); - m_projectPanel = &m_panels.Emplace(); - m_dockLayoutController = std::make_unique(); - - 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(); } } diff --git a/editor/src/Layers/EditorLayer.h b/editor/src/Layers/EditorLayer.h index 9257e27d..89a21376 100644 --- a/editor/src/Layers/EditorLayer.h +++ b/editor/src/Layers/EditorLayer.h @@ -1,24 +1,15 @@ #pragma once -#include "panels/PanelCollection.h" +#include "Core/EditorWorkspace.h" #include #include #include -#include 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 m_context; - std::unique_ptr m_dockLayoutController; - PanelCollection m_panels; - ProjectPanel* m_projectPanel = nullptr; + EditorWorkspace m_workspace; }; } diff --git a/editor/src/Platform/D3D12WindowRenderer.h b/editor/src/Platform/D3D12WindowRenderer.h new file mode 100644 index 00000000..5ea3813c --- /dev/null +++ b/editor/src/Platform/D3D12WindowRenderer.h @@ -0,0 +1,218 @@ +#pragma once + +#include "UI/ImGuiBackendBridge.h" + +#include +#include +#include + +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 diff --git a/editor/src/Platform/Win32EditorHost.h b/editor/src/Platform/Win32EditorHost.h new file mode 100644 index 00000000..16fa44d8 --- /dev/null +++ b/editor/src/Platform/Win32EditorHost.h @@ -0,0 +1,94 @@ +#pragma once + +#include "Application.h" +#include "UI/ImGuiBackendBridge.h" + +#include + +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(LOWORD(lParam)), static_cast(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(msg.wParam); +} + +} // namespace Platform +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Platform/Win32Utf8.h b/editor/src/Platform/Win32Utf8.h new file mode 100644 index 00000000..92fc70a2 --- /dev/null +++ b/editor/src/Platform/Win32Utf8.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +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 diff --git a/editor/src/UI/PopupState.h b/editor/src/UI/PopupState.h index 5cce8c12..c477e3a3 100644 --- a/editor/src/UI/PopupState.h +++ b/editor/src/UI/PopupState.h @@ -3,6 +3,7 @@ #include #include +#include namespace XCEngine { namespace Editor { @@ -35,6 +36,47 @@ private: bool m_openRequested = false; }; +template +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 class TextInputPopupState { public: diff --git a/editor/src/main.cpp b/editor/src/main.cpp index a67de45b..5fc1b2cd 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -1,95 +1,5 @@ -#include "Application.h" -#include "UI/ImGuiBackendBridge.h" -#include -#include -#include - -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); } diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 10f0d8cd..a49e89a1 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -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 -#include 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(); diff --git a/editor/src/panels/HierarchyPanel.h b/editor/src/panels/HierarchyPanel.h index 5085e6e8..5d29a5ed 100644 --- a/editor/src/panels/HierarchyPanel.h +++ b/editor/src/panels/HierarchyPanel.h @@ -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(); diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp index 0ce4b1c6..cdee4e1d 100644 --- a/editor/src/panels/ProjectPanel.cpp +++ b/editor/src/panels/ProjectPanel.cpp @@ -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 -#include 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"); - } } } diff --git a/editor/src/panels/ProjectPanel.h b/editor/src/panels/ProjectPanel.h index 3e55b81b..6954bcb7 100644 --- a/editor/src/panels/ProjectPanel.h +++ b/editor/src/panels/ProjectPanel.h @@ -18,7 +18,7 @@ private: char m_searchBuffer[256] = ""; UI::TextInputPopupState<256> m_createFolderDialog; - int m_contextMenuIndex = -1; + UI::TargetedPopupState m_itemContextMenu; std::string m_draggingPath; };