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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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