Route editor actions by active target

This commit is contained in:
2026-03-26 22:10:43 +08:00
parent 5c8042775c
commit 5735e769b0
21 changed files with 609 additions and 104 deletions

View File

@@ -2,7 +2,7 @@
## 当前判断
截至 2026-03-26Editor 的 UI 架构重构如果只计算“架构层整理”,不计算 Viewport/RHI 和 Scene/Game 真正内容开发,整体进度大约在 **90%**
截至 2026-03-26Editor 的 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 内联状态
### 低优先级

View File

@@ -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 <typename ExecuteFn>
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 <typename ExecuteFn>
inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) {
return HandleShortcut(action, GlobalShortcutContext(), std::forward<ExecuteFn>(execute));
}
} // namespace Actions
} // namespace Editor
} // namespace XCEngine

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Core/EditorActionRoute.h"
#include "Core/IEditorContext.h"
#include <imgui.h>
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

View File

@@ -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 <XCEngine/Components/GameObject.h>
@@ -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<int>(items.size())) {
return nullptr;
}
return items[selectedIndex];
}
} // namespace Actions
} // namespace Editor
} // namespace XCEngine

View File

@@ -7,16 +7,12 @@
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Debug/FileLogSink.h>
#include <XCEngine/Debug/ConsoleLogSink.h>
#include <imgui_impl_win32.h>
#include <imgui_impl_dx12.h>
#include <imgui_internal.h>
#include <filesystem>
#include <stdio.h>
#include <windows.h>
#include <string>
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<EditorExitRequestedEvent>(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;

View File

@@ -1,5 +1,8 @@
#pragma once
#include "UI/ImGuiBackendBridge.h"
#include "UI/ImGuiSession.h"
#include <memory>
#include <string>
#include <imgui.h>
@@ -8,8 +11,6 @@
#include <XCEngine/Core/LayerStack.h>
#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<IEditorContext> m_editorContext;
UI::ImGuiBackendBridge m_imguiBackend;
UI::ImGuiSession m_imguiSession;
uint64_t m_exitRequestedHandlerId = 0;
std::string m_imguiIniPath;
std::wstring m_lastWindowTitle;
};

View File

@@ -0,0 +1,13 @@
#pragma once
namespace XCEngine {
namespace Editor {
enum class EditorActionRoute {
None,
Hierarchy,
Project
};
} // namespace Editor
} // namespace XCEngine

View File

@@ -56,6 +56,14 @@ public:
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<SceneManager> m_sceneManager;
std::unique_ptr<UndoManager> m_undoManager;
std::unique_ptr<ProjectManager> m_projectManager;
EditorActionRoute m_activeActionRoute = EditorActionRoute::None;
std::string m_projectPath;
uint64_t m_entityDeletedHandlerId;
};

View File

@@ -25,6 +25,10 @@ struct EntityChangedEvent {
GameObjectID entityId;
};
struct EntityRenameRequestedEvent {
GameObjectID entityId;
};
struct EntityParentChangedEvent {
GameObjectID entityId;
GameObjectID oldParentId;

View File

@@ -1,5 +1,7 @@
#pragma once
#include "EditorActionRoute.h"
#include <memory>
#include <string>
@@ -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;

View File

@@ -0,0 +1,65 @@
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <imgui.h>
#include <imgui_impl_dx12.h>
#include <imgui_impl_win32.h>
#include <windows.h>
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

View File

@@ -0,0 +1,77 @@
#pragma once
#include "BaseTheme.h"
#include <imgui.h>
#include <filesystem>
#include <string>
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

View File

@@ -1,4 +1,5 @@
#include "Application.h"
#include "UI/ImGuiBackendBridge.h"
#include <imgui.h>
#include <windows.h>
#include <stdio.h>
@@ -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) {

View File

@@ -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<SelectionChangedEvent>(m_selectionHandlerId);
m_context->GetEventBus().Unsubscribe<EntityRenameRequestedEvent>(m_renameRequestHandlerId);
}
}
@@ -29,6 +31,11 @@ void HierarchyPanel::OnAttach() {
OnSelectionChanged(event);
}
);
m_renameRequestHandlerId = m_context->GetEventBus().Subscribe<EntityRenameRequestedEvent>(
[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,27 +318,25 @@ 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());
const Actions::ShortcutContext shortcutContext = Actions::FocusedWindowShortcutContext();
if (ImGui::IsWindowFocused()) {
Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), [&]() {
Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), shortcutContext, [&]() {
Commands::DeleteEntity(*m_context, selectedGameObject->GetID());
});
Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), [&]() {
Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), shortcutContext, [&]() {
BeginRename(selectedGameObject);
});
Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), shortcutContext, [&]() {
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
});
Actions::HandleShortcut(Actions::MakePasteEntityAction(*m_context), [&]() {
Actions::HandleShortcut(Actions::MakePasteEntityAction(*m_context), shortcutContext, [&]() {
Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0);
});
Actions::HandleShortcut(Actions::MakeDuplicateEntityAction(selectedGameObject), [&]() {
Actions::HandleShortcut(Actions::MakeDuplicateEntityAction(selectedGameObject), shortcutContext, [&]() {
Commands::DuplicateEntity(*m_context, selectedGameObject->GetID());
});
}
}
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return false;

View File

@@ -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<uint64_t, 256> m_renameState;
SortMode m_sortMode = SortMode::Name;
uint64_t m_selectionHandlerId = 0;
uint64_t m_renameRequestHandlerId = 0;
};
}

View File

@@ -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() });
});
});
}

View File

@@ -1,3 +1,4 @@
#include "Actions/ActionRouting.h"
#include "Actions/EditorActions.h"
#include "Commands/ProjectCommands.h"
#include "ProjectPanel.h"
@@ -33,6 +34,9 @@ void ProjectPanel::Render() {
return;
}
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project);
HandleKeyboardShortcuts();
auto& manager = m_context->GetProjectManager();
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
@@ -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);

View File

@@ -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] = "";

116
managed/CMakeLists.txt Normal file
View File

@@ -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
)

View File

@@ -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;
}
}

View File

@@ -0,0 +1,8 @@
namespace XCEngine
{
public class MonoBehaviour
{
public ulong GameObjectUUID;
public ulong ScriptComponentUUID;
}
}