From b8ccc2a6cd3c5e4d8947eabd08c0c81a320d2e55 Mon Sep 17 00:00:00 2001 From: scawful Date: Wed, 15 Oct 2025 14:55:49 -0400 Subject: [PATCH] refactor(editor): improve card visibility checks across various editors - Updated multiple editor components to check visibility flags before rendering cards, ensuring that only visible cards are displayed. - Refactored card rendering logic in GraphicsEditor, ScreenEditor, MessageEditor, MusicEditor, SpriteEditor, and Emulator to enhance user experience and performance. - Improved maintainability by centralizing visibility checks and ensuring consistent behavior across different editor types. Benefits: - Streamlines the rendering process, leading to a more efficient UI experience. - Enhances code clarity and maintainability by standardizing visibility handling across editors. --- src/app/editor/editor_manager.cc | 4 + src/app/editor/graphics/graphics_editor.cc | 50 ++-- src/app/editor/graphics/screen_editor.cc | 52 ++-- src/app/editor/message/message_editor.cc | 28 ++- src/app/editor/music/music_editor.cc | 33 ++- src/app/editor/sprite/sprite_editor.cc | 22 +- src/app/editor/system/editor_card_registry.cc | 223 ++++++++++++------ src/app/editor/system/settings_editor.cc | 42 ++-- src/app/editor/ui/ui_coordinator.cc | 22 +- src/app/emu/emulator.cc | 74 ++++-- 10 files changed, 361 insertions(+), 189 deletions(-) diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 9c227eb5..6048166c 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -1423,6 +1423,8 @@ absl::Status EditorManager::LoadAssets() { emulator_.set_renderer(renderer_); } + // Initialize all editors - this registers their cards with EditorCardRegistry + // and sets up any editor-specific resources. Must be called before Load(). current_editor_set_->overworld_editor_.Initialize(); current_editor_set_->message_editor_.Initialize(); current_editor_set_->graphics_editor_.Initialize(); @@ -1430,6 +1432,8 @@ absl::Status EditorManager::LoadAssets() { current_editor_set_->sprite_editor_.Initialize(); current_editor_set_->palette_editor_.Initialize(); current_editor_set_->assembly_editor_.Initialize(); + current_editor_set_->music_editor_.Initialize(); + current_editor_set_->settings_editor_.Initialize(); // Initialize settings editor to register System cards // 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(), diff --git a/src/app/editor/graphics/graphics_editor.cc b/src/app/editor/graphics/graphics_editor.cc index ecb7819e..f1fd45f8 100644 --- a/src/app/editor/graphics/graphics_editor.cc +++ b/src/app/editor/graphics/graphics_editor.cc @@ -114,30 +114,44 @@ absl::Status GraphicsEditor::Update() { player_anims_card.SetDefaultSize(500, 600); prototype_card.SetDefaultSize(600, 500); - // Get visibility flags from card manager and pass to Begin() - // Always call End() after Begin() - End() handles ImGui state safely - if (sheet_editor_card.Begin(card_registry->GetVisibilityFlag("graphics.sheet_editor"))) { - status_ = UpdateGfxEdit(); - } - sheet_editor_card.End(); - - if (sheet_browser_card.Begin(card_registry->GetVisibilityFlag("graphics.sheet_browser"))) { - if (asset_browser_.Initialized == false) { - asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets()); + // Sheet Editor Card - Check visibility flag exists and is true before rendering + bool* sheet_editor_visible = card_registry->GetVisibilityFlag("graphics.sheet_editor"); + if (sheet_editor_visible && *sheet_editor_visible) { + if (sheet_editor_card.Begin(sheet_editor_visible)) { + status_ = UpdateGfxEdit(); } - asset_browser_.Draw(gfx::Arena::Get().gfx_sheets()); + sheet_editor_card.End(); } - sheet_browser_card.End(); - if (player_anims_card.Begin(card_registry->GetVisibilityFlag("graphics.player_animations"))) { - status_ = UpdateLinkGfxView(); + // Sheet Browser Card - Check visibility flag exists and is true before rendering + bool* sheet_browser_visible = card_registry->GetVisibilityFlag("graphics.sheet_browser"); + if (sheet_browser_visible && *sheet_browser_visible) { + if (sheet_browser_card.Begin(sheet_browser_visible)) { + if (asset_browser_.Initialized == false) { + asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets()); + } + asset_browser_.Draw(gfx::Arena::Get().gfx_sheets()); + } + sheet_browser_card.End(); } - player_anims_card.End(); - if (prototype_card.Begin(card_registry->GetVisibilityFlag("graphics.prototype_viewer"))) { - status_ = UpdateScadView(); + // Player Animations Card - Check visibility flag exists and is true before rendering + bool* player_anims_visible = card_registry->GetVisibilityFlag("graphics.player_animations"); + if (player_anims_visible && *player_anims_visible) { + if (player_anims_card.Begin(player_anims_visible)) { + status_ = UpdateLinkGfxView(); + } + player_anims_card.End(); + } + + // Prototype Viewer Card - Check visibility flag exists and is true before rendering + bool* prototype_visible = card_registry->GetVisibilityFlag("graphics.prototype_viewer"); + if (prototype_visible && *prototype_visible) { + if (prototype_card.Begin(prototype_visible)) { + status_ = UpdateScadView(); + } + prototype_card.End(); } - prototype_card.End(); CLEAR_AND_RETURN_STATUS(status_) return absl::OkStatus(); diff --git a/src/app/editor/graphics/screen_editor.cc b/src/app/editor/graphics/screen_editor.cc index 63d9e944..b36ece05 100644 --- a/src/app/editor/graphics/screen_editor.cc +++ b/src/app/editor/graphics/screen_editor.cc @@ -122,32 +122,50 @@ absl::Status ScreenEditor::Update() { title_screen_card.SetDefaultSize(600, 500); naming_screen_card.SetDefaultSize(500, 400); - // Get visibility flags from card manager and pass to Begin() - // Always call End() after Begin() - End() handles ImGui state safely - if (dungeon_maps_card.Begin(card_registry->GetVisibilityFlag("screen.dungeon_maps"))) { - DrawDungeonMapsEditor(); + // Dungeon Maps Card - Check visibility flag exists and is true before rendering + bool* dungeon_maps_visible = card_registry->GetVisibilityFlag("screen.dungeon_maps"); + if (dungeon_maps_visible && *dungeon_maps_visible) { + if (dungeon_maps_card.Begin(dungeon_maps_visible)) { + DrawDungeonMapsEditor(); + } + dungeon_maps_card.End(); } - dungeon_maps_card.End(); - if (inventory_menu_card.Begin(card_registry->GetVisibilityFlag("screen.inventory_menu"))) { - DrawInventoryMenuEditor(); + // Inventory Menu Card - Check visibility flag exists and is true before rendering + bool* inventory_menu_visible = card_registry->GetVisibilityFlag("screen.inventory_menu"); + if (inventory_menu_visible && *inventory_menu_visible) { + if (inventory_menu_card.Begin(inventory_menu_visible)) { + DrawInventoryMenuEditor(); + } + inventory_menu_card.End(); } - inventory_menu_card.End(); - if (overworld_map_card.Begin(card_registry->GetVisibilityFlag("screen.overworld_map"))) { - DrawOverworldMapEditor(); + // Overworld Map Card - Check visibility flag exists and is true before rendering + bool* overworld_map_visible = card_registry->GetVisibilityFlag("screen.overworld_map"); + if (overworld_map_visible && *overworld_map_visible) { + if (overworld_map_card.Begin(overworld_map_visible)) { + DrawOverworldMapEditor(); + } + overworld_map_card.End(); } - overworld_map_card.End(); - if (title_screen_card.Begin(card_registry->GetVisibilityFlag("screen.title_screen"))) { - DrawTitleScreenEditor(); + // Title Screen Card - Check visibility flag exists and is true before rendering + bool* title_screen_visible = card_registry->GetVisibilityFlag("screen.title_screen"); + if (title_screen_visible && *title_screen_visible) { + if (title_screen_card.Begin(title_screen_visible)) { + DrawTitleScreenEditor(); + } + title_screen_card.End(); } - title_screen_card.End(); - if (naming_screen_card.Begin(card_registry->GetVisibilityFlag("screen.naming_screen"))) { - DrawNamingScreenEditor(); + // Naming Screen Card - Check visibility flag exists and is true before rendering + bool* naming_screen_visible = card_registry->GetVisibilityFlag("screen.naming_screen"); + if (naming_screen_visible && *naming_screen_visible) { + if (naming_screen_card.Begin(naming_screen_visible)) { + DrawNamingScreenEditor(); + } + naming_screen_card.End(); } - naming_screen_card.End(); return status_; } diff --git a/src/app/editor/message/message_editor.cc b/src/app/editor/message/message_editor.cc index 199611b6..03cea448 100644 --- a/src/app/editor/message/message_editor.cc +++ b/src/app/editor/message/message_editor.cc @@ -150,42 +150,46 @@ absl::Status MessageEditor::Update() { auto* card_registry = dependencies_.card_registry; - // Message List Card - if (card_registry->IsCardVisible(MakeCardId("message.message_list"))) { + // Message List Card - Get visibility flag and pass to Begin() for proper X button + bool* list_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_list")); + if (list_visible && *list_visible) { static gui::EditorCard list_card("Message List", ICON_MD_LIST); list_card.SetDefaultSize(400, 600); - if (list_card.Begin()) { + if (list_card.Begin(list_visible)) { DrawMessageList(); } list_card.End(); } - // Message Editor Card - if (card_registry->IsCardVisible(MakeCardId("message.message_editor"))) { + // Message Editor Card - Get visibility flag and pass to Begin() for proper X button + bool* editor_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_editor")); + if (editor_visible && *editor_visible) { static gui::EditorCard editor_card("Message Editor", ICON_MD_EDIT); editor_card.SetDefaultSize(500, 600); - if (editor_card.Begin()) { + if (editor_card.Begin(editor_visible)) { DrawCurrentMessage(); } editor_card.End(); } - // Font Atlas Card - if (card_registry->IsCardVisible(MakeCardId("message.font_atlas"))) { + // Font Atlas Card - Get visibility flag and pass to Begin() for proper X button + bool* font_visible = card_registry->GetVisibilityFlag(MakeCardId("message.font_atlas")); + if (font_visible && *font_visible) { static gui::EditorCard font_card("Font Atlas", ICON_MD_FONT_DOWNLOAD); font_card.SetDefaultSize(400, 500); - if (font_card.Begin()) { + if (font_card.Begin(font_visible)) { DrawFontAtlas(); DrawExpandedMessageSettings(); } font_card.End(); } - // Dictionary Card - if (card_registry->IsCardVisible(MakeCardId("message.dictionary"))) { + // Dictionary Card - Get visibility flag and pass to Begin() for proper X button + bool* dict_visible = card_registry->GetVisibilityFlag(MakeCardId("message.dictionary")); + if (dict_visible && *dict_visible) { static gui::EditorCard dict_card("Dictionary", ICON_MD_BOOK); dict_card.SetDefaultSize(400, 500); - if (dict_card.Begin()) { + if (dict_card.Begin(dict_visible)) { DrawTextCommands(); DrawSpecialCharacters(); DrawDictionary(); diff --git a/src/app/editor/music/music_editor.cc b/src/app/editor/music/music_editor.cc index 61909074..c41e943f 100644 --- a/src/app/editor/music/music_editor.cc +++ b/src/app/editor/music/music_editor.cc @@ -48,23 +48,32 @@ absl::Status MusicEditor::Update() { instrument_card.SetDefaultSize(600, 500); assembly_card.SetDefaultSize(700, 600); - // Music Tracker Card - if (tracker_card.Begin(card_registry->GetVisibilityFlag("music.tracker"))) { - DrawTrackerView(); + // Music Tracker Card - Check visibility flag exists and is true before rendering + bool* tracker_visible = card_registry->GetVisibilityFlag("music.tracker"); + if (tracker_visible && *tracker_visible) { + if (tracker_card.Begin(tracker_visible)) { + DrawTrackerView(); + } + tracker_card.End(); } - tracker_card.End(); - // Instrument Editor Card - if (instrument_card.Begin(card_registry->GetVisibilityFlag("music.instrument_editor"))) { - DrawInstrumentEditor(); + // Instrument Editor Card - Check visibility flag exists and is true before rendering + bool* instrument_visible = card_registry->GetVisibilityFlag("music.instrument_editor"); + if (instrument_visible && *instrument_visible) { + if (instrument_card.Begin(instrument_visible)) { + DrawInstrumentEditor(); + } + instrument_card.End(); } - instrument_card.End(); - // Assembly View Card - if (assembly_card.Begin(card_registry->GetVisibilityFlag("music.assembly"))) { - assembly_editor_.InlineUpdate(); + // Assembly View Card - Check visibility flag exists and is true before rendering + bool* assembly_visible = card_registry->GetVisibilityFlag("music.assembly"); + if (assembly_visible && *assembly_visible) { + if (assembly_card.Begin(assembly_visible)) { + assembly_editor_.InlineUpdate(); + } + assembly_card.End(); } - assembly_card.End(); return absl::OkStatus(); } diff --git a/src/app/editor/sprite/sprite_editor.cc b/src/app/editor/sprite/sprite_editor.cc index 67b0a3ef..3288a245 100644 --- a/src/app/editor/sprite/sprite_editor.cc +++ b/src/app/editor/sprite/sprite_editor.cc @@ -59,17 +59,23 @@ absl::Status SpriteEditor::Update() { vanilla_card.SetDefaultSize(900, 700); custom_card.SetDefaultSize(800, 600); - // Get visibility flags from card manager and pass to Begin() - // Always call End() after Begin() - End() handles ImGui state safely - if (vanilla_card.Begin(card_registry->GetVisibilityFlag("sprite.vanilla_editor"))) { - DrawVanillaSpriteEditor(); + // Vanilla Sprites Card - Check visibility flag exists and is true before rendering + bool* vanilla_visible = card_registry->GetVisibilityFlag("sprite.vanilla_editor"); + if (vanilla_visible && *vanilla_visible) { + if (vanilla_card.Begin(vanilla_visible)) { + DrawVanillaSpriteEditor(); + } + vanilla_card.End(); } - vanilla_card.End(); - if (custom_card.Begin(card_registry->GetVisibilityFlag("sprite.custom_editor"))) { - DrawCustomSprites(); + // Custom Sprites Card - Check visibility flag exists and is true before rendering + bool* custom_visible = card_registry->GetVisibilityFlag("sprite.custom_editor"); + if (custom_visible && *custom_visible) { + if (custom_card.Begin(custom_visible)) { + DrawCustomSprites(); + } + custom_card.End(); } - custom_card.End(); return status_.ok() ? absl::OkStatus() : status_; } diff --git a/src/app/editor/system/editor_card_registry.cc b/src/app/editor/system/editor_card_registry.cc index 4027231e..e0ca31b3 100644 --- a/src/app/editor/system/editor_card_registry.cc +++ b/src/app/editor/system/editor_card_registry.cc @@ -471,109 +471,192 @@ void EditorCardRegistry::DrawSidebar(size_t session_id, const std::vector& active_categories, std::function on_category_switch, std::function on_collapse) { - // Get cards for this session and category - auto cards = GetCardsInCategory(session_id, category); - - if (cards.empty()) { - return; - } - // Use ThemeManager for consistent theming const auto& theme = gui::ThemeManager::Get().GetCurrentTheme(); const float sidebar_width = GetSidebarWidth(); - // Fixed sidebar window on the left edge of screen (VSCode style) - ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetFrameHeight())); // Below menu bar - ImGui::SetNextWindowSize(ImVec2(sidebar_width + 220, -1)); // Full height below menu + // Fixed sidebar window on the left edge of screen - exactly like VSCode + // Positioned below menu bar, spans full height, fixed 48px width + ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetFrameHeight())); + ImGui::SetNextWindowSize(ImVec2(sidebar_width, -1)); // Exactly 48px wide, full height ImGuiWindowFlags sidebar_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoDocking | // Don't allow docking over sidebar + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoFocusOnAppearing | // Don't steal focus - ImGuiWindowFlags_NoNavFocus; // Don't participate in nav + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNavFocus; - // 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 + // VSCode-style dark sidebar background with visible border + ImVec4 sidebar_bg = ImVec4(0.18f, 0.18f, 0.20f, 1.0f); + ImVec4 sidebar_border = ImVec4(0.4f, 0.4f, 0.45f, 1.0f); ImGui::PushStyleColor(ImGuiCol_WindowBg, sidebar_bg); 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, 2.0f); // Thicker border + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); - ImGui::Begin("##EditorCardSidebar", nullptr, sidebar_flags); - - // Draw category tabs on the left (if multiple editors active) - if (active_categories.size() > 1) { - ImGui::BeginChild("CategoryTabs", ImVec2(sidebar_width, 0), false, - ImGuiWindowFlags_NoScrollbar); - - ImVec4 accent = gui::ConvertColorToImVec4(theme.accent); - ImVec4 inactive = gui::ConvertColorToImVec4(theme.button); - - for (const auto& cat : active_categories) { - bool is_current = (cat == category); + if (ImGui::Begin("##EditorCardSidebar", nullptr, sidebar_flags)) { + // Category switcher buttons at top (only if multiple editors are active) + if (active_categories.size() > 1) { + ImVec4 accent = gui::ConvertColorToImVec4(theme.accent); + ImVec4 inactive = gui::ConvertColorToImVec4(theme.button); - 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, gui::ConvertColorToImVec4(theme.button_hovered)); - } - - // Use first letter as icon - std::string icon = cat.substr(0, 1); - if (ImGui::Button(icon.c_str(), ImVec2(sidebar_width - 8, 40))) { - if (on_category_switch) { - on_category_switch(cat); + for (const auto& cat : active_categories) { + bool is_current = (cat == category); + + // Highlight current category with accent color + 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, gui::ConvertColorToImVec4(theme.button_hovered)); + } + + // Show first letter of category as button label + std::string btn_label = cat.empty() ? "?" : std::string(1, cat[0]); + if (ImGui::Button(btn_label.c_str(), ImVec2(40.0f, 32.0f))) { + // Switch to this category/editor + 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::PopStyleColor(2); + ImGui::Dummy(ImVec2(0, 2.0f)); + ImGui::Separator(); + ImGui::Spacing(); + } + + // Get cards for current category + auto cards = GetCardsInCategory(session_id, category); + + // Set this category as active when showing cards + if (!cards.empty()) { + SetActiveCategory(category); + } + + // Close All and Show All buttons (only if cards exist) + if (!cards.empty()) { + ImVec4 error_color = gui::ConvertColorToImVec4(theme.error); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4( + 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, 36.0f))) { + HideAllCardsInCategory(session_id, category); + } + + ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", cat.c_str()); + ImGui::SetTooltip("Close All %s Cards", category.c_str()); } - } + + // Show All button + ImVec4 success_color = gui::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(session_id, category); + } + + ImGui::PopStyleColor(3); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Show All %s Cards", category.c_str()); + } + + ImGui::Dummy(ImVec2(0, 2.0f)); + + // Draw individual card toggle buttons + ImVec4 accent_color = gui::ConvertColorToImVec4(theme.accent); + ImVec4 button_bg = gui::ConvertColorToImVec4(theme.button); + + for (const auto& card : cards) { + ImGui::PushID(card.card_id.c_str()); + + bool is_active = card.visibility_flag && *card.visibility_flag; + + // Highlight active cards with accent color + 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, gui::ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, gui::ConvertColorToImVec4(theme.button_active)); + } + + // Icon-only button for each card + if (ImGui::Button(card.icon.c_str(), ImVec2(40.0f, 40.0f))) { + ToggleCard(session_id, card.card_id); + SetActiveCategory(category); + } + + ImGui::PopStyleColor(3); + + // Show tooltip with card name and shortcut + 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(); + } + } // End if (!cards.empty()) - // Collapse button at bottom - ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 50); - if (ImGui::Button(ICON_MD_CHEVRON_LEFT, ImVec2(sidebar_width - 8, 40))) { - if (on_collapse) { + // Card Browser and Collapse buttons at bottom + if (on_collapse) { + ImGui::Dummy(ImVec2(0, 10.0f)); + ImGui::Separator(); + ImGui::Spacing(); + + // Collapse button + 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"); + } } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hide Sidebar (Ctrl+B)"); - } - - ImGui::EndChild(); - ImGui::SameLine(); } - - // Draw cards list on the right - ImGui::BeginChild("CardsList", ImVec2(0, 0), false); - - ImGui::Text("%s %s", ICON_MD_DASHBOARD, category.c_str()); - ImGui::Separator(); - - for (const auto& card : cards) { - DrawCardInSidebar(card, IsCardVisible(session_id, card.card_id)); - } - - ImGui::EndChild(); - ImGui::End(); - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(2); + ImGui::PopStyleVar(3); // WindowPadding, ItemSpacing, WindowBorderSize + ImGui::PopStyleColor(2); // WindowBg, Border } // ============================================================================ diff --git a/src/app/editor/system/settings_editor.cc b/src/app/editor/system/settings_editor.cc index 62ea5efe..09c97762 100644 --- a/src/app/editor/system/settings_editor.cc +++ b/src/app/editor/system/settings_editor.cc @@ -93,21 +93,23 @@ absl::Status SettingsEditor::Update() { if (!dependencies_.card_registry) return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; - // General Settings Card - if (card_registry->IsCardVisible(MakeCardId("settings.general"))) { + // General Settings Card - Check visibility flag and pass to Begin() for proper X button + bool* general_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.general")); + if (general_visible && *general_visible) { static gui::EditorCard general_card("General Settings", ICON_MD_SETTINGS); general_card.SetDefaultSize(600, 500); - if (general_card.Begin()) { + if (general_card.Begin(general_visible)) { DrawGeneralSettings(); } general_card.End(); } - // Appearance Card (Themes + Font Manager combined) - if (card_registry->IsCardVisible(MakeCardId("settings.appearance"))) { + // Appearance Card (Themes + Font Manager combined) - Check visibility and pass flag + bool* appearance_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.appearance")); + if (appearance_visible && *appearance_visible) { static gui::EditorCard appearance_card("Appearance", ICON_MD_PALETTE); appearance_card.SetDefaultSize(600, 600); - if (appearance_card.Begin()) { + if (appearance_card.Begin(appearance_visible)) { DrawThemeSettings(); ImGui::Separator(); gui::DrawFontManager(); @@ -115,41 +117,45 @@ absl::Status SettingsEditor::Update() { appearance_card.End(); } - // Editor Behavior Card - if (card_registry->IsCardVisible(MakeCardId("settings.editor_behavior"))) { + // Editor Behavior Card - Check visibility and pass flag + bool* behavior_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.editor_behavior")); + if (behavior_visible && *behavior_visible) { static gui::EditorCard behavior_card("Editor Behavior", ICON_MD_TUNE); behavior_card.SetDefaultSize(600, 500); - if (behavior_card.Begin()) { + if (behavior_card.Begin(behavior_visible)) { DrawEditorBehavior(); } behavior_card.End(); } - // Performance Card - if (card_registry->IsCardVisible(MakeCardId("settings.performance"))) { + // Performance Card - Check visibility and pass flag + bool* perf_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.performance")); + if (perf_visible && *perf_visible) { static gui::EditorCard perf_card("Performance", ICON_MD_SPEED); perf_card.SetDefaultSize(600, 450); - if (perf_card.Begin()) { + if (perf_card.Begin(perf_visible)) { DrawPerformanceSettings(); } perf_card.End(); } - // AI Agent Settings Card - if (card_registry->IsCardVisible(MakeCardId("settings.ai_agent"))) { + // AI Agent Settings Card - Check visibility and pass flag + bool* ai_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.ai_agent")); + if (ai_visible && *ai_visible) { static gui::EditorCard ai_card("AI Agent", ICON_MD_SMART_TOY); ai_card.SetDefaultSize(600, 550); - if (ai_card.Begin()) { + if (ai_card.Begin(ai_visible)) { DrawAIAgentSettings(); } ai_card.End(); } - // Keyboard Shortcuts Card - if (card_registry->IsCardVisible(MakeCardId("settings.shortcuts"))) { + // Keyboard Shortcuts Card - Check visibility and pass flag + bool* shortcuts_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.shortcuts")); + if (shortcuts_visible && *shortcuts_visible) { static gui::EditorCard shortcuts_card("Keyboard Shortcuts", ICON_MD_KEYBOARD); shortcuts_card.SetDefaultSize(700, 600); - if (shortcuts_card.Begin()) { + if (shortcuts_card.Begin(shortcuts_visible)) { DrawKeyboardShortcuts(); } shortcuts_card.End(); diff --git a/src/app/editor/ui/ui_coordinator.cc b/src/app/editor/ui/ui_coordinator.cc index 46ff6df2..5bf49bbd 100644 --- a/src/app/editor/ui/ui_coordinator.cc +++ b/src/app/editor/ui/ui_coordinator.cc @@ -173,21 +173,17 @@ void UICoordinator::DrawMenuBarExtras() { } void UICoordinator::DrawContextSensitiveCardControl() { - // Get current editor and determine category - auto* current_editor = editor_manager_->GetCurrentEditorSet(); - if (!current_editor) return; - - // Find active card-based editor - Editor* active_editor = nullptr; - for (auto* editor : current_editor->active_editors_) { - if (*editor->active() && editor_registry_.IsCardBasedEditor(editor->type())) { - active_editor = editor; - break; - } - } - + // Get the currently active editor directly from EditorManager + // This ensures we show cards for the correct editor that has focus + auto* active_editor = editor_manager_->GetCurrentEditor(); if (!active_editor) return; + // Only show card control for card-based editors (not palette, not assembly in legacy mode, etc.) + if (!editor_registry_.IsCardBasedEditor(active_editor->type())) { + return; + } + + // Get the category and session for the active editor std::string category = editor_registry_.GetEditorCategory(active_editor->type()); size_t session_id = editor_manager_->GetCurrentSessionId(); diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 5fbb7674..1854d17a 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -381,54 +381,86 @@ void Emulator::RenderEmulatorInterface() { breakpoints_card.SetDefaultSize(400, 350); performance_card.SetDefaultSize(350, 300); - if (card_registry_->IsCardVisible("emulator.cpu_debugger") && cpu_card.Begin()) { - RenderModernCpuDebugger(); + // Get visibility flags from registry and pass them to Begin() for proper X button functionality + // This ensures each card window can be closed by the user via the window close button + bool* cpu_visible = card_registry_->GetVisibilityFlag("emulator.cpu_debugger"); + if (cpu_visible && *cpu_visible) { + if (cpu_card.Begin(cpu_visible)) { + RenderModernCpuDebugger(); + } cpu_card.End(); } - if (card_registry_->IsCardVisible("emulator.ppu_viewer") && ppu_card.Begin()) { - RenderNavBar(); - RenderSnesPpu(); + bool* ppu_visible = card_registry_->GetVisibilityFlag("emulator.ppu_viewer"); + if (ppu_visible && *ppu_visible) { + if (ppu_card.Begin(ppu_visible)) { + RenderNavBar(); + RenderSnesPpu(); + } ppu_card.End(); } - if (card_registry_->IsCardVisible("emulator.memory_viewer") && memory_card.Begin()) { - RenderMemoryViewer(); + bool* memory_visible = card_registry_->GetVisibilityFlag("emulator.memory_viewer"); + if (memory_visible && *memory_visible) { + if (memory_card.Begin(memory_visible)) { + RenderMemoryViewer(); + } memory_card.End(); } - if (card_registry_->IsCardVisible("emulator.breakpoints") && breakpoints_card.Begin()) { - RenderBreakpointList(); + bool* breakpoints_visible = card_registry_->GetVisibilityFlag("emulator.breakpoints"); + if (breakpoints_visible && *breakpoints_visible) { + if (breakpoints_card.Begin(breakpoints_visible)) { + RenderBreakpointList(); + } breakpoints_card.End(); } - if (card_registry_->IsCardVisible("emulator.performance") && performance_card.Begin()) { - RenderPerformanceMonitor(); + bool* performance_visible = card_registry_->GetVisibilityFlag("emulator.performance"); + if (performance_visible && *performance_visible) { + if (performance_card.Begin(performance_visible)) { + RenderPerformanceMonitor(); + } performance_card.End(); } - if (card_registry_->IsCardVisible("emulator.ai_agent") && ai_card.Begin()) { - RenderAIAgentPanel(); + bool* ai_agent_visible = card_registry_->GetVisibilityFlag("emulator.ai_agent"); + if (ai_agent_visible && *ai_agent_visible) { + if (ai_card.Begin(ai_agent_visible)) { + RenderAIAgentPanel(); + } ai_card.End(); } - if (card_registry_->IsCardVisible("emulator.save_states") && save_states_card.Begin()) { - RenderSaveStates(); + bool* save_states_visible = card_registry_->GetVisibilityFlag("emulator.save_states"); + if (save_states_visible && *save_states_visible) { + if (save_states_card.Begin(save_states_visible)) { + RenderSaveStates(); + } save_states_card.End(); } - if (card_registry_->IsCardVisible("emulator.keyboard_config") && keyboard_card.Begin()) { - RenderKeyboardConfig(); + bool* keyboard_config_visible = card_registry_->GetVisibilityFlag("emulator.keyboard_config"); + if (keyboard_config_visible && *keyboard_config_visible) { + if (keyboard_card.Begin(keyboard_config_visible)) { + RenderKeyboardConfig(); + } keyboard_card.End(); } - if (card_registry_->IsCardVisible("emulator.apu_debugger") && apu_card.Begin()) { - RenderApuDebugger(); + bool* apu_debugger_visible = card_registry_->GetVisibilityFlag("emulator.apu_debugger"); + if (apu_debugger_visible && *apu_debugger_visible) { + if (apu_card.Begin(apu_debugger_visible)) { + RenderApuDebugger(); + } apu_card.End(); } - if (card_registry_->IsCardVisible("emulator.audio_mixer") && audio_card.Begin()) { - // RenderAudioMixer(); + bool* audio_mixer_visible = card_registry_->GetVisibilityFlag("emulator.audio_mixer"); + if (audio_mixer_visible && *audio_mixer_visible) { + if (audio_card.Begin(audio_mixer_visible)) { + // RenderAudioMixer(); + } audio_card.End(); }