Route editor actions by active target
This commit is contained in:
@@ -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 仍未回归,且部分编辑语义与回归验证还未补齐。
|
如果把“编辑器整体可用度”也算进去,则大约在 **65%** 左右,因为 `Scene` / `Game` 面板目前仍然只是占位壳,Viewport 仍未回归,且部分编辑语义与回归验证还未补齐。
|
||||||
|
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
- shortcut 文本
|
- shortcut 文本
|
||||||
- enabled 状态
|
- enabled 状态
|
||||||
- 文本输入时是否允许触发
|
- 文本输入时是否允许触发
|
||||||
|
- shortcut 触发上下文
|
||||||
- menu 与 shortcut 的统一绑定
|
- menu 与 shortcut 的统一绑定
|
||||||
- button / toolbar / inspector action 的统一接线
|
- button / toolbar / inspector action 的统一接线
|
||||||
|
|
||||||
@@ -93,6 +94,15 @@
|
|||||||
|
|
||||||
这一层已经不再只是 `MenuBar + Hierarchy` 的试点,而是开始成为 editor 范围内的统一动作定义入口。
|
这一层已经不再只是 `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 层
|
### 5. Dock / Layout 层
|
||||||
|
|
||||||
`Dock / Layout` 已从 `EditorLayer::setupDockspace()` 中抽出,形成独立 layout controller:
|
`Dock / Layout` 已从 `EditorLayer::setupDockspace()` 中抽出,形成独立 layout controller:
|
||||||
@@ -121,6 +131,20 @@
|
|||||||
- panel 的 attach / detach / render 顺序有了统一入口
|
- panel 的 attach / detach / render 顺序有了统一入口
|
||||||
- 后续继续拆 panel 或补 panel 时,不需要再改一大片壳层代码
|
- 后续继续拆 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
|
### MenuBar
|
||||||
@@ -134,6 +158,7 @@
|
|||||||
- `Reset Layout` 已接入事件驱动行为
|
- `Reset Layout` 已接入事件驱动行为
|
||||||
- `About` 已接入真实 modal popup
|
- `About` 已接入真实 modal popup
|
||||||
- `Exit` 已通过事件驱动关闭 editor
|
- `Exit` 已通过事件驱动关闭 editor
|
||||||
|
- `Edit` 菜单已开始跟随 active action route 在 `Hierarchy / Project` 之间切换
|
||||||
|
|
||||||
仍待完成:
|
仍待完成:
|
||||||
|
|
||||||
@@ -148,6 +173,7 @@
|
|||||||
- 快捷键已接 action 层
|
- 快捷键已接 action 层
|
||||||
- 重命名状态已收成 `Begin / Commit / Cancel`
|
- 重命名状态已收成 `Begin / Commit / Cancel`
|
||||||
- 重命名交互已从 panel 局部字段收口到 shared inline edit state
|
- 重命名交互已从 panel 局部字段收口到 shared inline edit state
|
||||||
|
- `Rename` 请求已能从 `MenuBar -> EventBus -> Hierarchy inline edit` 触发
|
||||||
|
|
||||||
仍待完成:
|
仍待完成:
|
||||||
|
|
||||||
@@ -162,6 +188,7 @@
|
|||||||
- 上下文菜单、返回按钮、创建文件夹对话框已接 action 层
|
- 上下文菜单、返回按钮、创建文件夹对话框已接 action 层
|
||||||
- 资源图标绘制与图标配色已下沉到 shared UI token / widget
|
- 资源图标绘制与图标配色已下沉到 shared UI token / widget
|
||||||
- 创建文件夹弹窗已改成 shared popup state 驱动
|
- 创建文件夹弹窗已改成 shared popup state 驱动
|
||||||
|
- `Back / Open / Delete` 已接 panel-focused keyboard action
|
||||||
|
|
||||||
仍待完成:
|
仍待完成:
|
||||||
|
|
||||||
@@ -209,13 +236,12 @@
|
|||||||
### 高优先级
|
### 高优先级
|
||||||
|
|
||||||
1. 整理 `Application.cpp` 和 `EditorLayer.cpp` 中剩余的 UI 壳逻辑
|
1. 整理 `Application.cpp` 和 `EditorLayer.cpp` 中剩余的 UI 壳逻辑
|
||||||
包括字体初始化、主 dock host 初始化、部分样式 push / pop 的收口。
|
包括 backend 初始化边界、主循环中剩余 ImGui / render 壳逻辑、部分样式 push / pop 的收口。
|
||||||
|
|
||||||
### 中优先级
|
### 中优先级
|
||||||
|
|
||||||
2. 继续将 panel 的本地状态机抽离
|
2. 继续将 panel 的本地状态机抽离
|
||||||
包括:
|
包括:
|
||||||
- inspector component popup state
|
|
||||||
- console filter state
|
- console filter state
|
||||||
- 其他仍散落在 panel 内的临时交互状态
|
- 其他仍散落在 panel 内的临时交互状态
|
||||||
|
|
||||||
@@ -224,9 +250,10 @@
|
|||||||
|
|
||||||
4. 统一 editor 范围内的编辑语义
|
4. 统一 editor 范围内的编辑语义
|
||||||
例如:
|
例如:
|
||||||
- 哪些 panel 消费 Copy / Paste / Delete
|
- 哪些 panel 消费 Copy / Paste / Delete / Open / Back
|
||||||
- 哪些动作受 selection 驱动
|
- 哪些动作受 selection 驱动
|
||||||
- 哪些动作在文本输入时必须屏蔽
|
- 哪些动作在文本输入时必须屏蔽
|
||||||
|
- 哪些动作通过 command,哪些动作通过 event 请求 panel 内联状态
|
||||||
|
|
||||||
### 低优先级
|
### 低优先级
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ struct ShortcutChord {
|
|||||||
bool alt = false;
|
bool alt = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ShortcutRoute {
|
||||||
|
Global,
|
||||||
|
FocusedWindow
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShortcutContext {
|
||||||
|
ShortcutRoute route = ShortcutRoute::Global;
|
||||||
|
bool enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
struct ActionBinding {
|
struct ActionBinding {
|
||||||
std::string label;
|
std::string label;
|
||||||
std::string shortcutLabel;
|
std::string shortcutLabel;
|
||||||
@@ -31,6 +41,14 @@ inline ShortcutChord Shortcut(ImGuiKey key, bool ctrl = false, bool shift = fals
|
|||||||
return ShortcutChord{ key, ctrl, shift, alt };
|
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(
|
inline ActionBinding MakeAction(
|
||||||
std::string label,
|
std::string label,
|
||||||
const char* shortcutLabel = nullptr,
|
const char* shortcutLabel = nullptr,
|
||||||
@@ -118,11 +136,30 @@ inline bool IsShortcutPressed(const ShortcutChord& shortcut, const ImGuiIO& io)
|
|||||||
return ImGui::IsKeyPressed(shortcut.key, false);
|
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) {
|
if (!action.enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!IsShortcutContextActive(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const ImGuiIO& io = ImGui::GetIO();
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
if (!action.allowWhenTextInput && io.WantTextInput) {
|
if (!action.allowWhenTextInput && io.WantTextInput) {
|
||||||
return false;
|
return false;
|
||||||
@@ -137,9 +174,13 @@ inline bool IsShortcutPressed(const ActionBinding& action) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool IsShortcutPressed(const ActionBinding& action) {
|
||||||
|
return IsShortcutPressed(action, GlobalShortcutContext());
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ExecuteFn>
|
template <typename ExecuteFn>
|
||||||
inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) {
|
inline bool HandleShortcut(const ActionBinding& action, const ShortcutContext& context, ExecuteFn&& execute) {
|
||||||
if (!IsShortcutPressed(action)) {
|
if (!IsShortcutPressed(action, context)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +188,11 @@ inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ExecuteFn>
|
||||||
|
inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) {
|
||||||
|
return HandleShortcut(action, GlobalShortcutContext(), std::forward<ExecuteFn>(execute));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Actions
|
} // namespace Actions
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
24
editor/src/Actions/ActionRouting.h
Normal file
24
editor/src/Actions/ActionRouting.h
Normal 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
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include "ActionBinding.h"
|
#include "ActionBinding.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "Core/IProjectManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/IUndoManager.h"
|
#include "Core/IUndoManager.h"
|
||||||
|
#include "Core/AssetItem.h"
|
||||||
|
|
||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
|
||||||
@@ -128,15 +130,15 @@ inline ActionBinding MakeExitAction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline ActionBinding MakeNavigateBackAction(bool enabled) {
|
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) {
|
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) {
|
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) {
|
inline ActionBinding MakeCreateFolderAction(bool enabled = true) {
|
||||||
@@ -179,6 +181,17 @@ inline ::XCEngine::Components::GameObject* GetSelectedGameObject(IEditorContext&
|
|||||||
return context.GetSceneManager().GetEntity(context.GetSelectionManager().GetSelectedEntity());
|
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 Actions
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
@@ -7,16 +7,12 @@
|
|||||||
#include <XCEngine/Debug/Logger.h>
|
#include <XCEngine/Debug/Logger.h>
|
||||||
#include <XCEngine/Debug/FileLogSink.h>
|
#include <XCEngine/Debug/FileLogSink.h>
|
||||||
#include <XCEngine/Debug/ConsoleLogSink.h>
|
#include <XCEngine/Debug/ConsoleLogSink.h>
|
||||||
#include <imgui_impl_win32.h>
|
|
||||||
#include <imgui_impl_dx12.h>
|
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::string WideToUtf8(const std::wstring& value) {
|
std::string WideToUtf8(const std::wstring& value) {
|
||||||
@@ -66,10 +62,6 @@ std::string GetExecutableLogPath(const char* fileName) {
|
|||||||
return GetExecutableDirectoryUtf8() + "\\" + fileName;
|
return GetExecutableDirectoryUtf8() + "\\" + fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BuildEditorConfigDirectory(const std::string& projectPath) {
|
|
||||||
return (std::filesystem::path(projectPath) / ".xceditor").string();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
||||||
@@ -142,12 +134,9 @@ bool Application::Initialize(HWND hwnd) {
|
|||||||
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ConfigureImGui(m_editorContext->GetProjectPath());
|
m_imguiSession.Initialize(m_editorContext->GetProjectPath());
|
||||||
|
|
||||||
ImGui_ImplWin32_Init(hwnd);
|
m_imguiBackend.Initialize(hwnd, m_device, m_srvHeap);
|
||||||
ImGui_ImplDX12_Init(m_device, 3, DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap,
|
|
||||||
m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
|
|
||||||
m_srvHeap->GetGPUDescriptorHandleForHeapStart());
|
|
||||||
|
|
||||||
m_editorLayer = new EditorLayer();
|
m_editorLayer = new EditorLayer();
|
||||||
m_editorLayer->SetContext(m_editorContext);
|
m_editorLayer->SetContext(m_editorContext);
|
||||||
@@ -159,16 +148,14 @@ bool Application::Initialize(HWND hwnd) {
|
|||||||
|
|
||||||
void Application::Shutdown() {
|
void Application::Shutdown() {
|
||||||
m_layerStack.onDetach();
|
m_layerStack.onDetach();
|
||||||
SaveImGuiSettings();
|
|
||||||
|
|
||||||
if (m_editorContext && m_exitRequestedHandlerId) {
|
if (m_editorContext && m_exitRequestedHandlerId) {
|
||||||
m_editorContext->GetEventBus().Unsubscribe<EditorExitRequestedEvent>(m_exitRequestedHandlerId);
|
m_editorContext->GetEventBus().Unsubscribe<EditorExitRequestedEvent>(m_exitRequestedHandlerId);
|
||||||
m_exitRequestedHandlerId = 0;
|
m_exitRequestedHandlerId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui_ImplDX12_Shutdown();
|
m_imguiBackend.Shutdown();
|
||||||
ImGui_ImplWin32_Shutdown();
|
m_imguiSession.Shutdown();
|
||||||
ImGui::DestroyContext();
|
|
||||||
|
|
||||||
CleanupRenderTarget();
|
CleanupRenderTarget();
|
||||||
|
|
||||||
@@ -183,9 +170,7 @@ void Application::Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Application::Render() {
|
void Application::Render() {
|
||||||
ImGui_ImplDX12_NewFrame();
|
m_imguiBackend.BeginFrame();
|
||||||
ImGui_ImplWin32_NewFrame();
|
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
m_layerStack.onImGuiRender();
|
m_layerStack.onImGuiRender();
|
||||||
UpdateWindowTitle();
|
UpdateWindowTitle();
|
||||||
@@ -214,7 +199,7 @@ void Application::Render() {
|
|||||||
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
|
ID3D12DescriptorHeap* heaps[] = { m_srvHeap };
|
||||||
m_commandList->SetDescriptorHeaps(1, heaps);
|
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.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
||||||
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
|
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() {
|
void Application::UpdateWindowTitle() {
|
||||||
if (!m_hwnd || !m_editorContext) {
|
if (!m_hwnd || !m_editorContext) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "UI/ImGuiBackendBridge.h"
|
||||||
|
#include "UI/ImGuiSession.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -8,8 +11,6 @@
|
|||||||
|
|
||||||
#include <XCEngine/Core/LayerStack.h>
|
#include <XCEngine/Core/LayerStack.h>
|
||||||
|
|
||||||
#include "Theme.h"
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
@@ -32,9 +33,6 @@ private:
|
|||||||
Application() = default;
|
Application() = default;
|
||||||
~Application() = default;
|
~Application() = default;
|
||||||
|
|
||||||
void ConfigureImGui(const std::string& projectPath);
|
|
||||||
void ConfigureImGuiIniFile(const std::string& projectPath, ImGuiIO& io);
|
|
||||||
void SaveImGuiSettings();
|
|
||||||
bool CreateDevice();
|
bool CreateDevice();
|
||||||
bool CreateRenderTarget();
|
bool CreateRenderTarget();
|
||||||
void CleanupRenderTarget();
|
void CleanupRenderTarget();
|
||||||
@@ -60,8 +58,9 @@ private:
|
|||||||
Core::LayerStack m_layerStack;
|
Core::LayerStack m_layerStack;
|
||||||
EditorLayer* m_editorLayer = nullptr;
|
EditorLayer* m_editorLayer = nullptr;
|
||||||
std::shared_ptr<IEditorContext> m_editorContext;
|
std::shared_ptr<IEditorContext> m_editorContext;
|
||||||
|
UI::ImGuiBackendBridge m_imguiBackend;
|
||||||
|
UI::ImGuiSession m_imguiSession;
|
||||||
uint64_t m_exitRequestedHandlerId = 0;
|
uint64_t m_exitRequestedHandlerId = 0;
|
||||||
std::string m_imguiIniPath;
|
|
||||||
std::wstring m_lastWindowTitle;
|
std::wstring m_lastWindowTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
13
editor/src/Core/EditorActionRoute.h
Normal file
13
editor/src/Core/EditorActionRoute.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
enum class EditorActionRoute {
|
||||||
|
None,
|
||||||
|
Hierarchy,
|
||||||
|
Project
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -56,6 +56,14 @@ public:
|
|||||||
return *m_undoManager;
|
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 {
|
void SetProjectPath(const std::string& path) override {
|
||||||
m_projectPath = path;
|
m_projectPath = path;
|
||||||
}
|
}
|
||||||
@@ -70,6 +78,7 @@ private:
|
|||||||
std::unique_ptr<SceneManager> m_sceneManager;
|
std::unique_ptr<SceneManager> m_sceneManager;
|
||||||
std::unique_ptr<UndoManager> m_undoManager;
|
std::unique_ptr<UndoManager> m_undoManager;
|
||||||
std::unique_ptr<ProjectManager> m_projectManager;
|
std::unique_ptr<ProjectManager> m_projectManager;
|
||||||
|
EditorActionRoute m_activeActionRoute = EditorActionRoute::None;
|
||||||
std::string m_projectPath;
|
std::string m_projectPath;
|
||||||
uint64_t m_entityDeletedHandlerId;
|
uint64_t m_entityDeletedHandlerId;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ struct EntityChangedEvent {
|
|||||||
GameObjectID entityId;
|
GameObjectID entityId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct EntityRenameRequestedEvent {
|
||||||
|
GameObjectID entityId;
|
||||||
|
};
|
||||||
|
|
||||||
struct EntityParentChangedEvent {
|
struct EntityParentChangedEvent {
|
||||||
GameObjectID entityId;
|
GameObjectID entityId;
|
||||||
GameObjectID oldParentId;
|
GameObjectID oldParentId;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "EditorActionRoute.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ public:
|
|||||||
virtual ISceneManager& GetSceneManager() = 0;
|
virtual ISceneManager& GetSceneManager() = 0;
|
||||||
virtual IProjectManager& GetProjectManager() = 0;
|
virtual IProjectManager& GetProjectManager() = 0;
|
||||||
virtual IUndoManager& GetUndoManager() = 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 void SetProjectPath(const std::string& path) = 0;
|
||||||
virtual const std::string& GetProjectPath() const = 0;
|
virtual const std::string& GetProjectPath() const = 0;
|
||||||
|
|||||||
65
editor/src/UI/ImGuiBackendBridge.h
Normal file
65
editor/src/UI/ImGuiBackendBridge.h
Normal 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
|
||||||
77
editor/src/UI/ImGuiSession.h
Normal file
77
editor/src/UI/ImGuiSession.h
Normal 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
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "UI/ImGuiBackendBridge.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -72,10 +73,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
|||||||
return 0;
|
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) {
|
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;
|
return true;
|
||||||
|
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "Actions/EditorActions.h"
|
#include "Actions/EditorActions.h"
|
||||||
|
#include "Actions/ActionRouting.h"
|
||||||
#include "Commands/EntityCommands.h"
|
#include "Commands/EntityCommands.h"
|
||||||
#include "HierarchyPanel.h"
|
#include "HierarchyPanel.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
@@ -20,6 +21,7 @@ HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
|
|||||||
HierarchyPanel::~HierarchyPanel() {
|
HierarchyPanel::~HierarchyPanel() {
|
||||||
if (m_context) {
|
if (m_context) {
|
||||||
m_context->GetEventBus().Unsubscribe<SelectionChangedEvent>(m_selectionHandlerId);
|
m_context->GetEventBus().Unsubscribe<SelectionChangedEvent>(m_selectionHandlerId);
|
||||||
|
m_context->GetEventBus().Unsubscribe<EntityRenameRequestedEvent>(m_renameRequestHandlerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +31,11 @@ void HierarchyPanel::OnAttach() {
|
|||||||
OnSelectionChanged(event);
|
OnSelectionChanged(event);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
m_renameRequestHandlerId = m_context->GetEventBus().Subscribe<EntityRenameRequestedEvent>(
|
||||||
|
[this](const EntityRenameRequestedEvent& event) {
|
||||||
|
OnRenameRequested(event);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& 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() {
|
void HierarchyPanel::Render() {
|
||||||
UI::PanelWindowScope panel(m_name.c_str());
|
UI::PanelWindowScope panel(m_name.c_str());
|
||||||
if (!panel.IsOpen()) {
|
if (!panel.IsOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
|
||||||
|
|
||||||
RenderSearchBar();
|
RenderSearchBar();
|
||||||
|
|
||||||
HandleKeyboardShortcuts();
|
HandleKeyboardShortcuts();
|
||||||
@@ -299,27 +318,25 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObje
|
|||||||
void HierarchyPanel::HandleKeyboardShortcuts() {
|
void HierarchyPanel::HandleKeyboardShortcuts() {
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
auto& selectionManager = m_context->GetSelectionManager();
|
auto& selectionManager = m_context->GetSelectionManager();
|
||||||
|
|
||||||
::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity());
|
::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity());
|
||||||
|
const Actions::ShortcutContext shortcutContext = Actions::FocusedWindowShortcutContext();
|
||||||
|
|
||||||
if (ImGui::IsWindowFocused()) {
|
Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), shortcutContext, [&]() {
|
||||||
Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), [&]() {
|
|
||||||
Commands::DeleteEntity(*m_context, selectedGameObject->GetID());
|
Commands::DeleteEntity(*m_context, selectedGameObject->GetID());
|
||||||
});
|
});
|
||||||
Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), [&]() {
|
Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), shortcutContext, [&]() {
|
||||||
BeginRename(selectedGameObject);
|
BeginRename(selectedGameObject);
|
||||||
});
|
});
|
||||||
Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
|
Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), shortcutContext, [&]() {
|
||||||
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
|
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);
|
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());
|
Commands::DuplicateEntity(*m_context, selectedGameObject->GetID());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
|
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
|
||||||
if (!gameObject) return false;
|
if (!gameObject) return false;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void OnSelectionChanged(const struct SelectionChangedEvent& event);
|
void OnSelectionChanged(const struct SelectionChangedEvent& event);
|
||||||
|
void OnRenameRequested(const struct EntityRenameRequestedEvent& event);
|
||||||
void RenderSearchBar();
|
void RenderSearchBar();
|
||||||
void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
|
void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
|
||||||
void RenderContextMenu(::XCEngine::Components::GameObject* gameObject);
|
void RenderContextMenu(::XCEngine::Components::GameObject* gameObject);
|
||||||
@@ -36,6 +37,7 @@ private:
|
|||||||
UI::InlineTextEditState<uint64_t, 256> m_renameState;
|
UI::InlineTextEditState<uint64_t, 256> m_renameState;
|
||||||
SortMode m_sortMode = SortMode::Name;
|
SortMode m_sortMode = SortMode::Name;
|
||||||
uint64_t m_selectionHandlerId = 0;
|
uint64_t m_selectionHandlerId = 0;
|
||||||
|
uint64_t m_renameRequestHandlerId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
#include "Actions/ActionRouting.h"
|
||||||
#include "Actions/EditorActions.h"
|
#include "Actions/EditorActions.h"
|
||||||
#include "Commands/EntityCommands.h"
|
#include "Commands/EntityCommands.h"
|
||||||
|
#include "Commands/ProjectCommands.h"
|
||||||
#include "Commands/SceneCommands.h"
|
#include "Commands/SceneCommands.h"
|
||||||
#include "MenuBar.h"
|
#include "MenuBar.h"
|
||||||
#include "Core/EditorEvents.h"
|
#include "Core/EditorEvents.h"
|
||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "Core/IProjectManager.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/IUndoManager.h"
|
#include "Core/IUndoManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
@@ -37,12 +40,14 @@ void MenuBar::HandleShortcuts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::HandleShortcut(Actions::MakeNewSceneAction(), [&]() { Commands::NewScene(*m_context); });
|
const Actions::ShortcutContext shortcutContext = Actions::GlobalShortcutContext();
|
||||||
Actions::HandleShortcut(Actions::MakeOpenSceneAction(), [&]() { Commands::OpenSceneWithDialog(*m_context); });
|
|
||||||
Actions::HandleShortcut(Actions::MakeSaveSceneAction(), [&]() { Commands::SaveCurrentScene(*m_context); });
|
Actions::HandleShortcut(Actions::MakeNewSceneAction(), shortcutContext, [&]() { Commands::NewScene(*m_context); });
|
||||||
Actions::HandleShortcut(Actions::MakeSaveSceneAsAction(), [&]() { Commands::SaveSceneAsWithDialog(*m_context); });
|
Actions::HandleShortcut(Actions::MakeOpenSceneAction(), shortcutContext, [&]() { Commands::OpenSceneWithDialog(*m_context); });
|
||||||
Actions::HandleShortcut(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); });
|
Actions::HandleShortcut(Actions::MakeSaveSceneAction(), shortcutContext, [&]() { Commands::SaveCurrentScene(*m_context); });
|
||||||
Actions::HandleShortcut(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); });
|
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() {
|
void MenuBar::ShowFileMenu() {
|
||||||
@@ -60,11 +65,29 @@ void MenuBar::ShowFileMenu() {
|
|||||||
|
|
||||||
void MenuBar::ShowEditMenu() {
|
void MenuBar::ShowEditMenu() {
|
||||||
::XCEngine::Components::GameObject* selectedGameObject = Actions::GetSelectedGameObject(*m_context);
|
::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", [&]() {
|
UI::DrawMenuScope("Edit", [&]() {
|
||||||
Actions::DrawMenuAction(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); });
|
Actions::DrawMenuAction(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); });
|
||||||
Actions::DrawMenuAction(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); });
|
Actions::DrawMenuAction(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); });
|
||||||
Actions::DrawMenuSeparator();
|
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::MakeCutAction(false), []() {});
|
||||||
Actions::DrawMenuAction(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
|
Actions::DrawMenuAction(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
|
||||||
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
|
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
|
||||||
@@ -72,6 +95,15 @@ void MenuBar::ShowEditMenu() {
|
|||||||
Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() {
|
Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() {
|
||||||
Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0);
|
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() });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "Actions/ActionRouting.h"
|
||||||
#include "Actions/EditorActions.h"
|
#include "Actions/EditorActions.h"
|
||||||
#include "Commands/ProjectCommands.h"
|
#include "Commands/ProjectCommands.h"
|
||||||
#include "ProjectPanel.h"
|
#include "ProjectPanel.h"
|
||||||
@@ -33,6 +34,9 @@ void ProjectPanel::Render() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project);
|
||||||
|
HandleKeyboardShortcuts();
|
||||||
|
|
||||||
auto& manager = m_context->GetProjectManager();
|
auto& manager = m_context->GetProjectManager();
|
||||||
|
|
||||||
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
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) {
|
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||||
auto& manager = m_context->GetProjectManager();
|
auto& manager = m_context->GetProjectManager();
|
||||||
bool isSelected = (manager.GetSelectedIndex() == index);
|
bool isSelected = (manager.GetSelectedIndex() == index);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public:
|
|||||||
void Initialize(const std::string& projectPath);
|
void Initialize(const std::string& projectPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
AssetItemPtr GetSelectedItem() const;
|
||||||
|
void HandleKeyboardShortcuts();
|
||||||
void RenderAssetItem(const AssetItemPtr& item, int index);
|
void RenderAssetItem(const AssetItemPtr& item, int index);
|
||||||
|
|
||||||
char m_searchBuffer[256] = "";
|
char m_searchBuffer[256] = "";
|
||||||
|
|||||||
116
managed/CMakeLists.txt
Normal file
116
managed/CMakeLists.txt
Normal 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
|
||||||
|
)
|
||||||
74
managed/GameScripts/LifecycleProbe.cs
Normal file
74
managed/GameScripts/LifecycleProbe.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
managed/XCEngine.ScriptCore/MonoBehaviour.cs
Normal file
8
managed/XCEngine.ScriptCore/MonoBehaviour.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace XCEngine
|
||||||
|
{
|
||||||
|
public class MonoBehaviour
|
||||||
|
{
|
||||||
|
public ulong GameObjectUUID;
|
||||||
|
public ulong ScriptComponentUUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user