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#include <cctype>
5#include <string>
6#include <vector>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
11#include "absl/strings/strip.h"
15#include "app/gui/core/color.h"
16#include "app/gui/core/icons.h"
19#include "imgui/imgui.h"
20#include "util/json.h"
21
22namespace yaze {
23namespace editor {
24
25using namespace yaze::gui;
31
32namespace {
33
34absl::StatusOr<uint16_t> ParseSnesHexToken(std::string token) {
35 token = std::string(absl::StripAsciiWhitespace(token));
36 if (token.empty()) {
37 return absl::InvalidArgumentError("Empty color token");
38 }
39
40 if (token[0] == '$') {
41 token.erase(0, 1);
42 } else if (token.size() > 2 && (token.rfind("0x", 0) == 0 ||
43 token.rfind("0X", 0) == 0)) {
44 token.erase(0, 2);
45 }
46
47 if (token.empty()) {
48 return absl::InvalidArgumentError("Color token is missing hex digits");
49 }
50
51 for (char ch : token) {
52 if (!std::isxdigit(static_cast<unsigned char>(ch))) {
53 return absl::InvalidArgumentError(
54 absl::StrCat("Invalid hex digit in color token: ", token));
55 }
56 }
57
58 if (token.size() > 4) {
59 return absl::InvalidArgumentError(
60 absl::StrCat("Color token is too long for SNES color: ", token));
61 }
62
63 uint32_t value = 0;
64 try {
65 value = static_cast<uint32_t>(std::stoul(token, nullptr, 16));
66 } catch (const std::exception&) {
67 return absl::InvalidArgumentError(
68 absl::StrCat("Failed to parse color token: ", token));
69 }
70
71 if (value > 0x7FFF) {
72 return absl::InvalidArgumentError(
73 absl::StrCat("SNES color out of range (0x0000-0x7FFF): ", token));
74 }
75
76 return static_cast<uint16_t>(value);
77}
78
79absl::StatusOr<std::vector<uint16_t>> ParseClipboardColors(
80 const std::string& clipboard) {
81 std::vector<uint16_t> colors;
82 for (const auto& raw_token : absl::StrSplit(
83 clipboard, absl::ByAnyChar(", \n\r\t"), absl::SkipEmpty())) {
84 const std::string token = std::string(absl::StripAsciiWhitespace(raw_token));
85 if (token.empty()) {
86 continue;
87 }
88 auto color_or = ParseSnesHexToken(token);
89 if (!color_or.ok()) {
90 return color_or.status();
91 }
92 colors.push_back(*color_or);
93 }
94
95 if (colors.empty()) {
96 return absl::InvalidArgumentError("No colors found in clipboard data");
97 }
98
99 return colors;
100}
101
102#if defined(YAZE_WITH_JSON)
103absl::StatusOr<uint16_t> ParseSnesColorJson(const yaze::Json& value) {
104 if (value.is_string()) {
105 return ParseSnesHexToken(value.get<std::string>());
106 }
107 if (value.is_number_integer()) {
108 int parsed = value.get<int>();
109 if (parsed < 0 || parsed > 0x7FFF) {
110 return absl::InvalidArgumentError(
111 absl::StrFormat("SNES color out of range: %d", parsed));
112 }
113 return static_cast<uint16_t>(parsed);
114 }
115 if (value.is_number_unsigned()) {
116 uint32_t parsed = value.get<uint32_t>();
117 if (parsed > 0x7FFF) {
118 return absl::InvalidArgumentError(
119 absl::StrFormat("SNES color out of range: %u", parsed));
120 }
121 return static_cast<uint16_t>(parsed);
122 }
123 return absl::InvalidArgumentError(
124 "Invalid color value type (expected string or number)");
125}
126#endif
127
128} // namespace
129
130PaletteGroupPanel::PaletteGroupPanel(const std::string& group_name,
131 const std::string& display_name, Rom* rom,
132 zelda3::GameData* game_data)
133 : group_name_(group_name),
134 display_name_(display_name),
135 rom_(rom),
136 game_data_(game_data) {
137 // Note: We can't call GetPaletteGroup() here because it's a pure virtual
138 // function and the derived class isn't fully constructed yet. Original
139 // palettes will be loaded on first Draw() call instead.
140}
141
142void PaletteGroupPanel::Draw(bool* p_open) {
143 if (!rom_ || !rom_->is_loaded()) {
144 return;
145 }
146
147 // PaletteManager handles initialization of original palettes
148 // No need for local snapshot management anymore
149
150 // Main card window
151 // Note: Window management is handled by PanelManager/EditorPanel
152
153 DrawToolbar();
154 ImGui::Separator();
155
156 // Two-column layout: Grid on left, picker on right
157 if (ImGui::BeginTable(
158 "##PalettePanelLayout", 2,
159 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
160 ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
161 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f);
162
163 ImGui::TableNextRow();
164 ImGui::TableNextColumn();
165
166 // Left: Palette selector + grid
168 ImGui::Separator();
170
171 ImGui::TableNextColumn();
172
173 // Right: Color picker + info
174 if (selected_color_ >= 0) {
176 ImGui::Separator();
178 ImGui::Separator();
180 } else {
181 ImGui::TextDisabled("Select a color to edit");
182 ImGui::Separator();
184 }
185
186 // Custom panels from derived classes
188
189 ImGui::EndTable();
190 }
191
192 // Batch operations popup
194}
195
197 // Query PaletteManager for group-specific modification status
199
200 // Save button (primary action)
201 ImGui::BeginDisabled(!has_changes);
202 if (PrimaryButton(absl::StrFormat("%s Save to ROM", ICON_MD_SAVE).c_str())) {
203 auto status = SaveToRom();
204 if (!status.ok()) {
205 if (toast_manager_) {
207 absl::StrFormat("Failed to save %s: %s", display_name_,
208 status.message()),
210 }
211 }
212 }
213 ImGui::EndDisabled();
214 if (!has_changes &&
215 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
216 ImGui::SetTooltip("No palette changes to save");
217 }
218
219 ImGui::SameLine();
220
221 // Discard button (danger action)
222 ImGui::BeginDisabled(!has_changes);
223 if (DangerButton(absl::StrFormat("%s Discard", ICON_MD_UNDO).c_str())) {
225 }
226 ImGui::EndDisabled();
227 if (!has_changes &&
228 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
229 ImGui::SetTooltip("No palette changes to discard");
230 }
231
232 ImGui::SameLine();
233
234 // Modified indicator badge (show modified color count for this group)
235 if (has_changes) {
236 size_t modified_count = 0;
237 auto* group = GetPaletteGroup();
238 if (group) {
239 for (int p = 0; p < group->size(); p++) {
241 modified_count++;
242 }
243 }
244 }
245 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s %zu modified",
246 ICON_MD_EDIT, modified_count);
247 }
248
249 ImGui::SameLine();
250 ImGui::Dummy(ImVec2(20, 0)); // Spacer
251 ImGui::SameLine();
252
253 // Undo/Redo (global operations via PaletteManager)
254 bool can_undo = gfx::PaletteManager::Get().CanUndo();
255 ImGui::BeginDisabled(!can_undo);
256 if (ThemedIconButton(ICON_MD_UNDO, "Undo")) {
257 Undo();
258 }
259 ImGui::EndDisabled();
260 if (!can_undo &&
261 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
262 ImGui::SetTooltip("Nothing to undo");
263 }
264
265 ImGui::SameLine();
266 bool can_redo = gfx::PaletteManager::Get().CanRedo();
267 ImGui::BeginDisabled(!can_redo);
268 if (ThemedIconButton(ICON_MD_REDO, "Redo")) {
269 Redo();
270 }
271 ImGui::EndDisabled();
272 if (!can_redo &&
273 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
274 ImGui::SetTooltip("Nothing to redo");
275 }
276
277 ImGui::SameLine();
278 ImGui::Dummy(ImVec2(20, 0)); // Spacer
279 ImGui::SameLine();
280
281 // Export/Import
282 if (ThemedIconButton(ICON_MD_FILE_DOWNLOAD, "Export to clipboard")) {
284 }
285
286 ImGui::SameLine();
287 if (ThemedIconButton(ICON_MD_FILE_UPLOAD, "Import from clipboard")) {
289 }
290
291 ImGui::SameLine();
292 if (ThemedIconButton(ICON_MD_MORE_VERT, "Batch operations")) {
293 ImGui::OpenPopup("BatchOperations");
294 }
295
296 // Custom toolbar buttons from derived classes
298}
299
301 auto* palette_group = GetPaletteGroup();
302 if (!palette_group)
303 return;
304
305 int num_palettes = palette_group->size();
306
307 ImGui::Text("Palette:");
308 ImGui::SameLine();
309
310 ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
311 if (ImGui::BeginCombo(
312 "##PaletteSelect",
313 absl::StrFormat("Palette %d", selected_palette_).c_str())) {
314 const float item_height = ImGui::GetTextLineHeightWithSpacing();
315 ImGuiListClipper clipper;
316 clipper.Begin(num_palettes, item_height);
317 if (selected_palette_ >= 0 && selected_palette_ < num_palettes) {
318 clipper.IncludeItemByIndex(selected_palette_);
319 }
320
321 while (clipper.Step()) {
322 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) {
323 bool is_selected = (selected_palette_ == i);
324 bool is_modified = IsPaletteModified(i);
325
326 std::string label = absl::StrFormat("Palette %d", i);
327 if (is_modified) {
328 label += " *";
329 }
330
331 if (ImGui::Selectable(label.c_str(), is_selected)) {
333 selected_color_ = -1; // Reset color selection
334 }
335 if (is_selected) {
336 ImGui::SetItemDefaultFocus();
337 }
338 }
339 }
340 ImGui::EndCombo();
341 }
342
343 // Show reset button for current palette
344 ImGui::SameLine();
345 ImGui::BeginDisabled(!IsPaletteModified(selected_palette_));
346 if (ThemedIconButton(ICON_MD_RESTORE, "Reset palette to original")) {
348 }
349 ImGui::EndDisabled();
350}
351
353 if (selected_color_ < 0)
354 return;
355
356 auto* palette = GetMutablePalette(selected_palette_);
357 if (!palette)
358 return;
359
360 SectionHeader("Color Editor");
361
362 auto& color = (*palette)[selected_color_];
364
365 // Color picker with hue wheel
367 if (ImGui::ColorPicker4("##picker", &col.x,
368 ImGuiColorEditFlags_NoAlpha |
369 ImGuiColorEditFlags_PickerHueWheel |
370 ImGuiColorEditFlags_DisplayRGB |
371 ImGuiColorEditFlags_DisplayHSV)) {
374 }
375
376 // Current vs Original comparison
377 ImGui::Separator();
378 ImGui::Text("Current vs Original");
379
380 ImGui::ColorButton("##current", col,
381 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
382 ImVec2(60, 40));
383
384 LayoutHelpers::HelpMarker("Current color being edited");
385
386 ImGui::SameLine();
387
388 ImVec4 orig_col = ConvertSnesColorToImVec4(original);
389 if (ImGui::ColorButton(
390 "##original", orig_col,
391 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
392 ImVec2(60, 40))) {
393 // Click to restore original
394 editing_color_ = original;
396 }
397
398 if (ImGui::IsItemHovered()) {
399 ImGui::SetTooltip("Click to restore original color");
400 }
401
402 // Reset button
403 ImGui::BeginDisabled(!IsColorModified(selected_palette_, selected_color_));
404 if (ThemedButton(absl::StrFormat("%s Reset", ICON_MD_RESTORE).c_str(),
405 ImVec2(-1, 0))) {
407 }
408 ImGui::EndDisabled();
409}
410
412 if (selected_color_ < 0)
413 return;
414
415 SectionHeader("Color Information");
416
417 auto col = editing_color_.rgb();
418 int r = static_cast<int>(col.x);
419 int g = static_cast<int>(col.y);
420 int b = static_cast<int>(col.z);
421
422 // RGB values
423 ImGui::Text("RGB (0-255): (%d, %d, %d)", r, g, b);
424 if (ImGui::IsItemClicked()) {
425 ImGui::SetClipboardText(absl::StrFormat("(%d, %d, %d)", r, g, b).c_str());
426 }
427
428 // SNES BGR555 value
429 if (show_snes_format_) {
430 ImGui::Text("SNES BGR555: $%04X", editing_color_.snes());
431 if (ImGui::IsItemClicked()) {
432 ImGui::SetClipboardText(
433 absl::StrFormat("$%04X", editing_color_.snes()).c_str());
434 }
435 }
436
437 // Hex value
438 if (show_hex_format_) {
439 ImGui::Text("Hex: #%02X%02X%02X", r, g, b);
440 if (ImGui::IsItemClicked()) {
441 ImGui::SetClipboardText(
442 absl::StrFormat("#%02X%02X%02X", r, g, b).c_str());
443 }
444 }
445
446 ImGui::TextDisabled("Click any value to copy");
447}
448
450 const auto& metadata = GetMetadata();
451 if (selected_palette_ >= metadata.palettes.size())
452 return;
453
454 const auto& pal_meta = metadata.palettes[selected_palette_];
455
456 SectionHeader("Palette Metadata");
457
458 // Palette ID
459 ImGui::Text("Palette ID: %d", pal_meta.palette_id);
460
461 // Name
462 if (!pal_meta.name.empty()) {
463 ImGui::Text("Name: %s", pal_meta.name.c_str());
464 }
465
466 // Description
467 if (!pal_meta.description.empty()) {
468 ImGui::TextWrapped("%s", pal_meta.description.c_str());
469 }
470
471 ImGui::Separator();
472
473 // Palette dimensions and color depth
474 ImGui::Text("Dimensions: %d colors (%dx%d)", metadata.colors_per_palette,
475 metadata.colors_per_row,
476 (metadata.colors_per_palette + metadata.colors_per_row - 1) /
477 metadata.colors_per_row);
478
479 ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4);
480 ImGui::TextDisabled("(16 colors per palette possible)");
481
482 ImGui::Separator();
483
484 // ROM Address
485 ImGui::Text("ROM Address: $%06X", pal_meta.rom_address);
486 if (ImGui::IsItemClicked()) {
487 ImGui::SetClipboardText(
488 absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
489 }
490 if (ImGui::IsItemHovered()) {
491 ImGui::SetTooltip("Click to copy address");
492 }
493
494 // VRAM Address (if applicable)
495 if (pal_meta.vram_address > 0) {
496 ImGui::Text("VRAM Address: $%04X", pal_meta.vram_address);
497 if (ImGui::IsItemClicked()) {
498 ImGui::SetClipboardText(
499 absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
500 }
501 if (ImGui::IsItemHovered()) {
502 ImGui::SetTooltip("Click to copy VRAM address");
503 }
504 }
505
506 // Usage notes
507 if (!pal_meta.usage_notes.empty()) {
508 ImGui::Separator();
509 ImGui::TextDisabled("Usage Notes:");
510 ImGui::TextWrapped("%s", pal_meta.usage_notes.c_str());
511 }
512}
513
515 if (ImGui::BeginPopup("BatchOperations")) {
516 SectionHeader("Batch Operations");
517
518 if (ThemedButton("Copy Current Palette", ImVec2(-1, 0))) {
520 ImGui::CloseCurrentPopup();
521 }
522
523 if (ThemedButton("Paste to Current Palette", ImVec2(-1, 0))) {
525 ImGui::CloseCurrentPopup();
526 }
527
528 ImGui::Separator();
529
530 if (ThemedButton("Reset All Palettes", ImVec2(-1, 0))) {
532 ImGui::CloseCurrentPopup();
533 }
534
535 ImGui::EndPopup();
536 }
537}
538
539// ========== Palette Operations ==========
540
541void PaletteGroupPanel::SetColor(int palette_index, int color_index,
542 const gfx::SnesColor& new_color) {
543 // Delegate to PaletteManager for centralized tracking and undo/redo
544 auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index,
545 color_index, new_color);
546 if (!status.ok()) {
547 if (toast_manager_) {
549 absl::StrFormat("Failed to set color: %s", status.message()),
551 }
552 return;
553 }
554
555 // Auto-save if enabled (PaletteManager doesn't handle this)
556 if (auto_save_enabled_) {
557 WriteColorToRom(palette_index, color_index, new_color);
558 }
559}
560
562 // Delegate to PaletteManager for centralized save operation
564}
565
567 // Delegate to PaletteManager for centralized discard operation
569
570 // Reset selection
571 selected_color_ = -1;
572}
573
574void PaletteGroupPanel::ResetPalette(int palette_index) {
575 // Delegate to PaletteManager for centralized reset operation
577}
578
579void PaletteGroupPanel::ResetColor(int palette_index, int color_index) {
580 // Delegate to PaletteManager for centralized reset operation
582 color_index);
583}
584
585// ========== History Management ==========
586
588 // Delegate to PaletteManager's global undo system
590}
591
593 // Delegate to PaletteManager's global redo system
595}
596
598 // Delegate to PaletteManager's global history
600}
601
602// ========== State Queries ==========
603
604bool PaletteGroupPanel::IsPaletteModified(int palette_index) const {
605 // Query PaletteManager for modification status
607 palette_index);
608}
609
611 int color_index) const {
612 // Query PaletteManager for modification status
614 color_index);
615}
616
618 // Query PaletteManager for group-specific modification status
620}
621
623 // Query PaletteManager for global undo availability
625}
626
628 // Query PaletteManager for global redo availability
630}
631
632// ========== Helper Methods ==========
633
635 auto* palette_group = GetPaletteGroup();
636 if (!palette_group || index < 0 || index >= palette_group->size()) {
637 return nullptr;
638 }
639 return palette_group->mutable_palette(index);
640}
641
643 int color_index) const {
644 // Get original color from PaletteManager's snapshots
645 return gfx::PaletteManager::Get().GetColor(group_name_, palette_index,
646 color_index);
647}
648
649absl::Status PaletteGroupPanel::WriteColorToRom(int palette_index,
650 int color_index,
651 const gfx::SnesColor& color) {
652 uint32_t address =
653 gfx::GetPaletteAddress(group_name_, palette_index, color_index);
654 return rom_->WriteColor(address, color);
655}
656
657// MarkModified and ClearModified removed - PaletteManager handles tracking now
658
659// ========== Export/Import ==========
660
662#if defined(YAZE_WITH_JSON)
663 auto* palette_group = GetPaletteGroup();
664 if (!palette_group) {
665 return "{}";
666 }
667
669 root["version"] = 1;
670 root["group"] = group_name_;
671 root["display_name"] = display_name_;
672 root["palettes"] = yaze::Json::array();
673
674 for (size_t palette_index = 0; palette_index < palette_group->size();
675 palette_index++) {
676 const auto& palette =
677 palette_group->palette_ref(static_cast<int>(palette_index));
678 yaze::Json palette_json = yaze::Json::object();
679 palette_json["index"] = static_cast<int>(palette_index);
680 palette_json["colors"] = yaze::Json::array();
681
682 for (size_t color_index = 0; color_index < palette.size(); color_index++) {
683 palette_json["colors"].push_back(
684 absl::StrFormat("$%04X", palette[color_index].snes()));
685 }
686
687 root["palettes"].push_back(palette_json);
688 }
689
690 return root.dump(2);
691#else
692 return "{}";
693#endif
694}
695
696absl::Status PaletteGroupPanel::ImportFromJson(const std::string& json) {
697#if !defined(YAZE_WITH_JSON)
698 return absl::UnimplementedError("JSON support is disabled");
699#else
700 auto* palette_group = GetPaletteGroup();
701 if (!palette_group) {
702 return absl::FailedPreconditionError("Palette group is unavailable");
703 }
704
705 yaze::Json root;
706 try {
707 root = yaze::Json::parse(json);
708 } catch (const std::exception& e) {
709 return absl::InvalidArgumentError(
710 absl::StrCat("Failed to parse palette JSON: ", e.what()));
711 }
712
713 if (!root.is_object()) {
714 return absl::InvalidArgumentError("Palette JSON must be an object");
715 }
716
717 if (root.contains("version")) {
718 const auto& version_value = root["version"];
719 if (!version_value.is_number_integer() &&
720 !version_value.is_number_unsigned()) {
721 return absl::InvalidArgumentError(
722 "Palette JSON 'version' must be an integer");
723 }
724 int version = version_value.get<int>();
725 if (version != 1) {
726 return absl::InvalidArgumentError(
727 absl::StrFormat("Unsupported palette JSON version: %d", version));
728 }
729 }
730
731 if (root.contains("group")) {
732 const auto& group_value = root["group"];
733 if (!group_value.is_string()) {
734 return absl::InvalidArgumentError(
735 "Palette JSON 'group' must be a string");
736 }
737 const std::string group = group_value.get<std::string>();
738 if (group != group_name_) {
739 return absl::InvalidArgumentError(absl::StrFormat(
740 "Palette JSON group '%s' does not match '%s'", group, group_name_));
741 }
742 }
743
744 if (!root.contains("palettes") || !root["palettes"].is_array()) {
745 return absl::InvalidArgumentError(
746 "Palette JSON must contain a 'palettes' array");
747 }
748
749 struct PaletteImport {
750 int index;
751 std::vector<uint16_t> colors;
752 };
753
754 std::vector<PaletteImport> imports;
755 for (const auto& palette_json : root["palettes"]) {
756 if (!palette_json.is_object()) {
757 return absl::InvalidArgumentError(
758 "Palette entry must be a JSON object");
759 }
760
761 if (!palette_json.contains("index") ||
762 !palette_json["index"].is_number_integer()) {
763 return absl::InvalidArgumentError(
764 "Palette entry is missing integer 'index'");
765 }
766
767 int palette_index = palette_json["index"].get<int>();
768 if (palette_index < 0 || palette_index >= palette_group->size()) {
769 return absl::InvalidArgumentError(absl::StrFormat(
770 "Palette index %d out of range [0, %d)", palette_index,
771 static_cast<int>(palette_group->size())));
772 }
773
774 if (!palette_json.contains("colors") ||
775 !palette_json["colors"].is_array()) {
776 return absl::InvalidArgumentError(
777 "Palette entry is missing 'colors' array");
778 }
779
780 std::vector<uint16_t> colors;
781 colors.reserve(palette_json["colors"].size());
782 for (const auto& color_json : palette_json["colors"]) {
783 auto color_or = ParseSnesColorJson(color_json);
784 if (!color_or.ok()) {
785 return color_or.status();
786 }
787 colors.push_back(*color_or);
788 }
789
790 const auto& palette = palette_group->palette_ref(palette_index);
791 if (colors.size() != palette.size()) {
792 return absl::InvalidArgumentError(absl::StrFormat(
793 "Palette %d expects %d colors but received %d", palette_index,
794 static_cast<int>(palette.size()),
795 static_cast<int>(colors.size())));
796 }
797
798 imports.push_back({palette_index, std::move(colors)});
799 }
800
801 auto& manager = gfx::PaletteManager::Get();
802 manager.BeginBatch();
803 for (const auto& import : imports) {
804 for (size_t color_index = 0; color_index < import.colors.size();
805 color_index++) {
806 auto status = manager.SetColor(
807 group_name_, import.index, static_cast<int>(color_index),
808 gfx::SnesColor(import.colors[color_index]));
809 if (!status.ok()) {
810 manager.EndBatch();
811 return status;
812 }
813 }
814 }
815 manager.EndBatch();
816
817 if (selected_palette_ >= 0 && selected_palette_ < palette_group->size()) {
818 const auto& palette = palette_group->palette_ref(selected_palette_);
819 if (selected_color_ >= 0 && selected_color_ < palette.size()) {
821 }
822 }
823
824 return absl::OkStatus();
825#endif
826}
827
829 auto* palette_group = GetPaletteGroup();
830 if (!palette_group || selected_palette_ >= palette_group->size()) {
831 return "";
832 }
833
834 auto palette = palette_group->palette(selected_palette_);
835 std::string result;
836
837 for (size_t i = 0; i < palette.size(); i++) {
838 result += absl::StrFormat("$%04X", palette[i].snes());
839 if (i < palette.size() - 1) {
840 result += ",";
841 }
842 }
843
844 ImGui::SetClipboardText(result.c_str());
845 return result;
846}
847
849 auto* palette = GetMutablePalette(selected_palette_);
850 if (!palette) {
851 return absl::FailedPreconditionError("No palette selected");
852 }
853
854 const char* clipboard = ImGui::GetClipboardText();
855 if (!clipboard || clipboard[0] == '\0') {
856 return absl::InvalidArgumentError("Clipboard is empty");
857 }
858
859 auto colors_or = ParseClipboardColors(clipboard);
860 if (!colors_or.ok()) {
861 return colors_or.status();
862 }
863
864 const auto& colors = *colors_or;
865 if (colors.size() != palette->size()) {
866 return absl::InvalidArgumentError(absl::StrFormat(
867 "Clipboard contains %d colors but palette expects %d",
868 static_cast<int>(colors.size()),
869 static_cast<int>(palette->size())));
870 }
871
872 auto& manager = gfx::PaletteManager::Get();
873 manager.BeginBatch();
874 for (size_t color_index = 0; color_index < colors.size(); color_index++) {
875 auto status = manager.SetColor(
876 group_name_, selected_palette_, static_cast<int>(color_index),
877 gfx::SnesColor(colors[color_index]));
878 if (!status.ok()) {
879 manager.EndBatch();
880 return status;
881 }
882 }
883 manager.EndBatch();
884
885 if (selected_color_ >= 0 && selected_color_ < palette->size()) {
886 editing_color_ = (*palette)[selected_color_];
887 }
888
889 return absl::OkStatus();
890}
891
892// ============================================================================
893// Concrete Palette Panel Implementations
894// ============================================================================
895
896// ========== Overworld Main Palette Panel ==========
897
900
902 Rom* rom, zelda3::GameData* game_data)
903 : PaletteGroupPanel("ow_main", "Overworld Main Palettes", rom, game_data) {}
904
906 PaletteGroupMetadata metadata;
907 metadata.group_name = "ow_main";
908 metadata.display_name = "Overworld Main Palettes";
909 metadata.colors_per_palette = 35;
910 metadata.colors_per_row = 7;
911
912 // ALTTP OW main palettes are 35 colors per set (5 sub-palettes x 7 colors),
913 // stored contiguously in ROM and loaded into CGRAM starting at $0042.
914 // See usdasm: bank_1B.asm -> PaletteLoad_OWBGMain / PaletteData_owmain_00.
915 for (int i = 0; i < 6; i++) {
916 PaletteMetadata pal;
917 pal.palette_id = i;
918 pal.name = absl::StrFormat("Overworld Main %02d", i);
919 pal.description = "BG main palette set (35 colors = 5x7, transparent slots are implicit)";
920 pal.rom_address = gfx::kOverworldPaletteMain + (i * (35 * 2));
921 pal.vram_address = 0;
922 pal.usage_notes =
923 "Loaded by PaletteLoad_OWBGMain to CGRAM $0042 (rows 2-6, cols 1-7).";
924 metadata.palettes.push_back(pal);
925 }
926
927 return metadata;
928}
929
935
937 if (!game_data_)
938 return nullptr;
939 return const_cast<zelda3::GameData*>(game_data_)
940 ->palette_groups.get_group("ow_main");
941}
942
944 auto* palette = GetMutablePalette(selected_palette_);
945 if (!palette)
946 return;
947
948 const float button_size = 32.0f;
949 const int colors_per_row = GetColorsPerRow();
950
951 for (int i = 0; i < palette->size(); i++) {
952 bool is_selected = (i == selected_color_);
953 bool is_modified = IsColorModified(selected_palette_, i);
954
955 ImGui::PushID(i);
956
957 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
958 (*palette)[i], is_selected, is_modified,
959 ImVec2(button_size, button_size))) {
960 selected_color_ = i;
961 editing_color_ = (*palette)[i];
962 }
963
964 ImGui::PopID();
965
966 // Wrap to next row
967 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
968 ImGui::SameLine();
969 }
970 }
971}
972
973// ========== Overworld Animated Palette Panel ==========
974
977
979 Rom* rom, zelda3::GameData* game_data)
980 : PaletteGroupPanel("ow_animated", "Overworld Animated Palettes", rom,
981 game_data) {}
982
984 PaletteGroupMetadata metadata;
985 metadata.group_name = "ow_animated";
986 metadata.display_name = "Overworld Animated Palettes";
987 metadata.colors_per_palette = 7;
988 metadata.colors_per_row = 7;
989
990 // ALTTP OW animated palettes are 7 colors each, stored at kOverworldPaletteAnimated.
991 // See usdasm: bank_1B.asm -> PaletteLoad_OWBG3 / PaletteData_owanim_00.
992 for (int i = 0; i < 14; i++) {
993 PaletteMetadata pal;
994 pal.palette_id = i;
995 pal.name = absl::StrFormat("OW Anim %02d", i);
996 pal.description = "Animated overlay palette (7 colors, transparent slot is implicit)";
997 pal.rom_address = gfx::kOverworldPaletteAnimated + (i * (7 * 2));
998 pal.vram_address = 0;
999 pal.usage_notes =
1000 "Loaded by PaletteLoad_OWBG3 to CGRAM $00E2 (row 7, cols 1-7).";
1001 metadata.palettes.push_back(pal);
1002 }
1003
1004 return metadata;
1005}
1006
1008 if (!game_data_)
1009 return nullptr;
1010 return game_data_->palette_groups.get_group("ow_animated");
1011}
1012
1014 const {
1015 if (!game_data_)
1016 return nullptr;
1017 return const_cast<zelda3::GameData*>(game_data_)
1018 ->palette_groups.get_group("ow_animated");
1019}
1020
1022 auto* palette = GetMutablePalette(selected_palette_);
1023 if (!palette)
1024 return;
1025
1026 const float button_size = 32.0f;
1027 const int colors_per_row = GetColorsPerRow();
1028
1029 for (int i = 0; i < palette->size(); i++) {
1030 bool is_selected = (i == selected_color_);
1031 bool is_modified = IsColorModified(selected_palette_, i);
1032
1033 ImGui::PushID(i);
1034
1035 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1036 (*palette)[i], is_selected, is_modified,
1037 ImVec2(button_size, button_size))) {
1038 selected_color_ = i;
1039 editing_color_ = (*palette)[i];
1040 }
1041
1042 ImGui::PopID();
1043
1044 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1045 ImGui::SameLine();
1046 }
1047 }
1048}
1049
1050// ========== Dungeon Main Palette Panel ==========
1051
1054
1056 zelda3::GameData* game_data)
1057 : PaletteGroupPanel("dungeon_main", "Dungeon Main Palettes", rom,
1058 game_data) {}
1059
1061 PaletteGroupMetadata metadata;
1062 metadata.group_name = "dungeon_main";
1063 metadata.display_name = "Dungeon Main Palettes";
1064 metadata.colors_per_palette = 90;
1065 metadata.colors_per_row = 15;
1066
1067 // Dungeon palettes (0-19)
1068 const char* dungeon_names[] = {
1069 "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace",
1070 "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire",
1071 "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town",
1072 "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2",
1073 "Generic 3", "Generic 4", "Generic 5", "Generic 6"};
1074
1075 for (int i = 0; i < 20; i++) {
1076 PaletteMetadata pal;
1077 pal.palette_id = i;
1078 pal.name = dungeon_names[i];
1079 pal.description = absl::StrFormat("Dungeon palette %d", i);
1080 pal.rom_address = gfx::kDungeonMainPalettes + (i * (90 * 2));
1081 pal.vram_address = 0;
1082 pal.usage_notes =
1083 "90 colors = 6 CGRAM banks x 15 colors (transparent slot per bank is implicit).";
1084 metadata.palettes.push_back(pal);
1085 }
1086
1087 return metadata;
1088}
1089
1091 if (!game_data_)
1092 return nullptr;
1093 return game_data_->palette_groups.get_group("dungeon_main");
1094}
1095
1097 if (!game_data_)
1098 return nullptr;
1099 return const_cast<zelda3::GameData*>(game_data_)
1100 ->palette_groups.get_group("dungeon_main");
1101}
1102
1104 auto* palette = GetMutablePalette(selected_palette_);
1105 if (!palette)
1106 return;
1107
1108 const float button_size = 28.0f;
1109 const int colors_per_row = GetColorsPerRow();
1110
1111 for (int i = 0; i < palette->size(); i++) {
1112 bool is_selected = (i == selected_color_);
1113 bool is_modified = IsColorModified(selected_palette_, i);
1114
1115 ImGui::PushID(i);
1116
1117 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1118 (*palette)[i], is_selected, is_modified,
1119 ImVec2(button_size, button_size))) {
1120 selected_color_ = i;
1121 editing_color_ = (*palette)[i];
1122 }
1123
1124 ImGui::PopID();
1125
1126 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1127 ImGui::SameLine();
1128 }
1129 }
1130}
1131
1132// ========== Sprite Palette Panel ==========
1133
1136
1138 : PaletteGroupPanel("global_sprites", "Sprite Palettes", rom, game_data) {}
1139
1141 PaletteGroupMetadata metadata;
1142 metadata.group_name = "global_sprites";
1143 metadata.display_name = "Global Sprite Palettes";
1144 metadata.colors_per_palette = 60; // 4 sprite banks x 15 colors (transparent is implicit)
1145 metadata.colors_per_row = 15; // Display as 4 rows of 15 to match ROM layout
1146
1147 // 2 palette sets: Light World and Dark World
1148 const char* sprite_names[] = {"Global Sprites (Light World)",
1149 "Global Sprites (Dark World)"};
1150
1151 for (int i = 0; i < 2; i++) {
1152 PaletteMetadata pal;
1153 pal.palette_id = i;
1154 pal.name = sprite_names[i];
1155 pal.description =
1156 "60 colors = 4 sprite banks x 15 colors (transparent slots are implicit)";
1157 pal.rom_address =
1159 pal.vram_address = 0; // Palettes reside in PPU CGRAM (not VRAM)
1160 pal.usage_notes =
1161 "Loaded into CGRAM rows 9-12, cols 1-15 (row col0 is transparent).";
1162 metadata.palettes.push_back(pal);
1163 }
1164
1165 return metadata;
1166}
1167
1169 if (!game_data_)
1170 return nullptr;
1171 return game_data_->palette_groups.get_group("global_sprites");
1172}
1173
1175 if (!game_data_)
1176 return nullptr;
1177 return const_cast<zelda3::GameData*>(game_data_)
1178 ->palette_groups.get_group("global_sprites");
1179}
1180
1182 auto* palette = GetMutablePalette(selected_palette_);
1183 if (!palette)
1184 return;
1185
1186 const float button_size = 28.0f;
1187 const int colors_per_row = GetColorsPerRow();
1188
1189 for (int i = 0; i < palette->size(); i++) {
1190 bool is_selected = (i == selected_color_);
1191 bool is_modified = IsColorModified(selected_palette_, i);
1192
1193 ImGui::PushID(i);
1194
1195 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1196 (*palette)[i], is_selected, is_modified,
1197 ImVec2(button_size, button_size))) {
1198 selected_color_ = i;
1199 editing_color_ = (*palette)[i];
1200 }
1201
1202 ImGui::PopID();
1203
1204 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1205 ImGui::SameLine();
1206 }
1207 }
1208}
1209
1211 SectionHeader("CGRAM Placement");
1212 ImGui::TextWrapped(
1213 "Global sprite palettes are stored in ROM as 4 banks of 15 colors "
1214 "(transparent is implicit) and loaded to PPU CGRAM rows 9-12, cols 1-15.");
1215 ImGui::TextDisabled("Note: Palettes live in CGRAM, not VRAM.");
1216}
1217
1218// ========== Equipment Palette Panel ==========
1219
1222
1224 zelda3::GameData* game_data)
1225 : PaletteGroupPanel("armors", "Equipment Palettes", rom, game_data) {}
1226
1228 PaletteGroupMetadata metadata;
1229 metadata.group_name = "armors";
1230 metadata.display_name = "Equipment Palettes";
1231 metadata.colors_per_palette = 15;
1232 metadata.colors_per_row = 15;
1233
1234 const char* armor_names[] = {"Green Mail", "Blue Mail", "Red Mail", "Bunny",
1235 "Electrocuted"};
1236
1237 for (int i = 0; i < 5; i++) {
1238 PaletteMetadata pal;
1239 pal.palette_id = i;
1240 pal.name = armor_names[i];
1241 pal.description = absl::StrFormat("Link appearance: %s", armor_names[i]);
1242 pal.rom_address = gfx::kArmorPalettes + (i * (15 * 2));
1243 pal.vram_address = 0;
1244 pal.usage_notes =
1245 "15 colors per set (transparent slot is implicit when loaded into CGRAM).";
1246 metadata.palettes.push_back(pal);
1247 }
1248
1249 return metadata;
1250}
1251
1253 if (!game_data_)
1254 return nullptr;
1255 return game_data_->palette_groups.get_group("armors");
1256}
1257
1259 if (!game_data_)
1260 return nullptr;
1261 return const_cast<zelda3::GameData*>(game_data_)
1262 ->palette_groups.get_group("armors");
1263}
1264
1266 auto* palette = GetMutablePalette(selected_palette_);
1267 if (!palette)
1268 return;
1269
1270 const float button_size = 32.0f;
1271 const int colors_per_row = GetColorsPerRow();
1272
1273 for (int i = 0; i < palette->size(); i++) {
1274 bool is_selected = (i == selected_color_);
1275 bool is_modified = IsColorModified(selected_palette_, i);
1276
1277 ImGui::PushID(i);
1278
1279 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1280 (*palette)[i], is_selected, is_modified,
1281 ImVec2(button_size, button_size))) {
1282 selected_color_ = i;
1283 editing_color_ = (*palette)[i];
1284 }
1285
1286 ImGui::PopID();
1287
1288 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1289 ImGui::SameLine();
1290 }
1291 }
1292}
1293
1294// ========== Sprites Aux1 Palette Panel ==========
1295
1298
1300 zelda3::GameData* game_data)
1301 : PaletteGroupPanel("sprites_aux1", "Sprites Aux 1", rom, game_data) {}
1302
1304 PaletteGroupMetadata metadata;
1305 metadata.group_name = "sprites_aux1";
1306 metadata.display_name = "Sprites Aux 1";
1307 metadata.colors_per_palette = 7;
1308 metadata.colors_per_row = 7;
1309
1310 for (int i = 0; i < 12; i++) {
1311 PaletteMetadata pal;
1312 pal.palette_id = i;
1313 pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
1314 pal.description = "Auxiliary sprite palette (7 colors, transparent is implicit)";
1315 pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
1316 pal.vram_address = 0;
1317 pal.usage_notes =
1318 "Loaded into CGRAM with an implicit transparent slot at index 0 of the bank.";
1319 metadata.palettes.push_back(pal);
1320 }
1321
1322 return metadata;
1323}
1324
1326 if (!game_data_)
1327 return nullptr;
1328 return game_data_->palette_groups.get_group("sprites_aux1");
1329}
1330
1332 if (!game_data_)
1333 return nullptr;
1334 return const_cast<zelda3::GameData*>(game_data_)
1335 ->palette_groups.get_group("sprites_aux1");
1336}
1337
1339 auto* palette = GetMutablePalette(selected_palette_);
1340 if (!palette)
1341 return;
1342
1343 const float button_size = 32.0f;
1344 const int colors_per_row = GetColorsPerRow();
1345
1346 for (int i = 0; i < palette->size(); i++) {
1347 bool is_selected = (i == selected_color_);
1348 bool is_modified = IsColorModified(selected_palette_, i);
1349
1350 ImGui::PushID(i);
1351
1352 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1353 (*palette)[i], is_selected, is_modified,
1354 ImVec2(button_size, button_size))) {
1355 selected_color_ = i;
1356 editing_color_ = (*palette)[i];
1357 }
1358
1359 ImGui::PopID();
1360
1361 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1362 ImGui::SameLine();
1363 }
1364 }
1365}
1366
1367// ========== Sprites Aux2 Palette Panel ==========
1368
1371
1373 zelda3::GameData* game_data)
1374 : PaletteGroupPanel("sprites_aux2", "Sprites Aux 2", rom, game_data) {}
1375
1377 PaletteGroupMetadata metadata;
1378 metadata.group_name = "sprites_aux2";
1379 metadata.display_name = "Sprites Aux 2";
1380 metadata.colors_per_palette = 7;
1381 metadata.colors_per_row = 7;
1382
1383 for (int i = 0; i < 11; i++) {
1384 PaletteMetadata pal;
1385 pal.palette_id = i;
1386 pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
1387 pal.description = "Auxiliary sprite palette (7 colors, transparent is implicit)";
1388 pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
1389 pal.vram_address = 0;
1390 pal.usage_notes =
1391 "Loaded into CGRAM with an implicit transparent slot at index 0 of the bank.";
1392 metadata.palettes.push_back(pal);
1393 }
1394
1395 return metadata;
1396}
1397
1399 if (!game_data_)
1400 return nullptr;
1401 return game_data_->palette_groups.get_group("sprites_aux2");
1402}
1403
1405 if (!game_data_)
1406 return nullptr;
1407 return const_cast<zelda3::GameData*>(game_data_)
1408 ->palette_groups.get_group("sprites_aux2");
1409}
1410
1412 auto* palette = GetMutablePalette(selected_palette_);
1413 if (!palette)
1414 return;
1415
1416 const float button_size = 32.0f;
1417 const int colors_per_row = GetColorsPerRow();
1418
1419 for (int i = 0; i < palette->size(); i++) {
1420 bool is_selected = (i == selected_color_);
1421 bool is_modified = IsColorModified(selected_palette_, i);
1422
1423 ImGui::PushID(i);
1424
1425 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1426 (*palette)[i], is_selected, is_modified,
1427 ImVec2(button_size, button_size))) {
1428 selected_color_ = i;
1429 editing_color_ = (*palette)[i];
1430 }
1431
1432 ImGui::PopID();
1433
1434 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1435 ImGui::SameLine();
1436 }
1437 }
1438}
1439
1440// ========== Sprites Aux3 Palette Panel ==========
1441
1444
1446 zelda3::GameData* game_data)
1447 : PaletteGroupPanel("sprites_aux3", "Sprites Aux 3", rom, game_data) {}
1448
1450 PaletteGroupMetadata metadata;
1451 metadata.group_name = "sprites_aux3";
1452 metadata.display_name = "Sprites Aux 3";
1453 metadata.colors_per_palette = 7;
1454 metadata.colors_per_row = 7;
1455
1456 for (int i = 0; i < 24; i++) {
1457 PaletteMetadata pal;
1458 pal.palette_id = i;
1459 pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
1460 pal.description = "Auxiliary sprite palette (7 colors, transparent is implicit)";
1461 pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
1462 pal.vram_address = 0;
1463 pal.usage_notes =
1464 "Loaded into CGRAM with an implicit transparent slot at index 0 of the bank.";
1465 metadata.palettes.push_back(pal);
1466 }
1467
1468 return metadata;
1469}
1470
1472 if (!game_data_)
1473 return nullptr;
1474 return game_data_->palette_groups.get_group("sprites_aux3");
1475}
1476
1478 if (!game_data_)
1479 return nullptr;
1480 return const_cast<zelda3::GameData*>(game_data_)
1481 ->palette_groups.get_group("sprites_aux3");
1482}
1483
1485 auto* palette = GetMutablePalette(selected_palette_);
1486 if (!palette)
1487 return;
1488
1489 const float button_size = 32.0f;
1490 const int colors_per_row = GetColorsPerRow();
1491
1492 for (int i = 0; i < palette->size(); i++) {
1493 bool is_selected = (i == selected_color_);
1494 bool is_modified = IsColorModified(selected_palette_, i);
1495
1496 ImGui::PushID(i);
1497
1498 // Draw transparent color indicator for index 0
1499 if (i == 0) {
1500 ImGui::BeginGroup();
1501 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1502 (*palette)[i], is_selected, is_modified,
1503 ImVec2(button_size, button_size))) {
1504 selected_color_ = i;
1505 editing_color_ = (*palette)[i];
1506 }
1507 // Draw "T" for transparent
1508 ImVec2 pos = ImGui::GetItemRectMin();
1509 ImGui::GetWindowDrawList()->AddText(
1510 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1511 IM_COL32(255, 255, 255, 200), "T");
1512 ImGui::EndGroup();
1513 } else {
1514 if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
1515 (*palette)[i], is_selected, is_modified,
1516 ImVec2(button_size, button_size))) {
1517 selected_color_ = i;
1518 editing_color_ = (*palette)[i];
1519 }
1520 }
1521
1522 ImGui::PopID();
1523
1524 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1525 ImGui::SameLine();
1526 }
1527 }
1528}
1529
1530} // namespace editor
1531} // namespace yaze
bool is_object() const
Definition json.h:57
static Json parse(const std::string &)
Definition json.h:36
bool is_array() const
Definition json.h:58
static Json object()
Definition json.h:34
bool is_string() const
Definition json.h:59
static Json array()
Definition json.h:35
T get() const
Definition json.h:49
std::string dump(int=-1, char=' ', bool=false, int=0) const
Definition json.h:91
bool contains(const std::string &) const
Definition json.h:53
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:578
bool is_loaded() const
Definition rom.h:132
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.
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.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
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
absl::StatusOr< uint16_t > ParseSnesHexToken(std::string token)
absl::StatusOr< std::vector< uint16_t > > ParseClipboardColors(const std::string &clipboard)
constexpr int kArmorPalettes
constexpr int kOverworldPaletteAnimated
constexpr int kOverworldPaletteMain
constexpr int kGlobalSpritesLW
constexpr int kGlobalSpritePalettesDW
constexpr int kDungeonMainPalettes
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 ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled, const char *panel_id, const char *anim_id)
Draw a standard icon button with theme-aware colors.
bool PrimaryButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a primary action button (accented color).
bool ThemedButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a standard text button with theme colors.
bool DangerButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a danger action button (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:452
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:22
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:35
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