From 533c218983cb37e294328cfc4d393e3d87c899f0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 13:52:02 -0700 Subject: [PATCH] feat(editor): select all, recent zones, minimap selection highlights - Ctrl+A selects all placed objects, context menu has Select All item - selectAll() added to ObjectPlacer, works with multi-select transforms - Recent Zones submenu in File menu (last 8 loaded zones, deduplicated) - Minimap: selected objects shown as white dots with gold ring outline vs yellow dots for unselected objects - Help panel updated with Ctrl+A and Ctrl+Shift+Click documentation --- tools/editor/editor_app.cpp | 11 +++++++++++ tools/editor/editor_app.hpp | 5 +++++ tools/editor/editor_ui.cpp | 24 ++++++++++++++++++++++-- tools/editor/object_placer.cpp | 9 +++++++++ tools/editor/object_placer.hpp | 3 ++- 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 6098b72a..fc406216 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -302,6 +302,10 @@ void EditorApp::processEvents() { ui_.openNewTerrainDialog(); if (sc == SDL_SCANCODE_O && (event.key.keysym.mod & KMOD_CTRL)) ui_.openLoadDialog(); + if (sc == SDL_SCANCODE_A && (event.key.keysym.mod & KMOD_CTRL)) { + objectPlacer_.selectAll(); + showToast("Selected " + std::to_string(objectPlacer_.selectionCount()) + " objects"); + } // Ctrl+Y = Redo (alternate binding) if (sc == SDL_SCANCODE_Y && (event.key.keysym.mod & KMOD_CTRL)) { if (terrainEditor_.history().canRedo()) { @@ -699,6 +703,13 @@ void EditorApp::loadADT(const std::string& mapName, int tileX, int tileY) { loadedTileX_ = tileX; loadedTileY_ = tileY; + // Track recent zones (deduplicate, max 8) + recentZones_.erase(std::remove_if(recentZones_.begin(), recentZones_.end(), + [&](const RecentZone& rz) { return rz.mapName == mapName && rz.tileX == tileX && rz.tileY == tileY; }), + recentZones_.end()); + recentZones_.insert(recentZones_.begin(), {mapName, tileX, tileY}); + if (recentZones_.size() > 8) recentZones_.resize(8); + // Position camera at terrain center using actual chunk positions if (mesh.validChunkCount > 0) { auto& firstChunk = mesh.chunks[0]; diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index b681c076..85ec9d65 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -138,12 +138,17 @@ private: bool autoSaveEnabled_ = true; bool showQuitConfirm_ = false; + // Recent zones + struct RecentZone { std::string mapName; int tileX; int tileY; }; + std::vector recentZones_; + // Toast notifications struct Toast { std::string msg; float timer; }; std::vector toasts_; public: void showToast(const std::string& msg, float duration = 3.0f); const std::vector& getToasts() const { return toasts_; } + const std::vector& getRecentZones() const { return recentZones_; } void updateToasts(float dt); private: size_t lastObjCount_ = 0; diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index e3df77c3..49f72ec9 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -254,6 +254,16 @@ void EditorUI::renderMenuBar(EditorApp& app) { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("Recent Zones", !app.getRecentZones().empty())) { + for (const auto& rz : app.getRecentZones()) { + char label[128]; + std::snprintf(label, sizeof(label), "%s [%d, %d]", + rz.mapName.c_str(), rz.tileX, rz.tileY); + if (ImGui::MenuItem(label)) + app.loadADT(rz.mapName, rz.tileX, rz.tileY); + } + ImGui::EndMenu(); + } if (ImGui::BeginMenu("Content Packs (.wcp)")) { static char wcpImportPath[256] = "content.wcp"; ImGui::InputText("Path##wcp", wcpImportPath, sizeof(wcpImportPath)); @@ -489,7 +499,9 @@ void EditorUI::renderMenuBar(EditorApp& app) { ImGui::Text("Quick Actions:"); ImGui::BulletText("Ctrl+N — new terrain"); ImGui::BulletText("Ctrl+O — load map tile"); + ImGui::BulletText("Ctrl+A — select all objects"); ImGui::BulletText("Alt+Click — eyedropper (paint mode)"); + ImGui::BulletText("Ctrl+Shift+Click — add to selection"); ImGui::BulletText("Middle-drag — orbit camera"); ImGui::Separator(); ImGui::Text("View:"); @@ -2044,6 +2056,9 @@ void EditorUI::renderContextMenu(EditorApp& app) { else { app.getNpcSpawner().removeCreature(app.getNpcSpawner().getSelectedIndex()); } app.markObjectsDirty(); } + if (ImGui::MenuItem("Select All", "Ctrl+A")) { + app.getObjectPlacer().selectAll(); + } if (ImGui::MenuItem("Deselect")) { app.getObjectPlacer().clearSelection(); app.getNpcSpawner().clearSelection(); @@ -2110,7 +2125,7 @@ void EditorUI::renderMinimap(EditorApp& app) { } } - // Draw objects as yellow dots + // Draw objects (yellow=normal, white+ring=selected) float tileNW_X = (32.0f - static_cast(terrain->coord.y)) * 533.33333f; float tileNW_Y = (32.0f - static_cast(terrain->coord.x)) * 533.33333f; for (const auto& obj : app.getObjectPlacer().getObjects()) { @@ -2118,7 +2133,12 @@ void EditorUI::renderMinimap(EditorApp& app) { float v = (tileNW_Y - obj.position.y) / 533.33333f; if (u >= 0 && u <= 1 && v >= 0 && v <= 1) { ImVec2 pt(origin.x + v * avail.x, origin.y + u * (16 * cellH)); - dl->AddCircleFilled(pt, 2.0f, IM_COL32(255, 220, 50, 200)); + if (obj.selected) { + dl->AddCircleFilled(pt, 3.5f, IM_COL32(255, 255, 255, 230)); + dl->AddCircle(pt, 5.0f, IM_COL32(255, 200, 50, 200), 0, 1.5f); + } else { + dl->AddCircleFilled(pt, 2.0f, IM_COL32(255, 220, 50, 200)); + } } } // Draw NPCs as red dots diff --git a/tools/editor/object_placer.cpp b/tools/editor/object_placer.cpp index 61790f36..8a4bb1dc 100644 --- a/tools/editor/object_placer.cpp +++ b/tools/editor/object_placer.cpp @@ -111,6 +111,15 @@ PlacedObject* ObjectPlacer::getSelected() { return &objects_[selectedIdx_]; } +void ObjectPlacer::selectAll() { + clearSelection(); + for (int i = 0; i < static_cast(objects_.size()); i++) { + objects_[i].selected = true; + selectedIndices_.push_back(i); + } + if (!objects_.empty()) selectedIdx_ = 0; +} + void ObjectPlacer::moveSelected(const glm::vec3& delta) { if (selectedIndices_.size() > 1) { for (int idx : selectedIndices_) objects_[idx].position += delta; diff --git a/tools/editor/object_placer.hpp b/tools/editor/object_placer.hpp index cd8a33e6..a243a5e8 100644 --- a/tools/editor/object_placer.hpp +++ b/tools/editor/object_placer.hpp @@ -60,7 +60,8 @@ public: const std::vector& getObjects() const { return objects_; } std::vector& getObjects() { return objects_; } - void clearAll() { objects_.clear(); undoStack_.clear(); selectedIdx_ = -1; } + void selectAll(); + void clearAll() { objects_.clear(); undoStack_.clear(); selectedIdx_ = -1; selectedIndices_.clear(); } size_t objectCount() const { return objects_.size(); } float getPlacementRotationY() const { return placementRotY_; }