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