feat(editor): improve scene edit save workflow

This commit is contained in:
ayuan9957 2026-06-17 11:03:02 +08:00
parent db78bed499
commit f9083c4aec
4 changed files with 182 additions and 25 deletions

View File

@ -196,23 +196,38 @@ std::unordered_map<ImGuiID, MetaCoreEditorStateSnapshot>& MetaCoreGetComponentIn
return snapshots;
}
std::vector<ImGuiID>& MetaCoreGetPendingComponentInspectorEditCommits() {
static std::vector<ImGuiID> 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<TValue> MetaCoreGetSharedSelectedValue(
template <typename TAccessor, typename TValue>
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 <typename T>

View File

@ -27,6 +27,7 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>
@ -870,6 +871,11 @@ std::unordered_map<ImGuiID, MetaCoreEditorStateSnapshot>& MetaCoreGetInspectorEd
return snapshots;
}
std::vector<ImGuiID>& MetaCoreGetPendingInspectorEditCommits() {
static std::vector<ImGuiID> pendingCommits;
return pendingCommits;
}
void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char* commandLabel, bool allowMerge);
template <typename TAccessor>
@ -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<MetaCoreIScenePersistenceService>& 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();
}

View File

@ -1494,6 +1494,34 @@ void MetaCoreEditorApp::DrawEditorStatusBar() {
ImGui::PopStyleColor();
}
const auto scenePersistenceService = ModuleRegistry_.ResolveService<MetaCoreIScenePersistenceService>();
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);

View File

@ -566,6 +566,7 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
}
MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject.GetComponent<MetaCoreTransformComponent>());
editorContext.GetScene().IncrementRevision();
}
HandleGizmoEndUse(editorContext, gizmoUsing);