4#include "absl/status/status.h"
5#include "absl/strings/str_cat.h"
12#include "imgui/imgui.h"
17using ImGui::AcceptDragDropPayload;
18using ImGui::BeginChild;
19using ImGui::BeginDragDropTarget;
20using ImGui::BeginGroup;
21using ImGui::BeginPopup;
22using ImGui::BeginPopupContextItem;
24using ImGui::ColorButton;
25using ImGui::ColorPicker4;
27using ImGui::EndDragDropTarget;
31using ImGui::OpenPopup;
35using ImGui::Selectable;
36using ImGui::Separator;
37using ImGui::SetClipboardText;
43 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
44 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
46constexpr ImGuiColorEditFlags
kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
49 ImGuiColorEditFlags_NoPicker |
50 ImGuiColorEditFlags_NoTooltip;
53 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
54 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
55 ImGuiColorEditFlags_DisplayHex;
61#ifdef IMGUI_USE_STB_SPRINTF
62 int w = stbsp_vsnprintf(buf, (
int)buf_size, fmt, args);
64 int w = vsnprintf(buf, buf_size, fmt, args);
67 if (buf ==
nullptr)
return w;
68 if (w == -1 || w >= (
int)buf_size) w = (int)buf_size - 1;
73static inline float color_saturate(
float f) {
74 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
77#define F32_TO_INT8_SAT(_VAL) \
78 ((int)(color_saturate(_VAL) * 255.0f + \
100 static ImVec4 color = ImVec4(0, 0, 0, 255.f);
101 static ImVec4 current_palette[256] = {};
102 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
103 ImGuiColorEditFlags_NoDragDrop |
104 ImGuiColorEditFlags_NoOptions;
107 static bool init =
false;
108 if (loaded && !init) {
109 for (
int n = 0; n < palette.
size(); n++) {
110 auto color = palette[n];
111 current_palette[n].x = color.rgb().x / 255;
112 current_palette[n].y = color.rgb().y / 255;
113 current_palette[n].z = color.rgb().z / 255;
114 current_palette[n].w = 255;
119 static ImVec4 backup_color;
120 bool open_popup = ColorButton(
"MyColor##3b", color, misc_flags);
121 SameLine(0, GetStyle().ItemInnerSpacing.x);
122 open_popup |= Button(
"Palette");
124 OpenPopup(
"mypicker");
125 backup_color = color;
128 if (BeginPopup(
"mypicker")) {
130 ColorPicker4(
"##picker", (
float*)&color,
131 misc_flags | ImGuiColorEditFlags_NoSidePreview |
132 ImGuiColorEditFlags_NoSmallPreview);
140 if (Button(
"Update Map Palette")) {
145 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
150 "##previous", backup_color,
151 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
153 color = backup_color;
158 for (
int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
160 if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
164 color = ImVec4(current_palette[n].x, current_palette[n].y,
165 current_palette[n].z, color.w);
167 if (BeginDragDropTarget()) {
168 if (
const ImGuiPayload* payload =
169 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
170 memcpy((
float*)¤t_palette[n], payload->Data,
sizeof(
float) * 3);
171 if (
const ImGuiPayload* payload =
172 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
173 memcpy((
float*)¤t_palette[n], payload->Data,
sizeof(
float) * 4);
183 return absl::OkStatus();
192 .card_id =
"palette.control_panel",
193 .display_name =
"Palette Controls",
195 .category =
"Palette",
196 .shortcut_hint =
"Ctrl+Shift+P",
201 card_registry->RegisterCard({
202 .card_id =
"palette.ow_main",
203 .display_name =
"Overworld Main",
205 .category =
"Palette",
206 .shortcut_hint =
"Ctrl+Alt+1",
211 card_registry->RegisterCard({
212 .card_id =
"palette.ow_animated",
213 .display_name =
"Overworld Animated",
215 .category =
"Palette",
216 .shortcut_hint =
"Ctrl+Alt+2",
221 card_registry->RegisterCard({
222 .card_id =
"palette.dungeon_main",
223 .display_name =
"Dungeon Main",
225 .category =
"Palette",
226 .shortcut_hint =
"Ctrl+Alt+3",
231 card_registry->RegisterCard({
232 .card_id =
"palette.sprites",
233 .display_name =
"Global Sprite Palettes",
235 .category =
"Palette",
236 .shortcut_hint =
"Ctrl+Alt+4",
241 card_registry->RegisterCard({
242 .card_id =
"palette.sprites_aux1",
243 .display_name =
"Sprites Aux 1",
245 .category =
"Palette",
246 .shortcut_hint =
"Ctrl+Alt+7",
251 card_registry->RegisterCard({
252 .card_id =
"palette.sprites_aux2",
253 .display_name =
"Sprites Aux 2",
255 .category =
"Palette",
256 .shortcut_hint =
"Ctrl+Alt+8",
261 card_registry->RegisterCard({
262 .card_id =
"palette.sprites_aux3",
263 .display_name =
"Sprites Aux 3",
265 .category =
"Palette",
266 .shortcut_hint =
"Ctrl+Alt+9",
271 card_registry->RegisterCard({
272 .card_id =
"palette.equipment",
273 .display_name =
"Equipment Palettes",
275 .category =
"Palette",
276 .shortcut_hint =
"Ctrl+Alt+5",
281 card_registry->RegisterCard({
282 .card_id =
"palette.quick_access",
283 .display_name =
"Quick Access",
285 .category =
"Palette",
286 .shortcut_hint =
"Ctrl+Alt+Q",
291 card_registry->RegisterCard({
292 .card_id =
"palette.custom",
293 .display_name =
"Custom Palette",
295 .category =
"Palette",
296 .shortcut_hint =
"Ctrl+Alt+C",
308 if (!
rom() || !
rom()->is_loaded()) {
309 return absl::NotFoundError(
"ROM not open, no palettes to display");
315 "Palette Group Name", std::to_string(i),
316 std::string(kPaletteGroupNames[i]));
333 return absl::OkStatus();
341 if (loading_card.
Begin()) {
342 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"Loading palette data...");
343 ImGui::TextWrapped(
"Palette cards will appear once ROM data is loaded.");
346 return absl::OkStatus();
357 ImGui::SetNextWindowPos(ImVec2(10, 100));
358 ImGui::SetNextWindowSize(ImVec2(50, 50));
359 ImGuiWindowFlags icon_flags = ImGuiWindowFlags_NoTitleBar |
360 ImGuiWindowFlags_NoResize |
361 ImGuiWindowFlags_NoScrollbar |
362 ImGuiWindowFlags_NoCollapse |
363 ImGuiWindowFlags_NoDocking;
365 if (ImGui::Begin(
"##PaletteControlIcon",
nullptr, icon_flags)) {
370 if (ImGui::IsItemHovered()) {
371 ImGui::SetTooltip(
"Open Palette Controls");
437 return absl::OkStatus();
441 BeginChild(
"QuickAccessPalettes", ImVec2(0, 0),
true);
443 Text(
"Custom Palette");
450 Text(
"Current Color");
460 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"RGB: %d, %d, %d", cr, cg, cb);
463 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"SNES: $%04X",
467 if (Button(
"Copy to Clipboard")) {
468 SetClipboardText(buf);
475 Text(
"Recently Used Colors");
478 if (i % 8 != 0) SameLine();
479 ImVec4 displayColor =
481 if (ImGui::ColorButton(
"##recent", displayColor)) {
507 if (BeginChild(
"ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
508 ImGuiWindowFlags_HorizontalScrollbar)) {
511 if (i > 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
515 bool open_color_picker = ImGui::ColorButton(
516 absl::StrFormat(
"##customPal%d", i).c_str(), displayColor);
518 if (open_color_picker) {
521 ImGui::OpenPopup(
"CustomPaletteColorEdit");
524 if (BeginPopupContextItem()) {
533 if (Button(
"Delete", ImVec2(-1, 0))) {
539 if (BeginDragDropTarget()) {
540 if (
const ImGuiPayload* payload =
541 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
543 memcpy((
float*)&color, payload->Data,
sizeof(
float) * 3);
555 if (ImGui::Button(
"+")) {
560 if (ImGui::Button(
"Clear")) {
565 if (ImGui::Button(
"Export")) {
566 std::string clipboard;
568 clipboard += absl::StrFormat(
"$%04X,", color.snes());
570 SetClipboardText(clipboard.c_str());
576 if (ImGui::BeginPopup(
"CustomPaletteColorEdit")) {
592 if (!
rom()->is_loaded()) {
593 return absl::NotFoundError(
"ROM not open, no palettes to display");
596 auto palette_group_name = kPaletteGroupNames[category];
599 const auto size = palette_group->
size();
601 for (
int j = 0; j < size; j++) {
603 auto pal_size = palette->
size();
610 false, palette_group_name.data(), std::to_string(j),
614 for (
int n = 0; n < pal_size; n++) {
616 if (n > 0 && n % 8 != 0) SameLine(0.0f, 2.0f);
619 absl::StrCat(kPaletteCategoryNames[category].data(), j,
"_", n);
622 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
627 if (BeginPopupContextItem(popup_id.c_str())) {
639 return absl::OkStatus();
644 auto it = std::find_if(
646 [&color](
const SnesColor& c) { return c.snes() == color.snes(); });
665 auto original_color = palette[n];
670 original_color, palette[n]);
671 palette[n].set_modified(
true);
683 Text(
"RGB: %d, %d, %d", cr, cg, cb);
684 Text(
"SNES: $%04X", palette[n].snes());
688 if (Button(
"Copy as..", ImVec2(-1, 0))) OpenPopup(
"Copy");
689 if (BeginPopup(
"Copy")) {
690 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"(%.3ff, %.3ff, %.3ff)", col[0],
692 if (Selectable(buf)) SetClipboardText(buf);
694 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"(%d,%d,%d)", cr, cg, cb);
695 if (Selectable(buf)) SetClipboardText(buf);
697 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"#%02X%02X%02X", cr, cg, cb);
698 if (Selectable(buf)) SetClipboardText(buf);
701 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"$%04X",
703 if (Selectable(buf)) SetClipboardText(buf);
709 if (Button(
"Add to Custom Palette", ImVec2(-1, 0))) {
714 return absl::OkStatus();
719 if (index >= palette.
size()) {
720 return absl::InvalidArgumentError(
"Index out of bounds");
724 auto color = palette[index];
725 auto currentColor = color.rgb();
726 if (ColorPicker4(
"Color Picker", (
float*)&palette[index])) {
733 return absl::OkStatus();
739 if (index >= palette.
size() || index >= originalPalette.
size()) {
740 return absl::InvalidArgumentError(
"Index out of bounds");
742 auto color = originalPalette[index];
743 auto originalColor = color.rgb();
745 return absl::OkStatus();
758 ImGui::SetNextWindowSize(ImVec2(320, 420), ImGuiCond_FirstUseEver);
759 ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
761 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
770 ImGui::Text(
"Palette Groups:");
771 if (ImGui::BeginTable(
"##PaletteToggles", 2,
772 ImGuiTableFlags_SizingStretchSame)) {
773 ImGui::TableNextRow();
774 ImGui::TableNextColumn();
776 ImGui::TableNextColumn();
779 ImGui::TableNextRow();
780 ImGui::TableNextColumn();
782 ImGui::TableNextColumn();
785 ImGui::TableNextRow();
786 ImGui::TableNextColumn();
788 ImGui::TableNextColumn();
796 ImGui::Text(
"Utilities:");
797 if (ImGui::BeginTable(
"##UtilityToggles", 2,
798 ImGuiTableFlags_SizingStretchSame)) {
799 ImGui::TableNextRow();
800 ImGui::TableNextColumn();
802 ImGui::TableNextColumn();
811 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
"Modified Cards:");
812 bool any_modified =
false;
815 ImGui::BulletText(
"Overworld Main");
819 ImGui::BulletText(
"Overworld Animated");
823 ImGui::BulletText(
"Dungeon Main");
827 ImGui::BulletText(
"Global Sprite Palettes");
831 ImGui::BulletText(
"Sprites Aux 1");
835 ImGui::BulletText(
"Sprites Aux 2");
839 ImGui::BulletText(
"Sprites Aux 3");
843 ImGui::BulletText(
"Equipment Palettes");
848 ImGui::TextDisabled(
"No unsaved changes");
854 ImGui::Text(
"Quick Actions:");
860 ImGui::BeginDisabled(!has_unsaved);
861 if (ImGui::Button(absl::StrFormat(
"Save All (%zu colors)", modified_count).c_str(),
866 ImGui::OpenPopup(
"SaveError");
869 ImGui::EndDisabled();
871 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
873 ImGui::SetTooltip(
"Save all modified colors to ROM");
875 ImGui::SetTooltip(
"No unsaved changes");
879 ImGui::BeginDisabled(!has_unsaved);
880 if (ImGui::Button(
"Discard All Changes", ImVec2(-1, 0))) {
881 ImGui::OpenPopup(
"ConfirmDiscardAll");
883 ImGui::EndDisabled();
885 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
887 ImGui::SetTooltip(
"Discard all unsaved changes");
889 ImGui::SetTooltip(
"No changes to discard");
894 if (ImGui::BeginPopupModal(
"ConfirmDiscardAll",
nullptr,
895 ImGuiWindowFlags_AlwaysAutoResize)) {
896 ImGui::Text(
"Discard all unsaved changes?");
897 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
898 "This will revert %zu modified colors.", modified_count);
901 if (ImGui::Button(
"Discard", ImVec2(120, 0))) {
903 ImGui::CloseCurrentPopup();
906 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
907 ImGui::CloseCurrentPopup();
913 if (ImGui::BeginPopupModal(
"SaveError",
nullptr,
914 ImGuiWindowFlags_AlwaysAutoResize)) {
915 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Failed to save changes");
916 ImGui::Text(
"An error occurred while saving to ROM.");
919 if (ImGui::Button(
"OK", ImVec2(120, 0))) {
920 ImGui::CloseCurrentPopup();
929 ImGui::OpenPopup(
"PaletteCardManager");
932 if (ImGui::BeginPopup(
"PaletteCardManager")) {
933 ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
964 ImGui::Text(
"Current Color");
974 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"RGB: %d, %d, %d", cr, cg, cb);
975 ImGui::Text(
"%s", buf);
977 CustomFormatString(buf, IM_ARRAYSIZE(buf),
"SNES: $%04X",
979 ImGui::Text(
"%s", buf);
981 if (ImGui::Button(
"Copy to Clipboard", ImVec2(-1, 0))) {
982 SetClipboardText(buf);
989 ImGui::Text(
"Recently Used Colors");
991 ImGui::TextDisabled(
"No recently used colors yet");
995 if (i % 8 != 0) SameLine();
996 ImVec4 displayColor =
1003 if (ImGui::IsItemHovered()) {
1021 "Create your own custom color palette for reference. "
1022 "Colors can be added from any palette group or created from scratch.");
1028 ImGui::TextDisabled(
"Your custom palette is empty.");
1029 ImGui::Text(
"Click + to add colors or drag colors from any palette.");
1033 if (i > 0 && i % 16 != 0) SameLine(0.0f, 2.0f);
1037 bool open_color_picker = ImGui::ColorButton(
1038 absl::StrFormat(
"##customPal%d", i).c_str(), displayColor,
1041 if (open_color_picker) {
1044 ImGui::OpenPopup(
"CustomPaletteColorEdit");
1047 if (BeginPopupContextItem()) {
1056 if (ImGui::Button(
"Delete", ImVec2(-1, 0))) {
1058 ImGui::CloseCurrentPopup();
1064 if (BeginDragDropTarget()) {
1065 if (
const ImGuiPayload* payload =
1066 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
1068 memcpy((
float*)&color, payload->Data,
sizeof(
float) * 3);
1073 EndDragDropTarget();
1094 std::string clipboard;
1096 clipboard += absl::StrFormat(
"$%04X,", color.snes());
1098 if (!clipboard.empty()) {
1099 clipboard.pop_back();
1101 SetClipboardText(clipboard.c_str());
1103 if (ImGui::IsItemHovered()) {
1104 ImGui::SetTooltip(
"Copy palette as comma-separated SNES values");
1110 if (ImGui::BeginPopup(
"CustomPaletteColorEdit")) {
1137 if (group_name ==
"ow_main") {
1143 }
else if (group_name ==
"ow_animated") {
1149 }
else if (group_name ==
"dungeon_main") {
1155 }
else if (group_name ==
"global_sprites") {
1161 }
else if (group_name ==
"sprites_aux1") {
1167 }
else if (group_name ==
"sprites_aux2") {
1173 }
else if (group_name ==
"sprites_aux3") {
1179 }
else if (group_name ==
"armors") {
#define F32_TO_INT8_SAT(_VAL)
project::ResourceLabelManager * resource_label()
auto mutable_palette_group()
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
std::unique_ptr< SpritesAux3PaletteCard > sprites_aux3_card_
void Initialize() override
absl::Status DrawPaletteGroup(int category, bool right_side=false)
std::unique_ptr< DungeonMainPaletteCard > dungeon_main_card_
std::unique_ptr< OverworldAnimatedPaletteCard > ow_animated_card_
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
std::vector< gfx::SnesColor > custom_palette_
void DrawCustomPalette()
Draw custom palette editor with enhanced ROM hacking features.
void DrawCustomPaletteCard()
bool show_sprites_aux2_card_
void AddRecentlyUsedColor(const gfx::SnesColor &color)
absl::Status Update() override
bool show_sprites_aux1_card_
bool show_sprites_aux3_card_
bool show_dungeon_main_card_
bool show_custom_palette_
absl::Status HandleColorPopup(gfx::SnesPalette &palette, int i, int j, int n)
gfx::SnesColor current_color_
std::unique_ptr< SpritesAux2PaletteCard > sprites_aux2_card_
void DrawQuickAccessTab()
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
std::unique_ptr< EquipmentPaletteCard > equipment_card_
absl::Status Load() override
void DrawQuickAccessCard()
bool show_ow_animated_card_
std::unique_ptr< SpritePaletteCard > sprite_card_
bool show_equipment_card_
void JumpToPalette(const std::string &group_name, int palette_index)
Jump to a specific palette by group and index.
bool control_panel_minimized_
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
std::unique_ptr< SpritesAux1PaletteCard > sprites_aux1_card_
std::unique_ptr< OverworldMainPaletteCard > ow_main_card_
void RecordChange(const std::string &group_name, size_t palette_index, size_t color_index, const gfx::SnesColor &original_color, const gfx::SnesColor &new_color)
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
void Initialize(Rom *rom)
Initialize the palette manager with ROM data.
void DiscardAllChanges()
Discard ALL unsaved changes.
size_t GetModifiedColorCount() const
Get count of modified colors across all groups.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
RAII timer for automatic timing management.
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).
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void SetPosition(Position pos)
#define ICON_MD_LANDSCAPE
#define ICON_MD_DASHBOARD
#define ICON_MD_CONTENT_COPY
#define ICON_MD_COLOR_LENS
#define RETURN_IF_ERROR(expression)
#define TEXT_WITH_SEPARATOR(text)
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
constexpr ImGuiTableFlags kPaletteTableFlags
constexpr ImGuiColorEditFlags kPalNoAlpha
absl::Status DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Display SNES palette with enhanced ROM hacking features.
constexpr ImGuiColorEditFlags kColorPopupFlags
constexpr ImGuiColorEditFlags kPalButtonFlags
constexpr int kNumPalettes
uint16_t ConvertRgbToSnes(const snes_color &color)
Convert RGB (0-255) to SNES 15-bit color.
std::array< float, 4 > ToFloatArray(const SnesColor &color)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Main namespace for the application.
EditorCardRegistry * card_registry
Represents a group of palettes.
auto mutable_palette(int i)
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)