1#define IMGUI_DEFINE_MATH_OPERATORS
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"
24#include "imgui/imgui.h"
25#include "imgui/imgui_internal.h"
41 token = std::string(absl::StripAsciiWhitespace(token));
43 return absl::InvalidArgumentError(
"Empty color token");
46 if (token[0] ==
'$') {
48 }
else if (token.size() > 2 &&
49 (token.rfind(
"0x", 0) == 0 || token.rfind(
"0X", 0) == 0)) {
54 return absl::InvalidArgumentError(
"Color token is missing hex digits");
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));
64 if (token.size() > 4) {
65 return absl::InvalidArgumentError(
66 absl::StrCat(
"Color token is too long for SNES color: ", token));
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));
78 return absl::InvalidArgumentError(
79 absl::StrCat(
"SNES color out of range (0x0000-0x7FFF): ", token));
82 return static_cast<uint16_t
>(value);
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));
97 return color_or.status();
99 colors.push_back(*color_or);
102 if (colors.empty()) {
103 return absl::InvalidArgumentError(
"No colors found in clipboard data");
109#if defined(YAZE_WITH_JSON)
110absl::StatusOr<uint16_t> ParseSnesColorJson(
const yaze::Json& value) {
112 return ParseSnesHexToken(value.
get<std::string>());
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));
120 return static_cast<uint16_t
>(parsed);
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));
128 return static_cast<uint16_t
>(parsed);
130 return absl::InvalidArgumentError(
131 "Invalid color value type (expected string or number)");
138 const std::string& display_name,
Rom* rom,
140 : group_name_(group_name),
141 display_name_(display_name),
143 game_data_(game_data) {
160 group && group->size() > 0
161 ? std::clamp(index, 0,
static_cast<int>(group->size()) - 1)
172 bool batch_requested =
false;
176 ImGui::OpenPopup(
"SelectedPaletteTools");
177 if (ImGui::BeginPopup(
"SelectedPaletteTools")) {
178 if (ImGui::MenuItem(tr(
"Copy colors")))
180 if (ImGui::MenuItem(tr(
"Paste colors"))) {
186 if (ImGui::MenuItem(tr(
"Batch operations...")))
187 batch_requested =
true;
193 ImGui::GetContentRegionAvail().x >= ImGui::GetFontSize() * 44;
194 const auto draw_grid = [
this]() {
197 if (ImGui::CollapsingHeader(tr(
"Details"))) {
202 const auto draw_picker = [
this]() {
205 if (ImGui::CollapsingHeader(tr(
"Color values")))
208 ImGui::TextDisabled(tr(
"Select a color to edit"));
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();
217 ImGui::TableNextColumn();
220 }
else if (!columns) {
226 ImGui::OpenPopup(
"BatchOperations");
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) {
247 if ((i + 1) % columns && i + 1 < palette->size())
257 ImGui::BeginDisabled(!has_changes);
259 absl::StrFormat(
"%s Apply to buffer",
ICON_MD_SAVE).c_str())) {
269 ImGui::EndDisabled();
271 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
272 ImGui::SetTooltip(tr(
"No palette changes to apply"));
278 ImGui::BeginDisabled(!has_changes);
282 ImGui::EndDisabled();
284 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
285 ImGui::SetTooltip(tr(
"No palette changes to discard"));
292 size_t modified_count = 0;
295 for (
int p = 0; p < group->size(); p++) {
301 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), tr(
"%s %zu modified"),
306 ImGui::Dummy(ImVec2(20, 0));
311 ImGui::BeginDisabled(!can_undo);
315 ImGui::EndDisabled();
316 if (!can_undo && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
317 ImGui::SetTooltip(tr(
"Nothing to undo"));
322 ImGui::BeginDisabled(!can_redo);
326 ImGui::EndDisabled();
327 if (!can_redo && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
328 ImGui::SetTooltip(tr(
"Nothing to redo"));
332 ImGui::Dummy(ImVec2(20, 0));
347 ImGui::OpenPopup(
"BatchOperations");
359 int num_palettes = palette_group->size();
361 ImGui::Text(tr(
"Palette:"));
365 if (ImGui::BeginCombo(
368 const float item_height = ImGui::GetTextLineHeightWithSpacing();
369 ImGuiListClipper clipper;
370 clipper.Begin(num_palettes, item_height);
375 while (clipper.Step()) {
376 for (
int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) {
380 std::string label = absl::StrFormat(
"Palette %d", i);
385 if (ImGui::Selectable(label.c_str(), is_selected)) {
390 ImGui::SetItemDefaultFocus();
403 ImGui::EndDisabled();
421 const ImGuiID picker_id = ImGui::GetID(
"##picker");
423 ImGui::IsKeyPressed(ImGuiKey_Escape,
false)) {
424 manager.CancelColorEdit(picker_id);
429 ImGui::SetNextItemWidth(
430 std::min(ImGui::GetContentRegionAvail().x, ImGui::GetFontSize() * 19));
433 const bool changed = ImGui::ColorPicker4(
435 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel |
436 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV);
437 const bool active = ImGui::IsItemActive();
441 picker_metadata.
label =
"Palette color picker";
442 picker_metadata.
window_name = ImGui::GetCurrentWindow()->Name;
443 picker_metadata.
visible = ImGui::IsItemVisible();
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",
452 ImGui::EndDisabled();
455 (
active_picker_ != picker_id || !manager.HasColorEdit(picker_id))) {
467 manager.CommitColorEdit(picker_id);
470 }
else if (
active_picker_ == picker_id && !ImGui::IsMouseDown(0)) {
478 absl::SNPrintF(snes,
sizeof(snes),
"%04X",
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);
496 ImGui::Text(tr(
"Current vs Original"));
498 ImGui::ColorButton(
"##current", col,
499 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
507 if (ImGui::ColorButton(
508 "##original", orig_col,
509 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
515 if (ImGui::IsItemHovered()) {
516 ImGui::SetTooltip(tr(
"Click to restore original color"));
527 ImGui::EndDisabled();
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);
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());
553 ImGui::Text(tr(
"SNES BGR555: $%04X"), color.snes());
554 if (ImGui::IsItemClicked()) {
555 ImGui::SetClipboardText(absl::StrFormat(
"$%04X", color.snes()).c_str());
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());
568 ImGui::TextDisabled(tr(
"Click any value to copy"));
581 ImGui::Text(tr(
"Palette ID: %d"), pal_meta.palette_id);
584 if (!pal_meta.name.empty()) {
585 ImGui::Text(tr(
"Name: %s"), pal_meta.name.c_str());
589 if (!pal_meta.description.empty()) {
590 ImGui::TextWrapped(
"%s", pal_meta.description.c_str());
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);
601 ImGui::Text(tr(
"Color Depth: %d BPP (4-bit SNES)"), 4);
602 ImGui::TextDisabled(tr(
"(16 colors per palette possible)"));
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());
612 if (ImGui::IsItemHovered()) {
613 ImGui::SetTooltip(tr(
"Click to copy address"));
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());
623 if (ImGui::IsItemHovered()) {
624 ImGui::SetTooltip(tr(
"Click to copy VRAM address"));
629 if (!pal_meta.usage_notes.empty()) {
631 ImGui::TextDisabled(tr(
"Usage Notes:"));
632 ImGui::TextWrapped(
"%s", pal_meta.usage_notes.c_str());
637 if (ImGui::BeginPopup(
"BatchOperations")) {
640 if (
ThemedButton(
"Copy Current Palette", ImVec2(-1, 0))) {
642 ImGui::CloseCurrentPopup();
645 if (
ThemedButton(
"Paste to Current Palette", ImVec2(-1, 0))) {
647 ImGui::CloseCurrentPopup();
652 if (
ThemedButton(
"Reset All Palettes", ImVec2(-1, 0))) {
654 ImGui::CloseCurrentPopup();
675 color_index, new_color);
679 absl::StrFormat(
"Failed to set color: %s", status.message()),
688 return absl::FailedPreconditionError(
689 "Cannot save palettes from an inactive ROM session");
694 palette_manager.GetModifiedGroupColorWriteRanges(
group_name_);
695 if (!ranges.empty()) {
699 if (!preflight.ok()) {
775 int color_index)
const {
812 if (!palette_group || index < 0 || index >= palette_group->size()) {
815 return palette_group->mutable_palette(index);
819 int color_index)
const {
835#if defined(YAZE_WITH_JSON)
837 if (!palette_group) {
847 for (
size_t palette_index = 0; palette_index < palette_group->size();
849 const auto& palette =
850 palette_group->palette_ref(
static_cast<int>(palette_index));
852 palette_json[
"index"] =
static_cast<int>(palette_index);
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()));
860 root[
"palettes"].push_back(palette_json);
871 return absl::FailedPreconditionError(
872 "Cannot import palettes into an inactive ROM session");
874#if !defined(YAZE_WITH_JSON)
875 return absl::UnimplementedError(
"JSON support is disabled");
878 if (!palette_group) {
879 return absl::FailedPreconditionError(
"Palette group is unavailable");
885 }
catch (
const std::exception& e) {
886 return absl::InvalidArgumentError(
887 absl::StrCat(
"Failed to parse palette JSON: ", e.what()));
891 return absl::InvalidArgumentError(
"Palette JSON must be an object");
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");
901 int version = version_value.get<
int>();
903 return absl::InvalidArgumentError(
904 absl::StrFormat(
"Unsupported palette JSON version: %d", version));
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");
914 const std::string group = group_value.get<std::string>();
916 return absl::InvalidArgumentError(absl::StrFormat(
917 "Palette JSON group '%s' does not match '%s'", group,
group_name_));
922 return absl::InvalidArgumentError(
923 "Palette JSON must contain a 'palettes' array");
926 struct PaletteImport {
928 std::vector<uint16_t> colors;
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");
937 if (!palette_json.contains(
"index") ||
938 !palette_json[
"index"].is_number_integer()) {
939 return absl::InvalidArgumentError(
940 "Palette entry is missing integer 'index'");
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())));
950 if (!palette_json.contains(
"colors") ||
951 !palette_json[
"colors"].is_array()) {
952 return absl::InvalidArgumentError(
953 "Palette entry is missing 'colors' array");
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();
963 colors.push_back(*color_or);
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())));
973 imports.push_back({palette_index, std::move(colors)});
977 manager.BeginBatch();
978 for (
const auto&
import : imports) {
979 for (
size_t color_index = 0; color_index <
import.colors.size();
981 auto status = manager.SetColor(
982 group_name_,
import.index,
static_cast<int>(color_index),
992 return absl::OkStatus();
1005 for (
size_t i = 0; i < palette.size(); i++) {
1006 result += absl::StrFormat(
"$%04X", palette[i].snes());
1007 if (i < palette.size() - 1) {
1012 ImGui::SetClipboardText(result.c_str());
1018 return absl::FailedPreconditionError(
1019 "Cannot import palettes into an inactive ROM session");
1023 return absl::FailedPreconditionError(
"No palette selected");
1026 const char* clipboard = ImGui::GetClipboardText();
1027 if (!clipboard || clipboard[0] ==
'\0') {
1028 return absl::InvalidArgumentError(
"Clipboard is empty");
1031 auto colors_or = ParseClipboardColors(clipboard);
1032 if (!colors_or.ok()) {
1033 return colors_or.status();
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())));
1044 manager.BeginBatch();
1045 for (
size_t color_index = 0; color_index < colors.size(); color_index++) {
1047 static_cast<int>(color_index),
1056 return absl::OkStatus();
1082 for (
int i = 0; i < 6; i++) {
1085 pal.
name = absl::StrFormat(
"Overworld Main %02d", i);
1087 "BG main palette set (35 colors = 5x7, transparent slots are implicit)";
1091 "Loaded by PaletteLoad_OWBGMain to CGRAM $0042 (rows 2-6, cols 1-7).";
1108 ->palette_groups.get_group(
"ow_main");
1130 for (
int i = 0; i < 14; i++) {
1133 pal.
name = absl::StrFormat(
"OW Anim %02d", i);
1135 "Animated overlay palette (7 colors, transparent slot is implicit)";
1139 "Loaded by PaletteLoad_OWBG3 to CGRAM $00E2 (row 7, cols 1-7).";
1157 ->palette_groups.get_group(
"ow_animated");
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"};
1185 for (
int i = 0; i < 20; i++) {
1188 pal.
name = dungeon_names[i];
1189 pal.
description = absl::StrFormat(
"Dungeon palette %d", i);
1193 "90 colors = 6 CGRAM banks x 15 colors (transparent slot per bank is "
1211 ->palette_groups.get_group(
"dungeon_main");
1231 const char* sprite_names[] = {
"Global Sprites (Light World)",
1232 "Global Sprites (Dark World)"};
1234 for (
int i = 0; i < 2; i++) {
1237 pal.
name = sprite_names[i];
1239 "60 colors = 4 sprite banks x 15 colors (transparent slots are "
1245 "Loaded into CGRAM rows 9-12, cols 1-15 (row col0 is transparent).";
1262 ->palette_groups.get_group(
"global_sprites");
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 "
1271 ImGui::TextDisabled(tr(
"Note: Palettes live in CGRAM, not VRAM."));
1290 const char* armor_names[] = {
"Green Mail",
"Blue Mail",
"Red Mail",
"Bunny",
1293 for (
int i = 0; i < 5; i++) {
1296 pal.
name = armor_names[i];
1297 pal.
description = absl::StrFormat(
"Link appearance: %s", armor_names[i]);
1301 "15 colors per set (transparent slot is implicit when loaded into "
1319 ->palette_groups.get_group(
"armors");
1338 for (
int i = 0; i < 12; i++) {
1341 pal.
name = absl::StrFormat(
"Sprites Aux1 %02d", i);
1343 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1347 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1365 ->palette_groups.get_group(
"sprites_aux1");
1384 for (
int i = 0; i < 11; i++) {
1387 pal.
name = absl::StrFormat(
"Sprites Aux2 %02d", i);
1389 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1393 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1411 ->palette_groups.get_group(
"sprites_aux2");
1430 for (
int i = 0; i < 24; i++) {
1433 pal.
name = absl::StrFormat(
"Sprites Aux3 %02d", i);
1435 "Auxiliary sprite palette (7 colors, transparent is implicit)";
1439 "Loaded into CGRAM with an implicit transparent slot at index 0 of the "
1457 ->palette_groups.get_group(
"sprites_aux3");
static Json parse(const std::string &)
std::string dump(int=-1, char=' ', bool=false, int=0) const
bool contains(const std::string &) const
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
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 PaletteGroupMetadata InitializeMetadata()
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.
absl::Status ImportFromClipboard()
void DiscardChanges()
Discard all unsaved changes.
std::string ExportToJson() const
void DrawToolbar()
Draw standard toolbar with save/discard/undo/redo.
bool HasUnsavedChanges() const
void DrawBatchOperationsPopup()
Draw batch operations popup.
void SetSelectedPaletteIndex(int index)
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.
ToastManager * toast_manager_
virtual void DrawCustomPanels()
Draw additional panels (called after main content)
void DrawColorPicker()
Draw color picker for selected color.
bool IsPaletteModified(int palette_index) const
std::string display_name_
PaletteGroupPanel(const std::string &group_name, const std::string &display_name, Rom *rom, zelda3::GameData *game_data=nullptr)
Construct a new Palette Group Panel.
void DrawContent(bool embedded)
zelda3::GameData * game_data_
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)
std::string ExportToClipboard() const
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.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static float GetStandardInputWidth()
#define ICON_MD_MORE_VERT
#define ICON_MD_FILE_DOWNLOAD
#define ICON_MD_MORE_HORIZ
#define ICON_MD_FILE_UPLOAD
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)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
core::HackManifest hack_manifest
gfx::PaletteGroupMap palette_groups