feat(editor): enhance EditorManager with card-based editor functionality

- Introduced methods to determine if an editor is card-based and to retrieve its category.
- Implemented a sidebar toggle shortcut (Ctrl+B) for managing card visibility.
- Added functionality to hide current editor cards when switching editors.
- Updated editor initialization to register global shortcuts for activating specific editors.
- Enhanced the Update method to draw a sidebar for the current category editor.

Benefits:
- Improved user experience by organizing editor cards and providing quick access to editor categories.
- Streamlined editor management, making it easier to switch between different editing contexts.
This commit is contained in:
scawful
2025-10-12 11:57:39 -04:00
parent b7f78008b7
commit 312522d709
20 changed files with 2213 additions and 343 deletions

View File

@@ -236,42 +236,9 @@ absl::Status DungeonEditorV2::Save() {
}
void DungeonEditorV2::DrawToolset() {
static gui::Toolset toolbar;
toolbar.Begin();
if (toolbar.AddAction(ICON_MD_ADD, "Open Room")) {
OnRoomSelected(room_selector_.current_room_id());
}
toolbar.AddSeparator();
if (toolbar.AddToggle(ICON_MD_LIST, &show_room_selector_, "Toggle Room Selector")) {
// Toggled
}
if (toolbar.AddToggle(ICON_MD_GRID_VIEW, &show_room_matrix_, "Toggle Room Matrix")) {
// Toggled
}
if (toolbar.AddToggle(ICON_MD_DOOR_FRONT, &show_entrances_list_, "Toggle Entrances List")) {
// Toggled
}
if (toolbar.AddToggle(ICON_MD_IMAGE, &show_room_graphics_, "Toggle Room Graphics")) {
// Toggled
}
toolbar.AddSeparator();
if (toolbar.AddToggle(ICON_MD_CONSTRUCTION, &show_object_editor_, "Toggle Object Editor")) {
// Toggled
}
if (toolbar.AddToggle(ICON_MD_PALETTE, &show_palette_editor_, "Toggle Palette Editor")) {
// Toggled
}
toolbar.End();
// Draw VSCode-style sidebar using EditorCardManager
// auto& card_manager = gui::EditorCardManager::Get();
// card_manager.DrawSidebar("Dungeon");
}
void DungeonEditorV2::DrawControlPanel() {

View File

@@ -113,6 +113,7 @@ class Editor {
bool* active() { return &active_; }
void set_active(bool active) { active_ = active; }
void toggle_active() { active_ = !active_; }
// ROM loading state helpers (default implementations)
virtual bool IsRomLoaded() const { return false; }

View File

@@ -90,6 +90,59 @@ std::string GetEditorName(EditorType type) {
} // namespace
// Static registry of editors that use the card-based layout system
bool EditorManager::IsCardBasedEditor(EditorType type) {
switch (type) {
case EditorType::kDungeon:
case EditorType::kPalette:
case EditorType::kGraphics:
case EditorType::kScreen:
case EditorType::kSprite:
case EditorType::kMessage:
case EditorType::kMusic:
case EditorType::kEmulator:
return true;
default:
return false;
}
}
std::string EditorManager::GetEditorCategory(EditorType type) {
switch (type) {
case EditorType::kDungeon:
return "Dungeon";
case EditorType::kPalette:
return "Palette";
case EditorType::kGraphics:
return "Graphics";
case EditorType::kOverworld:
return "Overworld";
case EditorType::kSprite:
return "Sprite";
case EditorType::kMessage:
return "Message";
case EditorType::kMusic:
return "Music";
case EditorType::kScreen:
return "Screen";
case EditorType::kEmulator:
return "Emulator";
default:
return "Unknown";
}
}
void EditorManager::HideCurrentEditorCards() {
if (!current_editor_) {
return;
}
auto& card_manager = gui::EditorCardManager::Get();
std::string category = GetEditorCategory(current_editor_->type());
card_manager.HideAllCardsInCategory(category);
printf("[EditorManager] Closed all cards for category: %s\n", category.c_str());
}
EditorManager::EditorManager() : blank_editor_set_(nullptr, &user_settings_) {
std::stringstream ss;
ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
@@ -163,6 +216,12 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file
// Set the popup manager in the context
context_.popup_manager = popup_manager_.get();
// Register global sidebar toggle shortcut (Ctrl+B)
context_.shortcut_manager.RegisterShortcut(
"global.toggle_sidebar",
{ImGuiKey_LeftCtrl, ImGuiKey_B},
[this]() { show_card_sidebar_ = !show_card_sidebar_; });
// Initialize project file editor
project_file_editor_.SetToastManager(&toast_manager_);
@@ -356,7 +415,7 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file
// Handle agent editor separately (doesn't require ROM)
if (type == EditorType::kAgent) {
#ifdef YAZE_WITH_GRPC
agent_editor_.set_active(true);
agent_editor_.toggle_active();
#endif
return;
}
@@ -365,28 +424,28 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file
switch (type) {
case EditorType::kOverworld:
current_editor_set_->overworld_editor_.set_active(true);
current_editor_set_->overworld_editor_.toggle_active();
break;
case EditorType::kDungeon:
current_editor_set_->dungeon_editor_.set_active(true);
current_editor_set_->dungeon_editor_.toggle_active();
break;
case EditorType::kGraphics:
current_editor_set_->graphics_editor_.set_active(true);
current_editor_set_->graphics_editor_.toggle_active();
break;
case EditorType::kSprite:
current_editor_set_->sprite_editor_.set_active(true);
current_editor_set_->sprite_editor_.toggle_active();
break;
case EditorType::kMessage:
current_editor_set_->message_editor_.set_active(true);
current_editor_set_->message_editor_.toggle_active();
break;
case EditorType::kMusic:
current_editor_set_->music_editor_.set_active(true);
current_editor_set_->music_editor_.toggle_active();
break;
case EditorType::kPalette:
current_editor_set_->palette_editor_.set_active(true);
current_editor_set_->palette_editor_.toggle_active();
break;
case EditorType::kScreen:
current_editor_set_->screen_editor_.set_active(true);
current_editor_set_->screen_editor_.toggle_active();
break;
case EditorType::kAssembly:
show_asm_editor_ = true;
@@ -395,7 +454,7 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file
show_emulator_ = true;
break;
case EditorType::kSettings:
current_editor_set_->settings_editor_.set_active(true);
current_editor_set_->settings_editor_.toggle_active();
break;
default:
break;
@@ -481,34 +540,34 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file
// Editor shortcuts (Ctrl+1-9, Ctrl+0)
context_.shortcut_manager.RegisterShortcut(
"Overworld Editor", {ImGuiKey_1, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->overworld_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->overworld_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Dungeon Editor", {ImGuiKey_2, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->dungeon_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->dungeon_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Graphics Editor", {ImGuiKey_3, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->graphics_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->graphics_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Sprite Editor", {ImGuiKey_4, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->sprite_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->sprite_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Message Editor", {ImGuiKey_5, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->message_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->message_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Music Editor", {ImGuiKey_6, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->music_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->music_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Palette Editor", {ImGuiKey_7, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->palette_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->palette_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Screen Editor", {ImGuiKey_8, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->screen_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->screen_editor_.toggle_active(); });
context_.shortcut_manager.RegisterShortcut(
"Assembly Editor", {ImGuiKey_9, ImGuiMod_Ctrl},
[this]() { show_asm_editor_ = true; });
context_.shortcut_manager.RegisterShortcut(
"Settings Editor", {ImGuiKey_0, ImGuiMod_Ctrl},
[this]() { if (current_editor_set_) current_editor_set_->settings_editor_.set_active(true); });
[this]() { if (current_editor_set_) current_editor_set_->settings_editor_.toggle_active(); });
// Editor Selection Dialog shortcut
context_.shortcut_manager.RegisterShortcut(
@@ -782,8 +841,7 @@ absl::Status EditorManager::Update() {
}
// CARD-BASED EDITORS: Don't wrap in Begin/End, they manage own windows
bool is_card_based_editor = (editor->type() == EditorType::kDungeon);
// TODO: Add EditorType::kGraphics, EditorType::kPalette when converted
bool is_card_based_editor = IsCardBasedEditor(editor->type());
if (is_card_based_editor) {
// Card-based editors create their own top-level windows
@@ -854,6 +912,13 @@ absl::Status EditorManager::Update() {
}
}
// Draw sidebar for current category editor
if (current_editor_) {
std::string category = GetEditorCategory(current_editor_->type());
auto& card_manager = gui::EditorCardManager::Get();
card_manager.DrawSidebar(category);
}
if (show_performance_dashboard_) {
gfx::PerformanceDashboard::Get().Render();
}
@@ -929,8 +994,8 @@ void EditorManager::DrawContextSensitiveCardControl() {
category = "Message";
break;
case EditorType::kPalette:
// Palette editor doesn't use cards (uses internal tabs)
return;
category = "Palette";
break;
case EditorType::kAssembly:
// Assembly editor uses dynamic file tabs
return;

View File

@@ -168,6 +168,15 @@ class EditorManager {
void JumpToOverworldMap(int map_id);
void SwitchToEditor(EditorType editor_type);
// Card-based editor registry
static bool IsCardBasedEditor(EditorType type);
static std::string GetEditorCategory(EditorType type);
bool IsSidebarVisible() const { return show_card_sidebar_; }
void SetSidebarVisible(bool visible) { show_card_sidebar_ = visible; }
// Clean up cards when switching editors
void HideCurrentEditorCards();
// Session management
void CreateNewSession();
void DuplicateCurrentSession();
@@ -247,6 +256,7 @@ class EditorManager {
bool show_welcome_screen_ = false;
bool welcome_screen_manually_closed_ = false;
bool show_card_browser_ = false;
bool show_card_sidebar_ = true; // VSCode-style sidebar for editor cards (toggle with Ctrl+B)
size_t session_to_rename_ = 0;
char session_rename_buffer_[256] = {};

View File

@@ -5,6 +5,9 @@
#include "app/gfx/performance/performance_profiler.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/color.h"
#include "app/gui/editor_card_manager.h"
#include "app/gui/editor_layout.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -16,7 +19,6 @@ using ImGui::BeginDragDropTarget;
using ImGui::BeginGroup;
using ImGui::BeginPopup;
using ImGui::BeginPopupContextItem;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::ColorButton;
using ImGui::ColorPicker4;
@@ -24,8 +26,6 @@ using ImGui::EndChild;
using ImGui::EndDragDropTarget;
using ImGui::EndGroup;
using ImGui::EndPopup;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::GetStyle;
using ImGui::OpenPopup;
using ImGui::PopID;
@@ -34,10 +34,6 @@ using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Separator;
using ImGui::SetClipboardText;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using namespace gfx;
@@ -187,114 +183,247 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
}
void PaletteEditor::Initialize() {
// Initialize palette cards
if (rom_ && rom_->is_loaded()) {
ow_main_card_ = std::make_unique<OverworldMainPaletteCard>(rom_);
ow_animated_card_ = std::make_unique<OverworldAnimatedPaletteCard>(rom_);
dungeon_main_card_ = std::make_unique<DungeonMainPaletteCard>(rom_);
sprite_card_ = std::make_unique<SpritePaletteCard>(rom_);
equipment_card_ = std::make_unique<EquipmentPaletteCard>(rom_);
}
// Register all cards with EditorCardManager (done once during initialization)
auto& card_manager = gui::EditorCardManager::Get();
card_manager.RegisterCard({
.card_id = "palette.control_panel",
.display_name = "Palette Controls",
.icon = ICON_MD_PALETTE,
.category = "Palette",
.shortcut_hint = "Ctrl+Shift+P",
.visibility_flag = &show_control_panel_,
.priority = 10
});
card_manager.RegisterCard({
.card_id = "palette.ow_main",
.display_name = "Overworld Main",
.icon = ICON_MD_LANDSCAPE,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+1",
.visibility_flag = &show_ow_main_card_,
.priority = 20
});
card_manager.RegisterCard({
.card_id = "palette.ow_animated",
.display_name = "Overworld Animated",
.icon = ICON_MD_WATER,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+2",
.visibility_flag = &show_ow_animated_card_,
.priority = 30
});
card_manager.RegisterCard({
.card_id = "palette.dungeon_main",
.display_name = "Dungeon Main",
.icon = ICON_MD_CASTLE,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+3",
.visibility_flag = &show_dungeon_main_card_,
.priority = 40
});
card_manager.RegisterCard({
.card_id = "palette.sprites",
.display_name = "Global Sprite Palettes",
.icon = ICON_MD_PETS,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+4",
.visibility_flag = &show_sprite_card_,
.priority = 50
});
card_manager.RegisterCard({
.card_id = "palette.sprites_aux1",
.display_name = "Sprites Aux 1",
.icon = ICON_MD_FILTER_1,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+7",
.visibility_flag = &show_sprites_aux1_card_,
.priority = 51
});
card_manager.RegisterCard({
.card_id = "palette.sprites_aux2",
.display_name = "Sprites Aux 2",
.icon = ICON_MD_FILTER_2,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+8",
.visibility_flag = &show_sprites_aux2_card_,
.priority = 52
});
card_manager.RegisterCard({
.card_id = "palette.sprites_aux3",
.display_name = "Sprites Aux 3",
.icon = ICON_MD_FILTER_3,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+9",
.visibility_flag = &show_sprites_aux3_card_,
.priority = 53
});
card_manager.RegisterCard({
.card_id = "palette.equipment",
.display_name = "Equipment Palettes",
.icon = ICON_MD_SHIELD,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+5",
.visibility_flag = &show_equipment_card_,
.priority = 60
});
card_manager.RegisterCard({
.card_id = "palette.quick_access",
.display_name = "Quick Access",
.icon = ICON_MD_COLOR_LENS,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+Q",
.visibility_flag = &show_quick_access_,
.priority = 70
});
card_manager.RegisterCard({
.card_id = "palette.custom",
.display_name = "Custom Palette",
.icon = ICON_MD_BRUSH,
.category = "Palette",
.shortcut_hint = "Ctrl+Alt+C",
.visibility_flag = &show_custom_palette_,
.priority = 80
});
}
absl::Status PaletteEditor::Load() {
gfx::ScopedTimer timer("PaletteEditor::Load");
if (rom()->is_loaded()) {
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
rom()->resource_label()->CreateOrGetLabel(
"Palette Group Name", std::to_string(i),
std::string(kPaletteGroupNames[i]));
}
} else {
if (!rom() || !rom()->is_loaded()) {
return absl::NotFoundError("ROM not open, no palettes to display");
}
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
rom()->resource_label()->CreateOrGetLabel(
"Palette Group Name", std::to_string(i),
std::string(kPaletteGroupNames[i]));
}
// Initialize palette card instances NOW (after ROM is loaded)
ow_main_card_ = std::make_unique<OverworldMainPaletteCard>(rom_);
ow_animated_card_ = std::make_unique<OverworldAnimatedPaletteCard>(rom_);
dungeon_main_card_ = std::make_unique<DungeonMainPaletteCard>(rom_);
sprite_card_ = std::make_unique<SpritePaletteCard>(rom_);
sprites_aux1_card_ = std::make_unique<SpritesAux1PaletteCard>(rom_);
sprites_aux2_card_ = std::make_unique<SpritesAux2PaletteCard>(rom_);
sprites_aux3_card_ = std::make_unique<SpritesAux3PaletteCard>(rom_);
equipment_card_ = std::make_unique<EquipmentPaletteCard>(rom_);
return absl::OkStatus();
}
absl::Status PaletteEditor::Update() {
static bool use_legacy_view = false;
// Toolbar with view selector
if (ImGui::Button(use_legacy_view ? "Switch to Card View" : "Switch to Legacy View")) {
use_legacy_view = !use_legacy_view;
if (!rom_ || !rom_->is_loaded()) {
// Create a minimal loading card
gui::EditorCard loading_card("Palette Editor Loading", ICON_MD_PALETTE);
loading_card.SetDefaultSize(400, 200);
if (loading_card.Begin()) {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading palette data...");
ImGui::TextWrapped("Palette cards will appear once ROM data is loaded.");
}
loading_card.End();
return absl::OkStatus();
}
ImGui::SameLine();
ImGui::TextDisabled("|");
ImGui::SameLine();
// CARD-BASED EDITOR: All windows are independent top-level cards
// No parent wrapper - this allows closing control panel without affecting palettes
if (use_legacy_view) {
// Original table-based view
static int current_palette_group = 0;
if (BeginTable("paletteGroupsTable", 3, kPaletteTableFlags)) {
TableSetupColumn("Categories", ImGuiTableColumnFlags_WidthFixed, 200);
TableSetupColumn("Palette Editor", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Quick Access", ImGuiTableColumnFlags_WidthStretch);
TableHeadersRow();
// Optional control panel (can be hidden/minimized)
if (show_control_panel_) {
DrawControlPanel();
} else if (control_panel_minimized_) {
// Draw floating icon button to reopen
ImGui::SetNextWindowPos(ImVec2(10, 100));
ImGui::SetNextWindowSize(ImVec2(50, 50));
ImGuiWindowFlags icon_flags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoDocking;
TableNextRow();
TableNextColumn();
static int selected_category = 0;
BeginChild("CategoryList", ImVec2(0, GetContentRegionAvail().y), true);
for (int i = 0; i < kNumPalettes; i++) {
const bool is_selected = (selected_category == i);
if (Selectable(std::string(kPaletteCategoryNames[i]).c_str(),
is_selected)) {
selected_category = i;
}
if (ImGui::Begin("##PaletteControlIcon", nullptr, icon_flags)) {
if (ImGui::Button(ICON_MD_PALETTE, ImVec2(40, 40))) {
show_control_panel_ = true;
control_panel_minimized_ = false;
}
EndChild();
TableNextColumn();
BeginChild("PaletteEditor", ImVec2(0, 0), true);
Text("%s", std::string(kPaletteCategoryNames[selected_category]).c_str());
Separator();
if (rom()->is_loaded()) {
status_ = DrawPaletteGroup(selected_category, true);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Open Palette Controls");
}
EndChild();
TableNextColumn();
DrawQuickAccessTab();
EndTable();
}
} else {
// New card-based view with quick access sidebar
if (BeginTable("paletteCardsTable", 2, kPaletteTableFlags)) {
TableSetupColumn("Palette Cards", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Quick Access", ImGuiTableColumnFlags_WidthFixed, 300);
TableHeadersRow();
TableNextRow();
TableNextColumn();
BeginChild("PaletteCardsView", ImVec2(0, 0), true);
DrawPaletteCards();
EndChild();
TableNextColumn();
DrawQuickAccessTab();
EndTable();
}
ImGui::End();
}
// Draw palette card windows (dockable/floating)
if (ow_main_card_) ow_main_card_->Draw();
if (ow_animated_card_) ow_animated_card_->Draw();
if (dungeon_main_card_) dungeon_main_card_->Draw();
if (sprite_card_) sprite_card_->Draw();
if (equipment_card_) equipment_card_->Draw();
// Draw all independent palette cards
// Each card has its own show_ flag that needs to be synced with our visibility flags
if (show_ow_main_card_ && ow_main_card_) {
if (!ow_main_card_->IsVisible()) ow_main_card_->Show();
ow_main_card_->Draw();
// Sync back if user closed the card with X button
if (!ow_main_card_->IsVisible()) show_ow_main_card_ = false;
}
if (show_ow_animated_card_ && ow_animated_card_) {
if (!ow_animated_card_->IsVisible()) ow_animated_card_->Show();
ow_animated_card_->Draw();
if (!ow_animated_card_->IsVisible()) show_ow_animated_card_ = false;
}
if (show_dungeon_main_card_ && dungeon_main_card_) {
if (!dungeon_main_card_->IsVisible()) dungeon_main_card_->Show();
dungeon_main_card_->Draw();
if (!dungeon_main_card_->IsVisible()) show_dungeon_main_card_ = false;
}
if (show_sprite_card_ && sprite_card_) {
if (!sprite_card_->IsVisible()) sprite_card_->Show();
sprite_card_->Draw();
if (!sprite_card_->IsVisible()) show_sprite_card_ = false;
}
if (show_sprites_aux1_card_ && sprites_aux1_card_) {
if (!sprites_aux1_card_->IsVisible()) sprites_aux1_card_->Show();
sprites_aux1_card_->Draw();
if (!sprites_aux1_card_->IsVisible()) show_sprites_aux1_card_ = false;
}
if (show_sprites_aux2_card_ && sprites_aux2_card_) {
if (!sprites_aux2_card_->IsVisible()) sprites_aux2_card_->Show();
sprites_aux2_card_->Draw();
if (!sprites_aux2_card_->IsVisible()) show_sprites_aux2_card_ = false;
}
if (show_sprites_aux3_card_ && sprites_aux3_card_) {
if (!sprites_aux3_card_->IsVisible()) sprites_aux3_card_->Show();
sprites_aux3_card_->Draw();
if (!sprites_aux3_card_->IsVisible()) show_sprites_aux3_card_ = false;
}
if (show_equipment_card_ && equipment_card_) {
if (!equipment_card_->IsVisible()) equipment_card_->Show();
equipment_card_->Draw();
if (!equipment_card_->IsVisible()) show_equipment_card_ = false;
}
// Draw quick access and custom palette cards
if (show_quick_access_) {
DrawQuickAccessCard();
}
if (show_custom_palette_) {
DrawCustomPaletteCard();
}
return absl::OkStatus();
}
@@ -450,7 +579,7 @@ void PaletteEditor::DrawCustomPalette() {
}
}
absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
absl::Status PaletteEditor::DrawPaletteGroup(int category, bool /*right_side*/) {
if (!rom()->is_loaded()) {
return absl::NotFoundError("ROM not open, no palettes to display");
}
@@ -607,70 +736,416 @@ absl::Status PaletteEditor::ResetColorToOriginal(
return absl::OkStatus();
}
void PaletteEditor::DrawPaletteCards() {
ImGui::TextWrapped(
"Click a palette card below to open it as a dockable/floating window. "
"Each card provides full editing capabilities with undo/redo, "
"save/discard workflow, and detailed metadata.");
// ============================================================================
// Card-Based UI Methods
// ============================================================================
ImGui::Separator();
void PaletteEditor::DrawToolset() {
// Draw VSCode-style sidebar using EditorCardManager
// auto& card_manager = gui::EditorCardManager::Get();
// card_manager.DrawSidebar("Palette");
}
// Draw card launcher buttons
ImGui::Text("Overworld Palettes");
if (ImGui::Button("Open Overworld Main", ImVec2(-1, 0))) {
if (ow_main_card_) ow_main_card_->Show();
void PaletteEditor::DrawControlPanel() {
ImGui::SetNextWindowSize(ImVec2(320, 420), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
ImGuiWindowFlags flags = ImGuiWindowFlags_None;
if (ImGui::Begin(ICON_MD_PALETTE " Palette Controls", &show_control_panel_, flags)) {
// Toolbar with quick toggles
DrawToolset();
ImGui::Separator();
// Quick toggle checkboxes in a table
ImGui::Text("Palette Groups:");
if (ImGui::BeginTable("##PaletteToggles", 2,
ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("OW Main", &show_ow_main_card_);
ImGui::TableNextColumn();
ImGui::Checkbox("OW Animated", &show_ow_animated_card_);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("Dungeon", &show_dungeon_main_card_);
ImGui::TableNextColumn();
ImGui::Checkbox("Sprites", &show_sprite_card_);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("Equipment", &show_equipment_card_);
ImGui::TableNextColumn();
// Empty cell
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Utilities:");
if (ImGui::BeginTable("##UtilityToggles", 2,
ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Checkbox("Quick Access", &show_quick_access_);
ImGui::TableNextColumn();
ImGui::Checkbox("Custom", &show_custom_palette_);
ImGui::EndTable();
}
ImGui::Separator();
// Modified status indicator
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified Cards:");
bool any_modified = false;
if (ow_main_card_ && ow_main_card_->HasUnsavedChanges()) {
ImGui::BulletText("Overworld Main");
any_modified = true;
}
if (ow_animated_card_ && ow_animated_card_->HasUnsavedChanges()) {
ImGui::BulletText("Overworld Animated");
any_modified = true;
}
if (dungeon_main_card_ && dungeon_main_card_->HasUnsavedChanges()) {
ImGui::BulletText("Dungeon Main");
any_modified = true;
}
if (sprite_card_ && sprite_card_->HasUnsavedChanges()) {
ImGui::BulletText("Global Sprite Palettes");
any_modified = true;
}
if (sprites_aux1_card_ && sprites_aux1_card_->HasUnsavedChanges()) {
ImGui::BulletText("Sprites Aux 1");
any_modified = true;
}
if (sprites_aux2_card_ && sprites_aux2_card_->HasUnsavedChanges()) {
ImGui::BulletText("Sprites Aux 2");
any_modified = true;
}
if (sprites_aux3_card_ && sprites_aux3_card_->HasUnsavedChanges()) {
ImGui::BulletText("Sprites Aux 3");
any_modified = true;
}
if (equipment_card_ && equipment_card_->HasUnsavedChanges()) {
ImGui::BulletText("Equipment Palettes");
any_modified = true;
}
if (!any_modified) {
ImGui::TextDisabled("No unsaved changes");
}
ImGui::Separator();
// Quick actions
ImGui::Text("Quick Actions:");
if (ImGui::Button("Save All Modified", ImVec2(-1, 0))) {
if (ow_main_card_ && ow_main_card_->HasUnsavedChanges()) {
ow_main_card_->SaveToRom();
}
if (ow_animated_card_ && ow_animated_card_->HasUnsavedChanges()) {
ow_animated_card_->SaveToRom();
}
if (dungeon_main_card_ && dungeon_main_card_->HasUnsavedChanges()) {
dungeon_main_card_->SaveToRom();
}
if (sprite_card_ && sprite_card_->HasUnsavedChanges()) {
sprite_card_->SaveToRom();
}
if (sprites_aux1_card_ && sprites_aux1_card_->HasUnsavedChanges()) {
sprites_aux1_card_->SaveToRom();
}
if (sprites_aux2_card_ && sprites_aux2_card_->HasUnsavedChanges()) {
sprites_aux2_card_->SaveToRom();
}
if (sprites_aux3_card_ && sprites_aux3_card_->HasUnsavedChanges()) {
sprites_aux3_card_->SaveToRom();
}
if (equipment_card_ && equipment_card_->HasUnsavedChanges()) {
equipment_card_->SaveToRom();
}
}
if (ImGui::Button("Discard All Changes", ImVec2(-1, 0))) {
if (ow_main_card_) ow_main_card_->DiscardChanges();
if (ow_animated_card_) ow_animated_card_->DiscardChanges();
if (dungeon_main_card_) dungeon_main_card_->DiscardChanges();
if (sprite_card_) sprite_card_->DiscardChanges();
if (sprites_aux1_card_) sprites_aux1_card_->DiscardChanges();
if (sprites_aux2_card_) sprites_aux2_card_->DiscardChanges();
if (sprites_aux3_card_) sprites_aux3_card_->DiscardChanges();
if (equipment_card_) equipment_card_->DiscardChanges();
}
ImGui::Separator();
// Editor Manager Menu Button
if (ImGui::Button(ICON_MD_DASHBOARD " Card Manager", ImVec2(-1, 0))) {
ImGui::OpenPopup("PaletteCardManager");
}
if (ImGui::BeginPopup("PaletteCardManager")) {
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
"%s Palette Card Manager", ICON_MD_PALETTE);
ImGui::Separator();
// Use EditorCardManager to draw the menu
auto& card_manager = gui::EditorCardManager::Get();
card_manager.DrawViewMenuSection("Palette");
ImGui::EndPopup();
}
ImGui::Separator();
// Minimize button
if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) {
control_panel_minimized_ = true;
show_control_panel_ = false;
}
}
if (ImGui::Button("Open Overworld Animated", ImVec2(-1, 0))) {
if (ow_animated_card_) ow_animated_card_->Show();
ImGui::End();
}
void PaletteEditor::DrawQuickAccessCard() {
gui::EditorCard card("Quick Access Palette", ICON_MD_COLOR_LENS,
&show_quick_access_);
card.SetDefaultSize(340, 300);
card.SetPosition(gui::EditorCard::Position::Right);
if (card.Begin(&show_quick_access_)) {
// Current color picker with more options
ImGui::BeginGroup();
ImGui::Text("Current Color");
gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
kColorPopupFlags);
char buf[64];
auto col = current_color_.rgb();
int cr = F32_TO_INT8_SAT(col.x / 255.0f);
int cg = F32_TO_INT8_SAT(col.y / 255.0f);
int cb = F32_TO_INT8_SAT(col.z / 255.0f);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
ImGui::Text("%s", buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
current_color_.snes());
ImGui::Text("%s", buf);
if (ImGui::Button("Copy to Clipboard", ImVec2(-1, 0))) {
SetClipboardText(buf);
}
ImGui::EndGroup();
ImGui::Separator();
// Recently used colors
ImGui::Text("Recently Used Colors");
if (recently_used_colors_.empty()) {
ImGui::TextDisabled("No recently used colors yet");
} else {
for (int i = 0; i < recently_used_colors_.size(); i++) {
PushID(i);
if (i % 8 != 0) SameLine();
ImVec4 displayColor =
gui::ConvertSnesColorToImVec4(recently_used_colors_[i]);
if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
ImVec2(28, 28))) {
// Set as current color
current_color_ = recently_used_colors_[i];
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("SNES: $%04X", recently_used_colors_[i].snes());
}
PopID();
}
}
}
card.End();
}
void PaletteEditor::DrawCustomPaletteCard() {
gui::EditorCard card("Custom Palette", ICON_MD_BRUSH,
&show_custom_palette_);
card.SetDefaultSize(420, 200);
card.SetPosition(gui::EditorCard::Position::Bottom);
if (card.Begin(&show_custom_palette_)) {
ImGui::TextWrapped(
"Create your own custom color palette for reference. "
"Colors can be added from any palette group or created from scratch.");
ImGui::Separator();
// Custom palette color grid
if (custom_palette_.empty()) {
ImGui::TextDisabled("Your custom palette is empty.");
ImGui::Text("Click + to add colors or drag colors from any palette.");
} else {
for (int i = 0; i < custom_palette_.size(); i++) {
PushID(i);
if (i > 0 && i % 16 != 0) SameLine(0.0f, 2.0f);
// Enhanced color button with context menu and drag-drop support
ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
bool open_color_picker = ImGui::ColorButton(
absl::StrFormat("##customPal%d", i).c_str(), displayColor,
kPalButtonFlags, ImVec2(28, 28));
if (open_color_picker) {
current_color_ = custom_palette_[i];
edit_palette_index_ = i;
ImGui::OpenPopup("CustomPaletteColorEdit");
}
if (BeginPopupContextItem()) {
// Edit color directly in the popup
SnesColor original_color = custom_palette_[i];
if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
kColorPopupFlags)) {
// Color was changed, add to recently used
AddRecentlyUsedColor(custom_palette_[i]);
}
if (ImGui::Button("Delete", ImVec2(-1, 0))) {
custom_palette_.erase(custom_palette_.begin() + i);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// Handle drag/drop for palette rearrangement
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
ImVec4 color;
memcpy((float*)&color, payload->Data, sizeof(float) * 3);
color.w = 1.0f; // Set alpha to 1.0
custom_palette_[i] = SnesColor(color);
AddRecentlyUsedColor(custom_palette_[i]);
}
EndDragDropTarget();
}
PopID();
}
}
ImGui::Separator();
// Buttons for palette management
if (ImGui::Button(ICON_MD_ADD " Add Color")) {
custom_palette_.push_back(SnesColor(0x7FFF));
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
custom_palette_.clear();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
std::string clipboard;
for (const auto& color : custom_palette_) {
clipboard += absl::StrFormat("$%04X,", color.snes());
}
if (!clipboard.empty()) {
clipboard.pop_back(); // Remove trailing comma
}
SetClipboardText(clipboard.c_str());
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Copy palette as comma-separated SNES values");
}
}
card.End();
// Color picker popup for custom palette editing
if (ImGui::BeginPopup("CustomPaletteColorEdit")) {
if (edit_palette_index_ >= 0 &&
edit_palette_index_ < custom_palette_.size()) {
SnesColor original_color = custom_palette_[edit_palette_index_];
if (gui::SnesColorEdit4(
"Edit Color", &custom_palette_[edit_palette_index_],
kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
// Color was changed, add to recently used
AddRecentlyUsedColor(custom_palette_[edit_palette_index_]);
}
}
ImGui::EndPopup();
}
}
void PaletteEditor::JumpToPalette(const std::string& group_name, int palette_index) {
// Hide all cards first
show_ow_main_card_ = false;
show_ow_animated_card_ = false;
show_dungeon_main_card_ = false;
show_sprite_card_ = false;
show_sprites_aux1_card_ = false;
show_sprites_aux2_card_ = false;
show_sprites_aux3_card_ = false;
show_equipment_card_ = false;
// Show and focus the appropriate card
if (group_name == "ow_main") {
show_ow_main_card_ = true;
if (ow_main_card_) {
ow_main_card_->Show();
ow_main_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "ow_animated") {
show_ow_animated_card_ = true;
if (ow_animated_card_) {
ow_animated_card_->Show();
ow_animated_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "dungeon_main") {
show_dungeon_main_card_ = true;
if (dungeon_main_card_) {
dungeon_main_card_->Show();
dungeon_main_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "global_sprites") {
show_sprite_card_ = true;
if (sprite_card_) {
sprite_card_->Show();
sprite_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "sprites_aux1") {
show_sprites_aux1_card_ = true;
if (sprites_aux1_card_) {
sprites_aux1_card_->Show();
sprites_aux1_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "sprites_aux2") {
show_sprites_aux2_card_ = true;
if (sprites_aux2_card_) {
sprites_aux2_card_->Show();
sprites_aux2_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "sprites_aux3") {
show_sprites_aux3_card_ = true;
if (sprites_aux3_card_) {
sprites_aux3_card_->Show();
sprites_aux3_card_->SetSelectedPaletteIndex(palette_index);
}
} else if (group_name == "armors") {
show_equipment_card_ = true;
if (equipment_card_) {
equipment_card_->Show();
equipment_card_->SetSelectedPaletteIndex(palette_index);
}
}
ImGui::Separator();
ImGui::Text("Dungeon Palettes");
if (ImGui::Button("Open Dungeon Main", ImVec2(-1, 0))) {
if (dungeon_main_card_) dungeon_main_card_->Show();
}
ImGui::Separator();
ImGui::Text("Sprite & Equipment Palettes");
if (ImGui::Button("Open Sprite Palettes", ImVec2(-1, 0))) {
if (sprite_card_) sprite_card_->Show();
}
if (ImGui::Button("Open Equipment Palettes", ImVec2(-1, 0))) {
if (equipment_card_) equipment_card_->Show();
}
ImGui::Separator();
// Show modified status for each card
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified Cards:");
bool any_modified = false;
if (ow_main_card_ && ow_main_card_->HasUnsavedChanges()) {
ImGui::BulletText("Overworld Main");
any_modified = true;
}
if (ow_animated_card_ && ow_animated_card_->HasUnsavedChanges()) {
ImGui::BulletText("Overworld Animated");
any_modified = true;
}
if (dungeon_main_card_ && dungeon_main_card_->HasUnsavedChanges()) {
ImGui::BulletText("Dungeon Main");
any_modified = true;
}
if (sprite_card_ && sprite_card_->HasUnsavedChanges()) {
ImGui::BulletText("Sprite Palettes");
any_modified = true;
}
if (equipment_card_ && equipment_card_->HasUnsavedChanges()) {
ImGui::BulletText("Equipment Palettes");
any_modified = true;
}
if (!any_modified) {
ImGui::TextDisabled("No unsaved changes");
}
// Show control panel too for easy navigation
show_control_panel_ = true;
}
} // namespace editor

View File

@@ -10,7 +10,6 @@
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/palette/palette_group_card.h"
#include "app/gfx/snes_color.h"
#include "app/gui/editor_card_manager.h"
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
@@ -97,24 +96,32 @@ class PaletteEditor : public Editor {
absl::Status Find() override { return absl::OkStatus(); }
absl::Status Save() override { return absl::UnimplementedError("Save"); }
void DrawQuickAccessTab();
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
/**
* @brief Jump to a specific palette by group and index
* @param group_name The palette group name (e.g., "ow_main", "dungeon_main")
* @param palette_index The palette index within the group
*/
void JumpToPalette(const std::string& group_name, int palette_index);
private:
void DrawToolset();
void DrawControlPanel();
void DrawQuickAccessCard();
void DrawCustomPaletteCard();
// Legacy methods (for backward compatibility if needed)
void DrawQuickAccessTab();
void DrawCustomPalette();
absl::Status DrawPaletteGroup(int category, bool right_side = false);
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette);
void AddRecentlyUsedColor(const gfx::SnesColor& color);
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private:
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
void DrawPaletteCards();
absl::Status status_;
gfx::SnesColor current_color_;
@@ -131,11 +138,28 @@ class PaletteEditor : public Editor {
Rom* rom_;
// Card visibility flags (registered with EditorCardManager)
bool show_control_panel_ = true;
bool show_ow_main_card_ = false;
bool show_ow_animated_card_ = false;
bool show_dungeon_main_card_ = false;
bool show_sprite_card_ = false;
bool show_sprites_aux1_card_ = false;
bool show_sprites_aux2_card_ = false;
bool show_sprites_aux3_card_ = false;
bool show_equipment_card_ = false;
bool show_quick_access_ = false;
bool show_custom_palette_ = false;
bool control_panel_minimized_ = false;
// Palette card instances
std::unique_ptr<OverworldMainPaletteCard> ow_main_card_;
std::unique_ptr<OverworldAnimatedPaletteCard> ow_animated_card_;
std::unique_ptr<DungeonMainPaletteCard> dungeon_main_card_;
std::unique_ptr<SpritePaletteCard> sprite_card_;
std::unique_ptr<SpritesAux1PaletteCard> sprites_aux1_card_;
std::unique_ptr<SpritesAux2PaletteCard> sprites_aux2_card_;
std::unique_ptr<SpritesAux3PaletteCard> sprites_aux3_card_;
std::unique_ptr<EquipmentPaletteCard> equipment_card_;
};

View File

@@ -13,7 +13,7 @@
namespace yaze {
namespace editor {
using namespace gui;
using namespace yaze::gui;
using gui::ThemedButton;
using gui::ThemedIconButton;
using gui::PrimaryButton;
@@ -26,8 +26,18 @@ PaletteGroupCard::PaletteGroupCard(const std::string& group_name,
: group_name_(group_name),
display_name_(display_name),
rom_(rom) {
// Load original palettes from ROM for reset/comparison
if (rom_ && rom_->is_loaded()) {
// Note: We can't call GetPaletteGroup() here because it's a pure virtual function
// and the derived class isn't fully constructed yet. Original palettes will be
// loaded on first Draw() call instead.
}
void PaletteGroupCard::Draw() {
if (!show_ || !rom_ || !rom_->is_loaded()) {
return;
}
// Lazy load original palettes on first draw (after derived class is fully constructed)
if (original_palettes_.empty()) {
auto* palette_group = GetPaletteGroup();
if (palette_group) {
for (size_t i = 0; i < palette_group->size(); i++) {
@@ -35,12 +45,6 @@ PaletteGroupCard::PaletteGroupCard(const std::string& group_name,
}
}
}
}
void PaletteGroupCard::Draw() {
if (!show_ || !rom_ || !rom_->is_loaded()) {
return;
}
// Main card window
if (ImGui::Begin(display_name_.c_str(), &show_)) {
@@ -314,11 +318,27 @@ void PaletteGroupCard::DrawMetadataInfo() {
ImGui::TextWrapped("%s", pal_meta.description.c_str());
}
ImGui::Separator();
// Palette dimensions and color depth
ImGui::Text("Dimensions: %d colors (%dx%d)",
metadata.colors_per_palette,
metadata.colors_per_row,
(metadata.colors_per_palette + metadata.colors_per_row - 1) / metadata.colors_per_row);
ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4);
ImGui::TextDisabled("(16 colors per palette possible)");
ImGui::Separator();
// ROM Address
ImGui::Text("ROM Address: $%06X", pal_meta.rom_address);
if (ImGui::IsItemClicked()) {
ImGui::SetClipboardText(absl::StrFormat("$%06X", pal_meta.rom_address).c_str());
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click to copy address");
}
// VRAM Address (if applicable)
if (pal_meta.vram_address > 0) {
@@ -326,6 +346,9 @@ void PaletteGroupCard::DrawMetadataInfo() {
if (ImGui::IsItemClicked()) {
ImGui::SetClipboardText(absl::StrFormat("$%04X", pal_meta.vram_address).c_str());
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click to copy VRAM address");
}
}
// Usage notes
@@ -580,7 +603,7 @@ std::string PaletteGroupCard::ExportToJson() const {
return "{}";
}
absl::Status PaletteGroupCard::ImportFromJson(const std::string& json) {
absl::Status PaletteGroupCard::ImportFromJson(const std::string& /*json*/) {
// TODO: Implement JSON import
return absl::UnimplementedError("Import from JSON not yet implemented");
}
@@ -690,7 +713,7 @@ void OverworldMainPaletteCard::DrawPaletteGrid() {
ImGui::PushID(i);
if (PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
@@ -758,7 +781,7 @@ void OverworldAnimatedPaletteCard::DrawPaletteGrid() {
ImGui::PushID(i);
if (PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
@@ -832,7 +855,7 @@ void DungeonMainPaletteCard::DrawPaletteGrid() {
ImGui::PushID(i);
if (PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
@@ -853,40 +876,30 @@ const PaletteGroupMetadata SpritePaletteCard::metadata_ =
SpritePaletteCard::InitializeMetadata();
SpritePaletteCard::SpritePaletteCard(Rom* rom)
: PaletteGroupCard("sprites", "Sprite Palettes", rom) {}
: PaletteGroupCard("global_sprites", "Sprite Palettes", rom) {}
PaletteGroupMetadata SpritePaletteCard::InitializeMetadata() {
PaletteGroupMetadata metadata;
metadata.group_name = "sprites";
metadata.display_name = "Sprite Palettes";
metadata.colors_per_palette = 8;
metadata.colors_per_row = 8;
metadata.group_name = "global_sprites";
metadata.display_name = "Global Sprite Palettes";
metadata.colors_per_palette = 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48)
metadata.colors_per_row = 16; // Display in 16-color rows
// Global sprite palettes (0-3)
// 2 palette sets: Light World and Dark World
const char* sprite_names[] = {
"Green Mail Sprite", "Blue Mail Sprite", "Red Mail Sprite", "Gold Armor"
"Global Sprites (Light World)",
"Global Sprites (Dark World)"
};
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 2; i++) {
PaletteMetadata pal;
pal.palette_id = i;
pal.name = sprite_names[i];
pal.description = "Global sprite palette";
pal.rom_address = 0xDD218 + (i * 16);
pal.vram_address = 0x8D00 + (i * 16); // VRAM sprite palette area
pal.usage_notes = "Used by sprites throughout the game";
metadata.palettes.push_back(pal);
}
// Auxiliary sprite palettes (4-5)
for (int i = 4; i < 6; i++) {
PaletteMetadata pal;
pal.palette_id = i;
pal.name = absl::StrFormat("Auxiliary %d", i - 4);
pal.description = "Auxiliary sprite palette";
pal.rom_address = 0xDD218 + (i * 16);
pal.vram_address = 0x8D00 + (i * 16);
pal.usage_notes = "Used by specific sprites";
pal.description = "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, 32, 48";
pal.rom_address = (i == 0) ? 0xDD218 : 0xDD290; // LW or DW address
pal.vram_address = 0; // Loaded dynamically
pal.usage_notes = "4 sprite sub-palettes of 15 colors + transparent each. "
"Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59";
metadata.palettes.push_back(pal);
}
@@ -894,18 +907,18 @@ PaletteGroupMetadata SpritePaletteCard::InitializeMetadata() {
}
gfx::PaletteGroup* SpritePaletteCard::GetPaletteGroup() {
return rom_->mutable_palette_group()->get_group("sprites");
return rom_->mutable_palette_group()->get_group("global_sprites");
}
const gfx::PaletteGroup* SpritePaletteCard::GetPaletteGroup() const {
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites");
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("global_sprites");
}
void SpritePaletteCard::DrawPaletteGrid() {
auto* palette = GetMutablePalette(selected_palette_);
if (!palette) return;
const float button_size = 32.0f;
const float button_size = 28.0f;
const int colors_per_row = GetColorsPerRow();
for (int i = 0; i < palette->size(); i++) {
@@ -914,11 +927,33 @@ void SpritePaletteCard::DrawPaletteGrid() {
ImGui::PushID(i);
if (PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
// Draw transparent color indicator at start of each 16-color row (0, 16, 32, 48, ...)
bool is_transparent_slot = (i % 16 == 0);
if (is_transparent_slot) {
ImGui::BeginGroup();
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
// Draw "T" for transparent
ImVec2 pos = ImGui::GetItemRectMin();
ImGui::GetWindowDrawList()->AddText(
ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
IM_COL32(255, 255, 255, 200), "T");
ImGui::EndGroup();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d", i / 16);
}
} else {
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
}
ImGui::PopID();
@@ -949,11 +984,11 @@ const PaletteGroupMetadata EquipmentPaletteCard::metadata_ =
EquipmentPaletteCard::InitializeMetadata();
EquipmentPaletteCard::EquipmentPaletteCard(Rom* rom)
: PaletteGroupCard("armor", "Equipment Palettes", rom) {}
: PaletteGroupCard("armors", "Equipment Palettes", rom) {}
PaletteGroupMetadata EquipmentPaletteCard::InitializeMetadata() {
PaletteGroupMetadata metadata;
metadata.group_name = "armor";
metadata.group_name = "armors";
metadata.display_name = "Equipment Palettes";
metadata.colors_per_palette = 8;
metadata.colors_per_row = 8;
@@ -975,11 +1010,11 @@ PaletteGroupMetadata EquipmentPaletteCard::InitializeMetadata() {
}
gfx::PaletteGroup* EquipmentPaletteCard::GetPaletteGroup() {
return rom_->mutable_palette_group()->get_group("armor");
return rom_->mutable_palette_group()->get_group("armors");
}
const gfx::PaletteGroup* EquipmentPaletteCard::GetPaletteGroup() const {
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("armor");
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("armors");
}
void EquipmentPaletteCard::DrawPaletteGrid() {
@@ -995,7 +1030,7 @@ void EquipmentPaletteCard::DrawPaletteGrid() {
ImGui::PushID(i);
if (PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
@@ -1010,5 +1045,251 @@ void EquipmentPaletteCard::DrawPaletteGrid() {
}
}
// ========== Sprites Aux1 Palette Card ==========
const PaletteGroupMetadata SpritesAux1PaletteCard::metadata_ =
SpritesAux1PaletteCard::InitializeMetadata();
SpritesAux1PaletteCard::SpritesAux1PaletteCard(Rom* rom)
: PaletteGroupCard("sprites_aux1", "Sprites Aux 1", rom) {}
PaletteGroupMetadata SpritesAux1PaletteCard::InitializeMetadata() {
PaletteGroupMetadata metadata;
metadata.group_name = "sprites_aux1";
metadata.display_name = "Sprites Aux 1";
metadata.colors_per_palette = 8; // 7 colors + transparent
metadata.colors_per_row = 8;
for (int i = 0; i < 12; i++) {
PaletteMetadata pal;
pal.palette_id = i;
pal.name = absl::StrFormat("Sprites Aux1 %02d", i);
pal.description = "Auxiliary sprite palette (7 colors + transparent)";
pal.rom_address = 0xDD39E + (i * 14); // 7 colors * 2 bytes
pal.vram_address = 0;
pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
metadata.palettes.push_back(pal);
}
return metadata;
}
gfx::PaletteGroup* SpritesAux1PaletteCard::GetPaletteGroup() {
return rom_->mutable_palette_group()->get_group("sprites_aux1");
}
const gfx::PaletteGroup* SpritesAux1PaletteCard::GetPaletteGroup() const {
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux1");
}
void SpritesAux1PaletteCard::DrawPaletteGrid() {
auto* palette = GetMutablePalette(selected_palette_);
if (!palette) return;
const float button_size = 32.0f;
const int colors_per_row = GetColorsPerRow();
for (int i = 0; i < palette->size(); i++) {
bool is_selected = (i == selected_color_);
bool is_modified = IsColorModified(selected_palette_, i);
ImGui::PushID(i);
// Draw transparent color indicator for index 0
if (i == 0) {
ImGui::BeginGroup();
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
// Draw "T" for transparent
ImVec2 pos = ImGui::GetItemRectMin();
ImGui::GetWindowDrawList()->AddText(
ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
IM_COL32(255, 255, 255, 200), "T");
ImGui::EndGroup();
} else {
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
}
ImGui::PopID();
if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
ImGui::SameLine();
}
}
}
// ========== Sprites Aux2 Palette Card ==========
const PaletteGroupMetadata SpritesAux2PaletteCard::metadata_ =
SpritesAux2PaletteCard::InitializeMetadata();
SpritesAux2PaletteCard::SpritesAux2PaletteCard(Rom* rom)
: PaletteGroupCard("sprites_aux2", "Sprites Aux 2", rom) {}
PaletteGroupMetadata SpritesAux2PaletteCard::InitializeMetadata() {
PaletteGroupMetadata metadata;
metadata.group_name = "sprites_aux2";
metadata.display_name = "Sprites Aux 2";
metadata.colors_per_palette = 8; // 7 colors + transparent
metadata.colors_per_row = 8;
for (int i = 0; i < 11; i++) {
PaletteMetadata pal;
pal.palette_id = i;
pal.name = absl::StrFormat("Sprites Aux2 %02d", i);
pal.description = "Auxiliary sprite palette (7 colors + transparent)";
pal.rom_address = 0xDD446 + (i * 14); // 7 colors * 2 bytes
pal.vram_address = 0;
pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
metadata.palettes.push_back(pal);
}
return metadata;
}
gfx::PaletteGroup* SpritesAux2PaletteCard::GetPaletteGroup() {
return rom_->mutable_palette_group()->get_group("sprites_aux2");
}
const gfx::PaletteGroup* SpritesAux2PaletteCard::GetPaletteGroup() const {
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux2");
}
void SpritesAux2PaletteCard::DrawPaletteGrid() {
auto* palette = GetMutablePalette(selected_palette_);
if (!palette) return;
const float button_size = 32.0f;
const int colors_per_row = GetColorsPerRow();
for (int i = 0; i < palette->size(); i++) {
bool is_selected = (i == selected_color_);
bool is_modified = IsColorModified(selected_palette_, i);
ImGui::PushID(i);
// Draw transparent color indicator for index 0
if (i == 0) {
ImGui::BeginGroup();
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
// Draw "T" for transparent
ImVec2 pos = ImGui::GetItemRectMin();
ImGui::GetWindowDrawList()->AddText(
ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
IM_COL32(255, 255, 255, 200), "T");
ImGui::EndGroup();
} else {
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
}
ImGui::PopID();
if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
ImGui::SameLine();
}
}
}
// ========== Sprites Aux3 Palette Card ==========
const PaletteGroupMetadata SpritesAux3PaletteCard::metadata_ =
SpritesAux3PaletteCard::InitializeMetadata();
SpritesAux3PaletteCard::SpritesAux3PaletteCard(Rom* rom)
: PaletteGroupCard("sprites_aux3", "Sprites Aux 3", rom) {}
PaletteGroupMetadata SpritesAux3PaletteCard::InitializeMetadata() {
PaletteGroupMetadata metadata;
metadata.group_name = "sprites_aux3";
metadata.display_name = "Sprites Aux 3";
metadata.colors_per_palette = 8; // 7 colors + transparent
metadata.colors_per_row = 8;
for (int i = 0; i < 24; i++) {
PaletteMetadata pal;
pal.palette_id = i;
pal.name = absl::StrFormat("Sprites Aux3 %02d", i);
pal.description = "Auxiliary sprite palette (7 colors + transparent)";
pal.rom_address = 0xDD4E0 + (i * 14); // 7 colors * 2 bytes
pal.vram_address = 0;
pal.usage_notes = "Used by specific sprites. Color 0 is transparent.";
metadata.palettes.push_back(pal);
}
return metadata;
}
gfx::PaletteGroup* SpritesAux3PaletteCard::GetPaletteGroup() {
return rom_->mutable_palette_group()->get_group("sprites_aux3");
}
const gfx::PaletteGroup* SpritesAux3PaletteCard::GetPaletteGroup() const {
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group("sprites_aux3");
}
void SpritesAux3PaletteCard::DrawPaletteGrid() {
auto* palette = GetMutablePalette(selected_palette_);
if (!palette) return;
const float button_size = 32.0f;
const int colors_per_row = GetColorsPerRow();
for (int i = 0; i < palette->size(); i++) {
bool is_selected = (i == selected_color_);
bool is_modified = IsColorModified(selected_palette_, i);
ImGui::PushID(i);
// Draw transparent color indicator for index 0
if (i == 0) {
ImGui::BeginGroup();
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
// Draw "T" for transparent
ImVec2 pos = ImGui::GetItemRectMin();
ImGui::GetWindowDrawList()->AddText(
ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8),
IM_COL32(255, 255, 255, 200), "T");
ImGui::EndGroup();
} else {
if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(),
(*palette)[i], is_selected, is_modified,
ImVec2(button_size, button_size))) {
selected_color_ = i;
editing_color_ = (*palette)[i];
}
}
ImGui::PopID();
if ((i + 1) % colors_per_row != 0 && i + 1 < palette->size()) {
ImGui::SameLine();
}
}
}
} // namespace editor
} // namespace yaze

View File

@@ -351,11 +351,12 @@ class DungeonMainPaletteCard : public PaletteGroupCard {
};
/**
* @brief Sprite palette group card
* @brief Global Sprite palette group card
*
* Manages sprite palettes with VRAM locations
* - Global sprites (palettes 0-3)
* - Auxiliary sprites (palettes 4-5)
* Manages global sprite palettes for Light World and Dark World
* - 2 palettes (LW and DW)
* - Each has 60 colors organized as 4 rows of 16 colors
* - Transparent colors at indices 0, 16, 32, 48
*/
class SpritePaletteCard : public PaletteGroupCard {
public:
@@ -367,7 +368,7 @@ class SpritePaletteCard : public PaletteGroupCard {
const gfx::PaletteGroup* GetPaletteGroup() const override;
const PaletteGroupMetadata& GetMetadata() const override { return metadata_; }
void DrawPaletteGrid() override;
int GetColorsPerRow() const override { return 8; }
int GetColorsPerRow() const override { return 16; }
void DrawCustomPanels() override; // Show VRAM info
private:
@@ -375,6 +376,75 @@ class SpritePaletteCard : public PaletteGroupCard {
static const PaletteGroupMetadata metadata_;
};
/**
* @brief Sprites Aux1 palette group card
*
* Manages auxiliary sprite palettes 1
* - 12 palettes of 8 colors (7 colors + transparent)
*/
class SpritesAux1PaletteCard : public PaletteGroupCard {
public:
explicit SpritesAux1PaletteCard(Rom* rom);
~SpritesAux1PaletteCard() override = default;
protected:
gfx::PaletteGroup* GetPaletteGroup() override;
const gfx::PaletteGroup* GetPaletteGroup() const override;
const PaletteGroupMetadata& GetMetadata() const override { return metadata_; }
void DrawPaletteGrid() override;
int GetColorsPerRow() const override { return 8; }
private:
static PaletteGroupMetadata InitializeMetadata();
static const PaletteGroupMetadata metadata_;
};
/**
* @brief Sprites Aux2 palette group card
*
* Manages auxiliary sprite palettes 2
* - 11 palettes of 8 colors (7 colors + transparent)
*/
class SpritesAux2PaletteCard : public PaletteGroupCard {
public:
explicit SpritesAux2PaletteCard(Rom* rom);
~SpritesAux2PaletteCard() override = default;
protected:
gfx::PaletteGroup* GetPaletteGroup() override;
const gfx::PaletteGroup* GetPaletteGroup() const override;
const PaletteGroupMetadata& GetMetadata() const override { return metadata_; }
void DrawPaletteGrid() override;
int GetColorsPerRow() const override { return 8; }
private:
static PaletteGroupMetadata InitializeMetadata();
static const PaletteGroupMetadata metadata_;
};
/**
* @brief Sprites Aux3 palette group card
*
* Manages auxiliary sprite palettes 3
* - 24 palettes of 8 colors (7 colors + transparent)
*/
class SpritesAux3PaletteCard : public PaletteGroupCard {
public:
explicit SpritesAux3PaletteCard(Rom* rom);
~SpritesAux3PaletteCard() override = default;
protected:
gfx::PaletteGroup* GetPaletteGroup() override;
const gfx::PaletteGroup* GetPaletteGroup() const override;
const PaletteGroupMetadata& GetMetadata() const override { return metadata_; }
void DrawPaletteGrid() override;
int GetColorsPerRow() const override { return 8; }
private:
static PaletteGroupMetadata InitializeMetadata();
static const PaletteGroupMetadata metadata_;
};
/**
* @brief Equipment/Armor palette group card
*

View File

@@ -0,0 +1,147 @@
#include "palette_utility.h"
#include "absl/strings/str_format.h"
#include "app/editor/palette/palette_editor.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/icons.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
namespace palette_utility {
bool DrawPaletteJumpButton(const char* label, const std::string& group_name,
int palette_index, PaletteEditor* editor) {
bool clicked = ImGui::SmallButton(
absl::StrFormat("%s %s", ICON_MD_PALETTE, label).c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Jump to palette editor:\n%s - Palette %d",
group_name.c_str(), palette_index);
}
if (clicked && editor) {
editor->JumpToPalette(group_name, palette_index);
}
return clicked;
}
bool DrawInlineColorEdit(const char* label, gfx::SnesColor* color,
const std::string& group_name, int palette_index,
int color_index, PaletteEditor* editor) {
ImGui::PushID(label);
// Draw color button
ImVec4 col = gui::ConvertSnesColorToImVec4(*color);
bool changed = ImGui::ColorEdit4(label, &col.x,
ImGuiColorEditFlags_NoInputs |
ImGuiColorEditFlags_NoLabel);
if (changed) {
*color = gui::ConvertImVec4ToSnesColor(col);
}
// Draw jump button
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) {
if (editor) {
editor->JumpToPalette(group_name, palette_index);
}
}
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Jump to Palette Editor");
ImGui::TextDisabled("%s - Palette %d, Color %d",
group_name.c_str(), palette_index, color_index);
DrawColorInfoTooltip(*color);
ImGui::EndTooltip();
}
ImGui::PopID();
return changed;
}
bool DrawPaletteIdSelector(const char* label, int* palette_id,
const std::string& group_name,
PaletteEditor* editor) {
ImGui::PushID(label);
// Draw combo box
bool changed = ImGui::InputInt(label, palette_id);
// Clamp to valid range (0-255 typically)
if (*palette_id < 0) *palette_id = 0;
if (*palette_id > 255) *palette_id = 255;
// Draw jump button
ImGui::SameLine();
if (DrawPaletteJumpButton("Jump", group_name, *palette_id, editor)) {
// Button clicked, editor will handle jump
}
ImGui::PopID();
return changed;
}
void DrawColorInfoTooltip(const gfx::SnesColor& color) {
auto rgb = color.rgb();
ImGui::Separator();
ImGui::Text("RGB: (%d, %d, %d)",
static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
static_cast<int>(rgb.z));
ImGui::Text("SNES: $%04X", color.snes());
ImGui::Text("Hex: #%02X%02X%02X",
static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
static_cast<int>(rgb.z));
}
void DrawPalettePreview(const std::string& group_name, int palette_index,
Rom* rom) {
if (!rom || !rom->is_loaded()) {
ImGui::TextDisabled("(ROM not loaded)");
return;
}
auto* group = rom->mutable_palette_group()->get_group(group_name);
if (!group || palette_index >= group->size()) {
ImGui::TextDisabled("(Palette not found)");
return;
}
auto palette = group->palette(palette_index);
// Draw colors in a row
int preview_size = std::min(8, static_cast<int>(palette.size()));
for (int i = 0; i < preview_size; i++) {
if (i > 0) ImGui::SameLine();
ImGui::PushID(i);
ImVec4 col = gui::ConvertSnesColorToImVec4(palette[i]);
ImGui::ColorButton("##preview", col,
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip,
ImVec2(16, 16));
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Color %d", i);
DrawColorInfoTooltip(palette[i]);
ImGui::EndTooltip();
}
ImGui::PopID();
}
}
} // namespace palette_utility
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,78 @@
#ifndef YAZE_APP_EDITOR_PALETTE_UTILITY_H
#define YAZE_APP_EDITOR_PALETTE_UTILITY_H
#include <string>
#include "app/gfx/snes_color.h"
#include "app/gui/color.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
class PaletteEditor; // Forward declaration
/**
* @brief Utility functions for palette operations across editors
*/
namespace palette_utility {
/**
* @brief Draw a palette selector button that opens palette editor
* @param label Button label
* @param group_name The palette group name
* @param palette_index The palette index within the group
* @param editor Pointer to palette editor (can be null)
* @return true if button was clicked
*/
bool DrawPaletteJumpButton(const char* label, const std::string& group_name,
int palette_index, PaletteEditor* editor);
/**
* @brief Draw inline color edit with jump to palette
* @param label Label for the color widget
* @param color Color to edit
* @param group_name Palette group this color belongs to
* @param palette_index Palette index within group
* @param color_index Color index within palette
* @param editor Pointer to palette editor (can be null)
* @return true if color was changed
*/
bool DrawInlineColorEdit(const char* label, gfx::SnesColor* color,
const std::string& group_name, int palette_index,
int color_index, PaletteEditor* editor);
/**
* @brief Draw a compact palette ID selector with preview
* @param label Label for the widget
* @param palette_id Current palette ID (in/out)
* @param group_name The palette group name
* @param editor Pointer to palette editor (can be null)
* @return true if palette ID changed
*/
bool DrawPaletteIdSelector(const char* label, int* palette_id,
const std::string& group_name,
PaletteEditor* editor);
/**
* @brief Draw color info tooltip on hover
* @param color The color to show info for
*/
void DrawColorInfoTooltip(const gfx::SnesColor& color);
/**
* @brief Draw a small palette preview (8 colors in a row)
* @param group_name Palette group name
* @param palette_index Palette index
* @param rom ROM instance to read palette from
*/
void DrawPalettePreview(const std::string& group_name, int palette_index,
class Rom* rom);
} // namespace palette_utility
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_PALETTE_UTILITY_H

View File

@@ -211,6 +211,8 @@ struct PaletteGroup {
PaletteGroup() = default;
PaletteGroup(const std::string& name) : name_(name) {}
// ========== Basic Operations ==========
void AddPalette(SnesPalette pal) { palettes.emplace_back(pal); }
void AddColor(SnesColor color) {
@@ -222,23 +224,74 @@ struct PaletteGroup {
void clear() { palettes.clear(); }
void resize(size_t new_size) { palettes.resize(new_size); }
// ========== Accessors ==========
auto name() const { return name_; }
auto size() const { return palettes.size(); }
bool empty() const { return palettes.empty(); }
// Const access
auto palette(int i) const { return palettes[i]; }
const SnesPalette& palette_ref(int i) const { return palettes[i]; }
// Mutable access
auto mutable_palette(int i) { return &palettes[i]; }
SnesPalette& palette_ref(int i) { return palettes[i]; }
// ========== Color Operations ==========
/**
* @brief Get a specific color from a palette
* @param palette_index The palette index
* @param color_index The color index within the palette
* @return The color, or SnesColor() if indices are invalid
*/
SnesColor GetColor(int palette_index, int color_index) const {
if (palette_index >= 0 && palette_index < palettes.size()) {
const auto& pal = palettes[palette_index];
if (color_index >= 0 && color_index < pal.size()) {
return pal[color_index];
}
}
return SnesColor();
}
/**
* @brief Set a specific color in a palette
* @param palette_index The palette index
* @param color_index The color index within the palette
* @param color The new color value
* @return true if color was set successfully
*/
bool SetColor(int palette_index, int color_index, const SnesColor& color) {
if (palette_index >= 0 && palette_index < palettes.size()) {
auto& pal = palettes[palette_index];
if (color_index >= 0 && color_index < pal.size()) {
pal[color_index] = color;
return true;
}
}
return false;
}
// ========== Operator Overloads ==========
SnesPalette operator[](int i) {
if (i > palettes.size()) {
std::cout << "PaletteGroup: Index out of bounds" << std::endl;
return palettes[0];
if (i >= palettes.size()) {
std::cout << "PaletteGroup: Index " << i << " out of bounds (size: "
<< palettes.size() << ")" << std::endl;
return SnesPalette();
}
return palettes[i];
}
const SnesPalette& operator[](int i) const {
if (i > palettes.size()) {
std::cout << "PaletteGroup: Index out of bounds" << std::endl;
return palettes[0];
if (i >= palettes.size()) {
std::cout << "PaletteGroup: Index " << i << " out of bounds (size: "
<< palettes.size() << ")" << std::endl;
static const SnesPalette empty_palette;
return empty_palette;
}
return palettes[i];
}

View File

@@ -260,5 +260,54 @@ absl::Status DisplayEditablePalette(gfx::SnesPalette& palette,
return absl::OkStatus();
}
IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size,
ImGuiColorEditFlags flags) {
ImVec4 display_color = ConvertSnesColorToImVec4(color);
// Add visual indicators for selection and modification
ImGui::PushID(id);
// Selection border
if (is_selected) {
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
}
bool clicked = ImGui::ColorButton(id, display_color, flags, size);
if (is_selected) {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
// Modification indicator (small dot in corner)
if (is_modified) {
ImVec2 pos = ImGui::GetItemRectMin();
ImVec2 dot_pos = ImVec2(pos.x + size.x - 6, pos.y + 2);
ImGui::GetWindowDrawList()->AddCircleFilled(dot_pos, 3.0f,
IM_COL32(255, 128, 0, 255));
}
// Tooltip with color info
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("SNES: $%04X", color.snes());
auto rgb = color.rgb();
ImGui::Text("RGB: (%d, %d, %d)",
static_cast<int>(rgb.x),
static_cast<int>(rgb.y),
static_cast<int>(rgb.z));
if (is_modified) {
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified");
}
ImGui::EndTooltip();
}
ImGui::PopID();
return clicked;
}
} // namespace gui
} // namespace yaze

View File

@@ -56,6 +56,12 @@ IMGUI_API absl::Status DisplayEditablePalette(gfx::SnesPalette &palette,
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics,
gfx::SnesPalette &palette);
// Palette color button with selection and modification indicators
IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size = ImVec2(28, 28),
ImGuiColorEditFlags flags = 0);
} // namespace gui
} // namespace yaze

View File

@@ -5,8 +5,8 @@
#include "absl/strings/str_format.h"
#include "app/gui/icons.h"
#include "app/gui/theme_manager.h"
#include "imgui/imgui.h"
#include "util/file_util.h"
namespace yaze {
namespace gui {
@@ -34,11 +34,54 @@ void EditorCardManager::RegisterCard(const CardInfo& info) {
info.card_id.c_str(), info.display_name.c_str());
}
void EditorCardManager::RegisterCard(const std::string& card_id,
const std::string& display_name,
const std::string& icon,
const std::string& category,
const std::string& shortcut_hint,
int priority,
std::function<void()> on_show,
std::function<void()> on_hide,
bool visible_by_default) {
if (card_id.empty()) {
printf("[EditorCardManager] Warning: Attempted to register card with empty ID\n");
return;
}
// Check if already registered
if (cards_.find(card_id) != cards_.end()) {
printf("[EditorCardManager] WARNING: Card '%s' already registered, skipping duplicate\n",
card_id.c_str());
return;
}
// Create centralized visibility flag
centralized_visibility_[card_id] = visible_by_default;
// Register card with pointer to centralized flag
CardInfo info;
info.card_id = card_id;
info.display_name = display_name;
info.icon = icon;
info.category = category;
info.shortcut_hint = shortcut_hint;
info.priority = priority;
info.visibility_flag = &centralized_visibility_[card_id];
info.on_show = on_show;
info.on_hide = on_hide;
cards_[card_id] = info;
printf("[EditorCardManager] Registered card with centralized visibility: %s (%s) [default: %s]\n",
card_id.c_str(), display_name.c_str(), visible_by_default ? "visible" : "hidden");
}
void EditorCardManager::UnregisterCard(const std::string& card_id) {
auto it = cards_.find(card_id);
if (it != cards_.end()) {
printf("[EditorCardManager] Unregistered card: %s\n", card_id.c_str());
cards_.erase(it);
// Also remove centralized visibility if it exists
centralized_visibility_.erase(card_id);
}
}
@@ -56,6 +99,9 @@ bool EditorCardManager::ShowCard(const std::string& card_id) {
if (it->second.visibility_flag) {
*it->second.visibility_flag = true;
// Set active category when showing a card
SetActiveCategory(it->second.category);
if (it->second.on_show) {
it->second.on_show();
}
@@ -669,6 +715,107 @@ void EditorCardManager::LoadPresetsFromFile() {
printf("[EditorCardManager] Loading presets from file\n");
}
void EditorCardManager::SetActiveCategory(const std::string& category) {
if (active_category_ != category) {
active_category_ = category;
printf("[EditorCardManager] Active category changed to: %s\n", category.c_str());
}
}
void EditorCardManager::DrawSidebar(const std::string& category) {
// Set this category as active when sidebar is drawn
SetActiveCategory(category);
// Use ThemeManager for consistent theming
const auto& theme = ThemeManager::Get().GetCurrentTheme();
const float sidebar_width = GetSidebarWidth();
// Fixed sidebar window on the left edge of screen
ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetFrameHeight())); // Below menu bar
ImGui::SetNextWindowSize(ImVec2(sidebar_width, -1)); // Full height below menu
ImGuiWindowFlags sidebar_flags =
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoBringToFrontOnFocus;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ConvertColorToImVec4(theme.child_bg));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 8.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 6.0f));
if (ImGui::Begin(absl::StrFormat("##%s_Sidebar", category).c_str(), nullptr, sidebar_flags)) {
// Get cards for this category
auto cards = GetCardsInCategory(category);
// Close All button at top
ImVec4 error_color = ConvertColorToImVec4(theme.error);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(
error_color.x * 0.6f, error_color.y * 0.6f, error_color.z * 0.6f, 0.7f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_color);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(
error_color.x * 1.2f, error_color.y * 1.2f, error_color.z * 1.2f, 1.0f));
if (ImGui::Button(ICON_MD_CLOSE, ImVec2(40.0f, 40.0f))) {
HideAllCardsInCategory(category);
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Close All %s Cards", category.c_str());
}
ImGui::Dummy(ImVec2(0, 4.0f));
// Draw card buttons
ImVec4 accent_color = ConvertColorToImVec4(theme.accent);
ImVec4 button_bg = ConvertColorToImVec4(theme.button);
for (const auto& card : cards) {
ImGui::PushID(card.card_id.c_str());
bool is_active = card.visibility_flag && *card.visibility_flag;
if (is_active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(
accent_color.x, accent_color.y, accent_color.z, 0.5f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(
accent_color.x, accent_color.y, accent_color.z, 0.7f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, accent_color);
} else {
ImGui::PushStyleColor(ImGuiCol_Button, button_bg);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active));
}
if (ImGui::Button(card.icon.c_str(), ImVec2(40.0f, 40.0f))) {
ToggleCard(card.card_id);
SetActiveCategory(category);
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered() || ImGui::IsItemActive()) {
SetActiveCategory(category);
ImGui::SetTooltip("%s\n%s", card.display_name.c_str(),
card.shortcut_hint.empty() ? "" : card.shortcut_hint.c_str());
}
ImGui::PopID();
}
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
}
} // namespace gui
} // namespace yaze

View File

@@ -75,6 +75,18 @@ class EditorCardManager {
// Registration
void RegisterCard(const CardInfo& info);
// Register card with centralized visibility management (preferred method)
void RegisterCard(const std::string& card_id,
const std::string& display_name,
const std::string& icon,
const std::string& category,
const std::string& shortcut_hint = "",
int priority = 50,
std::function<void()> on_show = nullptr,
std::function<void()> on_hide = nullptr,
bool visible_by_default = false);
void UnregisterCard(const std::string& card_id);
void ClearAllCards();
@@ -98,6 +110,15 @@ class EditorCardManager {
void DrawViewMenuSection(const std::string& category);
void DrawViewMenuAll(); // Draw all categories as submenus
// VSCode-style sidebar (replaces Toolset)
void DrawSidebar(const std::string& category); // Icon-only sidebar for category
static constexpr float GetSidebarWidth() { return 48.0f; }
// Active editor tracking (based on most recently interacted card)
void SetActiveCategory(const std::string& category);
std::string GetActiveCategory() const { return active_category_; }
bool IsCategoryActive(const std::string& category) const { return active_category_ == category; }
// Compact inline card control for menu bar
void DrawCompactCardControl(const std::string& category); // Shows only active editor's cards
void DrawInlineCardToggles(const std::string& category); // Minimal inline checkboxes
@@ -135,7 +156,9 @@ class EditorCardManager {
EditorCardManager& operator=(const EditorCardManager&) = delete;
std::unordered_map<std::string, CardInfo> cards_;
std::unordered_map<std::string, bool> centralized_visibility_; // Centralized card visibility flags
std::unordered_map<std::string, WorkspacePreset> presets_;
std::string active_category_; // Currently active editor category (based on last card interaction)
// Helper methods
void SavePresetsToFile();

View File

@@ -254,50 +254,7 @@ void ThemedProgressBar(float fraction, const ImVec2& size, const char* overlay)
// ============================================================================
// Palette Editor Widgets
// ============================================================================
bool PaletteColorButton(const char* label, const yaze::gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size) {
const auto& theme = GetTheme();
int style_count = 0;
// Draw modified indicator with warning border
if (is_modified) {
ImGui::PushStyleColor(ImGuiCol_Border, ConvertColorToImVec4(theme.warning));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
style_count++;
}
// Draw selection border (overrides modified if both)
if (is_selected) {
ImGui::PushStyleColor(ImGuiCol_Border, ConvertColorToImVec4(theme.accent));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 3.0f);
if (is_modified) {
ImGui::PopStyleVar(); // Remove modified border style
ImGui::PopStyleColor(); // Remove modified border color
}
style_count = 1; // Override count
}
// Convert SNES color to ImGui format
ImVec4 col = ConvertSnesColorToImVec4(color);
// Draw color button
bool clicked = ImGui::ColorButton(label, col,
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip,
size);
// Cleanup styles
if (style_count > 0) {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}
return clicked;
}
// NOTE: PaletteColorButton moved to color.cc
void ColorInfoPanel(const yaze::gfx::SnesColor& color,
bool show_snes_format,

View File

@@ -181,18 +181,7 @@ void ThemedProgressBar(float fraction, const ImVec2& size = ImVec2(-1, 0),
// Palette Editor Widgets
// ============================================================================
/**
* @brief Palette color button with modified and selection indicators
* @param label Widget ID
* @param color SNES color to display
* @param is_selected Whether this color is currently selected
* @param is_modified Whether this color has unsaved changes
* @param size Button size (default 24x24)
* @return true if clicked
*/
bool PaletteColorButton(const char* label, const yaze::gfx::SnesColor& color,
bool is_selected, bool is_modified,
const ImVec2& size = ImVec2(24, 24));
// NOTE: PaletteColorButton moved to color.h for consistency with other color utilities
/**
* @brief Display color information with copy-to-clipboard functionality

View File

@@ -0,0 +1,76 @@
#include "zelda3/palette_constants.h"
namespace yaze {
namespace zelda3 {
const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id) {
std::string id_str(group_id);
if (id_str == PaletteGroupName::kOverworldMain) {
return &PaletteMetadata::kOverworldMain;
} else if (id_str == PaletteGroupName::kOverworldAux) {
return &PaletteMetadata::kOverworldAux;
} else if (id_str == PaletteGroupName::kOverworldAnimated) {
return &PaletteMetadata::kOverworldAnimated;
} else if (id_str == PaletteGroupName::kDungeonMain) {
return &PaletteMetadata::kDungeonMain;
} else if (id_str == PaletteGroupName::kGlobalSprites) {
return &PaletteMetadata::kGlobalSprites;
} else if (id_str == PaletteGroupName::kSpritesAux1) {
return &PaletteMetadata::kSpritesAux1;
} else if (id_str == PaletteGroupName::kSpritesAux2) {
return &PaletteMetadata::kSpritesAux2;
} else if (id_str == PaletteGroupName::kSpritesAux3) {
return &PaletteMetadata::kSpritesAux3;
} else if (id_str == PaletteGroupName::kArmor) {
return &PaletteMetadata::kArmor;
} else if (id_str == PaletteGroupName::kSwords) {
return &PaletteMetadata::kSwords;
} else if (id_str == PaletteGroupName::kShields) {
return &PaletteMetadata::kShields;
} else if (id_str == PaletteGroupName::kHud) {
return &PaletteMetadata::kHud;
} else if (id_str == PaletteGroupName::kGrass) {
return &PaletteMetadata::kGrass;
} else if (id_str == PaletteGroupName::k3DObject) {
return &PaletteMetadata::k3DObject;
} else if (id_str == PaletteGroupName::kOverworldMiniMap) {
return &PaletteMetadata::kOverworldMiniMap;
}
return nullptr;
}
std::vector<const PaletteGroupMetadata*> GetAllPaletteGroups() {
return {
// Overworld
&PaletteMetadata::kOverworldMain,
&PaletteMetadata::kOverworldAux,
&PaletteMetadata::kOverworldAnimated,
// Dungeon
&PaletteMetadata::kDungeonMain,
// Sprites
&PaletteMetadata::kGlobalSprites,
&PaletteMetadata::kSpritesAux1,
&PaletteMetadata::kSpritesAux2,
&PaletteMetadata::kSpritesAux3,
// Equipment
&PaletteMetadata::kArmor,
&PaletteMetadata::kSwords,
&PaletteMetadata::kShields,
// Interface
&PaletteMetadata::kHud,
&PaletteMetadata::kOverworldMiniMap,
// Special
&PaletteMetadata::kGrass,
&PaletteMetadata::k3DObject
};
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,310 @@
#ifndef YAZE_ZELDA3_PALETTE_CONSTANTS_H
#define YAZE_ZELDA3_PALETTE_CONSTANTS_H
#include <cstdint>
#include <vector>
namespace yaze {
namespace zelda3 {
// ============================================================================
// Palette Group Names
// ============================================================================
// These constants ensure consistent naming across the entire program
namespace PaletteGroupName {
constexpr const char* kOverworldMain = "ow_main";
constexpr const char* kOverworldAux = "ow_aux";
constexpr const char* kOverworldAnimated = "ow_animated";
constexpr const char* kHud = "hud";
constexpr const char* kGlobalSprites = "global_sprites";
constexpr const char* kArmor = "armor";
constexpr const char* kSwords = "swords";
constexpr const char* kShields = "shields";
constexpr const char* kSpritesAux1 = "sprites_aux1";
constexpr const char* kSpritesAux2 = "sprites_aux2";
constexpr const char* kSpritesAux3 = "sprites_aux3";
constexpr const char* kDungeonMain = "dungeon_main";
constexpr const char* kGrass = "grass";
constexpr const char* k3DObject = "3d_object";
constexpr const char* kOverworldMiniMap = "ow_mini_map";
} // namespace PaletteGroupName
// ============================================================================
// ROM Addresses
// ============================================================================
namespace PaletteAddress {
constexpr uint32_t kOverworldMain = 0xDE6C8;
constexpr uint32_t kOverworldAux = 0xDE86C;
constexpr uint32_t kOverworldAnimated = 0xDE604;
constexpr uint32_t kGlobalSpritesLW = 0xDD218;
constexpr uint32_t kGlobalSpritesDW = 0xDD290;
constexpr uint32_t kArmor = 0xDD308;
constexpr uint32_t kSpritesAux1 = 0xDD39E;
constexpr uint32_t kSpritesAux2 = 0xDD446;
constexpr uint32_t kSpritesAux3 = 0xDD4E0;
constexpr uint32_t kSwords = 0xDD630;
constexpr uint32_t kShields = 0xDD648;
constexpr uint32_t kHud = 0xDD660;
constexpr uint32_t kDungeonMap = 0xDD70A;
constexpr uint32_t kDungeonMain = 0xDD734;
constexpr uint32_t kDungeonMapBg = 0xDE544;
constexpr uint32_t kGrassLW = 0x5FEA9;
constexpr uint32_t kGrassDW = 0x05FEB3;
constexpr uint32_t kGrassSpecial = 0x75640;
constexpr uint32_t kOverworldMiniMap = 0x55B27;
constexpr uint32_t kTriforce = 0x64425;
constexpr uint32_t kCrystal = 0xF4CD3;
} // namespace PaletteAddress
// ============================================================================
// Palette Counts
// ============================================================================
namespace PaletteCount {
constexpr int kHud = 2;
constexpr int kOverworldMain = 60; // 20 LW, 20 DW, 20 Special
constexpr int kOverworldAux = 20;
constexpr int kOverworldAnimated = 14;
constexpr int kGlobalSprites = 6;
constexpr int kArmor = 5;
constexpr int kSwords = 4;
constexpr int kSpritesAux1 = 12;
constexpr int kSpritesAux2 = 11;
constexpr int kSpritesAux3 = 24;
constexpr int kShields = 3;
constexpr int kDungeonMain = 20;
constexpr int kGrass = 3;
constexpr int k3DObject = 2;
constexpr int kOverworldMiniMap = 2;
} // namespace PaletteCount
// ============================================================================
// Palette Metadata
// ============================================================================
struct PaletteGroupMetadata {
const char* group_id; // Unique identifier (e.g., "ow_main")
const char* display_name; // Human-readable name
const char* category; // Category (e.g., "Overworld", "Dungeon")
uint32_t base_address; // ROM address
int palette_count; // Number of palettes
int colors_per_palette; // Colors in each palette
int colors_per_row; // How many colors per row in UI
int bits_per_pixel; // Color depth (typically 4 for SNES)
const char* description; // Usage description
bool has_animations; // Whether palettes animate
};
// Predefined metadata for all palette groups
namespace PaletteMetadata {
constexpr PaletteGroupMetadata kOverworldMain = {
.group_id = PaletteGroupName::kOverworldMain,
.display_name = "Overworld Main",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldMain,
.palette_count = PaletteCount::kOverworldMain,
.colors_per_palette = 35, // 35 colors: 2 full rows (0-15, 16-31) + 3 colors (32-34)
.colors_per_row = 7, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Main overworld palettes: 35 colors per set (2 full rows + 3 colors)",
.has_animations = false
};
constexpr PaletteGroupMetadata kOverworldAnimated = {
.group_id = PaletteGroupName::kOverworldAnimated,
.display_name = "Overworld Animated",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAnimated,
.palette_count = PaletteCount::kOverworldAnimated,
.colors_per_palette = 7, // 7 colors (overlay palette, no transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description = "Animated overlay palettes: 7 colors per set (water, lava, etc.)",
.has_animations = true
};
constexpr PaletteGroupMetadata kDungeonMain = {
.group_id = PaletteGroupName::kDungeonMain,
.display_name = "Dungeon Main",
.category = "Dungeon",
.base_address = PaletteAddress::kDungeonMain,
.palette_count = PaletteCount::kDungeonMain,
.colors_per_palette = 90, // 90 colors: 5 full rows (0-15, 16-31, 32-47, 48-63, 64-79) + 10 colors (80-89)
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Dungeon-specific palettes: 90 colors per set (5 full rows + 10 colors)",
.has_animations = false
};
constexpr PaletteGroupMetadata kGlobalSprites = {
.group_id = PaletteGroupName::kGlobalSprites,
.display_name = "Global Sprites",
.category = "Sprites",
.base_address = PaletteAddress::kGlobalSpritesLW,
.palette_count = 2, // 2 sets (LW and DW), each with 60 colors
.colors_per_palette = 60, // 60 colors: 4 rows (0-15, 16-31, 32-47, 48-59) with transparent at 0, 16, 32, 48
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Global sprite palettes: 60 colors per set (4 sprite sub-palettes of 15+transparent each)",
.has_animations = false
};
constexpr PaletteGroupMetadata kSpritesAux1 = {
.group_id = PaletteGroupName::kSpritesAux1,
.display_name = "Sprites Aux 1",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux1,
.palette_count = PaletteCount::kSpritesAux1,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 1: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
constexpr PaletteGroupMetadata kSpritesAux2 = {
.group_id = PaletteGroupName::kSpritesAux2,
.display_name = "Sprites Aux 2",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux2,
.palette_count = PaletteCount::kSpritesAux2,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 2: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
constexpr PaletteGroupMetadata kSpritesAux3 = {
.group_id = PaletteGroupName::kSpritesAux3,
.display_name = "Sprites Aux 3",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux3,
.palette_count = PaletteCount::kSpritesAux3,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 3: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
constexpr PaletteGroupMetadata kArmor = {
.group_id = PaletteGroupName::kArmor,
.display_name = "Armor / Link",
.category = "Equipment",
.base_address = PaletteAddress::kArmor,
.palette_count = PaletteCount::kArmor,
.colors_per_palette = 15, // 15 colors (ROM stores 15, transparent added in memory for full row)
.colors_per_row = 16, // Display as full 16-color rows (with transparent at index 0)
.bits_per_pixel = 4,
.description = "Link's tunic colors: 15 colors per palette (Green, Blue, Red, Bunny, Electrocuted)",
.has_animations = false
};
constexpr PaletteGroupMetadata kSwords = {
.group_id = PaletteGroupName::kSwords,
.display_name = "Swords",
.category = "Equipment",
.base_address = PaletteAddress::kSwords,
.palette_count = PaletteCount::kSwords,
.colors_per_palette = 3, // 3 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description = "Sword blade colors: 3 colors per palette (Fighter, Master, Tempered, Golden)",
.has_animations = false
};
constexpr PaletteGroupMetadata kShields = {
.group_id = PaletteGroupName::kShields,
.display_name = "Shields",
.category = "Equipment",
.base_address = PaletteAddress::kShields,
.palette_count = PaletteCount::kShields,
.colors_per_palette = 4, // 4 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description = "Shield colors: 4 colors per palette (Fighter, Fire, Mirror)",
.has_animations = false
};
constexpr PaletteGroupMetadata kHud = {
.group_id = PaletteGroupName::kHud,
.display_name = "HUD",
.category = "Interface",
.base_address = PaletteAddress::kHud,
.palette_count = PaletteCount::kHud,
.colors_per_palette = 32, // 32 colors: 2 full rows (0-15, 16-31) with transparent at 0, 16
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 2, // HUD palettes are 2bpp
.description = "HUD/Interface palettes: 32 colors per set (2 full rows)",
.has_animations = false
};
constexpr PaletteGroupMetadata kOverworldAux = {
.group_id = PaletteGroupName::kOverworldAux,
.display_name = "Overworld Auxiliary",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAux,
.palette_count = PaletteCount::kOverworldAux,
.colors_per_palette = 21, // 21 colors: 1 full row (0-15) + 5 colors (16-20)
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description = "Overworld auxiliary palettes: 21 colors per set (1 full row + 5 colors)",
.has_animations = false
};
constexpr PaletteGroupMetadata kGrass = {
.group_id = PaletteGroupName::kGrass,
.display_name = "Grass",
.category = "Overworld",
.base_address = PaletteAddress::kGrassLW,
.palette_count = PaletteCount::kGrass,
.colors_per_palette = 1, // Single color per entry
.colors_per_row = 3, // Display all 3 in one row
.bits_per_pixel = 4,
.description = "Hardcoded grass colors: 3 individual colors (LW, DW, Special)",
.has_animations = false
};
constexpr PaletteGroupMetadata k3DObject = {
.group_id = PaletteGroupName::k3DObject,
.display_name = "3D Objects",
.category = "Special",
.base_address = PaletteAddress::kTriforce,
.palette_count = PaletteCount::k3DObject,
.colors_per_palette = 8, // 8 colors per palette (7 + transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description = "3D object palettes: 8 colors per palette (Triforce, Crystal)",
.has_animations = false
};
constexpr PaletteGroupMetadata kOverworldMiniMap = {
.group_id = PaletteGroupName::kOverworldMiniMap,
.display_name = "Overworld Mini Map",
.category = "Interface",
.base_address = PaletteAddress::kOverworldMiniMap,
.palette_count = PaletteCount::kOverworldMiniMap,
.colors_per_palette = 128, // 128 colors: 8 full rows (0-127) with transparent at 0, 16, 32, 48, 64, 80, 96, 112
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description = "Overworld mini-map palettes: 128 colors per set (8 full rows)",
.has_animations = false
};
} // namespace PaletteMetadata
// Helper to get metadata by group name
const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id);
// Get all available palette groups
std::vector<const PaletteGroupMetadata*> GetAllPaletteGroups();
} // namespace zelda3
} // namespace yaze
#endif // YAZE_ZELDA3_PALETTE_CONSTANTS_H

View File

@@ -0,0 +1,142 @@
# SNES Palette Structure for ALTTP
## SNES Palette Memory Layout
The SNES has 256 color palette entries organized as:
- **16 palette rows** of **16 colors each**
- Each row starts with color index 0, which is **transparent**
- Palettes must be aligned to 16-color boundaries
### Example Layout
```
Row 0: Colors 0-15 (Color 0 = transparent)
Row 1: Colors 16-31 (Color 16 = transparent)
Row 2: Colors 32-47 (Color 32 = transparent)
...
```
## ALTTP Palette Groups - Corrected Structure
### Background Palettes (BG)
#### Overworld Main (35 colors per set)
- **Structure**: 2 full rows + 3 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- Row 2: Colors 32-34 (3 colors)
- **ROM**: 0xDE6C8
- **Sets**: 60 (20 LW, 20 DW, 20 Special)
#### Overworld Auxiliary (21 colors per set)
- **Structure**: 1 full row + 5 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-20 (5 colors)
- **ROM**: 0xDE86C
- **Sets**: 20
#### Overworld Animated (7 colors per set)
- **Structure**: Half-row without transparent
- Colors 0-6 (7 colors, no transparent marker as these overlay existing)
- **ROM**: 0xDE604
- **Sets**: 14
#### Dungeon Main (90 colors per set)
- **Structure**: 5 full rows + 10 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- Row 2: Colors 32-47 (transparent + 15 colors)
- Row 3: Colors 48-63 (transparent + 15 colors)
- Row 4: Colors 64-79 (transparent + 15 colors)
- Row 5: Colors 80-89 (10 colors)
- **ROM**: 0xDD734
- **Sets**: 20 (one per dungeon)
### Sprite Palettes (OAM)
Sprite palettes use rows 8-15 (colors 128-255).
#### Global Sprites (60 colors total)
- **Structure**: 4 rows (each with 15 actual colors + transparent)
- Row 8: Colors 128-143 (Sprite Palette 0: transparent + 15 colors)
- Row 9: Colors 144-159 (Sprite Palette 1: transparent + 15 colors)
- Row 10: Colors 160-175 (Sprite Palette 2: transparent + 15 colors)
- Row 11: Colors 176-191 (Sprite Palette 3: transparent + 15 colors)
- **ROM LW**: 0xDD218
- **ROM DW**: 0xDD290
- **Total**: 2 sets (LW and DW)
#### Sprites Auxiliary 1 (7 colors per palette)
- **Structure**: 12 palettes, each occupying half a row
- Palette 0: 7 colors (indices 1-7 of first half-row)
- Palette 1: 7 colors (indices 9-15 of second half-row)
- ...and so on
- **ROM**: 0xDD39E
- **Palettes**: 12
#### Sprites Auxiliary 2 (7 colors per palette)
- **Structure**: 11 palettes, each occupying half a row
- **ROM**: 0xDD446
- **Palettes**: 11
#### Sprites Auxiliary 3 (7 colors per palette)
- **Structure**: 24 palettes, each occupying half a row
- **ROM**: 0xDD4E0
- **Palettes**: 24
### Equipment/Link Palettes
#### Armor/Link (15 colors per palette)
- **Structure**: Full row minus transparent
- Each palette: transparent + 15 colors
- **ROM**: 0xDD308
- **Palettes**: 5 (Green Mail, Blue Mail, Red Mail, Bunny, Electrocuted)
#### Swords (3 colors per palette)
- **Structure**: 3 colors within row (no transparent needed as overlay)
- **ROM**: 0xDD630
- **Palettes**: 4 (Fighter, Master, Tempered, Golden)
#### Shields (4 colors per palette)
- **Structure**: 4 colors within row (no transparent needed as overlay)
- **ROM**: 0xDD648
- **Palettes**: 3 (Fighter, Fire, Mirror)
### HUD Palettes
#### HUD (32 colors per set)
- **Structure**: 2 full rows
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- **ROM**: 0xDD660
- **Sets**: 2
### Special Colors
#### Grass (3 individual colors)
- LW: 0x5FEA9
- DW: 0x5FEB3
- Special: 0x75640
#### 3D Objects (8 colors per palette)
- **Triforce**: 0x64425
- **Crystal**: 0xF4CD3
#### Overworld Mini Map (128 colors per set)
- **Structure**: 8 full rows
- **ROM**: 0x55B27
- **Sets**: 2
## Key Principles
1. **Transparent Color**: Always at indices 0, 16, 32, 48, 64, etc. (multiples of 16)
2. **Row Alignment**: Palettes should respect 16-color row boundaries
3. **Overlay Palettes**: Some palettes (like animated, swords, shields) overlay existing colors and don't have their own transparent
4. **Sub-Palettes**: Multiple small palettes can share a row if they're 8 colors or less
## Implementation Notes
When loading palettes, we must:
1. Mark color index 0 of each 16-color row as transparent
2. For palettes < 16 colors, understand if they're standalone (need transparent) or overlays (don't need transparent)
3. Display palettes in UI with proper row alignment for clarity