yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_group_card.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
24PaletteGroupCard::PaletteGroupCard(const std::string& group_name,
25 const std::string& display_name,
26 Rom* rom)
27 : group_name_(group_name),
28 display_name_(display_name),
29 rom_(rom) {
30 // Note: We can't call GetPaletteGroup() here because it's a pure virtual function
31 // and the derived class isn't fully constructed yet. Original palettes will be
32 // loaded on first Draw() call instead.
33}
34
36 if (!show_ || !rom_ || !rom_->is_loaded()) {
37 return;
38 }
39
40 // PaletteManager handles initialization of original palettes
41 // No need for local snapshot management anymore
42
43 // Main card window
44 if (ImGui::Begin(display_name_.c_str(), &show_)) {
46 ImGui::Separator();
47
48 // Two-column layout: Grid on left, picker on right
49 if (ImGui::BeginTable("##PaletteCardLayout", 2,
50 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
51 ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
52 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f);
53
54 ImGui::TableNextRow();
55 ImGui::TableNextColumn();
56
57 // Left: Palette selector + grid
59 ImGui::Separator();
61
62 ImGui::TableNextColumn();
63
64 // Right: Color picker + info
65 if (selected_color_ >= 0) {
67 ImGui::Separator();
69 ImGui::Separator();
71 } else {
72 ImGui::TextDisabled("Select a color to edit");
73 ImGui::Separator();
75 }
76
77 // Custom panels from derived classes
79
80 ImGui::EndTable();
81 }
82 }
83 ImGui::End();
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) return;
173
174 int num_palettes = palette_group->size();
175
176 ImGui::Text("Palette:");
177 ImGui::SameLine();
178
179 ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
180 if (ImGui::BeginCombo("##PaletteSelect",
181 absl::StrFormat("Palette %d", selected_palette_).c_str())) {
182 for (int i = 0; i < num_palettes; i++) {
183 bool is_selected = (selected_palette_ == i);
184 bool is_modified = IsPaletteModified(i);
185
186 std::string label = absl::StrFormat("Palette %d", i);
187 if (is_modified) {
188 label += " *";
189 }
190
191 if (ImGui::Selectable(label.c_str(), is_selected)) {
193 selected_color_ = -1; // Reset color selection
194 }
195 if (is_selected) {
196 ImGui::SetItemDefaultFocus();
197 }
198 }
199 ImGui::EndCombo();
200 }
201
202 // Show reset button for current palette
203 ImGui::SameLine();
204 ImGui::BeginDisabled(!IsPaletteModified(selected_palette_));
205 if (ThemedIconButton(ICON_MD_RESTORE, "Reset palette to original")) {
207 }
208 ImGui::EndDisabled();
209}
210
212 if (selected_color_ < 0) return;
213
214 auto* palette = GetMutablePalette(selected_palette_);
215 if (!palette) return;
216
217 SectionHeader("Color Editor");
218
219 auto& color = (*palette)[selected_color_];
221
222 // Color picker with hue wheel
224 if (ImGui::ColorPicker4("##picker", &col.x,
225 ImGuiColorEditFlags_NoAlpha |
226 ImGuiColorEditFlags_PickerHueWheel |
227 ImGuiColorEditFlags_DisplayRGB |
228 ImGuiColorEditFlags_DisplayHSV)) {
231 }
232
233 // Current vs Original comparison
234 ImGui::Separator();
235 ImGui::Text("Current vs Original");
236
237 ImGui::ColorButton("##current", col,
238 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
239 ImVec2(60, 40));
240
241 LayoutHelpers::HelpMarker("Current color being edited");
242
243 ImGui::SameLine();
244
245 ImVec4 orig_col = ConvertSnesColorToImVec4(original);
246 if (ImGui::ColorButton("##original", orig_col,
247 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
248 ImVec2(60, 40))) {
249 // Click to restore original
250 editing_color_ = original;
252 }
253
254 if (ImGui::IsItemHovered()) {
255 ImGui::SetTooltip("Click to restore original color");
256 }
257
258 // Reset button
259 ImGui::BeginDisabled(!IsColorModified(selected_palette_, selected_color_));
260 if (ThemedButton(absl::StrFormat("%s Reset", ICON_MD_RESTORE).c_str(),
261 ImVec2(-1, 0))) {
263 }
264 ImGui::EndDisabled();
265}
266
268 if (selected_color_ < 0) return;
269
270 SectionHeader("Color Information");
271
272 auto col = editing_color_.rgb();
273 int r = static_cast<int>(col.x);
274 int g = static_cast<int>(col.y);
275 int b = static_cast<int>(col.z);
276
277 // RGB values
278 ImGui::Text("RGB (0-255): (%d, %d, %d)", r, g, b);
279 if (ImGui::IsItemClicked()) {
280 ImGui::SetClipboardText(absl::StrFormat("(%d, %d, %d)", r, g, b).c_str());
281 }
282
283 // SNES BGR555 value
284 if (show_snes_format_) {
285 ImGui::Text("SNES BGR555: $%04X", editing_color_.snes());
286 if (ImGui::IsItemClicked()) {
287 ImGui::SetClipboardText(absl::StrFormat("$%04X", editing_color_.snes()).c_str());
288 }
289 }
290
291 // Hex value
292 if (show_hex_format_) {
293 ImGui::Text("Hex: #%02X%02X%02X", r, g, b);
294 if (ImGui::IsItemClicked()) {
295 ImGui::SetClipboardText(absl::StrFormat("#%02X%02X%02X", r, g, b).c_str());
296 }
297 }
298
299 ImGui::TextDisabled("Click any value to copy");
300}
301
303 const auto& metadata = GetMetadata();
304 if (selected_palette_ >= metadata.palettes.size()) return;
305
306 const auto& pal_meta = metadata.palettes[selected_palette_];
307
308 SectionHeader("Palette Metadata");
309
310 // Palette ID
311 ImGui::Text("Palette ID: %d", pal_meta.palette_id);
312
313 // Name
314 if (!pal_meta.name.empty()) {
315 ImGui::Text("Name: %s", pal_meta.name.c_str());
316 }
317
318 // Description
319 if (!pal_meta.description.empty()) {
320 ImGui::TextWrapped("%s", pal_meta.description.c_str());
321 }
322
323 ImGui::Separator();
324
325 // Palette dimensions and color depth
326 ImGui::Text("Dimensions: %d colors (%dx%d)",
327 metadata.colors_per_palette,
328 metadata.colors_per_row,
329 (metadata.colors_per_palette + metadata.colors_per_row - 1) / metadata.colors_per_row);
330
331 ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4);
332 ImGui::TextDisabled("(16 colors per palette possible)");
333
334 ImGui::Separator();
335
336 // ROM Address
337 ImGui::Text("ROM Address: $%06X", pal_meta.rom_address);
338 if (ImGui::IsItemClicked()) {
339 ImGui::SetClipboardText(absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
340 }
341 if (ImGui::IsItemHovered()) {
342 ImGui::SetTooltip("Click to copy address");
343 }
344
345 // VRAM Address (if applicable)
346 if (pal_meta.vram_address > 0) {
347 ImGui::Text("VRAM Address: $%04X", pal_meta.vram_address);
348 if (ImGui::IsItemClicked()) {
349 ImGui::SetClipboardText(absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
350 }
351 if (ImGui::IsItemHovered()) {
352 ImGui::SetTooltip("Click to copy VRAM address");
353 }
354 }
355
356 // Usage notes
357 if (!pal_meta.usage_notes.empty()) {
358 ImGui::Separator();
359 ImGui::TextDisabled("Usage Notes:");
360 ImGui::TextWrapped("%s", pal_meta.usage_notes.c_str());
361 }
362}
363
365 if (ImGui::BeginPopup("BatchOperations")) {
366 SectionHeader("Batch Operations");
367
368 if (ThemedButton("Copy Current Palette", ImVec2(-1, 0))) {
370 ImGui::CloseCurrentPopup();
371 }
372
373 if (ThemedButton("Paste to Current Palette", ImVec2(-1, 0))) {
375 ImGui::CloseCurrentPopup();
376 }
377
378 ImGui::Separator();
379
380 if (ThemedButton("Reset All Palettes", ImVec2(-1, 0))) {
382 ImGui::CloseCurrentPopup();
383 }
384
385 ImGui::EndPopup();
386 }
387}
388
389// ========== Palette Operations ==========
390
391void PaletteGroupCard::SetColor(int palette_index, int color_index,
392 const gfx::SnesColor& new_color) {
393 // Delegate to PaletteManager for centralized tracking and undo/redo
394 auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index,
395 color_index, new_color);
396 if (!status.ok()) {
397 // TODO: Show error notification
398 return;
399 }
400
401 // Auto-save if enabled (PaletteManager doesn't handle this)
402 if (auto_save_enabled_) {
403 WriteColorToRom(palette_index, color_index, new_color);
404 }
405}
406
408 // Delegate to PaletteManager for centralized save operation
410}
411
413 // Delegate to PaletteManager for centralized discard operation
415
416 // Reset selection
417 selected_color_ = -1;
418}
419
420void PaletteGroupCard::ResetPalette(int palette_index) {
421 // Delegate to PaletteManager for centralized reset operation
423}
424
425void PaletteGroupCard::ResetColor(int palette_index, int color_index) {
426 // Delegate to PaletteManager for centralized reset operation
428 color_index);
429}
430
431// ========== History Management ==========
432
434 // Delegate to PaletteManager's global undo system
436}
437
439 // Delegate to PaletteManager's global redo system
441}
442
444 // Delegate to PaletteManager's global history
446}
447
448// ========== State Queries ==========
449
450bool PaletteGroupCard::IsPaletteModified(int palette_index) const {
451 // Query PaletteManager for modification status
453 palette_index);
454}
455
457 int color_index) const {
458 // Query PaletteManager for modification status
460 color_index);
461}
462
464 // Query PaletteManager for group-specific modification status
466}
467
469 // Query PaletteManager for global undo availability
471}
472
474 // Query PaletteManager for global redo availability
476}
477
478// ========== Helper Methods ==========
479
481 auto* palette_group = GetPaletteGroup();
482 if (!palette_group || index < 0 || index >= palette_group->size()) {
483 return nullptr;
484 }
485 return palette_group->mutable_palette(index);
486}
487
489 int color_index) const {
490 // Get original color from PaletteManager's snapshots
491 return gfx::PaletteManager::Get().GetColor(group_name_, palette_index,
492 color_index);
493}
494
495absl::Status PaletteGroupCard::WriteColorToRom(int palette_index, int color_index,
496 const gfx::SnesColor& color) {
497 uint32_t address = gfx::GetPaletteAddress(group_name_, palette_index, color_index);
498 return rom_->WriteColor(address, color);
499}
500
501// MarkModified and ClearModified removed - PaletteManager handles tracking now
502
503// ========== Export/Import ==========
504
506 // TODO: Implement JSON export
507 return "{}";
508}
509
510absl::Status PaletteGroupCard::ImportFromJson(const std::string& /*json*/) {
511 // TODO: Implement JSON import
512 return absl::UnimplementedError("Import from JSON not yet implemented");
513}
514
516 auto* palette_group = GetPaletteGroup();
517 if (!palette_group || selected_palette_ >= palette_group->size()) {
518 return "";
519 }
520
521 auto palette = palette_group->palette(selected_palette_);
522 std::string result;
523
524 for (size_t i = 0; i < palette.size(); i++) {
525 result += absl::StrFormat("$%04X", palette[i].snes());
526 if (i < palette.size() - 1) {
527 result += ",";
528 }
529 }
530
531 ImGui::SetClipboardText(result.c_str());
532 return result;
533}
534
536 // TODO: Implement clipboard import
537 return absl::UnimplementedError("Import from clipboard not yet implemented");
538}
539
540// ============================================================================
541// Concrete Palette Card Implementations
542// ============================================================================
543
544// ========== Overworld Main Palette Card ==========
545
548
550 : PaletteGroupCard("ow_main", "Overworld Main Palettes", rom) {}
551
553 PaletteGroupMetadata metadata;
554 metadata.group_name = "ow_main";
555 metadata.display_name = "Overworld Main Palettes";
556 metadata.colors_per_palette = 8;
557 metadata.colors_per_row = 8;
558
559 // Light World palettes (0-19)
560 for (int i = 0; i < 20; i++) {
561 PaletteMetadata pal;
562 pal.palette_id = i;
563 pal.name = absl::StrFormat("Light World %d", i);
564 pal.description = "Used for Light World overworld graphics";
565 pal.rom_address = 0xDE6C8 + (i * 16); // Base address + offset
566 pal.vram_address = 0;
567 pal.usage_notes = "Modifying these colors affects Light World appearance";
568 metadata.palettes.push_back(pal);
569 }
570
571 // Dark World palettes (20-39)
572 for (int i = 20; i < 40; i++) {
573 PaletteMetadata pal;
574 pal.palette_id = i;
575 pal.name = absl::StrFormat("Dark World %d", i - 20);
576 pal.description = "Used for Dark World overworld graphics";
577 pal.rom_address = 0xDE6C8 + (i * 16);
578 pal.vram_address = 0;
579 pal.usage_notes = "Modifying these colors affects Dark World appearance";
580 metadata.palettes.push_back(pal);
581 }
582
583 // Special World palettes (40-59)
584 for (int i = 40; i < 60; i++) {
585 PaletteMetadata pal;
586 pal.palette_id = i;
587 pal.name = absl::StrFormat("Special %d", i - 40);
588 pal.description = "Used for Special World and triforce room";
589 pal.rom_address = 0xDE6C8 + (i * 16);
590 pal.vram_address = 0;
591 pal.usage_notes = "Modifying these colors affects Special World areas";
592 metadata.palettes.push_back(pal);
593 }
594
595 return metadata;
596}
597
601
603 // Note: rom_->palette_group() returns by value, so we need to use the mutable version
604 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("ow_main");
605}
606
608 auto* palette = GetMutablePalette(selected_palette_);
609 if (!palette) return;
610
611 const float button_size = 32.0f;
612 const int colors_per_row = GetColorsPerRow();
613
614 for (int i = 0; i < palette->size(); i++) {
615 bool is_selected = (i == selected_color_);
616 bool is_modified = IsColorModified(selected_palette_, i);
617
618 ImGui::PushID(i);
619
620 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
621 (*palette)[i], is_selected, is_modified,
622 ImVec2(button_size, button_size))) {
623 selected_color_ = i;
624 editing_color_ = (*palette)[i];
625 }
626
627 ImGui::PopID();
628
629 // Wrap to next row
630 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
631 ImGui::SameLine();
632 }
633 }
634}
635
636// ========== Overworld Animated Palette Card ==========
637
640
642 : PaletteGroupCard("ow_animated", "Overworld Animated Palettes", rom) {}
643
645 PaletteGroupMetadata metadata;
646 metadata.group_name = "ow_animated";
647 metadata.display_name = "Overworld Animated Palettes";
648 metadata.colors_per_palette = 8;
649 metadata.colors_per_row = 8;
650
651 // Animated palettes
652 const char* anim_names[] = {"Water", "Lava", "Poison Water", "Ice"};
653 for (int i = 0; i < 4; i++) {
654 PaletteMetadata pal;
655 pal.palette_id = i;
656 pal.name = anim_names[i];
657 pal.description = absl::StrFormat("%s animated palette cycle", anim_names[i]);
658 pal.rom_address = 0xDE86C + (i * 16);
659 pal.vram_address = 0;
660 pal.usage_notes = "These palettes cycle through multiple frames for animation";
661 metadata.palettes.push_back(pal);
662 }
663
664 return metadata;
665}
666
670
672 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("ow_animated");
673}
674
676 auto* palette = GetMutablePalette(selected_palette_);
677 if (!palette) return;
678
679 const float button_size = 32.0f;
680 const int colors_per_row = GetColorsPerRow();
681
682 for (int i = 0; i < palette->size(); i++) {
683 bool is_selected = (i == selected_color_);
684 bool is_modified = IsColorModified(selected_palette_, i);
685
686 ImGui::PushID(i);
687
688 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
689 (*palette)[i], is_selected, is_modified,
690 ImVec2(button_size, button_size))) {
691 selected_color_ = i;
692 editing_color_ = (*palette)[i];
693 }
694
695 ImGui::PopID();
696
697 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
698 ImGui::SameLine();
699 }
700 }
701}
702
703// ========== Dungeon Main Palette Card ==========
704
707
709 : PaletteGroupCard("dungeon_main", "Dungeon Main Palettes", rom) {}
710
712 PaletteGroupMetadata metadata;
713 metadata.group_name = "dungeon_main";
714 metadata.display_name = "Dungeon Main Palettes";
715 metadata.colors_per_palette = 16;
716 metadata.colors_per_row = 16;
717
718 // Dungeon palettes (0-19)
719 const char* dungeon_names[] = {
720 "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace",
721 "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire",
722 "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town",
723 "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2",
724 "Generic 3", "Generic 4", "Generic 5", "Generic 6"
725 };
726
727 for (int i = 0; i < 20; i++) {
728 PaletteMetadata pal;
729 pal.palette_id = i;
730 pal.name = dungeon_names[i];
731 pal.description = absl::StrFormat("Dungeon palette %d", i);
732 pal.rom_address = 0xDE604 + (i * 32);
733 pal.vram_address = 0;
734 pal.usage_notes = "16 colors per dungeon palette";
735 metadata.palettes.push_back(pal);
736 }
737
738 return metadata;
739}
740
744
746 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("dungeon_main");
747}
748
750 auto* palette = GetMutablePalette(selected_palette_);
751 if (!palette) return;
752
753 const float button_size = 28.0f;
754 const int colors_per_row = GetColorsPerRow();
755
756 for (int i = 0; i < palette->size(); i++) {
757 bool is_selected = (i == selected_color_);
758 bool is_modified = IsColorModified(selected_palette_, i);
759
760 ImGui::PushID(i);
761
762 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
763 (*palette)[i], is_selected, is_modified,
764 ImVec2(button_size, button_size))) {
765 selected_color_ = i;
766 editing_color_ = (*palette)[i];
767 }
768
769 ImGui::PopID();
770
771 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
772 ImGui::SameLine();
773 }
774 }
775}
776
777// ========== Sprite Palette Card ==========
778
781
783 : PaletteGroupCard("global_sprites", "Sprite Palettes", rom) {}
784
786 PaletteGroupMetadata metadata;
787 metadata.group_name = "global_sprites";
788 metadata.display_name = "Global Sprite Palettes";
789 metadata.colors_per_palette = 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48)
790 metadata.colors_per_row = 16; // Display in 16-color rows
791
792 // 2 palette sets: Light World and Dark World
793 const char* sprite_names[] = {
794 "Global Sprites (Light World)",
795 "Global Sprites (Dark World)"
796 };
797
798 for (int i = 0; i < 2; i++) {
799 PaletteMetadata pal;
800 pal.palette_id = i;
801 pal.name = sprite_names[i];
802 pal.description = "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, 32, 48";
803 pal.rom_address = (i == 0) ? 0xDD218 : 0xDD290; // LW or DW address
804 pal.vram_address = 0; // Loaded dynamically
805 pal.usage_notes = "4 sprite sub-palettes of 15 colors + transparent each. "
806 "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
807 metadata.palettes.push_back(pal);
808 }
809
810 return metadata;
811}
812
814 return rom_->mutable_palette_group()->get_group("global_sprites");
815}
816
818 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("global_sprites");
819}
820
822 auto* palette = GetMutablePalette(selected_palette_);
823 if (!palette) return;
824
825 const float button_size = 28.0f;
826 const int colors_per_row = GetColorsPerRow();
827
828 for (int i = 0; i < palette->size(); i++) {
829 bool is_selected = (i == selected_color_);
830 bool is_modified = IsColorModified(selected_palette_, i);
831
832 ImGui::PushID(i);
833
834 // Draw transparent color indicator at start of each 16-color row (0, 16, 32, 48, ...)
835 bool is_transparent_slot = (i % 16 == 0);
836 if (is_transparent_slot) {
837 ImGui::BeginGroup();
838 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
839 (*palette)[i], is_selected, is_modified,
840 ImVec2(button_size, button_size))) {
841 selected_color_ = i;
842 editing_color_ = (*palette)[i];
843 }
844 // Draw "T" for transparent
845 ImVec2 pos = ImGui::GetItemRectMin();
846 ImGui::GetWindowDrawList()->AddText(
847 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
848 IM_COL32(255, 255, 255, 200), "T");
849 ImGui::EndGroup();
850
851 if (ImGui::IsItemHovered()) {
852 ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d", i / 16);
853 }
854 } else {
855 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
856 (*palette)[i], is_selected, is_modified,
857 ImVec2(button_size, button_size))) {
858 selected_color_ = i;
859 editing_color_ = (*palette)[i];
860 }
861 }
862
863 ImGui::PopID();
864
865 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
866 ImGui::SameLine();
867 }
868 }
869}
870
872 // Show VRAM info panel
873 SectionHeader("VRAM Information");
874
875 const auto& metadata = GetMetadata();
876 if (selected_palette_ < metadata.palettes.size()) {
877 const auto& pal_meta = metadata.palettes[selected_palette_];
878
879 ImGui::TextWrapped("This sprite palette is loaded to VRAM address $%04X",
880 pal_meta.vram_address);
881 ImGui::TextDisabled("VRAM palettes are used by the SNES PPU for sprite rendering");
882 }
883}
884
885// ========== Equipment Palette Card ==========
886
889
891 : PaletteGroupCard("armors", "Equipment Palettes", rom) {}
892
894 PaletteGroupMetadata metadata;
895 metadata.group_name = "armors";
896 metadata.display_name = "Equipment Palettes";
897 metadata.colors_per_palette = 8;
898 metadata.colors_per_row = 8;
899
900 const char* armor_names[] = {"Green Mail", "Blue Mail", "Red Mail"};
901
902 for (int i = 0; i < 3; i++) {
903 PaletteMetadata pal;
904 pal.palette_id = i;
905 pal.name = armor_names[i];
906 pal.description = absl::StrFormat("Link's %s colors", armor_names[i]);
907 pal.rom_address = 0xDD308 + (i * 16);
908 pal.vram_address = 0;
909 pal.usage_notes = "Changes Link's tunic appearance";
910 metadata.palettes.push_back(pal);
911 }
912
913 return metadata;
914}
915
919
921 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("armors");
922}
923
925 auto* palette = GetMutablePalette(selected_palette_);
926 if (!palette) return;
927
928 const float button_size = 32.0f;
929 const int colors_per_row = GetColorsPerRow();
930
931 for (int i = 0; i < palette->size(); i++) {
932 bool is_selected = (i == selected_color_);
933 bool is_modified = IsColorModified(selected_palette_, i);
934
935 ImGui::PushID(i);
936
937 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
938 (*palette)[i], is_selected, is_modified,
939 ImVec2(button_size, button_size))) {
940 selected_color_ = i;
941 editing_color_ = (*palette)[i];
942 }
943
944 ImGui::PopID();
945
946 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
947 ImGui::SameLine();
948 }
949 }
950}
951
952// ========== Sprites Aux1 Palette Card ==========
953
956
958 : PaletteGroupCard("sprites_aux1", "Sprites Aux 1", rom) {}
959
961 PaletteGroupMetadata metadata;
962 metadata.group_name = "sprites_aux1";
963 metadata.display_name = "Sprites Aux 1";
964 metadata.colors_per_palette = 8; // 7 colors + transparent
965 metadata.colors_per_row = 8;
966
967 for (int i = 0; i < 12; i++) {
968 PaletteMetadata pal;
969 pal.palette_id = i;
970 pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
971 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
972 pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
973 pal.vram_address = 0;
974 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
975 metadata.palettes.push_back(pal);
976 }
977
978 return metadata;
979}
980
984
986 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux1");
987}
988
990 auto* palette = GetMutablePalette(selected_palette_);
991 if (!palette) return;
992
993 const float button_size = 32.0f;
994 const int colors_per_row = GetColorsPerRow();
995
996 for (int i = 0; i < palette->size(); i++) {
997 bool is_selected = (i == selected_color_);
998 bool is_modified = IsColorModified(selected_palette_, i);
999
1000 ImGui::PushID(i);
1001
1002 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1003 (*palette)[i], is_selected, is_modified,
1004 ImVec2(button_size, button_size))) {
1005 selected_color_ = i;
1006 editing_color_ = (*palette)[i];
1007 }
1008
1009 ImGui::PopID();
1010
1011 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1012 ImGui::SameLine();
1013 }
1014 }
1015}
1016
1017// ========== Sprites Aux2 Palette Card ==========
1018
1021
1023 : PaletteGroupCard("sprites_aux2", "Sprites Aux 2", rom) {}
1024
1026 PaletteGroupMetadata metadata;
1027 metadata.group_name = "sprites_aux2";
1028 metadata.display_name = "Sprites Aux 2";
1029 metadata.colors_per_palette = 8; // 7 colors + transparent
1030 metadata.colors_per_row = 8;
1031
1032 for (int i = 0; i < 11; i++) {
1033 PaletteMetadata pal;
1034 pal.palette_id = i;
1035 pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
1036 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1037 pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
1038 pal.vram_address = 0;
1039 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1040 metadata.palettes.push_back(pal);
1041 }
1042
1043 return metadata;
1044}
1045
1047 return rom_->mutable_palette_group()->get_group("sprites_aux2");
1048}
1049
1051 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux2");
1052}
1053
1055 auto* palette = GetMutablePalette(selected_palette_);
1056 if (!palette) return;
1057
1058 const float button_size = 32.0f;
1059 const int colors_per_row = GetColorsPerRow();
1060
1061 for (int i = 0; i < palette->size(); i++) {
1062 bool is_selected = (i == selected_color_);
1063 bool is_modified = IsColorModified(selected_palette_, i);
1064
1065 ImGui::PushID(i);
1066
1067 // Draw transparent color indicator for index 0
1068 if (i == 0) {
1069 ImGui::BeginGroup();
1070 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1071 (*palette)[i], is_selected, is_modified,
1072 ImVec2(button_size, button_size))) {
1073 selected_color_ = i;
1074 editing_color_ = (*palette)[i];
1075 }
1076 // Draw "T" for transparent
1077 ImVec2 pos = ImGui::GetItemRectMin();
1078 ImGui::GetWindowDrawList()->AddText(
1079 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1080 IM_COL32(255, 255, 255, 200), "T");
1081 ImGui::EndGroup();
1082 } else {
1083 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1084 (*palette)[i], is_selected, is_modified,
1085 ImVec2(button_size, button_size))) {
1086 selected_color_ = i;
1087 editing_color_ = (*palette)[i];
1088 }
1089 }
1090
1091 ImGui::PopID();
1092
1093 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1094 ImGui::SameLine();
1095 }
1096 }
1097}
1098
1099// ========== Sprites Aux3 Palette Card ==========
1100
1103
1105 : PaletteGroupCard("sprites_aux3", "Sprites Aux 3", rom) {}
1106
1108 PaletteGroupMetadata metadata;
1109 metadata.group_name = "sprites_aux3";
1110 metadata.display_name = "Sprites Aux 3";
1111 metadata.colors_per_palette = 8; // 7 colors + transparent
1112 metadata.colors_per_row = 8;
1113
1114 for (int i = 0; i < 24; i++) {
1115 PaletteMetadata pal;
1116 pal.palette_id = i;
1117 pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
1118 pal.description = "Auxiliary sprite palette (7 colors + transparent)";
1119 pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
1120 pal.vram_address = 0;
1121 pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
1122 metadata.palettes.push_back(pal);
1123 }
1124
1125 return metadata;
1126}
1127
1129 return rom_->mutable_palette_group()->get_group("sprites_aux3");
1130}
1131
1133 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux3");
1134}
1135
1137 auto* palette = GetMutablePalette(selected_palette_);
1138 if (!palette) return;
1139
1140 const float button_size = 32.0f;
1141 const int colors_per_row = GetColorsPerRow();
1142
1143 for (int i = 0; i < palette->size(); i++) {
1144 bool is_selected = (i == selected_color_);
1145 bool is_modified = IsColorModified(selected_palette_, i);
1146
1147 ImGui::PushID(i);
1148
1149 // Draw transparent color indicator for index 0
1150 if (i == 0) {
1151 ImGui::BeginGroup();
1152 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1153 (*palette)[i], is_selected, is_modified,
1154 ImVec2(button_size, button_size))) {
1155 selected_color_ = i;
1156 editing_color_ = (*palette)[i];
1157 }
1158 // Draw "T" for transparent
1159 ImVec2 pos = ImGui::GetItemRectMin();
1160 ImGui::GetWindowDrawList()->AddText(
1161 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1162 IM_COL32(255, 255, 255, 200), "T");
1163 ImGui::EndGroup();
1164 } else {
1165 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1166 (*palette)[i], is_selected, is_modified,
1167 ImVec2(button_size, button_size))) {
1168 selected_color_ = i;
1169 editing_color_ = (*palette)[i];
1170 }
1171 }
1172
1173 ImGui::PopID();
1174
1175 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1176 ImGui::SameLine();
1177 }
1178 }
1179}
1180
1181} // namespace editor
1182} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:74
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:795
auto mutable_palette_group()
Definition rom.h:217
bool is_loaded() const
Definition rom.h:200
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static PaletteGroupMetadata InitializeMetadata()
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.
static const PaletteGroupMetadata metadata_
static const PaletteGroupMetadata metadata_
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
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.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
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()
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
Base class for palette group editing cards.
void DrawPaletteSelector()
Draw palette selector dropdown.
absl::Status SaveToRom()
Save all modified palettes to ROM.
gfx::SnesPalette * GetMutablePalette(int index)
Get mutable palette by index.
virtual gfx::PaletteGroup * GetPaletteGroup()=0
Get the palette group for this card.
bool IsColorModified(int palette_index, int color_index) const
void DrawColorPicker()
Draw color picker for selected color.
void SetColor(int palette_index, int color_index, const gfx::SnesColor &new_color)
Set a color value (records change for undo)
void DrawBatchOperationsPopup()
Draw batch operations popup.
virtual void DrawCustomToolbarButtons()
Draw additional toolbar buttons (called after standard buttons)
void DrawToolbar()
Draw standard toolbar with save/discard/undo/redo.
virtual void DrawCustomPanels()
Draw additional panels (called after main content)
virtual void DrawPaletteGrid()=0
Draw the palette grid specific to this palette type.
virtual const PaletteGroupMetadata & GetMetadata() const =0
Get metadata for this palette group.
void ResetPalette(int palette_index)
Reset a specific palette to original ROM values.
gfx::SnesColor GetOriginalColor(int palette_index, int color_index) const
Get original color from ROM (for reset/comparison)
absl::Status WriteColorToRom(int palette_index, int color_index, const gfx::SnesColor &color)
Write a single color to ROM.
void ResetColor(int palette_index, int color_index)
Reset a specific color to original ROM value.
void DrawMetadataInfo()
Draw palette metadata info panel.
void DrawColorInfo()
Draw color info panel with RGB/SNES/Hex values.
PaletteGroupCard(const std::string &group_name, const std::string &display_name, Rom *rom)
Construct a new Palette Group Card.
bool IsPaletteModified(int palette_index) const
void DiscardChanges()
Discard all unsaved changes.
void Draw()
Draw the card's ImGui UI.
absl::Status ImportFromJson(const std::string &json)
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
void DrawCustomPanels() override
Draw additional panels (called after main content)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
const PaletteGroupMetadata & GetMetadata() const override
Get metadata for this palette group.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
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.
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
static PaletteGroupMetadata InitializeMetadata()
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
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:109
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:182
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:192
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:1241
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:742
#define ICON_MD_REDO
Definition icons.h:1568
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_FILE_UPLOAD
Definition icons.h:747
#define ICON_MD_RESTORE
Definition icons.h:1603
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_UNDO
Definition icons.h:2037
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)
Theme-aware widget library.
bool DangerButton(const char *label, const ImVec2 &size)
Danger/destructive action button (uses error color)
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
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:445
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)
Primary action button (uses accent color)
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:32
bool ThemedIconButton(const char *icon, const char *tooltip)
Themed button with icon (Material Design Icons)
Main namespace for the application.
Definition controller.cc:20
Metadata for an entire palette group.
std::vector< PaletteMetadata > palettes
Metadata for a single palette in a group.
Represents a group of palettes.