From 5735e769b0d212ba286680b0418cc86eb82956cd Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 26 Mar 2026 22:10:43 +0800 Subject: [PATCH] Route editor actions by active target --- docs/plan/Editor重构3.26.md | 35 +++++- editor/src/Actions/ActionBinding.h | 52 ++++++++- editor/src/Actions/ActionRouting.h | 24 ++++ editor/src/Actions/EditorActions.h | 19 ++- editor/src/Application.cpp | 66 +---------- editor/src/Application.h | 11 +- editor/src/Core/EditorActionRoute.h | 13 +++ editor/src/Core/EditorContext.h | 9 ++ editor/src/Core/EditorEvents.h | 4 + editor/src/Core/IEditorContext.h | 4 + editor/src/UI/ImGuiBackendBridge.h | 65 +++++++++++ editor/src/UI/ImGuiSession.h | 77 ++++++++++++ editor/src/main.cpp | 5 +- editor/src/panels/HierarchyPanel.cpp | 55 ++++++--- editor/src/panels/HierarchyPanel.h | 2 + editor/src/panels/MenuBar.cpp | 44 ++++++- editor/src/panels/ProjectPanel.cpp | 28 +++++ editor/src/panels/ProjectPanel.h | 2 + managed/CMakeLists.txt | 116 +++++++++++++++++++ managed/GameScripts/LifecycleProbe.cs | 74 ++++++++++++ managed/XCEngine.ScriptCore/MonoBehaviour.cs | 8 ++ 21 files changed, 609 insertions(+), 104 deletions(-) create mode 100644 editor/src/Actions/ActionRouting.h create mode 100644 editor/src/Core/EditorActionRoute.h create mode 100644 editor/src/UI/ImGuiBackendBridge.h create mode 100644 editor/src/UI/ImGuiSession.h create mode 100644 managed/CMakeLists.txt create mode 100644 managed/GameScripts/LifecycleProbe.cs create mode 100644 managed/XCEngine.ScriptCore/MonoBehaviour.cs diff --git a/docs/plan/Editor重构3.26.md b/docs/plan/Editor重构3.26.md index 00a6bada..704e5c11 100644 --- a/docs/plan/Editor重构3.26.md +++ b/docs/plan/Editor重构3.26.md @@ -2,7 +2,7 @@ ## 当前判断 -截至 2026-03-26,Editor 的 UI 架构重构如果只计算“架构层整理”,不计算 Viewport/RHI 和 Scene/Game 真正内容开发,整体进度大约在 **90%**。 +截至 2026-03-26,Editor 的 UI 架构重构如果只计算“架构层整理”,不计算 Viewport/RHI 和 Scene/Game 真正内容开发,整体进度大约在 **92%**。 如果把“编辑器整体可用度”也算进去,则大约在 **65%** 左右,因为 `Scene` / `Game` 面板目前仍然只是占位壳,Viewport 仍未回归,且部分编辑语义与回归验证还未补齐。 @@ -75,6 +75,7 @@ - shortcut 文本 - enabled 状态 - 文本输入时是否允许触发 +- shortcut 触发上下文 - menu 与 shortcut 的统一绑定 - button / toolbar / inspector action 的统一接线 @@ -93,6 +94,15 @@ 这一层已经不再只是 `MenuBar + Hierarchy` 的试点,而是开始成为 editor 范围内的统一动作定义入口。 +当前新增结果: + +- 已补 shared shortcut context +- `Global` 与 `FocusedWindow` 两种快捷键路由开始统一 +- `Hierarchy` 与 `Project` 已接入同一套 shortcut 分发 +- 已补 active action route +- `MenuBar Edit` 已开始跟随 `Hierarchy / Project` 切换动作目标 +- `Rename` 这类依赖 panel 内联状态的动作,开始通过 `EventBus` 请求而不是直接耦合 panel 实现 + ### 5. Dock / Layout 层 `Dock / Layout` 已从 `EditorLayer::setupDockspace()` 中抽出,形成独立 layout controller: @@ -121,6 +131,20 @@ - panel 的 attach / detach / render 顺序有了统一入口 - 后续继续拆 panel 或补 panel 时,不需要再改一大片壳层代码 +### 7. Application / ImGui Session 层 + +`Application` 中原先混在一起的 ImGui context 创建、字体加载、ini 路径配置、layout 持久化,已经开始抽成独立 session: + +- `editor/src/UI/ImGuiSession.h` +- `editor/src/UI/ImGuiBackendBridge.h` + +这一层的意义是: + +- `Application` 更接近窗口 / 设备 / layer host +- ImGui 生命周期不再继续堆在 `Application.cpp` +- Win32 / DX12 backend API 不再散落在 `Application.cpp` 与 `main.cpp` +- 后续继续清理 backend 初始化边界时,有稳定落点 + ## 主要面板状态 ### MenuBar @@ -134,6 +158,7 @@ - `Reset Layout` 已接入事件驱动行为 - `About` 已接入真实 modal popup - `Exit` 已通过事件驱动关闭 editor +- `Edit` 菜单已开始跟随 active action route 在 `Hierarchy / Project` 之间切换 仍待完成: @@ -148,6 +173,7 @@ - 快捷键已接 action 层 - 重命名状态已收成 `Begin / Commit / Cancel` - 重命名交互已从 panel 局部字段收口到 shared inline edit state +- `Rename` 请求已能从 `MenuBar -> EventBus -> Hierarchy inline edit` 触发 仍待完成: @@ -162,6 +188,7 @@ - 上下文菜单、返回按钮、创建文件夹对话框已接 action 层 - 资源图标绘制与图标配色已下沉到 shared UI token / widget - 创建文件夹弹窗已改成 shared popup state 驱动 +- `Back / Open / Delete` 已接 panel-focused keyboard action 仍待完成: @@ -209,13 +236,12 @@ ### 高优先级 1. 整理 `Application.cpp` 和 `EditorLayer.cpp` 中剩余的 UI 壳逻辑 - 包括字体初始化、主 dock host 初始化、部分样式 push / pop 的收口。 + 包括 backend 初始化边界、主循环中剩余 ImGui / render 壳逻辑、部分样式 push / pop 的收口。 ### 中优先级 2. 继续将 panel 的本地状态机抽离 包括: - - inspector component popup state - console filter state - 其他仍散落在 panel 内的临时交互状态 @@ -224,9 +250,10 @@ 4. 统一 editor 范围内的编辑语义 例如: - - 哪些 panel 消费 Copy / Paste / Delete + - 哪些 panel 消费 Copy / Paste / Delete / Open / Back - 哪些动作受 selection 驱动 - 哪些动作在文本输入时必须屏蔽 + - 哪些动作通过 command,哪些动作通过 event 请求 panel 内联状态 ### 低优先级 diff --git a/editor/src/Actions/ActionBinding.h b/editor/src/Actions/ActionBinding.h index 9765e156..d1c71408 100644 --- a/editor/src/Actions/ActionBinding.h +++ b/editor/src/Actions/ActionBinding.h @@ -17,6 +17,16 @@ struct ShortcutChord { bool alt = false; }; +enum class ShortcutRoute { + Global, + FocusedWindow +}; + +struct ShortcutContext { + ShortcutRoute route = ShortcutRoute::Global; + bool enabled = true; +}; + struct ActionBinding { std::string label; std::string shortcutLabel; @@ -31,6 +41,14 @@ inline ShortcutChord Shortcut(ImGuiKey key, bool ctrl = false, bool shift = fals return ShortcutChord{ key, ctrl, shift, alt }; } +inline ShortcutContext GlobalShortcutContext() { + return ShortcutContext{ ShortcutRoute::Global, true }; +} + +inline ShortcutContext FocusedWindowShortcutContext() { + return ShortcutContext{ ShortcutRoute::FocusedWindow, true }; +} + inline ActionBinding MakeAction( std::string label, const char* shortcutLabel = nullptr, @@ -118,11 +136,30 @@ inline bool IsShortcutPressed(const ShortcutChord& shortcut, const ImGuiIO& io) return ImGui::IsKeyPressed(shortcut.key, false); } -inline bool IsShortcutPressed(const ActionBinding& action) { +inline bool IsShortcutContextActive(const ShortcutContext& context) { + if (!context.enabled) { + return false; + } + + switch (context.route) { + case ShortcutRoute::Global: + return true; + case ShortcutRoute::FocusedWindow: + return ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + } + + return false; +} + +inline bool IsShortcutPressed(const ActionBinding& action, const ShortcutContext& context) { if (!action.enabled) { return false; } + if (!IsShortcutContextActive(context)) { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); if (!action.allowWhenTextInput && io.WantTextInput) { return false; @@ -137,9 +174,13 @@ inline bool IsShortcutPressed(const ActionBinding& action) { return false; } +inline bool IsShortcutPressed(const ActionBinding& action) { + return IsShortcutPressed(action, GlobalShortcutContext()); +} + template -inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) { - if (!IsShortcutPressed(action)) { +inline bool HandleShortcut(const ActionBinding& action, const ShortcutContext& context, ExecuteFn&& execute) { + if (!IsShortcutPressed(action, context)) { return false; } @@ -147,6 +188,11 @@ inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) { return true; } +template +inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) { + return HandleShortcut(action, GlobalShortcutContext(), std::forward(execute)); +} + } // namespace Actions } // namespace Editor } // namespace XCEngine diff --git a/editor/src/Actions/ActionRouting.h b/editor/src/Actions/ActionRouting.h new file mode 100644 index 00000000..082597df --- /dev/null +++ b/editor/src/Actions/ActionRouting.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Core/EditorActionRoute.h" +#include "Core/IEditorContext.h" + +#include + +namespace XCEngine { +namespace Editor { +namespace Actions { + +inline void ObserveFocusedActionRoute(IEditorContext& context, EditorActionRoute route) { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { + context.SetActiveActionRoute(route); + } +} + +inline bool IsActionRouteActive(const IEditorContext& context, EditorActionRoute route) { + return context.GetActiveActionRoute() == route; +} + +} // namespace Actions +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Actions/EditorActions.h b/editor/src/Actions/EditorActions.h index cda909a7..bf519622 100644 --- a/editor/src/Actions/EditorActions.h +++ b/editor/src/Actions/EditorActions.h @@ -2,9 +2,11 @@ #include "ActionBinding.h" #include "Core/IEditorContext.h" +#include "Core/IProjectManager.h" #include "Core/ISelectionManager.h" #include "Core/ISceneManager.h" #include "Core/IUndoManager.h" +#include "Core/AssetItem.h" #include @@ -128,15 +130,15 @@ inline ActionBinding MakeExitAction() { } inline ActionBinding MakeNavigateBackAction(bool enabled) { - return MakeAction("<", nullptr, false, enabled); + return MakeAction("<", "Alt+Left", false, enabled, false, Shortcut(ImGuiKey_LeftArrow, false, false, true)); } inline ActionBinding MakeOpenAssetAction(bool enabled) { - return MakeAction("Open", nullptr, false, enabled); + return MakeAction("Open", "Enter", false, enabled, false, Shortcut(ImGuiKey_Enter), Shortcut(ImGuiKey_KeypadEnter)); } inline ActionBinding MakeDeleteAssetAction(bool enabled = true) { - return MakeAction("Delete", nullptr, false, enabled); + return MakeAction("Delete", "Delete", false, enabled, false, Shortcut(ImGuiKey_Delete)); } inline ActionBinding MakeCreateFolderAction(bool enabled = true) { @@ -179,6 +181,17 @@ inline ::XCEngine::Components::GameObject* GetSelectedGameObject(IEditorContext& return context.GetSceneManager().GetEntity(context.GetSelectionManager().GetSelectedEntity()); } +inline AssetItemPtr GetSelectedAssetItem(IEditorContext& context) { + auto& projectManager = context.GetProjectManager(); + const int selectedIndex = projectManager.GetSelectedIndex(); + auto& items = projectManager.GetCurrentItems(); + if (selectedIndex < 0 || selectedIndex >= static_cast(items.size())) { + return nullptr; + } + + return items[selectedIndex]; +} + } // namespace Actions } // namespace Editor } // namespace XCEngine diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 41ef359f..aa3e98c7 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -7,16 +7,12 @@ #include #include #include -#include -#include #include #include #include #include #include -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - namespace { std::string WideToUtf8(const std::wstring& value) { @@ -66,10 +62,6 @@ std::string GetExecutableLogPath(const char* fileName) { return GetExecutableDirectoryUtf8() + "\\" + fileName; } -std::string BuildEditorConfigDirectory(const std::string& projectPath) { - return (std::filesystem::path(projectPath) / ".xceditor").string(); -} - } // namespace static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) { @@ -142,12 +134,9 @@ bool Application::Initialize(HWND hwnd) { PostMessageW(m_hwnd, WM_CLOSE, 0, 0); } }); - ConfigureImGui(m_editorContext->GetProjectPath()); + m_imguiSession.Initialize(m_editorContext->GetProjectPath()); - ImGui_ImplWin32_Init(hwnd); - ImGui_ImplDX12_Init(m_device, 3, DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap, - m_srvHeap->GetCPUDescriptorHandleForHeapStart(), - m_srvHeap->GetGPUDescriptorHandleForHeapStart()); + m_imguiBackend.Initialize(hwnd, m_device, m_srvHeap); m_editorLayer = new EditorLayer(); m_editorLayer->SetContext(m_editorContext); @@ -159,16 +148,14 @@ bool Application::Initialize(HWND hwnd) { void Application::Shutdown() { m_layerStack.onDetach(); - SaveImGuiSettings(); if (m_editorContext && m_exitRequestedHandlerId) { m_editorContext->GetEventBus().Unsubscribe(m_exitRequestedHandlerId); m_exitRequestedHandlerId = 0; } - ImGui_ImplDX12_Shutdown(); - ImGui_ImplWin32_Shutdown(); - ImGui::DestroyContext(); + m_imguiBackend.Shutdown(); + m_imguiSession.Shutdown(); CleanupRenderTarget(); @@ -183,9 +170,7 @@ void Application::Shutdown() { } void Application::Render() { - ImGui_ImplDX12_NewFrame(); - ImGui_ImplWin32_NewFrame(); - ImGui::NewFrame(); + m_imguiBackend.BeginFrame(); m_layerStack.onImGuiRender(); UpdateWindowTitle(); @@ -214,7 +199,7 @@ void Application::Render() { ID3D12DescriptorHeap* heaps[] = { m_srvHeap }; m_commandList->SetDescriptorHeaps(1, heaps); - ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_commandList); + m_imguiBackend.RenderDrawData(m_commandList); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; @@ -233,45 +218,6 @@ void Application::Render() { } } -void Application::ConfigureImGui(const std::string& projectPath) { - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ConfigureImGuiIniFile(projectPath, io); - - if (ImFont* uiFont = io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 15.0f)) { - io.FontDefault = uiFont; - } else { - io.FontDefault = io.Fonts->AddFontDefault(); - } - - unsigned char* pixels = nullptr; - int width = 0; - int height = 0; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - ApplyUnityDarkTheme(); -} - -void Application::ConfigureImGuiIniFile(const std::string& projectPath, ImGuiIO& io) { - const std::filesystem::path configDir = BuildEditorConfigDirectory(projectPath); - std::error_code ec; - std::filesystem::create_directories(configDir, ec); - - m_imguiIniPath = (configDir / "imgui_layout.ini").string(); - io.IniFilename = m_imguiIniPath.c_str(); -} - -void Application::SaveImGuiSettings() { - if (m_imguiIniPath.empty() || ImGui::GetCurrentContext() == nullptr) { - return; - } - - ImGui::SaveIniSettingsToDisk(m_imguiIniPath.c_str()); -} - void Application::UpdateWindowTitle() { if (!m_hwnd || !m_editorContext) { return; diff --git a/editor/src/Application.h b/editor/src/Application.h index 3a6626cc..1369721d 100644 --- a/editor/src/Application.h +++ b/editor/src/Application.h @@ -1,5 +1,8 @@ #pragma once +#include "UI/ImGuiBackendBridge.h" +#include "UI/ImGuiSession.h" + #include #include #include @@ -8,8 +11,6 @@ #include -#include "Theme.h" - namespace XCEngine { namespace Editor { @@ -32,9 +33,6 @@ private: Application() = default; ~Application() = default; - void ConfigureImGui(const std::string& projectPath); - void ConfigureImGuiIniFile(const std::string& projectPath, ImGuiIO& io); - void SaveImGuiSettings(); bool CreateDevice(); bool CreateRenderTarget(); void CleanupRenderTarget(); @@ -60,8 +58,9 @@ private: Core::LayerStack m_layerStack; EditorLayer* m_editorLayer = nullptr; std::shared_ptr m_editorContext; + UI::ImGuiBackendBridge m_imguiBackend; + UI::ImGuiSession m_imguiSession; uint64_t m_exitRequestedHandlerId = 0; - std::string m_imguiIniPath; std::wstring m_lastWindowTitle; }; diff --git a/editor/src/Core/EditorActionRoute.h b/editor/src/Core/EditorActionRoute.h new file mode 100644 index 00000000..28affaa8 --- /dev/null +++ b/editor/src/Core/EditorActionRoute.h @@ -0,0 +1,13 @@ +#pragma once + +namespace XCEngine { +namespace Editor { + +enum class EditorActionRoute { + None, + Hierarchy, + Project +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Core/EditorContext.h b/editor/src/Core/EditorContext.h index 55939e59..9a726ef0 100644 --- a/editor/src/Core/EditorContext.h +++ b/editor/src/Core/EditorContext.h @@ -55,6 +55,14 @@ public: IUndoManager& GetUndoManager() override { return *m_undoManager; } + + void SetActiveActionRoute(EditorActionRoute route) override { + m_activeActionRoute = route; + } + + EditorActionRoute GetActiveActionRoute() const override { + return m_activeActionRoute; + } void SetProjectPath(const std::string& path) override { m_projectPath = path; @@ -70,6 +78,7 @@ private: std::unique_ptr m_sceneManager; std::unique_ptr m_undoManager; std::unique_ptr m_projectManager; + EditorActionRoute m_activeActionRoute = EditorActionRoute::None; std::string m_projectPath; uint64_t m_entityDeletedHandlerId; }; diff --git a/editor/src/Core/EditorEvents.h b/editor/src/Core/EditorEvents.h index 6c6fd8cf..a7055f1c 100644 --- a/editor/src/Core/EditorEvents.h +++ b/editor/src/Core/EditorEvents.h @@ -25,6 +25,10 @@ struct EntityChangedEvent { GameObjectID entityId; }; +struct EntityRenameRequestedEvent { + GameObjectID entityId; +}; + struct EntityParentChangedEvent { GameObjectID entityId; GameObjectID oldParentId; diff --git a/editor/src/Core/IEditorContext.h b/editor/src/Core/IEditorContext.h index d4d4eeed..af4745cf 100644 --- a/editor/src/Core/IEditorContext.h +++ b/editor/src/Core/IEditorContext.h @@ -1,5 +1,7 @@ #pragma once +#include "EditorActionRoute.h" + #include #include @@ -21,6 +23,8 @@ public: virtual ISceneManager& GetSceneManager() = 0; virtual IProjectManager& GetProjectManager() = 0; virtual IUndoManager& GetUndoManager() = 0; + virtual void SetActiveActionRoute(EditorActionRoute route) = 0; + virtual EditorActionRoute GetActiveActionRoute() const = 0; virtual void SetProjectPath(const std::string& path) = 0; virtual const std::string& GetProjectPath() const = 0; diff --git a/editor/src/UI/ImGuiBackendBridge.h b/editor/src/UI/ImGuiBackendBridge.h new file mode 100644 index 00000000..583ac281 --- /dev/null +++ b/editor/src/UI/ImGuiBackendBridge.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +namespace XCEngine { +namespace Editor { +namespace UI { + +class ImGuiBackendBridge { +public: + void Initialize( + HWND hwnd, + ID3D12Device* device, + ID3D12DescriptorHeap* srvHeap, + int frameCount = 3, + DXGI_FORMAT backBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM) { + ImGui_ImplWin32_Init(hwnd); + ImGui_ImplDX12_Init( + device, + frameCount, + backBufferFormat, + srvHeap, + srvHeap->GetCPUDescriptorHandleForHeapStart(), + srvHeap->GetGPUDescriptorHandleForHeapStart()); + m_initialized = true; + } + + void Shutdown() { + if (!m_initialized) { + return; + } + + ImGui_ImplDX12_Shutdown(); + ImGui_ImplWin32_Shutdown(); + m_initialized = false; + } + + void BeginFrame() const { + ImGui_ImplDX12_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + } + + void RenderDrawData(ID3D12GraphicsCommandList* commandList) const { + ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), commandList); + } + + static bool HandleWindowMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam) != 0; + } + +private: + bool m_initialized = false; +}; + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/ImGuiSession.h b/editor/src/UI/ImGuiSession.h new file mode 100644 index 00000000..8bec7776 --- /dev/null +++ b/editor/src/UI/ImGuiSession.h @@ -0,0 +1,77 @@ +#pragma once + +#include "BaseTheme.h" + +#include + +#include +#include + +namespace XCEngine { +namespace Editor { +namespace UI { + +class ImGuiSession { +public: + void Initialize(const std::string& projectPath) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ConfigureIniFile(projectPath, io); + ConfigureFonts(io); + ApplyBaseTheme(ImGui::GetStyle()); + } + + void Shutdown() { + SaveSettings(); + if (ImGui::GetCurrentContext() != nullptr) { + ImGui::DestroyContext(); + } + + m_iniPath.clear(); + } + + void SaveSettings() const { + if (m_iniPath.empty() || ImGui::GetCurrentContext() == nullptr) { + return; + } + + ImGui::SaveIniSettingsToDisk(m_iniPath.c_str()); + } + + const std::string& GetIniPath() const { + return m_iniPath; + } + +private: + void ConfigureIniFile(const std::string& projectPath, ImGuiIO& io) { + const std::filesystem::path configDir = std::filesystem::path(projectPath) / ".xceditor"; + std::error_code ec; + std::filesystem::create_directories(configDir, ec); + + m_iniPath = (configDir / "imgui_layout.ini").string(); + io.IniFilename = m_iniPath.c_str(); + } + + void ConfigureFonts(ImGuiIO& io) const { + if (ImFont* uiFont = io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 15.0f)) { + io.FontDefault = uiFont; + } else { + io.FontDefault = io.Fonts->AddFontDefault(); + } + + unsigned char* pixels = nullptr; + int width = 0; + int height = 0; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + } + + std::string m_iniPath; +}; + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 00643c65..a67de45b 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -1,4 +1,5 @@ #include "Application.h" +#include "UI/ImGuiBackendBridge.h" #include #include #include @@ -72,10 +73,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { return 0; } -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) + if (XCEngine::Editor::UI::ImGuiBackendBridge::HandleWindowMessage(hWnd, msg, wParam, lParam)) return true; switch (msg) { diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 126e78c4..a7c6515e 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -1,4 +1,5 @@ #include "Actions/EditorActions.h" +#include "Actions/ActionRouting.h" #include "Commands/EntityCommands.h" #include "HierarchyPanel.h" #include "Core/IEditorContext.h" @@ -20,6 +21,7 @@ HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") { HierarchyPanel::~HierarchyPanel() { if (m_context) { m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); + m_context->GetEventBus().Unsubscribe(m_renameRequestHandlerId); } } @@ -29,6 +31,11 @@ void HierarchyPanel::OnAttach() { OnSelectionChanged(event); } ); + m_renameRequestHandlerId = m_context->GetEventBus().Subscribe( + [this](const EntityRenameRequestedEvent& event) { + OnRenameRequested(event); + } + ); } void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& event) { @@ -37,12 +44,24 @@ void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& event) { } } +void HierarchyPanel::OnRenameRequested(const EntityRenameRequestedEvent& event) { + if (!m_context || event.entityId == 0) { + return; + } + + if (auto* gameObject = m_context->GetSceneManager().GetEntity(event.entityId)) { + BeginRename(gameObject); + } +} + void HierarchyPanel::Render() { UI::PanelWindowScope panel(m_name.c_str()); if (!panel.IsOpen()) { return; } + Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy); + RenderSearchBar(); HandleKeyboardShortcuts(); @@ -299,26 +318,24 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObje void HierarchyPanel::HandleKeyboardShortcuts() { auto& sceneManager = m_context->GetSceneManager(); auto& selectionManager = m_context->GetSelectionManager(); - ::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity()); - - if (ImGui::IsWindowFocused()) { - Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), [&]() { - Commands::DeleteEntity(*m_context, selectedGameObject->GetID()); - }); - Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), [&]() { - BeginRename(selectedGameObject); - }); - Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), [&]() { - Commands::CopyEntity(*m_context, selectedGameObject->GetID()); - }); - Actions::HandleShortcut(Actions::MakePasteEntityAction(*m_context), [&]() { - Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0); - }); - Actions::HandleShortcut(Actions::MakeDuplicateEntityAction(selectedGameObject), [&]() { - Commands::DuplicateEntity(*m_context, selectedGameObject->GetID()); - }); - } + const Actions::ShortcutContext shortcutContext = Actions::FocusedWindowShortcutContext(); + + Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), shortcutContext, [&]() { + Commands::DeleteEntity(*m_context, selectedGameObject->GetID()); + }); + Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), shortcutContext, [&]() { + BeginRename(selectedGameObject); + }); + Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), shortcutContext, [&]() { + Commands::CopyEntity(*m_context, selectedGameObject->GetID()); + }); + Actions::HandleShortcut(Actions::MakePasteEntityAction(*m_context), shortcutContext, [&]() { + Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0); + }); + Actions::HandleShortcut(Actions::MakeDuplicateEntityAction(selectedGameObject), shortcutContext, [&]() { + Commands::DuplicateEntity(*m_context, selectedGameObject->GetID()); + }); } bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) { diff --git a/editor/src/panels/HierarchyPanel.h b/editor/src/panels/HierarchyPanel.h index 57846e21..505f3b18 100644 --- a/editor/src/panels/HierarchyPanel.h +++ b/editor/src/panels/HierarchyPanel.h @@ -20,6 +20,7 @@ public: private: void OnSelectionChanged(const struct SelectionChangedEvent& event); + void OnRenameRequested(const struct EntityRenameRequestedEvent& event); void RenderSearchBar(); void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter); void RenderContextMenu(::XCEngine::Components::GameObject* gameObject); @@ -36,6 +37,7 @@ private: UI::InlineTextEditState m_renameState; SortMode m_sortMode = SortMode::Name; uint64_t m_selectionHandlerId = 0; + uint64_t m_renameRequestHandlerId = 0; }; } diff --git a/editor/src/panels/MenuBar.cpp b/editor/src/panels/MenuBar.cpp index 05a51e69..43b00486 100644 --- a/editor/src/panels/MenuBar.cpp +++ b/editor/src/panels/MenuBar.cpp @@ -1,10 +1,13 @@ +#include "Actions/ActionRouting.h" #include "Actions/EditorActions.h" #include "Commands/EntityCommands.h" +#include "Commands/ProjectCommands.h" #include "Commands/SceneCommands.h" #include "MenuBar.h" #include "Core/EditorEvents.h" #include "Core/EventBus.h" #include "Core/IEditorContext.h" +#include "Core/IProjectManager.h" #include "Core/ISceneManager.h" #include "Core/IUndoManager.h" #include "Core/ISelectionManager.h" @@ -37,12 +40,14 @@ void MenuBar::HandleShortcuts() { return; } - Actions::HandleShortcut(Actions::MakeNewSceneAction(), [&]() { Commands::NewScene(*m_context); }); - Actions::HandleShortcut(Actions::MakeOpenSceneAction(), [&]() { Commands::OpenSceneWithDialog(*m_context); }); - Actions::HandleShortcut(Actions::MakeSaveSceneAction(), [&]() { Commands::SaveCurrentScene(*m_context); }); - Actions::HandleShortcut(Actions::MakeSaveSceneAsAction(), [&]() { Commands::SaveSceneAsWithDialog(*m_context); }); - Actions::HandleShortcut(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); }); - Actions::HandleShortcut(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); }); + const Actions::ShortcutContext shortcutContext = Actions::GlobalShortcutContext(); + + Actions::HandleShortcut(Actions::MakeNewSceneAction(), shortcutContext, [&]() { Commands::NewScene(*m_context); }); + Actions::HandleShortcut(Actions::MakeOpenSceneAction(), shortcutContext, [&]() { Commands::OpenSceneWithDialog(*m_context); }); + Actions::HandleShortcut(Actions::MakeSaveSceneAction(), shortcutContext, [&]() { Commands::SaveCurrentScene(*m_context); }); + Actions::HandleShortcut(Actions::MakeSaveSceneAsAction(), shortcutContext, [&]() { Commands::SaveSceneAsWithDialog(*m_context); }); + Actions::HandleShortcut(Actions::MakeUndoAction(*m_context), shortcutContext, [&]() { m_context->GetUndoManager().Undo(); }); + Actions::HandleShortcut(Actions::MakeRedoAction(*m_context), shortcutContext, [&]() { m_context->GetUndoManager().Redo(); }); } void MenuBar::ShowFileMenu() { @@ -60,11 +65,29 @@ void MenuBar::ShowFileMenu() { void MenuBar::ShowEditMenu() { ::XCEngine::Components::GameObject* selectedGameObject = Actions::GetSelectedGameObject(*m_context); + const AssetItemPtr selectedAssetItem = Actions::GetSelectedAssetItem(*m_context); + auto& projectManager = m_context->GetProjectManager(); + const EditorActionRoute actionRoute = m_context->GetActiveActionRoute(); UI::DrawMenuScope("Edit", [&]() { Actions::DrawMenuAction(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); }); Actions::DrawMenuAction(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); }); Actions::DrawMenuSeparator(); + + if (actionRoute == EditorActionRoute::Project) { + Actions::DrawMenuAction(Actions::MakeOpenAssetAction(Commands::CanOpenAsset(selectedAssetItem)), [&]() { + Commands::OpenAsset(*m_context, selectedAssetItem); + }); + Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(selectedAssetItem != nullptr), [&]() { + Commands::DeleteAsset(projectManager, projectManager.GetSelectedIndex()); + }); + Actions::DrawMenuSeparator(); + Actions::DrawMenuAction(Actions::MakeNavigateBackAction(projectManager.CanNavigateBack()), [&]() { + projectManager.NavigateBack(); + }); + return; + } + Actions::DrawMenuAction(Actions::MakeCutAction(false), []() {}); Actions::DrawMenuAction(Actions::MakeCopyEntityAction(selectedGameObject), [&]() { Commands::CopyEntity(*m_context, selectedGameObject->GetID()); @@ -72,6 +95,15 @@ void MenuBar::ShowEditMenu() { Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() { Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0); }); + Actions::DrawMenuAction(Actions::MakeDuplicateEntityAction(selectedGameObject), [&]() { + Commands::DuplicateEntity(*m_context, selectedGameObject->GetID()); + }); + Actions::DrawMenuAction(Actions::MakeDeleteEntityAction(selectedGameObject), [&]() { + Commands::DeleteEntity(*m_context, selectedGameObject->GetID()); + }); + Actions::DrawMenuAction(Actions::MakeRenameEntityAction(selectedGameObject), [&]() { + m_context->GetEventBus().Publish(EntityRenameRequestedEvent{ selectedGameObject->GetID() }); + }); }); } diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp index 907e9304..46cdeb4f 100644 --- a/editor/src/panels/ProjectPanel.cpp +++ b/editor/src/panels/ProjectPanel.cpp @@ -1,3 +1,4 @@ +#include "Actions/ActionRouting.h" #include "Actions/EditorActions.h" #include "Commands/ProjectCommands.h" #include "ProjectPanel.h" @@ -32,6 +33,9 @@ void ProjectPanel::Render() { if (!panel.IsOpen()) { return; } + + Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project); + HandleKeyboardShortcuts(); auto& manager = m_context->GetProjectManager(); @@ -146,6 +150,30 @@ void ProjectPanel::Render() { } } +AssetItemPtr ProjectPanel::GetSelectedItem() const { + return m_context ? Actions::GetSelectedAssetItem(*m_context) : nullptr; +} + +void ProjectPanel::HandleKeyboardShortcuts() { + if (!m_context) { + return; + } + + auto& manager = m_context->GetProjectManager(); + const AssetItemPtr selectedItem = GetSelectedItem(); + const Actions::ShortcutContext shortcutContext = Actions::FocusedWindowShortcutContext(); + + Actions::HandleShortcut(Actions::MakeNavigateBackAction(manager.CanNavigateBack()), shortcutContext, [&]() { + manager.NavigateBack(); + }); + Actions::HandleShortcut(Actions::MakeOpenAssetAction(Commands::CanOpenAsset(selectedItem)), shortcutContext, [&]() { + Commands::OpenAsset(*m_context, selectedItem); + }); + Actions::HandleShortcut(Actions::MakeDeleteAssetAction(selectedItem != nullptr), shortcutContext, [&]() { + Commands::DeleteAsset(manager, manager.GetSelectedIndex()); + }); +} + void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) { auto& manager = m_context->GetProjectManager(); bool isSelected = (manager.GetSelectedIndex() == index); diff --git a/editor/src/panels/ProjectPanel.h b/editor/src/panels/ProjectPanel.h index 3e55b81b..828634d6 100644 --- a/editor/src/panels/ProjectPanel.h +++ b/editor/src/panels/ProjectPanel.h @@ -14,6 +14,8 @@ public: void Initialize(const std::string& projectPath); private: + AssetItemPtr GetSelectedItem() const; + void HandleKeyboardShortcuts(); void RenderAssetItem(const AssetItemPtr& item, int index); char m_searchBuffer[256] = ""; diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt new file mode 100644 index 00000000..c698a19a --- /dev/null +++ b/managed/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.15) + +project(XCEngineManaged NONE) + +if(NOT XCENGINE_ENABLE_MONO_SCRIPTING) + return() +endif() + +if(NOT WIN32) + message(FATAL_ERROR "XCENGINE_ENABLE_MONO_SCRIPTING currently requires Windows.") +endif() + +find_program(XCENGINE_DOTNET_EXECUTABLE dotnet REQUIRED) + +set( + XCENGINE_MANAGED_OUTPUT_DIR + "${CMAKE_BINARY_DIR}/managed" + CACHE PATH + "Output directory for generated managed assemblies") + +execute_process( + COMMAND ${XCENGINE_DOTNET_EXECUTABLE} --list-sdks + OUTPUT_VARIABLE XCENGINE_DOTNET_SDK_LIST + OUTPUT_STRIP_TRAILING_WHITESPACE) + +string(REGEX MATCHALL "[0-9]+\\.[0-9]+\\.[0-9]+" XCENGINE_DOTNET_SDK_VERSIONS "${XCENGINE_DOTNET_SDK_LIST}") +if(NOT XCENGINE_DOTNET_SDK_VERSIONS) + message(FATAL_ERROR "Failed to locate a .NET SDK for managed scripting assemblies.") +endif() + +list(GET XCENGINE_DOTNET_SDK_VERSIONS -1 XCENGINE_DOTNET_SDK_VERSION) + +set(XCENGINE_CSC_DLL "C:/Program Files/dotnet/sdk/${XCENGINE_DOTNET_SDK_VERSION}/Roslyn/bincore/csc.dll") +set(XCENGINE_NET472_REFERENCE_DIR "C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETFramework/v4.7.2") +set(XCENGINE_MONO_CORLIB_DIR "${XCENGINE_MONO_ROOT_DIR}/binary" CACHE PATH "Directory containing the bundled Mono corlib") +set(XCENGINE_MONO_MSCORLIB_PATH "${XCENGINE_MONO_CORLIB_DIR}/mscorlib.dll") + +set(XCENGINE_SCRIPT_CORE_DLL "${XCENGINE_MANAGED_OUTPUT_DIR}/XCEngine.ScriptCore.dll" CACHE FILEPATH "Generated XCEngine.ScriptCore assembly") +set(XCENGINE_GAME_SCRIPTS_DLL "${XCENGINE_MANAGED_OUTPUT_DIR}/GameScripts.dll" CACHE FILEPATH "Generated GameScripts assembly") + +foreach(XCENGINE_REQUIRED_PATH + "${XCENGINE_CSC_DLL}" + "${XCENGINE_NET472_REFERENCE_DIR}/mscorlib.dll" + "${XCENGINE_NET472_REFERENCE_DIR}/System.dll" + "${XCENGINE_NET472_REFERENCE_DIR}/System.Core.dll" + "${XCENGINE_MONO_MSCORLIB_PATH}") + if(NOT EXISTS "${XCENGINE_REQUIRED_PATH}") + message(FATAL_ERROR "Required managed scripting dependency is missing: ${XCENGINE_REQUIRED_PATH}") + endif() +endforeach() + +set(XCENGINE_SCRIPT_CORE_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MonoBehaviour.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Vector2.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Vector3.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Vector4.cs +) + +set(XCENGINE_GAME_SCRIPT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/LifecycleProbe.cs +) + +set(XCENGINE_MANAGED_FRAMEWORK_REFERENCES + /reference:${XCENGINE_NET472_REFERENCE_DIR}/mscorlib.dll + /reference:${XCENGINE_NET472_REFERENCE_DIR}/System.dll + /reference:${XCENGINE_NET472_REFERENCE_DIR}/System.Core.dll +) + +add_custom_command( + OUTPUT ${XCENGINE_SCRIPT_CORE_DLL} + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_MANAGED_OUTPUT_DIR} + COMMAND ${XCENGINE_DOTNET_EXECUTABLE} ${XCENGINE_CSC_DLL} + /nologo + /target:library + /langversion:latest + /nostdlib+ + /out:${XCENGINE_SCRIPT_CORE_DLL} + ${XCENGINE_MANAGED_FRAMEWORK_REFERENCES} + ${XCENGINE_SCRIPT_CORE_SOURCES} + DEPENDS ${XCENGINE_SCRIPT_CORE_SOURCES} + VERBATIM + COMMENT "Building XCEngine.ScriptCore.dll") + +add_custom_command( + OUTPUT ${XCENGINE_GAME_SCRIPTS_DLL} + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_MANAGED_OUTPUT_DIR} + COMMAND ${XCENGINE_DOTNET_EXECUTABLE} ${XCENGINE_CSC_DLL} + /nologo + /target:library + /langversion:latest + /nostdlib+ + /out:${XCENGINE_GAME_SCRIPTS_DLL} + ${XCENGINE_MANAGED_FRAMEWORK_REFERENCES} + /reference:${XCENGINE_SCRIPT_CORE_DLL} + ${XCENGINE_GAME_SCRIPT_SOURCES} + DEPENDS ${XCENGINE_GAME_SCRIPT_SOURCES} ${XCENGINE_SCRIPT_CORE_DLL} + VERBATIM + COMMENT "Building GameScripts.dll") + +add_custom_command( + OUTPUT ${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll + COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_MANAGED_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${XCENGINE_MONO_MSCORLIB_PATH} + ${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll + DEPENDS ${XCENGINE_MONO_MSCORLIB_PATH} + VERBATIM + COMMENT "Copying mscorlib.dll for Mono runtime resolution") + +add_custom_target( + xcengine_managed_assemblies + DEPENDS + ${XCENGINE_SCRIPT_CORE_DLL} + ${XCENGINE_GAME_SCRIPTS_DLL} + ${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll +) diff --git a/managed/GameScripts/LifecycleProbe.cs b/managed/GameScripts/LifecycleProbe.cs new file mode 100644 index 00000000..986973d4 --- /dev/null +++ b/managed/GameScripts/LifecycleProbe.cs @@ -0,0 +1,74 @@ +using XCEngine; + +namespace Gameplay +{ + public abstract class AbstractLifecycleProbe : MonoBehaviour + { + } + + public sealed class LifecycleProbe : MonoBehaviour + { + public int AwakeCount; + public int EnableCount; + public int StartCount; + public int FixedUpdateCount; + public int UpdateCount; + public int LateUpdateCount; + public int DisableCount; + public int DestroyCount; + + public float Speed; + public string Label = string.Empty; + public bool WasAwakened; + public Vector3 SpawnPoint; + + public void Awake() + { + AwakeCount += 1; + WasAwakened = true; + Label = Label + "|Awake"; + } + + public void OnEnable() + { + EnableCount += 1; + } + + public void Start() + { + StartCount += 1; + } + + public void FixedUpdate() + { + FixedUpdateCount += 1; + } + + public void Update() + { + UpdateCount += 1; + Speed += 1.0f; + } + + public void LateUpdate() + { + LateUpdateCount += 1; + SpawnPoint = new Vector3(SpawnPoint.X + 1.0f, SpawnPoint.Y, SpawnPoint.Z); + } + + public void OnDisable() + { + DisableCount += 1; + } + + public void OnDestroy() + { + DestroyCount += 1; + } + } + + public sealed class UtilityHelper + { + public int Value; + } +} diff --git a/managed/XCEngine.ScriptCore/MonoBehaviour.cs b/managed/XCEngine.ScriptCore/MonoBehaviour.cs new file mode 100644 index 00000000..6868e8aa --- /dev/null +++ b/managed/XCEngine.ScriptCore/MonoBehaviour.cs @@ -0,0 +1,8 @@ +namespace XCEngine +{ + public class MonoBehaviour + { + public ulong GameObjectUUID; + public ulong ScriptComponentUUID; + } +}