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.
1#define IMGUI_DEFINE_MATH_OPERATORS
3#include "util/i18n/tr.h"
4
5#include <algorithm>
6#include <cctype>
7#include <chrono>
8#include <string>
9#include <vector>
10
11#include "absl/strings/str_cat.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_split.h"
14#include "absl/strings/strip.h"
20#include "app/gui/core/color.h"
21#include "app/gui/core/icons.h"
24#include "imgui/imgui.h"
25#include "imgui/imgui_internal.h"
26#include "util/json.h"
27
28namespace yaze {
29namespace editor {
30
31using namespace yaze::gui;
37
38namespace {
39
40absl::StatusOr<uint16_t> ParseSnesHexToken(std::string token) {
41 token = std::string(absl::StripAsciiWhitespace(token));
42 if (token.empty()) {
43 return absl::InvalidArgumentError("Empty color token");
44 }
45
46 if (token[0] == '$') {
47 token.erase(0, 1);
48 } else if (token.size() > 2 &&
49 (token.rfind("0x", 0) == 0 || token.rfind("0X", 0) == 0)) {
50 token.erase(0, 2);
51 }
52
53 if (token.empty()) {
54 return absl::InvalidArgumentError("Color token is missing hex digits");
55 }
56
57 for (char ch : token) {
58 if (!std::isxdigit(static_cast<unsigned char>(ch))) {
59 return absl::InvalidArgumentError(
60 absl::StrCat("Invalid hex digit in color token: ", token));
61 }
62 }
63
64 if (token.size() > 4) {
65 return absl::InvalidArgumentError(
66 absl::StrCat("Color token is too long for SNES color: ", token));
67 }
68
69 uint32_t value = 0;
70 try {
71 value = static_cast<uint32_t>(std::stoul(token, nullptr, 16));
72 } catch (const std::exception&) {
73 return absl::InvalidArgumentError(
74 absl::StrCat("Failed to parse color token: ", token));
75 }
76
77 if (value > 0x7FFF) {
78 return absl::InvalidArgumentError(
79 absl::StrCat("SNES color out of range (0x0000-0x7FFF): ", token));
80 }
81
82 return static_cast<uint16_t>(value);
83}
84
85absl::StatusOr<std::vector<uint16_t>> ParseClipboardColors(
86 const std::string& clipboard) {
87 std::vector<uint16_t> colors;
88 for (const auto& raw_token : absl::StrSplit(
89 clipboard, absl::ByAnyChar(", \n\r\t"), absl::SkipEmpty())) {
90 const std::string token =
91 std::string(absl::StripAsciiWhitespace(raw_token));
92 if (token.empty()) {
93 continue;
94 }
95 auto color_or = ParseSnesHexToken(token);
96 if (!color_or.ok()) {
97 return color_or.status();
98 }
99 colors.push_back(*color_or);
100 }
101
102 if (colors.empty()) {
103 return absl::InvalidArgumentError("No colors found in clipboard data");
104 }
105
106 return colors;
107}
108
109#if defined(YAZE_WITH_JSON)
110absl::StatusOr<uint16_t> ParseSnesColorJson(const yaze::Json& value) {
111 if (value.is_string()) {
112 return ParseSnesHexToken(value.get<std::string>());
113 }
114 if (value.is_number_integer()) {
115 int parsed = value.get<int>();
116 if (parsed < 0 || parsed > 0x7FFF) {
117 return absl::InvalidArgumentError(
118 absl::StrFormat("SNES color out of range: %d", parsed));
119 }
120 return static_cast<uint16_t>(parsed);
121 }
122 if (value.is_number_unsigned()) {
123 uint32_t parsed = value.get<uint32_t>();
124 if (parsed > 0x7FFF) {
125 return absl::InvalidArgumentError(
126 absl::StrFormat("SNES color out of range: %u", parsed));
127 }
128 return static_cast<uint16_t>(parsed);
129 }
130 return absl::InvalidArgumentError(
131 "Invalid color value type (expected string or number)");
132}
133#endif
134
135} // namespace
136
137PaletteGroupPanel::PaletteGroupPanel(const std::string& group_name,
138 const std::string& display_name, Rom* rom,
139 zelda3::GameData* game_data)
140 : group_name_(group_name),
141 display_name_(display_name),
142 rom_(rom),
143 game_data_(game_data) {
144 // Note: We can't call GetPaletteGroup() here because it's a pure virtual
145 // function and the derived class isn't fully constructed yet. Original
146 // palettes will be loaded on first Draw() call instead.
147}
148
149void PaletteGroupPanel::Draw(bool* p_open) {
150 DrawContent(false);
151}
152
156
158 const auto* group = GetPaletteGroup();
160 group && group->size() > 0
161 ? std::clamp(index, 0, static_cast<int>(group->size()) - 1)
162 : 0;
163 selected_color_ = -1;
164}
165
167 if (!IsManagedSession() || !rom_ || !rom_->is_loaded())
168 return;
169 if (!embedded)
170 DrawToolbar();
172 bool batch_requested = false;
173 if (embedded) {
174 ImGui::SameLine();
175 if (ThemedIconButton(ICON_MD_MORE_HORIZ, "Selected palette tools"))
176 ImGui::OpenPopup("SelectedPaletteTools");
177 if (ImGui::BeginPopup("SelectedPaletteTools")) {
178 if (ImGui::MenuItem(tr("Copy colors")))
180 if (ImGui::MenuItem(tr("Paste colors"))) {
181 auto result = ImportFromClipboard();
182 if (!result.ok() && toast_manager_)
183 toast_manager_->Show(std::string(result.message()),
185 }
186 if (ImGui::MenuItem(tr("Batch operations...")))
187 batch_requested = true;
188 ImGui::EndPopup();
189 }
190 }
191 ImGui::Separator();
192 const bool columns =
193 ImGui::GetContentRegionAvail().x >= ImGui::GetFontSize() * 44;
194 const auto draw_grid = [this]() {
196 ImGui::Spacing();
197 if (ImGui::CollapsingHeader(tr("Details"))) {
200 }
201 };
202 const auto draw_picker = [this]() {
203 if (selected_color_ >= 0) {
205 if (ImGui::CollapsingHeader(tr("Color values")))
207 } else {
208 ImGui::TextDisabled(tr("Select a color to edit"));
209 }
210 };
211 if (columns &&
212 ImGui::BeginTable("PaletteContent", 2, ImGuiTableFlags_Resizable)) {
213 ImGui::TableSetupColumn("Colors", ImGuiTableColumnFlags_WidthStretch, 0.6f);
214 ImGui::TableSetupColumn("Edit", ImGuiTableColumnFlags_WidthStretch, 0.4f);
215 ImGui::TableNextColumn();
216 draw_grid();
217 ImGui::TableNextColumn();
218 draw_picker();
219 ImGui::EndTable();
220 } else if (!columns) {
221 draw_grid();
222 ImGui::Spacing();
223 draw_picker();
224 }
225 if (batch_requested)
226 ImGui::OpenPopup("BatchOperations");
228}
229
231 auto* palette = GetMutablePalette(selected_palette_);
232 if (!palette)
233 return;
234 const int columns = GetColorsPerRow();
235 const float spacing = ImGui::GetStyle().ItemSpacing.x;
236 const float cell = std::clamp(
237 (ImGui::GetContentRegionAvail().x - (columns - 1) * spacing) / columns,
238 12.0f, ImGui::GetFrameHeight() * 1.35f);
239 for (int i = 0; i < palette->size(); ++i) {
240 ImGui::PushID(i);
241 if (gui::PaletteColorButton("##color", (*palette)[i], i == selected_color_,
243 ImVec2(cell, cell)))
244 selected_color_ = i;
245 gui::AutoRegisterLastItem("button", absl::StrFormat("Palette color %d", i));
246 ImGui::PopID();
247 if ((i + 1) % columns && i + 1 < palette->size())
248 ImGui::SameLine();
249 }
250}
251
253 // Query PaletteManager for group-specific modification status
255
256 // Save button (primary action)
257 ImGui::BeginDisabled(!has_changes);
258 if (PrimaryButton(
259 absl::StrFormat("%s Apply to buffer", ICON_MD_SAVE).c_str())) {
260 auto status = SaveToRom();
261 if (!status.ok()) {
262 if (toast_manager_) {
263 toast_manager_->Show(absl::StrFormat("Failed to save %s: %s",
264 display_name_, status.message()),
266 }
267 }
268 }
269 ImGui::EndDisabled();
270 if (!has_changes &&
271 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
272 ImGui::SetTooltip(tr("No palette changes to apply"));
273 }
274
275 ImGui::SameLine();
276
277 // Discard button (danger action)
278 ImGui::BeginDisabled(!has_changes);
279 if (DangerButton(absl::StrFormat("%s Discard", ICON_MD_UNDO).c_str())) {
281 }
282 ImGui::EndDisabled();
283 if (!has_changes &&
284 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
285 ImGui::SetTooltip(tr("No palette changes to discard"));
286 }
287
288 ImGui::SameLine();
289
290 // Modified indicator badge (show modified color count for this group)
291 if (has_changes) {
292 size_t modified_count = 0;
293 auto* group = GetPaletteGroup();
294 if (group) {
295 for (int p = 0; p < group->size(); p++) {
297 modified_count++;
298 }
299 }
300 }
301 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), tr("%s %zu modified"),
302 ICON_MD_EDIT, modified_count);
303 }
304
305 ImGui::SameLine();
306 ImGui::Dummy(ImVec2(20, 0)); // Spacer
307 ImGui::SameLine();
308
309 // Undo/Redo (global operations via PaletteManager)
310 bool can_undo = gfx::PaletteManager::Get().CanUndo();
311 ImGui::BeginDisabled(!can_undo);
312 if (ThemedIconButton(ICON_MD_UNDO, "Undo")) {
313 Undo();
314 }
315 ImGui::EndDisabled();
316 if (!can_undo && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
317 ImGui::SetTooltip(tr("Nothing to undo"));
318 }
319
320 ImGui::SameLine();
321 bool can_redo = gfx::PaletteManager::Get().CanRedo();
322 ImGui::BeginDisabled(!can_redo);
323 if (ThemedIconButton(ICON_MD_REDO, "Redo")) {
324 Redo();
325 }
326 ImGui::EndDisabled();
327 if (!can_redo && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
328 ImGui::SetTooltip(tr("Nothing to redo"));
329 }
330
331 ImGui::SameLine();
332 ImGui::Dummy(ImVec2(20, 0)); // Spacer
333 ImGui::SameLine();
334
335 // Export/Import
336 if (ThemedIconButton(ICON_MD_FILE_DOWNLOAD, "Export to clipboard")) {
338 }
339
340 ImGui::SameLine();
341 if (ThemedIconButton(ICON_MD_FILE_UPLOAD, "Import from clipboard")) {
343 }
344
345 ImGui::SameLine();
346 if (ThemedIconButton(ICON_MD_MORE_VERT, "Batch operations")) {
347 ImGui::OpenPopup("BatchOperations");
348 }
349
350 // Custom toolbar buttons from derived classes
352}
353
355 auto* palette_group = GetPaletteGroup();
356 if (!palette_group)
357 return;
358
359 int num_palettes = palette_group->size();
360
361 ImGui::Text(tr("Palette:"));
362 ImGui::SameLine();
363
364 ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
365 if (ImGui::BeginCombo(
366 "##PaletteSelect",
367 absl::StrFormat("Palette %d", selected_palette_).c_str())) {
368 const float item_height = ImGui::GetTextLineHeightWithSpacing();
369 ImGuiListClipper clipper;
370 clipper.Begin(num_palettes, item_height);
371 if (selected_palette_ >= 0 && selected_palette_ < num_palettes) {
372 clipper.IncludeItemByIndex(selected_palette_);
373 }
374
375 while (clipper.Step()) {
376 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) {
377 bool is_selected = (selected_palette_ == i);
378 bool is_modified = IsPaletteModified(i);
379
380 std::string label = absl::StrFormat("Palette %d", i);
381 if (is_modified) {
382 label += " *";
383 }
384
385 if (ImGui::Selectable(label.c_str(), is_selected)) {
387 selected_color_ = -1; // Reset color selection
388 }
389 if (is_selected) {
390 ImGui::SetItemDefaultFocus();
391 }
392 }
393 }
394 ImGui::EndCombo();
395 }
396
397 // Show reset button for current palette
398 ImGui::SameLine();
399 ImGui::BeginDisabled(!IsPaletteModified(selected_palette_));
400 if (ThemedIconButton(ICON_MD_RESTORE, "Reset palette to original")) {
402 }
403 ImGui::EndDisabled();
404}
405
407 if (selected_color_ < 0)
408 return;
409
410 auto* palette = GetMutablePalette(selected_palette_);
411 if (!palette)
412 return;
413
414 SectionHeader("Color Editor");
415
416 if (selected_color_ >= palette->size())
417 return;
419
420 auto& manager = gfx::PaletteManager::Get();
421 const ImGuiID picker_id = ImGui::GetID("##picker");
422 if (active_picker_ == picker_id &&
423 ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
424 manager.CancelColorEdit(picker_id);
425 picker_cancelled_ = true;
426 }
427
428 // Read current model state so Undo/Redo and comparison windows stay in sync.
429 ImGui::SetNextItemWidth(
430 std::min(ImGui::GetContentRegionAvail().x, ImGui::GetFontSize() * 19));
431 ImVec4 col = ConvertSnesColorToImVec4((*palette)[selected_color_]);
432 ImGui::BeginDisabled(picker_cancelled_ && active_picker_ == picker_id);
433 const bool changed = ImGui::ColorPicker4(
434 "##picker", &col.x,
435 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel |
436 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV);
437 const bool active = ImGui::IsItemActive();
438 // ColorPicker is a group with ID zero while idle; register its actual bounds
439 // with our stable ID so automation can discover it before starting a drag.
441 picker_metadata.label = "Palette color picker";
442 picker_metadata.window_name = ImGui::GetCurrentWindow()->Name;
443 picker_metadata.visible = ImGui::IsItemVisible();
444 picker_metadata.enabled = !picker_cancelled_;
445 const auto min = ImGui::GetItemRectMin();
446 const auto max = ImGui::GetItemRectMax();
447 picker_metadata.bounds = {min.x, min.y, max.x, max.y, true};
449 absl::StrCat("Palette/picker:", picker_id), "color", picker_id,
450 "Drag to preview; release commits one Undo step; Escape cancels",
451 picker_metadata);
452 ImGui::EndDisabled();
453 if (!picker_cancelled_) {
454 if (active &&
455 (active_picker_ != picker_id || !manager.HasColorEdit(picker_id))) {
456 const auto status = manager.BeginColorEdit(group_name_, selected_palette_,
457 selected_color_, picker_id);
458 if (status.ok()) {
459 active_picker_ = picker_id;
460 }
461 }
462 if (changed) {
465 }
466 if (!active && active_picker_ == picker_id) {
467 manager.CommitColorEdit(picker_id);
468 active_picker_ = 0;
469 }
470 } else if (active_picker_ == picker_id && !ImGui::IsMouseDown(0)) {
471 // Keep a held mouse from starting another edit immediately after Escape.
472 picker_cancelled_ = false;
473 active_picker_ = 0;
474 }
475 col = ConvertSnesColorToImVec4((*palette)[selected_color_]);
476
477 char snes[5];
478 absl::SNPrintF(snes, sizeof(snes), "%04X",
479 (*palette)[selected_color_].snes());
480 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 7);
481 if (ImGui::InputText("SNES", snes, sizeof(snes),
482 ImGuiInputTextFlags_CharsHexadecimal |
483 ImGuiInputTextFlags_EnterReturnsTrue)) {
484 const auto parsed = ParseSnesHexToken(snes);
485 if (parsed.ok()) {
487 } else if (toast_manager_) {
488 toast_manager_->Show(std::string(parsed.status().message()),
490 }
491 }
492 gui::AutoRegisterLastItem("input", "SNES color");
493
494 // Current vs Original comparison
495 ImGui::Separator();
496 ImGui::Text(tr("Current vs Original"));
497
498 ImGui::ColorButton("##current", col,
499 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
500 ImVec2(60, 40));
501
502 gui::AutoRegisterLastItem("color", "Current palette color");
503
504 ImGui::SameLine();
505
506 ImVec4 orig_col = ConvertSnesColorToImVec4(original);
507 if (ImGui::ColorButton(
508 "##original", orig_col,
509 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
510 ImVec2(60, 40))) {
511 // Click to restore original
513 }
514
515 if (ImGui::IsItemHovered()) {
516 ImGui::SetTooltip(tr("Click to restore original color"));
517 }
518
519 gui::AutoRegisterLastItem("button", "Restore original palette color");
520
521 // Reset button
522 ImGui::BeginDisabled(!IsColorModified(selected_palette_, selected_color_));
523 if (ThemedButton(absl::StrFormat("%s Reset", ICON_MD_RESTORE).c_str(),
524 ImVec2(-1, 0))) {
526 }
527 ImGui::EndDisabled();
528}
529
531 if (selected_color_ < 0)
532 return;
533
534 SectionHeader("Color Information");
535
536 const auto* palette = GetMutablePalette(selected_palette_);
537 if (!palette || selected_color_ >= palette->size())
538 return;
539 const auto color = (*palette)[selected_color_];
540 auto col = color.rgb();
541 int r = static_cast<int>(col.x);
542 int g = static_cast<int>(col.y);
543 int b = static_cast<int>(col.z);
544
545 // RGB values
546 ImGui::Text(tr("RGB (0-255): (%d, %d, %d)"), r, g, b);
547 if (ImGui::IsItemClicked()) {
548 ImGui::SetClipboardText(absl::StrFormat("(%d, %d, %d)", r, g, b).c_str());
549 }
550
551 // SNES BGR555 value
552 if (show_snes_format_) {
553 ImGui::Text(tr("SNES BGR555: $%04X"), color.snes());
554 if (ImGui::IsItemClicked()) {
555 ImGui::SetClipboardText(absl::StrFormat("$%04X", color.snes()).c_str());
556 }
557 }
558
559 // Hex value
560 if (show_hex_format_) {
561 ImGui::Text(tr("Hex: #%02X%02X%02X"), r, g, b);
562 if (ImGui::IsItemClicked()) {
563 ImGui::SetClipboardText(
564 absl::StrFormat("#%02X%02X%02X", r, g, b).c_str());
565 }
566 }
567
568 ImGui::TextDisabled(tr("Click any value to copy"));
569}
570
572 const auto& metadata = GetMetadata();
573 if (selected_palette_ >= metadata.palettes.size())
574 return;
575
576 const auto& pal_meta = metadata.palettes[selected_palette_];
577
578 SectionHeader("Palette Metadata");
579
580 // Palette ID
581 ImGui::Text(tr("Palette ID: %d"), pal_meta.palette_id);
582
583 // Name
584 if (!pal_meta.name.empty()) {
585 ImGui::Text(tr("Name: %s"), pal_meta.name.c_str());
586 }
587
588 // Description
589 if (!pal_meta.description.empty()) {
590 ImGui::TextWrapped("%s", pal_meta.description.c_str());
591 }
592
593 ImGui::Separator();
594
595 // Palette dimensions and color depth
596 ImGui::Text(tr("Dimensions: %d colors (%dx%d)"), metadata.colors_per_palette,
597 metadata.colors_per_row,
598 (metadata.colors_per_palette + metadata.colors_per_row - 1) /
599 metadata.colors_per_row);
600
601 ImGui::Text(tr("Color Depth: %d BPP (4-bit SNES)"), 4);
602 ImGui::TextDisabled(tr("(16 colors per palette possible)"));
603
604 ImGui::Separator();
605
606 // ROM Address
607 ImGui::Text(tr("ROM Address: $%06X"), pal_meta.rom_address);
608 if (ImGui::IsItemClicked()) {
609 ImGui::SetClipboardText(
610 absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
611 }
612 if (ImGui::IsItemHovered()) {
613 ImGui::SetTooltip(tr("Click to copy address"));
614 }
615
616 // VRAM Address (if applicable)
617 if (pal_meta.vram_address > 0) {
618 ImGui::Text(tr("VRAM Address: $%04X"), pal_meta.vram_address);
619 if (ImGui::IsItemClicked()) {
620 ImGui::SetClipboardText(
621 absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
622 }
623 if (ImGui::IsItemHovered()) {
624 ImGui::SetTooltip(tr("Click to copy VRAM address"));
625 }
626 }
627
628 // Usage notes
629 if (!pal_meta.usage_notes.empty()) {
630 ImGui::Separator();
631 ImGui::TextDisabled(tr("Usage Notes:"));
632 ImGui::TextWrapped("%s", pal_meta.usage_notes.c_str());
633 }
634}
635
637 if (ImGui::BeginPopup("BatchOperations")) {
638 SectionHeader("Batch Operations");
639
640 if (ThemedButton("Copy Current Palette", ImVec2(-1, 0))) {
642 ImGui::CloseCurrentPopup();
643 }
644
645 if (ThemedButton("Paste to Current Palette", ImVec2(-1, 0))) {
647 ImGui::CloseCurrentPopup();
648 }
649
650 ImGui::Separator();
651
652 if (ThemedButton("Reset All Palettes", ImVec2(-1, 0))) {
654 ImGui::CloseCurrentPopup();
655 }
656
657 ImGui::EndPopup();
658 }
659}
660
661// ========== Palette Operations ==========
662
663void PaletteGroupPanel::SetColor(int palette_index, int color_index,
664 const gfx::SnesColor& new_color) {
665 if (!IsManagedSession()) {
666 if (toast_manager_) {
667 toast_manager_->Show("Cannot edit palettes from an inactive ROM session",
669 }
670 return;
671 }
672
673 // Delegate to PaletteManager for centralized tracking and undo/redo
674 auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index,
675 color_index, new_color);
676 if (!status.ok()) {
677 if (toast_manager_) {
679 absl::StrFormat("Failed to set color: %s", status.message()),
681 }
682 return;
683 }
684}
685
687 if (!IsManagedSession()) {
688 return absl::FailedPreconditionError(
689 "Cannot save palettes from an inactive ROM session");
690 }
691 auto& palette_manager = gfx::PaletteManager::Get();
692 if (project_ != nullptr && project_->hack_manifest.loaded()) {
693 const auto ranges =
694 palette_manager.GetModifiedGroupColorWriteRanges(group_name_);
695 if (!ranges.empty()) {
696 const absl::Status preflight = ValidateHackManifestSaveConflicts(
698 display_name_, "PaletteGroupPanel", toast_manager_);
699 if (!preflight.ok()) {
700 return preflight;
701 }
702 }
703 }
704
705 // Delegate to PaletteManager for centralized save operation.
706 return palette_manager.SaveGroup(group_name_);
707}
708
710 if (!IsManagedSession()) {
711 return;
712 }
713 // Delegate to PaletteManager for centralized discard operation
715
716 // Reset selection
717 selected_color_ = -1;
718}
719
720void PaletteGroupPanel::ResetPalette(int palette_index) {
721 if (!IsManagedSession()) {
722 return;
723 }
724 // Delegate to PaletteManager for centralized reset operation
726}
727
728void PaletteGroupPanel::ResetColor(int palette_index, int color_index) {
729 if (!IsManagedSession()) {
730 return;
731 }
732 // Delegate to PaletteManager for centralized reset operation
734 color_index);
735}
736
737// ========== History Management ==========
738
740 if (!IsManagedSession()) {
741 return;
742 }
743 // Delegate to PaletteManager's global undo system
745}
746
748 if (!IsManagedSession()) {
749 return;
750 }
751 // Delegate to PaletteManager's global redo system
753}
754
756 if (!IsManagedSession()) {
757 return;
758 }
759 // Delegate to PaletteManager's global history
761}
762
763// ========== State Queries ==========
764
765bool PaletteGroupPanel::IsPaletteModified(int palette_index) const {
766 if (!IsManagedSession()) {
767 return false;
768 }
769 // Query PaletteManager for modification status
771 palette_index);
772}
773
775 int color_index) const {
776 if (!IsManagedSession()) {
777 return false;
778 }
779 // Query PaletteManager for modification status
781 color_index);
782}
783
785 if (!IsManagedSession()) {
786 return false;
787 }
788 // Query PaletteManager for group-specific modification status
790}
791
793 if (!IsManagedSession()) {
794 return false;
795 }
796 // Query PaletteManager for global undo availability
798}
799
801 if (!IsManagedSession()) {
802 return false;
803 }
804 // Query PaletteManager for global redo availability
806}
807
808// ========== Helper Methods ==========
809
811 auto* palette_group = GetPaletteGroup();
812 if (!palette_group || index < 0 || index >= palette_group->size()) {
813 return nullptr;
814 }
815 return palette_group->mutable_palette(index);
816}
817
819 int color_index) const {
820 // Get original color from PaletteManager's snapshots
822 color_index);
823}
824
829
830// MarkModified and ClearModified removed - PaletteManager handles tracking now
831
832// ========== Export/Import ==========
833
835#if defined(YAZE_WITH_JSON)
836 auto* palette_group = GetPaletteGroup();
837 if (!palette_group) {
838 return "{}";
839 }
840
842 root["version"] = 1;
843 root["group"] = group_name_;
844 root["display_name"] = display_name_;
845 root["palettes"] = yaze::Json::array();
846
847 for (size_t palette_index = 0; palette_index < palette_group->size();
848 palette_index++) {
849 const auto& palette =
850 palette_group->palette_ref(static_cast<int>(palette_index));
851 yaze::Json palette_json = yaze::Json::object();
852 palette_json["index"] = static_cast<int>(palette_index);
853 palette_json["colors"] = yaze::Json::array();
854
855 for (size_t color_index = 0; color_index < palette.size(); color_index++) {
856 palette_json["colors"].push_back(
857 absl::StrFormat("$%04X", palette[color_index].snes()));
858 }
859
860 root["palettes"].push_back(palette_json);
861 }
862
863 return root.dump(2);
864#else
865 return "{}";
866#endif
867}
868
869absl::Status PaletteGroupPanel::ImportFromJson(const std::string& json) {
870 if (!IsManagedSession()) {
871 return absl::FailedPreconditionError(
872 "Cannot import palettes into an inactive ROM session");
873 }
874#if !defined(YAZE_WITH_JSON)
875 return absl::UnimplementedError("JSON support is disabled");
876#else
877 auto* palette_group = GetPaletteGroup();
878 if (!palette_group) {
879 return absl::FailedPreconditionError("Palette group is unavailable");
880 }
881
882 yaze::Json root;
883 try {
884 root = yaze::Json::parse(json);
885 } catch (const std::exception& e) {
886 return absl::InvalidArgumentError(
887 absl::StrCat("Failed to parse palette JSON: ", e.what()));
888 }
889
890 if (!root.is_object()) {
891 return absl::InvalidArgumentError("Palette JSON must be an object");
892 }
893
894 if (root.contains("version")) {
895 const auto& version_value = root["version"];
896 if (!version_value.is_number_integer() &&
897 !version_value.is_number_unsigned()) {
898 return absl::InvalidArgumentError(
899 "Palette JSON 'version' must be an integer");
900 }
901 int version = version_value.get<int>();
902 if (version != 1) {
903 return absl::InvalidArgumentError(
904 absl::StrFormat("Unsupported palette JSON version: %d", version));
905 }
906 }
907
908 if (root.contains("group")) {
909 const auto& group_value = root["group"];
910 if (!group_value.is_string()) {
911 return absl::InvalidArgumentError(
912 "Palette JSON 'group' must be a string");
913 }
914 const std::string group = group_value.get<std::string>();
915 if (group != group_name_) {
916 return absl::InvalidArgumentError(absl::StrFormat(
917 "Palette JSON group '%s' does not match '%s'", group, group_name_));
918 }
919 }
920
921 if (!root.contains("palettes") || !root["palettes"].is_array()) {
922 return absl::InvalidArgumentError(
923 "Palette JSON must contain a 'palettes' array");
924 }
925
926 struct PaletteImport {
927 int index;
928 std::vector<uint16_t> colors;
929 };
930
931 std::vector<PaletteImport> imports;
932 for (const auto& palette_json : root["palettes"]) {
933 if (!palette_json.is_object()) {
934 return absl::InvalidArgumentError("Palette entry must be a JSON object");
935 }
936
937 if (!palette_json.contains("index") ||
938 !palette_json["index"].is_number_integer()) {
939 return absl::InvalidArgumentError(
940 "Palette entry is missing integer 'index'");
941 }
942
943 int palette_index = palette_json["index"].get<int>();
944 if (palette_index < 0 || palette_index >= palette_group->size()) {
945 return absl::InvalidArgumentError(absl::StrFormat(
946 "Palette index %d out of range [0, %d)", palette_index,
947 static_cast<int>(palette_group->size())));
948 }
949
950 if (!palette_json.contains("colors") ||
951 !palette_json["colors"].is_array()) {
952 return absl::InvalidArgumentError(
953 "Palette entry is missing 'colors' array");
954 }
955
956 std::vector<uint16_t> colors;
957 colors.reserve(palette_json["colors"].size());
958 for (const auto& color_json : palette_json["colors"]) {
959 auto color_or = ParseSnesColorJson(color_json);
960 if (!color_or.ok()) {
961 return color_or.status();
962 }
963 colors.push_back(*color_or);
964 }
965
966 const auto& palette = palette_group->palette_ref(palette_index);
967 if (colors.size() != palette.size()) {
968 return absl::InvalidArgumentError(absl::StrFormat(
969 "Palette %d expects %d colors but received %d", palette_index,
970 static_cast<int>(palette.size()), static_cast<int>(colors.size())));
971 }
972
973 imports.push_back({palette_index, std::move(colors)});
974 }
975
976 auto& manager = gfx::PaletteManager::Get();
977 manager.BeginBatch();
978 for (const auto& import : imports) {
979 for (size_t color_index = 0; color_index < import.colors.size();
980 color_index++) {
981 auto status = manager.SetColor(
982 group_name_, import.index, static_cast<int>(color_index),
983 gfx::SnesColor(import.colors[color_index]));
984 if (!status.ok()) {
985 manager.EndBatch();
986 return status;
987 }
988 }
989 }
990 manager.EndBatch();
991
992 return absl::OkStatus();
993#endif
994}
995
997 auto* palette_group = GetPaletteGroup();
998 if (!palette_group || selected_palette_ >= palette_group->size()) {
999 return "";
1000 }
1001
1002 auto palette = palette_group->palette(selected_palette_);
1003 std::string result;
1004
1005 for (size_t i = 0; i < palette.size(); i++) {
1006 result += absl::StrFormat("$%04X", palette[i].snes());
1007 if (i < palette.size() - 1) {
1008 result += ",";
1009 }
1010 }
1011
1012 ImGui::SetClipboardText(result.c_str());
1013 return result;
1014}
1015
1017 if (!IsManagedSession()) {
1018 return absl::FailedPreconditionError(
1019 "Cannot import palettes into an inactive ROM session");
1020 }
1021 auto* palette = GetMutablePalette(selected_palette_);
1022 if (!palette) {
1023 return absl::FailedPreconditionError("No palette selected");
1024 }
1025
1026 const char* clipboard = ImGui::GetClipboardText();
1027 if (!clipboard || clipboard[0] == '\0') {
1028 return absl::InvalidArgumentError("Clipboard is empty");
1029 }
1030
1031 auto colors_or = ParseClipboardColors(clipboard);
1032 if (!colors_or.ok()) {
1033 return colors_or.status();
1034 }
1035
1036 const auto& colors = *colors_or;
1037 if (colors.size() != palette->size()) {
1038 return absl::InvalidArgumentError(absl::StrFormat(
1039 "Clipboard contains %d colors but palette expects %d",
1040 static_cast<int>(colors.size()), static_cast<int>(palette->size())));
1041 }
1042
1043 auto& manager = gfx::PaletteManager::Get();
1044 manager.BeginBatch();
1045 for (size_t color_index = 0; color_index < colors.size(); color_index++) {
1046 auto status = manager.SetColor(group_name_, selected_palette_,
1047 static_cast<int>(color_index),
1048 gfx::SnesColor(colors[color_index]));
1049 if (!status.ok()) {
1050 manager.EndBatch();
1051 return status;
1052 }
1053 }
1054 manager.EndBatch();
1055
1056 return absl::OkStatus();
1057}
1058
1059// ============================================================================
1060// Concrete Palette Panel Implementations
1061// ============================================================================
1062
1063// ========== Overworld Main Palette Panel ==========
1064
1067
1069 Rom* rom, zelda3::GameData* game_data)
1070 : PaletteGroupPanel("ow_main", "Overworld Main Palettes", rom, game_data) {}
1071
1073 PaletteGroupMetadata metadata;
1074 metadata.group_name = "ow_main";
1075 metadata.display_name = "Overworld Main Palettes";
1076 metadata.colors_per_palette = 35;
1077 metadata.colors_per_row = 7;
1078
1079 // ALTTP OW main palettes are 35 colors per set (5 sub-palettes x 7 colors),
1080 // stored contiguously in ROM and loaded into CGRAM starting at $0042.
1081 // See usdasm: bank_1B.asm -> PaletteLoad_OWBGMain / PaletteData_owmain_00.
1082 for (int i = 0; i < 6; i++) {
1083 PaletteMetadata pal;
1084 pal.palette_id = i;
1085 pal.name = absl::StrFormat("Overworld Main %02d", i);
1086 pal.description =
1087 "BG main palette set (35 colors = 5x7, transparent slots are implicit)";
1088 pal.rom_address = gfx::kOverworldPaletteMain + (i * (35 * 2));
1089 pal.vram_address = 0;
1090 pal.usage_notes =
1091 "Loaded by PaletteLoad_OWBGMain to CGRAM $0042 (rows 2-6, cols 1-7).";
1092 metadata.palettes.push_back(pal);
1093 }
1094
1095 return metadata;
1096}
1097
1099 if (!game_data_)
1100 return nullptr;
1101 return game_data_->palette_groups.get_group("ow_main");
1102}
1103
1105 if (!game_data_)
1106 return nullptr;
1107 return const_cast<zelda3::GameData*>(game_data_)
1108 ->palette_groups.get_group("ow_main");
1109}
1110
1111// ========== Overworld Animated Palette Panel ==========
1112
1115
1117 Rom* rom, zelda3::GameData* game_data)
1118 : PaletteGroupPanel("ow_animated", "Overworld Animated Palettes", rom,
1119 game_data) {}
1120
1122 PaletteGroupMetadata metadata;
1123 metadata.group_name = "ow_animated";
1124 metadata.display_name = "Overworld Animated Palettes";
1125 metadata.colors_per_palette = 7;
1126 metadata.colors_per_row = 7;
1127
1128 // ALTTP OW animated palettes are 7 colors each, stored at kOverworldPaletteAnimated.
1129 // See usdasm: bank_1B.asm -> PaletteLoad_OWBG3 / PaletteData_owanim_00.
1130 for (int i = 0; i < 14; i++) {
1131 PaletteMetadata pal;
1132 pal.palette_id = i;
1133 pal.name = absl::StrFormat("OW Anim %02d", i);
1134 pal.description =
1135 "Animated overlay palette (7 colors, transparent slot is implicit)";
1136 pal.rom_address = gfx::kOverworldPaletteAnimated + (i * (7 * 2));
1137 pal.vram_address = 0;
1138 pal.usage_notes =
1139 "Loaded by PaletteLoad_OWBG3 to CGRAM $00E2 (row 7, cols 1-7).";
1140 metadata.palettes.push_back(pal);
1141 }
1142
1143 return metadata;
1144}
1145
1147 if (!game_data_)
1148 return nullptr;
1149 return game_data_->palette_groups.get_group("ow_animated");
1150}
1151
1153 const {
1154 if (!game_data_)
1155 return nullptr;
1156 return const_cast<zelda3::GameData*>(game_data_)
1157 ->palette_groups.get_group("ow_animated");
1158}
1159
1160// ========== Dungeon Main Palette Panel ==========
1161
1164
1166 zelda3::GameData* game_data)
1167 : PaletteGroupPanel("dungeon_main", "Dungeon Main Palettes", rom,
1168 game_data) {}
1169
1171 PaletteGroupMetadata metadata;
1172 metadata.group_name = "dungeon_main";
1173 metadata.display_name = "Dungeon Main Palettes";
1174 metadata.colors_per_palette = 90;
1175 metadata.colors_per_row = 15;
1176
1177 // Dungeon palettes (0-19)
1178 const char* dungeon_names[] = {
1179 "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace",
1180 "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire",
1181 "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town",
1182 "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2",
1183 "Generic 3", "Generic 4", "Generic 5", "Generic 6"};
1184
1185 for (int i = 0; i < 20; i++) {
1186 PaletteMetadata pal;
1187 pal.palette_id = i;
1188 pal.name = dungeon_names[i];
1189 pal.description = absl::StrFormat("Dungeon palette %d", i);
1190 pal.rom_address = gfx::kDungeonMainPalettes + (i * (90 * 2));
1191 pal.vram_address = 0;
1192 pal.usage_notes =
1193 "90 colors = 6 CGRAM banks x 15 colors (transparent slot per bank is "
1194 "implicit).";
1195 metadata.palettes.push_back(pal);
1196 }
1197
1198 return metadata;
1199}
1200
1202 if (!game_data_)
1203 return nullptr;
1204 return game_data_->palette_groups.get_group("dungeon_main");
1205}
1206
1208 if (!game_data_)
1209 return nullptr;
1210 return const_cast<zelda3::GameData*>(game_data_)
1211 ->palette_groups.get_group("dungeon_main");
1212}
1213
1214// ========== Sprite Palette Panel ==========
1215
1218
1220 : PaletteGroupPanel("global_sprites", "Sprite Palettes", rom, game_data) {}
1221
1223 PaletteGroupMetadata metadata;
1224 metadata.group_name = "global_sprites";
1225 metadata.display_name = "Global Sprite Palettes";
1226 metadata.colors_per_palette =
1227 60; // 4 sprite banks x 15 colors (transparent is implicit)
1228 metadata.colors_per_row = 15; // Display as 4 rows of 15 to match ROM layout
1229
1230 // 2 palette sets: Light World and Dark World
1231 const char* sprite_names[] = {"Global Sprites (Light World)",
1232 "Global Sprites (Dark World)"};
1233
1234 for (int i = 0; i < 2; i++) {
1235 PaletteMetadata pal;
1236 pal.palette_id = i;
1237 pal.name = sprite_names[i];
1238 pal.description =
1239 "60 colors = 4 sprite banks x 15 colors (transparent slots are "
1240 "implicit)";
1241 pal.rom_address =
1243 pal.vram_address = 0; // Palettes reside in PPU CGRAM (not VRAM)
1244 pal.usage_notes =
1245 "Loaded into CGRAM rows 9-12, cols 1-15 (row col0 is transparent).";
1246 metadata.palettes.push_back(pal);
1247 }
1248
1249 return metadata;
1250}
1251
1253 if (!game_data_)
1254 return nullptr;
1255 return game_data_->palette_groups.get_group("global_sprites");
1256}
1257
1259 if (!game_data_)
1260 return nullptr;
1261 return const_cast<zelda3::GameData*>(game_data_)
1262 ->palette_groups.get_group("global_sprites");
1263}
1264
1266 SectionHeader("CGRAM Placement");
1267 ImGui::TextWrapped(
1268 tr("Global sprite palettes are stored in ROM as 4 banks of 15 colors "
1269 "(transparent is implicit) and loaded to PPU CGRAM rows 9-12, cols "
1270 "1-15."));
1271 ImGui::TextDisabled(tr("Note: Palettes live in CGRAM, not VRAM."));
1272}
1273
1274// ========== Equipment Palette Panel ==========
1275
1278
1280 zelda3::GameData* game_data)
1281 : PaletteGroupPanel("armors", "Equipment Palettes", rom, game_data) {}
1282
1284 PaletteGroupMetadata metadata;
1285 metadata.group_name = "armors";
1286 metadata.display_name = "Equipment Palettes";
1287 metadata.colors_per_palette = 15;
1288 metadata.colors_per_row = 15;
1289
1290 const char* armor_names[] = {"Green Mail", "Blue Mail", "Red Mail", "Bunny",
1291 "Electrocuted"};
1292
1293 for (int i = 0; i < 5; i++) {
1294 PaletteMetadata pal;
1295 pal.palette_id = i;
1296 pal.name = armor_names[i];
1297 pal.description = absl::StrFormat("Link appearance: %s", armor_names[i]);
1298 pal.rom_address = gfx::kArmorPalettes + (i * (15 * 2));
1299 pal.vram_address = 0;
1300 pal.usage_notes =
1301 "15 colors per set (transparent slot is implicit when loaded into "
1302 "CGRAM).";
1303 metadata.palettes.push_back(pal);
1304 }
1305
1306 return metadata;
1307}
1308
1310 if (!game_data_)
1311 return nullptr;
1312 return game_data_->palette_groups.get_group("armors");
1313}
1314
1316 if (!game_data_)
1317 return nullptr;
1318 return const_cast<zelda3::GameData*>(game_data_)
1319 ->palette_groups.get_group("armors");
1320}
1321
1322// ========== Sprites Aux1 Palette Panel ==========
1323
1326
1328 zelda3::GameData* game_data)
1329 : PaletteGroupPanel("sprites_aux1", "Sprites Aux 1", rom, game_data) {}
1330
1332 PaletteGroupMetadata metadata;
1333 metadata.group_name = "sprites_aux1";
1334 metadata.display_name = "Sprites Aux 1";
1335 metadata.colors_per_palette = 7;
1336 metadata.colors_per_row = 7;
1337
1338 for (int i = 0; i < 12; i++) {
1339 PaletteMetadata pal;
1340 pal.palette_id = i;
1341 pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
1342 pal.description =
1343 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1344 pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
1345 pal.vram_address = 0;
1346 pal.usage_notes =
1347 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1348 "bank.";
1349 metadata.palettes.push_back(pal);
1350 }
1351
1352 return metadata;
1353}
1354
1356 if (!game_data_)
1357 return nullptr;
1358 return game_data_->palette_groups.get_group("sprites_aux1");
1359}
1360
1362 if (!game_data_)
1363 return nullptr;
1364 return const_cast<zelda3::GameData*>(game_data_)
1365 ->palette_groups.get_group("sprites_aux1");
1366}
1367
1368// ========== Sprites Aux2 Palette Panel ==========
1369
1372
1374 zelda3::GameData* game_data)
1375 : PaletteGroupPanel("sprites_aux2", "Sprites Aux 2", rom, game_data) {}
1376
1378 PaletteGroupMetadata metadata;
1379 metadata.group_name = "sprites_aux2";
1380 metadata.display_name = "Sprites Aux 2";
1381 metadata.colors_per_palette = 7;
1382 metadata.colors_per_row = 7;
1383
1384 for (int i = 0; i < 11; i++) {
1385 PaletteMetadata pal;
1386 pal.palette_id = i;
1387 pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
1388 pal.description =
1389 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1390 pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
1391 pal.vram_address = 0;
1392 pal.usage_notes =
1393 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1394 "bank.";
1395 metadata.palettes.push_back(pal);
1396 }
1397
1398 return metadata;
1399}
1400
1402 if (!game_data_)
1403 return nullptr;
1404 return game_data_->palette_groups.get_group("sprites_aux2");
1405}
1406
1408 if (!game_data_)
1409 return nullptr;
1410 return const_cast<zelda3::GameData*>(game_data_)
1411 ->palette_groups.get_group("sprites_aux2");
1412}
1413
1414// ========== Sprites Aux3 Palette Panel ==========
1415
1418
1420 zelda3::GameData* game_data)
1421 : PaletteGroupPanel("sprites_aux3", "Sprites Aux 3", rom, game_data) {}
1422
1424 PaletteGroupMetadata metadata;
1425 metadata.group_name = "sprites_aux3";
1426 metadata.display_name = "Sprites Aux 3";
1427 metadata.colors_per_palette = 7;
1428 metadata.colors_per_row = 7;
1429
1430 for (int i = 0; i < 24; i++) {
1431 PaletteMetadata pal;
1432 pal.palette_id = i;
1433 pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
1434 pal.description =
1435 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1436 pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
1437 pal.vram_address = 0;
1438 pal.usage_notes =
1439 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1440 "bank.";
1441 metadata.palettes.push_back(pal);
1442 }
1443
1444 return metadata;
1445}
1446
1448 if (!game_data_)
1449 return nullptr;
1450 return game_data_->palette_groups.get_group("sprites_aux3");
1451}
1452
1454 if (!game_data_)
1455 return nullptr;
1456 return const_cast<zelda3::GameData*>(game_data_)
1457 ->palette_groups.get_group("sprites_aux3");
1458}
1459
1460} // namespace editor
1461} // 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:49
bool is_loaded() const
Definition rom.h:187
bool loaded() const
Check if the manifest has been loaded.
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.
EquipmentPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
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)
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
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 DrawPaletteGrid()
Draw the palette grid specific to this palette type.
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 int GetColorsPerRow() const =0
Get the number of colors per row for grid layout.
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
SpritePalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
void DrawCustomPanels() override
Draw additional panels (called after main content)
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
SpritesAux1PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
SpritesAux2PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
SpritesAux3PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
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, std::string action_label={}, std::function< void()> on_action=nullptr)
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 the complete redo entry matches current colors. Conflicting history is retained,...
bool CanUndo() const
Check if the complete undo entry matches current colors. Conflicting history is retained,...
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.
SnesColor GetOriginalColor(const std::string &group_name, int palette_index, int color_index) const
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.
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
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static float GetStandardInputWidth()
void RegisterWidget(const std::string &full_path, const std::string &type, ImGuiID imgui_id, const std::string &description="", const WidgetMetadata &metadata=WidgetMetadata())
static WidgetIdRegistry & Instance()
#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_MORE_HORIZ
Definition icons.h:1241
#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).
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
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:463
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:24
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:37
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:225
core::HackManifest hack_manifest
Definition project.h:360
gfx::PaletteGroupMap palette_groups
Definition game_data.h:95
Automatic widget registration helpers for ImGui Test Engine integration.