From b319c93ceb5056a890def3f9fff9df6e4aa88a2e Mon Sep 17 00:00:00 2001 From: acpushbutton Date: Sat, 28 Feb 2026 15:11:01 +1100 Subject: [PATCH 1/3] Initial Note undo commit. Undos create only. --- .../TFE_Editor/LevelEditor/editNotes.cpp | 8 +++- .../LevelEditor/levelEditorData.cpp | 27 +++++++++++ .../TFE_Editor/LevelEditor/levelEditorData.h | 2 + .../LevelEditor/levelEditorHistory.cpp | 48 +++++++++++++++++++ .../LevelEditor/levelEditorHistory.h | 5 ++ 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp index 12ae26d8a..232bd73cd 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp @@ -2,6 +2,7 @@ #include "hotkeys.h" #include "levelEditor.h" #include "levelEditorData.h" +#include "levelEditorHistory.h" #include "sharedState.h" #include #include @@ -110,7 +111,11 @@ namespace LevelEditor // Test newNote.note = "Test Note\n * Item 1\n * Item 2\n"; // - addLevelNoteToLevel(&newNote); + s32 resultId = addLevelNoteToLevel(&newNote); + if (resultId >= 0) + { + cmd_levelNoteSnapshot(LName_LevelNote_Create); + } } else if (s_singleClick && s_hoveredLevelNote >= 0) { @@ -195,6 +200,7 @@ namespace LevelEditor } Vec3f moveDir = { note->pos.x - prevPos.x, 0.0f, note->pos.z - prevPos.z }; snapToGrid(¬e->pos); + cmd_levelNoteSnapshot(LName_LevelNote_Move); if (s_view == EDIT_VIEW_3D) { diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.cpp b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.cpp index 00f450136..f6e7d79d4 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.cpp @@ -5291,6 +5291,33 @@ namespace LevelEditor readGuidelineFromSnapshot(guideline); } + void level_createLevelNoteSnapshot(SnapshotBuffer* buffer) + { + setSnapshotWriteBuffer(buffer); + const u32 noteCount = (u32)s_level.notes.size(); + const LevelNote* note = s_level.notes.data(); + + writeU32(noteCount); + for (u32 i = 0; i < noteCount; i++, note++) + { + writeLevelNoteToSnapshot(note); + } + } + + void level_unpackLevelNoteSnapshot(u32 size, void* data) + { + setSnapshotReadBuffer((u8*)data, size); + + const u32 noteCount = readU32(); + s_level.notes.resize(noteCount); + + LevelNote* note = s_level.notes.data(); + for (u32 i = 0; i < noteCount; i++, note++) + { + readLevelNoteFromSnapshot(note); + } + } + // Find a sector based on DF rules. EditorSector* findSectorDf(const Vec3f pos) { diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.h b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.h index dc39fe830..485a66831 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.h +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorData.h @@ -335,6 +335,7 @@ namespace LevelEditor void level_createFeatureTextureSnapshot(TFE_Editor::SnapshotBuffer* buffer, s32 count, const FeatureId* feature); void level_createEntiyListSnapshot(TFE_Editor::SnapshotBuffer* buffer, s32 sectorId); void level_createGuidelineSnapshot(TFE_Editor::SnapshotBuffer* buffer); + void level_createLevelNoteSnapshot(TFE_Editor::SnapshotBuffer* buffer); void level_createSingleGuidelineSnapshot(TFE_Editor::SnapshotBuffer* buffer, s32 index); void level_createLevelSectorSnapshotSameAssets(std::vector& sectors); @@ -348,6 +349,7 @@ namespace LevelEditor void level_unpackEntiyListSnapshot(u32 size, void* data); void level_unpackGuidelineSnapshot(u32 size, void* data); void level_unpackSingleGuidelineSnapshot(u32 size, void* data); + void level_unpackLevelNoteSnapshot(u32 size, void* data); // Spatial Queries s32 findSectorByName(const char* name, s32 excludeId = -1); diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp index f390b2277..82865ec8b 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp @@ -29,6 +29,7 @@ namespace LevelEditor LCmd_Set_Textures, LCmd_Guideline_Snapshot, LCmd_Guideline_Snapshot_Single, + LCmd_LevelNote_Snapshot, LCmd_Count }; @@ -46,6 +47,7 @@ namespace LevelEditor void cmd_applySetTextures(); void cmd_applyGuidelineSnapshot(); void cmd_applyGuidelineSingleSnapshot(); + void cmd_applyLevelNoteSnapshot(); /////////////////////////////////// // API @@ -61,6 +63,7 @@ namespace LevelEditor history_registerCommand(LCmd_Set_Textures, cmd_applySetTextures); history_registerCommand(LCmd_Guideline_Snapshot, cmd_applyGuidelineSnapshot); history_registerCommand(LCmd_Guideline_Snapshot_Single, cmd_applyGuidelineSingleSnapshot); + history_registerCommand(LCmd_LevelNote_Snapshot, cmd_applyLevelNoteSnapshot); history_registerName(LName_MoveVertex, "Move Vertice(s)"); history_registerName(LName_SetVertex, "Set Vertex Position"); @@ -100,6 +103,10 @@ namespace LevelEditor history_registerName(LName_Guideline_Create, "Create Guidelines"); history_registerName(LName_Guideline_Delete, "Delete Guidelines"); history_registerName(LName_Guideline_Edit, "Edit Guidelines"); + history_registerName(LName_LevelNote_Create, "Create Note"); + history_registerName(LName_LevelNote_Delete, "Delete Note"); + history_registerName(LName_LevelNote_Move, "Move Note"); + history_registerName(LName_LevelNote_Change, "Change Note"); } void levHistory_destroy() @@ -305,6 +312,34 @@ namespace LevelEditor } CMD_END(); } + + void cmd_levelNoteSnapshot(u32 name) + { + // Merge consecutive attribute changes + u16 prevCmd, prevName; + history_getPrevCmdAndName(prevCmd, prevName); + if (prevName == LName_LevelNote_Change) + { + history_removeLast(); + } + + s_workBuffer[0].clear(); + s_workBuffer[1].clear(); + level_createLevelNoteSnapshot(&s_workBuffer[0]); + if (s_workBuffer[0].empty()) { return; } + + const u32 uncompressedSize = (u32)s_workBuffer[0].size(); + const u32 compressedSize = compressBuffer(); + if (!compressedSize) { return; } + + CMD_BEGIN(LCmd_LevelNote_Snapshot, name); + { + hBuffer_addU32(uncompressedSize); + hBuffer_addU32(compressedSize); + hBuffer_addArrayU8(compressedSize, s_workBuffer[1].data()); + } + CMD_END(); + } //////////////////////////////// // History Commands @@ -400,6 +435,19 @@ namespace LevelEditor } } + void cmd_applyLevelNoteSnapshot() + { + const u32 uncompressedSize = hBuffer_getU32(); + const u32 compressedSize = hBuffer_getU32(); + const u8* compressedData = hBuffer_getArrayU8(compressedSize); + + s_workBuffer[0].resize(uncompressedSize); + if (zstd_decompress(s_workBuffer[0].data(), uncompressedSize, compressedData, compressedSize)) + { + level_unpackLevelNoteSnapshot(uncompressedSize, s_workBuffer[0].data()); + } + } + ////////////////////////////////////////////////// // Internal ////////////////////////////////////////////////// diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h index 4d9f8a879..a65782ffc 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h @@ -46,6 +46,10 @@ namespace LevelEditor LName_Guideline_Create, LName_Guideline_Delete, LName_Guideline_Edit, + LName_LevelNote_Create, + LName_LevelNote_Delete, + LName_LevelNote_Move, + LName_LevelNote_Change, LName_Count }; @@ -65,5 +69,6 @@ namespace LevelEditor void cmd_objectListSnapshot(u32 name, s32 sectorId); void cmd_setTextures(u32 name, s32 count, FeatureId* features); void cmd_guidelineSnapshot(u32 name); + void cmd_levelNoteSnapshot(u32 name); void cmd_guidelineSingleSnapshot(u32 name, s32 index, bool idChanged); } From 9a010abd6a6aa3ab60c6abbb225823e206d72371 Mon Sep 17 00:00:00 2001 From: acpushbutton Date: Sat, 28 Feb 2026 15:58:55 +1100 Subject: [PATCH 2/3] Fix note moves to only record in history on mouse up and if position changed --- TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp index 232bd73cd..3644fc6cd 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp @@ -200,7 +200,6 @@ namespace LevelEditor } Vec3f moveDir = { note->pos.x - prevPos.x, 0.0f, note->pos.z - prevPos.z }; snapToGrid(¬e->pos); - cmd_levelNoteSnapshot(LName_LevelNote_Move); if (s_view == EDIT_VIEW_3D) { @@ -217,6 +216,17 @@ namespace LevelEditor s_editMove = false; } } + else if (!s_leftMouseDown && s_editMove) + { + // Only record in history if moved, not clicked + if (s_moveBasePos3d.x != s_level.notes[s_curLevelNote].pos.x || + s_moveBasePos3d.y != s_level.notes[s_curLevelNote].pos.y || + s_moveBasePos3d.z != s_level.notes[s_curLevelNote].pos.z) + { + cmd_levelNoteSnapshot(LName_LevelNote_Move); + } + s_editMove = false; + } else { s_editMove = false; From 0421ee6bdabf613d8da8c308b5712bad16939f4a Mon Sep 17 00:00:00 2001 From: acpushbutton Date: Sat, 28 Feb 2026 17:14:21 +1100 Subject: [PATCH 3/3] Implement attrib edit case. Bundle consecutive attribs. Update infoPanelNote(). --- .../TFE_Editor/LevelEditor/editNotes.cpp | 5 ++-- .../TFE_Editor/LevelEditor/infoPanel.cpp | 29 +++++++++++++++---- .../LevelEditor/levelEditorHistory.cpp | 6 ++-- .../LevelEditor/levelEditorHistory.h | 2 +- .../TFE_Editor/LevelEditor/note.cpp | 1 + TheForceEngine/TFE_Editor/LevelEditor/note.h | 1 + 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp index 3644fc6cd..9d1230e07 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/editNotes.cpp @@ -114,7 +114,7 @@ namespace LevelEditor s32 resultId = addLevelNoteToLevel(&newNote); if (resultId >= 0) { - cmd_levelNoteSnapshot(LName_LevelNote_Create); + cmd_levelNoteSnapshot(LName_LevelNote_Create, false); } } else if (s_singleClick && s_hoveredLevelNote >= 0) @@ -223,7 +223,7 @@ namespace LevelEditor s_moveBasePos3d.y != s_level.notes[s_curLevelNote].pos.y || s_moveBasePos3d.z != s_level.notes[s_curLevelNote].pos.z) { - cmd_levelNoteSnapshot(LName_LevelNote_Move); + cmd_levelNoteSnapshot(LName_LevelNote_Move, false); } s_editMove = false; } @@ -247,6 +247,7 @@ namespace LevelEditor } if (deleted) { + cmd_levelNoteSnapshot(LName_LevelNote_Delete, false); clearLevelNoteSelection(); } } diff --git a/TheForceEngine/TFE_Editor/LevelEditor/infoPanel.cpp b/TheForceEngine/TFE_Editor/LevelEditor/infoPanel.cpp index fa68ca8dd..711f3df1b 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/infoPanel.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/infoPanel.cpp @@ -2882,22 +2882,23 @@ namespace LevelEditor { s32 id = s_curLevelNote >= 0 ? s_curLevelNote : s_hoveredLevelNote; if (id < 0) { return; } - + LevelNote* note = &s_level.notes[id]; + bool changed = false; - ImGui::CheckboxFlags("2D Only", ¬e->flags, LNF_2D_ONLY); ImGui::SameLine(); - ImGui::CheckboxFlags("No Fade in 3D", ¬e->flags, LNF_3D_NO_FADE); ImGui::SameLine(); - ImGui::CheckboxFlags("Always Show Text", ¬e->flags, LNF_TEXT_ALWAYS_SHOW); + changed |= ImGui::CheckboxFlags("2D Only", ¬e->flags, LNF_2D_ONLY); ImGui::SameLine(); + changed |= ImGui::CheckboxFlags("No Fade in 3D", ¬e->flags, LNF_3D_NO_FADE); ImGui::SameLine(); + changed |= ImGui::CheckboxFlags("Always Show Text", ¬e->flags, LNF_TEXT_ALWAYS_SHOW); ImGui::Spacing(); ImGui::SetNextItemWidth(128.0f); ImGui::LabelText("##Label", "Start Fade (3D)"); ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f); - ImGui::InputFloat("##StartFade", ¬e->fade.x, 1.0f, 10.0f, "%.1f"); + changed |= ImGui::InputFloat("##StartFade", ¬e->fade.x, 1.0f, 10.0f, "%.1f"); s_textInputFocused |= ImGui::IsItemActive(); ImGui::SetNextItemWidth(128.0f); ImGui::LabelText("##Label", "End Fade (3D)"); ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f); - ImGui::InputFloat("##EndFade", ¬e->fade.z, 1.0f, 10.0f, "%.1f"); + changed |= ImGui::InputFloat("##EndFade", ¬e->fade.z, 1.0f, 10.0f, "%.1f"); s_textInputFocused |= ImGui::IsItemActive(); ImGui::Separator(); ImGui::SetNextItemWidth(128.0f); @@ -2905,6 +2906,7 @@ namespace LevelEditor Vec4f iconColor = packedColorToVec4(note->iconColor); if (ImGui::ColorEdit4(editor_getUniqueLabel(""), iconColor.m, ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoInputs)) { + changed = true; note->iconColor = colorVec4ToPacked(iconColor); } ImGui::SetNextItemWidth(128.0f); @@ -2912,6 +2914,7 @@ namespace LevelEditor Vec4f textColor = packedColorToVec4(note->textColor); if (ImGui::ColorEdit4(editor_getUniqueLabel(""), textColor.m, ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoInputs)) { + changed = true; note->textColor = colorVec4ToPacked(textColor); } ImGui::Separator(); @@ -2923,9 +2926,23 @@ namespace LevelEditor if (ImGui::InputTextMultiline(textInputId, tmpBuffer, 4096, { s_infoWith - 16.0f, 354.0f })) { + changed = true; note->note = tmpBuffer; } s_textInputFocused |= ImGui::IsItemActive(); + + if (changed) + { + if (s_prevLevelNote == s_curLevelNote) + { + cmd_levelNoteSnapshot(LName_LevelNote_Change, false); + } + else + { + cmd_levelNoteSnapshot(LName_LevelNote_Change, true); + } + s_prevLevelNote = s_curLevelNote; + } } bool drawInfoPanel(EditorView view) diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp index 82865ec8b..e4c32d688 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.cpp @@ -313,12 +313,12 @@ namespace LevelEditor CMD_END(); } - void cmd_levelNoteSnapshot(u32 name) + void cmd_levelNoteSnapshot(u32 name, bool idChanged) { - // Merge consecutive attribute changes + // Bundle consecutive attribute changes u16 prevCmd, prevName; history_getPrevCmdAndName(prevCmd, prevName); - if (prevName == LName_LevelNote_Change) + if (prevName == LName_LevelNote_Change && name == prevName && !idChanged) { history_removeLast(); } diff --git a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h index a65782ffc..863b67cf3 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h +++ b/TheForceEngine/TFE_Editor/LevelEditor/levelEditorHistory.h @@ -69,6 +69,6 @@ namespace LevelEditor void cmd_objectListSnapshot(u32 name, s32 sectorId); void cmd_setTextures(u32 name, s32 count, FeatureId* features); void cmd_guidelineSnapshot(u32 name); - void cmd_levelNoteSnapshot(u32 name); + void cmd_levelNoteSnapshot(u32 name, bool idChanged); void cmd_guidelineSingleSnapshot(u32 name, s32 index, bool idChanged); } diff --git a/TheForceEngine/TFE_Editor/LevelEditor/note.cpp b/TheForceEngine/TFE_Editor/LevelEditor/note.cpp index 0b7827e40..2f472adc0 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/note.cpp +++ b/TheForceEngine/TFE_Editor/LevelEditor/note.cpp @@ -36,6 +36,7 @@ namespace LevelEditor s32 s_hoveredLevelNote = -1; s32 s_curLevelNote = -1; + s32 s_prevLevelNote = -1; // For bundling consecutive attrib changes as one undo void clearLevelNoteSelection() { diff --git a/TheForceEngine/TFE_Editor/LevelEditor/note.h b/TheForceEngine/TFE_Editor/LevelEditor/note.h index ddf5d87ac..610ce4a84 100644 --- a/TheForceEngine/TFE_Editor/LevelEditor/note.h +++ b/TheForceEngine/TFE_Editor/LevelEditor/note.h @@ -48,6 +48,7 @@ namespace LevelEditor extern const u32 c_noteColors[3]; extern s32 s_hoveredLevelNote; extern s32 s_curLevelNote; + extern s32 s_prevLevelNote; void clearLevelNoteSelection(); }