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