yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_group_panel.cc
Go to the documentation of this file.
2
3#include <chrono>
4
5#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
13
14namespace yaze {
15namespace editor {
16
17using namespace yaze::gui;
23
24PaletteGroupPanel::PaletteGroupPanel(const std::string& group_name,
25 const std::string& display_name, Rom* rom,
26 zelda3::GameData* game_data)
27 : group_name_(group_name), display_name_(display_name), rom_(rom),
28 game_data_(game_data) {
29 // Note: We can't call GetPaletteGroup() here because it's a pure virtual
30 // function and the derived class isn't fully constructed yet. Original
31 // palettes will be loaded on first Draw() call instead.
32}
33
34void PaletteGroupPanel::Draw(bool* p_open) {
35 if (!rom_ || !rom_->is_loaded()) {
36 return;
37 }
38
39 // PaletteManager handles initialization of original palettes
40 // No need for local snapshot management anymore
41
42 // Main card window
43 // Note: Window management is handled by PanelManager/EditorPanel
44
46 ImGui::Separator();
47
48 // Two-column layout: Grid on left, picker on right
49 if (ImGui::BeginTable(
50 "##PalettePanelLayout", 2,
51 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
52 ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
53 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
54 0.4f);
55
56 ImGui::TableNextRow();
57 ImGui::TableNextColumn();
58
59 // Left: Palette selector + grid
61 ImGui::Separator();
63
64 ImGui::TableNextColumn();
65
66 // Right: Color picker + info
67 if (selected_color_ >= 0) {
69 ImGui::Separator();
71 ImGui::Separator();
73 } else {
74 ImGui::TextDisabled("Select a color to edit");
75 ImGui::Separator();
77 }
78
79 // Custom panels from derived classes
81
82 ImGui::EndTable();
83 }
84
85 // Batch operations popup
87}
88
90 // Query PaletteManager for group-specific modification status
92
93 // Save button (primary action)
94 ImGui::BeginDisabled(!has_changes);
95 if (PrimaryButton(absl::StrFormat("%s Save to ROM", ICON_MD_SAVE).c_str())) {
96 auto status = SaveToRom();
97 if (!status.ok()) {
98 // TODO: Show error toast
99 }
100 }
101 ImGui::EndDisabled();
102
103 ImGui::SameLine();
104
105 // Discard button (danger action)
106 ImGui::BeginDisabled(!has_changes);
107 if (DangerButton(absl::StrFormat("%s Discard", ICON_MD_UNDO).c_str())) {
109 }
110 ImGui::EndDisabled();
111
112 ImGui::SameLine();
113
114 // Modified indicator badge (show modified color count for this group)
115 if (has_changes) {
116 size_t modified_count = 0;
117 auto* group = GetPaletteGroup();
118 if (group) {
119 for (int p = 0; p < group->size(); p++) {
121 modified_count++;
122 }
123 }
124 }
125 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s %zu modified",
126 ICON_MD_EDIT, modified_count);
127 }
128
129 ImGui::SameLine();
130 ImGui::Dummy(ImVec2(20, 0)); // Spacer
131 ImGui::SameLine();
132
133 // Undo/Redo (global operations via PaletteManager)
134 ImGui::BeginDisabled(!gfx::PaletteManager::Get().CanUndo());
135 if (ThemedIconButton(ICON_MD_UNDO, "Undo")) {
136 Undo();
137 }
138 ImGui::EndDisabled();
139
140 ImGui::SameLine();
141 ImGui::BeginDisabled(!gfx::PaletteManager::Get().CanRedo());
142 if (ThemedIconButton(ICON_MD_REDO, "Redo")) {
143 Redo();
144 }
145 ImGui::EndDisabled();
146
147 ImGui::SameLine();
148 ImGui::Dummy(ImVec2(20, 0)); // Spacer
149 ImGui::SameLine();
150
151 // Export/Import
152 if (ThemedIconButton(ICON_MD_FILE_DOWNLOAD, "Export to clipboard")) {
154 }
155
156 ImGui::SameLine();
157 if (ThemedIconButton(ICON_MD_FILE_UPLOAD, "Import from clipboard")) {
159 }
160
161 ImGui::SameLine();
162 if (ThemedIconButton(ICON_MD_MORE_VERT, "Batch operations")) {
163 ImGui::OpenPopup("BatchOperations");
164 }
165
166 // Custom toolbar buttons from derived classes
168}
169
171 auto* palette_group = GetPaletteGroup();
172 if (!palette_group)
173 return;
174
175 int num_palettes = palette_group->size();
176
177 ImGui::Text("Palette:");
178 ImGui::SameLine();
179
180 ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
181 if (ImGui::BeginCombo(
182 "##PaletteSelect",
183 absl::StrFormat("Palette %d", selected_palette_).c_str())) {
184 for (int i = 0; i < num_palettes; i++) {
185 bool is_selected = (selected_palette_ == i);
186 bool is_modified = IsPaletteModified(i);
187
188 std::string label = absl::StrFormat("Palette %d", i);
189 if (is_modified) {
190 label += " *";
191 }
192
193 if (ImGui::Selectable(label.c_str(), is_selected)) {
195 selected_color_ = -1; // Reset color selection
196 }
197 if (is_selected) {
198 ImGui::SetItemDefaultFocus();
199 }
200 }
201 ImGui::EndCombo();
202 }
203
204 // Show reset button for current palette
205 ImGui::SameLine();
206 ImGui::BeginDisabled(!IsPaletteModified(selected_palette_));
207 if (ThemedIconButton(ICON_MD_RESTORE, "Reset palette to original")) {
209 }
210 ImGui::EndDisabled();
211}
212
214 if (selected_color_ < 0)
215 return;
216
217 auto* palette = GetMutablePalette(selected_palette_);
218 if (!palette)
219 return;
220
221 SectionHeader("Color Editor");
222
223 auto& color = (*palette)[selected_color_];
225
226 // Color picker with hue wheel
228 if (ImGui::ColorPicker4("##picker", &col.x,
229 ImGuiColorEditFlags_NoAlpha |
230 ImGuiColorEditFlags_PickerHueWheel |
231 ImGuiColorEditFlags_DisplayRGB |
232 ImGuiColorEditFlags_DisplayHSV)) {
235 }
236
237 // Current vs Original comparison
238 ImGui::Separator();
239 ImGui::Text("Current vs Original");
240
241 ImGui::ColorButton("##current", col,
242 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
243 ImVec2(60, 40));
244
245 LayoutHelpers::HelpMarker("Current color being edited");
246
247 ImGui::SameLine();
248
249 ImVec4 orig_col = ConvertSnesColorToImVec4(original);
250 if (ImGui::ColorButton(
251 "##original", orig_col,
252 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
253 ImVec2(60, 40))) {
254 // Click to restore original
255 editing_color_ = original;
257 }
258
259 if (ImGui::IsItemHovered()) {
260 ImGui::SetTooltip("Click to restore original color");
261 }
262
263 // Reset button
264 ImGui::BeginDisabled(!IsColorModified(selected_palette_, selected_color_));
265 if (ThemedButton(absl::StrFormat("%s Reset", ICON_MD_RESTORE).c_str(),
266 ImVec2(-1, 0))) {
268 }
269 ImGui::EndDisabled();
270}
271
273 if (selected_color_ < 0)
274 return;
275
276 SectionHeader("Color Information");
277
278 auto col = editing_color_.rgb();
279 int r = static_cast<int>(col.x);
280 int g = static_cast<int>(col.y);
281 int b = static_cast<int>(col.z);
282
283 // RGB values
284 ImGui::Text("RGB (0-255): (%d, %d, %d)", r, g, b);
285 if (ImGui::IsItemClicked()) {
286 ImGui::SetClipboardText(absl::StrFormat("(%d, %d, %d)", r, g, b).c_str());
287 }
288
289 // SNES BGR555 value
290 if (show_snes_format_) {
291 ImGui::Text("SNES BGR555: $%04X", editing_color_.snes());
292 if (ImGui::IsItemClicked()) {
293 ImGui::SetClipboardText(
294 absl::StrFormat("$%04X", editing_color_.snes()).c_str());
295 }
296 }
297
298 // Hex value
299 if (show_hex_format_) {
300 ImGui::Text("Hex: #%02X%02X%02X", r, g, b);
301 if (ImGui::IsItemClicked()) {
302 ImGui::SetClipboardText(
303 absl::StrFormat("#%02X%02X%02X", r, g, b).c_str());
304 }
305 }
306
307 ImGui::TextDisabled("Click any value to copy");
308}
309
311 const auto& metadata = GetMetadata();
312 if (selected_palette_ >= metadata.palettes.size())
313 return;
314
315 const auto& pal_meta = metadata.palettes[selected_palette_];
316
317 SectionHeader("Palette Metadata");
318
319 // Palette ID
320 ImGui::Text("Palette ID: %d", pal_meta.palette_id);
321
322 // Name
323 if (!pal_meta.name.empty()) {
324 ImGui::Text("Name: %s", pal_meta.name.c_str());
325 }
326
327 // Description
328 if (!pal_meta.description.empty()) {
329 ImGui::TextWrapped("%s", pal_meta.description.c_str());
330 }
331
332 ImGui::Separator();
333
334 // Palette dimensions and color depth
335 ImGui::Text("Dimensions: %d colors (%dx%d)", metadata.colors_per_palette,
336 metadata.colors_per_row,
337 (metadata.colors_per_palette + metadata.colors_per_row - 1) /
338 metadata.colors_per_row);
339
340 ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4);
341 ImGui::TextDisabled("(16 colors per palette possible)");
342
343 ImGui::Separator();
344
345 // ROM Address
346 ImGui::Text("ROM Address: $%06X", pal_meta.rom_address);
347 if (ImGui::IsItemClicked()) {
348 ImGui::SetClipboardText(
349 absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
350 }
351 if (ImGui::IsItemHovered()) {
352 ImGui::SetTooltip("Click to copy address");
353 }
354
355 // VRAM Address (if applicable)
356 if (pal_meta.vram_address > 0) {
357 ImGui::Text("VRAM Address: $%04X", pal_meta.vram_address);
358 if (ImGui::IsItemClicked()) {
359 ImGui::SetClipboardText(
360 absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
361 }
362 if (ImGui::IsItemHovered()) {
363 ImGui::SetTooltip("Click to copy VRAM address");
364 }
365 }
366
367 // Usage notes
368 if (!pal_meta.usage_notes.empty()) {
369 ImGui::Separator();
370 ImGui::TextDisabled("Usage Notes:");
371 ImGui::TextWrapped("%s", pal_meta.usage_notes.c_str());
372 }
373}
374
376 if (ImGui::BeginPopup("BatchOperations")) {
377 SectionHeader("Batch Operations");
378
379 if (ThemedButton("Copy Current Palette", ImVec2(-1, 0))) {
381 ImGui::CloseCurrentPopup();
382 }
383
384 if (ThemedButton("Paste to Current Palette", ImVec2(-1, 0))) {
386 ImGui::CloseCurrentPopup();
387 }
388
389 ImGui::Separator();
390
391 if (ThemedButton("Reset All Palettes", ImVec2(-1, 0))) {
393 ImGui::CloseCurrentPopup();
394 }
395
396 ImGui::EndPopup();
397 }
398}
399
400// ========== Palette Operations ==========
401
402void PaletteGroupPanel::SetColor(int palette_index, int color_index,
403 const gfx::SnesColor& new_color) {
404 // Delegate to PaletteManager for centralized tracking and undo/redo
405 auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index,
406 color_index, new_color);
407 if (!status.ok()) {
408 // TODO: Show error notification
409 return;
410 }
411
412 // Auto-save if enabled (PaletteManager doesn't handle this)
413 if (auto_save_enabled_) {
414 WriteColorToRom(palette_index, color_index, new_color);
415 }
416}
417
419 // Delegate to PaletteManager for centralized save operation
421}
422
424 // Delegate to PaletteManager for centralized discard operation
426
427 // Reset selection
428 selected_color_ = -1;
429}
430
431void PaletteGroupPanel::ResetPalette(int palette_index) {
432 // Delegate to PaletteManager for centralized reset operation
434}
435
436void PaletteGroupPanel::ResetColor(int palette_index, int color_index) {
437 // Delegate to PaletteManager for centralized reset operation
439 color_index);
440}
441
442// ========== History Management ==========
443
445 // Delegate to PaletteManager's global undo system
447}
448
450 // Delegate to PaletteManager's global redo system
452}
453
455 // Delegate to PaletteManager's global history
457}
458
459// ========== State Queries ==========
460
461bool PaletteGroupPanel::IsPaletteModified(int palette_index) const {
462 // Query PaletteManager for modification status
464 palette_index);
465}
466
468 int color_index) const {
469 // Query PaletteManager for modification status
471 color_index);
472}
473
475 // Query PaletteManager for group-specific modification status
477}
478
480 // Query PaletteManager for global undo availability
482}
483
485 // Query PaletteManager for global redo availability
487}
488
489// ========== Helper Methods ==========
490
492 auto* palette_group = GetPaletteGroup();
493 if (!palette_group || index < 0 || index >= palette_group->size()) {
494 return nullptr;
495 }
496 return palette_group->mutable_palette(index);
497}
498
500 int color_index) const {
501 // Get original color from PaletteManager's snapshots
502 return gfx::PaletteManager::Get().GetColor(group_name_, palette_index,
503 color_index);
504}
505
506absl::Status PaletteGroupPanel::WriteColorToRom(int palette_index,
507 int color_index,
508 const gfx::SnesColor& color) {
509 uint32_t address =
510 gfx::GetPaletteAddress(group_name_, palette_index, color_index);
511 return rom_->WriteColor(address, color);
512}
513
514// MarkModified and ClearModified removed - PaletteManager handles tracking now
515
516// ========== Export/Import ==========
517
519 // TODO: Implement JSON export
520 return "{}";
521}
522
523absl::Status PaletteGroupPanel::ImportFromJson(const std::string& /*json*/) {
524 // TODO: Implement JSON import
525 return absl::UnimplementedError("Import from JSON not yet implemented");
526}
527
529 auto* palette_group = GetPaletteGroup();
530 if (!palette_group || selected_palette_ >= palette_group->size()) {
531 return "";
532 }
533
534 auto palette = palette_group->palette(selected_palette_);
535 std::string result;
536
537 for (size_t i = 0; i < palette.size(); i++) {
538 result += absl::StrFormat("$%04X", palette[i].snes());
539 if (i < palette.size() - 1) {
540 result += ",";
541 }
542 }
543
544 ImGui::SetClipboardText(result.c_str());
545 return result;
546}
547
549 // TODO: Implement clipboard import
550 return absl::UnimplementedError("Import from clipboard not yet implemented");
551}
552
553// ============================================================================
554// Concrete Palette Panel Implementations
555// ============================================================================
556
557// ========== Overworld Main Palette Panel ==========
558
561
563 : PaletteGroupPanel("ow_main", "Overworld Main Palettes", rom, game_data) {}
564
566 PaletteGroupMetadata metadata;
567 metadata.group_name = "ow_main";
568 metadata.display_name = "Overworld Main Palettes";
569 metadata.colors_per_palette = 8;
570 metadata.colors_per_row = 8;
571
572 // Light World palettes (0-19)
573 for (int i = 0; i < 20; i++) {
574 PaletteMetadata pal;
575 pal.palette_id = i;
576 pal.name = absl::StrFormat("Light World %d", i);
577 pal.description = "Used for Light World overworld graphics";
578 pal.rom_address = 0xDE6C8 + (i * 16); // Base address + offset
579 pal.vram_address = 0;
580 pal.usage_notes = "Modifying these colors affects Light World appearance";
581 metadata.palettes.push_back(pal);
582 }
583
584 // Dark World palettes (20-39)
585 for (int i = 20; i < 40; i++) {
586 PaletteMetadata pal;
587 pal.palette_id = i;
588 pal.name = absl::StrFormat("Dark World %d", i - 20);
589 pal.description = "Used for Dark World overworld graphics";
590 pal.rom_address = 0xDE6C8 + (i * 16);
591 pal.vram_address = 0;
592 pal.usage_notes = "Modifying these colors affects Dark World appearance";
593 metadata.palettes.push_back(pal);
594 }
595
596 // Special World palettes (40-59)
597 for (int i = 40; i < 60; i++) {
598 PaletteMetadata pal;
599 pal.palette_id = i;
600 pal.name = absl::StrFormat("Special %d", i - 40);
601 pal.description = "Used for Special World and triforce room";
602 pal.rom_address = 0xDE6C8 + (i * 16);
603 pal.vram_address = 0;
604 pal.usage_notes = "Modifying these colors affects Special World areas";
605 metadata.palettes.push_back(pal);
606 }
607
608 return metadata;
609}
610
615
617 if (!game_data_) return nullptr;
618 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group("ow_main");
619}
620
622 auto* palette = GetMutablePalette(selected_palette_);
623 if (!palette)
624 return;
625
626 const float button_size = 32.0f;
627 const int colors_per_row = GetColorsPerRow();
628
629 for (int i = 0; i < palette->size(); i++) {
630 bool is_selected = (i == selected_color_);
631 bool is_modified = IsColorModified(selected_palette_, i);
632
633 ImGui::PushID(i);
634
635 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
636 (*palette)[i], is_selected, is_modified,
637 ImVec2(button_size, button_size))) {
638 selected_color_ = i;
639 editing_color_ = (*palette)[i];
640 }
641
642 ImGui::PopID();
643
644 // Wrap to next row
645 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
646 ImGui::SameLine();
647 }
648 }
649}
650
651// ========== Overworld Animated Palette Panel ==========
652
655
657 : PaletteGroupPanel("ow_animated", "Overworld Animated Palettes", rom, game_data) {}
658
660 PaletteGroupMetadata metadata;
661 metadata.group_name = "ow_animated";
662 metadata.display_name = "Overworld Animated Palettes";
663 metadata.colors_per_palette = 8;
664 metadata.colors_per_row = 8;
665
666 // Animated palettes
667 const char* anim_names[] = {"Water", "Lava", "Poison Water", "Ice"};
668 for (int i = 0; i < 4; i++) {
669 PaletteMetadata pal;
670 pal.palette_id = i;
671 pal.name = anim_names[i];
672 pal.description =
673 absl::StrFormat("%s animated palette cycle", anim_names[i]);
674 pal.rom_address = 0xDE86C + (i * 16);
675 pal.vram_address = 0;
676 pal.usage_notes =
677 "These palettes cycle through multiple frames for animation";
678 metadata.palettes.push_back(pal);
679 }
680
681 return metadata;
682}
683
688
690 if (!game_data_) return nullptr;
691 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
692 "ow_animated");
693}
694
696 auto* palette = GetMutablePalette(selected_palette_);
697 if (!palette)
698 return;
699
700 const float button_size = 32.0f;
701 const int colors_per_row = GetColorsPerRow();
702
703 for (int i = 0; i < palette->size(); i++) {
704 bool is_selected = (i == selected_color_);
705 bool is_modified = IsColorModified(selected_palette_, i);
706
707 ImGui::PushID(i);
708
709 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
710 (*palette)[i], is_selected, is_modified,
711 ImVec2(button_size, button_size))) {
712 selected_color_ = i;
713 editing_color_ = (*palette)[i];
714 }
715
716 ImGui::PopID();
717
718 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
719 ImGui::SameLine();
720 }
721 }
722}
723
724// ========== Dungeon Main Palette Panel ==========
725
728
730 : PaletteGroupPanel("dungeon_main", "Dungeon Main Palettes", rom, game_data) {}
731
733 PaletteGroupMetadata metadata;
734 metadata.group_name = "dungeon_main";
735 metadata.display_name = "Dungeon Main Palettes";
736 metadata.colors_per_palette = 16;
737 metadata.colors_per_row = 16;
738
739 // Dungeon palettes (0-19)
740 const char* dungeon_names[] = {
741 "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace",
742 "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire",
743 "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town",
744 "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2",
745 "Generic 3", "Generic 4", "Generic 5", "Generic 6"};
746
747 for (int i = 0; i < 20; i++) {
748 PaletteMetadata pal;
749 pal.palette_id = i;
750 pal.name = dungeon_names[i];
751 pal.description = absl::StrFormat("Dungeon palette %d", i);
752 pal.rom_address = 0xDE604 + (i * 32);
753 pal.vram_address = 0;
754 pal.usage_notes = "16 colors per dungeon palette";
755 metadata.palettes.push_back(pal);
756 }
757
758 return metadata;
759}
760
762 if (!game_data_) return nullptr;
763 return game_data_->palette_groups.get_group("dungeon_main");
764}
765
767 if (!game_data_) return nullptr;
768 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
769 "dungeon_main");
770}
771
773 auto* palette = GetMutablePalette(selected_palette_);
774 if (!palette)
775 return;
776
777 const float button_size = 28.0f;
778 const int colors_per_row = GetColorsPerRow();
779
780 for (int i = 0; i < palette->size(); i++) {
781 bool is_selected = (i == selected_color_);
782 bool is_modified = IsColorModified(selected_palette_, i);
783
784 ImGui::PushID(i);
785
786 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
787 (*palette)[i], is_selected, is_modified,
788 ImVec2(button_size, button_size))) {
789 selected_color_ = i;
790 editing_color_ = (*palette)[i];
791 }
792
793 ImGui::PopID();
794
795 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
796 ImGui::SameLine();
797 }
798 }
799}
800
801// ========== Sprite Palette Panel ==========
802
805
807 : PaletteGroupPanel("global_sprites", "Sprite Palettes", rom, game_data) {}
808
810 PaletteGroupMetadata metadata;
811 metadata.group_name = "global_sprites";
812 metadata.display_name = "Global Sprite Palettes";
813 metadata.colors_per_palette =
814 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48)
815 metadata.colors_per_row = 16; // Display in 16-color rows
816
817 // 2 palette sets: Light World and Dark World
818 const char* sprite_names[] = {"Global Sprites (Light World)",
819 "Global Sprites (Dark World)"};
820
821 for (int i = 0; i < 2; i++) {
822 PaletteMetadata pal;
823 pal.palette_id = i;
824 pal.name = sprite_names[i];
825 pal.description =
826 "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, "
827 "32, 48";
828 pal.rom_address = (i == 0) ? 0xDD218 : 0xDD290; // LW or DW address
829 pal.vram_address = 0; // Loaded dynamically
830 pal.usage_notes =
831 "4 sprite sub-palettes of 15 colors + transparent each. "
832 "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
833 metadata.palettes.push_back(pal);
834 }
835
836 return metadata;
837}
838
840 if (!game_data_) return nullptr;
841 return game_data_->palette_groups.get_group("global_sprites");
842}
843
845 if (!game_data_) return nullptr;
846 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
847 "global_sprites");
848}
849
851 auto* palette = GetMutablePalette(selected_palette_);
852 if (!palette)
853 return;
854
855 const float button_size = 28.0f;
856 const int colors_per_row = GetColorsPerRow();
857
858 for (int i = 0; i < palette->size(); i++) {
859 bool is_selected = (i == selected_color_);
860 bool is_modified = IsColorModified(selected_palette_, i);
861
862 ImGui::PushID(i);
863
864 // Draw transparent color indicator at start of each 16-color row (0, 16,
865 // 32, 48, ...)
866 bool is_transparent_slot = (i % 16 == 0);
867 if (is_transparent_slot) {
868 ImGui::BeginGroup();
869 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
870 (*palette)[i], is_selected, is_modified,
871 ImVec2(button_size, button_size))) {
872 selected_color_ = i;
873 editing_color_ = (*palette)[i];
874 }
875 // Draw "T" for transparent
876 ImVec2 pos = ImGui::GetItemRectMin();
877 ImGui::GetWindowDrawList()->AddText(
878 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
879 IM_COL32(255, 255, 255, 200), "T");
880 ImGui::EndGroup();
881
882 if (ImGui::IsItemHovered()) {
883 ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d",
884 i / 16);
885 }
886 } else {
887 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
888 (*palette)[i], is_selected, is_modified,
889 ImVec2(button_size, button_size))) {
890 selected_color_ = i;
891 editing_color_ = (*palette)[i];
892 }
893 }
894
895 ImGui::PopID();
896
897 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
898 ImGui::SameLine();
899 }
900 }
901}
902
904 // Show VRAM info panel
905 SectionHeader("VRAM Information");
906
907 const auto& metadata = GetMetadata();
908 if (selected_palette_ < metadata.palettes.size()) {
909 const auto& pal_meta = metadata.palettes[selected_palette_];
910
911 ImGui::TextWrapped("This sprite palette is loaded to VRAM address $%04X",
912 pal_meta.vram_address);
913 ImGui::TextDisabled(
914 "VRAM palettes are used by the SNES PPU for sprite rendering");
915 }
916}
917
918// ========== Equipment Palette Panel ==========
919
922
924 : PaletteGroupPanel("armors", "Equipment Palettes", rom, game_data) {}
925
927 PaletteGroupMetadata metadata;
928 metadata.group_name = "armors";
929 metadata.display_name = "Equipment Palettes";
930 metadata.colors_per_palette = 8;
931 metadata.colors_per_row = 8;
932
933 const char* armor_names[] = {"Green Mail", "Blue Mail", "Red Mail"};
934
935 for (int i = 0; i < 3; i++) {
936 PaletteMetadata pal;
937 pal.palette_id = i;
938 pal.name = armor_names[i];
939 pal.description = absl::StrFormat("Link's %s colors", armor_names[i]);
940 pal.rom_address = 0xDD308 + (i * 16);
941 pal.vram_address = 0;
942 pal.usage_notes = "Changes Link's tunic appearance";
943 metadata.palettes.push_back(pal);
944 }
945
946 return metadata;
947}
948
953
955 if (!game_data_) return nullptr;
956 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group("armors");
957}
958
960 auto* palette = GetMutablePalette(selected_palette_);
961 if (!palette)
962 return;
963
964 const float button_size = 32.0f;
965 const int colors_per_row = GetColorsPerRow();
966
967 for (int i = 0; i < palette->size(); i++) {
968 bool is_selected = (i == selected_color_);
969 bool is_modified = IsColorModified(selected_palette_, i);
970
971 ImGui::PushID(i);
972
973 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
974 (*palette)[i], is_selected, is_modified,
975 ImVec2(button_size, button_size))) {
976 selected_color_ = i;
977 editing_color_ = (*palette)[i];
978 }
979
980 ImGui::PopID();
981
982 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
983 ImGui::SameLine();
984 }
985 }
986}
987
988// ========== Sprites Aux1 Palette Panel ==========
989
992
994 zelda3::GameData* game_data)
995 : PaletteGroupPanel("sprites_aux1", "Sprites Aux 1", rom, game_data) {}
996
998 PaletteGroupMetadata metadata;
999 metadata.group_name = "sprites_aux1";
1000 metadata.display_name = "Sprites Aux 1";
1001 metadata.colors_per_palette = 8; // 7 colors + transparent
1002 metadata.colors_per_row = 8;
1003
1004 for (int i = 0; i < 12; i++) {
1005 PaletteMetadata pal;
1006 pal.palette_id = i;
1007 pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
1008 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1009 pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
1010 pal.vram_address = 0;
1011 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1012 metadata.palettes.push_back(pal);
1013 }
1014
1015 return metadata;
1016}
1017
1019 if (!game_data_) return nullptr;
1020 return game_data_->palette_groups.get_group("sprites_aux1");
1021}
1022
1024 if (!game_data_) return nullptr;
1025 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
1026 "sprites_aux1");
1027}
1028
1030 auto* palette = GetMutablePalette(selected_palette_);
1031 if (!palette)
1032 return;
1033
1034 const float button_size = 32.0f;
1035 const int colors_per_row = GetColorsPerRow();
1036
1037 for (int i = 0; i < palette->size(); i++) {
1038 bool is_selected = (i == selected_color_);
1039 bool is_modified = IsColorModified(selected_palette_, i);
1040
1041 ImGui::PushID(i);
1042
1043 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1044 (*palette)[i], is_selected, is_modified,
1045 ImVec2(button_size, button_size))) {
1046 selected_color_ = i;
1047 editing_color_ = (*palette)[i];
1048 }
1049
1050 ImGui::PopID();
1051
1052 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1053 ImGui::SameLine();
1054 }
1055 }
1056}
1057
1058// ========== Sprites Aux2 Palette Panel ==========
1059
1062
1064 zelda3::GameData* game_data)
1065 : PaletteGroupPanel("sprites_aux2", "Sprites Aux 2", rom, game_data) {}
1066
1068 PaletteGroupMetadata metadata;
1069 metadata.group_name = "sprites_aux2";
1070 metadata.display_name = "Sprites Aux 2";
1071 metadata.colors_per_palette = 8; // 7 colors + transparent
1072 metadata.colors_per_row = 8;
1073
1074 for (int i = 0; i < 11; i++) {
1075 PaletteMetadata pal;
1076 pal.palette_id = i;
1077 pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
1078 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1079 pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
1080 pal.vram_address = 0;
1081 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1082 metadata.palettes.push_back(pal);
1083 }
1084
1085 return metadata;
1086}
1087
1089 if (!game_data_) return nullptr;
1090 return game_data_->palette_groups.get_group("sprites_aux2");
1091}
1092
1094 if (!game_data_) return nullptr;
1095 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
1096 "sprites_aux2");
1097}
1098
1100 auto* palette = GetMutablePalette(selected_palette_);
1101 if (!palette)
1102 return;
1103
1104 const float button_size = 32.0f;
1105 const int colors_per_row = GetColorsPerRow();
1106
1107 for (int i = 0; i < palette->size(); i++) {
1108 bool is_selected = (i == selected_color_);
1109 bool is_modified = IsColorModified(selected_palette_, i);
1110
1111 ImGui::PushID(i);
1112
1113 // Draw transparent color indicator for index 0
1114 if (i == 0) {
1115 ImGui::BeginGroup();
1116 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1117 (*palette)[i], is_selected, is_modified,
1118 ImVec2(button_size, button_size))) {
1119 selected_color_ = i;
1120 editing_color_ = (*palette)[i];
1121 }
1122 // Draw "T" for transparent
1123 ImVec2 pos = ImGui::GetItemRectMin();
1124 ImGui::GetWindowDrawList()->AddText(
1125 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1126 IM_COL32(255, 255, 255, 200), "T");
1127 ImGui::EndGroup();
1128 } else {
1129 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1130 (*palette)[i], is_selected, is_modified,
1131 ImVec2(button_size, button_size))) {
1132 selected_color_ = i;
1133 editing_color_ = (*palette)[i];
1134 }
1135 }
1136
1137 ImGui::PopID();
1138
1139 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1140 ImGui::SameLine();
1141 }
1142 }
1143}
1144
1145// ========== Sprites Aux3 Palette Panel ==========
1146
1149
1151 zelda3::GameData* game_data)
1152 : PaletteGroupPanel("sprites_aux3", "Sprites Aux 3", rom, game_data) {}
1153
1155 PaletteGroupMetadata metadata;
1156 metadata.group_name = "sprites_aux3";
1157 metadata.display_name = "Sprites Aux 3";
1158 metadata.colors_per_palette = 8; // 7 colors + transparent
1159 metadata.colors_per_row = 8;
1160
1161 for (int i = 0; i < 24; i++) {
1162 PaletteMetadata pal;
1163 pal.palette_id = i;
1164 pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
1165 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1166 pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
1167 pal.vram_address = 0;
1168 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1169 metadata.palettes.push_back(pal);
1170 }
1171
1172 return metadata;
1173}
1174
1176 if (!game_data_) return nullptr;
1177 return game_data_->palette_groups.get_group("sprites_aux3");
1178}
1179
1181 if (!game_data_) return nullptr;
1182 return const_cast<zelda3::GameData*>(game_data_)->palette_groups.get_group(
1183 "sprites_aux3");
1184}
1185
1187 auto* palette = GetMutablePalette(selected_palette_);
1188 if (!palette)
1189 return;
1190
1191 const float button_size = 32.0f;
1192 const int colors_per_row = GetColorsPerRow();
1193
1194 for (int i = 0; i < palette->size(); i++) {
1195 bool is_selected = (i == selected_color_);
1196 bool is_modified = IsColorModified(selected_palette_, i);
1197
1198 ImGui::PushID(i);
1199
1200 // Draw transparent color indicator for index 0
1201 if (i == 0) {
1202 ImGui::BeginGroup();
1203 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1204 (*palette)[i], is_selected, is_modified,
1205 ImVec2(button_size, button_size))) {
1206 selected_color_ = i;
1207 editing_color_ = (*palette)[i];
1208 }
1209 // Draw "T" for transparent
1210 ImVec2 pos = ImGui::GetItemRectMin();
1211 ImGui::GetWindowDrawList()->AddText(
1212 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1213 IM_COL32(255, 255, 255, 200), "T");
1214 ImGui::EndGroup();
1215 } else {
1216 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1217 (*palette)[i], is_selected, is_modified,
1218 ImVec2(button_size, button_size))) {
1219 selected_color_ = i;
1220 editing_color_ = (*palette)[i];
1221 }
1222 }
1223
1224 ImGui::PopID();
1225
1226 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1227 ImGui::SameLine();
1228 }
1229 }
1230}
1231
1232} // namespace editor
1233} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:357
bool is_loaded() const
Definition rom.h:128
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
static const PaletteGroupMetadata metadata_
DungeonMainPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
EquipmentPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
OverworldAnimatedPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
OverworldMainPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
Base class for palette group editing cards.
virtual gfx::PaletteGroup * GetPaletteGroup()=0
Get the palette group for this card.
void ResetPalette(int palette_index)
Reset a specific palette to original ROM values.
virtual void DrawCustomToolbarButtons()
Draw additional toolbar buttons (called after standard buttons)
void DrawPaletteSelector()
Draw palette selector dropdown.
void ResetColor(int palette_index, int color_index)
Reset a specific color to original ROM value.
gfx::SnesColor GetOriginalColor(int palette_index, int color_index) const
Get original color from ROM (for reset/comparison)
gfx::SnesPalette * GetMutablePalette(int index)
Get mutable palette by index.
void DiscardChanges()
Discard all unsaved changes.
void DrawToolbar()
Draw standard toolbar with save/discard/undo/redo.
void DrawBatchOperationsPopup()
Draw batch operations popup.
void DrawMetadataInfo()
Draw palette metadata info panel.
void SetColor(int palette_index, int color_index, const gfx::SnesColor &new_color)
Set a color value (records change for undo)
virtual const PaletteGroupMetadata & GetMetadata() const =0
Get metadata for this palette group.
absl::Status SaveToRom()
Save all modified palettes to ROM.
absl::Status WriteColorToRom(int palette_index, int color_index, const gfx::SnesColor &color)
Write a single color to ROM.
virtual void DrawCustomPanels()
Draw additional panels (called after main content)
void DrawColorPicker()
Draw color picker for selected color.
bool IsPaletteModified(int palette_index) const
PaletteGroupPanel(const std::string &group_name, const std::string &display_name, Rom *rom, zelda3::GameData *game_data=nullptr)
Construct a new Palette Group Panel.
bool IsColorModified(int palette_index, int color_index) const
void Draw(bool *p_open) override
Draw the card's ImGui UI.
void DrawColorInfo()
Draw color info panel with RGB/SNES/Hex values.
absl::Status ImportFromJson(const std::string &json)
virtual void DrawPaletteGrid()=0
Draw the palette grid specific to this palette type.
const PaletteGroupMetadata & GetMetadata() const override
Get metadata for this palette group.
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
SpritePalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
void DrawCustomPanels() override
Draw additional panels (called after main content)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
SpritesAux1PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static const PaletteGroupMetadata metadata_
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
SpritesAux2PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
SpritesAux3PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
absl::Status SetColor(const std::string &group_name, int palette_index, int color_index, const SnesColor &new_color)
Set a color in a palette (records change for undo)
bool IsGroupModified(const std::string &group_name) const
Check if a specific palette group has modifications.
absl::Status SaveGroup(const std::string &group_name)
Save a specific palette group to ROM.
void Undo()
Undo the most recent change.
void ClearHistory()
Clear undo/redo history.
bool CanRedo() const
Check if redo is available.
bool CanUndo() const
Check if undo is available.
absl::Status ResetColor(const std::string &group_name, int palette_index, int color_index)
Reset a single color to its original ROM value.
absl::Status ResetPalette(const std::string &group_name, int palette_index)
Reset an entire palette to original ROM values.
bool IsColorModified(const std::string &group_name, int palette_index, int color_index) const
Check if a specific color is modified.
void DiscardGroup(const std::string &group_name)
Discard changes for a specific group.
static PaletteManager & Get()
Get the singleton instance.
SnesColor GetColor(const std::string &group_name, int palette_index, int color_index) const
Get a color from a palette.
bool IsPaletteModified(const std::string &group_name, int palette_index) const
Check if a specific palette is modified.
void Redo()
Redo the most recently undone change.
SNES Color container.
Definition snes_color.h:110
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:183
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static void HelpMarker(const char *desc)
static float GetStandardInputWidth()
#define ICON_MD_MORE_VERT
Definition icons.h:1243
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_FILE_UPLOAD
Definition icons.h:749
#define ICON_MD_RESTORE
Definition icons.h:1605
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_UNDO
Definition icons.h:2039
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
Graphical User Interface (GUI) components for the application.
bool ThemedButton(const char *label, const ImVec2 &size)
Draw a standard text button with theme colors.
bool DangerButton(const char *label, const ImVec2 &size)
Draw a danger action button (error color).
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled)
Draw a standard icon button with theme-aware colors.
IMGUI_API bool PaletteColorButton(const char *id, const gfx::SnesColor &color, bool is_selected, bool is_modified, const ImVec2 &size, ImGuiColorEditFlags flags)
Definition color.cc:449
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:19
bool PrimaryButton(const char *label, const ImVec2 &size)
Draw a primary action button (accented color).
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:32
Metadata for an entire palette group.
std::vector< PaletteMetadata > palettes
Metadata for a single palette in a group.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89