diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 5100985..6eec08f 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -196,23 +196,38 @@ std::unordered_map& MetaCoreGetComponentIn return snapshots; } +std::vector& MetaCoreGetPendingComponentInspectorEditCommits() { + static std::vector pendingCommits; + return pendingCommits; +} + void MetaCoreTrackComponentInspectorEdit(MetaCoreEditorContext& editorContext, const char* commandLabel, bool allowMerge) { + auto& snapshots = MetaCoreGetComponentInspectorEditSnapshots(); + auto& pendingCommits = MetaCoreGetPendingComponentInspectorEditCommits(); + if (!pendingCommits.empty()) { + const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot(); + for (const ImGuiID pendingItemId : pendingCommits) { + const auto iterator = snapshots.find(pendingItemId); + if (iterator != snapshots.end()) { + (void)editorContext.CommitStateTransition(commandLabel, iterator->second, afterSnapshot, allowMerge); + snapshots.erase(iterator); + } + } + pendingCommits.clear(); + } + const ImGuiID itemId = ImGui::GetItemID(); if (itemId == 0) { return; } - auto& snapshots = MetaCoreGetComponentInspectorEditSnapshots(); if (ImGui::IsItemActivated()) { snapshots[itemId] = editorContext.CaptureStateSnapshot(); } if (ImGui::IsItemDeactivatedAfterEdit()) { - const auto iterator = snapshots.find(itemId); - if (iterator != snapshots.end()) { - const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot(); - (void)editorContext.CommitStateTransition(commandLabel, iterator->second, afterSnapshot, allowMerge); - snapshots.erase(iterator); + if (std::find(pendingCommits.begin(), pendingCommits.end(), itemId) == pendingCommits.end()) { + pendingCommits.push_back(itemId); } } } @@ -2360,11 +2375,16 @@ std::optional MetaCoreGetSharedSelectedValue( template void MetaCoreApplyValueToSelectedObjects(MetaCoreEditorContext& editorContext, TAccessor&& accessor, const TValue& value) { + bool anyTarget = false; MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { if (auto* targetValue = accessor(selectedObject); targetValue != nullptr) { *targetValue = value; + anyTarget = true; } }); + if (anyTarget) { + editorContext.GetScene().IncrementRevision(); + } } template diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index e491843..fc6cf56 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -870,6 +871,11 @@ std::unordered_map& MetaCoreGetInspectorEd return snapshots; } +std::vector& MetaCoreGetPendingInspectorEditCommits() { + static std::vector pendingCommits; + return pendingCommits; +} + void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char* commandLabel, bool allowMerge); template @@ -2093,22 +2099,32 @@ struct MetaCoreTextureAssetChoicesCache { } void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char* commandLabel, bool allowMerge) { + auto& snapshots = MetaCoreGetInspectorEditSnapshots(); + auto& pendingCommits = MetaCoreGetPendingInspectorEditCommits(); + if (!pendingCommits.empty()) { + const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot(); + for (const ImGuiID pendingItemId : pendingCommits) { + const auto iterator = snapshots.find(pendingItemId); + if (iterator != snapshots.end()) { + (void)editorContext.CommitStateTransition(commandLabel, iterator->second, afterSnapshot, allowMerge); + snapshots.erase(iterator); + } + } + pendingCommits.clear(); + } + const ImGuiID itemId = ImGui::GetItemID(); if (itemId == 0) { return; } - auto& snapshots = MetaCoreGetInspectorEditSnapshots(); if (ImGui::IsItemActivated()) { snapshots[itemId] = editorContext.CaptureStateSnapshot(); } if (ImGui::IsItemDeactivatedAfterEdit()) { - const auto iterator = snapshots.find(itemId); - if (iterator != snapshots.end()) { - const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot(); - (void)editorContext.CommitStateTransition(commandLabel, iterator->second, afterSnapshot, allowMerge); - snapshots.erase(iterator); + if (std::find(pendingCommits.begin(), pendingCommits.end(), itemId) == pendingCommits.end()) { + pendingCommits.push_back(itemId); } } } @@ -5219,7 +5235,7 @@ public: editorContext.ClearSelection(); if (ImGui::IsMouseDoubleClicked(0)) { - if (item.Type == "scene" && scenePersistenceService) (void)scenePersistenceService->LoadScene(editorContext, item.Path); + if (item.Type == "scene" && scenePersistenceService) RequestOpenScene(editorContext, *scenePersistenceService, item.Path); else if (item.Type == "model") (void)MetaCoreInstantiateModelAsset(editorContext, item.Guid, std::nullopt); else if (item.Type == "prefab") (void)MetaCoreInstantiatePrefab(editorContext, item.Guid, std::nullopt); } @@ -5260,7 +5276,7 @@ public: } else { if (ImGui::MenuItem("Open")) { if (item.Type == "scene" && scenePersistenceService) { - (void)scenePersistenceService->LoadScene(editorContext, item.Path); + RequestOpenScene(editorContext, *scenePersistenceService, item.Path); } else if (item.Type == "model") { (void)MetaCoreInstantiateModelAsset(editorContext, item.Guid, std::nullopt); } else if (item.Type == "prefab") { @@ -5556,7 +5572,7 @@ public: editorContext.ClearSelection(); if (ImGui::IsMouseDoubleClicked(0)) { - if (item.Type == "scene" && scenePersistenceService) (void)scenePersistenceService->LoadScene(editorContext, item.Path); + if (item.Type == "scene" && scenePersistenceService) RequestOpenScene(editorContext, *scenePersistenceService, item.Path); else if (item.Type == "model") (void)MetaCoreInstantiateModelAsset(editorContext, item.Guid, std::nullopt); else if (item.Type == "prefab") (void)MetaCoreInstantiatePrefab(editorContext, item.Guid, std::nullopt); } @@ -5597,7 +5613,7 @@ public: } else { if (ImGui::MenuItem("Open")) { if (item.Type == "scene" && scenePersistenceService) { - (void)scenePersistenceService->LoadScene(editorContext, item.Path); + RequestOpenScene(editorContext, *scenePersistenceService, item.Path); } else if (item.Type == "model") { (void)MetaCoreInstantiateModelAsset(editorContext, item.Guid, std::nullopt); } else if (item.Type == "prefab") { @@ -5777,7 +5793,74 @@ public: } ImGui::EndPopup(); } + + DrawPendingOpenScenePopup(editorContext, scenePersistenceService); } + +private: + void RequestOpenScene( + MetaCoreEditorContext& editorContext, + MetaCoreIScenePersistenceService& scenePersistenceService, + const std::filesystem::path& scenePath + ) { + if (!scenePersistenceService.IsSceneDirty()) { + (void)scenePersistenceService.LoadScene(editorContext, scenePath); + return; + } + + PendingOpenScenePath_ = scenePath; + OpenPendingScenePopup_ = true; + } + + void DrawPendingOpenScenePopup( + MetaCoreEditorContext& editorContext, + const std::shared_ptr& scenePersistenceService + ) { + if (scenePersistenceService == nullptr) { + PendingOpenScenePath_.clear(); + OpenPendingScenePopup_ = false; + return; + } + + if (OpenPendingScenePopup_) { + ImGui::OpenPopup("Unsaved Scene Changes"); + OpenPendingScenePopup_ = false; + } + + if (ImGui::BeginPopupModal("Unsaved Scene Changes", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + const std::string currentScene = scenePersistenceService->HasOpenScene() + ? scenePersistenceService->GetCurrentSceneDisplayName() + : std::string("Untitled"); + ImGui::Text("Save changes to %s before opening another scene?", currentScene.c_str()); + ImGui::TextDisabled("Next scene: %s", PendingOpenScenePath_.generic_string().c_str()); + ImGui::Separator(); + + if (ImGui::Button("Save and Open", ImVec2(130, 0))) { + if (scenePersistenceService->SaveCurrentScene(editorContext)) { + (void)scenePersistenceService->LoadScene(editorContext, PendingOpenScenePath_); + PendingOpenScenePath_.clear(); + ImGui::CloseCurrentPopup(); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Scene", "保存失败,已取消切换场景"); + } + } + ImGui::SameLine(); + if (ImGui::Button("Don't Save", ImVec2(110, 0))) { + (void)scenePersistenceService->LoadScene(editorContext, PendingOpenScenePath_); + PendingOpenScenePath_.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(90, 0))) { + PendingOpenScenePath_.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + std::filesystem::path PendingOpenScenePath_{}; + bool OpenPendingScenePopup_ = false; }; class MetaCoreConsolePanelProvider final : public MetaCoreIEditorPanelProvider { @@ -7475,6 +7558,9 @@ private: ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 4)); + ImGuiID editedItemId = 0; + bool editedItemDeactivated = false; + auto drawComponent = [&](const char* compLabel, float& val, const ImVec4& color) { ImGui::TextColored(color, "%s", compLabel); ImGui::SameLine(); @@ -7487,7 +7573,15 @@ private: else if (compLabel[0] == 'Y') { displayValues.x *= ratio; displayValues.z *= ratio; } else if (compLabel[0] == 'Z') { displayValues.x *= ratio; displayValues.y *= ratio; } } - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + } + + const ImGuiID itemId = ImGui::GetItemID(); + if (itemId != 0 && ImGui::IsItemActivated()) { + MetaCoreGetInspectorEditSnapshots()[itemId] = editorContext.CaptureStateSnapshot(); + } + if (itemId != 0 && ImGui::IsItemDeactivatedAfterEdit()) { + editedItemId = itemId; + editedItemDeactivated = true; } }; @@ -7508,16 +7602,30 @@ private: values = displayValues; } + if (editedItemDeactivated && editedItemId != 0) { + auto& snapshots = MetaCoreGetInspectorEditSnapshots(); + const auto iterator = snapshots.find(editedItemId); + if (iterator != snapshots.end()) { + const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot(); + (void)editorContext.CommitStateTransition("修改检查器属性", iterator->second, afterSnapshot, true); + snapshots.erase(iterator); + } + } + if (ImGui::BeginPopupContextItem("TransformContext")) { if (ImGui::MenuItem("Reset")) { - MetaCoreApplyValueToSelectedObjects( - editorContext, - [&](MetaCoreGameObject& selectedObject) -> glm::vec3* { - return getTargetValue(selectedObject); - }, - glm::vec3(resetValue) - ); - values = glm::vec3(resetValue); + const glm::vec3 resetVec(resetValue); + (void)editorContext.ExecuteSnapshotCommand("重置 Transform 字段", [&]() { + MetaCoreApplyValueToSelectedObjects( + editorContext, + [&](MetaCoreGameObject& selectedObject) -> glm::vec3* { + return getTargetValue(selectedObject); + }, + resetVec + ); + return true; + }); + values = resetVec; } ImGui::EndPopup(); } diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 86bc68d..22e92cd 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -1494,6 +1494,34 @@ void MetaCoreEditorApp::DrawEditorStatusBar() { ImGui::PopStyleColor(); } + const auto scenePersistenceService = ModuleRegistry_.ResolveService(); + std::string sceneStatus = "No Scene"; + ImVec4 sceneStatusColor(0.55F, 0.55F, 0.55F, 1.00F); + if (scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()) { + const bool sceneDirty = scenePersistenceService->IsSceneDirty(); + sceneStatus = scenePersistenceService->GetCurrentSceneDisplayName(); + sceneStatus += sceneDirty ? " * Unsaved" : " Saved"; + sceneStatusColor = sceneDirty + ? ImVec4(0.890F, 0.710F, 0.300F, 1.00F) + : ImVec4(0.55F, 0.70F, 0.55F, 1.00F); + } + + const float sceneStatusWidth = std::min(300.0F, std::max(120.0F, leftZoneW * 0.32F)); + const float sceneStatusX = std::max(0.0F, leftZoneW - sceneStatusWidth - 140.0F); + if (sceneStatusX > ImGui::GetCursorPosX()) { + ImGui::SameLine(sceneStatusX); + ImGui::PushStyleColor(ImGuiCol_Text, sceneStatusColor); + ImGui::TextUnformatted(sceneStatus.c_str()); + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered() && scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()) { + ImGui::SetTooltip( + "%s\n%s", + scenePersistenceService->GetCurrentScenePath().generic_string().c_str(), + scenePersistenceService->IsSceneDirty() ? "Unsaved changes" : "Scene saved" + ); + } + } + // 3. Engine status (right zone) float statusX = leftZoneW + 8.0F; ImGui::SameLine(statusX); diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index 498ac3d..b14634e 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -566,6 +566,7 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont } MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject.GetComponent()); + editorContext.GetScene().IncrementRevision(); } HandleGizmoEndUse(editorContext, gizmoUsing);