From 7a624adadac83fe9e0b08e8cc2bc4c1421b6da55 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 12:43:58 -0700 Subject: [PATCH] feat(editor): zone audio panel scans Data dir for music + ambience files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Zone Audio panel previously only offered five hardcoded preset paths. Replaced with a recursive directory walk of /Sound/ Music and /Sound/Ambience for any .mp3/.wav/.ogg files. Results cached after the first scan; a Refresh button forces a rebuild for when the user drops new files in. Two ImGui combos populate from the scan: - "Music File" — picks from Sound/Music - "Ambience File" — picks from Sound/Ambience Each combo has a "(none)" option to clear. Selecting an entry updates both the manifest field and the existing manual text input buffer below, so users can fine-tune the path after picking from the combo. EditorApp gets a public getDataPath() so the UI can reach the configured asset root without exposing the rest of the private state. Audio playback preview is the remaining piece — needs SDL_mixer or similar wired into the editor; not in this commit. --- tools/editor/editor_app.hpp | 3 ++ tools/editor/editor_ui.cpp | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index 8b923e42..5d0c573c 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -186,6 +186,9 @@ private: float waterHeight_ = 100.0f; uint16_t waterType_ = 0; std::string dataPath_; +public: + const std::string& getDataPath() const { return dataPath_; } +private: std::string loadedMap_; int loadedTileX_ = -1; diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 631b26a7..c8abfa21 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -2932,6 +2932,86 @@ void EditorUI::renderPropertiesPanel(EditorApp& app) { std::strncpy(ambDayBuf, manifest.ambienceDay.c_str(), sizeof(ambDayBuf) - 1); std::strncpy(ambNightBuf, manifest.ambienceNight.c_str(), sizeof(ambNightBuf) - 1); + // Lazy-loaded scan of available music files. Walks + // /Sound/Music recursively and grabs any .mp3/.wav/ + // .ogg. Cached after first build; "Refresh" rebuilds the + // list. The dropdown then offers each scanned file as a + // selectable preset alongside the manual text input below. + static std::vector musicScan; + static std::vector ambienceScan; + static bool scanned = false; + auto rescan = [&]() { + musicScan.clear(); + ambienceScan.clear(); + namespace fs = std::filesystem; + std::string dp = app.getDataPath(); + if (dp.empty()) dp = "Data"; + std::string musicRoot = dp + "/Sound/Music"; + std::string ambRoot = dp + "/Sound/Ambience"; + auto walk = [](const std::string& root, + std::vector& out) { + std::error_code ec; + if (!fs::exists(root)) return; + for (const auto& e : fs::recursive_directory_iterator(root, ec)) { + if (!e.is_regular_file()) continue; + std::string ext = e.path().extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (ext == ".mp3" || ext == ".wav" || ext == ".ogg") { + out.push_back(e.path().string()); + } + } + std::sort(out.begin(), out.end()); + }; + walk(musicRoot, musicScan); + walk(ambRoot, ambienceScan); + scanned = true; + }; + if (!scanned) rescan(); + if (ImGui::SmallButton("Refresh##audioScan")) rescan(); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), + "%zu music / %zu ambience files", + musicScan.size(), ambienceScan.size()); + // Music dropdown. + if (ImGui::BeginCombo("Music File##audio", + manifest.musicTrack.empty() + ? "(none)" + : manifest.musicTrack.c_str())) { + if (ImGui::Selectable("(none)", manifest.musicTrack.empty())) { + manifest.musicTrack.clear(); + musicBuf[0] = 0; + } + for (const auto& p : musicScan) { + bool sel = (p == manifest.musicTrack); + if (ImGui::Selectable(p.c_str(), sel)) { + manifest.musicTrack = p; + std::strncpy(musicBuf, p.c_str(), sizeof(musicBuf) - 1); + musicBuf[sizeof(musicBuf) - 1] = 0; + } + } + ImGui::EndCombo(); + } + // Day-ambience dropdown. + if (ImGui::BeginCombo("Ambience File##audio", + manifest.ambienceDay.empty() + ? "(none)" + : manifest.ambienceDay.c_str())) { + if (ImGui::Selectable("(none)", manifest.ambienceDay.empty())) { + manifest.ambienceDay.clear(); + ambDayBuf[0] = 0; + } + for (const auto& p : ambienceScan) { + bool sel = (p == manifest.ambienceDay); + if (ImGui::Selectable(p.c_str(), sel)) { + manifest.ambienceDay = p; + std::strncpy(ambDayBuf, p.c_str(), sizeof(ambDayBuf) - 1); + ambDayBuf[sizeof(ambDayBuf) - 1] = 0; + } + } + ImGui::EndCombo(); + } + if (ImGui::InputText("Music##audio", musicBuf, sizeof(musicBuf))) manifest.musicTrack = musicBuf; if (ImGui::InputText("Ambience (Day)##audio", ambDayBuf, sizeof(ambDayBuf)))