yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
song_browser_view.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstring>
5
6#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
11
12namespace yaze {
13namespace editor {
14namespace music {
15
20
22 // Search filter
23 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
24 ImGui::InputTextWithHint("##SongFilter", ICON_MD_SEARCH " Search songs...",
26
27 // Update filter cache if needed
28 if (std::string(search_buffer_) != last_search_buffer_ ||
32 }
33
34 // Bank Space Management Section
35 if (ImGui::CollapsingHeader(ICON_MD_STORAGE " Bank Space")) {
36 ImGui::Indent(8.0f);
37
38 // Check for expanded music patch
39 if (bank.HasExpandedMusicPatch()) {
40 ImGui::TextColored(GetSuccessColor(), ICON_MD_CHECK_CIRCLE
41 " Oracle of Secrets expanded music detected");
42 const auto& info = bank.GetExpandedBankInfo();
43 ImGui::TextDisabled("Expanded bank at $%06X, Aux at $%06X",
44 info.main_rom_offset, info.aux_rom_offset);
45 ImGui::Spacing();
46 }
47
48 // Display space for each bank
49 static const char* bank_names[] = {"Overworld", "Dungeon", "Credits",
50 "Expanded", "Auxiliary"};
51 static const MusicBank::Bank banks[] = {
55
56 int num_banks = bank.HasExpandedMusicPatch() ? 5 : 3;
57
58 for (int i = 0; i < num_banks; ++i) {
59 auto space = bank.CalculateSpaceUsage(banks[i]);
60 if (space.total_bytes == 0)
61 continue; // Skip empty/invalid banks
62
63 // Progress bar color based on usage
64 ImVec4 bar_color =
65 space.is_critical
67 : (space.is_warning ? GetWarningColor() : GetSuccessColor());
68
69 ImGui::Text("%s:", bank_names[i]);
70 ImGui::SameLine(100);
71
72 // Progress bar
73 {
74 gui::StyleColorGuard bar_guard(ImGuiCol_PlotHistogram, bar_color);
75 float fraction = space.usage_percent / 100.0f;
76 std::string overlay =
77 absl::StrFormat("%d / %d bytes (%.1f%%)", space.used_bytes,
78 space.total_bytes, space.usage_percent);
79 ImGui::ProgressBar(fraction, ImVec2(-1, 0), overlay.c_str());
80 }
81
82 // Warning/critical messages
83 if (space.is_critical) {
84 ImGui::TextColored(GetErrorColor(), ICON_MD_ERROR " %s",
85 space.recommendation.c_str());
86 } else if (space.is_warning) {
87 ImGui::TextColored(GetWarningColor(), ICON_MD_WARNING " %s",
88 space.recommendation.c_str());
89 }
90 }
91
92 // Overall status
93 ImGui::Spacing();
94 if (!bank.AllSongsFit()) {
95 ImGui::TextColored(GetErrorColor(),
96 ICON_MD_ERROR " Some banks are overflowing!");
97 ImGui::TextDisabled("Songs won't fit in ROM. Remove or shorten songs.");
98 } else {
99 ImGui::TextColored(GetSuccessColor(),
100 ICON_MD_CHECK " All songs fit in ROM");
101 }
102
103 ImGui::Unindent(8.0f);
104 }
105
106 ImGui::Separator();
107
108 // Toolbar
109 if (ImGui::Button(ICON_MD_ADD " New Song")) {
110 int new_idx = bank.CreateNewSong("New Song", MusicBank::Bank::Dungeon);
111 if (new_idx >= 0) {
112 selected_song_index_ = new_idx;
114 on_song_selected_(new_idx);
115 if (on_edit_)
116 on_edit_();
117 }
118 }
119 ImGui::SameLine();
120 if (ImGui::Button(ICON_MD_FILE_UPLOAD " Import")) {
121 // TODO: Implement SPC/MML import
122 }
123
124 ImGui::Separator();
125
126 ImGui::BeginChild("SongList", ImVec2(0, 0), true);
127
128 // Vanilla Songs Section
129 if (ImGui::CollapsingHeader(ICON_MD_LIBRARY_MUSIC " Vanilla Songs",
130 ImGuiTreeNodeFlags_DefaultOpen)) {
131 const float item_height = ImGui::GetTextLineHeightWithSpacing();
132 ImGuiListClipper clipper;
133 clipper.Begin(static_cast<int>(filtered_vanilla_indices_.size()),
134 item_height);
135
136 while (clipper.Step()) {
137 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
138 int i = filtered_vanilla_indices_[row];
139 const auto* song = bank.GetSong(i);
140 if (!song)
141 continue;
142
143 std::string display_name =
144 absl::StrFormat("%02X: %s", i + 1, song->name);
145
146 // Icon + label
147 std::string label = absl::StrFormat(ICON_MD_MUSIC_NOTE " %s##vanilla%d",
148 display_name, i);
149 bool is_selected = (selected_song_index_ == i);
150
151 // Push ID to avoid conflicts with same-named items across filter resets
152 ImGui::PushID(i);
153 if (ImGui::Selectable(label.c_str(), is_selected)) {
155 if (on_song_selected_) {
157 }
158 }
159
160 // Double-click opens tracker
161 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
162 if (on_open_tracker_) {
164 }
165 }
166
167 // Context menu for vanilla songs
168 if (ImGui::BeginPopupContextItem()) {
169 if (ImGui::MenuItem(ICON_MD_MUSIC_NOTE " Open Tracker")) {
172 }
173 if (ImGui::MenuItem(ICON_MD_PIANO " Open Piano Roll")) {
176 }
177 ImGui::Separator();
178 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Duplicate as Custom")) {
179 bank.DuplicateSong(i);
180 if (on_edit_)
181 on_edit_();
182 // Force cache rebuild on next frame (or immediately).
183 last_search_buffer_.clear();
184 }
185 ImGui::Separator();
186 if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Export to ASM...")) {
187 if (on_export_asm_)
189 }
190 ImGui::EndPopup();
191 }
192 ImGui::PopID();
193 }
194 }
195 }
196
197 // Custom Songs Section
198 if (ImGui::CollapsingHeader(ICON_MD_EDIT " Custom Songs",
199 ImGuiTreeNodeFlags_DefaultOpen)) {
200 if (filtered_custom_indices_.empty()) {
201 ImGui::TextDisabled("No custom songs match filter");
202 ImGui::TextDisabled("Click 'New Song' or duplicate a vanilla song");
203 } else {
204 const float item_height = ImGui::GetTextLineHeightWithSpacing();
205 ImGuiListClipper clipper;
206 clipper.Begin(static_cast<int>(filtered_custom_indices_.size()),
207 item_height);
208
209 while (clipper.Step()) {
210 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
211 int i = filtered_custom_indices_[row];
212 const auto* song = bank.GetSong(i);
213 if (!song)
214 continue;
215
216 std::string display_name =
217 absl::StrFormat("%02X: %s", i + 1, song->name);
218
219 // Custom song icon + label (different color)
220 std::string label = absl::StrFormat(
221 ICON_MD_AUDIOTRACK " %s##custom%d", display_name, i);
222 bool is_selected = (selected_song_index_ == i);
223
224 gui::StyleColorGuard text_guard(ImGuiCol_Text, GetSuccessColor());
225 ImGui::PushID(i);
226 if (ImGui::Selectable(label.c_str(), is_selected)) {
228 if (on_song_selected_) {
230 }
231 }
232
233 // Double-click opens tracker
234 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
235 if (on_open_tracker_) {
237 }
238 }
239
240 // Context menu for custom songs (includes delete/rename)
241 if (ImGui::BeginPopupContextItem()) {
242 if (ImGui::MenuItem(ICON_MD_MUSIC_NOTE " Open Tracker")) {
245 }
246 if (ImGui::MenuItem(ICON_MD_PIANO " Open Piano Roll")) {
249 }
250 ImGui::Separator();
251 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Duplicate")) {
252 bank.DuplicateSong(i);
253 if (on_edit_)
254 on_edit_();
255 last_search_buffer_.clear(); // Force rebuild.
256 }
257 if (ImGui::MenuItem(ICON_MD_DRIVE_FILE_RENAME_OUTLINE " Rename")) {
259 // TODO: Open rename popup
260 }
261 ImGui::Separator();
262 if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Export to ASM...")) {
263 if (on_export_asm_)
265 }
266 if (ImGui::MenuItem(ICON_MD_FILE_UPLOAD " Import from ASM...")) {
267 if (on_import_asm_)
269 }
270 ImGui::Separator();
271 if (ImGui::MenuItem(ICON_MD_DELETE " Delete")) {
272 (void)bank.DeleteSong(i);
273 if (selected_song_index_ == i) {
275 }
276 if (on_edit_)
277 on_edit_();
278 last_search_buffer_.clear(); // Force rebuild.
279 }
280 ImGui::EndPopup();
281 }
282 ImGui::PopID();
283 }
284 }
285 }
286 }
287
288 ImGui::EndChild();
289}
290
291bool SongBrowserView::MatchesSearch(const std::string& name) const {
292 if (search_buffer_[0] == '\0')
293 return true;
294
295 // Case-insensitive search
296 std::string lower_name = name;
297 std::string lower_search(search_buffer_);
298 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
299 ::tolower);
300 std::transform(lower_search.begin(), lower_search.end(), lower_search.begin(),
301 ::tolower);
302
303 return lower_name.find(lower_search) != std::string::npos;
304}
305
309
310 for (size_t i = 0; i < bank.GetSongCount(); ++i) {
311 const auto* song = bank.GetSong(static_cast<int>(i));
312 if (!song)
313 continue;
314
315 std::string display_name =
316 absl::StrFormat("%02X: %s", i + 1, song->name);
317 if (!MatchesSearch(display_name))
318 continue;
319
320 if (bank.IsVanilla(static_cast<int>(i))) {
321 filtered_vanilla_indices_.push_back(static_cast<int>(i));
322 } else {
323 filtered_custom_indices_.push_back(static_cast<int>(i));
324 }
325 }
326}
327
328} // namespace music
329} // namespace editor
330} // namespace yaze
std::function< void(int)> on_open_piano_roll_
std::function< void(int)> on_song_selected_
std::function< void(int)> on_export_asm_
bool MatchesSearch(const std::string &name) const
std::function< void(int)> on_open_tracker_
void RebuildFilterCache(const MusicBank &bank)
void Draw(MusicBank &bank)
Draw the song browser.
std::function< void(int)> on_import_asm_
RAII guard for ImGui style colors.
Definition style_guard.h:27
Manages the collection of songs, instruments, and samples from a ROM.
Definition music_bank.h:27
bool IsVanilla(int index) const
Check if a song is a vanilla (original) song.
MusicSong * GetSong(int index)
Get a song by index.
size_t GetSongCount() const
Get the number of songs loaded.
Definition music_bank.h:97
const ExpandedBankInfo & GetExpandedBankInfo() const
Get information about the expanded bank configuration.
Definition music_bank.h:160
bool AllSongsFit() const
Check if all songs fit in their banks.
absl::Status DeleteSong(int index)
Delete a song by index.
SpaceInfo CalculateSpaceUsage(Bank bank) const
Calculate space usage for a bank.
int CreateNewSong(const std::string &name, Bank bank)
Create a new empty song.
bool HasExpandedMusicPatch() const
Check if the ROM has the Oracle of Secrets expanded music patch.
Definition music_bank.h:147
int DuplicateSong(int index)
Duplicate a song.
#define ICON_MD_PIANO
Definition icons.h:1462
#define ICON_MD_LIBRARY_MUSIC
Definition icons.h:1080
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_AUDIOTRACK
Definition icons.h:213
#define ICON_MD_FILE_UPLOAD
Definition icons.h:749
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_DRIVE_FILE_RENAME_OUTLINE
Definition icons.h:630
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:48
ImVec4 GetErrorColor()
Definition ui_helpers.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53