diff --git a/Apps/MetaCorePlayer/main.cpp b/Apps/MetaCorePlayer/main.cpp index 28c1926..e5925ae 100644 --- a/Apps/MetaCorePlayer/main.cpp +++ b/Apps/MetaCorePlayer/main.cpp @@ -9,6 +9,7 @@ #include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h" +#include "MetaCoreScene/MetaCoreRuntimeLifecycle.h" #include "MetaCoreScene/MetaCoreScenePackage.h" #include "MetaCoreScene/MetaCoreSceneSerializer.h" #include "MetaCoreScene/MetaCoreScene.h" @@ -727,6 +728,21 @@ int main(int argc, char* argv[]) { scene = MetaCore::MetaCoreCreateDefaultScene(); std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n"; } + MetaCore::MetaCoreRuntimeLifecycleExecutor runtimeLifecycleExecutor; + const std::size_t runtimeComponentCount = runtimeLifecycleExecutor.RegisterDescriptors( + MetaCore::MetaCoreGetRuntimeLifecycleRegistry().GetDescriptors() + ); + if (runtimeComponentCount > 0) { + std::cout << "MetaCorePlayer: registered " << runtimeComponentCount + << " runtime lifecycle component(s)\n"; + } + const auto runtimeLifecycleErrorSink = [](const MetaCore::MetaCoreRuntimeLifecycleError& error) { + std::cerr << "MetaCorePlayer: lifecycle " << error.Phase + << " failed component=" << error.ComponentTypeId + << " object=" << error.ObjectId + << " error=" << error.Message << '\n'; + }; + runtimeLifecycleExecutor.EnterScene(scene, runtimeLifecycleErrorSink); std::optional startupUiDocument; if (!runtimeProjectDocument.StartupUiPath.empty()) { @@ -892,6 +908,7 @@ int main(int argc, char* argv[]) { }); runtimeUiRenderer.Resize(windowWidth, windowHeight); runtimeUiRenderer.BeginFrame(1.0F / 60.0F); + runtimeLifecycleExecutor.TickScene(scene, 1.0F / 60.0F, runtimeLifecycleErrorSink); if (!runtimeSources.empty()) { std::uint64_t latestUpdateAt = 0; for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) { @@ -949,6 +966,7 @@ int main(int argc, char* argv[]) { window.EndFrame(); } + runtimeLifecycleExecutor.ExitScene(scene, runtimeLifecycleErrorSink); runtimeUiRenderer.Shutdown(); viewportRenderer.Shutdown(); renderDevice.Shutdown(); diff --git a/CMakeLists.txt b/CMakeLists.txt index c3eae85..197e16f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,7 @@ target_compile_definitions(MetaCorePlatform PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX set(METACORE_SCENE_HEADERS Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h + Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreRuntimeLifecycle.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h @@ -232,6 +233,7 @@ set(METACORE_SCENE_HEADERS set(METACORE_SCENE_SOURCES Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp + Source/MetaCoreScene/Private/MetaCoreRuntimeLifecycle.cpp Source/MetaCoreScene/Private/MetaCoreScene.cpp Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp Source/MetaCoreScene/Private/MetaCoreUiRmlCompiler.cpp @@ -486,6 +488,7 @@ endif() add_executable(MetaCorePlayer Apps/MetaCorePlayer/main.cpp + third_party/stb/stb_image_impl.cpp ) target_link_libraries(MetaCorePlayer diff --git a/SandboxProject/Assets/111.glb b/SandboxProject/Assets/111.glb new file mode 100644 index 0000000..c32568d Binary files /dev/null and b/SandboxProject/Assets/111.glb differ diff --git a/SandboxProject/Assets/111.glb.bak b/SandboxProject/Assets/111.glb.bak new file mode 100644 index 0000000..b0773c6 Binary files /dev/null and b/SandboxProject/Assets/111.glb.bak differ diff --git a/SandboxProject/Assets/111.glb.bak.mcmeta b/SandboxProject/Assets/111.glb.bak.mcmeta new file mode 100644 index 0000000..8287e89 --- /dev/null +++ b/SandboxProject/Assets/111.glb.bak.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "asset", + "guid": "305e95e7-4f41-426b-8ea5-8ba4fc1a8724", + "importer_id": "BinaryImporter", + "package_path": "Assets/111.glb.bak.mcasset", + "source_hash": 17894180331051193223, + "source_path": "Assets/111.glb.bak" +} \ No newline at end of file diff --git a/SandboxProject/Assets/111.glb.bak2 b/SandboxProject/Assets/111.glb.bak2 new file mode 100644 index 0000000..8bd6513 Binary files /dev/null and b/SandboxProject/Assets/111.glb.bak2 differ diff --git a/SandboxProject/Assets/111.glb.bak2.mcmeta b/SandboxProject/Assets/111.glb.bak2.mcmeta new file mode 100644 index 0000000..6d648cc --- /dev/null +++ b/SandboxProject/Assets/111.glb.bak2.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "asset", + "guid": "b5c7953d-3806-4660-9596-043eaa9220a1", + "importer_id": "BinaryImporter", + "package_path": "Assets/111.glb.bak2.mcasset", + "source_hash": 16380358792779090604, + "source_path": "Assets/111.glb.bak2" +} \ No newline at end of file diff --git a/SandboxProject/Assets/111.glb.mcmeta b/SandboxProject/Assets/111.glb.mcmeta new file mode 100644 index 0000000..1d6705d --- /dev/null +++ b/SandboxProject/Assets/111.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "ee0fec65-bcc9-4efb-a5b4-871385648c23", + "importer_id": "GltfModelImporter", + "package_path": "Assets/111.glb.mcasset", + "source_hash": 11760661239691667159, + "source_path": "Assets/111.glb", + "sub_assets": [ + { + "guid": "ff7c19a0-ba4c-d02a-2111-835582811855", + "index": 0, + "name": "111", + "type": "mesh" + }, + { + "guid": "030a04c3-630d-affd-1c58-fc3bc8aa9f91", + "index": 0, + "name": "111_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/Cube.glb.mcasset b/SandboxProject/Assets/Models/Cube.glb.mcasset new file mode 100644 index 0000000..fc78a4d Binary files /dev/null and b/SandboxProject/Assets/Models/Cube.glb.mcasset differ diff --git a/SandboxProject/Assets/Models/Cube.glb.mcmeta b/SandboxProject/Assets/Models/Cube.glb.mcmeta index 2fce38a..6b234fd 100644 Binary files a/SandboxProject/Assets/Models/Cube.glb.mcmeta and b/SandboxProject/Assets/Models/Cube.glb.mcmeta differ diff --git a/SandboxProject/Assets/Models/Floor4.glb b/SandboxProject/Assets/Models/Floor4.glb new file mode 100644 index 0000000..4a204c9 Binary files /dev/null and b/SandboxProject/Assets/Models/Floor4.glb differ diff --git a/SandboxProject/Assets/Models/Floor4.glb.mcmeta b/SandboxProject/Assets/Models/Floor4.glb.mcmeta new file mode 100644 index 0000000..c93bade --- /dev/null +++ b/SandboxProject/Assets/Models/Floor4.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "7817e162-2dd7-4747-bdfb-93bb38a72ebb", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/Floor4.glb.mcasset", + "source_hash": 17521808281966876099, + "source_path": "Assets/Models/Floor4.glb", + "sub_assets": [ + { + "guid": "f0a46cf7-2e2c-d921-eb90-551705ce1b1e", + "index": 0, + "name": "Floor4", + "type": "mesh" + }, + { + "guid": "7424828f-d8de-69d9-8850-1cc4e467ba88", + "index": 0, + "name": "Floor4_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/JXB.glb b/SandboxProject/Assets/Models/JXB.glb new file mode 100644 index 0000000..0e9af22 Binary files /dev/null and b/SandboxProject/Assets/Models/JXB.glb differ diff --git a/SandboxProject/Assets/Models/JXB.glb.mcmeta b/SandboxProject/Assets/Models/JXB.glb.mcmeta new file mode 100644 index 0000000..3308abe --- /dev/null +++ b/SandboxProject/Assets/Models/JXB.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "f6a51ac3-fb03-42df-8836-9bc605b60b0c", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/JXB.glb.mcasset", + "source_hash": 9095552786760188715, + "source_path": "Assets/Models/JXB.glb", + "sub_assets": [ + { + "guid": "20593992-3c28-20dc-0734-2068a98c33dc", + "index": 0, + "name": "JXB", + "type": "mesh" + }, + { + "guid": "24102470-e146-19aa-cccb-65dc97d3c0db", + "index": 0, + "name": "JXB_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/RFF-37-3D.glb b/SandboxProject/Assets/Models/RFF-37-3D.glb new file mode 100644 index 0000000..9e1d9ff Binary files /dev/null and b/SandboxProject/Assets/Models/RFF-37-3D.glb differ diff --git a/SandboxProject/Assets/Models/RFF-37-3D.glb.mcmeta b/SandboxProject/Assets/Models/RFF-37-3D.glb.mcmeta new file mode 100644 index 0000000..60eeef0 --- /dev/null +++ b/SandboxProject/Assets/Models/RFF-37-3D.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "0998aab9-9952-4b49-8723-7a1dd5903e9e", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/RFF-37-3D.glb.mcasset", + "source_hash": 7543758070289905162, + "source_path": "Assets/Models/RFF-37-3D.glb", + "sub_assets": [ + { + "guid": "e642444d-d75f-96ad-fedb-d226fc4669fc", + "index": 0, + "name": "RFF-37-3D", + "type": "mesh" + }, + { + "guid": "22e350d5-7fa6-c46b-0533-914588f2d8a6", + "index": 0, + "name": "RFF-37-3D_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/box1.glb.mcasset b/SandboxProject/Assets/Models/box1.glb.mcasset new file mode 100644 index 0000000..220e9f4 Binary files /dev/null and b/SandboxProject/Assets/Models/box1.glb.mcasset differ diff --git a/SandboxProject/Assets/Models/box1.glb.mcmeta b/SandboxProject/Assets/Models/box1.glb.mcmeta index 668468a..baf4a12 100644 Binary files a/SandboxProject/Assets/Models/box1.glb.mcmeta and b/SandboxProject/Assets/Models/box1.glb.mcmeta differ diff --git a/SandboxProject/Assets/Models/ch_desk.glb b/SandboxProject/Assets/Models/ch_desk.glb new file mode 100644 index 0000000..f2c7295 Binary files /dev/null and b/SandboxProject/Assets/Models/ch_desk.glb differ diff --git a/SandboxProject/Assets/Models/ch_desk.glb.mcmeta b/SandboxProject/Assets/Models/ch_desk.glb.mcmeta new file mode 100644 index 0000000..cbafea0 --- /dev/null +++ b/SandboxProject/Assets/Models/ch_desk.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "719e2e9f-4445-48f5-bf2e-eb4bacf2cac5", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/ch_desk.glb.mcasset", + "source_hash": 2230133667640719958, + "source_path": "Assets/Models/ch_desk.glb", + "sub_assets": [ + { + "guid": "eefc1764-5c68-39f6-74f5-2d02ce94d07a", + "index": 0, + "name": "ch_desk", + "type": "mesh" + }, + { + "guid": "a29b1867-cf52-f37c-e545-39802041e061", + "index": 0, + "name": "ch_desk_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/cuite.glb b/SandboxProject/Assets/Models/cuite.glb new file mode 100644 index 0000000..c707435 Binary files /dev/null and b/SandboxProject/Assets/Models/cuite.glb differ diff --git a/SandboxProject/Assets/Models/cuite.glb.mcmeta b/SandboxProject/Assets/Models/cuite.glb.mcmeta new file mode 100644 index 0000000..543bab6 --- /dev/null +++ b/SandboxProject/Assets/Models/cuite.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "ad0a19f9-393b-4ed8-899c-d4e44505396f", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/cuite.glb.mcasset", + "source_hash": 16889646040474063424, + "source_path": "Assets/Models/cuite.glb", + "sub_assets": [ + { + "guid": "e46dfa30-317d-d3a3-bc8e-f4e2cbcb067d", + "index": 0, + "name": "cuite", + "type": "mesh" + }, + { + "guid": "e0590e30-51ba-e7cd-b784-c0a613b48da3", + "index": 0, + "name": "cuite_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/Models/haqijingzhu.glb b/SandboxProject/Assets/Models/haqijingzhu.glb new file mode 100644 index 0000000..452e3bd Binary files /dev/null and b/SandboxProject/Assets/Models/haqijingzhu.glb differ diff --git a/SandboxProject/Assets/Models/haqijingzhu.glb.mcmeta b/SandboxProject/Assets/Models/haqijingzhu.glb.mcmeta new file mode 100644 index 0000000..9537a9b --- /dev/null +++ b/SandboxProject/Assets/Models/haqijingzhu.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "9f9aa1f2-21ea-4b17-9da7-3713c32e11ee", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/haqijingzhu.glb.mcasset", + "source_hash": 8705570643519871155, + "source_path": "Assets/Models/haqijingzhu.glb", + "sub_assets": [ + { + "guid": "e0e7ad5c-3363-965c-37a7-7715359229a1", + "index": 0, + "name": "haqijingzhu", + "type": "mesh" + }, + { + "guid": "7c3bb866-d2e3-de15-c287-49e822dbec2b", + "index": 0, + "name": "haqijingzhu_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/RFF-37-3D.glb b/SandboxProject/Assets/RFF-37-3D.glb new file mode 100644 index 0000000..9e1d9ff Binary files /dev/null and b/SandboxProject/Assets/RFF-37-3D.glb differ diff --git a/SandboxProject/Assets/RFF-37-3D.glb.mcmeta b/SandboxProject/Assets/RFF-37-3D.glb.mcmeta new file mode 100644 index 0000000..4fd91ee --- /dev/null +++ b/SandboxProject/Assets/RFF-37-3D.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "54bd5826-8c6e-4b90-be92-d77180516bf6", + "importer_id": "GltfModelImporter", + "package_path": "Assets/RFF-37-3D.glb.mcasset", + "source_hash": 7543758070289905162, + "source_path": "Assets/RFF-37-3D.glb", + "sub_assets": [ + { + "guid": "bbb65c83-5b02-4945-f61d-657c8260dd61", + "index": 0, + "name": "RFF-37-3D", + "type": "mesh" + }, + { + "guid": "ef308213-3ea5-cb66-35a6-23bf086156eb", + "index": 0, + "name": "RFF-37-3D_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Assets/haqijingzhu.glb b/SandboxProject/Assets/haqijingzhu.glb new file mode 100644 index 0000000..452e3bd Binary files /dev/null and b/SandboxProject/Assets/haqijingzhu.glb differ diff --git a/SandboxProject/Assets/haqijingzhu.glb.mcmeta b/SandboxProject/Assets/haqijingzhu.glb.mcmeta new file mode 100644 index 0000000..058c3bf --- /dev/null +++ b/SandboxProject/Assets/haqijingzhu.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "f9c69ec5-0d65-450d-80f2-636393b1d608", + "importer_id": "GltfModelImporter", + "package_path": "Assets/haqijingzhu.glb.mcasset", + "source_hash": 8705570643519871155, + "source_path": "Assets/haqijingzhu.glb", + "sub_assets": [ + { + "guid": "61886497-7a91-4806-5681-54511c9ff052", + "index": 0, + "name": "haqijingzhu", + "type": "mesh" + }, + { + "guid": "5d2f7f6a-fa58-6d4a-ab5e-dfebc1789e24", + "index": 0, + "name": "haqijingzhu_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Scenes/Main.mcscene b/SandboxProject/Scenes/Main.mcscene.bak similarity index 100% rename from SandboxProject/Scenes/Main.mcscene rename to SandboxProject/Scenes/Main.mcscene.bak diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 4eb6972..fb4945b 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -2010,7 +2010,7 @@ void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char } const bool changed = editorContext.ExecuteSnapshotCommand( - addComponent ? "添加组件" : "移除组件", + addComponent ? "Add Component" : "移除组件", [&]() { bool anyChanged = false; for (const MetaCoreId selectedId : editorContext.GetSelectedObjectIds()) { @@ -2408,7 +2408,8 @@ public: const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService(); const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); - if (ImGui::BeginMenu("文件")) { + // 顶级菜单 1:项目 (对应 Project) + if (ImGui::BeginMenu("项目")) { if (ImGui::MenuItem("新建项目...")) { if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) { const auto& project = assetDatabaseService->GetProjectDescriptor(); @@ -2437,16 +2438,41 @@ public: if (ImGui::MenuItem("刷新 Asset 数据库", nullptr, false, assetDatabaseService != nullptr)) { MetaCoreHandleReloadAssets(editorContext); } + ImGui::Separator(); + + // 收纳 MetaCore 专属资源功能菜单项 + if (ImGui::BeginMenu("资源管理")) { + ImGui::MenuItem("导入资源", nullptr, false, false); + if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + (void)MetaCoreCreatePrefabFromSelection(editorContext); + } + ImGui::EndMenu(); + } + + // 复制 Infernux 占位项,对齐界面一致度 + ImGui::MenuItem("偏好设置", nullptr, false, false); + ImGui::MenuItem("构建设置", nullptr, false, false); + ImGui::MenuItem("物理层交互矩阵", nullptr, false, false); + ImGui::Separator(); if (ImGui::MenuItem("重置布局")) { editorContext.SetDockLayoutBuilt(false); } + + // 收纳原有的 Help 菜单 + if (ImGui::BeginMenu("帮助说明")) { + ImGui::MenuItem("MetaCore Editor", nullptr, false, false); + ImGui::MenuItem("当前运行模式:MetaCore 编辑器", nullptr, false, false); + ImGui::EndMenu(); + } + if (ImGui::MenuItem("退出")) { editorContext.GetWindow().RequestClose(); } ImGui::EndMenu(); } + // 新建项目弹窗中文化 if (ImGui::BeginPopupModal("MetaCoreCreateProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::InputText("项目目录", CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_)); ImGui::InputText("项目名称", CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_)); @@ -2462,6 +2488,7 @@ public: ImGui::EndPopup(); } + // 打开项目弹窗中文化 if (ImGui::BeginPopupModal("MetaCoreOpenProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::InputText("项目路径", OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_)); if (ImGui::Button("打开")) { @@ -2475,6 +2502,7 @@ public: ImGui::EndPopup(); } + // 顶级菜单 2:编辑 (对应 Edit) if (ImGui::BeginMenu("编辑")) { if (ImGui::MenuItem("撤销", "Ctrl+Z", false, editorContext.GetCommandService().CanUndo())) { MetaCoreHandleUndo(editorContext); @@ -2492,52 +2520,48 @@ public: if (ImGui::MenuItem("重命名", "F2", false, editorContext.GetActiveObjectId() != 0)) { editorContext.RequestRenameActiveObject(); } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("资源")) { - ImGui::MenuItem("导入资源", nullptr, false, false); - if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { - (void)MetaCoreCreatePrefabFromSelection(editorContext); - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("游戏对象")) { - if (ImGui::MenuItem("创建空对象")) { - (void)MetaCoreCreateObject(editorContext, "GameObject", false, std::nullopt); - } - if (ImGui::MenuItem("创建立方体")) { - (void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt); - } - if (ImGui::MenuItem("创建平面")) { - (void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt); - } ImGui::Separator(); - if (ImGui::MenuItem("应用当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { - (void)MetaCoreApplySelectedPrefabInstance(editorContext); + + // 收纳 MetaCore 专属游戏对象与组件创建逻辑 + if (ImGui::BeginMenu("创建游戏对象")) { + if (ImGui::MenuItem("创建空对象")) { + (void)MetaCoreCreateObject(editorContext, "GameObject", false, std::nullopt); + } + if (ImGui::MenuItem("创建立方体")) { + (void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt); + } + if (ImGui::MenuItem("创建平面")) { + (void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt); + } + ImGui::Separator(); + if (ImGui::MenuItem("应用当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + (void)MetaCoreApplySelectedPrefabInstance(editorContext); + } + if (ImGui::MenuItem("还原当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + (void)MetaCoreRevertSelectedPrefabInstance(editorContext); + } + if (ImGui::MenuItem("Break Prefab Instance", nullptr, false, editorContext.GetActiveObjectId() != 0)) { + (void)MetaCoreBreakSelectedPrefabInstance(editorContext); + } + ImGui::Separator(); + if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { + (void)MetaCoreDuplicateSelection(editorContext); + } + if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { + (void)MetaCoreDeleteSelection(editorContext); + } + ImGui::EndMenu(); } - if (ImGui::MenuItem("还原当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { - (void)MetaCoreRevertSelectedPrefabInstance(editorContext); - } - if (ImGui::MenuItem("Break Prefab Instance", nullptr, false, editorContext.GetActiveObjectId() != 0)) { - (void)MetaCoreBreakSelectedPrefabInstance(editorContext); - } - ImGui::Separator(); - if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { - (void)MetaCoreDuplicateSelection(editorContext); - } - if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { - (void)MetaCoreDeleteSelection(editorContext); + + if (ImGui::BeginMenu("添加组件")) { + ImGui::MenuItem("Add Component", nullptr, false, false); + ImGui::EndMenu(); } + ImGui::EndMenu(); } - if (ImGui::BeginMenu("组件")) { - ImGui::MenuItem("添加组件", nullptr, false, false); - ImGui::EndMenu(); - } - + // 顶级菜单 3:窗口 (对应 Window) if (ImGui::BeginMenu("窗口")) { for (const auto& panelProvider : editorContext.GetModuleRegistry().GetPanelProviders()) { bool& panelOpen = editorContext.GetModuleRegistry().AccessPanelOpenState(panelProvider->GetPanelId()); @@ -2545,12 +2569,6 @@ public: } ImGui::EndMenu(); } - - if (ImGui::BeginMenu("帮助")) { - ImGui::MenuItem("MetaCore Editor", nullptr, false, false); - ImGui::MenuItem("当前运行模式:MetaCore 编辑器", nullptr, false, false); - ImGui::EndMenu(); - } } private: @@ -2562,34 +2580,33 @@ private: class MetaCoreHierarchyPanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Hierarchy"; } - std::string GetPanelTitle() const override { return "层级"; } + std::string GetPanelTitle() const override { return "Hierarchy"; } bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext& editorContext) override { + // 全局资源选择状态 + static std::filesystem::path GProjectSelectedDirectory{}; + static MetaCoreAssetGuid GProjectSelectedAssetGuid{}; + static std::filesystem::path GProjectSelectedAssetPath{}; + static std::string GProjectSelectedAssetType{}; + static MetaCoreAssetStorageKind GProjectSelectedAssetStorageKind = MetaCoreAssetStorageKind::SourcePackage; + static MetaCoreAssetGuid GProjectSelectedGeneratedResourceGuid{}; + static std::string GProjectSelectedGeneratedResourceKind{}; + static std::string GProjectGeneratedResourceFilter{}; + static std::string GProjectGeneratedResourceKindFilter{}; + static std::string GProjectSelectedUiNodeId{}; -// Global Asset Selection State in BuiltinEditorModule -static std::filesystem::path GProjectSelectedDirectory{}; -static MetaCoreAssetGuid GProjectSelectedAssetGuid{}; -static std::filesystem::path GProjectSelectedAssetPath{}; -static std::string GProjectSelectedAssetType{}; -static MetaCoreAssetStorageKind GProjectSelectedAssetStorageKind = MetaCoreAssetStorageKind::SourcePackage; -static MetaCoreAssetGuid GProjectSelectedGeneratedResourceGuid{}; -static std::string GProjectSelectedGeneratedResourceKind{}; -static std::string GProjectGeneratedResourceFilter{}; -static std::string GProjectGeneratedResourceKindFilter{}; -static std::string GProjectSelectedUiNodeId{}; - -static std::filesystem::path GProjectPendingRenamePath{}; -static std::array GProjectAssetFilterBuffer{}; -static std::array GProjectFolderNameBuffer{}; -static std::array GProjectRenamePathBuffer{}; + static std::filesystem::path GProjectPendingRenamePath{}; + static std::array GProjectAssetFilterBuffer{}; + static std::array GProjectFolderNameBuffer{}; + static std::array GProjectRenamePathBuffer{}; const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService(); if (const MetaCoreId requestedRenameId = editorContext.ConsumeRenameRequestObjectId(); requestedRenameId != 0) { BeginRename(editorContext, requestedRenameId); } ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8)); - ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), " (S) "); + ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), "Scene: "); ImGui::SameLine(); const std::string sceneLabel = scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene() @@ -2609,7 +2626,6 @@ static std::array GProjectRenamePathBuffer{}; DrawRootDropTarget(editorContext); DrawWindowContextMenu(editorContext); - DrawRenamePopup(editorContext); } private: @@ -2620,6 +2636,34 @@ private: return; } + // 就地重命名交互逻辑 + if (RenameObjectId_ == objectId) { + if (OpenRenamePopup_) { + ImGui::SetKeyboardFocusHere(); + OpenRenamePopup_ = false; + } + ImGui::SetNextItemWidth(-1.0f); + const bool submittedByEnter = ImGui::InputText( + "##InlineRename", + RenameBuffer_.data(), + RenameBuffer_.size(), + ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue + ); + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + RenameObjectId_ = 0; + return; + } + if (submittedByEnter || ImGui::IsItemDeactivated()) { + std::string newName(RenameBuffer_.data()); + if (!newName.empty()) { + (void)MetaCoreRenameObject(editorContext, objectId, newName); + } + RenameObjectId_ = 0; + return; + } + return; + } + const std::vector childIds = scene.GetChildrenOf(objectId); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth; if (childIds.empty()) { @@ -2635,19 +2679,12 @@ private: gameObject.HasComponent() && !gameObject.GetComponent().SourceModelPath.empty(); - // 1:1 对齐 Infernux 装饰样式:模型实例冠以 * 前缀 (使用 ASCII 规避缺失字体导致的问号) + // 对齐 Infernux 装饰样式:模型实例以星号前缀标示 std::string hierarchyLabel = gameObject.GetName(); if (isModelInstance) { hierarchyLabel = "* " + hierarchyLabel; } - // 1:1 对齐选中背景色与模型实例的文字红强调色 - if (isSelected) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - } - bool pushedTextColor = false; if (isModelInstance) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(235.0F / 255.0F, 87.0F / 255.0F, 87.0F / 255.0F, 1.0F)); @@ -2665,10 +2702,6 @@ private: ImGui::PopStyleColor(1); } - if (isSelected) { - ImGui::PopStyleColor(3); - } - const bool leftClicked = ImGui::IsItemClicked(ImGuiMouseButton_Left); const bool rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right); if ((leftClicked || rightClicked) && !ImGui::IsItemToggledOpen()) { @@ -2697,7 +2730,7 @@ private: } MetaCoreId payloadId = objectId; ImGui::SetDragDropPayload("MC_HIERARCHY_OBJECT", &payloadId, sizeof(payloadId)); - ImGui::Text("拖拽 %zu 个对象", editorContext.GetSelectedObjectIds().size()); + ImGui::Text("Drag %zu objects", editorContext.GetSelectedObjectIds().size()); ImGui::EndDragDropSource(); } @@ -2729,13 +2762,13 @@ private: void DrawNodeContextMenu(MetaCoreEditorContext& editorContext, MetaCoreId objectId) { const MetaCoreGameObject gameObject = editorContext.GetScene().FindGameObject(objectId); - if (ImGui::MenuItem("创建空对象")) { + if (ImGui::MenuItem("Create Empty")) { (void)MetaCoreCreateObject(editorContext, "GameObject", false, objectId); } - if (ImGui::MenuItem("创建立方体")) { + if (ImGui::MenuItem("Create Cube")) { (void)MetaCoreCreateObject(editorContext, "Cube", true, objectId); } - if (ImGui::MenuItem("创建平面")) { + if (ImGui::MenuItem("Create Plane")) { (void)MetaCoreCreateObject(editorContext, "Plane", true, objectId); } ImGui::Separator(); @@ -2746,35 +2779,35 @@ private: const auto sameSourceModelObjects = FindSceneObjectsUsingSameSourceModel(editorContext, gameObject); const auto sameSourceNodeObjects = FindSceneObjectsUsingSameSourceNode(editorContext, gameObject); if (sourceAsset.has_value()) { - ImGui::TextDisabled("模型: %s", sourceAsset->RelativePath.filename().string().c_str()); + ImGui::TextDisabled("Model: %s", sourceAsset->RelativePath.filename().string().c_str()); } else { - ImGui::TextDisabled("模型: %s", gameObject.GetComponent().SourceModelPath.c_str()); + ImGui::TextDisabled("Model: %s", gameObject.GetComponent().SourceModelPath.c_str()); } - ImGui::TextDisabled("节点: %d", gameObject.GetComponent().ModelNodeIndex); - ImGui::TextDisabled("同源模型实例: %d", static_cast(sameSourceModelObjects.size())); - ImGui::TextDisabled("同源节点实例: %d", static_cast(sameSourceNodeObjects.size())); + ImGui::TextDisabled("Node: %d", gameObject.GetComponent().ModelNodeIndex); + ImGui::TextDisabled("Same Source Model Instances: %d", static_cast(sameSourceModelObjects.size())); + ImGui::TextDisabled("Same Source Node Instances: %d", static_cast(sameSourceNodeObjects.size())); ImGui::Separator(); - if (ImGui::MenuItem("打开源模型")) { + if (ImGui::MenuItem("Open Source Model")) { OpenSourceModelForGameObject(editorContext, gameObject); } - if (ImGui::MenuItem("选择源节点", nullptr, false, gameObject.GetComponent().ModelNodeIndex >= 0)) { + if (ImGui::MenuItem("Select Source Node", nullptr, false, gameObject.GetComponent().ModelNodeIndex >= 0)) { OpenSourceModelForGameObject(editorContext, gameObject); } - if (ImGui::MenuItem("选择同源模型实例", nullptr, false, !sameSourceModelObjects.empty())) { + if (ImGui::MenuItem("Select Same Source Model Instances", nullptr, false, !sameSourceModelObjects.empty())) { editorContext.SetSelection(sameSourceModelObjects, sameSourceModelObjects.front()); } - if (ImGui::MenuItem("选择同源节点实例", nullptr, false, !sameSourceNodeObjects.empty())) { + if (ImGui::MenuItem("Select Same Source Node Instances", nullptr, false, !sameSourceNodeObjects.empty())) { editorContext.SetSelection(sameSourceNodeObjects, sameSourceNodeObjects.front()); } ImGui::Separator(); } - if (ImGui::MenuItem("重命名", "F2", false, objectId != 0)) { + if (ImGui::MenuItem("Rename", "F2", false, objectId != 0)) { BeginRename(editorContext, objectId); } - if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { + if (ImGui::MenuItem("Duplicate", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { (void)MetaCoreDuplicateSelection(editorContext); } - if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { + if (ImGui::MenuItem("Delete", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { (void)MetaCoreDeleteSelection(editorContext); } } @@ -2784,23 +2817,23 @@ private: return; } - if (ImGui::MenuItem("创建空对象")) { + if (ImGui::MenuItem("Create Empty")) { (void)MetaCoreCreateObject(editorContext, "GameObject", false, 0); } - if (ImGui::MenuItem("创建立方体")) { + if (ImGui::MenuItem("Create Cube")) { (void)MetaCoreCreateObject(editorContext, "Cube", true, 0); } - if (ImGui::MenuItem("创建平面")) { + if (ImGui::MenuItem("Create Plane")) { (void)MetaCoreCreateObject(editorContext, "Plane", true, 0); } ImGui::Separator(); - if (ImGui::MenuItem("重命名", "F2", false, editorContext.GetActiveObjectId() != 0)) { + if (ImGui::MenuItem("Rename", "F2", false, editorContext.GetActiveObjectId() != 0)) { BeginRename(editorContext, editorContext.GetActiveObjectId()); } - if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { + if (ImGui::MenuItem("Duplicate", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) { (void)MetaCoreDuplicateSelection(editorContext); } - if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { + if (ImGui::MenuItem("Delete", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) { (void)MetaCoreDeleteSelection(editorContext); } @@ -2809,16 +2842,20 @@ private: void DrawRootDropTarget(MetaCoreEditorContext& editorContext) { ImGui::Separator(); - ImGui::TextDisabled("拖拽到下方设为根节点"); + ImGui::TextDisabled("Drag here to reparent to root"); const ImVec2 dropAreaMin = ImGui::GetCursorScreenPos(); const ImVec2 dropAreaSize = ImVec2(ImGui::GetContentRegionAvail().x, 24.0F); ImGui::InvisibleButton("MetaCoreHierarchyRootDrop", dropAreaSize); const ImVec2 dropAreaMax = ImVec2(dropAreaMin.x + dropAreaSize.x, dropAreaMin.y + dropAreaSize.y); + + // 绘制 Notion 虚线直角框 ImGui::GetWindowDrawList()->AddRect( dropAreaMin, dropAreaMax, IM_COL32(96, 106, 122, 180), - 2.0F + 0.0F, + 0, + 1.0F ); if (ImGui::BeginDragDropTarget()) { @@ -2843,53 +2880,11 @@ private: } RenameObjectId_ = objectId; - RenameBuffer_.fill('\0'); + RenameBuffer_.fill(0); std::snprintf(RenameBuffer_.data(), RenameBuffer_.size(), "%s", object.GetName().c_str()); OpenRenamePopup_ = true; } - void DrawRenamePopup(MetaCoreEditorContext& editorContext) { - if (RenameObjectId_ == 0) { - return; - } - - if (OpenRenamePopup_) { - ImGui::OpenPopup("重命名对象##MetaCoreHierarchy"); - OpenRenamePopup_ = false; - } - - if (!ImGui::BeginPopupModal("重命名对象##MetaCoreHierarchy", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - return; - } - - if (ImGui::IsWindowAppearing()) { - ImGui::SetKeyboardFocusHere(); - } - - const bool submittedByEnter = ImGui::InputText( - "名称", - RenameBuffer_.data(), - RenameBuffer_.size(), - ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue - ); - - bool closePopup = false; - if (submittedByEnter || ImGui::Button("确定", ImVec2(90.0F, 0.0F))) { - closePopup = MetaCoreRenameObject(editorContext, RenameObjectId_, RenameBuffer_.data()); - } - ImGui::SameLine(); - if (ImGui::Button("取消", ImVec2(90.0F, 0.0F))) { - closePopup = true; - } - - if (closePopup) { - RenameObjectId_ = 0; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - MetaCoreId RenameObjectId_ = 0; std::array RenameBuffer_{}; bool OpenRenamePopup_ = false; @@ -2898,7 +2893,7 @@ private: class MetaCoreScenePanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Scene"; } - std::string GetPanelTitle() const override { return "场景"; } + std::string GetPanelTitle() const override { return "Scene"; } bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext&) override { @@ -4530,18 +4525,8 @@ void DrawDirectoryTree( if (!MatchesAssetFilter(label) && !HasVisibleChildren(assetDatabaseService, dir)) return; const bool isSelectedDirectory = editorContext.GetSelectedProjectDirectory() == dir; - if (isSelectedDirectory) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - } - const bool nodeOpened = ImGui::TreeNodeEx(dir.generic_string().c_str(), ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth | (isSelectedDirectory ? ImGuiTreeNodeFlags_Selected : 0), "%s", label.c_str()); - if (isSelectedDirectory) { - ImGui::PopStyleColor(3); - } - if (nodeOpened) { if (ImGui::IsItemClicked()) editorContext.SetSelectedProjectDirectory(dir); for (const auto& d : assetDatabaseService.GetDirectoriesUnder(dir)) { @@ -4556,7 +4541,7 @@ void DrawDirectoryTree( class MetaCoreProjectPanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Project"; } - std::string GetPanelTitle() const override { return "项目"; } + std::string GetPanelTitle() const override { return "Project"; } bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext& editorContext) override { @@ -4569,8 +4554,8 @@ public: bool openRenamePopup = false; ImGui::TextDisabled("%s | %s", assetDatabaseService->GetProjectDescriptor().Name.c_str(), assetDatabaseService->GetProjectDescriptor().RootPath.string().c_str()); - if (ImGui::Button("刷新")) { MetaCoreHandleReloadAssets(editorContext); assetDatabaseService->Refresh(); } - ImGui::InputTextWithHint("##Filter", "过滤资源...", AssetFilterBuffer_.data(), AssetFilterBuffer_.size()); + + ImGui::InputTextWithHint("##Filter", "Filter Assets...", AssetFilterBuffer_.data(), AssetFilterBuffer_.size()); ImGui::Columns(2, "ProjectCols", true); DrawDirectoryTree(editorContext, *assetDatabaseService, "Assets", "Assets"); @@ -4584,7 +4569,7 @@ public: // 1. 绘制面包屑导航 (Breadcrumbs) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 2)); ImGui::AlignTextToFramePadding(); - ImGui::TextDisabled("路径:"); + ImGui::TextDisabled("Path:"); ImGui::SameLine(); std::vector pathSegments; @@ -4622,7 +4607,7 @@ public: ImGui::PushItemWidth(100.0f); ImGui::SliderFloat("##IconSize", &IconSize_, 16.0f, 128.0f, ""); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("图标大小: %.0f px", IconSize_); + ImGui::SetTooltip("Icon Size: %.0f px", IconSize_); } ImGui::PopItemWidth(); ImGui::PopStyleVar(); @@ -4684,17 +4669,8 @@ public: isSelected = (editorContext.GetSelectedAsset().Guid == item.Guid); } - if (isSelected) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - } - ImGui::PushID(item.IsDirectory ? item.Path.string().c_str() : item.Guid.ToString().c_str()); const bool clicked = ImGui::Selectable(labelText.c_str(), isSelected); - if (isSelected) { - ImGui::PopStyleColor(3); - } // 2. 用 ImDrawList 在该行的左侧空白处绘制 10px 精美矢量微型图标 (避免缺失显示为问号) ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -4707,8 +4683,9 @@ public: float h = miniSize * 0.75f; ImVec2 topLeft(miniIconCenter.x - w * 0.5f, miniIconCenter.y - h * 0.5f + h * 0.1f); ImVec2 bottomRight(miniIconCenter.x + w * 0.5f, miniIconCenter.y + h * 0.5f); - drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(230, 180, 45, 255), 1.0f); - drawList->AddRectFilled(ImVec2(topLeft.x, topLeft.y - h * 0.2f), ImVec2(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f), IM_COL32(190, 145, 30, 255), 0.5f); + // 替换亮黄色为 Notion 冷灰色 + drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(110, 110, 110, 255), 1.0f); + drawList->AddRectFilled(ImVec2(topLeft.x, topLeft.y - h * 0.2f), ImVec2(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f), IM_COL32(80, 80, 80, 255), 0.5f); } else if (item.Type == "material") { drawList->AddCircleFilled(miniIconCenter, miniSize * 0.45f, IM_COL32(140, 80, 210, 255), 12); ImVec2 highlightCenter(miniIconCenter.x - miniSize * 0.15f, miniIconCenter.y - miniSize * 0.15f); @@ -4847,15 +4824,7 @@ public: const std::string subId = MetaCoreBuildGeneratedResourceSubId(generatedEntry.Kind, generatedEntry.AssetGuid); const bool generatedSelected = editorContext.GetSelectedAsset().Guid == a.Guid && editorContext.GetSelectedAssetSubId() == subId; const std::string generatedLabel = "↳ " + generatedEntry.Name + " [" + generatedEntry.SecondaryText + "]"; - if (generatedSelected) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F)); - } const bool generatedClicked = ImGui::Selectable(generatedLabel.c_str(), generatedSelected); - if (generatedSelected) { - ImGui::PopStyleColor(3); - } if (generatedClicked) { editorContext.SelectAsset(MetaCoreSelectedAssetState{ a.Guid, @@ -4903,9 +4872,9 @@ public: ImGui::SetCursorPos(startPos); if (isSelected) { - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 0.40F)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 0.60F)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 0.80F)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.200F, 0.200F, 0.200F, 0.50F)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.220F, 0.165F, 0.165F, 0.60F)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.270F, 0.180F, 0.180F, 0.80F)); } else { ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.08f)); @@ -4922,20 +4891,20 @@ public: float largeSize = IconSize_; if (item.IsDirectory) { - // 绘制带有浅黄色前盖和深黄色后盖层叠效果的双矩形立体文件夹 + // 绘制带有浅灰色前盖和深灰色后盖层叠效果的双矩形立体文件夹 float w = largeSize * 0.85f; float h = largeSize * 0.65f; ImVec2 topLeft(gridIconCenter.x - w * 0.5f, gridIconCenter.y - h * 0.5f + h * 0.1f); ImVec2 bottomRight(gridIconCenter.x + w * 0.5f, gridIconCenter.y + h * 0.5f); - // 绘制文件夹后背板 (略暗) - drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(190, 145, 30, 255), 3.0f); + // 绘制文件夹后背板 (略暗,改冷灰) + drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(80, 80, 80, 255), 3.0f); // 绘制文件夹上方标签页 ImVec2 tabTopLeft(topLeft.x, topLeft.y - h * 0.2f); ImVec2 tabBottomRight(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f); - drawList->AddRectFilled(tabTopLeft, tabBottomRight, IM_COL32(190, 145, 30, 255), 2.0f); - // 绘制文件夹前挡板 (略亮) + drawList->AddRectFilled(tabTopLeft, tabBottomRight, IM_COL32(80, 80, 80, 255), 2.0f); + // 绘制文件夹前挡板 (略亮,改冷灰) ImVec2 frontTopLeft(topLeft.x, topLeft.y + h * 0.15f); - drawList->AddRectFilled(frontTopLeft, bottomRight, IM_COL32(235, 185, 45, 255), 3.0f); + drawList->AddRectFilled(frontTopLeft, bottomRight, IM_COL32(110, 110, 110, 255), 3.0f); } else if (item.Type == "material") { // 绘制具有左上角高光球体感的紫色材质圆球 float radius = largeSize * 0.4f; @@ -5145,19 +5114,19 @@ public: ImGui::Columns(1); if (openRenamePopup) { - ImGui::OpenPopup("重命名资源"); + ImGui::OpenPopup("Rename Asset"); } if (ImGui::BeginPopupContextWindow("ProjectBlankContextMenu", ImGuiPopupFlags_MouseButtonRight)) { - if (ImGui::MenuItem("刷新")) { + if (ImGui::MenuItem("Refresh")) { MetaCoreHandleReloadAssets(editorContext); assetDatabaseService->Refresh(); } - if (ImGui::MenuItem("新建文件夹")) { + if (ImGui::MenuItem("New Folder")) { std::snprintf(NewFolderBuffer_.data(), NewFolderBuffer_.size(), "NewFolder"); - ImGui::OpenPopup("新建文件夹"); + ImGui::OpenPopup("New Folder"); } - if (ImGui::MenuItem("新建材质")) { + if (ImGui::MenuItem("New Material")) { CreateNewMaterial(editorContext, *assetDatabaseService); } if (ImGui::MenuItem("New UI Document")) { @@ -5185,10 +5154,10 @@ public: ImGui::EndPopup(); } - if (ImGui::BeginPopupModal("重命名资源", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("请输入新名称:"); + if (ImGui::BeginPopupModal("Rename Asset", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Enter new name:"); ImGui::InputText("##NewName", RenameBuffer_.data(), RenameBuffer_.size()); - if (ImGui::Button("确定", ImVec2(120, 0))) { + if (ImGui::Button("OK", ImVec2(120, 0))) { std::string newName(RenameBuffer_.data()); if (!newName.empty()) { (void)assetDatabaseService->RenamePath(PendingRenamePath_, newName); @@ -5196,16 +5165,16 @@ public: ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("取消", ImVec2(120, 0))) { + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } - if (ImGui::BeginPopupModal("新建文件夹", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("请输入文件夹名称:"); + if (ImGui::BeginPopupModal("New Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Enter folder name:"); ImGui::InputText("##FolderName", NewFolderBuffer_.data(), NewFolderBuffer_.size()); - if (ImGui::Button("确定", ImVec2(120, 0))) { + if (ImGui::Button("OK", ImVec2(120, 0))) { std::string folderName(NewFolderBuffer_.data()); if (!folderName.empty()) { std::filesystem::path newDirPath = assetDatabaseService->GetProjectDescriptor().RootPath / selectedDirectory / folderName; @@ -5216,7 +5185,7 @@ public: ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("取消", ImVec2(120, 0))) { + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -5227,7 +5196,7 @@ public: class MetaCoreConsolePanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Console"; } - std::string GetPanelTitle() const override { return "控制台"; } + std::string GetPanelTitle() const override { return "Console"; } bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext& editorContext) override { @@ -5290,13 +5259,15 @@ public: ImGui::Separator(); // 2. 日志列表区域 - // 为下方详细文本保留 85px 高度 - float availHeight = ImGui::GetContentRegionAvail().y - 85.0f; - if (availHeight < 50.0f) { - availHeight = 50.0f; + static float ConsoleDetailHeight = 100.0f; + float totalHeight = ImGui::GetContentRegionAvail().y; + float splitterHeight = 4.0f; + float listHeight = totalHeight - ConsoleDetailHeight - splitterHeight; + if (listHeight < 50.0f) { + listHeight = 50.0f; } - if (ImGui::BeginChild("LogList", ImVec2(0, availHeight), true)) { + if (ImGui::BeginChild("LogList", ImVec2(0, listHeight), true)) { struct DisplayEntry { size_t OriginalIndex = 0; MetaCoreLogLevel Level = MetaCoreLogLevel::Info; @@ -5394,9 +5365,22 @@ public: } ImGui::EndChild(); + // 可拖动高度调节栏 Splitter + ImGui::Separator(); + ImGui::InvisibleButton("##ConsoleSplitter", ImVec2(-1, splitterHeight)); + if (ImGui::IsItemActive()) { + float dy = ImGui::GetMouseDragDelta(0).y; + if (std::abs(dy) > 0.5f) { + ConsoleDetailHeight = std::clamp(ConsoleDetailHeight - dy, 40.0f, totalHeight - 60.0f); + ImGui::ResetMouseDragDelta(0); + } + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } else if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } ImGui::Separator(); - // 3. 详细展示区 + // 3. 详细展示区 (只读 InputTextMultiline 支持 Ctrl+C 复制) if (ImGui::BeginChild("LogDetail", ImVec2(0, 0), true)) { if (ConsoleSelectedEntryIdx_ >= 0 && ConsoleSelectedEntryIdx_ < static_cast(logService.GetEntries().size())) { const auto& entry = logService.GetEntries()[ConsoleSelectedEntryIdx_]; @@ -5407,12 +5391,24 @@ public: txtColor = ImVec4(0.95f, 0.35f, 0.30f, 1.0f); } - ImGui::TextColored(txtColor, "[%s]", entry.Category.c_str()); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.85f, 0.85f, 1.0f)); - ImGui::TextWrapped("%s", entry.Message.c_str()); - ImGui::PopStyleColor(); + std::string logDetailText = "[" + entry.Category + "] " + entry.Message; + ImGui::PushStyleColor(ImGuiCol_Text, txtColor); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + + ImGui::InputTextMultiline( + "##ConsoleDetailText", + const_cast(logDetailText.c_str()), + logDetailText.size() + 1, + ImVec2(-1, -1), + ImGuiInputTextFlags_ReadOnly + ); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); } else { - ImGui::TextDisabled("选择任意日志项以查看详细信息。"); + ImGui::TextDisabled("Select a log entry to view details."); } } ImGui::EndChild(); @@ -6131,10 +6127,7 @@ public: ImGui::Text("Platform"); if (ImGui::BeginChild("PlatformList", ImVec2(0, 180.0f), true)) { - // Unity 风格的高亮单选列表 - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173f, 0.365f, 0.529f, 0.8f)); ImGui::Selectable("💻 PC, Mac & Linux", true); - ImGui::PopStyleColor(); ImGui::Selectable("📱 Android", false); ImGui::Selectable("🍏 iOS", false); @@ -6644,7 +6637,7 @@ bool MetaCoreTransformInspectorDrawer::GScaleLocked = true; class MetaCoreInspectorPanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Inspector"; } - std::string GetPanelTitle() const override { return "检查器"; } + std::string GetPanelTitle() const override { return "Inspector"; } bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext& editorContext) override { @@ -6660,14 +6653,14 @@ public: MetaCoreDrawAssetInspector(editorContext, *assetDatabaseService, *packageService, *reflectionRegistry); } } else { - ImGui::TextUnformatted("当前没有选中对象。"); + ImGui::TextUnformatted("No object selected."); } return; } MetaCoreGameObject selectedObject = editorContext.GetSelectedGameObject(); if (!selectedObject) { - ImGui::TextUnformatted("当前没有可编辑的主对象。"); + ImGui::TextUnformatted("No active object editable."); return; } @@ -6702,7 +6695,7 @@ public: if (!hasActiveProp) { defaultActiveState = activeState; } - (void)editorContext.ExecuteSnapshotCommand("修改对象激活状态", [&]() { + (void)editorContext.ExecuteSnapshotCommand("Modify Active State", [&]() { bool anyChanged = false; for (const MetaCoreId selectedId : editorContext.GetSelectedObjectIds()) { MetaCoreGameObject obj = editorContext.GetScene().FindGameObject(selectedId); @@ -6779,7 +6772,7 @@ public: ImGui::SameLine(); // 扁平化名称输入框样式,移除突兀的蓝色边框,并与 Static 控件完美对齐,使用全局 Infernux Theme 配色 - float staticControlWidth = 85.0F; // "静态的" 文字及 checkbox 的总估算宽度 + float staticControlWidth = 85.0F; // "Static" 文字及 checkbox 的总估算宽度 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.165F, 0.165F, 0.165F, 1.0F)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); @@ -6794,7 +6787,7 @@ public: ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - staticControlWidth); static bool isStatic = false; - ImGui::TextDisabled("静态的"); ImGui::SameLine(); + ImGui::TextDisabled("Static"); ImGui::SameLine(); ImGui::Checkbox("##Static", &isStatic); // Tag and Layer @@ -6810,12 +6803,12 @@ public: ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0F, 0.5F)); // 文本左对齐 - ImGui::Text("标签"); ImGui::SameLine(); + ImGui::Text("Tag"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); if (ImGui::Button("Untagged ▾##Tag", ImVec2(-1, 0))) { /* Menu */ } ImGui::NextColumn(); - ImGui::Text("图层"); ImGui::SameLine(); + ImGui::Text("Layer"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); if (ImGui::Button("Default ▾##Layer", ImVec2(-1, 0))) { /* Menu */ } diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 9aec10c..8f47a17 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -478,66 +478,96 @@ void MetaCoreApplyEditorStyle() { style.WindowBorderSize = 1.0F; style.ChildBorderSize = 1.0F; style.PopupBorderSize = 1.0F; - style.FrameBorderSize = 0.0F; - style.TabBorderSize = 1.0F; + style.FrameBorderSize = 1.0F; + style.TabBorderSize = 0.0F; + style.TabBarBorderSize = 1.0F; - style.WindowPadding = ImVec2(4.0F, 4.0F); - style.FramePadding = ImVec2(4.0F, 2.0F); - style.ItemSpacing = ImVec2(4.0F, 4.0F); - style.ItemInnerSpacing = ImVec2(4.0F, 4.0F); + style.WindowPadding = ImVec2(10.0F, 10.0F); + style.FramePadding = ImVec2(8.0F, 3.0F); + style.CellPadding = ImVec2(4.0F, 4.0F); + style.ItemSpacing = ImVec2(8.0F, 6.0F); + style.ItemInnerSpacing = ImVec2(6.0F, 4.0F); + style.IndentSpacing = 18.0F; + style.ScrollbarSize = 8.0F; + style.GrabMinSize = 6.0F; - // 1:1 对齐 Infernux Unity 风格暗色调与暗红高亮主题 - style.Colors[ImGuiCol_Text] = ImVec4(0.84F, 0.84F, 0.84F, 1.0F); // 主文本色 - style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.40F, 0.40F, 0.40F, 1.0F); // 禁用文本色 - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); // 窗口背景 (Unity #383838) - style.Colors[ImGuiCol_ChildBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F); // 子窗口背景 (透明) - style.Colors[ImGuiCol_PopupBg] = ImVec4(0.24F, 0.24F, 0.24F, 0.96F); // 弹出菜单背景 (Unity #3E3E3E) - style.Colors[ImGuiCol_Border] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 边框颜色 (Unity #1A1A1A) - style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F); // 边框阴影 (无) - style.Colors[ImGuiCol_FrameBg] = ImVec4(0.165F, 0.165F, 0.165F, 1.0F); // 输入框/滑动条背景 (Unity #2A2A2A) - style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20F, 0.20F, 0.20F, 1.0F); // 输入框悬停 - style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.24F, 0.17F, 0.17F, 1.0F); // 输入框激活 (带微弱红调) - style.Colors[ImGuiCol_TitleBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 标题栏背景 - style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 活动标题栏背景 - style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.16F, 0.16F, 0.16F, 0.5F); // 折叠标题栏背景 - style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 菜单栏背景 - style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.13F, 0.13F, 0.13F, 1.0F); // 滚动条背景 - style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 滚动条滑块 (Unity #404040) - style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30F, 0.25F, 0.25F, 1.0F); // 滚动条悬停 (微红) - style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 滚动条激活 - style.Colors[ImGuiCol_CheckMark] = ImVec4(0.922F, 0.341F, 0.341F, 1.0F); // 勾选标记 (强调红 #EB5757) - style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 滑动条滑块 - style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 滑动条激活 - style.Colors[ImGuiCol_Button] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 普通按钮背景 - style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.30F, 0.25F, 0.25F, 1.0F); // 按钮悬停 (微红调) - style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 按钮激活 (深红调) - style.Colors[ImGuiCol_Header] = ImVec4(0.235F, 0.235F, 0.235F, 1.0F); // 折叠头背景 (Unity #3C3C3C) - style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); // 悬停 (微红调) - style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.32F, 0.25F, 0.25F, 1.0F); // 激活 (深红调) - style.Colors[ImGuiCol_Separator] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 分割线 (同边框) - style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.35F, 0.25F, 0.25F, 0.6F); // 悬停分割线 (红暖色) - style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.40F, 0.28F, 0.28F, 0.8F); // 激活分割线 (深红) - style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.16F, 0.16F, 0.16F, 0.5F); // 调整手柄 - style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.28F, 0.24F, 0.24F, 0.6F); - style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.32F, 0.25F, 0.25F, 0.9F); - style.Colors[ImGuiCol_Tab] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F); // 标签页未选中 (暗灰色) - style.Colors[ImGuiCol_TabHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); // 标签悬停 (微红调) - style.Colors[ImGuiCol_TabActive] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); // 活动标签 (同窗口底色) - style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F); - style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); - style.Colors[ImGuiCol_DockingPreview] = ImVec4(0.173F, 0.365F, 0.529F, 0.7F); // 停靠预览区 (Unity 蓝色半透明) - style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F); - style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 表格头背景 - style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 强边框 - style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.15F, 0.15F, 0.15F, 1.0F); // 弱边框 - style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F); - style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00F, 1.00F, 1.00F, 0.03F); // 斑马纹交替底色 - style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.173F, 0.365F, 0.529F, 1.0F); // 选中文本背景 (Unity 经典选色 #2C5D87) - style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.173F, 0.365F, 0.529F, 0.9F); // 拖放目标高亮 (带选蓝) - style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.922F, 0.341F, 0.341F, 1.0F); // 导航高亮 - style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00F, 1.00F, 1.00F, 0.7F); - style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80F, 0.80F, 0.80F, 0.2F); - style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.6F); // 模态遮罩 + // Anti-aliasing + style.AntiAliasedLines = true; + style.AntiAliasedFill = true; + + // 1:1 对齐 Infernux Notion 风格暗色调与亮红高亮主题 + constexpr float AR = 0.922F; // #EB5757 Accent color R + constexpr float AG = 0.341F; // #EB5757 Accent color G + constexpr float AB = 0.341F; // #EB5757 Accent color B + + style.Colors[ImGuiCol_Text] = ImVec4(0.812F, 0.812F, 0.812F, 1.00F); // #CFCFCF + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.333F, 0.333F, 0.333F, 1.00F); // #555555 + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.200F, 0.200F, 0.200F, 0.60F); // #333333 + + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); // #191919 + style.Colors[ImGuiCol_ChildBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020 + style.Colors[ImGuiCol_PopupBg] = ImVec4(0.125F, 0.125F, 0.125F, 0.98F); // #202020 + style.Colors[ImGuiCol_FrameBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020 + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.240F, 0.170F, 0.170F, 1.00F); // 激活时偏红 + + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.098F, 0.098F, 0.098F, 0.75F); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); + + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.098F, 0.098F, 0.098F, 0.00F); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.333F, 0.333F, 0.333F, 1.00F); + style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.439F, 0.439F, 0.439F, 1.00F); + + style.Colors[ImGuiCol_CheckMark] = ImVec4(AR, AG, AB, 1.00F); // #EB5757 + style.Colors[ImGuiCol_SliderGrab] = ImVec4(AR, AG, AB, 0.88F); // #EB5757 + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(AR, AG, AB, 1.00F); // #EB5757 + style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F); + + style.Colors[ImGuiCol_Button] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.220F, 0.165F, 0.165F, 1.00F); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.270F, 0.180F, 0.180F, 1.00F); + + style.Colors[ImGuiCol_Header] = ImVec4(0.200F, 0.200F, 0.200F, 1.00F); // #333333 + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.200F, 0.160F, 0.160F, 1.00F); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.240F, 0.170F, 0.170F, 1.00F); + + style.Colors[ImGuiCol_Border] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F); + style.Colors[ImGuiCol_Separator] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F); + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(AR, AG, AB, 0.60F); // #EB5757 + style.Colors[ImGuiCol_SeparatorActive] = ImVec4(AR, AG, AB, 0.80F); // #EB5757 + + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(AR, AG, AB, 0.30F); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(AR, AG, AB, 0.50F); + + style.Colors[ImGuiCol_Tab] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); // #191919 + style.Colors[ImGuiCol_TabHovered] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A + style.Colors[ImGuiCol_TabActive] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020 + style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); + style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); + +#ifdef ImGuiCol_TabSelectedOverline + style.Colors[ImGuiCol_TabSelectedOverline] = ImVec4(AR, AG, AB, 1.00F); // #EB5757 +#endif +#ifdef ImGuiCol_TabDimmedSelectedOverline + style.Colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(AR, AG, AB, 0.60F); +#endif + + style.Colors[ImGuiCol_DockingPreview] = ImVec4(AR, AG, AB, 0.25F); // #EB5757 + style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.060F, 0.060F, 0.060F, 1.00F); + + style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); + style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F); + style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.149F, 0.149F, 0.149F, 1.00F); + style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F); + style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00F, 1.00F, 1.00F, 0.02F); + + style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00F, 1.00F, 1.00F, 1.00F); + style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.56F); // 优化 ImGuizmo 属性,使其看起来更高端并移除了舱口剖面线(虚线黑影) ImGuizmo::Style& gizmoStyle = ImGuizmo::GetStyle(); @@ -1009,9 +1039,9 @@ void MetaCoreEditorApp::DrawEditorFrame() { DrawSceneViewWindow(); DrawGameViewWindow(); - DrawPlaceholderDockWindow("UI Editor###ui_editor", "UI Editor"); - DrawPlaceholderDockWindow("Anim Clip 2D###animclip2d_editor", "Animation Clip 2D"); - DrawPlaceholderDockWindow("Anim FSM###animfsm_editor", "Animation FSM"); + DrawPlaceholderDockWindow("UI编辑器###ui_editor", "UI编辑器"); + DrawPlaceholderDockWindow("2D动画片段编辑器###animclip2d_editor", "2D动画片段编辑器"); + DrawPlaceholderDockWindow("动画状态机编辑器###animfsm_editor", "动画状态机编辑器"); ApplyPendingDockTabSelections(); DrawEditorStatusBar(); } @@ -1024,31 +1054,66 @@ void MetaCoreEditorApp::DrawEditorToolbar() { ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 4.0F)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0F, 4.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0F, 4.0F)); // TOOLBAR_WIN_PAD + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F)); // TOOLBAR_FRAME_PAD + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0F, 4.0F)); // TOOLBAR_ITEM_SPC + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0F); // TOOLBAR_FRAME_RND + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); // TOOLBAR_FRAME_BRD + if (ImGui::Begin("Toolbar###toolbar", nullptr, toolbarWindowFlags)) { - auto drawButton = [](const char* label, bool selected, bool enabled = true) { - if (selected) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20F, 0.34F, 0.58F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25F, 0.40F, 0.68F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.29F, 0.50F, 1.0F)); - } else if (!enabled) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.16F, 0.16F, 1.0F)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.45F, 0.45F, 0.45F, 1.0F)); - } - const bool clicked = enabled && ImGui::Button(label, ImVec2(0.0F, 24.0F)); - if (selected) { - ImGui::PopStyleColor(3); - } else if (!enabled) { - ImGui::PopStyleColor(4); + enum ButtonStyle { + Idle = 0, + PlayActive, + PauseActive, + Disabled + }; + + auto drawButton = [](const char* label, ButtonStyle style) { + ImVec4 baseColor; + ImVec4 hoverColor; + ImVec4 activeColor; + ImVec4 textColor = ImVec4(0.812F, 0.812F, 0.812F, 1.00F); + bool enabled = true; + + switch (style) { + case PlayActive: + baseColor = ImVec4(0.20F, 0.45F, 0.30F, 1.00F); // PLAY_ACTIVE + hoverColor = ImVec4(0.26F, 0.51F, 0.36F, 1.00F); + activeColor = ImVec4(0.32F, 0.57F, 0.42F, 1.00F); + break; + case PauseActive: + baseColor = ImVec4(0.50F, 0.40F, 0.15F, 1.00F); // PAUSE_ACTIVE + hoverColor = ImVec4(0.56F, 0.46F, 0.21F, 1.00F); + activeColor = ImVec4(0.62F, 0.52F, 0.27F, 1.00F); + break; + case Disabled: + baseColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F); // BTN_DISABLED + hoverColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F); + activeColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F); + textColor = ImVec4(0.40F, 0.40F, 0.40F, 1.00F); + enabled = false; + break; + case Idle: + default: + baseColor = ImVec4(0.18F, 0.18F, 0.18F, 1.00F); // BTN_IDLE + hoverColor = ImVec4(0.24F, 0.24F, 0.24F, 1.00F); + activeColor = ImVec4(0.30F, 0.30F, 0.30F, 1.00F); + break; } + + ImGui::PushStyleColor(ImGuiCol_Button, baseColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoverColor); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor); + ImGui::PushStyleColor(ImGuiCol_Text, textColor); + + const bool clicked = enabled && ImGui::Button(label, ImVec2(52.0F, 24.0F)); + + ImGui::PopStyleColor(4); return clicked; }; const float windowWidth = ImGui::GetWindowWidth(); - const float playControlsWidth = 300.0F; + const float playControlsWidth = 160.0F; // 52 * 3 + 2 * 2 = 160 ImGui::SetCursorPosX(std::max(8.0F, (windowWidth - playControlsWidth) * 0.5F)); if (const auto playModeService = ModuleRegistry_.ResolveService(); @@ -1056,109 +1121,170 @@ void MetaCoreEditorApp::DrawEditorToolbar() { const MetaCorePlayModeState playState = playModeService->GetState(); const bool isPlaying = playState == MetaCorePlayModeState::Playing || playState == MetaCorePlayModeState::Paused; const bool isPaused = playState == MetaCorePlayModeState::Paused; - if (playState == MetaCorePlayModeState::Edit) { - if (drawButton("Play", false)) { - (void)playModeService->EnterPlayMode(*EditorContext_); - } - } else { - if (drawButton("Stop", true)) { + + // 1. Play / Stop + if (isPlaying) { + if (drawButton("停止", PlayActive)) { (void)playModeService->ExitPlayMode(*EditorContext_); } + } else { + if (drawButton("播放", Idle)) { + (void)playModeService->EnterPlayMode(*EditorContext_); + } } - ImGui::SameLine(0.0F, 4.0F); - if (isPaused) { - if (drawButton("Resume", true)) { + + ImGui::SameLine(0.0F, 2.0F); + + // 2. Pause / Resume + if (!isPlaying) { + (void)drawButton("暂停", Disabled); + } else if (isPaused) { + if (drawButton("继续", PauseActive)) { (void)playModeService->ResumePlayMode(*EditorContext_); } - } else if (drawButton("Pause", false, isPlaying)) { - (void)playModeService->PausePlayMode(*EditorContext_); + } else { + if (drawButton("暂停", Idle)) { + (void)playModeService->PausePlayMode(*EditorContext_); + } } - ImGui::SameLine(0.0F, 4.0F); - if (drawButton("Step", false, isPaused)) { - (void)playModeService->StepPlayMode(*EditorContext_, 1.0F / 60.0F); + + ImGui::SameLine(0.0F, 2.0F); + + // 3. Step + if (isPaused) { + if (drawButton("步进", Idle)) { + (void)playModeService->StepPlayMode(*EditorContext_, 1.0F / 60.0F); + } + } else { + (void)drawButton("步进", Disabled); } + if (isPlaying) { ImGui::SameLine(0.0F, 8.0F); - ImGui::Text("Time %.2fs", playModeService->GetElapsedPlayTimeSeconds()); + ImGui::Text("已运行 %.2fs", playModeService->GetElapsedPlayTimeSeconds()); } } else { - (void)drawButton("Play", false, false); - ImGui::SameLine(0.0F, 4.0F); - (void)drawButton("Pause", false, false); - ImGui::SameLine(0.0F, 4.0F); - (void)drawButton("Step", false, false); + (void)drawButton("播放", Disabled); + ImGui::SameLine(0.0F, 2.0F); + (void)drawButton("暂停", Disabled); + ImGui::SameLine(0.0F, 2.0F); + (void)drawButton("步进", Disabled); } - constexpr float rightControlsWidth = 180.0F; - ImGui::SameLine(std::max(8.0F, windowWidth - rightControlsWidth)); - if (ImGui::Button("Gizmos", ImVec2(78.0F, 24.0F))) { + // 右对齐下拉菜单 + const float rightX = windowWidth - 160.0F; + if (rightX > 250.0F) { + ImGui::SameLine(rightX); + } + + // Gizmos 下拉菜单 (对齐 Infernux "Gizmos v") + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); // 幽灵按钮风格 + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0F, 1.0F, 1.0F, 0.06F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0F, 1.0F, 1.0F, 0.10F)); + if (ImGui::Button("Gizmos v", ImVec2(72.0F, 24.0F))) { ImGui::OpenPopup("MetaCoreToolbarGizmosPopup"); } ImGui::SameLine(0.0F, 4.0F); - if (ImGui::Button("Camera", ImVec2(78.0F, 24.0F))) { + if (ImGui::Button("Camera v", ImVec2(72.0F, 24.0F))) { ImGui::OpenPopup("MetaCoreToolbarCameraPopup"); } + ImGui::PopStyleColor(3); if (ImGui::BeginPopup("MetaCoreToolbarGizmosPopup")) { - if (ImGui::MenuItem("View", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 8.0F)); // POPUP_WIN_PAD + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 4.0F)); // POPUP_ITEM_SPC + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 3.0F)); // POPUP_FRAME_PAD + + ImGui::Dummy(ImVec2(180.0F, 0.0F)); + ImGui::TextUnformatted("Gizmos 设置"); + ImGui::Separator(); + ImGui::Dummy(ImVec2(0.0F, 4.0F)); + + if (ImGui::MenuItem("视图", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) { EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::None); } - if (ImGui::MenuItem("Move", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) { + if (ImGui::MenuItem("位移", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) { EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Translate); } - if (ImGui::MenuItem("Rotate", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) { + if (ImGui::MenuItem("旋转", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) { EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Rotate); } - if (ImGui::MenuItem("Scale", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) { + if (ImGui::MenuItem("缩放", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) { EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Scale); } ImGui::Separator(); - if (ImGui::MenuItem("Local", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) { + if (ImGui::MenuItem("局部", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) { EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Local); } - if (ImGui::MenuItem("Global", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Global)) { + if (ImGui::MenuItem("全局", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Global)) { EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Global); } ImGui::Separator(); bool snapEnabled = EditorContext_->GetGizmoSnapSettings().Enabled; - if (ImGui::Checkbox("Snap", &snapEnabled)) { + if (ImGui::Checkbox("吸附", &snapEnabled)) { EditorContext_->SetGizmoSnapEnabled(snapEnabled); } bool showGrid = EditorContext_->GetShowViewportGrid(); - if (ImGui::Checkbox("Show Grid", &showGrid)) { + if (ImGui::Checkbox("显示网格", &showGrid)) { EditorContext_->SetShowViewportGrid(showGrid); } + + ImGui::PopStyleVar(3); ImGui::EndPopup(); } if (ImGui::BeginPopup("MetaCoreToolbarCameraPopup")) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 8.0F)); // POPUP_WIN_PAD + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 4.0F)); // POPUP_ITEM_SPC + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 3.0F)); // POPUP_FRAME_PAD + MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView(); - ImGui::TextUnformatted("Scene Camera"); + ImGui::Dummy(ImVec2(340.0F, 0.0F)); + ImGui::TextUnformatted("场景相机"); ImGui::Separator(); + ImGui::Dummy(ImVec2(0.0F, 4.0F)); + bool cameraChanged = false; float fieldOfViewDegrees = sceneView.VerticalFieldOfViewDegrees; - ImGui::SetNextItemWidth(180.0F); - if (ImGui::SliderFloat("FOV", &fieldOfViewDegrees, 20.0F, 110.0F, "%.1f")) { + ImGui::TextUnformatted("视野角度"); + ImGui::SameLine(130.0F); + ImGui::SetNextItemWidth(120.0F); + if (ImGui::SliderFloat("##fov_slider", &fieldOfViewDegrees, 10.0F, 120.0F, "%.1F")) { sceneView.VerticalFieldOfViewDegrees = fieldOfViewDegrees; cameraChanged = true; } - ImGui::SetNextItemWidth(220.0F); - if (ImGui::DragFloat3("Position", glm::value_ptr(sceneView.CameraPosition), 0.05F)) { + ImGui::SameLine(0.0F, 6.0F); + ImGui::SetNextItemWidth(72.0F); + if (ImGui::InputFloat("##fov_input", &fieldOfViewDegrees, 1.0F, 10.0F, "%.1f")) { + sceneView.VerticalFieldOfViewDegrees = std::clamp(fieldOfViewDegrees, 10.0F, 120.0F); cameraChanged = true; } - ImGui::SetNextItemWidth(220.0F); - if (ImGui::DragFloat3("Target", glm::value_ptr(sceneView.CameraTarget), 0.05F)) { + + ImGui::Dummy(ImVec2(0.0F, 4.0F)); + ImGui::TextUnformatted("相机坐标"); + ImGui::SetNextItemWidth(260.0F); + if (ImGui::DragFloat3("##pos_drag", glm::value_ptr(sceneView.CameraPosition), 0.05F)) { cameraChanged = true; } + + ImGui::Dummy(ImVec2(0.0F, 4.0F)); + ImGui::TextUnformatted("相机目标点"); + ImGui::SetNextItemWidth(260.0F); + if (ImGui::DragFloat3("##target_drag", glm::value_ptr(sceneView.CameraTarget), 0.05F)) { + cameraChanged = true; + } + if (cameraChanged) { EditorContext_->GetCameraController().ApplySceneView(sceneView); } - if (ImGui::Button("Focus Selection", ImVec2(-1.0F, 0.0F))) { + + ImGui::Dummy(ImVec2(0.0F, 4.0F)); + if (ImGui::Button("聚焦选中物体", ImVec2(-1.0F, 24.0F))) { if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) { EditorContext_->GetCameraController().FocusGameObject(selectedObject); } } - if (ImGui::Button("Reset Camera", ImVec2(-1.0F, 0.0F))) { + if (ImGui::Button("重置相机设置", ImVec2(-1.0F, 24.0F))) { MetaCoreSceneView defaultSceneView{}; defaultSceneView.CameraPosition = {0.0F, 2.5F, 6.5F}; defaultSceneView.CameraTarget = {0.0F, 0.7F, 0.0F}; @@ -1166,11 +1292,13 @@ void MetaCoreEditorApp::DrawEditorToolbar() { defaultSceneView.VerticalFieldOfViewDegrees = 60.0F; EditorContext_->GetCameraController().ApplySceneView(defaultSceneView); } + + ImGui::PopStyleVar(3); ImGui::EndPopup(); } } ImGui::End(); - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(5); } void MetaCoreEditorApp::DrawEditorStatusBar() { @@ -1188,25 +1316,118 @@ void MetaCoreEditorApp::DrawEditorStatusBar() { ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 3.0F)); - ImGui::Begin("MetaCoreStatusBar", nullptr, statusWindowFlags); - ImGui::TextUnformatted("MetaCore"); - ImGui::SameLine(0.0F, 16.0F); - if (const auto playModeService = ModuleRegistry_.ResolveService(); - playModeService != nullptr) { - const MetaCorePlayModeState playState = playModeService->GetState(); - const char* stateText = playState == MetaCorePlayModeState::Edit ? "Edit" : - (playState == MetaCorePlayModeState::Playing ? "Playing" : "Paused"); - ImGui::Text("Mode: %s", stateText); - } - if (const auto assetDatabaseService = ModuleRegistry_.ResolveService(); - assetDatabaseService != nullptr && assetDatabaseService->HasProject()) { - ImGui::SameLine(0.0F, 16.0F); - const std::string projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath.string(); - ImGui::Text("Project: %s", projectRoot.c_str()); + // Background color: STATUS_BAR_BG #131313 + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13F, 0.13F, 0.13F, 1.00F)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0F, 4.0F)); // STATUS_BAR_WIN_PAD + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 0.0F)); // STATUS_BAR_ITEM_SPC + + if (ImGui::Begin("##MetaCoreStatusBar", nullptr, statusWindowFlags)) { + float dispW = ImGui::GetWindowWidth(); + float leftZoneW = dispW - 160.0F; + + // Retrieve log entries + const auto& entries = EditorContext_->GetLogService().GetEntries(); + std::string latestMsg = ""; + MetaCoreLogLevel latestLevel = MetaCoreLogLevel::Info; + int warnCount = 0; + int errorCount = 0; + + for (const auto& entry : entries) { + if (entry.Level == MetaCoreLogLevel::Warning) { + warnCount++; + } else if (entry.Level == MetaCoreLogLevel::Error) { + errorCount++; + } + latestMsg = entry.Message; + latestLevel = entry.Level; + } + + // 1. Clickable area for latest message -> opens/selects Console panel + float clickW = std::max(leftZoneW - 8.0F, 100.0F); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); // Ghost + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.20F, 0.20F, 0.20F, 0.60F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.25F, 0.25F, 0.25F, 0.80F)); + + if (ImGui::InvisibleButton("##StatusBarClick", ImVec2(clickW, 16.0F))) { + ModuleRegistry_.AccessPanelOpenState("Console") = true; + ImGui::SetWindowFocus("Console###console"); + } + ImGui::PopStyleColor(3); + + // Overlay text on the invisible button + ImGui::SameLine(6.0F); + + // Truncate message + int maxChars = std::max(10, static_cast((leftZoneW - 160.0F) / 8.0F)); + std::string msgDisplay = latestMsg; + if (static_cast(msgDisplay.size()) > maxChars) { + msgDisplay = msgDisplay.substr(0, maxChars - 1) + "..."; + } + + // Color and icon for level + ImVec4 levelClr = ImVec4(0.82F, 0.82F, 0.85F, 1.00F); // Info color + std::string icon = ""; + if (latestLevel == MetaCoreLogLevel::Error) { + levelClr = ImVec4(0.922F, 0.341F, 0.341F, 1.00F); // Accent red + icon = "* "; // Character for error + } else if (latestLevel == MetaCoreLogLevel::Warning) { + levelClr = ImVec4(0.890F, 0.710F, 0.300F, 1.00F); // Yellow + icon = "! "; // Warning icon character + } + + ImGui::PushStyleColor(ImGuiCol_Text, levelClr); + if (latestMsg.empty()) { + ImGui::TextUnformatted("MetaCore Ready"); + } else { + ImGui::TextUnformatted((icon + msgDisplay).c_str()); + } + ImGui::PopStyleColor(); + + // 2. Right counters: [W] N [E] N + float counterX = leftZoneW - 130.0F; + if (counterX > 0.0F) { + ImGui::SameLine(counterX); + + // Warning count + if (warnCount > 0) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.890F, 0.710F, 0.300F, 1.00F)); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.133F, 0.133F, 0.133F, 0.60F)); // Dimmed + } + ImGui::Text("! %d", warnCount); + ImGui::PopStyleColor(); + + ImGui::SameLine(0.0F, 12.0F); + + // Error count + if (errorCount > 0) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.922F, 0.341F, 0.341F, 1.00F)); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.133F, 0.133F, 0.133F, 0.60F)); // Dimmed + } + ImGui::Text("* %d", errorCount); + ImGui::PopStyleColor(); + } + + // 3. Engine status (right zone) + float statusX = leftZoneW + 8.0F; + ImGui::SameLine(statusX); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.55F, 0.55F, 0.55F, 1.00F)); // Dim + if (const auto playModeService = ModuleRegistry_.ResolveService(); + playModeService != nullptr) { + const MetaCorePlayModeState playState = playModeService->GetState(); + const char* stateText = playState == MetaCorePlayModeState::Edit ? "Edit" : + (playState == MetaCorePlayModeState::Playing ? "Playing" : "Paused"); + ImGui::TextUnformatted(stateText); + } else { + ImGui::TextUnformatted("Edit"); + } + ImGui::PopStyleColor(); } ImGui::End(); - ImGui::PopStyleVar(); + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(); } void MetaCoreEditorApp::DrawSceneViewWindow() { @@ -1225,7 +1446,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGuiWindowFlags_NoScrollWithMouse; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F)); - if (ImGui::Begin("\u573A\u666F###scene_view", &sceneOpen, sceneWindowFlags)) { + if (ImGui::Begin("场景###scene_view", &sceneOpen, sceneWindowFlags)) { MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState(); ImVec2 viewportPos = ImGui::GetCursorScreenPos(); ImVec2 viewportSize = ImGui::GetContentRegionAvail(); @@ -1276,22 +1497,22 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGui::Dummy(viewportSize); } - // 场景视口浮动快捷工具条 (Toolbar Overlay) - // 浮动在视口左上角,采用半透明暗色背景,1:1 复刻 Unity 视口功能控制体验 + // 场景视口浮动快捷工具条 (ViewportToolbar) ImGui::SetCursorScreenPos(ImVec2(viewportPos.x + 10.0f, viewportPos.y + 10.0f)); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.12f, 0.12f, 0.85f)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 4.0f)); - if (ImGui::BeginChild("ViewportToolbar", ImVec2(410.0f, 28.0f), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::BeginChild("ViewportToolbar", ImVec2(370.0f, 28.0f), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { // A. Gizmo 轴操作切换 (T: 位移, R: 旋转, S: 缩放) auto currentOp = EditorContext_->GetGizmoOperation(); auto drawToolbarButton = [](const char* label, bool active) -> bool { if (active) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.173f, 0.365f, 0.529f, 1.0f)); // 选中亮蓝色 - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.22f, 0.45f, 0.65f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.12f, 0.28f, 0.42f, 1.0f)); + // 替换亮蓝色为 Notion 红灰风格高亮 + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.270F, 0.180F, 0.180F, 1.00F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.320f, 0.220f, 0.220f, 1.00F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.200f, 0.140f, 0.140f, 1.00F)); } else { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); // 平时透明 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.12f)); @@ -1323,10 +1544,10 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGui::TextDisabled("|"); ImGui::SameLine(0, 6); - // B. 坐标系切换 (Local本地 / Global世界) + // B. 坐标系切换 (局部 / 全局) auto currentMode = EditorContext_->GetGizmoMode(); - const char* modeLabel = (currentMode == MetaCoreGizmoMode::Local) ? "Local" : "Global"; - if (ImGui::Button(modeLabel, ImVec2(50.0f, 20.0f))) { + const char* modeLabel = (currentMode == MetaCoreGizmoMode::Local) ? "局部" : "全局"; + if (ImGui::Button(modeLabel, ImVec2(40.0f, 20.0f))) { EditorContext_->SetGizmoMode(currentMode == MetaCoreGizmoMode::Local ? MetaCoreGizmoMode::Global : MetaCoreGizmoMode::Local); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("切换本地/全局坐标系"); @@ -1335,10 +1556,10 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGui::TextDisabled("|"); ImGui::SameLine(0, 6); - // C. 枢轴模式切换 (Pivot枢轴 / Center中心) + // C. 枢轴模式切换 (枢轴 / 中心) auto currentPivot = EditorContext_->GetGizmoPivotMode(); - const char* pivotLabel = (currentPivot == MetaCoreGizmoPivotMode::Pivot) ? "Pivot" : "Center"; - if (ImGui::Button(pivotLabel, ImVec2(55.0f, 20.0f))) { + const char* pivotLabel = (currentPivot == MetaCoreGizmoPivotMode::Pivot) ? "枢轴" : "中心"; + if (ImGui::Button(pivotLabel, ImVec2(40.0f, 20.0f))) { EditorContext_->SetGizmoPivotMode(currentPivot == MetaCoreGizmoPivotMode::Pivot ? MetaCoreGizmoPivotMode::Center : MetaCoreGizmoPivotMode::Pivot); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("切换 Pivot/Center 枢轴中心"); @@ -1367,7 +1588,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() { ImGui::SameLine(0, 6); ImGui::TextDisabled("|"); ImGui::SameLine(0, 6); - if (ImGui::Button("Reset Cam", ImVec2(75.0f, 20.0f))) { + if (ImGui::Button("复位", ImVec2(40.0f, 20.0f))) { EditorContext_->GetCameraController().FocusGameObject({}); // 传入空 GameObject 表示复位摄像机到初始中心位置 } if (ImGui::IsItemHovered()) ImGui::SetTooltip("复位视口摄像机"); @@ -1454,7 +1675,7 @@ void MetaCoreEditorApp::DrawGameViewWindow() { ImGuiWindowFlags_NoScrollWithMouse; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F)); - if (ImGui::Begin("\u6E38\u620F###game_view", nullptr, gameWindowFlags)) { + if (ImGui::Begin("游戏###game_view", nullptr, gameWindowFlags)) { const ImVec2 origin = ImGui::GetCursorScreenPos(); ImVec2 size = ImGui::GetContentRegionAvail(); if (size.x < 1.0F) { diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp index b94250e..f8a422c 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp @@ -18,35 +18,68 @@ void MetaCoreEditorCameraController::Update(const MetaCoreSceneViewportState& vi } ImGuiIO& io = ImGui::GetIO(); - if (io.WantCaptureMouse) { - return; - } - + + // We remove the strict WantCaptureMouse check here because the entire Scene Viewport + // is an ImGui window, and WantCaptureMouse is always true. + // We rely on viewportState.Hovered instead to determine if we should process input. + const bool altDown = input.IsKeyDown(MetaCoreInputKey::LeftAlt) || input.IsKeyDown(MetaCoreInputKey::RightAlt); // Zoom: Alt + Right Drag or Mouse Wheel if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { Distance_ *= (1.0F - io.MouseDelta.y * 0.01F); - Distance_ = std::clamp(Distance_, 0.5F, 100.0F); - } else if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) { - Distance_ *= (io.MouseWheel > 0.0F) ? 0.88F : 1.12F; - Distance_ = std::clamp(Distance_, 0.5F, 100.0F); + Distance_ = std::max(0.01F, Distance_); + } else if (viewportState.Hovered && !ImGui::IsMouseDown(ImGuiMouseButton_Right) && std::abs(io.MouseWheel) > 0.001F) { + // [Unity Style] 滚轮直接带动整个相机坐标系朝正前方无限推进/后退 + float scrollSpeed = std::max(1.0F, Distance_ * 0.1F) * CameraSpeed_ * 0.1F; + Target_ += GetForwardDirection() * io.MouseWheel * scrollSpeed; } // Orbit: Alt + Left Drag if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - YawDegrees_ -= io.MouseDelta.x * 0.35F; // 逆时针 + YawDegrees_ += io.MouseDelta.x * 0.35F; // [Unity Style] 右拖逆时针 PitchDegrees_ -= io.MouseDelta.y * 0.35F; PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F); } - // Look Around: Right Drag (without Alt) - else if (viewportState.Hovered && !altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { + // Fly Mode / Look Around: Right Hold (without Alt) + else if (viewportState.Hovered && !altDown && ImGui::IsMouseDown(ImGuiMouseButton_Right)) { + // --- 0. 滚轮动态调速 --- + if (std::abs(io.MouseWheel) > 0.001F) { + CameraSpeed_ *= (io.MouseWheel > 0.0F) ? 1.25F : 0.8F; + CameraSpeed_ = std::clamp(CameraSpeed_, 0.1F, 2000.0F); + } + + // --- 1. 原地张望 (Free Look) --- const glm::vec3 oldPosition = GetCameraPosition(); - YawDegrees_ -= io.MouseDelta.x * 0.35F; - PitchDegrees_ -= io.MouseDelta.y * 0.35F; + + // [Unity Style] 鼠标右移=视角右转(Yaw++),鼠标下移=视角下看(Pitch--) + YawDegrees_ += io.MouseDelta.x * 0.18F; + PitchDegrees_ -= io.MouseDelta.y * 0.18F; PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F); Target_ = oldPosition + GetForwardDirection() * Distance_; + + // --- 2. 自由漫游飞行 (FPS Flight) --- + glm::vec3 movement(0.0F); + + if (ImGui::IsKeyDown(ImGuiKey_W)) movement += GetForwardDirection(); + if (ImGui::IsKeyDown(ImGuiKey_S)) movement -= GetForwardDirection(); + if (ImGui::IsKeyDown(ImGuiKey_D)) movement += GetRightDirection(); + if (ImGui::IsKeyDown(ImGuiKey_A)) movement -= GetRightDirection(); + if (ImGui::IsKeyDown(ImGuiKey_E)) movement += glm::vec3(0.0F, 0.0F, 1.0F); + if (ImGui::IsKeyDown(ImGuiKey_Q)) movement -= glm::vec3(0.0F, 0.0F, 1.0F); + + if (glm::length(movement) > 0.001F) { + movement = glm::normalize(movement); + + float speed = CameraSpeed_ * io.DeltaTime; + + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + speed *= 3.0F; + } + + Target_ += movement * speed; + } } // Pan: Middle Drag diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h index 38107f5..f591ad9 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h @@ -26,6 +26,7 @@ private: glm::vec3 Target_{0.0F, 0.0F, 0.0F}; float Distance_ = 10.0F; + float CameraSpeed_ = 10.0F; // [MetaCore] Dynamic camera flight speed float YawDegrees_ = 45.0F; float PitchDegrees_ = -35.0F; float FieldOfViewDegrees_ = 60.0F; diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index 75037c1..498ac3d 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -203,6 +203,7 @@ void MetaCoreExpandFocusBounds( void MetaCoreSceneInteractionService::ResetFrameState() { GizmoWasUsing_ = false; HasGizmoBeforeSnapshot_ = false; + GizmoDragStarted_ = false; } MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( @@ -229,11 +230,11 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( sceneView.CameraTarget, sceneView.CameraUp ); - const glm::mat4 projectionMatrix = glm::perspective( + const glm::mat4 projectionMatrix = glm::perspectiveZO( glm::radians(sceneView.VerticalFieldOfViewDegrees), viewportState.Width / viewportState.Height, - 0.05F, - 500.0F + 0.1F, + 1000.0F ); const float normalizedX = (localCursor.x / viewportState.Width) * 2.0F - 1.0F; @@ -509,13 +510,19 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont const int h = static_cast(viewportState.Height); const float gizmoAspect = (h > 0) ? (static_cast(w) / static_cast(h)) : 1.0f; const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp); - const glm::mat4 gizmoProjectionMatrix = glm::perspective( + const glm::mat4 gizmoProjectionMatrix = glm::perspectiveZO( glm::radians(sceneView.VerticalFieldOfViewDegrees), gizmoAspect, 0.1F, 1000.0F ); + // 获取当前物体的真实世界矩阵 glm::mat4 worldMatrixMetaCore = MetaCoreBuildSelectionGizmoWorldMatrix(editorContext, selectedObject.GetId()); - const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore; + + // 若当前未进行拖拽,保持每一帧更新初始矩阵为物体的最新矩阵 + if (!GizmoDragStarted_) { + GizmoStartMatrix_ = worldMatrixMetaCore; + GizmoCurrentMatrix_ = worldMatrixMetaCore; + } ImGuizmo::SetOrthographic(false); ImGuizmo::Enable(true); @@ -538,32 +545,29 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont ImGuizmo::PushID(reinterpret_cast(selectedObject.GetId())); glm::mat4 gizmoMatrix = worldMatrixMetaCore; - const MetaCoreGizmoSnapSettings& snapSettings = editorContext.GetGizmoSnapSettings(); - const float snapStep = snapSettings.StepForOperation(currentOp); - const float snapValues[3] = {snapStep, snapStep, snapStep}; ImGuizmo::Manipulate( glm::value_ptr(gizmoViewMatrix), glm::value_ptr(gizmoProjectionMatrix), MetaCoreToImGuizmoOperation(currentOp), effectiveMode, - glm::value_ptr(gizmoMatrix), - nullptr, - snapSettings.Enabled ? snapValues : nullptr + glm::value_ptr(gizmoMatrix) ); const bool gizmoUsing = ImGuizmo::IsUsing(); ImGuizmo::PopID(); HandleGizmoBeginUse(editorContext, gizmoUsing); - if (gizmoUsing && gizmoMatrix != originalWorldMatrixMetaCore) { - (void)ApplyWorldTransformDeltaToSelection( - editorContext, - selectedObject.GetId(), - originalWorldMatrixMetaCore, - gizmoMatrix, - editorContext.GetGizmoPivotMode() - ); + + if (gizmoUsing && gizmoMatrix != GizmoStartMatrix_) { + glm::mat4 newLocalMatrix = gizmoMatrix; + if (selectedObject.GetParentId() != 0) { + const glm::mat4 parentWorldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject.GetParentId()); + newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * gizmoMatrix; + } + + MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject.GetComponent()); } + HandleGizmoEndUse(editorContext, gizmoUsing); } @@ -776,9 +780,10 @@ void MetaCoreSceneInteractionService::HandleShortcuts(MetaCoreEditorContext& edi FocusSelectedObject(editorContext); } - if (ImGui::IsKeyPressed(ImGuiKey_A)) { - FocusVisibleScene(editorContext); - } + // [MetaCore] Removed 'A' hotkey for FocusVisibleScene because it conflicts with WASD FPS flight + // if (ImGui::IsKeyPressed(ImGuiKey_A)) { + // FocusVisibleScene(editorContext); + // } if (ImGui::IsKeyPressed(ImGuiKey_G)) { editorContext.SetShowViewportGrid(!editorContext.GetShowViewportGrid()); diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h index af128ad..253418d 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h @@ -46,6 +46,11 @@ private: bool GizmoWasUsing_ = false; bool HasGizmoBeforeSnapshot_ = false; MetaCoreEditorStateSnapshot GizmoBeforeSnapshot_{}; + + // 记录 Gizmo 拖拽状态以防止单轴拖拽卡顿并保持视觉轴实时跟随 + bool GizmoDragStarted_ = false; + glm::mat4 GizmoStartMatrix_{1.0F}; + glm::mat4 GizmoCurrentMatrix_{1.0F}; }; } // namespace MetaCore diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index b5f661f..8011d1b 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -22,10 +22,12 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -94,12 +96,18 @@ public: Scene_ = Engine_->createScene(); // 创建默认灯光 + filament::LightManager::ShadowOptions shadowOptions; + shadowOptions.mapSize = 2048; + shadowOptions.shadowCascades = 3; + + // 主光 (Sun) Light_ = utils::EntityManager::get().create(); filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL) - .color(filament::Color::toLinear({ 1.0f, 1.0f, 1.0f })) + .color(filament::Color::toLinear({ 1.0f, 0.95f, 0.9f })) .intensity(100000.0f) - .direction({ 0.5f, 0.5f, -1.0f }) // Z-up 下 Z 向下 + .direction({ 0.5f, 0.5f, -1.0f }) .castShadows(true) + .shadowOptions(shadowOptions) .build(*Engine_, Light_); Scene_->addEntity(Light_); EntitiesInScene_.insert(Light_); @@ -165,6 +173,63 @@ public: MaterialProvider_ = filament::gltfio::createJitShaderProvider(Engine_); NameManager_ = new utils::NameComponentManager(utils::EntityManager::get()); AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ }); + ResourceLoader_ = new filament::gltfio::ResourceLoader({ Engine_ }); + + // 创建 STB 图像解码器以支持 JPG/PNG 等内部贴图 + StbDecoder_ = filament::gltfio::createStbProvider(Engine_); + ResourceLoader_->addTextureProvider("image/png", StbDecoder_); + ResourceLoader_->addTextureProvider("image/jpeg", StbDecoder_); + + // 构造一个 1x1 的浅灰色 Texture 充当 IBL 反射天空盒 + + + // 生成标准的 256x256 全级 Mipmap IBL 反射贴图 + // Filament 要求 IBL 至少 256 且具有完整的 Mipmap,否则带有 Roughness 的 PBR 材质(尤其是金属)会因为采样不到而死黑! + // 生成标准的 256x256 全级 Mipmap HDR(RGBA16F) IBL 反射贴图 + // Filament 的 IndirectLight::reflections 严格要求必须是 HDR 格式(如 RGBA16F),如果给 LDR (RGBA8) 则会直接被丢弃! + // 必须使用 FLOAT 数据类型提交! + uint32_t texSize = 256; + IblTexture_ = filament::Texture::Builder() + .width(texSize).height(texSize).levels(9) // 256 共有 9 级 mipmap + .usage(filament::Texture::Usage::DEFAULT) + .format(filament::Texture::InternalFormat::RGBA16F) + .sampler(filament::Texture::Sampler::SAMPLER_CUBEMAP) + .build(*Engine_); + + for (uint8_t level = 0; level < 9; ++level) { + uint32_t shifted = texSize >> level; + uint32_t levelSize = shifted > 0 ? shifted : 1; + size_t facePixels = levelSize * levelSize; + + float* colors = new float[facePixels * 6 * 4]; + for (size_t i = 0; i < facePixels * 6 * 4; ++i) colors[i] = 0.5f; // HDR 环境光强度 0.5 + + filament::Texture::PixelBufferDescriptor bufferDesc( + colors, facePixels * 6 * 4 * sizeof(float), + filament::Texture::Format::RGBA, + filament::Texture::Type::FLOAT, + [](void* buffer, size_t size, void* user) { delete[] static_cast(buffer); } + ); + + filament::Texture::FaceOffsets faceOffsets; + for (int i = 0; i < 6; ++i) faceOffsets.offsets[i] = i * facePixels * 4 * sizeof(float); + + IblTexture_->setImage(*Engine_, level, std::move(bufferDesc), faceOffsets); + } + + // 生成默认全局环境漫反射与镜面反射 (IBL) + filament::math::float3 sh[9] = {}; + sh[0] = { 1.0f, 1.0f, 1.0f }; // 基础环境光亮度 + IndirectLight_ = filament::IndirectLight::Builder() + .irradiance(3, sh) + .reflections(IblTexture_) // 恢复物理正确的镜面反射 + .intensity(30000.0f) // 正常物理光强 + .build(*Engine_); + Scene_->setIndirectLight(IndirectLight_); + + // 开启高级后处理 + View_->setAntiAliasing(filament::View::AntiAliasing::FXAA); + View_->setBloomOptions({ .strength = 0.15f, .resolution = 384, .levels = 6, .blendMode = filament::View::BloomOptions::BlendMode::ADD, .threshold = true }); std::cout << "FilamentSceneBridge: Initialized with Offscreen=" << (offscreen ? "true" : "false") << std::endl; return true; @@ -184,6 +249,7 @@ public: std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl; } LoadedAssets_.clear(); + AssetBuffers_.clear(); // 2. 销毁所有 pivotEntity 中间辅助坐标变换实体 for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { @@ -211,16 +277,9 @@ public: } ObjectToFilamentEntity_.clear(); ObjectWorldMatrices_.clear(); + AssetBuffers_.clear(); + - // 3. 销毁所有克隆的材质实例,由于引用的实体都已被销毁,此时可安全释放 - for (auto& [id, mats] : AssetDuplicatedMaterials_) { - for (auto* mat : mats) { - if (mat) { - Engine_->destroy(mat); - } - } - } - AssetDuplicatedMaterials_.clear(); for (auto& [id, entity] : SceneLightEntities_) { if (Scene_) { @@ -240,6 +299,22 @@ public: filament::gltfio::AssetLoader::destroy(&AssetLoader_); AssetLoader_ = nullptr; } + if (ResourceLoader_) { + delete ResourceLoader_; + ResourceLoader_ = nullptr; + } + if (StbDecoder_) { + delete StbDecoder_; + StbDecoder_ = nullptr; + } + if (IndirectLight_) { + Engine_->destroy(IndirectLight_); + IndirectLight_ = nullptr; + } + if (IblTexture_) { + Engine_->destroy(IblTexture_); + IblTexture_ = nullptr; + } if (MaterialProvider_) { MaterialProvider_->destroyMaterials(); delete MaterialProvider_; @@ -525,18 +600,10 @@ public: // 确保顶级父 ID 的变换缓存也被清除 ObjectWorldMatrices_.erase(hostRootId); - // 销毁克隆的材质 - auto matIt = AssetDuplicatedMaterials_.find(hostRootId); - if (matIt != AssetDuplicatedMaterials_.end()) { - for (auto* mat : matIt->second) { - if (mat) { - Engine_->destroy(mat); - } - } - AssetDuplicatedMaterials_.erase(matIt); - } + // 从 LoadedAssets_ 中移除 + AssetBuffers_.erase(hostRootId); it = LoadedAssets_.erase(it); } else { ++it; @@ -599,8 +666,10 @@ public: } // 加载贴图等资源 - filament::gltfio::ResourceLoader resourceLoader({ Engine_ }); - resourceLoader.loadResources(asset); + if (ResourceLoader_) { + ResourceLoader_->loadResources(asset); + ResourceLoader_->asyncUpdateLoad(); // 触发底层的纹理同步 + } // 添加到场景 Scene_->addEntities(asset->getEntities(), asset->getEntityCount()); @@ -613,32 +682,9 @@ public: } LoadedAssets_[hostRootId] = asset; + AssetBuffers_[hostRootId] = std::move(buffer); + - // 复制材质实例防止共享干扰,放在加载好资源后 - { - auto& rm = Engine_->getRenderableManager(); - std::vector& dupList = AssetDuplicatedMaterials_[hostRootId]; - const utils::Entity* assetEntities = asset->getEntities(); - size_t assetEntityCount = asset->getEntityCount(); - for (size_t i = 0; i < assetEntityCount; ++i) { - utils::Entity ent = assetEntities[i]; - if (!ent) continue; - auto rmInst = rm.getInstance(ent); - if (rmInst) { - size_t primCount = rm.getPrimitiveCount(rmInst); - for (size_t primIndex = 0; primIndex < primCount; ++primIndex) { - filament::MaterialInstance* originalMat = rm.getMaterialInstanceAt(rmInst, primIndex); - if (originalMat) { - filament::MaterialInstance* dupMat = filament::MaterialInstance::duplicate(originalMat); - if (dupMat) { - rm.setMaterialInstanceAt(rmInst, primIndex, dupMat); - dupList.push_back(dupMat); - } - } - } - } - } - } if (scene != nullptr) { // 有 ECS 实例模式下同步层级结构并补挂 Tag @@ -928,6 +974,11 @@ public: return; } + // 持续推进后台的纹理解码任务! + if (ResourceLoader_) { + ResourceLoader_->asyncUpdateLoad(); + } + bool renderRuntimeUi = false; if (!RenderTarget_ && RuntimeUIView_ && RuntimeUiHelper_) { const auto viewport = View_->getViewport(); @@ -1263,12 +1314,17 @@ private: activeLightIds.insert(light.ObjectId); auto lightIt = SceneLightEntities_.find(light.ObjectId); if (lightIt == SceneLightEntities_.end()) { + filament::LightManager::ShadowOptions shadowOptions; + shadowOptions.mapSize = 2048; + shadowOptions.shadowCascades = 3; + utils::Entity lightEntity = utils::EntityManager::get().create(); filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL) .color(BuildFilamentLightColor(light.Color)) .intensity(BuildFilamentLightIntensity(light.Intensity)) .direction(BuildFilamentLightDirection(light.WorldMatrix)) .castShadows(true) + .shadowOptions(shadowOptions) .build(*Engine_, lightEntity); Scene_->addEntity(lightEntity); EntitiesInScene_.insert(lightEntity); @@ -1331,14 +1387,19 @@ private: filament::gltfio::AssetLoader* AssetLoader_ = nullptr; filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr; + filament::gltfio::ResourceLoader* ResourceLoader_ = nullptr; utils::NameComponentManager* NameManager_ = nullptr; + filament::IndirectLight* IndirectLight_ = nullptr; + filament::Texture* IblTexture_ = nullptr; + filament::gltfio::TextureProvider* StbDecoder_ = nullptr; MetaCoreSceneRenderSync RenderSync_{}; MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{}; std::unordered_map ObjectWorldMatrices_{}; std::unordered_map> ObjectToFilamentEntity_; std::unordered_map LoadedAssets_; - std::unordered_map> AssetDuplicatedMaterials_; + std::unordered_map> AssetBuffers_; + std::unordered_map SceneLightEntities_; std::unordered_set EntitiesInScene_; diff --git a/Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp b/Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp index 6b7577d..f9904df 100644 --- a/Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp @@ -89,7 +89,10 @@ MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, c mRenderable = em.create(); mScene->addEntity(mRenderable); - ImGui::StyleColorsDark(); + if (mOwnsImGuiContext) { + // 只有在拥有私有 ImGui 上下文时才调用 StyleColorsDark() + ImGui::StyleColorsDark(); + } } void MetaCoreImGuiHelper::createAtlasTexture(Engine* engine) { diff --git a/third_party/ImGuizmo/ImGuizmo.cpp b/third_party/ImGuizmo/ImGuizmo.cpp index 73e8b68..c890037 100644 --- a/third_party/ImGuizmo/ImGuizmo.cpp +++ b/third_party/ImGuizmo/ImGuizmo.cpp @@ -1098,19 +1098,24 @@ namespace IMGUIZMO_NAMESPACE // projection reverse vec_t nearPos, farPos; - nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); - farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); - gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + // FIXED: Force mReversed to false. The automatic detection fails for Right-Handed ZO projection matrices, + // causing rays to be cast backwards from the far plane, which breaks horizon detection. + gContext.mReversed = false; - // compute scale from the size of camera right vector projected on screen at the matrix position + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f;// compute scale from the size of camera right vector projected on screen at the matrix position vec_t pointRight = viewInverse.v.right; pointRight.TransformPoint(gContext.mViewProjection); vec_t rightViewInverse = viewInverse.v.right; rightViewInverse.TransformVector(gContext.mModelInverse); float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); - gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + if (!gContext.mbUsing) + { + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + } ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); gContext.mScreenSquareCenter = centerSSpace; @@ -2169,7 +2174,12 @@ namespace IMGUIZMO_NAMESPACE ImGui::CaptureMouseFromApp(); #endif const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); - const float len = fabsf(signedLength); // near plan + if (signedLength < 0.0f) { + // FIXED: If intersection is behind the camera (mouse points at the sky above the horizon), + // ignore the movement to prevent the object from shooting to infinity. + return false; + } + const float len = signedLength; // FIXED: Removed fabsf to allow correct projection when ray diverges const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; // compute delta @@ -2258,8 +2268,15 @@ namespace IMGUIZMO_NAMESPACE for (unsigned int i = 0; i < 3; i++) { vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); - movePlanNormal[i].Cross(orthoVector); - movePlanNormal[i].Normalize(); + if (orthoVector.Length() < 0.1f) // FIXED: If axis is almost parallel to camera, use view plane to avoid singularity + { + movePlanNormal[i] = -gContext.mCameraDir; + } + else + { + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } } // pickup plan gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); @@ -2323,6 +2340,10 @@ namespace IMGUIZMO_NAMESPACE ImGui::CaptureMouseFromApp(); #endif const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + if (len < 0.0f) { + // FIXED: Prevent sky/horizon explosion + return false; + } vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; vec_t delta = newOrigin - gContext.mModelLocal.v.position;