From befa12f9e61638478b925456463bde68a34dc411 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 05:20:53 -0700 Subject: [PATCH] fix(editor): Clear All, New Terrain reset, right-click menu, gizmo drag - Clear All now actually removes all objects and NPCs (was only clearing selections before). Uses new ObjectPlacer::clearAll() method. - New Terrain clears all objects/NPCs and resets viewport before creating fresh terrain. Fixes stale state from previous session. - Right-click context menu works on both objects AND NPCs with appropriate options for each (Move/Rotate/Scale for objects, Fly To/Duplicate for NPCs) - Gizmo drag: left-click now confirms the transform (ends drag) instead of requiring mouse-up. Right-click cancels. Camera no longer steals mouse events while gizmo is active. - Right-click on unselected area passes through to camera correctly --- tools/editor/editor_app.cpp | 24 +++++++++--- tools/editor/editor_ui.cpp | 69 ++++++++++++++++++++++------------ tools/editor/object_placer.hpp | 1 + 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index ac9ee45f..00748713 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -281,26 +281,32 @@ void EditorApp::processEvents() { } if ((event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) && !io.WantCaptureMouse) { - // Right-click context menu on selected objects + // Right-click on selected objects = context menu if (event.button.button == SDL_BUTTON_RIGHT && event.type == SDL_MOUSEBUTTONDOWN) { auto& giz = viewport_.getGizmo(); if (giz.isDragging()) { giz.endDrag(); giz.setMode(TransformMode::None); - } else if (objectPlacer_.getSelected()) { + } else if (objectPlacer_.getSelected() || npcSpawner_.getSelected()) { openContextMenu_ = true; } else { camera_.processMouseButton(event.button); } + } else if (event.button.button == SDL_BUTTON_RIGHT && event.type == SDL_MOUSEBUTTONUP) { + if (!objectPlacer_.getSelected() && !npcSpawner_.getSelected()) + camera_.processMouseButton(event.button); } else { - camera_.processMouseButton(event.button); + // Only pass to camera if gizmo not active + auto& giz = viewport_.getGizmo(); + if (!giz.isDragging()) + camera_.processMouseButton(event.button); } // Left click if (event.button.button == SDL_BUTTON_LEFT && terrain_.isLoaded()) { - // End gizmo drag on click release auto& giz = viewport_.getGizmo(); - if (giz.isDragging() && event.type == SDL_MOUSEBUTTONUP) { + // End gizmo drag on left click + if (giz.isDragging() && event.type == SDL_MOUSEBUTTONDOWN) { giz.endDrag(); giz.setMode(TransformMode::None); } else if (event.type == SDL_MOUSEBUTTONDOWN) { @@ -540,6 +546,12 @@ void EditorApp::loadADT(const std::string& mapName, int tileX, int tileY) { void EditorApp::createNewTerrain(const std::string& mapName, int tileX, int tileY, float baseHeight, Biome biome) { terrain_ = TerrainEditor::createBlankTerrain(tileX, tileY, baseHeight, biome); + // Clear previous state + objectPlacer_.clearAll(); + npcSpawner_.clearSelection(); + npcSpawner_.getSpawns().clear(); + viewport_.clearObjects(); + terrainEditor_.setTerrain(&terrain_); terrainEditor_.history().clear(); texturePainter_.setTerrain(&terrain_); @@ -552,6 +564,8 @@ void EditorApp::createNewTerrain(const std::string& mapName, int tileX, int tile loadedMap_ = mapName; loadedTileX_ = tileX; loadedTileY_ = tileY; + lastObjectCount_ = 0; + objectsDirty_ = false; float centerX = (32.0f - tileY) * 533.33333f - 8.0f * 533.33333f / 16.0f; float centerY = (32.0f - tileX) * 533.33333f - 8.0f * 533.33333f / 16.0f; diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 64958f31..3596e1f0 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -100,10 +100,13 @@ void EditorUI::renderMenuBar(EditorApp& app) { } ImGui::EndMenu(); } - if (ImGui::MenuItem("Clear All", nullptr, false, app.hasTerrainLoaded())) { - app.getTerrainEditor().history().clear(); - app.getObjectPlacer().clearSelection(); + if (ImGui::MenuItem("Clear All Objects/NPCs", nullptr, false, app.hasTerrainLoaded())) { + app.getObjectPlacer().clearAll(); app.getNpcSpawner().clearSelection(); + app.getNpcSpawner().getSpawns().clear(); + app.getTerrainEditor().history().clear(); + app.markObjectsDirty(); + app.showToast("All objects and NPCs cleared"); } ImGui::Separator(); if (ImGui::MenuItem("Quick Save", "Ctrl+S", false, app.hasTerrainLoaded())) @@ -900,38 +903,54 @@ void EditorUI::renderContextMenu(EditorApp& app) { app.clearContextMenuFlag(); } if (ImGui::BeginPopup("ObjectContextMenu")) { - auto* sel = app.getObjectPlacer().getSelected(); - if (!sel) { ImGui::EndPopup(); return; } + auto* objSel = app.getObjectPlacer().getSelected(); + auto* npcSel = app.getNpcSpawner().getSelected(); + if (!objSel && !npcSel) { ImGui::EndPopup(); return; } - std::string display = sel->path; - auto slash = display.rfind('\\'); - if (slash != std::string::npos) display = display.substr(slash + 1); - ImGui::TextColored(ImVec4(1, 0.9f, 0.3f, 1), "%s", display.c_str()); + if (objSel) { + std::string display = objSel->path; + auto slash = display.rfind('\\'); + if (slash != std::string::npos) display = display.substr(slash + 1); + ImGui::TextColored(ImVec4(1, 0.9f, 0.3f, 1), "%s", display.c_str()); + } else { + ImGui::TextColored(ImVec4(1, 0.9f, 0.3f, 1), "%s (NPC)", npcSel->name.c_str()); + } ImGui::Separator(); - if (ImGui::MenuItem("Move (left-drag)")) - app.startGizmoMode(TransformMode::Move); - if (ImGui::MenuItem("Rotate (left-drag)")) - app.startGizmoMode(TransformMode::Rotate); - if (ImGui::MenuItem("Scale (left-drag)")) - app.startGizmoMode(TransformMode::Scale); - - ImGui::Separator(); - if (ImGui::BeginMenu("Constrain Axis")) { - if (ImGui::MenuItem("All Axes")) app.setGizmoAxis(TransformAxis::All); - if (ImGui::MenuItem("X (Red)")) app.setGizmoAxis(TransformAxis::X); - if (ImGui::MenuItem("Y (Green)")) app.setGizmoAxis(TransformAxis::Y); - if (ImGui::MenuItem("Z (Blue)")) app.setGizmoAxis(TransformAxis::Z); - ImGui::EndMenu(); + if (objSel) { + if (ImGui::MenuItem("Move (G)")) + app.startGizmoMode(TransformMode::Move); + if (ImGui::MenuItem("Rotate (R)")) + app.startGizmoMode(TransformMode::Rotate); + if (ImGui::MenuItem("Scale (T)")) + app.startGizmoMode(TransformMode::Scale); + ImGui::Separator(); + if (ImGui::MenuItem("Snap to Ground")) + app.snapSelectedToGround(); + if (ImGui::MenuItem("Fly To")) + app.flyToSelected(); + } + if (npcSel) { + if (ImGui::MenuItem("Fly To")) + app.flyToSelected(); + if (ImGui::MenuItem("Duplicate")) { + CreatureSpawn copy = *npcSel; + copy.position += glm::vec3(10, 10, 0); + app.getNpcSpawner().placeCreature(copy); + app.markObjectsDirty(); + } } ImGui::Separator(); if (ImGui::MenuItem("Delete")) { - app.getObjectPlacer().deleteSelected(); + if (objSel) { app.getObjectPlacer().deleteSelected(); } + else { app.getNpcSpawner().removeCreature(app.getNpcSpawner().getSelectedIndex()); } app.markObjectsDirty(); } - if (ImGui::MenuItem("Deselect")) + if (ImGui::MenuItem("Deselect")) { app.getObjectPlacer().clearSelection(); + app.getNpcSpawner().clearSelection(); + } ImGui::EndPopup(); } diff --git a/tools/editor/object_placer.hpp b/tools/editor/object_placer.hpp index 1e5c6ae3..208aace6 100644 --- a/tools/editor/object_placer.hpp +++ b/tools/editor/object_placer.hpp @@ -54,6 +54,7 @@ public: bool loadFromFile(const std::string& path); const std::vector& getObjects() const { return objects_; } + void clearAll() { objects_.clear(); undoStack_.clear(); selectedIdx_ = -1; } size_t objectCount() const { return objects_.size(); } float getPlacementRotationY() const { return placementRotY_; }