#include "app/editor/graphics/palette_controls_panel.h" #include "absl/strings/str_format.h" #include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/gui/core/icons.h" #include "app/gui/core/style.h" #include "imgui/imgui.h" namespace yaze { namespace editor { using gfx::kPaletteGroupAddressesKeys; void PaletteControlsPanel::Initialize() { // Initialize with default palette group state_->palette_group_index = 0; state_->palette_index = 0; state_->sub_palette_index = 0; } void PaletteControlsPanel::Draw(bool* p_open) { // EditorPanel interface - delegate to existing Update() logic if (!rom_ || !rom_->is_loaded()) { ImGui::TextDisabled("Load a ROM to manage palettes"); return; } DrawPresets(); ImGui::Separator(); DrawPaletteGroupSelector(); ImGui::Separator(); DrawPaletteDisplay(); ImGui::Separator(); DrawApplyButtons(); } absl::Status PaletteControlsPanel::Update() { if (!rom_ || !rom_->is_loaded()) { ImGui::TextDisabled("Load a ROM to manage palettes"); return absl::OkStatus(); } DrawPresets(); ImGui::Separator(); DrawPaletteGroupSelector(); ImGui::Separator(); DrawPaletteDisplay(); ImGui::Separator(); DrawApplyButtons(); return absl::OkStatus(); } void PaletteControlsPanel::DrawPresets() { gui::TextWithSeparators("Quick Presets"); if (ImGui::Button(ICON_MD_LANDSCAPE " Overworld")) { state_->palette_group_index = 0; // Dungeon Main (used for overworld too) state_->palette_index = 0; state_->refresh_graphics = true; } HOVER_HINT("Standard overworld palette"); ImGui::SameLine(); if (ImGui::Button(ICON_MD_CASTLE " Dungeon")) { state_->palette_group_index = 0; // Dungeon Main state_->palette_index = 1; state_->refresh_graphics = true; } HOVER_HINT("Standard dungeon palette"); ImGui::SameLine(); if (ImGui::Button(ICON_MD_PERSON " Sprites")) { state_->palette_group_index = 4; // Sprites Aux1 state_->palette_index = 0; state_->refresh_graphics = true; } HOVER_HINT("Sprite/enemy palette"); if (ImGui::Button(ICON_MD_ACCOUNT_BOX " Link")) { state_->palette_group_index = 3; // Sprite Aux3 (Link's palettes) state_->palette_index = 0; state_->refresh_graphics = true; } HOVER_HINT("Link's palette"); ImGui::SameLine(); if (ImGui::Button(ICON_MD_MENU " HUD")) { state_->palette_group_index = 6; // HUD palettes state_->palette_index = 0; state_->refresh_graphics = true; } HOVER_HINT("HUD/menu palette"); } void PaletteControlsPanel::DrawPaletteGroupSelector() { gui::TextWithSeparators("Palette Selection"); // Palette group combo ImGui::SetNextItemWidth(160); if (ImGui::Combo("Group", reinterpret_cast(&state_->palette_group_index), kPaletteGroupAddressesKeys, IM_ARRAYSIZE(kPaletteGroupAddressesKeys))) { state_->refresh_graphics = true; } // Palette index within group ImGui::SetNextItemWidth(100); int palette_idx = static_cast(state_->palette_index); if (ImGui::InputInt("Palette", &palette_idx)) { state_->palette_index = static_cast(std::max(0, palette_idx)); state_->refresh_graphics = true; } HOVER_HINT("Palette index within the group"); // Sub-palette index (for multi-row palettes) ImGui::SetNextItemWidth(100); int sub_idx = static_cast(state_->sub_palette_index); if (ImGui::InputInt("Sub-Palette", &sub_idx)) { state_->sub_palette_index = static_cast(std::max(0, sub_idx)); state_->refresh_graphics = true; } HOVER_HINT("Sub-palette row (0-7 for SNES 128-color palettes)"); } void PaletteControlsPanel::DrawPaletteDisplay() { gui::TextWithSeparators("Current Palette"); // Get the current palette from GameData if (!game_data_) return; auto palette_group_result = game_data_->palette_groups.get_group( kPaletteGroupAddressesKeys[state_->palette_group_index]); if (!palette_group_result) { ImGui::TextDisabled("Invalid palette group"); return; } auto palette_group = *palette_group_result; if (state_->palette_index >= palette_group.size()) { ImGui::TextDisabled("Invalid palette index"); return; } auto palette = palette_group.palette(state_->palette_index); // Display palette colors in rows of 16 int colors_per_row = 16; int total_colors = static_cast(palette.size()); int num_rows = (total_colors + colors_per_row - 1) / colors_per_row; for (int row = 0; row < num_rows; row++) { for (int col = 0; col < colors_per_row; col++) { int idx = row * colors_per_row + col; if (idx >= total_colors) break; if (col > 0) ImGui::SameLine(); auto& color = palette[idx]; ImVec4 im_color(color.rgb().x / 255.0f, color.rgb().y / 255.0f, color.rgb().z / 255.0f, 1.0f); // Highlight current sub-palette row bool in_sub_palette = (row == static_cast(state_->sub_palette_index)); if (in_sub_palette) { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); } std::string id = absl::StrFormat("##PalColor%d", idx); if (ImGui::ColorButton(id.c_str(), im_color, ImGuiColorEditFlags_NoTooltip, ImVec2(18, 18))) { // Clicking a color in a row selects that sub-palette state_->sub_palette_index = static_cast(row); state_->refresh_graphics = true; } if (in_sub_palette) { ImGui::PopStyleColor(); ImGui::PopStyleVar(); } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Index: %d (Row %d, Col %d)", idx, row, col); ImGui::Text("SNES: $%04X", color.snes()); ImGui::Text("RGB: %d, %d, %d", static_cast(color.rgb().x), static_cast(color.rgb().y), static_cast(color.rgb().z)); ImGui::EndTooltip(); } } } // Row selection buttons ImGui::Text("Sub-palette Row:"); for (int i = 0; i < std::min(8, num_rows); i++) { if (i > 0) ImGui::SameLine(); bool selected = (state_->sub_palette_index == static_cast(i)); if (selected) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f)); } if (ImGui::SmallButton(absl::StrFormat("%d", i).c_str())) { state_->sub_palette_index = static_cast(i); state_->refresh_graphics = true; } if (selected) { ImGui::PopStyleColor(); } } } void PaletteControlsPanel::DrawApplyButtons() { gui::TextWithSeparators("Apply Palette"); // Apply to current sheet ImGui::BeginDisabled(state_->open_sheets.empty()); if (ImGui::Button(ICON_MD_BRUSH " Apply to Current Sheet")) { ApplyPaletteToSheet(state_->current_sheet_id); } ImGui::EndDisabled(); HOVER_HINT("Apply palette to the currently selected sheet"); ImGui::SameLine(); // Apply to all sheets if (ImGui::Button(ICON_MD_FORMAT_PAINT " Apply to All Sheets")) { ApplyPaletteToAllSheets(); } HOVER_HINT("Apply palette to all active graphics sheets"); // Apply to selected sheets (multi-select) if (!state_->selected_sheets.empty()) { if (ImGui::Button( absl::StrFormat(ICON_MD_CHECKLIST " Apply to %zu Selected", state_->selected_sheets.size()) .c_str())) { for (uint16_t sheet_id : state_->selected_sheets) { ApplyPaletteToSheet(sheet_id); } } HOVER_HINT("Apply palette to all selected sheets in browser"); } // Refresh button ImGui::Separator(); if (ImGui::Button(ICON_MD_REFRESH " Refresh Graphics")) { state_->refresh_graphics = true; if (!state_->open_sheets.empty()) { ApplyPaletteToSheet(state_->current_sheet_id); } } HOVER_HINT("Force refresh of current sheet graphics"); } void PaletteControlsPanel::ApplyPaletteToSheet(uint16_t sheet_id) { if (!rom_ || !rom_->is_loaded() || !game_data_) return; auto palette_group_result = game_data_->palette_groups.get_group( kPaletteGroupAddressesKeys[state_->palette_group_index]); if (!palette_group_result) return; auto palette_group = *palette_group_result; if (state_->palette_index >= palette_group.size()) return; auto palette = palette_group.palette(state_->palette_index); auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id); if (sheet.is_active() && sheet.surface()) { sheet.SetPaletteWithTransparent(palette, state_->sub_palette_index); gfx::Arena::Get().NotifySheetModified(sheet_id); } } void PaletteControlsPanel::ApplyPaletteToAllSheets() { if (!rom_ || !rom_->is_loaded() || !game_data_) return; auto palette_group_result = game_data_->palette_groups.get_group( kPaletteGroupAddressesKeys[state_->palette_group_index]); if (!palette_group_result) return; auto palette_group = *palette_group_result; if (state_->palette_index >= palette_group.size()) return; auto palette = palette_group.palette(state_->palette_index); for (int i = 0; i < zelda3::kNumGfxSheets; i++) { auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->data()[i]; if (sheet.is_active() && sheet.surface()) { sheet.SetPaletteWithTransparent(palette, state_->sub_palette_index); gfx::Arena::Get().NotifySheetModified(i); } } } } // namespace editor } // namespace yaze