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"
8#include "imgui/imgui.h"
9
10namespace yaze {
11namespace editor {
12namespace music {
13
15
17 // Search filter
18 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
19 ImGui::InputTextWithHint("##SongFilter", ICON_MD_SEARCH " Search songs...",
21
22 // Bank Space Management Section
23 if (ImGui::CollapsingHeader(ICON_MD_STORAGE " Bank Space")) {
24 ImGui::Indent(8.0f);
25
26 // Check for expanded music patch
27 if (bank.HasExpandedMusicPatch()) {
28 ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f),
29 ICON_MD_CHECK_CIRCLE " Oracle of Secrets expanded music detected");
30 const auto& info = bank.GetExpandedBankInfo();
31 ImGui::TextDisabled("Expanded bank at $%06X, Aux at $%06X",
32 info.main_rom_offset, info.aux_rom_offset);
33 ImGui::Spacing();
34 }
35
36 // Display space for each bank
37 static const char* bank_names[] = {"Overworld", "Dungeon", "Credits",
38 "Expanded", "Auxiliary"};
39 static const MusicBank::Bank banks[] = {
43
44 int num_banks = bank.HasExpandedMusicPatch() ? 5 : 3;
45
46 for (int i = 0; i < num_banks; ++i) {
47 auto space = bank.CalculateSpaceUsage(banks[i]);
48 if (space.total_bytes == 0) continue; // Skip empty/invalid banks
49
50 // Progress bar color based on usage
51 ImVec4 bar_color;
52 if (space.is_critical) {
53 bar_color = ImVec4(0.9f, 0.2f, 0.2f, 1.0f); // Red
54 } else if (space.is_warning) {
55 bar_color = ImVec4(0.9f, 0.7f, 0.2f, 1.0f); // Yellow
56 } else {
57 bar_color = ImVec4(0.3f, 0.7f, 0.3f, 1.0f); // Green
58 }
59
60 ImGui::Text("%s:", bank_names[i]);
61 ImGui::SameLine(100);
62
63 // Progress bar
64 ImGui::PushStyleColor(ImGuiCol_PlotHistogram, bar_color);
65 float fraction = space.usage_percent / 100.0f;
66 std::string overlay = absl::StrFormat(
67 "%d / %d bytes (%.1f%%)", space.used_bytes, space.total_bytes,
68 space.usage_percent);
69 ImGui::ProgressBar(fraction, ImVec2(-1, 0), overlay.c_str());
70 ImGui::PopStyleColor();
71
72 // Warning/critical messages
73 if (space.is_critical) {
74 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f),
75 ICON_MD_ERROR " %s", space.recommendation.c_str());
76 } else if (space.is_warning) {
77 ImGui::TextColored(ImVec4(0.9f, 0.7f, 0.2f, 1.0f),
78 ICON_MD_WARNING " %s", space.recommendation.c_str());
79 }
80 }
81
82 // Overall status
83 ImGui::Spacing();
84 if (!bank.AllSongsFit()) {
85 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f),
86 ICON_MD_ERROR " Some banks are overflowing!");
87 ImGui::TextDisabled("Songs won't fit in ROM. Remove or shorten songs.");
88 } else {
89 ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f),
90 ICON_MD_CHECK " All songs fit in ROM");
91 }
92
93 ImGui::Unindent(8.0f);
94 }
95
96 ImGui::Separator();
97
98 // Toolbar
99 if (ImGui::Button(ICON_MD_ADD " New Song")) {
100 int new_idx = bank.CreateNewSong("New Song", MusicBank::Bank::Dungeon);
101 if (new_idx >= 0) {
102 selected_song_index_ = new_idx;
104 if (on_edit_) on_edit_();
105 }
106 }
107 ImGui::SameLine();
108 if (ImGui::Button(ICON_MD_FILE_UPLOAD " Import")) {
109 // TODO: Implement SPC/MML import
110 }
111
112 ImGui::Separator();
113
114 ImGui::BeginChild("SongList", ImVec2(0, 0), true);
115
116 // Vanilla Songs Section
117 if (ImGui::CollapsingHeader(ICON_MD_LIBRARY_MUSIC " Vanilla Songs",
118 ImGuiTreeNodeFlags_DefaultOpen)) {
119 for (size_t i = 0; i < bank.GetSongCount(); ++i) {
120 const auto* song = bank.GetSong(static_cast<int>(i));
121 if (!song || !bank.IsVanilla(static_cast<int>(i))) continue;
122
123 // Filter check
124 std::string display_name = absl::StrFormat("%02X: %s", i + 1, song->name);
125 if (!MatchesSearch(display_name)) continue;
126
127 // Icon + label
128 std::string label =
129 absl::StrFormat(ICON_MD_MUSIC_NOTE " %s##vanilla%zu", display_name, i);
130 bool is_selected = (selected_song_index_ == static_cast<int>(i));
131 if (ImGui::Selectable(label.c_str(), is_selected)) {
132 selected_song_index_ = static_cast<int>(i);
133 if (on_song_selected_) {
135 }
136 }
137
138 // Double-click opens tracker
139 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
140 if (on_open_tracker_) {
141 on_open_tracker_(static_cast<int>(i));
142 }
143 }
144
145 // Context menu for vanilla songs
146 if (ImGui::BeginPopupContextItem()) {
147 if (ImGui::MenuItem(ICON_MD_MUSIC_NOTE " Open Tracker")) {
148 if (on_open_tracker_) on_open_tracker_(static_cast<int>(i));
149 }
150 if (ImGui::MenuItem(ICON_MD_PIANO " Open Piano Roll")) {
151 if (on_open_piano_roll_) on_open_piano_roll_(static_cast<int>(i));
152 }
153 ImGui::Separator();
154 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Duplicate as Custom")) {
155 bank.DuplicateSong(static_cast<int>(i));
156 if (on_edit_) on_edit_();
157 }
158 ImGui::Separator();
159 if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Export to ASM...")) {
160 if (on_export_asm_) on_export_asm_(static_cast<int>(i));
161 }
162 ImGui::EndPopup();
163 }
164 }
165 }
166
167 // Custom Songs Section
168 if (ImGui::CollapsingHeader(ICON_MD_EDIT " Custom Songs",
169 ImGuiTreeNodeFlags_DefaultOpen)) {
170 bool has_custom = false;
171 for (size_t i = 0; i < bank.GetSongCount(); ++i) {
172 const auto* song = bank.GetSong(static_cast<int>(i));
173 if (!song || bank.IsVanilla(static_cast<int>(i))) continue;
174
175 has_custom = true;
176
177 // Filter check
178 std::string display_name = absl::StrFormat("%02X: %s", i + 1, song->name);
179 if (!MatchesSearch(display_name)) continue;
180
181 // Custom song icon + label (different color)
182 std::string label =
183 absl::StrFormat(ICON_MD_AUDIOTRACK " %s##custom%zu", display_name, i);
184 bool is_selected = (selected_song_index_ == static_cast<int>(i));
185
186 // Highlight custom songs with a subtle green color
187 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.9f, 0.6f, 1.0f));
188 if (ImGui::Selectable(label.c_str(), is_selected)) {
189 selected_song_index_ = static_cast<int>(i);
190 if (on_song_selected_) {
192 }
193 }
194 ImGui::PopStyleColor();
195
196 // Double-click opens tracker
197 if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
198 if (on_open_tracker_) {
199 on_open_tracker_(static_cast<int>(i));
200 }
201 }
202
203 // Context menu for custom songs (includes delete/rename)
204 if (ImGui::BeginPopupContextItem()) {
205 if (ImGui::MenuItem(ICON_MD_MUSIC_NOTE " Open Tracker")) {
206 if (on_open_tracker_) on_open_tracker_(static_cast<int>(i));
207 }
208 if (ImGui::MenuItem(ICON_MD_PIANO " Open Piano Roll")) {
209 if (on_open_piano_roll_) on_open_piano_roll_(static_cast<int>(i));
210 }
211 ImGui::Separator();
212 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Duplicate")) {
213 bank.DuplicateSong(static_cast<int>(i));
214 if (on_edit_) on_edit_();
215 }
216 if (ImGui::MenuItem(ICON_MD_DRIVE_FILE_RENAME_OUTLINE " Rename")) {
217 rename_target_index_ = static_cast<int>(i);
218 // TODO: Open rename popup
219 }
220 ImGui::Separator();
221 if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Export to ASM...")) {
222 if (on_export_asm_) on_export_asm_(static_cast<int>(i));
223 }
224 if (ImGui::MenuItem(ICON_MD_FILE_UPLOAD " Import from ASM...")) {
225 if (on_import_asm_) on_import_asm_(static_cast<int>(i));
226 }
227 ImGui::Separator();
228 if (ImGui::MenuItem(ICON_MD_DELETE " Delete")) {
229 (void)bank.DeleteSong(static_cast<int>(i));
230 if (selected_song_index_ == static_cast<int>(i)) {
232 }
233 if (on_edit_) on_edit_();
234 }
235 ImGui::EndPopup();
236 }
237 }
238
239 if (!has_custom) {
240 ImGui::TextDisabled("No custom songs yet");
241 ImGui::TextDisabled("Click 'New Song' or duplicate a vanilla song");
242 }
243 }
244
245 ImGui::EndChild();
246}
247
248bool SongBrowserView::MatchesSearch(const std::string& name) const {
249 if (search_buffer_[0] == '\0') return true;
250
251 // Case-insensitive search
252 std::string lower_name = name;
253 std::string lower_search(search_buffer_);
254 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
255 ::tolower);
256 std::transform(lower_search.begin(), lower_search.end(), lower_search.begin(),
257 ::tolower);
258
259 return lower_name.find(lower_search) != std::string::npos;
260}
261
262} // namespace music
263} // namespace editor
264} // 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 Draw(MusicBank &bank)
Draw the song browser.
std::function< void(int)> on_import_asm_
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