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),
28 display_name_(display_name),
29 rom_(rom),
30 game_data_(game_data) {
31 // Note: We can't call GetPaletteGroup() here because it's a pure virtual
32 // function and the derived class isn't fully constructed yet. Original
33 // palettes will be loaded on first Draw() call instead.
34}
35
36void PaletteGroupPanel::Draw(bool* p_open) {
37 if (!rom_ || !rom_->is_loaded()) {
38 return;
39 }
40
41 // PaletteManager handles initialization of original palettes
42 // No need for local snapshot management anymore
43
44 // Main card window
45 // Note: Window management is handled by PanelManager/EditorPanel
46
48 ImGui::Separator();
49
50 // Two-column layout: Grid on left, picker on right
51 if (ImGui::BeginTable(
52 "##PalettePanelLayout", 2,
53 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
54 ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
55 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f);
56
57 ImGui::TableNextRow();
58 ImGui::TableNextColumn();
59
60 // Left: Palette selector + grid
62 ImGui::Separator();
64
65 ImGui::TableNextColumn();
66
67 // Right: Color picker + info
68 if (selected_color_ >= 0) {
70 ImGui::Separator();
72 ImGui::Separator();
74 } else {
75 ImGui::TextDisabled("Select a color to edit");
76 ImGui::Separator();
78 }
79
80 // Custom panels from derived classes
82
83 ImGui::EndTable();
84 }
85
86 // Batch operations popup
88}
89
91 // Query PaletteManager for group-specific modification status
93
94 // Save button (primary action)
95 ImGui::BeginDisabled(!has_changes);
96 if (PrimaryButton(absl::StrFormat("%s Save to ROM", ICON_MD_SAVE).c_str())) {
97 auto status = SaveToRom();
98 if (!status.ok()) {
99 // TODO: Show error toast
100 }
101 }
102 ImGui::EndDisabled();
103
104 ImGui::SameLine();
105
106 // Discard button (danger action)
107 ImGui::BeginDisabled(!has_changes);
108 if (DangerButton(absl::StrFormat("%s Discard", ICON_MD_UNDO).c_str())) {
110 }
111 ImGui::EndDisabled();
112
113 ImGui::SameLine();
114
115 // Modified indicator badge (show modified color count for this group)
116 if (has_changes) {
117 size_t modified_count = 0;
118 auto* group = GetPaletteGroup();
119 if (group) {
120 for (int p = 0; p < group->size(); p++) {
122 modified_count++;
123 }
124 }
125 }
126 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s %zu modified",
127 ICON_MD_EDIT, modified_count);
128 }
129
130 ImGui::SameLine();
131 ImGui::Dummy(ImVec2(20, 0)); // Spacer
132 ImGui::SameLine();
133
134 // Undo/Redo (global operations via PaletteManager)
135 ImGui::BeginDisabled(!gfx::PaletteManager::Get().CanUndo());
136 if (ThemedIconButton(ICON_MD_UNDO, "Undo")) {
137 Undo();
138 }
139 ImGui::EndDisabled();
140
141 ImGui::SameLine();
142 ImGui::BeginDisabled(!gfx::PaletteManager::Get().CanRedo());
143 if (ThemedIconButton(ICON_MD_REDO, "Redo")) {
144 Redo();
145 }
146 ImGui::EndDisabled();
147
148 ImGui::SameLine();
149 ImGui::Dummy(ImVec2(20, 0)); // Spacer
150 ImGui::SameLine();
151
152 // Export/Import
153 if (ThemedIconButton(ICON_MD_FILE_DOWNLOAD, "Export to clipboard")) {
155 }
156
157 ImGui::SameLine();
158 if (ThemedIconButton(ICON_MD_FILE_UPLOAD, "Import from clipboard")) {
160 }
161
162 ImGui::SameLine();
163 if (ThemedIconButton(ICON_MD_MORE_VERT, "Batch operations")) {
164 ImGui::OpenPopup("BatchOperations");
165 }
166
167 // Custom toolbar buttons from derived classes
169}
170
172 auto* palette_group = GetPaletteGroup();
173 if (!palette_group)
174 return;
175
176 int num_palettes = palette_group->size();
177
178 ImGui::Text("Palette:");
179 ImGui::SameLine();
180
181 ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
182 if (ImGui::BeginCombo(
183 "##PaletteSelect",
184 absl::StrFormat("Palette %d", selected_palette_).c_str())) {
185 for (int i = 0; i < num_palettes; i++) {
186 bool is_selected = (selected_palette_ == i);
187 bool is_modified = IsPaletteModified(i);
188
189 std::string label = absl::StrFormat("Palette %d", i);
190 if (is_modified) {
191 label += " *";
192 }
193
194 if (ImGui::Selectable(label.c_str(), is_selected)) {
196 selected_color_ = -1; // Reset color selection
197 }
198 if (is_selected) {
199 ImGui::SetItemDefaultFocus();
200 }
201 }
202 ImGui::EndCombo();
203 }
204
205 // Show reset button for current palette
206 ImGui::SameLine();
207 ImGui::BeginDisabled(!IsPaletteModified(selected_palette_));
208 if (ThemedIconButton(ICON_MD_RESTORE, "Reset palette to original")) {
210 }
211 ImGui::EndDisabled();
212}
213
215 if (selected_color_ < 0)
216 return;
217
218 auto* palette = GetMutablePalette(selected_palette_);
219 if (!palette)
220 return;
221
222 SectionHeader("Color Editor");
223
224 auto& color = (*palette)[selected_color_];
226
227 // Color picker with hue wheel
229 if (ImGui::ColorPicker4("##picker", &col.x,
230 ImGuiColorEditFlags_NoAlpha |
231 ImGuiColorEditFlags_PickerHueWheel |
232 ImGuiColorEditFlags_DisplayRGB |
233 ImGuiColorEditFlags_DisplayHSV)) {
236 }
237
238 // Current vs Original comparison
239 ImGui::Separator();
240 ImGui::Text("Current vs Original");
241
242 ImGui::ColorButton("##current", col,
243 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
244 ImVec2(60, 40));
245
246 LayoutHelpers::HelpMarker("Current color being edited");
247
248 ImGui::SameLine();
249
250 ImVec4 orig_col = ConvertSnesColorToImVec4(original);
251 if (ImGui::ColorButton(
252 "##original", orig_col,
253 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
254 ImVec2(60, 40))) {
255 // Click to restore original
256 editing_color_ = original;
258 }
259
260 if (ImGui::IsItemHovered()) {
261 ImGui::SetTooltip("Click to restore original color");
262 }
263
264 // Reset button
265 ImGui::BeginDisabled(!IsColorModified(selected_palette_, selected_color_));
266 if (ThemedButton(absl::StrFormat("%s Reset", ICON_MD_RESTORE).c_str(),
267 ImVec2(-1, 0))) {
269 }
270 ImGui::EndDisabled();
271}
272
274 if (selected_color_ < 0)
275 return;
276
277 SectionHeader("Color Information");
278
279 auto col = editing_color_.rgb();
280 int r = static_cast<int>(col.x);
281 int g = static_cast<int>(col.y);
282 int b = static_cast<int>(col.z);
283
284 // RGB values
285 ImGui::Text("RGB (0-255): (%d, %d, %d)", r, g, b);
286 if (ImGui::IsItemClicked()) {
287 ImGui::SetClipboardText(absl::StrFormat("(%d, %d, %d)", r, g, b).c_str());
288 }
289
290 // SNES BGR555 value
291 if (show_snes_format_) {
292 ImGui::Text("SNES BGR555: $%04X", editing_color_.snes());
293 if (ImGui::IsItemClicked()) {
294 ImGui::SetClipboardText(
295 absl::StrFormat("$%04X", editing_color_.snes()).c_str());
296 }
297 }
298
299 // Hex value
300 if (show_hex_format_) {
301 ImGui::Text("Hex: #%02X%02X%02X", r, g, b);
302 if (ImGui::IsItemClicked()) {
303 ImGui::SetClipboardText(
304 absl::StrFormat("#%02X%02X%02X", r, g, b).c_str());
305 }
306 }
307
308 ImGui::TextDisabled("Click any value to copy");
309}
310
312 const auto& metadata = GetMetadata();
313 if (selected_palette_ >= metadata.palettes.size())
314 return;
315
316 const auto& pal_meta = metadata.palettes[selected_palette_];
317
318 SectionHeader("Palette Metadata");
319
320 // Palette ID
321 ImGui::Text("Palette ID: %d", pal_meta.palette_id);
322
323 // Name
324 if (!pal_meta.name.empty()) {
325 ImGui::Text("Name: %s", pal_meta.name.c_str());
326 }
327
328 // Description
329 if (!pal_meta.description.empty()) {
330 ImGui::TextWrapped("%s", pal_meta.description.c_str());
331 }
332
333 ImGui::Separator();
334
335 // Palette dimensions and color depth
336 ImGui::Text("Dimensions: %d colors (%dx%d)", metadata.colors_per_palette,
337 metadata.colors_per_row,
338 (metadata.colors_per_palette + metadata.colors_per_row - 1) /
339 metadata.colors_per_row);
340
341 ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4);
342 ImGui::TextDisabled("(16 colors per palette possible)");
343
344 ImGui::Separator();
345
346 // ROM Address
347 ImGui::Text("ROM Address: $%06X", pal_meta.rom_address);
348 if (ImGui::IsItemClicked()) {
349 ImGui::SetClipboardText(
350 absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
351 }
352 if (ImGui::IsItemHovered()) {
353 ImGui::SetTooltip("Click to copy address");
354 }
355
356 // VRAM Address (if applicable)
357 if (pal_meta.vram_address > 0) {
358 ImGui::Text("VRAM Address: $%04X", pal_meta.vram_address);
359 if (ImGui::IsItemClicked()) {
360 ImGui::SetClipboardText(
361 absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
362 }
363 if (ImGui::IsItemHovered()) {
364 ImGui::SetTooltip("Click to copy VRAM address");
365 }
366 }
367
368 // Usage notes
369 if (!pal_meta.usage_notes.empty()) {
370 ImGui::Separator();
371 ImGui::TextDisabled("Usage Notes:");
372 ImGui::TextWrapped("%s", pal_meta.usage_notes.c_str());
373 }
374}
375
377 if (ImGui::BeginPopup("BatchOperations")) {
378 SectionHeader("Batch Operations");
379
380 if (ThemedButton("Copy Current Palette", ImVec2(-1, 0))) {
382 ImGui::CloseCurrentPopup();
383 }
384
385 if (ThemedButton("Paste to Current Palette", ImVec2(-1, 0))) {
387 ImGui::CloseCurrentPopup();
388 }
389
390 ImGui::Separator();
391
392 if (ThemedButton("Reset All Palettes", ImVec2(-1, 0))) {
394 ImGui::CloseCurrentPopup();
395 }
396
397 ImGui::EndPopup();
398 }
399}
400
401// ========== Palette Operations ==========
402
403void PaletteGroupPanel::SetColor(int palette_index, int color_index,
404 const gfx::SnesColor& new_color) {
405 // Delegate to PaletteManager for centralized tracking and undo/redo
406 auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index,
407 color_index, new_color);
408 if (!status.ok()) {
409 // TODO: Show error notification
410 return;
411 }
412
413 // Auto-save if enabled (PaletteManager doesn't handle this)
414 if (auto_save_enabled_) {
415 WriteColorToRom(palette_index, color_index, new_color);
416 }
417}
418
420 // Delegate to PaletteManager for centralized save operation
422}
423
425 // Delegate to PaletteManager for centralized discard operation
427
428 // Reset selection
429 selected_color_ = -1;
430}
431
432void PaletteGroupPanel::ResetPalette(int palette_index) {
433 // Delegate to PaletteManager for centralized reset operation
435}
436
437void PaletteGroupPanel::ResetColor(int palette_index, int color_index) {
438 // Delegate to PaletteManager for centralized reset operation
440 color_index);
441}
442
443// ========== History Management ==========
444
446 // Delegate to PaletteManager's global undo system
448}
449
451 // Delegate to PaletteManager's global redo system
453}
454
456 // Delegate to PaletteManager's global history
458}
459
460// ========== State Queries ==========
461
462bool PaletteGroupPanel::IsPaletteModified(int palette_index) const {
463 // Query PaletteManager for modification status
465 palette_index);
466}
467
469 int color_index) const {
470 // Query PaletteManager for modification status
472 color_index);
473}
474
476 // Query PaletteManager for group-specific modification status
478}
479
481 // Query PaletteManager for global undo availability
483}
484
486 // Query PaletteManager for global redo availability
488}
489
490// ========== Helper Methods ==========
491
493 auto* palette_group = GetPaletteGroup();
494 if (!palette_group || index < 0 || index >= palette_group->size()) {
495 return nullptr;
496 }
497 return palette_group->mutable_palette(index);
498}
499
501 int color_index) const {
502 // Get original color from PaletteManager's snapshots
503 return gfx::PaletteManager::Get().GetColor(group_name_, palette_index,
504 color_index);
505}
506
507absl::Status PaletteGroupPanel::WriteColorToRom(int palette_index,
508 int color_index,
509 const gfx::SnesColor& color) {
510 uint32_t address =
511 gfx::GetPaletteAddress(group_name_, palette_index, color_index);
512 return rom_->WriteColor(address, color);
513}
514
515// MarkModified and ClearModified removed - PaletteManager handles tracking now
516
517// ========== Export/Import ==========
518
520 // TODO: Implement JSON export
521 return "{}";
522}
523
524absl::Status PaletteGroupPanel::ImportFromJson(const std::string& /*json*/) {
525 // TODO: Implement JSON import
526 return absl::UnimplementedError("Import from JSON not yet implemented");
527}
528
530 auto* palette_group = GetPaletteGroup();
531 if (!palette_group || selected_palette_ >= palette_group->size()) {
532 return "";
533 }
534
535 auto palette = palette_group->palette(selected_palette_);
536 std::string result;
537
538 for (size_t i = 0; i < palette.size(); i++) {
539 result += absl::StrFormat("$%04X", palette[i].snes());
540 if (i < palette.size() - 1) {
541 result += ",";
542 }
543 }
544
545 ImGui::SetClipboardText(result.c_str());
546 return result;
547}
548
550 // TODO: Implement clipboard import
551 return absl::UnimplementedError("Import from clipboard not yet implemented");
552}
553
554// ============================================================================
555// Concrete Palette Panel Implementations
556// ============================================================================
557
558// ========== Overworld Main Palette Panel ==========
559
562
564 Rom* rom, zelda3::GameData* game_data)
565 : PaletteGroupPanel("ow_main", "Overworld Main Palettes", rom, game_data) {}
566
568 PaletteGroupMetadata metadata;
569 metadata.group_name = "ow_main";
570 metadata.display_name = "Overworld Main Palettes";
571 metadata.colors_per_palette = 8;
572 metadata.colors_per_row = 8;
573
574 // Light World palettes (0-19)
575 for (int i = 0; i < 20; i++) {
576 PaletteMetadata pal;
577 pal.palette_id = i;
578 pal.name = absl::StrFormat("Light World %d", i);
579 pal.description = "Used for Light World overworld graphics";
580 pal.rom_address = 0xDE6C8 + (i * 16); // Base address + offset
581 pal.vram_address = 0;
582 pal.usage_notes = "Modifying these colors affects Light World appearance";
583 metadata.palettes.push_back(pal);
584 }
585
586 // Dark World palettes (20-39)
587 for (int i = 20; i < 40; i++) {
588 PaletteMetadata pal;
589 pal.palette_id = i;
590 pal.name = absl::StrFormat("Dark World %d", i - 20);
591 pal.description = "Used for Dark World overworld graphics";
592 pal.rom_address = 0xDE6C8 + (i * 16);
593 pal.vram_address = 0;
594 pal.usage_notes = "Modifying these colors affects Dark World appearance";
595 metadata.palettes.push_back(pal);
596 }
597
598 // Special World palettes (40-59)
599 for (int i = 40; i < 60; i++) {
600 PaletteMetadata pal;
601 pal.palette_id = i;
602 pal.name = absl::StrFormat("Special %d", i - 40);
603 pal.description = "Used for Special World and triforce room";
604 pal.rom_address = 0xDE6C8 + (i * 16);
605 pal.vram_address = 0;
606 pal.usage_notes = "Modifying these colors affects Special World areas";
607 metadata.palettes.push_back(pal);
608 }
609
610 return metadata;
611}
612
618
620 if (!game_data_)
621 return nullptr;
622 return const_cast<zelda3::GameData*>(game_data_)
623 ->palette_groups.get_group("ow_main");
624}
625
627 auto* palette = GetMutablePalette(selected_palette_);
628 if (!palette)
629 return;
630
631 const float button_size = 32.0f;
632 const int colors_per_row = GetColorsPerRow();
633
634 for (int i = 0; i < palette->size(); i++) {
635 bool is_selected = (i == selected_color_);
636 bool is_modified = IsColorModified(selected_palette_, i);
637
638 ImGui::PushID(i);
639
640 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
641 (*palette)[i], is_selected, is_modified,
642 ImVec2(button_size, button_size))) {
643 selected_color_ = i;
644 editing_color_ = (*palette)[i];
645 }
646
647 ImGui::PopID();
648
649 // Wrap to next row
650 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
651 ImGui::SameLine();
652 }
653 }
654}
655
656// ========== Overworld Animated Palette Panel ==========
657
660
662 Rom* rom, zelda3::GameData* game_data)
663 : PaletteGroupPanel("ow_animated", "Overworld Animated Palettes", rom,
664 game_data) {}
665
667 PaletteGroupMetadata metadata;
668 metadata.group_name = "ow_animated";
669 metadata.display_name = "Overworld Animated Palettes";
670 metadata.colors_per_palette = 8;
671 metadata.colors_per_row = 8;
672
673 // Animated palettes
674 const char* anim_names[] = {"Water", "Lava", "Poison Water", "Ice"};
675 for (int i = 0; i < 4; i++) {
676 PaletteMetadata pal;
677 pal.palette_id = i;
678 pal.name = anim_names[i];
679 pal.description =
680 absl::StrFormat("%s animated palette cycle", anim_names[i]);
681 pal.rom_address = 0xDE86C + (i * 16);
682 pal.vram_address = 0;
683 pal.usage_notes =
684 "These palettes cycle through multiple frames for animation";
685 metadata.palettes.push_back(pal);
686 }
687
688 return metadata;
689}
690
692 if (!game_data_)
693 return nullptr;
694 return game_data_->palette_groups.get_group("ow_animated");
695}
696
698 const {
699 if (!game_data_)
700 return nullptr;
701 return const_cast<zelda3::GameData*>(game_data_)
702 ->palette_groups.get_group("ow_animated");
703}
704
706 auto* palette = GetMutablePalette(selected_palette_);
707 if (!palette)
708 return;
709
710 const float button_size = 32.0f;
711 const int colors_per_row = GetColorsPerRow();
712
713 for (int i = 0; i < palette->size(); i++) {
714 bool is_selected = (i == selected_color_);
715 bool is_modified = IsColorModified(selected_palette_, i);
716
717 ImGui::PushID(i);
718
719 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
720 (*palette)[i], is_selected, is_modified,
721 ImVec2(button_size, button_size))) {
722 selected_color_ = i;
723 editing_color_ = (*palette)[i];
724 }
725
726 ImGui::PopID();
727
728 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
729 ImGui::SameLine();
730 }
731 }
732}
733
734// ========== Dungeon Main Palette Panel ==========
735
738
740 zelda3::GameData* game_data)
741 : PaletteGroupPanel("dungeon_main", "Dungeon Main Palettes", rom,
742 game_data) {}
743
745 PaletteGroupMetadata metadata;
746 metadata.group_name = "dungeon_main";
747 metadata.display_name = "Dungeon Main Palettes";
748 metadata.colors_per_palette = 16;
749 metadata.colors_per_row = 16;
750
751 // Dungeon palettes (0-19)
752 const char* dungeon_names[] = {
753 "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace",
754 "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire",
755 "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town",
756 "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2",
757 "Generic 3", "Generic 4", "Generic 5", "Generic 6"};
758
759 for (int i = 0; i < 20; i++) {
760 PaletteMetadata pal;
761 pal.palette_id = i;
762 pal.name = dungeon_names[i];
763 pal.description = absl::StrFormat("Dungeon palette %d", i);
764 pal.rom_address = 0xDE604 + (i * 32);
765 pal.vram_address = 0;
766 pal.usage_notes = "16 colors per dungeon palette";
767 metadata.palettes.push_back(pal);
768 }
769
770 return metadata;
771}
772
774 if (!game_data_)
775 return nullptr;
776 return game_data_->palette_groups.get_group("dungeon_main");
777}
778
780 if (!game_data_)
781 return nullptr;
782 return const_cast<zelda3::GameData*>(game_data_)
783 ->palette_groups.get_group("dungeon_main");
784}
785
787 auto* palette = GetMutablePalette(selected_palette_);
788 if (!palette)
789 return;
790
791 const float button_size = 28.0f;
792 const int colors_per_row = GetColorsPerRow();
793
794 for (int i = 0; i < palette->size(); i++) {
795 bool is_selected = (i == selected_color_);
796 bool is_modified = IsColorModified(selected_palette_, i);
797
798 ImGui::PushID(i);
799
800 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
801 (*palette)[i], is_selected, is_modified,
802 ImVec2(button_size, button_size))) {
803 selected_color_ = i;
804 editing_color_ = (*palette)[i];
805 }
806
807 ImGui::PopID();
808
809 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
810 ImGui::SameLine();
811 }
812 }
813}
814
815// ========== Sprite Palette Panel ==========
816
819
821 : PaletteGroupPanel("global_sprites", "Sprite Palettes", rom, game_data) {}
822
824 PaletteGroupMetadata metadata;
825 metadata.group_name = "global_sprites";
826 metadata.display_name = "Global Sprite Palettes";
827 metadata.colors_per_palette =
828 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48)
829 metadata.colors_per_row = 16; // Display in 16-color rows
830
831 // 2 palette sets: Light World and Dark World
832 const char* sprite_names[] = {"Global Sprites (Light World)",
833 "Global Sprites (Dark World)"};
834
835 for (int i = 0; i < 2; i++) {
836 PaletteMetadata pal;
837 pal.palette_id = i;
838 pal.name = sprite_names[i];
839 pal.description =
840 "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, "
841 "32, 48";
842 pal.rom_address = (i == 0) ? 0xDD218 : 0xDD290; // LW or DW address
843 pal.vram_address = 0; // Loaded dynamically
844 pal.usage_notes =
845 "4 sprite sub-palettes of 15 colors + transparent each. "
846 "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
847 metadata.palettes.push_back(pal);
848 }
849
850 return metadata;
851}
852
854 if (!game_data_)
855 return nullptr;
856 return game_data_->palette_groups.get_group("global_sprites");
857}
858
860 if (!game_data_)
861 return nullptr;
862 return const_cast<zelda3::GameData*>(game_data_)
863 ->palette_groups.get_group("global_sprites");
864}
865
867 auto* palette = GetMutablePalette(selected_palette_);
868 if (!palette)
869 return;
870
871 const float button_size = 28.0f;
872 const int colors_per_row = GetColorsPerRow();
873
874 for (int i = 0; i < palette->size(); i++) {
875 bool is_selected = (i == selected_color_);
876 bool is_modified = IsColorModified(selected_palette_, i);
877
878 ImGui::PushID(i);
879
880 // Draw transparent color indicator at start of each 16-color row (0, 16,
881 // 32, 48, ...)
882 bool is_transparent_slot = (i % 16 == 0);
883 if (is_transparent_slot) {
884 ImGui::BeginGroup();
885 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
886 (*palette)[i], is_selected, is_modified,
887 ImVec2(button_size, button_size))) {
888 selected_color_ = i;
889 editing_color_ = (*palette)[i];
890 }
891 // Draw "T" for transparent
892 ImVec2 pos = ImGui::GetItemRectMin();
893 ImGui::GetWindowDrawList()->AddText(
894 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
895 IM_COL32(255, 255, 255, 200), "T");
896 ImGui::EndGroup();
897
898 if (ImGui::IsItemHovered()) {
899 ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d",
900 i / 16);
901 }
902 } else {
903 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
904 (*palette)[i], is_selected, is_modified,
905 ImVec2(button_size, button_size))) {
906 selected_color_ = i;
907 editing_color_ = (*palette)[i];
908 }
909 }
910
911 ImGui::PopID();
912
913 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
914 ImGui::SameLine();
915 }
916 }
917}
918
920 // Show VRAM info panel
921 SectionHeader("VRAM Information");
922
923 const auto& metadata = GetMetadata();
924 if (selected_palette_ < metadata.palettes.size()) {
925 const auto& pal_meta = metadata.palettes[selected_palette_];
926
927 ImGui::TextWrapped("This sprite palette is loaded to VRAM address $%04X",
928 pal_meta.vram_address);
929 ImGui::TextDisabled(
930 "VRAM palettes are used by the SNES PPU for sprite rendering");
931 }
932}
933
934// ========== Equipment Palette Panel ==========
935
938
940 zelda3::GameData* game_data)
941 : PaletteGroupPanel("armors", "Equipment Palettes", rom, game_data) {}
942
944 PaletteGroupMetadata metadata;
945 metadata.group_name = "armors";
946 metadata.display_name = "Equipment Palettes";
947 metadata.colors_per_palette = 8;
948 metadata.colors_per_row = 8;
949
950 const char* armor_names[] = {"Green Mail", "Blue Mail", "Red Mail"};
951
952 for (int i = 0; i < 3; i++) {
953 PaletteMetadata pal;
954 pal.palette_id = i;
955 pal.name = armor_names[i];
956 pal.description = absl::StrFormat("Link's %s colors", armor_names[i]);
957 pal.rom_address = 0xDD308 + (i * 16);
958 pal.vram_address = 0;
959 pal.usage_notes = "Changes Link's tunic appearance";
960 metadata.palettes.push_back(pal);
961 }
962
963 return metadata;
964}
965
967 if (!game_data_)
968 return nullptr;
969 return game_data_->palette_groups.get_group("armors");
970}
971
973 if (!game_data_)
974 return nullptr;
975 return const_cast<zelda3::GameData*>(game_data_)
976 ->palette_groups.get_group("armors");
977}
978
980 auto* palette = GetMutablePalette(selected_palette_);
981 if (!palette)
982 return;
983
984 const float button_size = 32.0f;
985 const int colors_per_row = GetColorsPerRow();
986
987 for (int i = 0; i < palette->size(); i++) {
988 bool is_selected = (i == selected_color_);
989 bool is_modified = IsColorModified(selected_palette_, i);
990
991 ImGui::PushID(i);
992
993 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
994 (*palette)[i], is_selected, is_modified,
995 ImVec2(button_size, button_size))) {
996 selected_color_ = i;
997 editing_color_ = (*palette)[i];
998 }
999
1000 ImGui::PopID();
1001
1002 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1003 ImGui::SameLine();
1004 }
1005 }
1006}
1007
1008// ========== Sprites Aux1 Palette Panel ==========
1009
1012
1014 zelda3::GameData* game_data)
1015 : PaletteGroupPanel("sprites_aux1", "Sprites Aux 1", rom, game_data) {}
1016
1018 PaletteGroupMetadata metadata;
1019 metadata.group_name = "sprites_aux1";
1020 metadata.display_name = "Sprites Aux 1";
1021 metadata.colors_per_palette = 8; // 7 colors + transparent
1022 metadata.colors_per_row = 8;
1023
1024 for (int i = 0; i < 12; i++) {
1025 PaletteMetadata pal;
1026 pal.palette_id = i;
1027 pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
1028 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1029 pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
1030 pal.vram_address = 0;
1031 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1032 metadata.palettes.push_back(pal);
1033 }
1034
1035 return metadata;
1036}
1037
1039 if (!game_data_)
1040 return nullptr;
1041 return game_data_->palette_groups.get_group("sprites_aux1");
1042}
1043
1045 if (!game_data_)
1046 return nullptr;
1047 return const_cast<zelda3::GameData*>(game_data_)
1048 ->palette_groups.get_group("sprites_aux1");
1049}
1050
1052 auto* palette = GetMutablePalette(selected_palette_);
1053 if (!palette)
1054 return;
1055
1056 const float button_size = 32.0f;
1057 const int colors_per_row = GetColorsPerRow();
1058
1059 for (int i = 0; i < palette->size(); i++) {
1060 bool is_selected = (i == selected_color_);
1061 bool is_modified = IsColorModified(selected_palette_, i);
1062
1063 ImGui::PushID(i);
1064
1065 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1066 (*palette)[i], is_selected, is_modified,
1067 ImVec2(button_size, button_size))) {
1068 selected_color_ = i;
1069 editing_color_ = (*palette)[i];
1070 }
1071
1072 ImGui::PopID();
1073
1074 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1075 ImGui::SameLine();
1076 }
1077 }
1078}
1079
1080// ========== Sprites Aux2 Palette Panel ==========
1081
1084
1086 zelda3::GameData* game_data)
1087 : PaletteGroupPanel("sprites_aux2", "Sprites Aux 2", rom, game_data) {}
1088
1090 PaletteGroupMetadata metadata;
1091 metadata.group_name = "sprites_aux2";
1092 metadata.display_name = "Sprites Aux 2";
1093 metadata.colors_per_palette = 8; // 7 colors + transparent
1094 metadata.colors_per_row = 8;
1095
1096 for (int i = 0; i < 11; i++) {
1097 PaletteMetadata pal;
1098 pal.palette_id = i;
1099 pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
1100 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1101 pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
1102 pal.vram_address = 0;
1103 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1104 metadata.palettes.push_back(pal);
1105 }
1106
1107 return metadata;
1108}
1109
1111 if (!game_data_)
1112 return nullptr;
1113 return game_data_->palette_groups.get_group("sprites_aux2");
1114}
1115
1117 if (!game_data_)
1118 return nullptr;
1119 return const_cast<zelda3::GameData*>(game_data_)
1120 ->palette_groups.get_group("sprites_aux2");
1121}
1122
1124 auto* palette = GetMutablePalette(selected_palette_);
1125 if (!palette)
1126 return;
1127
1128 const float button_size = 32.0f;
1129 const int colors_per_row = GetColorsPerRow();
1130
1131 for (int i = 0; i < palette->size(); i++) {
1132 bool is_selected = (i == selected_color_);
1133 bool is_modified = IsColorModified(selected_palette_, i);
1134
1135 ImGui::PushID(i);
1136
1137 // Draw transparent color indicator for index 0
1138 if (i == 0) {
1139 ImGui::BeginGroup();
1140 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1141 (*palette)[i], is_selected, is_modified,
1142 ImVec2(button_size, button_size))) {
1143 selected_color_ = i;
1144 editing_color_ = (*palette)[i];
1145 }
1146 // Draw "T" for transparent
1147 ImVec2 pos = ImGui::GetItemRectMin();
1148 ImGui::GetWindowDrawList()->AddText(
1149 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1150 IM_COL32(255, 255, 255, 200), "T");
1151 ImGui::EndGroup();
1152 } else {
1153 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1154 (*palette)[i], is_selected, is_modified,
1155 ImVec2(button_size, button_size))) {
1156 selected_color_ = i;
1157 editing_color_ = (*palette)[i];
1158 }
1159 }
1160
1161 ImGui::PopID();
1162
1163 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1164 ImGui::SameLine();
1165 }
1166 }
1167}
1168
1169// ========== Sprites Aux3 Palette Panel ==========
1170
1173
1175 zelda3::GameData* game_data)
1176 : PaletteGroupPanel("sprites_aux3", "Sprites Aux 3", rom, game_data) {}
1177
1179 PaletteGroupMetadata metadata;
1180 metadata.group_name = "sprites_aux3";
1181 metadata.display_name = "Sprites Aux 3";
1182 metadata.colors_per_palette = 8; // 7 colors + transparent
1183 metadata.colors_per_row = 8;
1184
1185 for (int i = 0; i < 24; i++) {
1186 PaletteMetadata pal;
1187 pal.palette_id = i;
1188 pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
1189 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1190 pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
1191 pal.vram_address = 0;
1192 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1193 metadata.palettes.push_back(pal);
1194 }
1195
1196 return metadata;
1197}
1198
1200 if (!game_data_)
1201 return nullptr;
1202 return game_data_->palette_groups.get_group("sprites_aux3");
1203}
1204
1206 if (!game_data_)
1207 return nullptr;
1208 return const_cast<zelda3::GameData*>(game_data_)
1209 ->palette_groups.get_group("sprites_aux3");
1210}
1211
1213 auto* palette = GetMutablePalette(selected_palette_);
1214 if (!palette)
1215 return;
1216
1217 const float button_size = 32.0f;
1218 const int colors_per_row = GetColorsPerRow();
1219
1220 for (int i = 0; i < palette->size(); i++) {
1221 bool is_selected = (i == selected_color_);
1222 bool is_modified = IsColorModified(selected_palette_, i);
1223
1224 ImGui::PushID(i);
1225
1226 // Draw transparent color indicator for index 0
1227 if (i == 0) {
1228 ImGui::BeginGroup();
1229 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1230 (*palette)[i], is_selected, is_modified,
1231 ImVec2(button_size, button_size))) {
1232 selected_color_ = i;
1233 editing_color_ = (*palette)[i];
1234 }
1235 // Draw "T" for transparent
1236 ImVec2 pos = ImGui::GetItemRectMin();
1237 ImGui::GetWindowDrawList()->AddText(
1238 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1239 IM_COL32(255, 255, 255, 200), "T");
1240 ImGui::EndGroup();
1241 } else {
1242 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1243 (*palette)[i], is_selected, is_modified,
1244 ImVec2(button_size, button_size))) {
1245 selected_color_ = i;
1246 editing_color_ = (*palette)[i];
1247 }
1248 }
1249
1250 ImGui::PopID();
1251
1252 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1253 ImGui::SameLine();
1254 }
1255 }
1256}
1257
1258} // namespace editor
1259} // 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:362
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:450
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:20
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:33
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