diff --git a/src/app/editor/code/assembly_editor.cc b/src/app/editor/code/assembly_editor.cc index 82b795aa..2140e689 100644 --- a/src/app/editor/code/assembly_editor.cc +++ b/src/app/editor/code/assembly_editor.cc @@ -174,6 +174,17 @@ FolderItem LoadFolder(const std::string& folder) { void AssemblyEditor::Initialize() { text_editor_.SetLanguageDefinition(GetAssemblyLanguageDef()); + + // Register cards with EditorCardManager + auto& card_manager = gui::EditorCardManager::Get(); + card_manager.RegisterCard({.card_id = "assembly.editor", .display_name = "Assembly Editor", + .icon = ICON_MD_CODE, .category = "Assembly", + .shortcut_hint = "", .priority = 10}); + card_manager.RegisterCard({.card_id = "assembly.file_browser", .display_name = "File Browser", + .icon = ICON_MD_FOLDER_OPEN, .category = "Assembly", + .shortcut_hint = "", .priority = 20}); + + // Don't show by default - only show when user explicitly opens Assembly Editor } absl::Status AssemblyEditor::Load() { diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index b8126073..75b74cea 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -108,6 +108,10 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { .visibility_flag = &show_debug_controls_, .priority = 80 }); + + // Show control panel and room selector by default when Dungeon Editor is activated + show_control_panel_ = true; + show_room_selector_ = true; } void DungeonEditorV2::Initialize() {} @@ -187,32 +191,6 @@ absl::Status DungeonEditorV2::Update() { // CARD-BASED EDITOR: All windows are independent top-level cards // No parent wrapper - this allows closing control panel without affecting rooms - // 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; - - if (ImGui::Begin("##DungeonControlIcon", nullptr, icon_flags)) { - if (ImGui::Button(ICON_MD_CASTLE, ImVec2(40, 40))) { - show_control_panel_ = true; - control_panel_minimized_ = false; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Open Dungeon Controls"); - } - } - ImGui::End(); - } - - // Render all independent cards (these are ALL top-level windows now) DrawLayout(); return absl::OkStatus(); @@ -235,73 +213,6 @@ absl::Status DungeonEditorV2::Save() { return absl::OkStatus(); } -void DungeonEditorV2::DrawToolset() { - // Draw VSCode-style sidebar using EditorCardManager - // auto& card_manager = gui::EditorCardManager::Get(); - // card_manager.DrawSidebar("Dungeon"); -} - -void DungeonEditorV2::DrawControlPanel() { - // Small, collapsible control panel for dungeon editor - ImGui::SetNextWindowSize(ImVec2(280, 280), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver); - - ImGuiWindowFlags flags = ImGuiWindowFlags_None; - - if (ImGui::Begin(ICON_MD_CASTLE " Dungeon Controls", &show_control_panel_, flags)) { - ImGui::TextWrapped("Welcome to Dungeon Editor V2!"); - ImGui::TextDisabled("Use checkboxes below to open cards"); - ImGui::Separator(); - - DrawToolset(); - - ImGui::Separator(); - ImGui::Text("Quick Toggles:"); - - // Checkbox grid for quick toggles - if (ImGui::BeginTable("##QuickToggles", 2, ImGuiTableFlags_SizingStretchSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Rooms", &show_room_selector_); - - ImGui::TableNextColumn(); - ImGui::Checkbox("Matrix", &show_room_matrix_); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Entrances", &show_entrances_list_); - - ImGui::TableNextColumn(); - ImGui::Checkbox("Graphics", &show_room_graphics_); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Objects", &show_object_editor_); - - ImGui::TableNextColumn(); - ImGui::Checkbox("Palette", &show_palette_editor_); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("Debug", &show_debug_controls_); - - ImGui::EndTable(); - } - - ImGui::Separator(); - - // Minimize button - if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) { - control_panel_minimized_ = true; - show_control_panel_ = false; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Collapse to floating icon. Rooms stay open."); - } - } - ImGui::End(); -} - void DungeonEditorV2::DrawLayout() { // NO TABLE LAYOUT - All independent dockable EditorCards // All cards check their visibility flags and can be closed with X button diff --git a/src/app/editor/dungeon/dungeon_editor_v2.h b/src/app/editor/dungeon/dungeon_editor_v2.h index 397e1667..38fedca5 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.h +++ b/src/app/editor/dungeon/dungeon_editor_v2.h @@ -102,13 +102,11 @@ class DungeonEditorV2 : public Editor { // Simple UI layout void DrawLayout(); void DrawRoomTab(int room_id); - void DrawToolset(); void DrawRoomMatrixCard(); void DrawRoomsListCard(); void DrawEntrancesListCard(); void DrawRoomGraphicsCard(); void DrawDebugControlsCard(); - void DrawControlPanel(); // Texture processing (critical for rendering) void ProcessDeferredTextures(); diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 08221dea..4e834c85 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -91,17 +91,22 @@ std::string GetEditorName(EditorType type) { } // namespace // Static registry of editors that use the card-based layout system -// These editors register their cards with EditorCardManager +// These editors register their cards with EditorCardManager and manage their own windows +// They do NOT need the traditional ImGui::Begin/End wrapper - they create cards internally 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::kOverworld: + case EditorType::kDungeon: // ✅ Full card system + case EditorType::kPalette: // ✅ Full card system + case EditorType::kGraphics: // ✅ EditorCard wrappers + Toolset + case EditorType::kScreen: // ✅ EditorCard wrappers + Toolset + case EditorType::kSprite: // ✅ EditorCard wrappers + Toolset + case EditorType::kOverworld: // ✅ Inline EditorCard + Toolset + case EditorType::kEmulator: // ✅ Emulator UI panels as cards + case EditorType::kMessage: // ✅ Message editor cards + case EditorType::kHex: // ✅ Memory/Hex editor + case EditorType::kAssembly: // ✅ Assembly editor return true; + // Music: Traditional UI - needs wrapper default: return false; } @@ -127,11 +132,30 @@ std::string EditorManager::GetEditorCategory(EditorType type) { return "Screen"; case EditorType::kEmulator: return "Emulator"; + case EditorType::kHex: + return "Memory"; + case EditorType::kAssembly: + return "Assembly"; default: return "Unknown"; } } +EditorType EditorManager::GetEditorTypeFromCategory(const std::string& category) { + if (category == "Dungeon") return EditorType::kDungeon; + if (category == "Palette") return EditorType::kPalette; + if (category == "Graphics") return EditorType::kGraphics; + if (category == "Overworld") return EditorType::kOverworld; + if (category == "Sprite") return EditorType::kSprite; + if (category == "Message") return EditorType::kMessage; + if (category == "Music") return EditorType::kMusic; + if (category == "Screen") return EditorType::kScreen; + if (category == "Emulator") return EditorType::kEmulator; + if (category == "Memory") return EditorType::kHex; + if (category == "Assembly") return EditorType::kAssembly; + return EditorType::kUnknown; +} + void EditorManager::HideCurrentEditorCards() { if (!current_editor_) { return; @@ -221,6 +245,37 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file "global.toggle_sidebar", {ImGuiKey_LeftCtrl, ImGuiKey_B}, [this]() { show_card_sidebar_ = !show_card_sidebar_; }); + + // Register emulator cards early (emulator Initialize might not be called) + auto& card_manager = gui::EditorCardManager::Get(); + card_manager.RegisterCard({.card_id = "emulator.cpu_debugger", .display_name = "CPU Debugger", + .icon = ICON_MD_BUG_REPORT, .category = "Emulator", .priority = 10}); + card_manager.RegisterCard({.card_id = "emulator.ppu_viewer", .display_name = "PPU Viewer", + .icon = ICON_MD_VIDEOGAME_ASSET, .category = "Emulator", .priority = 20}); + card_manager.RegisterCard({.card_id = "emulator.memory_viewer", .display_name = "Memory Viewer", + .icon = ICON_MD_MEMORY, .category = "Emulator", .priority = 30}); + card_manager.RegisterCard({.card_id = "emulator.breakpoints", .display_name = "Breakpoints", + .icon = ICON_MD_STOP, .category = "Emulator", .priority = 40}); + card_manager.RegisterCard({.card_id = "emulator.performance", .display_name = "Performance", + .icon = ICON_MD_SPEED, .category = "Emulator", .priority = 50}); + card_manager.RegisterCard({.card_id = "emulator.ai_agent", .display_name = "AI Agent", + .icon = ICON_MD_SMART_TOY, .category = "Emulator", .priority = 60}); + card_manager.RegisterCard({.card_id = "emulator.save_states", .display_name = "Save States", + .icon = ICON_MD_SAVE, .category = "Emulator", .priority = 70}); + card_manager.RegisterCard({.card_id = "emulator.keyboard_config", .display_name = "Keyboard Config", + .icon = ICON_MD_KEYBOARD, .category = "Emulator", .priority = 80}); + card_manager.RegisterCard({.card_id = "emulator.apu_debugger", .display_name = "APU Debugger", + .icon = ICON_MD_AUDIOTRACK, .category = "Emulator", .priority = 90}); + card_manager.RegisterCard({.card_id = "emulator.audio_mixer", .display_name = "Audio Mixer", + .icon = ICON_MD_AUDIO_FILE, .category = "Emulator", .priority = 100}); + + // Show CPU debugger and PPU viewer by default for emulator + card_manager.ShowCard("emulator.cpu_debugger"); + card_manager.ShowCard("emulator.ppu_viewer"); + + // Register memory/hex editor card + card_manager.RegisterCard({.card_id = "memory.hex_editor", .display_name = "Hex Editor", + .icon = ICON_MD_MEMORY, .category = "Memory", .priority = 10}); // Initialize project file editor project_file_editor_.SetToastManager(&toast_manager_); @@ -411,54 +466,7 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file // Initialize editor selection dialog callback editor_selection_dialog_.SetSelectionCallback([this](EditorType type) { editor_selection_dialog_.MarkRecentlyUsed(type); - - // Handle agent editor separately (doesn't require ROM) - if (type == EditorType::kAgent) { -#ifdef YAZE_WITH_GRPC - agent_editor_.toggle_active(); -#endif - return; - } - - if (!current_editor_set_) return; - - switch (type) { - case EditorType::kOverworld: - current_editor_set_->overworld_editor_.toggle_active(); - break; - case EditorType::kDungeon: - current_editor_set_->dungeon_editor_.toggle_active(); - break; - case EditorType::kGraphics: - current_editor_set_->graphics_editor_.toggle_active(); - break; - case EditorType::kSprite: - current_editor_set_->sprite_editor_.toggle_active(); - break; - case EditorType::kMessage: - current_editor_set_->message_editor_.toggle_active(); - break; - case EditorType::kMusic: - current_editor_set_->music_editor_.toggle_active(); - break; - case EditorType::kPalette: - current_editor_set_->palette_editor_.toggle_active(); - break; - case EditorType::kScreen: - current_editor_set_->screen_editor_.toggle_active(); - break; - case EditorType::kAssembly: - show_asm_editor_ = true; - break; - case EditorType::kEmulator: - show_emulator_ = true; - break; - case EditorType::kSettings: - current_editor_set_->settings_editor_.toggle_active(); - break; - default: - break; - } + SwitchToEditor(type); // Use centralized method }); // Load user settings - this must happen after context is initialized @@ -537,37 +545,37 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file context_.shortcut_manager.RegisterShortcut( "F1", ImGuiKey_F1, [this]() { popup_manager_->Show("About"); }); - // Editor shortcuts (Ctrl+1-9, Ctrl+0) + // Editor shortcuts (Ctrl+1-9, Ctrl+0) - all use SwitchToEditor for consistency context_.shortcut_manager.RegisterShortcut( - "Overworld Editor", {ImGuiKey_1, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->overworld_editor_.toggle_active(); }); + "Overworld Editor", {ImGuiKey_LeftCtrl, ImGuiKey_1}, + [this]() { SwitchToEditor(EditorType::kOverworld); }); context_.shortcut_manager.RegisterShortcut( - "Dungeon Editor", {ImGuiKey_2, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->dungeon_editor_.toggle_active(); }); + "Dungeon Editor", {ImGuiKey_LeftCtrl, ImGuiKey_2}, + [this]() { SwitchToEditor(EditorType::kDungeon); }); context_.shortcut_manager.RegisterShortcut( - "Graphics Editor", {ImGuiKey_3, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->graphics_editor_.toggle_active(); }); + "Graphics Editor", {ImGuiKey_LeftCtrl, ImGuiKey_3}, + [this]() { SwitchToEditor(EditorType::kGraphics); }); context_.shortcut_manager.RegisterShortcut( - "Sprite Editor", {ImGuiKey_4, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->sprite_editor_.toggle_active(); }); + "Sprite Editor", {ImGuiKey_LeftCtrl, ImGuiKey_4}, + [this]() { SwitchToEditor(EditorType::kSprite); }); context_.shortcut_manager.RegisterShortcut( - "Message Editor", {ImGuiKey_5, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->message_editor_.toggle_active(); }); + "Message Editor", {ImGuiKey_LeftCtrl, ImGuiKey_5}, + [this]() { SwitchToEditor(EditorType::kMessage); }); context_.shortcut_manager.RegisterShortcut( - "Music Editor", {ImGuiKey_6, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->music_editor_.toggle_active(); }); + "Music Editor", {ImGuiKey_LeftCtrl, ImGuiKey_6}, + [this]() { SwitchToEditor(EditorType::kMusic); }); context_.shortcut_manager.RegisterShortcut( - "Palette Editor", {ImGuiKey_7, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->palette_editor_.toggle_active(); }); + "Palette Editor", {ImGuiKey_LeftCtrl, ImGuiKey_7}, + [this]() { SwitchToEditor(EditorType::kPalette); }); context_.shortcut_manager.RegisterShortcut( - "Screen Editor", {ImGuiKey_8, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->screen_editor_.toggle_active(); }); + "Screen Editor", {ImGuiKey_LeftCtrl, ImGuiKey_8}, + [this]() { SwitchToEditor(EditorType::kScreen); }); context_.shortcut_manager.RegisterShortcut( - "Assembly Editor", {ImGuiKey_9, ImGuiMod_Ctrl}, - [this]() { show_asm_editor_ = true; }); + "Assembly Editor", {ImGuiKey_LeftCtrl, ImGuiKey_9}, + [this]() { SwitchToEditor(EditorType::kAssembly); }); context_.shortcut_manager.RegisterShortcut( - "Settings Editor", {ImGuiKey_0, ImGuiMod_Ctrl}, - [this]() { if (current_editor_set_) current_editor_set_->settings_editor_.toggle_active(); }); + "Settings Editor", {ImGuiKey_LeftCtrl, ImGuiKey_0}, + [this]() { SwitchToEditor(EditorType::kSettings); }); // Editor Selection Dialog shortcut context_.shortcut_manager.RegisterShortcut( @@ -912,13 +920,6 @@ absl::Status EditorManager::Update() { } } - // Draw sidebar for current card-based editor (only if sidebar is visible) - if (show_card_sidebar_ && current_editor_ && IsCardBasedEditor(current_editor_->type())) { - 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(); } @@ -933,6 +934,59 @@ absl::Status EditorManager::Update() { } #endif + // Draw unified sidebar LAST so it appears on top of all other windows + if (show_card_sidebar_ && current_editor_set_) { + auto& card_manager = gui::EditorCardManager::Get(); + + // Collect all active card-based editors + std::vector active_categories; + for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) { + auto& session = sessions_[session_idx]; + if (!session.rom.is_loaded()) continue; + + for (auto editor : session.editors.active_editors_) { + if (*editor->active() && IsCardBasedEditor(editor->type())) { + std::string category = GetEditorCategory(editor->type()); + if (std::find(active_categories.begin(), active_categories.end(), category) == active_categories.end()) { + active_categories.push_back(category); + } + } + } + } + + // Determine which category to show in sidebar + std::string sidebar_category; + + // Priority 1: Use active_category from card manager (user's last interaction) + if (!card_manager.GetActiveCategory().empty() && + std::find(active_categories.begin(), active_categories.end(), + card_manager.GetActiveCategory()) != active_categories.end()) { + sidebar_category = card_manager.GetActiveCategory(); + } + // Priority 2: Use first active category + else if (!active_categories.empty()) { + sidebar_category = active_categories[0]; + card_manager.SetActiveCategory(sidebar_category); + } + + // Draw sidebar if we have a category + if (!sidebar_category.empty()) { + // Callback to switch editors when category button is clicked + auto category_switch_callback = [this](const std::string& new_category) { + EditorType editor_type = GetEditorTypeFromCategory(new_category); + if (editor_type != EditorType::kUnknown) { + SwitchToEditor(editor_type); + } + }; + + auto collapse_callback = [this]() { + show_card_sidebar_ = false; + }; + + card_manager.DrawSidebar(sidebar_category, active_categories, category_switch_callback, collapse_callback); + } + } + return absl::OkStatus(); } @@ -1112,23 +1166,23 @@ void EditorManager::BuildModernMenu() { [this]() { show_editor_selection_ = true; }, "Ctrl+E") .Separator() .Item("Overworld", ICON_MD_MAP, - [this]() { current_editor_set_->overworld_editor_.set_active(true); }, "Ctrl+1") + [this]() { SwitchToEditor(EditorType::kOverworld); }, "Ctrl+1") .Item("Dungeon", ICON_MD_CASTLE, - [this]() { current_editor_set_->dungeon_editor_.set_active(true); }, "Ctrl+2") + [this]() { SwitchToEditor(EditorType::kDungeon); }, "Ctrl+2") .Item("Graphics", ICON_MD_IMAGE, - [this]() { current_editor_set_->graphics_editor_.set_active(true); }, "Ctrl+3") + [this]() { SwitchToEditor(EditorType::kGraphics); }, "Ctrl+3") .Item("Sprites", ICON_MD_TOYS, - [this]() { current_editor_set_->sprite_editor_.set_active(true); }, "Ctrl+4") + [this]() { SwitchToEditor(EditorType::kSprite); }, "Ctrl+4") .Item("Messages", ICON_MD_CHAT_BUBBLE, - [this]() { current_editor_set_->message_editor_.set_active(true); }, "Ctrl+5") + [this]() { SwitchToEditor(EditorType::kMessage); }, "Ctrl+5") .Item("Music", ICON_MD_MUSIC_NOTE, - [this]() { current_editor_set_->music_editor_.set_active(true); }, "Ctrl+6") + [this]() { SwitchToEditor(EditorType::kMusic); }, "Ctrl+6") .Item("Palettes", ICON_MD_PALETTE, - [this]() { current_editor_set_->palette_editor_.set_active(true); }, "Ctrl+7") + [this]() { SwitchToEditor(EditorType::kPalette); }, "Ctrl+7") .Item("Screens", ICON_MD_TV, - [this]() { current_editor_set_->screen_editor_.set_active(true); }, "Ctrl+8") + [this]() { SwitchToEditor(EditorType::kScreen); }, "Ctrl+8") .Item("Hex Editor", ICON_MD_DATA_ARRAY, - [this]() { show_memory_editor_ = true; }, "Ctrl+0") + [this]() { gui::EditorCardManager::Get().ShowCard("memory.hex_editor"); }, "Ctrl+0") #ifdef YAZE_WITH_GRPC .Item("AI Agent", ICON_MD_SMART_TOY, [this]() { agent_editor_.set_active(true); }, "Ctrl+Shift+A") @@ -1388,9 +1442,9 @@ void EditorManager::BuildModernMenu() { .Separator() // Development Tools .Item("Memory Editor", ICON_MD_MEMORY, - [this]() { show_memory_editor_ = true; }) + [this]() { gui::EditorCardManager::Get().ShowCard("memory.hex_editor"); }) .Item("Assembly Editor", ICON_MD_CODE, - [this]() { show_asm_editor_ = true; }) + [this]() { gui::EditorCardManager::Get().ShowCard("assembly.editor"); }) .Item("Feature Flags", ICON_MD_FLAG, [this]() { popup_manager_->Show("Feature Flags"); }) .Separator() @@ -1512,11 +1566,15 @@ void EditorManager::DrawMenuBar() { ShowDemoWindow(&show_imgui_demo_); if (show_imgui_metrics_) ShowMetricsWindow(&show_imgui_metrics_); - if (show_memory_editor_ && current_editor_set_) { - current_editor_set_->memory_editor_.Update(show_memory_editor_); + + auto& card_manager = gui::EditorCardManager::Get(); + if (card_manager.IsCardVisible("memory.hex_editor") && current_editor_set_) { + bool show_memory = true; + current_editor_set_->memory_editor_.Update(show_memory); } - if (show_asm_editor_ && current_editor_set_) { - current_editor_set_->assembly_editor_.Update(show_asm_editor_); + if (card_manager.IsCardVisible("assembly.editor") && current_editor_set_) { + bool show_asm = true; + current_editor_set_->assembly_editor_.Update(show_asm); } // Project file editor @@ -1551,10 +1609,9 @@ void EditorManager::DrawMenuBar() { DrawWelcomeScreen(); } + // Emulator is now card-based - it creates its own windows if (show_emulator_) { - Begin("Emulator", &show_emulator_, ImGuiWindowFlags_MenuBar); emulator_.Run(current_rom_); - End(); } // Enhanced Command Palette UI with Fuzzy Search @@ -2172,6 +2229,11 @@ absl::Status EditorManager::LoadAssets() { current_editor_set_->overworld_editor_.Initialize(); current_editor_set_->message_editor_.Initialize(); + current_editor_set_->graphics_editor_.Initialize(); + current_editor_set_->screen_editor_.Initialize(); + current_editor_set_->sprite_editor_.Initialize(); + current_editor_set_->palette_editor_.Initialize(); + current_editor_set_->assembly_editor_.Initialize(); // Initialize the dungeon editor with the renderer current_editor_set_->dungeon_editor_.Initialize(renderer_, current_rom_); ASSIGN_OR_RETURN(*gfx::Arena::Get().mutable_gfx_sheets(), @@ -3352,14 +3414,40 @@ void EditorManager::JumpToOverworldMap(int map_id) { } void EditorManager::SwitchToEditor(EditorType editor_type) { - // Find the editor tab and activate it - for (size_t i = 0; i < current_editor_set_->active_editors_.size(); ++i) { - if (current_editor_set_->active_editors_[i]->type() == editor_type) { - current_editor_set_->active_editors_[i]->set_active(true); + if (!current_editor_set_) return; + + // Toggle the editor + for (auto* editor : current_editor_set_->active_editors_) { + if (editor->type() == editor_type) { + editor->toggle_active(); - // Set editor as the current/focused one - // This will make it visible when tabs are rendered - break; + if (IsCardBasedEditor(editor_type)) { + auto& card_manager = gui::EditorCardManager::Get(); + + if (*editor->active()) { + // Editor activated - set its category + card_manager.SetActiveCategory(GetEditorCategory(editor_type)); + } else { + // Editor deactivated - switch to another active card-based editor + for (auto* other : current_editor_set_->active_editors_) { + if (*other->active() && IsCardBasedEditor(other->type()) && other != editor) { + card_manager.SetActiveCategory(GetEditorCategory(other->type())); + break; + } + } + } + } + return; + } + } + + // Handle non-editor-class cases + if (editor_type == EditorType::kAssembly) { + show_asm_editor_ = !show_asm_editor_; + } else if (editor_type == EditorType::kEmulator) { + show_emulator_ = !show_emulator_; + if (show_emulator_) { + gui::EditorCardManager::Get().SetActiveCategory("Emulator"); } } } diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index e604b763..9fd24f8e 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -171,6 +171,7 @@ class EditorManager { // Card-based editor registry static bool IsCardBasedEditor(EditorType type); static std::string GetEditorCategory(EditorType type); + static EditorType GetEditorTypeFromCategory(const std::string& category); bool IsSidebarVisible() const { return show_card_sidebar_; } void SetSidebarVisible(bool visible) { show_card_sidebar_ = visible; } diff --git a/src/app/editor/graphics/graphics_editor.cc b/src/app/editor/graphics/graphics_editor.cc index a11ba858..218cd66c 100644 --- a/src/app/editor/graphics/graphics_editor.cc +++ b/src/app/editor/graphics/graphics_editor.cc @@ -43,48 +43,23 @@ constexpr ImGuiTableFlags kGfxEditTableFlags = ImGuiTableFlags_SizingFixedFit; void GraphicsEditor::Initialize() { - // Register cards with EditorCardManager during initialization (once) auto& card_manager = gui::EditorCardManager::Get(); - card_manager.RegisterCard({ - .card_id = "graphics.sheet_editor", - .display_name = "Sheet Editor", - .icon = ICON_MD_EDIT, - .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+1", - .visibility_flag = &show_sheet_editor_, - .priority = 10 - }); + card_manager.RegisterCard({.card_id = "graphics.sheet_editor", .display_name = "Sheet Editor", + .icon = ICON_MD_EDIT, .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+1", .priority = 10}); + card_manager.RegisterCard({.card_id = "graphics.sheet_browser", .display_name = "Sheet Browser", + .icon = ICON_MD_VIEW_LIST, .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+2", .priority = 20}); + card_manager.RegisterCard({.card_id = "graphics.player_animations", .display_name = "Player Animations", + .icon = ICON_MD_PERSON, .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+3", .priority = 30}); + card_manager.RegisterCard({.card_id = "graphics.prototype_viewer", .display_name = "Prototype Viewer", + .icon = ICON_MD_CONSTRUCTION, .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+4", .priority = 40}); - card_manager.RegisterCard({ - .card_id = "graphics.sheet_browser", - .display_name = "Sheet Browser", - .icon = ICON_MD_VIEW_LIST, - .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+2", - .visibility_flag = &show_sheet_browser_, - .priority = 20 - }); - - card_manager.RegisterCard({ - .card_id = "graphics.player_animations", - .display_name = "Player Animations", - .icon = ICON_MD_PERSON, - .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+3", - .visibility_flag = &show_player_animations_, - .priority = 30 - }); - - card_manager.RegisterCard({ - .card_id = "graphics.prototype_viewer", - .display_name = "Prototype Viewer", - .icon = ICON_MD_CONSTRUCTION, - .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+4", - .visibility_flag = &show_prototype_viewer_, - .priority = 40 - }); + // Show sheet editor by default when Graphics Editor is activated + card_manager.ShowCard("graphics.sheet_editor"); } absl::Status GraphicsEditor::Load() { @@ -124,56 +99,46 @@ absl::Status GraphicsEditor::Load() { } absl::Status GraphicsEditor::Update() { - DrawToolset(); - gui::VerticalSpacing(2.0f); + auto& card_manager = gui::EditorCardManager::Get(); + + static gui::EditorCard sheet_editor_card("Sheet Editor", ICON_MD_EDIT); + static gui::EditorCard sheet_browser_card("Sheet Browser", ICON_MD_VIEW_LIST); + static gui::EditorCard player_anims_card("Player Animations", ICON_MD_PERSON); + static gui::EditorCard prototype_card("Prototype Viewer", ICON_MD_CONSTRUCTION); - // Create session-aware cards (non-static for multi-session support) - gui::EditorCard sheet_editor_card(MakeCardTitle("Sheet Editor").c_str(), ICON_MD_EDIT); - gui::EditorCard sheet_browser_card(MakeCardTitle("Sheet Browser").c_str(), ICON_MD_VIEW_LIST); - gui::EditorCard player_anims_card(MakeCardTitle("Player Animations").c_str(), ICON_MD_PERSON); - gui::EditorCard prototype_card(MakeCardTitle("Prototype Viewer").c_str(), ICON_MD_CONSTRUCTION); + sheet_editor_card.SetDefaultSize(900, 700); + sheet_browser_card.SetDefaultSize(400, 600); + player_anims_card.SetDefaultSize(500, 600); + prototype_card.SetDefaultSize(600, 500); - if (show_sheet_editor_) { - if (sheet_editor_card.Begin(&show_sheet_editor_)) { - status_ = UpdateGfxEdit(); - } - sheet_editor_card.End(); // ALWAYS call End after Begin + // Get visibility flags from card manager and pass to Begin() + if (sheet_editor_card.Begin(card_manager.GetVisibilityFlag("graphics.sheet_editor"))) { + status_ = UpdateGfxEdit(); + sheet_editor_card.End(); } - if (show_sheet_browser_) { - if (sheet_browser_card.Begin(&show_sheet_browser_)) { - if (asset_browser_.Initialized == false) { - asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets()); - } - asset_browser_.Draw(gfx::Arena::Get().gfx_sheets()); + if (sheet_browser_card.Begin(card_manager.GetVisibilityFlag("graphics.sheet_browser"))) { + if (asset_browser_.Initialized == false) { + asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets()); } - sheet_browser_card.End(); // ALWAYS call End after Begin + asset_browser_.Draw(gfx::Arena::Get().gfx_sheets()); + sheet_browser_card.End(); } - if (show_player_animations_) { - if (player_anims_card.Begin(&show_player_animations_)) { - status_ = UpdateLinkGfxView(); - } - player_anims_card.End(); // ALWAYS call End after Begin + if (player_anims_card.Begin(card_manager.GetVisibilityFlag("graphics.player_animations"))) { + status_ = UpdateLinkGfxView(); + player_anims_card.End(); } - if (show_prototype_viewer_) { - if (prototype_card.Begin(&show_prototype_viewer_)) { - status_ = UpdateScadView(); - } - prototype_card.End(); // ALWAYS call End after Begin + if (prototype_card.Begin(card_manager.GetVisibilityFlag("graphics.prototype_viewer"))) { + status_ = UpdateScadView(); + prototype_card.End(); } CLEAR_AND_RETURN_STATUS(status_) return absl::OkStatus(); } -void GraphicsEditor::DrawToolset() { - // Sidebar is now drawn by EditorManager for card-based editors - // This method kept for compatibility but sidebar handles card toggles -} - - absl::Status GraphicsEditor::UpdateGfxEdit() { if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, ImVec2(0, 0))) { @@ -606,8 +571,6 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() { } absl::Status GraphicsEditor::UpdateScadView() { - DrawToolset(); - if (open_memory_editor_) { ImGui::Begin("Memory Editor", &open_memory_editor_); RETURN_IF_ERROR(DrawMemoryEditor()) diff --git a/src/app/editor/graphics/graphics_editor.h b/src/app/editor/graphics/graphics_editor.h index 324b0507..a6ede3c8 100644 --- a/src/app/editor/graphics/graphics_editor.h +++ b/src/app/editor/graphics/graphics_editor.h @@ -107,7 +107,6 @@ class GraphicsEditor : public Editor { absl::Status DrawTilemapImport(); // Other Functions - void DrawToolset(); absl::Status DrawPaletteControls(); absl::Status DrawClipboardImport(); absl::Status DrawExperimentalFeatures(); @@ -117,12 +116,7 @@ class GraphicsEditor : public Editor { absl::Status DecompressSuperDonkey(); // Member Variables - // Card visibility - ALL FALSE by default to prevent crash on ROM load - // Cards only shown when user explicitly opens them via View menu or shortcuts - bool show_sheet_editor_ = false; - bool show_sheet_browser_ = false; - bool show_player_animations_ = false; - bool show_prototype_viewer_ = false; + // Card visibility managed by EditorCardManager ImVec4 current_color_; uint16_t current_sheet_ = 0; diff --git a/src/app/editor/graphics/screen_editor.cc b/src/app/editor/graphics/screen_editor.cc index 80049791..6e5e024a 100644 --- a/src/app/editor/graphics/screen_editor.cc +++ b/src/app/editor/graphics/screen_editor.cc @@ -30,58 +30,26 @@ namespace editor { constexpr uint32_t kRedPen = 0xFF0000FF; void ScreenEditor::Initialize() { - // Register cards with EditorCardManager during initialization (once) auto& card_manager = gui::EditorCardManager::Get(); - card_manager.RegisterCard({ - .card_id = "screen.dungeon_maps", - .display_name = "Dungeon Maps", - .icon = ICON_MD_MAP, - .category = "Screen", - .shortcut_hint = "Alt+1", - .visibility_flag = &show_dungeon_maps_, - .priority = 10 - }); + card_manager.RegisterCard({.card_id = "screen.dungeon_maps", .display_name = "Dungeon Maps", + .icon = ICON_MD_MAP, .category = "Screen", + .shortcut_hint = "Alt+1", .priority = 10}); + card_manager.RegisterCard({.card_id = "screen.inventory_menu", .display_name = "Inventory Menu", + .icon = ICON_MD_INVENTORY, .category = "Screen", + .shortcut_hint = "Alt+2", .priority = 20}); + card_manager.RegisterCard({.card_id = "screen.overworld_map", .display_name = "Overworld Map", + .icon = ICON_MD_PUBLIC, .category = "Screen", + .shortcut_hint = "Alt+3", .priority = 30}); + card_manager.RegisterCard({.card_id = "screen.title_screen", .display_name = "Title Screen", + .icon = ICON_MD_TITLE, .category = "Screen", + .shortcut_hint = "Alt+4", .priority = 40}); + card_manager.RegisterCard({.card_id = "screen.naming_screen", .display_name = "Naming Screen", + .icon = ICON_MD_EDIT, .category = "Screen", + .shortcut_hint = "Alt+5", .priority = 50}); - card_manager.RegisterCard({ - .card_id = "screen.inventory_menu", - .display_name = "Inventory Menu", - .icon = ICON_MD_INVENTORY, - .category = "Screen", - .shortcut_hint = "Alt+2", - .visibility_flag = &show_inventory_menu_, - .priority = 20 - }); - - card_manager.RegisterCard({ - .card_id = "screen.overworld_map", - .display_name = "Overworld Map", - .icon = ICON_MD_PUBLIC, - .category = "Screen", - .shortcut_hint = "Alt+3", - .visibility_flag = &show_overworld_map_, - .priority = 30 - }); - - card_manager.RegisterCard({ - .card_id = "screen.title_screen", - .display_name = "Title Screen", - .icon = ICON_MD_TITLE, - .category = "Screen", - .shortcut_hint = "Alt+4", - .visibility_flag = &show_title_screen_, - .priority = 40 - }); - - card_manager.RegisterCard({ - .card_id = "screen.naming_screen", - .display_name = "Naming Screen", - .icon = ICON_MD_EDIT, - .category = "Screen", - .shortcut_hint = "Alt+5", - .visibility_flag = &show_naming_screen_, - .priority = 50 - }); + // Show title screen by default + card_manager.ShowCard("screen.title_screen"); } absl::Status ScreenEditor::Load() { @@ -119,45 +87,40 @@ absl::Status ScreenEditor::Load() { } absl::Status ScreenEditor::Update() { - DrawToolset(); - gui::VerticalSpacing(2.0f); + auto& card_manager = gui::EditorCardManager::Get(); - // Create session-aware cards (non-static for multi-session support) - gui::EditorCard dungeon_maps_card(MakeCardTitle("Dungeon Maps").c_str(), ICON_MD_MAP); - gui::EditorCard inventory_menu_card(MakeCardTitle("Inventory Menu").c_str(), ICON_MD_INVENTORY); - gui::EditorCard overworld_map_card(MakeCardTitle("Overworld Map").c_str(), ICON_MD_PUBLIC); - gui::EditorCard title_screen_card(MakeCardTitle("Title Screen").c_str(), ICON_MD_TITLE); - gui::EditorCard naming_screen_card(MakeCardTitle("Naming Screen").c_str(), ICON_MD_EDIT_ATTRIBUTES); + static gui::EditorCard dungeon_maps_card("Dungeon Maps", ICON_MD_MAP); + static gui::EditorCard inventory_menu_card("Inventory Menu", ICON_MD_INVENTORY); + static gui::EditorCard overworld_map_card("Overworld Map", ICON_MD_PUBLIC); + static gui::EditorCard title_screen_card("Title Screen", ICON_MD_TITLE); + static gui::EditorCard naming_screen_card("Naming Screen", ICON_MD_EDIT_ATTRIBUTES); - if (show_dungeon_maps_) { - if (dungeon_maps_card.Begin(&show_dungeon_maps_)) { - DrawDungeonMapsEditor(); - } - dungeon_maps_card.End(); // ALWAYS call End after Begin + dungeon_maps_card.SetDefaultSize(800, 600); + inventory_menu_card.SetDefaultSize(800, 600); + overworld_map_card.SetDefaultSize(600, 500); + title_screen_card.SetDefaultSize(600, 500); + naming_screen_card.SetDefaultSize(500, 400); + + // Get visibility flags from card manager and pass to Begin() + if (dungeon_maps_card.Begin(card_manager.GetVisibilityFlag("screen.dungeon_maps"))) { + DrawDungeonMapsEditor(); + dungeon_maps_card.End(); } - if (show_inventory_menu_) { - if (inventory_menu_card.Begin(&show_inventory_menu_)) { - DrawInventoryMenuEditor(); - } - inventory_menu_card.End(); // ALWAYS call End after Begin + if (inventory_menu_card.Begin(card_manager.GetVisibilityFlag("screen.inventory_menu"))) { + DrawInventoryMenuEditor(); + inventory_menu_card.End(); } - if (show_overworld_map_) { - if (overworld_map_card.Begin(&show_overworld_map_)) { - DrawOverworldMapEditor(); - } - overworld_map_card.End(); // ALWAYS call End after Begin + if (overworld_map_card.Begin(card_manager.GetVisibilityFlag("screen.overworld_map"))) { + DrawOverworldMapEditor(); + overworld_map_card.End(); } - if (show_title_screen_) { - if (title_screen_card.Begin(&show_title_screen_)) { - DrawTitleScreenEditor(); - } - title_screen_card.End(); // ALWAYS call End after Begin + if (title_screen_card.Begin(card_manager.GetVisibilityFlag("screen.title_screen"))) { + DrawTitleScreenEditor(); + title_screen_card.End(); } - if (show_naming_screen_) { - if (naming_screen_card.Begin(&show_naming_screen_)) { - DrawNamingScreenEditor(); - } - naming_screen_card.End(); // ALWAYS call End after Begin + if (naming_screen_card.Begin(card_manager.GetVisibilityFlag("screen.naming_screen"))) { + DrawNamingScreenEditor(); + naming_screen_card.End(); } return status_; diff --git a/src/app/editor/graphics/screen_editor.h b/src/app/editor/graphics/screen_editor.h index 1a6c31b7..530f47ba 100644 --- a/src/app/editor/graphics/screen_editor.h +++ b/src/app/editor/graphics/screen_editor.h @@ -78,14 +78,6 @@ class ScreenEditor : public Editor { EditingMode current_mode_ = EditingMode::DRAW; - // Card visibility - ALL FALSE by default to prevent crash on ROM load - // Cards only shown when user explicitly opens them via View menu or shortcuts - bool show_dungeon_maps_ = false; - bool show_inventory_menu_ = false; - bool show_overworld_map_ = false; - bool show_title_screen_ = false; - bool show_naming_screen_ = false; - bool binary_gfx_loaded_ = false; uint8_t selected_room = 0; diff --git a/src/app/editor/message/message_editor.cc b/src/app/editor/message/message_editor.cc index ec4861aa..025bd553 100644 --- a/src/app/editor/message/message_editor.cc +++ b/src/app/editor/message/message_editor.cc @@ -14,6 +14,7 @@ #include "app/gfx/snes_tile.h" #include "app/gui/canvas.h" #include "app/gui/style.h" +#include "app/gui/icons.h" #include "app/rom.h" #include "app/gui/input.h" #include "imgui.h" @@ -61,7 +62,7 @@ constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable; void MessageEditor::Initialize() { - // Register cards with EditorCardManager + // Register cards with EditorCardManager (using centralized visibility) auto& card_manager = gui::EditorCardManager::Get(); card_manager.RegisterCard({ @@ -69,7 +70,6 @@ void MessageEditor::Initialize() { .display_name = "Message List", .icon = ICON_MD_LIST, .category = "Message", - .visibility_flag = &show_message_list_, .priority = 10 }); @@ -78,7 +78,6 @@ void MessageEditor::Initialize() { .display_name = "Message Editor", .icon = ICON_MD_EDIT, .category = "Message", - .visibility_flag = &show_message_editor_, .priority = 20 }); @@ -87,7 +86,6 @@ void MessageEditor::Initialize() { .display_name = "Font Atlas", .icon = ICON_MD_FONT_DOWNLOAD, .category = "Message", - .visibility_flag = &show_font_atlas_, .priority = 30 }); @@ -96,10 +94,12 @@ void MessageEditor::Initialize() { .display_name = "Dictionary", .icon = ICON_MD_BOOK, .category = "Message", - .visibility_flag = &show_dictionary_, .priority = 40 }); + // Show message list by default + card_manager.ShowCard("message.message_list"); + for (int i = 0; i < kWidthArraySize; i++) { message_preview_.width_array[i] = rom()->data()[kCharactersWidth + i]; } @@ -143,30 +143,51 @@ absl::Status MessageEditor::Load() { } absl::Status MessageEditor::Update() { - if (BeginTable("##MessageEditor", 4, kMessageTableFlags)) { - TableSetupColumn("List"); - TableSetupColumn("Contents"); - TableSetupColumn("Font Atlas"); - TableSetupColumn("Commands"); - TableHeadersRow(); - - TableNextColumn(); - DrawMessageList(); - - TableNextColumn(); - DrawCurrentMessage(); - - TableNextColumn(); - DrawFontAtlas(); - DrawExpandedMessageSettings(); - - TableNextColumn(); - DrawTextCommands(); - DrawSpecialCharacters(); - DrawDictionary(); - - EndTable(); + auto& card_manager = gui::EditorCardManager::Get(); + + // Message List Card + if (card_manager.IsCardVisible("message.message_list")) { + static gui::EditorCard list_card("Message List", ICON_MD_LIST); + list_card.SetDefaultSize(400, 600); + if (list_card.Begin()) { + DrawMessageList(); + list_card.End(); + } } + + // Message Editor Card + if (card_manager.IsCardVisible("message.message_editor")) { + static gui::EditorCard editor_card("Message Editor", ICON_MD_EDIT); + editor_card.SetDefaultSize(500, 600); + if (editor_card.Begin()) { + DrawCurrentMessage(); + editor_card.End(); + } + } + + // Font Atlas Card + if (card_manager.IsCardVisible("message.font_atlas")) { + static gui::EditorCard font_card("Font Atlas", ICON_MD_FONT_DOWNLOAD); + font_card.SetDefaultSize(400, 500); + if (font_card.Begin()) { + DrawFontAtlas(); + DrawExpandedMessageSettings(); + font_card.End(); + } + } + + // Dictionary Card + if (card_manager.IsCardVisible("message.dictionary")) { + static gui::EditorCard dict_card("Dictionary", ICON_MD_BOOK); + dict_card.SetDefaultSize(400, 500); + if (dict_card.Begin()) { + DrawTextCommands(); + DrawSpecialCharacters(); + DrawDictionary(); + dict_card.End(); + } + } + return absl::OkStatus(); } diff --git a/src/app/editor/message/message_editor.h b/src/app/editor/message/message_editor.h index b5adde7f..25bdaec4 100644 --- a/src/app/editor/message/message_editor.h +++ b/src/app/editor/message/message_editor.h @@ -10,6 +10,7 @@ #include "app/editor/message/message_data.h" #include "app/editor/message/message_preview.h" #include "app/gui/editor_card_manager.h" +#include "app/gui/editor_layout.h" #include "app/gfx/bitmap.h" #include "app/gui/canvas.h" #include "app/gui/style.h" diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 36d9d985..44279d51 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -51,6 +51,17 @@ void OverworldEditor::Initialize() { // Register cards with EditorCardManager auto& card_manager = gui::EditorCardManager::Get(); + // Register Overworld Canvas (main canvas card with toolset) + card_manager.RegisterCard({ + .card_id = "overworld.canvas", + .display_name = "Overworld Canvas", + .icon = ICON_MD_MAP, + .category = "Overworld", + .shortcut_hint = "Ctrl+Shift+O", + .visibility_flag = &show_overworld_canvas_, + .priority = 5 // Show first, most important + }); + card_manager.RegisterCard({ .card_id = "overworld.tile16_selector", .display_name = "Tile16 Selector", @@ -193,11 +204,8 @@ absl::Status OverworldEditor::Update() { return status_; } - // Modern layout - no tabs, just toolbar + canvas + floating cards - DrawToolset(); - gui::VerticalSpacing(2.0f); - // Create session-aware cards (non-static for multi-session support) + gui::EditorCard overworld_canvas_card(MakeCardTitle("Overworld Canvas").c_str(), ICON_MD_PUBLIC); gui::EditorCard tile16_card(MakeCardTitle("Tile16 Selector").c_str(), ICON_MD_GRID_3X3); gui::EditorCard tile8_card(MakeCardTitle("Tile8 Selector").c_str(), ICON_MD_GRID_4X4); gui::EditorCard area_gfx_card(MakeCardTitle("Area Graphics").c_str(), ICON_MD_IMAGE); @@ -211,6 +219,9 @@ absl::Status OverworldEditor::Update() { static bool cards_configured = false; if (!cards_configured) { // Position cards for optimal workflow + overworld_canvas_card.SetDefaultSize(1000, 700); + overworld_canvas_card.SetPosition(gui::EditorCard::Position::Floating); + tile16_card.SetDefaultSize(300, 600); tile16_card.SetPosition(gui::EditorCard::Position::Right); @@ -239,7 +250,13 @@ absl::Status OverworldEditor::Update() { } // Main canvas (full width when cards are docked) - DrawOverworldCanvas(); + if (show_overworld_canvas_) { + if (overworld_canvas_card.Begin(&show_overworld_canvas_)) { + DrawToolset(); + DrawOverworldCanvas(); + } + overworld_canvas_card.End(); // ALWAYS call End after Begin + } // Floating tile selector cards (4 tabs converted to separate cards) if (show_tile16_selector_) { diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 7006cc68..dba328a5 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -296,6 +296,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { bool show_overlay_preview_ = false; // Card visibility states - Start hidden to prevent crash + bool show_overworld_canvas_ = true; bool show_tile16_selector_ = false; bool show_tile8_selector_ = false; bool show_area_gfx_ = false; diff --git a/src/app/editor/palette/palette_editor.cc b/src/app/editor/palette/palette_editor.cc index 8109ae01..5131a344 100644 --- a/src/app/editor/palette/palette_editor.cc +++ b/src/app/editor/palette/palette_editor.cc @@ -295,6 +295,9 @@ void PaletteEditor::Initialize() { .visibility_flag = &show_custom_palette_, .priority = 80 }); + + // Show control panel by default when Palette Editor is activated + show_control_panel_ = true; } absl::Status PaletteEditor::Load() { diff --git a/src/app/editor/sprite/sprite_editor.cc b/src/app/editor/sprite/sprite_editor.cc index bd27c120..582d578e 100644 --- a/src/app/editor/sprite/sprite_editor.cc +++ b/src/app/editor/sprite/sprite_editor.cc @@ -25,28 +25,17 @@ using ImGui::TableSetupColumn; using ImGui::Text; void SpriteEditor::Initialize() { - // Register cards with EditorCardManager during initialization (once) auto& card_manager = gui::EditorCardManager::Get(); - card_manager.RegisterCard({ - .card_id = "sprite.vanilla_editor", - .display_name = "Vanilla Sprites", - .icon = ICON_MD_SMART_TOY, - .category = "Sprite", - .shortcut_hint = "Alt+Shift+1", - .visibility_flag = &show_vanilla_editor_, - .priority = 10 - }); + card_manager.RegisterCard({.card_id = "sprite.vanilla_editor", .display_name = "Vanilla Sprites", + .icon = ICON_MD_SMART_TOY, .category = "Sprite", + .shortcut_hint = "Alt+Shift+1", .priority = 10}); + card_manager.RegisterCard({.card_id = "sprite.custom_editor", .display_name = "Custom Sprites", + .icon = ICON_MD_ADD_CIRCLE, .category = "Sprite", + .shortcut_hint = "Alt+Shift+2", .priority = 20}); - card_manager.RegisterCard({ - .card_id = "sprite.custom_editor", - .display_name = "Custom Sprites", - .icon = ICON_MD_ADD_CIRCLE, - .category = "Sprite", - .shortcut_hint = "Alt+Shift+2", - .visibility_flag = &show_custom_editor_, - .priority = 20 - }); + // Show vanilla editor by default + card_manager.ShowCard("sprite.vanilla_editor"); } absl::Status SpriteEditor::Load() { @@ -56,29 +45,26 @@ absl::Status SpriteEditor::Load() { absl::Status SpriteEditor::Update() { if (rom()->is_loaded() && !sheets_loaded_) { - // Load the values for current_sheets_ array sheets_loaded_ = true; } - DrawToolset(); - gui::VerticalSpacing(2.0f); + auto& card_manager = gui::EditorCardManager::Get(); - // Create session-aware cards (non-static for multi-session support) - gui::EditorCard vanilla_card(MakeCardTitle("Vanilla Sprites").c_str(), ICON_MD_PEST_CONTROL_RODENT); - gui::EditorCard custom_card(MakeCardTitle("Custom Sprites").c_str(), ICON_MD_ADD_MODERATOR); + static gui::EditorCard vanilla_card("Vanilla Sprites", ICON_MD_SMART_TOY); + static gui::EditorCard custom_card("Custom Sprites", ICON_MD_ADD_CIRCLE); - if (show_vanilla_editor_) { - if (vanilla_card.Begin(&show_vanilla_editor_)) { - DrawVanillaSpriteEditor(); - } - vanilla_card.End(); // ALWAYS call End after Begin + vanilla_card.SetDefaultSize(900, 700); + custom_card.SetDefaultSize(800, 600); + + // Get visibility flags from card manager and pass to Begin() + if (vanilla_card.Begin(card_manager.GetVisibilityFlag("sprite.vanilla_editor"))) { + DrawVanillaSpriteEditor(); + vanilla_card.End(); } - if (show_custom_editor_) { - if (custom_card.Begin(&show_custom_editor_)) { - DrawCustomSprites(); - } - custom_card.End(); // ALWAYS call End after Begin + if (custom_card.Begin(card_manager.GetVisibilityFlag("sprite.custom_editor"))) { + DrawCustomSprites(); + custom_card.End(); } return status_.ok() ? absl::OkStatus() : status_; diff --git a/src/app/editor/sprite/sprite_editor.h b/src/app/editor/sprite/sprite_editor.h index 51ae6696..632726bf 100644 --- a/src/app/editor/sprite/sprite_editor.h +++ b/src/app/editor/sprite/sprite_editor.h @@ -37,8 +37,8 @@ constexpr ImGuiTableFlags kSpriteTableFlags = */ class SpriteEditor : public Editor { public: - explicit SpriteEditor(Rom* rom = nullptr) : rom_(rom) { - type_ = EditorType::kSprite; + explicit SpriteEditor(Rom* rom = nullptr) : rom_(rom) { + type_ = EditorType::kSprite; } void Initialize() override; @@ -51,10 +51,10 @@ class SpriteEditor : public Editor { absl::Status Paste() override { return absl::UnimplementedError("Paste"); } absl::Status Find() override { return absl::UnimplementedError("Find"); } absl::Status Save() override { return absl::UnimplementedError("Save"); } - + // Set the ROM pointer void set_rom(Rom* rom) { rom_ = rom; } - + // Get the ROM pointer Rom* rom() const { return rom_; } @@ -84,11 +84,6 @@ class SpriteEditor : public Editor { void DrawAnimationFrames(); void DrawToolset(); - // Card visibility - ALL FALSE by default to prevent crash on ROM load - // Cards only shown when user explicitly opens them via View menu or shortcuts - bool show_vanilla_editor_ = false; - bool show_custom_editor_ = false; - ImVector active_sprites_; /**< Active sprites. */ int current_sprite_id_; /**< Current sprite ID. */ diff --git a/src/app/editor/system/shortcut_manager.cc b/src/app/editor/system/shortcut_manager.cc index daf18bc4..afb8ba56 100644 --- a/src/app/editor/system/shortcut_manager.cc +++ b/src/app/editor/system/shortcut_manager.cc @@ -140,25 +140,37 @@ std::vector ParseShortcut(const std::string& shortcut) { void ExecuteShortcuts(const ShortcutManager& shortcut_manager) { // Check for keyboard shortcuts using the shortcut manager for (const auto& shortcut : shortcut_manager.GetShortcuts()) { - bool keys_pressed = true; - // Check for all the keys in the shortcut + bool modifiers_held = true; + bool key_pressed = false; + ImGuiKey main_key = ImGuiKey_None; + + // Separate modifiers from main key for (const auto& key : shortcut.second.keys) { - if (key == ImGuiMod_Ctrl) { - keys_pressed &= ImGui::GetIO().KeyCtrl; - } else if (key == ImGuiMod_Alt) { - keys_pressed &= ImGui::GetIO().KeyAlt; - } else if (key == ImGuiMod_Shift) { - keys_pressed &= ImGui::GetIO().KeyShift; - } else if (key == ImGuiMod_Super) { - keys_pressed &= ImGui::GetIO().KeySuper; + if (key == ImGuiMod_Ctrl || key == ImGuiMod_Alt || + key == ImGuiMod_Shift || key == ImGuiMod_Super) { + // Check if modifier is held + if (key == ImGuiMod_Ctrl) { + modifiers_held &= ImGui::GetIO().KeyCtrl; + } else if (key == ImGuiMod_Alt) { + modifiers_held &= ImGui::GetIO().KeyAlt; + } else if (key == ImGuiMod_Shift) { + modifiers_held &= ImGui::GetIO().KeyShift; + } else if (key == ImGuiMod_Super) { + modifiers_held &= ImGui::GetIO().KeySuper; + } } else { - keys_pressed &= ImGui::IsKeyDown(key); - } - if (!keys_pressed) { - break; + // This is the main key - use IsKeyPressed for single trigger + main_key = key; } } - if (keys_pressed) { + + // Check if main key was pressed (not just held) + if (main_key != ImGuiKey_None) { + key_pressed = ImGui::IsKeyPressed(main_key, false); // false = no repeat + } + + // Execute if modifiers held and key pressed + if (modifiers_held && key_pressed && shortcut.second.callback) { shortcut.second.callback(); } } diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index d8a81e6a..c22784d9 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -49,46 +49,7 @@ void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector& renderer_ = renderer; rom_data_ = rom_data; - // Register emulator cards with EditorCardManager - auto& card_manager = gui::EditorCardManager::Get(); - - card_manager.RegisterCard({ - .card_id = "emulator.cpu_debugger", - .display_name = "CPU Debugger", - .icon = ICON_MD_BUG_REPORT, - .category = "Emulator", - .visibility_flag = &show_cpu_debugger_, - .priority = 10 - }); - - card_manager.RegisterCard({ - .card_id = "emulator.memory_viewer", - .display_name = "Memory Viewer", - .icon = ICON_MD_MEMORY, - .category = "Emulator", - .visibility_flag = &show_memory_viewer_, - .priority = 20 - }); - - card_manager.RegisterCard({ - .card_id = "emulator.ppu_viewer", - .display_name = "PPU Viewer", - .icon = ICON_MD_GRID_VIEW, - .category = "Emulator", - .visibility_flag = &show_ppu_viewer_, - .priority = 30 - }); - - card_manager.RegisterCard({ - .card_id = "emulator.audio_mixer", - .display_name = "Audio Mixer", - .icon = ICON_MD_AUDIO_FILE, - .category = "Emulator", - .visibility_flag = &show_audio_mixer_, - .priority = 40 - }); - - printf("[Emulator] Registered 4 cards with EditorCardManager\n"); + // Cards are registered in EditorManager::Initialize() to avoid duplication // Reset state for new ROM running_ = false; @@ -211,7 +172,6 @@ void Emulator::Run(Rom* rom) { running_ = true; } - RenderNavBar(); // Auto-pause emulator during window resize to prevent crashes // MODERN APPROACH: Only pause on actual window resize, not focus loss @@ -345,142 +305,75 @@ void Emulator::Run(Rom* rom) { } void Emulator::RenderEmulatorInterface() { - // Apply modern theming with safety checks try { - auto& theme_manager = gui::ThemeManager::Get(); - const auto& theme = theme_manager.GetCurrentTheme(); + auto& card_manager = gui::EditorCardManager::Get(); - // Modern EditorCard-based layout - modular and flexible - static bool show_cpu_debugger_ = true; - static bool show_ppu_display_ = true; - static bool show_memory_viewer_ = false; - static bool show_breakpoints_ = false; - static bool show_performance_ = true; - static bool show_ai_agent_ = false; - static bool show_save_states_ = false; - static bool show_keyboard_config_ = false; - static bool show_apu_debugger_ = true; + static gui::EditorCard cpu_card("CPU Debugger", ICON_MD_MEMORY); + static gui::EditorCard ppu_card("PPU Viewer", ICON_MD_VIDEOGAME_ASSET); + static gui::EditorCard memory_card("Memory Viewer", ICON_MD_MEMORY); + static gui::EditorCard breakpoints_card("Breakpoints", ICON_MD_STOP); + static gui::EditorCard performance_card("Performance", ICON_MD_SPEED); + static gui::EditorCard ai_card("AI Agent", ICON_MD_SMART_TOY); + static gui::EditorCard save_states_card("Save States", ICON_MD_SAVE); + static gui::EditorCard keyboard_card("Keyboard Config", ICON_MD_KEYBOARD); + static gui::EditorCard apu_card("APU Debugger", ICON_MD_MUSIC_NOTE); + static gui::EditorCard audio_card("Audio Mixer", ICON_MD_AUDIO_FILE); - // Create session-aware cards - static gui::EditorCard cpu_card(ICON_MD_MEMORY " CPU Debugger", ICON_MD_MEMORY); - static gui::EditorCard ppu_card(ICON_MD_VIDEOGAME_ASSET " PPU Display", - ICON_MD_VIDEOGAME_ASSET); - static gui::EditorCard memory_card(ICON_MD_DATA_ARRAY " Memory Viewer", - ICON_MD_DATA_ARRAY); - static gui::EditorCard breakpoints_card(ICON_MD_BUG_REPORT " Breakpoints", - ICON_MD_BUG_REPORT); - static gui::EditorCard performance_card(ICON_MD_SPEED " Performance", - ICON_MD_SPEED); - static gui::EditorCard ai_card(ICON_MD_SMART_TOY " AI Agent", ICON_MD_SMART_TOY); - static gui::EditorCard save_states_card(ICON_MD_SAVE " Save States", ICON_MD_SAVE); - static gui::EditorCard keyboard_card(ICON_MD_KEYBOARD " Keyboard Config", - ICON_MD_KEYBOARD); - static gui::EditorCard apu_debug_card(ICON_MD_MUSIC_NOTE " APU Debugger", - ICON_MD_MUSIC_NOTE); + cpu_card.SetDefaultSize(400, 500); + ppu_card.SetDefaultSize(550, 520); + memory_card.SetDefaultSize(800, 600); + breakpoints_card.SetDefaultSize(400, 350); + performance_card.SetDefaultSize(350, 300); - // Configure default positions - static bool cards_configured = false; - if (!cards_configured) { - cpu_card.SetDefaultSize(400, 500); - cpu_card.SetPosition(gui::EditorCard::Position::Right); - - ppu_card.SetDefaultSize(550, 520); - ppu_card.SetPosition(gui::EditorCard::Position::Floating); - - memory_card.SetDefaultSize(800, 600); - memory_card.SetPosition(gui::EditorCard::Position::Floating); - - breakpoints_card.SetDefaultSize(400, 350); - breakpoints_card.SetPosition(gui::EditorCard::Position::Right); - - performance_card.SetDefaultSize(350, 300); - performance_card.SetPosition(gui::EditorCard::Position::Bottom); - - ai_card.SetDefaultSize(500, 450); - ai_card.SetPosition(gui::EditorCard::Position::Floating); - - save_states_card.SetDefaultSize(400, 300); - save_states_card.SetPosition(gui::EditorCard::Position::Floating); - - keyboard_card.SetDefaultSize(450, 400); - keyboard_card.SetPosition(gui::EditorCard::Position::Floating); - - apu_debug_card.SetDefaultSize(500, 400); - apu_debug_card.SetPosition(gui::EditorCard::Position::Floating); - - cards_configured = true; - } - - // CPU Debugger Card - if (show_cpu_debugger_) { - if (cpu_card.Begin(&show_cpu_debugger_)) { - RenderModernCpuDebugger(); - } + if (card_manager.IsCardVisible("emulator.cpu_debugger") && cpu_card.Begin()) { + RenderModernCpuDebugger(); cpu_card.End(); } - // PPU Display Card - if (show_ppu_display_) { - if (ppu_card.Begin(&show_ppu_display_)) { - RenderSnesPpu(); - } + if (card_manager.IsCardVisible("emulator.ppu_viewer") && ppu_card.Begin()) { + RenderNavBar(); + RenderSnesPpu(); ppu_card.End(); } - // Memory Viewer Card - if (show_memory_viewer_) { - if (memory_card.Begin(&show_memory_viewer_)) { - RenderMemoryViewer(); - } + if (card_manager.IsCardVisible("emulator.memory_viewer") && memory_card.Begin()) { + RenderMemoryViewer(); memory_card.End(); } - // Breakpoints Card - if (show_breakpoints_) { - if (breakpoints_card.Begin(&show_breakpoints_)) { - RenderBreakpointList(); - } + if (card_manager.IsCardVisible("emulator.breakpoints") && breakpoints_card.Begin()) { + RenderBreakpointList(); breakpoints_card.End(); } - // Performance Monitor Card - if (show_performance_) { - if (performance_card.Begin(&show_performance_)) { - RenderPerformanceMonitor(); - } + if (card_manager.IsCardVisible("emulator.performance") && performance_card.Begin()) { + RenderPerformanceMonitor(); performance_card.End(); } - // AI Agent Card - if (show_ai_agent_) { - if (ai_card.Begin(&show_ai_agent_)) { - RenderAIAgentPanel(); - } + if (card_manager.IsCardVisible("emulator.ai_agent") && ai_card.Begin()) { + RenderAIAgentPanel(); ai_card.End(); } - // Save States Card - if (show_save_states_) { - if (save_states_card.Begin(&show_save_states_)) { - RenderSaveStates(); - } + if (card_manager.IsCardVisible("emulator.save_states") && save_states_card.Begin()) { + RenderSaveStates(); save_states_card.End(); } - // Keyboard Configuration Card - if (show_keyboard_config_) { - if (keyboard_card.Begin(&show_keyboard_config_)) { - RenderKeyboardConfig(); - } + if (card_manager.IsCardVisible("emulator.keyboard_config") && keyboard_card.Begin()) { + RenderKeyboardConfig(); keyboard_card.End(); } - // APU Debugger Card - if (show_apu_debugger_) { - if (apu_debug_card.Begin(&show_apu_debugger_)) { - RenderApuDebugger(); - } - apu_debug_card.End(); + if (card_manager.IsCardVisible("emulator.apu_debugger") && apu_card.Begin()) { + RenderApuDebugger(); + apu_card.End(); + } + + if (card_manager.IsCardVisible("emulator.audio_mixer") && audio_card.Begin()) { + // RenderAudioMixer(); + audio_card.End(); } } catch (const std::exception& e) { diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index fe3e4fd2..85e40ee1 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -38,11 +38,7 @@ class Emulator { void Run(Rom* rom); void Cleanup(); - // Card visibility for emulator UI panels - bool& show_cpu_debugger() { return show_cpu_debugger_; } - bool& show_memory_viewer() { return show_memory_viewer_; } - bool& show_ppu_viewer() { return show_ppu_viewer_; } - bool& show_audio_mixer() { return show_audio_mixer_; } + // Card visibility managed by EditorCardManager auto snes() -> Snes& { return snes_; } auto running() const -> bool { return running_; } @@ -163,11 +159,7 @@ class Emulator { gfx::IRenderer* renderer_ = nullptr; void* ppu_texture_ = nullptr; - // Card visibility states - bool show_cpu_debugger_ = false; - bool show_memory_viewer_ = false; - bool show_ppu_viewer_ = false; - bool show_audio_mixer_ = false; + // Card visibility managed by EditorCardManager - no member variables needed! // Debugger infrastructure BreakpointManager breakpoint_manager_; diff --git a/src/app/gui/editor_card_manager.cc b/src/app/gui/editor_card_manager.cc index 2fc68d00..91b7de17 100644 --- a/src/app/gui/editor_card_manager.cc +++ b/src/app/gui/editor_card_manager.cc @@ -18,20 +18,23 @@ EditorCardManager& EditorCardManager::Get() { void EditorCardManager::RegisterCard(const CardInfo& info) { if (info.card_id.empty()) { - printf("[EditorCardManager] Warning: Attempted to register card with empty ID\n"); return; } // Check if already registered to avoid duplicates if (cards_.find(info.card_id) != cards_.end()) { - printf("[EditorCardManager] WARNING: Card '%s' already registered, skipping duplicate\n", - info.card_id.c_str()); return; } - cards_[info.card_id] = info; - printf("[EditorCardManager] Registered card: %s (%s)\n", - info.card_id.c_str(), info.display_name.c_str()); + CardInfo new_info = info; + + // If no visibility_flag provided, create centralized one + if (!new_info.visibility_flag) { + centralized_visibility_[info.card_id] = false; // Hidden by default + new_info.visibility_flag = ¢ralized_visibility_[info.card_id]; + } + + cards_[info.card_id] = new_info; } void EditorCardManager::RegisterCard(const std::string& card_id, @@ -161,6 +164,14 @@ bool EditorCardManager::IsCardVisible(const std::string& card_id) const { return false; } +bool* EditorCardManager::GetVisibilityFlag(const std::string& card_id) { + auto it = cards_.find(card_id); + if (it != cards_.end()) { + return it->second.visibility_flag; + } + return nullptr; +} + void EditorCardManager::ShowAllCardsInCategory(const std::string& category) { for (auto& [id, info] : cards_) { if (info.category == category && info.visibility_flag) { @@ -716,13 +727,13 @@ void EditorCardManager::LoadPresetsFromFile() { } 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()); - } + active_category_ = category; } -void EditorCardManager::DrawSidebar(const std::string& category) { +void EditorCardManager::DrawSidebar(const std::string& category, + const std::vector& active_categories, + std::function on_category_switch, + std::function on_collapse) { // Use ThemeManager for consistent theming const auto& theme = ThemeManager::Get().GetCurrentTheme(); @@ -737,58 +748,80 @@ void EditorCardManager::DrawSidebar(const std::string& category) { ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoDocking | // Don't allow docking over sidebar ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoBringToFrontOnFocus; + ImGuiWindowFlags_NoFocusOnAppearing | // Don't steal focus + ImGuiWindowFlags_NoNavFocus; // Don't participate in nav - // Make sidebar more opaque and visible - ImVec4 sidebar_bg = ConvertColorToImVec4(theme.child_bg); - sidebar_bg.w = 1.0f; // Full opacity + // Make sidebar VERY visible - fully opaque dark background + ImVec4 sidebar_bg = ImVec4(0.18f, 0.18f, 0.20f, 1.0f); // Dark opaque gray + ImVec4 sidebar_border = ImVec4(0.4f, 0.4f, 0.45f, 1.0f); // Visible border ImGui::PushStyleColor(ImGuiCol_WindowBg, sidebar_bg); - ImGui::PushStyleColor(ImGuiCol_Border, ConvertColorToImVec4(theme.border)); + ImGui::PushStyleColor(ImGuiCol_Border, sidebar_border); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 8.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); // Thicker border if (ImGui::Begin("##EditorCardSidebar", nullptr, sidebar_flags)) { + // Category switcher buttons at top (if multiple editors active) + if (active_categories.size() > 1) { + ImVec4 accent = ConvertColorToImVec4(theme.accent); + ImVec4 inactive = ConvertColorToImVec4(theme.button); + + for (const auto& cat : active_categories) { + bool is_current = (cat == category); + + if (is_current) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(accent.x, accent.y, accent.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(accent.x, accent.y, accent.z, 1.0f)); + } else { + ImGui::PushStyleColor(ImGuiCol_Button, inactive); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered)); + } + + // Show first letter of category + std::string btn_label = cat.empty() ? "?" : std::string(1, cat[0]); + if (ImGui::Button(btn_label.c_str(), ImVec2(40.0f, 32.0f))) { + // Call callback to switch editor, not just set category + if (on_category_switch) { + on_category_switch(cat); + } else { + SetActiveCategory(cat); + } + } + + ImGui::PopStyleColor(2); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s Editor\nClick to switch", cat.c_str()); + } + } + + ImGui::Dummy(ImVec2(0, 2.0f)); + ImGui::Separator(); + ImGui::Spacing(); + } + // Get cards for this category auto cards = GetCardsInCategory(category); - // If no cards available, show editor selection instead - if (cards.empty()) { - ImGui::PushStyleColor(ImGuiCol_Text, ConvertColorToImVec4(theme.text_disabled)); - ImGui::TextWrapped("No cards"); - ImGui::PopStyleColor(); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Show editor selection buttons - ImGui::TextWrapped("Select Editor:"); - ImGui::Dummy(ImVec2(0, 4.0f)); - - // TODO: Add editor selection buttons here - // For now, just show message - ImGui::PushStyleColor(ImGuiCol_Text, ConvertColorToImVec4(theme.text_secondary)); - ImGui::TextWrapped("Open an editor to see cards"); - ImGui::PopStyleColor(); - } else { - // Set this category as active when cards are present + // Set this category as active when showing cards + if (!cards.empty()) { SetActiveCategory(category); } - // Close All button at top (only if cards exist) + // Close All button (only if cards exist) if (!cards.empty()) { 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)); + error_color.x * 0.6f, error_color.y * 0.6f, error_color.z * 0.6f, 0.9f)); 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))) { + if (ImGui::Button(ICON_MD_CLOSE, ImVec2(40.0f, 36.0f))) { HideAllCardsInCategory(category); } @@ -798,7 +831,25 @@ void EditorCardManager::DrawSidebar(const std::string& category) { ImGui::SetTooltip("Close All %s Cards", category.c_str()); } - ImGui::Dummy(ImVec2(0, 4.0f)); + // Show All button + ImVec4 success_color = ConvertColorToImVec4(theme.success); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4( + success_color.x * 0.6f, success_color.y * 0.6f, success_color.z * 0.6f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, success_color); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4( + success_color.x * 1.2f, success_color.y * 1.2f, success_color.z * 1.2f, 1.0f)); + + if (ImGui::Button(ICON_MD_DONE_ALL, ImVec2(40.0f, 36.0f))) { + ShowAllCardsInCategory(category); + } + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show All %s Cards", category.c_str()); + } + + ImGui::Dummy(ImVec2(0, 2.0f)); // Draw card buttons ImVec4 accent_color = ConvertColorToImVec4(theme.accent); @@ -838,6 +889,27 @@ void EditorCardManager::DrawSidebar(const std::string& category) { ImGui::PopID(); } } // End if (!cards.empty()) + + // Collapse sidebar button at bottom + if (on_collapse) { + ImGui::Dummy(ImVec2(0, 10.0f)); // Add some space + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.22f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.32f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.25f, 0.25f, 0.27f, 1.0f)); + + if (ImGui::Button(ICON_MD_KEYBOARD_ARROW_LEFT, ImVec2(40.0f, 36.0f))) { + on_collapse(); + } + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hide Sidebar\nCtrl+B"); + } + } } ImGui::End(); diff --git a/src/app/gui/editor_card_manager.h b/src/app/gui/editor_card_manager.h index fd0dab7a..7d20862b 100644 --- a/src/app/gui/editor_card_manager.h +++ b/src/app/gui/editor_card_manager.h @@ -96,6 +96,9 @@ class EditorCardManager { bool ToggleCard(const std::string& card_id); bool IsCardVisible(const std::string& card_id) const; + // Get visibility flag pointer (for passing to EditorCard::Begin) + bool* GetVisibilityFlag(const std::string& card_id); + // Batch operations void ShowAllCardsInCategory(const std::string& category); void HideAllCardsInCategory(const std::string& category); @@ -111,7 +114,10 @@ class EditorCardManager { void DrawViewMenuAll(); // Draw all categories as submenus // VSCode-style sidebar (replaces Toolset) - void DrawSidebar(const std::string& category); // Icon-only sidebar for category + void DrawSidebar(const std::string& category, + const std::vector& active_categories = {}, + std::function on_category_switch = nullptr, + std::function on_collapse = nullptr); static constexpr float GetSidebarWidth() { return 48.0f; } // Active editor tracking (based on most recently interacted card) diff --git a/src/app/gui/editor_layout.cc b/src/app/gui/editor_layout.cc index 9453e5db..cf6be6ce 100644 --- a/src/app/gui/editor_layout.cc +++ b/src/app/gui/editor_layout.cc @@ -248,6 +248,11 @@ void EditorCard::SetPosition(Position pos) { } bool EditorCard::Begin(bool* p_open) { + // Check visibility flag first - if provided and false, don't show the card + if (p_open && !*p_open) { + return false; + } + // Handle icon-collapsed state if (icon_collapsible_ && collapsed_to_icon_) { DrawFloatingIconButton();