5#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
25 const std::string& display_name,
Rom* rom,
27 : group_name_(group_name),
28 display_name_(display_name),
30 game_data_(game_data) {
51 if (ImGui::BeginTable(
52 "##PalettePanelLayout", 2,
53 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
54 ImGui::TableSetupColumn(
"Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f);
55 ImGui::TableSetupColumn(
"Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f);
57 ImGui::TableNextRow();
58 ImGui::TableNextColumn();
65 ImGui::TableNextColumn();
75 ImGui::TextDisabled(
"Select a color to edit");
95 ImGui::BeginDisabled(!has_changes);
102 ImGui::EndDisabled();
107 ImGui::BeginDisabled(!has_changes);
111 ImGui::EndDisabled();
117 size_t modified_count = 0;
120 for (
int p = 0; p < group->size(); p++) {
126 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
"%s %zu modified",
131 ImGui::Dummy(ImVec2(20, 0));
139 ImGui::EndDisabled();
146 ImGui::EndDisabled();
149 ImGui::Dummy(ImVec2(20, 0));
164 ImGui::OpenPopup(
"BatchOperations");
176 int num_palettes = palette_group->size();
178 ImGui::Text(
"Palette:");
182 if (ImGui::BeginCombo(
185 for (
int i = 0; i < num_palettes; i++) {
189 std::string label = absl::StrFormat(
"Palette %d", i);
194 if (ImGui::Selectable(label.c_str(), is_selected)) {
199 ImGui::SetItemDefaultFocus();
211 ImGui::EndDisabled();
229 if (ImGui::ColorPicker4(
"##picker", &col.x,
230 ImGuiColorEditFlags_NoAlpha |
231 ImGuiColorEditFlags_PickerHueWheel |
232 ImGuiColorEditFlags_DisplayRGB |
233 ImGuiColorEditFlags_DisplayHSV)) {
240 ImGui::Text(
"Current vs Original");
242 ImGui::ColorButton(
"##current", col,
243 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
251 if (ImGui::ColorButton(
252 "##original", orig_col,
253 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
260 if (ImGui::IsItemHovered()) {
261 ImGui::SetTooltip(
"Click to restore original color");
270 ImGui::EndDisabled();
280 int r =
static_cast<int>(col.x);
281 int g =
static_cast<int>(col.y);
282 int b =
static_cast<int>(col.z);
285 ImGui::Text(
"RGB (0-255): (%d, %d, %d)", r, g, b);
286 if (ImGui::IsItemClicked()) {
287 ImGui::SetClipboardText(absl::StrFormat(
"(%d, %d, %d)", r, g, b).c_str());
293 if (ImGui::IsItemClicked()) {
294 ImGui::SetClipboardText(
301 ImGui::Text(
"Hex: #%02X%02X%02X", r, g, b);
302 if (ImGui::IsItemClicked()) {
303 ImGui::SetClipboardText(
304 absl::StrFormat(
"#%02X%02X%02X", r, g, b).c_str());
308 ImGui::TextDisabled(
"Click any value to copy");
321 ImGui::Text(
"Palette ID: %d", pal_meta.palette_id);
324 if (!pal_meta.name.empty()) {
325 ImGui::Text(
"Name: %s", pal_meta.name.c_str());
329 if (!pal_meta.description.empty()) {
330 ImGui::TextWrapped(
"%s", pal_meta.description.c_str());
336 ImGui::Text(
"Dimensions: %d colors (%dx%d)", metadata.colors_per_palette,
337 metadata.colors_per_row,
338 (metadata.colors_per_palette + metadata.colors_per_row - 1) /
339 metadata.colors_per_row);
341 ImGui::Text(
"Color Depth: %d BPP (4-bit SNES)", 4);
342 ImGui::TextDisabled(
"(16 colors per palette possible)");
347 ImGui::Text(
"ROM Address: $%06X", pal_meta.rom_address);
348 if (ImGui::IsItemClicked()) {
349 ImGui::SetClipboardText(
350 absl::StrFormat(
"$%06X", pal_meta.rom_address).c_str());
352 if (ImGui::IsItemHovered()) {
353 ImGui::SetTooltip(
"Click to copy address");
357 if (pal_meta.vram_address > 0) {
358 ImGui::Text(
"VRAM Address: $%04X", pal_meta.vram_address);
359 if (ImGui::IsItemClicked()) {
360 ImGui::SetClipboardText(
361 absl::StrFormat(
"$%04X", pal_meta.vram_address).c_str());
363 if (ImGui::IsItemHovered()) {
364 ImGui::SetTooltip(
"Click to copy VRAM address");
369 if (!pal_meta.usage_notes.empty()) {
371 ImGui::TextDisabled(
"Usage Notes:");
372 ImGui::TextWrapped(
"%s", pal_meta.usage_notes.c_str());
377 if (ImGui::BeginPopup(
"BatchOperations")) {
380 if (
ThemedButton(
"Copy Current Palette", ImVec2(-1, 0))) {
382 ImGui::CloseCurrentPopup();
385 if (
ThemedButton(
"Paste to Current Palette", ImVec2(-1, 0))) {
387 ImGui::CloseCurrentPopup();
392 if (
ThemedButton(
"Reset All Palettes", ImVec2(-1, 0))) {
394 ImGui::CloseCurrentPopup();
407 color_index, new_color);
469 int color_index)
const {
494 if (!palette_group || index < 0 || index >= palette_group->size()) {
497 return palette_group->mutable_palette(index);
501 int color_index)
const {
526 return absl::UnimplementedError(
"Import from JSON not yet implemented");
538 for (
size_t i = 0; i < palette.size(); i++) {
539 result += absl::StrFormat(
"$%04X", palette[i].snes());
540 if (i < palette.size() - 1) {
545 ImGui::SetClipboardText(result.c_str());
551 return absl::UnimplementedError(
"Import from clipboard not yet implemented");
575 for (
int i = 0; i < 20; i++) {
578 pal.
name = absl::StrFormat(
"Light World %d", i);
579 pal.
description =
"Used for Light World overworld graphics";
582 pal.
usage_notes =
"Modifying these colors affects Light World appearance";
587 for (
int i = 20; i < 40; i++) {
590 pal.
name = absl::StrFormat(
"Dark World %d", i - 20);
591 pal.
description =
"Used for Dark World overworld graphics";
594 pal.
usage_notes =
"Modifying these colors affects Dark World appearance";
599 for (
int i = 40; i < 60; i++) {
602 pal.
name = absl::StrFormat(
"Special %d", i - 40);
603 pal.
description =
"Used for Special World and triforce room";
606 pal.
usage_notes =
"Modifying these colors affects Special World areas";
623 ->palette_groups.get_group(
"ow_main");
631 const float button_size = 32.0f;
634 for (
int i = 0; i < palette->size(); i++) {
641 (*palette)[i], is_selected, is_modified,
642 ImVec2(button_size, button_size))) {
650 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
674 const char* anim_names[] = {
"Water",
"Lava",
"Poison Water",
"Ice"};
675 for (
int i = 0; i < 4; i++) {
678 pal.
name = anim_names[i];
680 absl::StrFormat(
"%s animated palette cycle", anim_names[i]);
684 "These palettes cycle through multiple frames for animation";
702 ->palette_groups.get_group(
"ow_animated");
710 const float button_size = 32.0f;
713 for (
int i = 0; i < palette->size(); i++) {
720 (*palette)[i], is_selected, is_modified,
721 ImVec2(button_size, button_size))) {
728 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
752 const char* dungeon_names[] = {
753 "Sewers",
"Hyrule Castle",
"Eastern Palace",
"Desert Palace",
754 "Agahnim's Tower",
"Swamp Palace",
"Palace of Darkness",
"Misery Mire",
755 "Skull Woods",
"Ice Palace",
"Tower of Hera",
"Thieves' Town",
756 "Turtle Rock",
"Ganon's Tower",
"Generic 1",
"Generic 2",
757 "Generic 3",
"Generic 4",
"Generic 5",
"Generic 6"};
759 for (
int i = 0; i < 20; i++) {
762 pal.
name = dungeon_names[i];
763 pal.
description = absl::StrFormat(
"Dungeon palette %d", i);
783 ->palette_groups.get_group(
"dungeon_main");
791 const float button_size = 28.0f;
794 for (
int i = 0; i < palette->size(); i++) {
801 (*palette)[i], is_selected, is_modified,
802 ImVec2(button_size, button_size))) {
809 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
832 const char* sprite_names[] = {
"Global Sprites (Light World)",
833 "Global Sprites (Dark World)"};
835 for (
int i = 0; i < 2; i++) {
838 pal.
name = sprite_names[i];
840 "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, "
845 "4 sprite sub-palettes of 15 colors + transparent each. "
846 "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
863 ->palette_groups.get_group(
"global_sprites");
871 const float button_size = 28.0f;
874 for (
int i = 0; i < palette->size(); i++) {
882 bool is_transparent_slot = (i % 16 == 0);
883 if (is_transparent_slot) {
886 (*palette)[i], is_selected, is_modified,
887 ImVec2(button_size, button_size))) {
892 ImVec2 pos = ImGui::GetItemRectMin();
893 ImGui::GetWindowDrawList()->AddText(
894 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
895 IM_COL32(255, 255, 255, 200),
"T");
898 if (ImGui::IsItemHovered()) {
899 ImGui::SetTooltip(
"Transparent color slot for sprite sub-palette %d",
904 (*palette)[i], is_selected, is_modified,
905 ImVec2(button_size, button_size))) {
913 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
927 ImGui::TextWrapped(
"This sprite palette is loaded to VRAM address $%04X",
928 pal_meta.vram_address);
930 "VRAM palettes are used by the SNES PPU for sprite rendering");
950 const char* armor_names[] = {
"Green Mail",
"Blue Mail",
"Red Mail"};
952 for (
int i = 0; i < 3; i++) {
955 pal.
name = armor_names[i];
956 pal.
description = absl::StrFormat(
"Link's %s colors", armor_names[i]);
959 pal.
usage_notes =
"Changes Link's tunic appearance";
976 ->palette_groups.get_group(
"armors");
984 const float button_size = 32.0f;
987 for (
int i = 0; i < palette->size(); i++) {
994 (*palette)[i], is_selected, is_modified,
995 ImVec2(button_size, button_size))) {
1002 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1024 for (
int i = 0; i < 12; i++) {
1027 pal.
name = absl::StrFormat(
"Sprites Aux1 %02d", i);
1028 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
1031 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
1048 ->palette_groups.get_group(
"sprites_aux1");
1056 const float button_size = 32.0f;
1059 for (
int i = 0; i < palette->size(); i++) {
1066 (*palette)[i], is_selected, is_modified,
1067 ImVec2(button_size, button_size))) {
1074 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1096 for (
int i = 0; i < 11; i++) {
1099 pal.
name = absl::StrFormat(
"Sprites Aux2 %02d", i);
1100 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
1103 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
1120 ->palette_groups.get_group(
"sprites_aux2");
1128 const float button_size = 32.0f;
1131 for (
int i = 0; i < palette->size(); i++) {
1139 ImGui::BeginGroup();
1141 (*palette)[i], is_selected, is_modified,
1142 ImVec2(button_size, button_size))) {
1147 ImVec2 pos = ImGui::GetItemRectMin();
1148 ImGui::GetWindowDrawList()->AddText(
1149 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1150 IM_COL32(255, 255, 255, 200),
"T");
1154 (*palette)[i], is_selected, is_modified,
1155 ImVec2(button_size, button_size))) {
1163 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
1185 for (
int i = 0; i < 24; i++) {
1188 pal.
name = absl::StrFormat(
"Sprites Aux3 %02d", i);
1189 pal.
description =
"Auxiliary sprite palette (7 colors + transparent)";
1192 pal.
usage_notes =
"Used by specific sprites. Color 0 is transparent.";
1209 ->palette_groups.get_group(
"sprites_aux3");
1217 const float button_size = 32.0f;
1220 for (
int i = 0; i < palette->size(); i++) {
1228 ImGui::BeginGroup();
1230 (*palette)[i], is_selected, is_modified,
1231 ImVec2(button_size, button_size))) {
1236 ImVec2 pos = ImGui::GetItemRectMin();
1237 ImGui::GetWindowDrawList()->AddText(
1238 ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
1239 IM_COL32(255, 255, 255, 200),
"T");
1243 (*palette)[i], is_selected, is_modified,
1244 ImVec2(button_size, button_size))) {
1252 if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
static const PaletteGroupMetadata metadata_
DungeonMainPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
EquipmentPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
OverworldAnimatedPalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static 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)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
Base class for palette group editing cards.
gfx::SnesColor editing_color_
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.
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 DrawMetadataInfo()
Draw palette metadata info panel.
void SetColor(int palette_index, int color_index, const gfx::SnesColor &new_color)
Set a color value (records change for undo)
virtual const PaletteGroupMetadata & GetMetadata() const =0
Get metadata for this palette group.
absl::Status SaveToRom()
Save all modified palettes to ROM.
absl::Status WriteColorToRom(int palette_index, int color_index, const gfx::SnesColor &color)
Write a single color to ROM.
virtual void DrawCustomPanels()
Draw additional panels (called after main content)
void DrawColorPicker()
Draw color picker for selected color.
bool IsPaletteModified(int palette_index) const
PaletteGroupPanel(const std::string &group_name, const std::string &display_name, Rom *rom, zelda3::GameData *game_data=nullptr)
Construct a new Palette Group Panel.
zelda3::GameData * game_data_
bool IsColorModified(int palette_index, int color_index) const
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 void DrawPaletteGrid()=0
Draw the palette grid specific to this palette type.
const PaletteGroupMetadata & GetMetadata() const override
Get metadata for this palette group.
static PaletteGroupMetadata InitializeMetadata()
static const PaletteGroupMetadata metadata_
SpritePalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
void DrawCustomPanels() override
Draw additional panels (called after main content)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
static PaletteGroupMetadata InitializeMetadata()
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
SpritesAux1PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static const PaletteGroupMetadata metadata_
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
static PaletteGroupMetadata InitializeMetadata()
SpritesAux2PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
static PaletteGroupMetadata InitializeMetadata()
int GetColorsPerRow() const override
Get the number of colors per row for grid layout.
SpritesAux3PalettePanel(Rom *rom, zelda3::GameData *game_data=nullptr)
void DrawPaletteGrid() override
Draw the palette grid specific to this palette type.
static const PaletteGroupMetadata metadata_
gfx::PaletteGroup * GetPaletteGroup() override
Get the palette group for this card.
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)
Draw a standard text button with theme colors.
bool DangerButton(const char *label, const ImVec2 &size)
Draw a danger action button (error color).
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled)
Draw a standard icon button with theme-aware colors.
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)
Draw a primary action button (accented color).
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
gfx::PaletteGroupMap palette_groups