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:
@@ -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() {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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] = {};
|
||||
|
||||
|
||||
@@ -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", ¤t_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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
147
src/app/editor/palette/palette_utility.cc
Normal file
147
src/app/editor/palette/palette_utility.cc
Normal 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
|
||||
|
||||
78
src/app/editor/palette/palette_utility.h
Normal file
78
src/app/editor/palette/palette_utility.h
Normal 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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = ¢ralized_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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
76
src/zelda3/palette_constants.cc
Normal file
76
src/zelda3/palette_constants.cc
Normal 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
|
||||
|
||||
310
src/zelda3/palette_constants.h
Normal file
310
src/zelda3/palette_constants.h
Normal 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
|
||||
|
||||
142
src/zelda3/palette_structure.md
Normal file
142
src/zelda3/palette_structure.md
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user