5#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
25 const std::string& display_name,
27 : group_name_(group_name),
28 display_name_(display_name),
49 if (ImGui::BeginTable(
"##PaletteCardLayout", 2,
50 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
51 ImGui::TableSetupColumn(
"Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
52 ImGui::TableSetupColumn(
"Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f);
54 ImGui::TableNextRow();
55 ImGui::TableNextColumn();
62 ImGui::TableNextColumn();
72 ImGui::TextDisabled(
"Select a color to edit");
94 ImGui::BeginDisabled(!has_changes);
101 ImGui::EndDisabled();
106 ImGui::BeginDisabled(!has_changes);
110 ImGui::EndDisabled();
116 size_t modified_count = 0;
119 for (
int p = 0; p < group->size(); p++) {
125 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
"%s %zu modified",
130 ImGui::Dummy(ImVec2(20, 0));
138 ImGui::EndDisabled();
145 ImGui::EndDisabled();
148 ImGui::Dummy(ImVec2(20, 0));
163 ImGui::OpenPopup(
"BatchOperations");
172 if (!palette_group)
return;
174 int num_palettes = palette_group->size();
176 ImGui::Text(
"Palette:");
180 if (ImGui::BeginCombo(
"##PaletteSelect",
182 for (
int i = 0; i < num_palettes; i++) {
186 std::string label = absl::StrFormat(
"Palette %d", i);
191 if (ImGui::Selectable(label.c_str(), is_selected)) {
196 ImGui::SetItemDefaultFocus();
208 ImGui::EndDisabled();
215 if (!palette)
return;
224 if (ImGui::ColorPicker4(
"##picker", &col.x,
225 ImGuiColorEditFlags_NoAlpha |
226 ImGuiColorEditFlags_PickerHueWheel |
227 ImGuiColorEditFlags_DisplayRGB |
228 ImGuiColorEditFlags_DisplayHSV)) {
235 ImGui::Text(
"Current vs Original");
237 ImGui::ColorButton(
"##current", col,
238 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
246 if (ImGui::ColorButton(
"##original", orig_col,
247 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
254 if (ImGui::IsItemHovered()) {
255 ImGui::SetTooltip(
"Click to restore original color");
264 ImGui::EndDisabled();
273 int r =
static_cast<int>(col.x);
274 int g =
static_cast<int>(col.y);
275 int b =
static_cast<int>(col.z);
278 ImGui::Text(
"RGB (0-255): (%d, %d, %d)", r, g, b);
279 if (ImGui::IsItemClicked()) {
280 ImGui::SetClipboardText(absl::StrFormat(
"(%d, %d, %d)", r, g, b).c_str());
286 if (ImGui::IsItemClicked()) {
293 ImGui::Text(
"Hex: #%02X%02X%02X", r, g, b);
294 if (ImGui::IsItemClicked()) {
295 ImGui::SetClipboardText(absl::StrFormat(
"#%02X%02X%02X", r, g, b).c_str());
299 ImGui::TextDisabled(
"Click any value to copy");
311 ImGui::Text(
"Palette ID: %d", pal_meta.palette_id);
314 if (!pal_meta.name.empty()) {
315 ImGui::Text(
"Name: %s", pal_meta.name.c_str());
319 if (!pal_meta.description.empty()) {
320 ImGui::TextWrapped(
"%s", pal_meta.description.c_str());
326 ImGui::Text(
"Dimensions: %d colors (%dx%d)",
327 metadata.colors_per_palette,
328 metadata.colors_per_row,
329 (metadata.colors_per_palette + metadata.colors_per_row - 1) / metadata.colors_per_row);
331 ImGui::Text(
"Color Depth: %d BPP (4-bit SNES)", 4);
332 ImGui::TextDisabled(
"(16 colors per palette possible)");
337 ImGui::Text(
"ROM Address: $%06X", pal_meta.rom_address);
338 if (ImGui::IsItemClicked()) {
339 ImGui::SetClipboardText(absl::StrFormat(
"$%06X", pal_meta.rom_address).c_str());
341 if (ImGui::IsItemHovered()) {
342 ImGui::SetTooltip(
"Click to copy address");
346 if (pal_meta.vram_address > 0) {
347 ImGui::Text(
"VRAM Address: $%04X", pal_meta.vram_address);
348 if (ImGui::IsItemClicked()) {
349 ImGui::SetClipboardText(absl::StrFormat(
"$%04X", pal_meta.vram_address).c_str());
351 if (ImGui::IsItemHovered()) {
352 ImGui::SetTooltip(
"Click to copy VRAM address");
357 if (!pal_meta.usage_notes.empty()) {
359 ImGui::TextDisabled(
"Usage Notes:");
360 ImGui::TextWrapped(
"%s", pal_meta.usage_notes.c_str());
365 if (ImGui::BeginPopup(
"BatchOperations")) {
368 if (
ThemedButton(
"Copy Current Palette", ImVec2(-1, 0))) {
370 ImGui::CloseCurrentPopup();
373 if (
ThemedButton(
"Paste to Current Palette", ImVec2(-1, 0))) {
375 ImGui::CloseCurrentPopup();
380 if (
ThemedButton(
"Reset All Palettes", ImVec2(-1, 0))) {
382 ImGui::CloseCurrentPopup();
395 color_index, new_color);
457 int color_index)
const {
482 if (!palette_group || index < 0 || index >= palette_group->size()) {
485 return palette_group->mutable_palette(index);
489 int color_index)
const {
512 return absl::UnimplementedError(
"Import from JSON not yet implemented");
524 for (
size_t i = 0; i < palette.size(); i++) {
525 result += absl::StrFormat(
"$%04X", palette[i].snes());
526 if (i < palette.size() - 1) {
531 ImGui::SetClipboardText(result.c_str());
537 return absl::UnimplementedError(
"Import from clipboard not yet implemented");
560 for (
int i = 0; i < 20; i++) {
563 pal.
name = absl::StrFormat(
"Light World %d", i);
564 pal.
description =
"Used for Light World overworld graphics";
567 pal.
usage_notes =
"Modifying these colors affects Light World appearance";
572 for (
int i = 20; i < 40; i++) {
575 pal.
name = absl::StrFormat(
"Dark World %d", i - 20);
576 pal.
description =
"Used for Dark World overworld graphics";
579 pal.
usage_notes =
"Modifying these colors affects Dark World appearance";
584 for (
int i = 40; i < 60; i++) {
587 pal.
name = absl::StrFormat(
"Special %d", i - 40);
588 pal.
description =
"Used for Special World and triforce room";
591 pal.
usage_notes =
"Modifying these colors affects Special World areas";
604 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"ow_main");
609 if (!palette)
return;
611 const float button_size = 32.0f;
614 for (
int i = 0; i < palette->size(); i++) {
621 (*palette)[i], is_selected, is_modified,
622 ImVec2(button_size, button_size))) {
630 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
652 const char* anim_names[] = {
"Water",
"Lava",
"Poison Water",
"Ice"};
653 for (
int i = 0; i < 4; i++) {
656 pal.
name = anim_names[i];
657 pal.
description = absl::StrFormat(
"%s animated palette cycle", anim_names[i]);
660 pal.
usage_notes =
"These palettes cycle through multiple frames for animation";
672 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"ow_animated");
677 if (!palette)
return;
679 const float button_size = 32.0f;
682 for (
int i = 0; i < palette->size(); i++) {
689 (*palette)[i], is_selected, is_modified,
690 ImVec2(button_size, button_size))) {
697 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
719 const char* dungeon_names[] = {
720 "Sewers",
"Hyrule Castle",
"Eastern Palace",
"Desert Palace",
721 "Agahnim's Tower",
"Swamp Palace",
"Palace of Darkness",
"Misery Mire",
722 "Skull Woods",
"Ice Palace",
"Tower of Hera",
"Thieves' Town",
723 "Turtle Rock",
"Ganon's Tower",
"Generic 1",
"Generic 2",
724 "Generic 3",
"Generic 4",
"Generic 5",
"Generic 6"
727 for (
int i = 0; i < 20; i++) {
730 pal.
name = dungeon_names[i];
731 pal.
description = absl::StrFormat(
"Dungeon palette %d", i);
746 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"dungeon_main");
751 if (!palette)
return;
753 const float button_size = 28.0f;
756 for (
int i = 0; i < palette->size(); i++) {
763 (*palette)[i], is_selected, is_modified,
764 ImVec2(button_size, button_size))) {
771 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
793 const char* sprite_names[] = {
794 "Global Sprites (Light World)",
795 "Global Sprites (Dark World)"
798 for (
int i = 0; i < 2; i++) {
801 pal.
name = sprite_names[i];
802 pal.
description =
"60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, 32, 48";
805 pal.
usage_notes =
"4 sprite sub-palettes of 15 colors + transparent each. "
806 "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
818 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"global_sprites");
823 if (!palette)
return;
825 const float button_size = 28.0f;
828 for (
int i = 0; i < palette->size(); i++) {
835 bool is_transparent_slot = (i % 16 == 0);
836 if (is_transparent_slot) {
839 (*palette)[i], is_selected, is_modified,
840 ImVec2(button_size, button_size))) {
845 ImVec2 pos = ImGui::GetItemRectMin();
846 ImGui::GetWindowDrawList()->AddText(
847 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
848 IM_COL32(255, 255, 255, 200),
"T");
851 if (ImGui::IsItemHovered()) {
852 ImGui::SetTooltip(
"Transparent color slot for sprite sub-palette %d", i / 16);
856 (*palette)[i], is_selected, is_modified,
857 ImVec2(button_size, button_size))) {
865 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
879 ImGui::TextWrapped(
"This sprite palette is loaded to VRAM address $%04X",
880 pal_meta.vram_address);
881 ImGui::TextDisabled(
"VRAM palettes are used by the SNES PPU for sprite rendering");
900 const char* armor_names[] = {
"Green Mail",
"Blue Mail",
"Red Mail"};
902 for (
int i = 0; i < 3; i++) {
905 pal.
name = armor_names[i];
906 pal.
description = absl::StrFormat(
"Link's %s colors", armor_names[i]);
909 pal.
usage_notes =
"Changes Link's tunic appearance";
921 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"armors");
926 if (!palette)
return;
928 const float button_size = 32.0f;
931 for (
int i = 0; i < palette->size(); i++) {
938 (*palette)[i], is_selected, is_modified,
939 ImVec2(button_size, button_size))) {
946 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
967 for (
int i = 0; i < 12; i++) {
970 pal.
name = absl::StrFormat(
"Sprites Aux1 %02d", i);
971 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
974 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
986 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"sprites_aux1");
991 if (!palette)
return;
993 const float button_size = 32.0f;
996 for (
int i = 0; i < palette->size(); i++) {
1003 (*palette)[i], is_selected, is_modified,
1004 ImVec2(button_size, button_size))) {
1011 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1032 for (
int i = 0; i < 11; i++) {
1035 pal.
name = absl::StrFormat(
"Sprites Aux2 %02d", i);
1036 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
1039 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
1051 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"sprites_aux2");
1056 if (!palette)
return;
1058 const float button_size = 32.0f;
1061 for (
int i = 0; i < palette->size(); i++) {
1069 ImGui::BeginGroup();
1071 (*palette)[i], is_selected, is_modified,
1072 ImVec2(button_size, button_size))) {
1077 ImVec2 pos = ImGui::GetItemRectMin();
1078 ImGui::GetWindowDrawList()->AddText(
1079 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1080 IM_COL32(255, 255, 255, 200),
"T");
1084 (*palette)[i], is_selected, is_modified,
1085 ImVec2(button_size, button_size))) {
1093 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1114 for (
int i = 0; i < 24; i++) {
1117 pal.
name = absl::StrFormat(
"Sprites Aux3 %02d", i);
1118 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
1121 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
1133 return const_cast<Rom*
>(
rom_)->mutable_palette_group()->get_group(
"sprites_aux3");
1138 if (!palette)
return;
1140 const float button_size = 32.0f;
1143 for (
int i = 0; i < palette->size(); i++) {
1151 ImGui::BeginGroup();
1153 (*palette)[i], is_selected, is_modified,
1154 ImVec2(button_size, button_size))) {
1159 ImVec2 pos = ImGui::GetItemRectMin();
1160 ImGui::GetWindowDrawList()->AddText(
1161 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1162 IM_COL32(255, 255, 255, 200),
"T");
1166 (*palette)[i], is_selected, is_modified,
1167 ImVec2(button_size, button_size))) {
1175 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
The Rom class is used to load, save, and modify Rom data.
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
auto mutable_palette_group()
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
DungeonMainPaletteCard(Rom *rom)
static PaletteGroupMetadata InitializeMetadata()
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.
static const PaletteGroupMetadata metadata_
static const PaletteGroupMetadata metadata_
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static PaletteGroupMetadata InitializeMetadata()
EquipmentPaletteCard(Rom *rom)
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.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
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()
OverworldAnimatedPaletteCard(Rom *rom)
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
OverworldMainPaletteCard(Rom *rom)
static const PaletteGroupMetadata metadata_
Base class for palette group editing cards.
void DrawPaletteSelector()
Draw palette selector dropdown.
absl::Status SaveToRom()
Save all modified palettes to ROM.
gfx::SnesPalette * GetMutablePalette(int index)
Get mutable palette by index.
absl::Status ImportFromClipboard()
virtual gfx::PaletteGroup * GetPaletteGroup()=0
Get the palette group for this card.
bool IsColorModified(int palette_index, int color_index) const
void DrawColorPicker()
Draw color picker for selected color.
void SetColor(int palette_index, int color_index, const gfx::SnesColor &new_color)
Set a color value (records change for undo)
void DrawBatchOperationsPopup()
Draw batch operations popup.
gfx::SnesColor editing_color_
virtual void DrawCustomToolbarButtons()
Draw additional toolbar buttons (called after standard buttons)
void DrawToolbar()
Draw standard toolbar with save/discard/undo/redo.
virtual void DrawCustomPanels()
Draw additional panels (called after main content)
virtual void DrawPaletteGrid()=0
Draw the palette grid specific to this palette type.
virtual const PaletteGroupMetadata & GetMetadata() const =0
Get metadata for this palette group.
void ResetPalette(int palette_index)
Reset a specific palette to original ROM values.
gfx::SnesColor GetOriginalColor(int palette_index, int color_index) const
Get original color from ROM (for reset/comparison)
std::string ExportToClipboard() const
absl::Status WriteColorToRom(int palette_index, int color_index, const gfx::SnesColor &color)
Write a single color to ROM.
void ResetColor(int palette_index, int color_index)
Reset a specific color to original ROM value.
void DrawMetadataInfo()
Draw palette metadata info panel.
void DrawColorInfo()
Draw color info panel with RGB/SNES/Hex values.
std::string display_name_
PaletteGroupCard(const std::string &group_name, const std::string &display_name, Rom *rom)
Construct a new Palette Group Card.
bool IsPaletteModified(int palette_index) const
void DiscardChanges()
Discard all unsaved changes.
bool HasUnsavedChanges() const
void Draw()
Draw the card's ImGui UI.
std::string ExportToJson() const
absl::Status ImportFromJson(const std::string &json)
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
SpritePaletteCard(Rom *rom)
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
void DrawCustomPanels() override
Draw additional panels (called after main content)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
const PaletteGroupMetadata & GetMetadata() const override
Get metadata for this palette group.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
SpritesAux1PaletteCard(Rom *rom)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
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.
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
SpritesAux2PaletteCard(Rom *rom)
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
static PaletteGroupMetadata InitializeMetadata()
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
SpritesAux3PaletteCard(Rom *rom)
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static const PaletteGroupMetadata metadata_
absl::Status SetColor(const std::string &group_name, int palette_index, int color_index, const SnesColor &new_color)
Set a color in a palette (records change for undo)
bool IsGroupModified(const std::string &group_name) const
Check if a specific palette group has modifications.
absl::Status SaveGroup(const std::string &group_name)
Save a specific palette group to ROM.
void Undo()
Undo the most recent change.
void ClearHistory()
Clear undo/redo history.
bool CanRedo() const
Check if redo is available.
bool CanUndo() const
Check if undo is available.
absl::Status ResetColor(const std::string &group_name, int palette_index, int color_index)
Reset a single color to its original ROM value.
absl::Status ResetPalette(const std::string &group_name, int palette_index)
Reset an entire palette to original ROM values.
bool IsColorModified(const std::string &group_name, int palette_index, int color_index) const
Check if a specific color is modified.
void DiscardGroup(const std::string &group_name)
Discard changes for a specific group.
static PaletteManager & Get()
Get the singleton instance.
SnesColor GetColor(const std::string &group_name, int palette_index, int color_index) const
Get a color from a palette.
bool IsPaletteModified(const std::string &group_name, int palette_index) const
Check if a specific palette is modified.
void Redo()
Redo the most recently undone change.
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
constexpr uint16_t snes() const
Get SNES 15-bit color.
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
#define ICON_MD_FILE_DOWNLOAD
#define ICON_MD_FILE_UPLOAD
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 ThemedButton(const char *label, const ImVec2 &size)
Theme-aware widget library.
bool DangerButton(const char *label, const ImVec2 &size)
Danger/destructive action button (uses 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.
bool PrimaryButton(const char *label, const ImVec2 &size)
Primary action button (uses accent color)
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
bool ThemedIconButton(const char *icon, const char *tooltip)
Themed button with icon (Material Design Icons)
Main namespace for the application.
Represents a group of palettes.