diff --git a/src/app/editor/editor.h b/src/app/editor/editor.h index 2b0dbbfe..5aca2929 100644 --- a/src/app/editor/editor.h +++ b/src/app/editor/editor.h @@ -2,6 +2,7 @@ #define YAZE_APP_CORE_EDITOR_H #include +#include #include #include @@ -16,12 +17,67 @@ namespace yaze { +// Forward declarations +class Rom; +namespace gfx { +class Renderer; +} + /** * @namespace yaze::editor * @brief Editors are the view controllers for the application. */ namespace editor { +// Forward declarations +class EditorCardRegistry; +class ToastManager; +class UserSettings; + +/** + * @struct EditorDependencies + * @brief Unified dependency container for all editor types + * + * This struct encapsulates all dependencies that editors might need, + * providing a clean interface for dependency injection. It supports + * both standard editors and specialized ones (emulator, dungeon) that + * need additional dependencies like renderers. + * + * Design Philosophy: + * - Single point of dependency management + * - Type-safe for common dependencies + * - Extensible via custom_data for editor-specific needs + * - Session-aware for multi-session support + * + * Usage: + * ```cpp + * EditorDependencies deps; + * deps.rom = current_rom; + * deps.card_registry = &card_registry_; + * deps.session_id = session_index; + * + * // Standard editor + * OverworldEditor editor(deps); + * + * // Specialized editor with renderer + * deps.renderer = renderer_; + * DungeonEditor dungeon_editor(deps); + * ``` + */ +struct EditorDependencies { + Rom* rom = nullptr; + EditorCardRegistry* card_registry = nullptr; + ToastManager* toast_manager = nullptr; + PopupManager* popup_manager = nullptr; + ShortcutManager* shortcut_manager = nullptr; + UserSettings* user_settings = nullptr; + size_t session_id = 0; + + // Optional dependencies for specialized editors + gfx::Renderer* renderer = nullptr; // For emulator, dungeon editor + void* custom_data = nullptr; // Type-erased for editor-specific needs +}; + struct EditorContext { CommandManager command_manager; ExtensionManager extension_manager; diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index facc2e70..18919b94 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -33,6 +33,7 @@ set( app/editor/sprite/sprite_editor.cc app/editor/system/command_manager.cc app/editor/system/command_palette.cc + app/editor/system/editor_card_registry.cc app/editor/system/editor_registry.cc app/editor/system/extension_manager.cc app/editor/system/menu_orchestrator.cc diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 0fc21aca..8b1d1655 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -137,7 +137,7 @@ EditorManager::EditorManager() // Initialize MenuOrchestrator after SessionCoordinator is created menu_orchestrator_ = std::make_unique( - menu_builder_, rom_file_manager_, project_manager_, editor_registry_, + this, menu_builder_, rom_file_manager_, project_manager_, editor_registry_, *session_coordinator_, toast_manager_); // Initialize UICoordinator after all other components are created @@ -215,7 +215,11 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, // Register global sidebar toggle shortcut (Ctrl+B) context_.shortcut_manager.RegisterShortcut( "global.toggle_sidebar", {ImGuiKey_LeftCtrl, ImGuiKey_B}, - [this]() { show_card_sidebar_ = !show_card_sidebar_; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ToggleCardSidebar(); + } + }); // Register emulator cards early (emulator Initialize might not be called) auto& card_manager = gui::EditorCardManager::Get(); @@ -463,17 +467,17 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, welcome_screen_.SetNewProjectCallback([this]() { status_ = CreateNewProject(); - if (status_.ok()) { - show_welcome_screen_ = false; - welcome_screen_manually_closed_ = true; + if (status_.ok() && ui_coordinator_) { + ui_coordinator_->SetWelcomeScreenVisible(false); + ui_coordinator_->SetWelcomeScreenManuallyClosed(true); } }); welcome_screen_.SetOpenProjectCallback([this](const std::string& filepath) { status_ = OpenRomOrProject(filepath); - if (status_.ok()) { - show_welcome_screen_ = false; - welcome_screen_manually_closed_ = true; + if (status_.ok() && ui_coordinator_) { + ui_coordinator_->SetWelcomeScreenVisible(false); + ui_coordinator_->SetWelcomeScreenManuallyClosed(true); } }); @@ -542,10 +546,18 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, // Command Palette and Global Search context_.shortcut_manager.RegisterShortcut( "Command Palette", {ImGuiKey_P, ImGuiMod_Ctrl, ImGuiMod_Shift}, - [this]() { show_command_palette_ = true; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ShowCommandPalette(); + } + }); context_.shortcut_manager.RegisterShortcut( "Global Search", {ImGuiKey_K, ImGuiMod_Ctrl, ImGuiMod_Shift}, - [this]() { show_global_search_ = true; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ShowGlobalSearch(); + } + }); context_.shortcut_manager.RegisterShortcut( "Load Last ROM", {ImGuiKey_R, ImGuiMod_Ctrl}, [this]() { @@ -594,12 +606,20 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, // Editor Selection Dialog shortcut context_.shortcut_manager.RegisterShortcut( "Editor Selection", {ImGuiKey_E, ImGuiMod_Ctrl}, - [this]() { show_editor_selection_ = true; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ShowEditorSelection(); + } + }); // Card Browser shortcut context_.shortcut_manager.RegisterShortcut( "Card Browser", {ImGuiKey_B, ImGuiMod_Ctrl, ImGuiMod_Shift}, - [this]() { show_card_browser_ = true; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ShowCardBrowser(); + } + }); // === SIMPLIFIED CARD SHORTCUTS - Use Card Browser instead of individual shortcuts === // Individual card shortcuts removed to prevent hash table overflow @@ -664,7 +684,11 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, [this]() { CloseCurrentSession(); }); context_.shortcut_manager.RegisterShortcut( "Session Switcher", std::vector{ImGuiKey_Tab, ImGuiMod_Ctrl}, - [this]() { show_session_switcher_ = true; }); + [this]() { + if (ui_coordinator_) { + ui_coordinator_->ShowSessionSwitcher(); + } + }); context_.shortcut_manager.RegisterShortcut( "Save Layout", std::vector{ImGuiKey_S, ImGuiMod_Ctrl, ImGuiMod_Shift}, @@ -759,14 +783,22 @@ absl::Status EditorManager::Update() { ExecuteShortcuts(context_.shortcut_manager); toast_manager_.Draw(); - // Draw editor selection dialog - if (show_editor_selection_) { - editor_selection_dialog_.Show(&show_editor_selection_); + // Draw editor selection dialog (managed by UICoordinator) + if (ui_coordinator_ && ui_coordinator_->IsEditorSelectionVisible()) { + bool show = true; + editor_selection_dialog_.Show(&show); + if (!show) { + ui_coordinator_->SetEditorSelectionVisible(false); + } } - // Draw card browser - if (show_card_browser_) { - gui::EditorCardManager::Get().DrawCardBrowser(&show_card_browser_); + // Draw card browser (managed by UICoordinator) + if (ui_coordinator_ && ui_coordinator_->IsCardBrowserVisible()) { + bool show = true; + gui::EditorCardManager::Get().DrawCardBrowser(&show); + if (!show) { + ui_coordinator_->SetCardBrowserVisible(false); + } } #ifdef YAZE_WITH_GRPC @@ -830,20 +862,13 @@ absl::Status EditorManager::Update() { // Check if ROM is loaded before allowing editor updates if (!current_editor_set_) { - // Show welcome screen when no session is active, but only if not manually closed - if (sessions_.empty() && !welcome_screen_manually_closed_) { - show_welcome_screen_ = true; - } - // Don't auto-show here, let the manual control handle it + // Note: Welcome screen auto-show is now handled by UICoordinator return absl::OkStatus(); } // Check if current ROM is valid if (!current_rom_) { - // Only show welcome screen for truly empty state, not when ROM is loaded but current_rom_ is null - if (sessions_.empty() && !welcome_screen_manually_closed_) { - show_welcome_screen_ = true; - } + // Note: Welcome screen auto-show is now handled by UICoordinator return absl::OkStatus(); } @@ -934,7 +959,7 @@ absl::Status EditorManager::Update() { } } - if (show_performance_dashboard_) { + if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) { gfx::PerformanceDashboard::Get().Render(); } @@ -949,7 +974,7 @@ 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_) { + if (ui_coordinator_ && ui_coordinator_->IsCardSidebarVisible() && current_editor_set_) { auto& card_manager = gui::EditorCardManager::Get(); // Collect all active card-based editors @@ -998,7 +1023,9 @@ absl::Status EditorManager::Update() { }; auto collapse_callback = [this]() { - show_card_sidebar_ = false; + if (ui_coordinator_) { + ui_coordinator_->SetCardSidebarVisible(false); + } }; card_manager.DrawSidebar(sidebar_category, active_categories, @@ -1143,12 +1170,12 @@ void EditorManager::DrawMenuBar() { // Project file editor project_file_editor_.Draw(); - if (show_performance_dashboard_) { + if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) { gfx::PerformanceDashboard::Get().SetVisible(true); gfx::PerformanceDashboard::Get().Update(); gfx::PerformanceDashboard::Get().Render(); if (!gfx::PerformanceDashboard::Get().IsVisible()) { - show_performance_dashboard_ = false; + ui_coordinator_->SetPerformanceDashboardVisible(false); } } @@ -1168,9 +1195,9 @@ void EditorManager::DrawMenuBar() { // Agent chat history popup (left side) agent_chat_history_popup_.Draw(); - // Welcome screen (accessible from View menu) - if (show_welcome_screen_) { - DrawWelcomeScreen(); + // Welcome screen (managed by UICoordinator) + if (ui_coordinator_) { + ui_coordinator_->DrawWelcomeScreen(); } // Emulator is now card-based - it creates its own windows @@ -1178,14 +1205,15 @@ void EditorManager::DrawMenuBar() { emulator_.Run(current_rom_); } - // Enhanced Command Palette UI with Fuzzy Search - if (show_command_palette_) { + // Enhanced Command Palette UI with Fuzzy Search (managed by UICoordinator) + if (ui_coordinator_ && ui_coordinator_->IsCommandPaletteVisible()) { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + bool show_palette = true; if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(), - &show_command_palette_, ImGuiWindowFlags_NoCollapse)) { + &show_palette, ImGuiWindowFlags_NoCollapse)) { // Search input with focus management static char query[256] = {}; @@ -1296,7 +1324,9 @@ void EditorManager::DrawMenuBar() { auto it = shortcuts.find(command_name); if (it != shortcuts.end() && it->second.callback) { it->second.callback(); - show_command_palette_ = false; + if (ui_coordinator_) { + ui_coordinator_->SetCommandPaletteVisible(false); + } } } ImGui::PopID(); @@ -1335,17 +1365,23 @@ void EditorManager::DrawMenuBar() { ImGui::TextDisabled("| ↑↓=Navigate | Enter=Execute | Esc=Close"); } End(); + + // Update visibility state + if (!show_palette && ui_coordinator_) { + ui_coordinator_->SetCommandPaletteVisible(false); + } } - // Enhanced Global Search UI - if (show_global_search_) { + // Enhanced Global Search UI (managed by UICoordinator) + if (ui_coordinator_ && ui_coordinator_->IsGlobalSearchVisible()) { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + bool show_search = true; if (Begin( absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(), - &show_global_search_, ImGuiWindowFlags_NoCollapse)) { + &show_search, ImGuiWindowFlags_NoCollapse)) { // Enhanced search input with focus management static char query[256] = {}; @@ -1413,7 +1449,9 @@ void EditorManager::DrawMenuBar() { ImGui::PushID(file.c_str()); if (ImGui::Button("Open")) { status_ = OpenRomOrProject(file); - show_global_search_ = false; + if (ui_coordinator_) { + ui_coordinator_->SetGlobalSearchVisible(false); + } } ImGui::PopID(); } @@ -1498,7 +1536,9 @@ void EditorManager::DrawMenuBar() { .c_str())) { if (!is_current) { SwitchToSession(i); - show_global_search_ = false; + if (ui_coordinator_) { + ui_coordinator_->SetGlobalSearchVisible(false); + } } } @@ -1518,6 +1558,11 @@ void EditorManager::DrawMenuBar() { ImGui::Text("%s Global search across all YAZE data", ICON_MD_INFO); } End(); + + // Update visibility state + if (!show_search && ui_coordinator_) { + ui_coordinator_->SetGlobalSearchVisible(false); + } } if (show_palette_editor_ && current_editor_set_) { @@ -1793,11 +1838,11 @@ absl::Status EditorManager::LoadRom() { RETURN_IF_ERROR(LoadAssets()); // Hide welcome screen when ROM is successfully loaded - don't reset manual close state - show_welcome_screen_ = false; + ui_coordinator_->SetWelcomeScreenVisible(false); // Clear recent editors for fresh start with new ROM and show editor selection dialog editor_selection_dialog_.ClearRecentEditors(); - show_editor_selection_ = true; + ui_coordinator_->SetEditorSelectionVisible(true); return absl::OkStatus(); } @@ -1933,9 +1978,9 @@ absl::Status EditorManager::OpenRomOrProject(const std::string& filename) { RETURN_IF_ERROR(LoadAssets()); // Hide welcome screen and show editor selection when ROM is loaded - show_welcome_screen_ = false; + ui_coordinator_->SetWelcomeScreenVisible(false); editor_selection_dialog_.ClearRecentEditors(); - show_editor_selection_ = true; + ui_coordinator_->SetEditorSelectionVisible(true); } return absl::OkStatus(); } @@ -2013,9 +2058,9 @@ absl::Status EditorManager::OpenProject() { RETURN_IF_ERROR(LoadAssets()); // Hide welcome screen and show editor selection when project ROM is loaded - show_welcome_screen_ = false; + ui_coordinator_->SetWelcomeScreenVisible(false); editor_selection_dialog_.ClearRecentEditors(); - show_editor_selection_ = true; + ui_coordinator_->SetEditorSelectionVisible(true); } // Apply workspace settings @@ -2515,9 +2560,9 @@ void EditorManager::LoadUserSettings() { ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale; // Apply welcome screen preference - if (!user_settings_.prefs().show_welcome_on_startup) { - show_welcome_screen_ = false; - welcome_screen_manually_closed_ = true; + if (ui_coordinator_ && !user_settings_.prefs().show_welcome_on_startup) { + ui_coordinator_->SetWelcomeScreenVisible(false); + ui_coordinator_->SetWelcomeScreenManuallyClosed(true); } } diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 20f9835b..cad1f8c8 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -186,8 +186,14 @@ class EditorManager { 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; } + bool IsSidebarVisible() const { + return ui_coordinator_ ? ui_coordinator_->IsCardSidebarVisible() : false; + } + void SetSidebarVisible(bool visible) { + if (ui_coordinator_) { + ui_coordinator_->SetCardSidebarVisible(visible); + } + } // Clean up cards when switching editors void HideCurrentEditorCards(); @@ -220,6 +226,19 @@ class EditorManager { bool HasDuplicateSession(const std::string& filepath); void RenameSession(size_t index, const std::string& new_name); + // ROM and Project operations (public for MenuOrchestrator) + absl::Status LoadRom(); + absl::Status SaveRom(); + absl::Status SaveRomAs(const std::string& filename); + absl::Status OpenRomOrProject(const std::string& filename); + absl::Status CreateNewProject(const std::string& template_name = "Basic ROM Hack"); + absl::Status OpenProject(); + absl::Status SaveProject(); + absl::Status SaveProjectAs(); + absl::Status ImportProject(const std::string& project_path); + absl::Status RepairCurrentProject(); + void ShowProjectHelp(); + private: void DrawWelcomeScreen(); absl::Status DrawRomSelector(); @@ -229,21 +248,7 @@ class EditorManager { void DrawLayoutPresets(); void DrawSessionRenameDialog(); - absl::Status LoadRom(); absl::Status LoadAssets(); - absl::Status SaveRom(); - absl::Status SaveRomAs(const std::string& filename); - absl::Status OpenRomOrProject(const std::string& filename); - - // Project and session management - absl::Status CreateNewProject( - const std::string& template_name = "Basic ROM Hack"); - absl::Status OpenProject(); - absl::Status SaveProject(); - absl::Status SaveProjectAs(); - absl::Status ImportProject(const std::string& project_path); - absl::Status RepairCurrentProject(); - void ShowProjectHelp(); // Testing system void InitializeTestSuites(); @@ -258,26 +263,15 @@ class EditorManager { bool show_imgui_demo_ = false; bool show_palette_editor_ = false; bool show_resource_label_manager = false; + // Workspace dialog flags (managed by EditorManager, not UI) bool show_workspace_layout = false; bool show_save_workspace_preset_ = false; bool show_load_workspace_preset_ = false; - bool show_session_switcher_ = false; - bool show_session_manager_ = false; - bool show_layout_presets_ = false; - bool show_homepage_ = true; - bool show_command_palette_ = false; - bool show_global_search_ = false; - bool show_session_rename_dialog_ = false; - bool show_welcome_screen_ = false; - bool welcome_screen_manually_closed_ = false; - bool show_card_browser_ = false; - bool show_card_sidebar_ = true; // VSCode-style sidebar for editor cards (toggle with Ctrl+B) size_t session_to_rename_ = 0; char session_rename_buffer_[256] = {}; - // Testing interface - bool show_test_dashboard_ = false; - bool show_performance_dashboard_ = false; + // Note: Most UI visibility flags have been moved to UICoordinator + // Access via ui_coordinator_->IsXxxVisible() or SetXxxVisible() // Agent proposal drawer ProposalDrawer proposal_drawer_; @@ -294,11 +288,9 @@ class EditorManager { // Project file editor ProjectFileEditor project_file_editor_; - // Editor selection dialog + // Note: Editor selection dialog and welcome screen are now managed by UICoordinator + // Kept here for backward compatibility during transition EditorSelectionDialog editor_selection_dialog_; - bool show_editor_selection_ = false; - - // Welcome screen WelcomeScreen welcome_screen_; #ifdef YAZE_WITH_GRPC diff --git a/src/app/editor/system/editor_card_registry.cc b/src/app/editor/system/editor_card_registry.cc new file mode 100644 index 00000000..d0ff6695 --- /dev/null +++ b/src/app/editor/system/editor_card_registry.cc @@ -0,0 +1,888 @@ +#include "app/editor/system/editor_card_registry.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "app/gui/core/icons.h" +#include "app/gui/core/theme_manager.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +// ============================================================================ +// Session Lifecycle Management +// ============================================================================ + +void EditorCardRegistry::RegisterSession(size_t session_id) { + if (session_cards_.find(session_id) == session_cards_.end()) { + session_cards_[session_id] = std::vector(); + session_card_mapping_[session_id] = std::unordered_map(); + UpdateSessionCount(); + printf("[EditorCardRegistry] Registered session %zu (total: %zu)\n", + session_id, session_count_); + } +} + +void EditorCardRegistry::UnregisterSession(size_t session_id) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + UnregisterSessionCards(session_id); + session_cards_.erase(it); + session_card_mapping_.erase(session_id); + UpdateSessionCount(); + + // Reset active session if it was the one being removed + if (active_session_ == session_id) { + active_session_ = 0; + if (!session_cards_.empty()) { + active_session_ = session_cards_.begin()->first; + } + } + + printf("[EditorCardRegistry] Unregistered session %zu (total: %zu)\n", + session_id, session_count_); + } +} + +void EditorCardRegistry::SetActiveSession(size_t session_id) { + if (session_cards_.find(session_id) != session_cards_.end()) { + active_session_ = session_id; + printf("[EditorCardRegistry] Set active session to %zu\n", session_id); + } +} + +// ============================================================================ +// Card Registration +// ============================================================================ + +void EditorCardRegistry::RegisterCard(size_t session_id, const CardInfo& base_info) { + RegisterSession(session_id); // Ensure session exists + + std::string prefixed_id = MakeCardId(session_id, base_info.card_id); + + // Check if already registered to avoid duplicates + if (cards_.find(prefixed_id) != cards_.end()) { + printf("[EditorCardRegistry] WARNING: Card '%s' already registered, skipping duplicate\n", + prefixed_id.c_str()); + return; + } + + // Create new CardInfo with prefixed ID + CardInfo prefixed_info = base_info; + prefixed_info.card_id = prefixed_id; + + // If no visibility_flag provided, create centralized one + if (!prefixed_info.visibility_flag) { + centralized_visibility_[prefixed_id] = false; // Hidden by default + prefixed_info.visibility_flag = ¢ralized_visibility_[prefixed_id]; + } + + // Register the card + cards_[prefixed_id] = prefixed_info; + + // Track in our session mapping + session_cards_[session_id].push_back(prefixed_id); + session_card_mapping_[session_id][base_info.card_id] = prefixed_id; + + printf("[EditorCardRegistry] Registered card %s -> %s for session %zu\n", + base_info.card_id.c_str(), prefixed_id.c_str(), session_id); +} + +void EditorCardRegistry::RegisterCard(size_t session_id, + const std::string& card_id, + const std::string& display_name, + const std::string& icon, + const std::string& category, + const std::string& shortcut_hint, + int priority, + std::function on_show, + std::function on_hide, + bool visible_by_default) { + CardInfo info; + info.card_id = card_id; + info.display_name = display_name; + info.icon = icon; + info.category = category; + info.shortcut_hint = shortcut_hint; + info.priority = priority; + info.visibility_flag = nullptr; // Will be created in RegisterCard + info.on_show = on_show; + info.on_hide = on_hide; + + RegisterCard(session_id, info); + + // Set initial visibility if requested + if (visible_by_default) { + ShowCard(session_id, card_id); + } +} + +void EditorCardRegistry::UnregisterCard(size_t session_id, const std::string& base_card_id) { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end()) { + printf("[EditorCardRegistry] Unregistered card: %s\n", prefixed_id.c_str()); + cards_.erase(it); + centralized_visibility_.erase(prefixed_id); + + // Remove from session tracking + auto& session_card_list = session_cards_[session_id]; + session_card_list.erase( + std::remove(session_card_list.begin(), session_card_list.end(), prefixed_id), + session_card_list.end()); + + session_card_mapping_[session_id].erase(base_card_id); + } +} + +void EditorCardRegistry::UnregisterCardsWithPrefix(const std::string& prefix) { + std::vector to_remove; + + // Find all cards with the given prefix + for (const auto& [card_id, card_info] : cards_) { + if (card_id.find(prefix) == 0) { // Starts with prefix + to_remove.push_back(card_id); + } + } + + // Remove them + for (const auto& card_id : to_remove) { + cards_.erase(card_id); + centralized_visibility_.erase(card_id); + printf("[EditorCardRegistry] Unregistered card with prefix '%s': %s\n", + prefix.c_str(), card_id.c_str()); + } + + // Also clean up session tracking + for (auto& [session_id, card_list] : session_cards_) { + card_list.erase( + std::remove_if(card_list.begin(), card_list.end(), + [&prefix](const std::string& id) { + return id.find(prefix) == 0; + }), + card_list.end()); + } +} + +void EditorCardRegistry::ClearAllCards() { + cards_.clear(); + centralized_visibility_.clear(); + session_cards_.clear(); + session_card_mapping_.clear(); + session_count_ = 0; + printf("[EditorCardRegistry] Cleared all cards\n"); +} + +// ============================================================================ +// Card Control (Programmatic, No GUI) +// ============================================================================ + +bool EditorCardRegistry::ShowCard(size_t session_id, const std::string& base_card_id) { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end()) { + if (it->second.visibility_flag) { + *it->second.visibility_flag = true; + } + if (it->second.on_show) { + it->second.on_show(); + } + return true; + } + return false; +} + +bool EditorCardRegistry::HideCard(size_t session_id, const std::string& base_card_id) { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end()) { + if (it->second.visibility_flag) { + *it->second.visibility_flag = false; + } + if (it->second.on_hide) { + it->second.on_hide(); + } + return true; + } + return false; +} + +bool EditorCardRegistry::ToggleCard(size_t session_id, const std::string& base_card_id) { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end() && it->second.visibility_flag) { + bool new_state = !(*it->second.visibility_flag); + *it->second.visibility_flag = new_state; + + if (new_state && it->second.on_show) { + it->second.on_show(); + } else if (!new_state && it->second.on_hide) { + it->second.on_hide(); + } + return true; + } + return false; +} + +bool EditorCardRegistry::IsCardVisible(size_t session_id, const std::string& base_card_id) const { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end() && it->second.visibility_flag) { + return *it->second.visibility_flag; + } + return false; +} + +bool* EditorCardRegistry::GetVisibilityFlag(size_t session_id, const std::string& base_card_id) { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return nullptr; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end()) { + return it->second.visibility_flag; + } + return nullptr; +} + +// ============================================================================ +// Batch Operations +// ============================================================================ + +void EditorCardRegistry::ShowAllCardsInSession(size_t session_id) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.visibility_flag) { + *card_it->second.visibility_flag = true; + if (card_it->second.on_show) { + card_it->second.on_show(); + } + } + } + } +} + +void EditorCardRegistry::HideAllCardsInSession(size_t session_id) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.visibility_flag) { + *card_it->second.visibility_flag = false; + if (card_it->second.on_hide) { + card_it->second.on_hide(); + } + } + } + } +} + +void EditorCardRegistry::ShowAllCardsInCategory(size_t session_id, const std::string& category) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.category == category) { + if (card_it->second.visibility_flag) { + *card_it->second.visibility_flag = true; + } + if (card_it->second.on_show) { + card_it->second.on_show(); + } + } + } + } +} + +void EditorCardRegistry::HideAllCardsInCategory(size_t session_id, const std::string& category) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.category == category) { + if (card_it->second.visibility_flag) { + *card_it->second.visibility_flag = false; + } + if (card_it->second.on_hide) { + card_it->second.on_hide(); + } + } + } + } +} + +void EditorCardRegistry::ShowOnlyCard(size_t session_id, const std::string& base_card_id) { + // First get the category of the target card + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return; + } + + auto target_it = cards_.find(prefixed_id); + if (target_it == cards_.end()) { + return; + } + + std::string category = target_it->second.category; + + // Hide all cards in the same category + HideAllCardsInCategory(session_id, category); + + // Show the target card + ShowCard(session_id, base_card_id); +} + +// ============================================================================ +// Query Methods +// ============================================================================ + +std::vector EditorCardRegistry::GetCardsInSession(size_t session_id) const { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + return it->second; + } + return {}; +} + +std::vector EditorCardRegistry::GetCardsInCategory(size_t session_id, + const std::string& category) const { + std::vector result; + + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.category == category) { + result.push_back(card_it->second); + } + } + } + + // Sort by priority + std::sort(result.begin(), result.end(), + [](const CardInfo& a, const CardInfo& b) { + return a.priority < b.priority; + }); + + return result; +} + +std::vector EditorCardRegistry::GetAllCategories(size_t session_id) const { + std::vector categories; + + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end()) { + if (std::find(categories.begin(), categories.end(), + card_it->second.category) == categories.end()) { + categories.push_back(card_it->second.category); + } + } + } + } + return categories; +} + +const CardInfo* EditorCardRegistry::GetCardInfo(size_t session_id, + const std::string& base_card_id) const { + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return nullptr; + } + + auto it = cards_.find(prefixed_id); + if (it != cards_.end()) { + return &it->second; + } + return nullptr; +} + +std::vector EditorCardRegistry::GetAllCategories() const { + std::vector categories; + for (const auto& [card_id, card_info] : cards_) { + if (std::find(categories.begin(), categories.end(), + card_info.category) == categories.end()) { + categories.push_back(card_info.category); + } + } + return categories; +} + +// ============================================================================ +// View Menu Integration +// ============================================================================ + +void EditorCardRegistry::DrawViewMenuSection(size_t session_id, const std::string& category) { + auto cards = GetCardsInCategory(session_id, category); + + if (cards.empty()) { + return; + } + + if (ImGui::BeginMenu(category.c_str())) { + for (const auto& card : cards) { + DrawCardMenuItem(card); + } + ImGui::EndMenu(); + } +} + +void EditorCardRegistry::DrawViewMenuAll(size_t session_id) { + auto categories = GetAllCategories(session_id); + + for (const auto& category : categories) { + DrawViewMenuSection(session_id, category); + } +} + +// ============================================================================ +// VSCode-Style Sidebar +// ============================================================================ + +void EditorCardRegistry::DrawSidebar(size_t session_id, + const std::string& category, + 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; + } + + // Sidebar window + ImGui::SetNextWindowSize(ImVec2(GetSidebarWidth() + 220, 0), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetFrameHeightWithSpacing()), ImGuiCond_Always); + + ImGui::Begin("##CardSidebar", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + + // Draw category tabs on the left + ImGui::BeginChild("CategoryTabs", ImVec2(GetSidebarWidth(), 0), true); + + for (const auto& cat : active_categories) { + bool is_active = (cat == category); + if (is_active) { + ImGui::PushStyleColor(ImGuiCol_Button, gui::GetPrimaryVec4()); + } + + if (ImGui::Button(cat.substr(0, 1).c_str(), ImVec2(GetSidebarWidth() - 8, 40))) { + if (on_category_switch) { + on_category_switch(cat); + } + } + + if (is_active) { + ImGui::PopStyleColor(); + } + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", cat.c_str()); + } + } + + ImGui::EndChild(); + + ImGui::SameLine(); + + // Draw cards on the right + ImGui::BeginChild("Cards", ImVec2(0, 0), true); + + 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(); +} + +// ============================================================================ +// Compact Controls for Menu Bar +// ============================================================================ + +void EditorCardRegistry::DrawCompactCardControl(size_t session_id, const std::string& category) { + auto cards = GetCardsInCategory(session_id, category); + + if (cards.empty()) { + return; + } + + // Compact dropdown + if (ImGui::BeginCombo("##CardControl", absl::StrFormat("%s Cards", ICON_MD_DASHBOARD).c_str())) { + for (const auto& card : cards) { + bool visible = card.visibility_flag ? *card.visibility_flag : false; + if (ImGui::MenuItem(absl::StrFormat("%s %s", card.icon.c_str(), + card.display_name.c_str()).c_str(), + nullptr, visible)) { + ToggleCard(session_id, card.card_id); + } + } + ImGui::EndCombo(); + } +} + +void EditorCardRegistry::DrawInlineCardToggles(size_t session_id, const std::string& category) { + auto cards = GetCardsInCategory(session_id, category); + + size_t visible_count = 0; + for (const auto& card : cards) { + if (card.visibility_flag && *card.visibility_flag) { + visible_count++; + } + } + + ImGui::Text("(%zu/%zu)", visible_count, cards.size()); +} + +// ============================================================================ +// Card Browser UI +// ============================================================================ + +void EditorCardRegistry::DrawCardBrowser(size_t session_id, bool* p_open) { + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + + if (ImGui::Begin(absl::StrFormat("%s Card Browser", ICON_MD_DASHBOARD).c_str(), + p_open)) { + + static char search_filter[256] = ""; + static std::string category_filter = "All"; + + // Search bar + ImGui::SetNextItemWidth(300); + ImGui::InputTextWithHint("##Search", absl::StrFormat("%s Search cards...", + ICON_MD_SEARCH).c_str(), + search_filter, sizeof(search_filter)); + + ImGui::SameLine(); + + // Category filter + if (ImGui::BeginCombo("##CategoryFilter", category_filter.c_str())) { + if (ImGui::Selectable("All", category_filter == "All")) { + category_filter = "All"; + } + + auto categories = GetAllCategories(session_id); + for (const auto& cat : categories) { + if (ImGui::Selectable(cat.c_str(), category_filter == cat)) { + category_filter = cat; + } + } + ImGui::EndCombo(); + } + + ImGui::Separator(); + + // Card table + if (ImGui::BeginTable("##CardTable", 4, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Borders)) { + + ImGui::TableSetupColumn("Visible", ImGuiTableColumnFlags_WidthFixed, 60); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableHeadersRow(); + + auto cards = (category_filter == "All") + ? GetCardsInSession(session_id) + : std::vector{}; + + if (category_filter != "All") { + auto cat_cards = GetCardsInCategory(session_id, category_filter); + for (const auto& card : cat_cards) { + cards.push_back(card.card_id); + } + } + + for (const auto& card_id : cards) { + auto card_it = cards_.find(card_id); + if (card_it == cards_.end()) continue; + + const auto& card = card_it->second; + + // Apply search filter + std::string search_str = search_filter; + if (!search_str.empty()) { + std::string card_lower = card.display_name; + std::transform(card_lower.begin(), card_lower.end(), card_lower.begin(), ::tolower); + std::transform(search_str.begin(), search_str.end(), search_str.begin(), ::tolower); + if (card_lower.find(search_str) == std::string::npos) { + continue; + } + } + + ImGui::TableNextRow(); + + // Visibility toggle + ImGui::TableNextColumn(); + if (card.visibility_flag) { + bool visible = *card.visibility_flag; + if (ImGui::Checkbox(absl::StrFormat("##vis_%s", card.card_id.c_str()).c_str(), + &visible)) { + *card.visibility_flag = visible; + if (visible && card.on_show) { + card.on_show(); + } else if (!visible && card.on_hide) { + card.on_hide(); + } + } + } + + // Name with icon + ImGui::TableNextColumn(); + ImGui::Text("%s %s", card.icon.c_str(), card.display_name.c_str()); + + // Category + ImGui::TableNextColumn(); + ImGui::Text("%s", card.category.c_str()); + + // Shortcut + ImGui::TableNextColumn(); + ImGui::TextDisabled("%s", card.shortcut_hint.c_str()); + } + + ImGui::EndTable(); + } + } + ImGui::End(); +} + +// ============================================================================ +// Workspace Presets +// ============================================================================ + +void EditorCardRegistry::SavePreset(const std::string& name, const std::string& description) { + WorkspacePreset preset; + preset.name = name; + preset.description = description; + + // Collect all visible cards across all sessions + for (const auto& [card_id, card_info] : cards_) { + if (card_info.visibility_flag && *card_info.visibility_flag) { + preset.visible_cards.push_back(card_id); + } + } + + presets_[name] = preset; + SavePresetsToFile(); + printf("[EditorCardRegistry] Saved preset: %s (%zu cards)\n", + name.c_str(), preset.visible_cards.size()); +} + +bool EditorCardRegistry::LoadPreset(const std::string& name) { + auto it = presets_.find(name); + if (it == presets_.end()) { + return false; + } + + // First hide all cards + for (auto& [card_id, card_info] : cards_) { + if (card_info.visibility_flag) { + *card_info.visibility_flag = false; + } + } + + // Then show preset cards + for (const auto& card_id : it->second.visible_cards) { + auto card_it = cards_.find(card_id); + if (card_it != cards_.end() && card_it->second.visibility_flag) { + *card_it->second.visibility_flag = true; + if (card_it->second.on_show) { + card_it->second.on_show(); + } + } + } + + printf("[EditorCardRegistry] Loaded preset: %s\n", name.c_str()); + return true; +} + +void EditorCardRegistry::DeletePreset(const std::string& name) { + presets_.erase(name); + SavePresetsToFile(); +} + +std::vector EditorCardRegistry::GetPresets() const { + std::vector result; + for (const auto& [name, preset] : presets_) { + result.push_back(preset); + } + return result; +} + +// ============================================================================ +// Quick Actions +// ============================================================================ + +void EditorCardRegistry::ShowAll(size_t session_id) { + ShowAllCardsInSession(session_id); +} + +void EditorCardRegistry::HideAll(size_t session_id) { + HideAllCardsInSession(session_id); +} + +void EditorCardRegistry::ResetToDefaults(size_t session_id) { + // Hide all cards first + HideAllCardsInSession(session_id); + + // TODO: Load default visibility from config file or hardcoded defaults + printf("[EditorCardRegistry] Reset to defaults for session %zu\n", session_id); +} + +// ============================================================================ +// Statistics +// ============================================================================ + +size_t EditorCardRegistry::GetVisibleCardCount(size_t session_id) const { + size_t count = 0; + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + auto card_it = cards_.find(prefixed_card_id); + if (card_it != cards_.end() && card_it->second.visibility_flag) { + if (*card_it->second.visibility_flag) { + count++; + } + } + } + } + return count; +} + +// ============================================================================ +// Session Prefixing Utilities +// ============================================================================ + +std::string EditorCardRegistry::MakeCardId(size_t session_id, const std::string& base_id) const { + if (ShouldPrefixCards()) { + return absl::StrFormat("s%zu.%s", session_id, base_id); + } + return base_id; +} + +// ============================================================================ +// Helper Methods (Private) +// ============================================================================ + +void EditorCardRegistry::UpdateSessionCount() { + session_count_ = session_cards_.size(); +} + +std::string EditorCardRegistry::GetPrefixedCardId(size_t session_id, + const std::string& base_id) const { + auto session_it = session_card_mapping_.find(session_id); + if (session_it != session_card_mapping_.end()) { + auto card_it = session_it->second.find(base_id); + if (card_it != session_it->second.end()) { + return card_it->second; + } + } + + // Fallback: try unprefixed ID (for single session or direct access) + if (cards_.find(base_id) != cards_.end()) { + return base_id; + } + + return ""; // Card not found +} + +void EditorCardRegistry::UnregisterSessionCards(size_t session_id) { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& prefixed_card_id : it->second) { + cards_.erase(prefixed_card_id); + centralized_visibility_.erase(prefixed_card_id); + } + } +} + +void EditorCardRegistry::SavePresetsToFile() { + // TODO: Implement file I/O for presets + printf("[EditorCardRegistry] SavePresetsToFile() - not yet implemented\n"); +} + +void EditorCardRegistry::LoadPresetsFromFile() { + // TODO: Implement file I/O for presets + printf("[EditorCardRegistry] LoadPresetsFromFile() - not yet implemented\n"); +} + +void EditorCardRegistry::DrawCardMenuItem(const CardInfo& info) { + bool visible = info.visibility_flag ? *info.visibility_flag : false; + + std::string label = absl::StrFormat("%s %s", info.icon.c_str(), + info.display_name.c_str()); + + const char* shortcut = info.shortcut_hint.empty() ? nullptr : info.shortcut_hint.c_str(); + + if (ImGui::MenuItem(label.c_str(), shortcut, visible)) { + if (info.visibility_flag) { + *info.visibility_flag = !visible; + if (*info.visibility_flag && info.on_show) { + info.on_show(); + } else if (!*info.visibility_flag && info.on_hide) { + info.on_hide(); + } + } + } +} + +void EditorCardRegistry::DrawCardInSidebar(const CardInfo& info, bool is_active) { + if (is_active) { + ImGui::PushStyleColor(ImGuiCol_Button, gui::GetPrimaryVec4()); + } + + if (ImGui::Button(absl::StrFormat("%s %s", info.icon.c_str(), + info.display_name.c_str()).c_str(), + ImVec2(-1, 0))) { + if (info.visibility_flag) { + *info.visibility_flag = !*info.visibility_flag; + if (*info.visibility_flag && info.on_show) { + info.on_show(); + } else if (!*info.visibility_flag && info.on_hide) { + info.on_hide(); + } + } + } + + if (is_active) { + ImGui::PopStyleColor(); + } +} + +} // namespace editor +} // namespace yaze + diff --git a/src/app/editor/system/editor_card_registry.h b/src/app/editor/system/editor_card_registry.h new file mode 100644 index 00000000..9662c194 --- /dev/null +++ b/src/app/editor/system/editor_card_registry.h @@ -0,0 +1,406 @@ +#ifndef YAZE_APP_EDITOR_SYSTEM_EDITOR_CARD_REGISTRY_H_ +#define YAZE_APP_EDITOR_SYSTEM_EDITOR_CARD_REGISTRY_H_ + +#include +#include +#include +#include +#include + +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +// Forward declaration +class EditorCard; + +/** + * @struct CardInfo + * @brief Metadata for an editor card + * + * Describes a registerable UI card that can be shown/hidden, + * organized by category, and controlled programmatically. + */ +struct CardInfo { + std::string card_id; // Unique identifier (e.g., "dungeon.room_selector") + std::string display_name; // Human-readable name (e.g., "Room Selector") + std::string icon; // Material icon + std::string category; // Category (e.g., "Dungeon", "Graphics", "Palette") + std::string shortcut_hint; // Display hint (e.g., "Ctrl+Shift+R") + bool* visibility_flag; // Pointer to bool controlling visibility + EditorCard* card_instance; // Pointer to actual card (optional) + std::function on_show; // Callback when card is shown + std::function on_hide; // Callback when card is hidden + int priority; // Display priority for menus (lower = higher) +}; + +/** + * @class EditorCardRegistry + * @brief Central registry for all editor cards with session awareness and dependency injection + * + * This class combines the functionality of EditorCardManager (global card management) + * and SessionCardRegistry (session-aware prefixing) into a single, dependency-injected + * component that can be passed to editors. + * + * Design Philosophy: + * - Dependency injection (no singleton pattern) + * - Session-aware card ID prefixing for multi-session support + * - Centralized visibility management + * - View menu integration + * - Workspace preset system + * - No direct GUI dependency in registration logic + * + * Session-Aware Card IDs: + * - Single session: "dungeon.room_selector" + * - Multiple sessions: "s0.dungeon.room_selector", "s1.dungeon.room_selector" + * + * Usage: + * ```cpp + * // In EditorManager: + * EditorCardRegistry card_registry; + * EditorDependencies deps; + * deps.card_registry = &card_registry; + * + * // In Editor: + * deps.card_registry->RegisterCard(deps.session_id, { + * .card_id = "dungeon.room_selector", + * .display_name = "Room Selector", + * .icon = ICON_MD_LIST, + * .category = "Dungeon", + * .on_show = []() { } + * }); + * + * // Programmatic control: + * deps.card_registry->ShowCard(deps.session_id, "dungeon.room_selector"); + * ``` + */ +class EditorCardRegistry { + public: + EditorCardRegistry() = default; + ~EditorCardRegistry() = default; + + // Non-copyable, non-movable (manages pointers and callbacks) + EditorCardRegistry(const EditorCardRegistry&) = delete; + EditorCardRegistry& operator=(const EditorCardRegistry&) = delete; + EditorCardRegistry(EditorCardRegistry&&) = delete; + EditorCardRegistry& operator=(EditorCardRegistry&&) = delete; + + // ============================================================================ + // Session Lifecycle Management + // ============================================================================ + + /** + * @brief Register a new session in the registry + * @param session_id Unique session identifier + * + * Creates internal tracking structures for the session. + * Must be called before registering cards for a session. + */ + void RegisterSession(size_t session_id); + + /** + * @brief Unregister a session and all its cards + * @param session_id Session identifier to remove + * + * Automatically unregisters all cards associated with the session. + */ + void UnregisterSession(size_t session_id); + + /** + * @brief Set the currently active session + * @param session_id Session to make active + * + * Used for determining whether to apply card ID prefixing. + */ + void SetActiveSession(size_t session_id); + + // ============================================================================ + // Card Registration + // ============================================================================ + + /** + * @brief Register a card for a specific session + * @param session_id Session this card belongs to + * @param base_info Card metadata (ID will be automatically prefixed if needed) + * + * The card_id in base_info should be the unprefixed ID. This method + * automatically applies session prefixing when multiple sessions exist. + */ + void RegisterCard(size_t session_id, const CardInfo& base_info); + + /** + * @brief Register a card with inline parameters (convenience method) + */ + void RegisterCard(size_t session_id, + const std::string& card_id, + const std::string& display_name, + const std::string& icon, + const std::string& category, + const std::string& shortcut_hint = "", + int priority = 50, + std::function on_show = nullptr, + std::function on_hide = nullptr, + bool visible_by_default = false); + + /** + * @brief Unregister a specific card + * @param session_id Session the card belongs to + * @param base_card_id Unprefixed card ID + */ + void UnregisterCard(size_t session_id, const std::string& base_card_id); + + /** + * @brief Unregister all cards with a given prefix + * @param prefix Prefix to match (e.g., "s0" or "s1.dungeon") + * + * Useful for cleaning up session cards or category cards. + */ + void UnregisterCardsWithPrefix(const std::string& prefix); + + /** + * @brief Remove all registered cards (use with caution) + */ + void ClearAllCards(); + + // ============================================================================ + // Card Control (Programmatic, No GUI) + // ============================================================================ + + /** + * @brief Show a card programmatically + * @param session_id Session the card belongs to + * @param base_card_id Unprefixed card ID + * @return true if card was found and shown + */ + bool ShowCard(size_t session_id, const std::string& base_card_id); + + /** + * @brief Hide a card programmatically + */ + bool HideCard(size_t session_id, const std::string& base_card_id); + + /** + * @brief Toggle a card's visibility + */ + bool ToggleCard(size_t session_id, const std::string& base_card_id); + + /** + * @brief Check if a card is currently visible + */ + bool IsCardVisible(size_t session_id, const std::string& base_card_id) const; + + /** + * @brief Get visibility flag pointer for a card + * @return Pointer to bool controlling card visibility (for passing to EditorCard::Begin) + */ + bool* GetVisibilityFlag(size_t session_id, const std::string& base_card_id); + + // ============================================================================ + // Batch Operations + // ============================================================================ + + /** + * @brief Show all cards in a specific session + */ + void ShowAllCardsInSession(size_t session_id); + + /** + * @brief Hide all cards in a specific session + */ + void HideAllCardsInSession(size_t session_id); + + /** + * @brief Show all cards in a category for a session + */ + void ShowAllCardsInCategory(size_t session_id, const std::string& category); + + /** + * @brief Hide all cards in a category for a session + */ + void HideAllCardsInCategory(size_t session_id, const std::string& category); + + /** + * @brief Show only one card, hiding all others in its category + */ + void ShowOnlyCard(size_t session_id, const std::string& base_card_id); + + // ============================================================================ + // Query Methods + // ============================================================================ + + /** + * @brief Get all cards registered for a session + * @return Vector of prefixed card IDs + */ + std::vector GetCardsInSession(size_t session_id) const; + + /** + * @brief Get cards in a specific category for a session + */ + std::vector GetCardsInCategory(size_t session_id, const std::string& category) const; + + /** + * @brief Get all categories for a session + */ + std::vector GetAllCategories(size_t session_id) const; + + /** + * @brief Get card metadata + * @param session_id Session the card belongs to + * @param base_card_id Unprefixed card ID + */ + const CardInfo* GetCardInfo(size_t session_id, const std::string& base_card_id) const; + + /** + * @brief Get all registered categories across all sessions + */ + std::vector GetAllCategories() const; + + // ============================================================================ + // View Menu Integration + // ============================================================================ + + /** + * @brief Draw view menu section for a category + */ + void DrawViewMenuSection(size_t session_id, const std::string& category); + + /** + * @brief Draw all categories as view menu submenus + */ + void DrawViewMenuAll(size_t session_id); + + // ============================================================================ + // VSCode-Style Sidebar + // ============================================================================ + + /** + * @brief Draw sidebar for a category with session filtering + */ + void DrawSidebar(size_t session_id, + 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; } + + // ============================================================================ + // Compact Controls for Menu Bar + // ============================================================================ + + /** + * @brief Draw compact card control for active editor's cards + */ + void DrawCompactCardControl(size_t session_id, const std::string& category); + + /** + * @brief Draw minimal inline card toggles + */ + void DrawInlineCardToggles(size_t session_id, const std::string& category); + + // ============================================================================ + // Card Browser UI + // ============================================================================ + + /** + * @brief Draw visual card browser/toggler + */ + void DrawCardBrowser(size_t session_id, bool* p_open); + + // ============================================================================ + // Workspace Presets + // ============================================================================ + + struct WorkspacePreset { + std::string name; + std::vector visible_cards; // Card IDs + std::string description; + }; + + void SavePreset(const std::string& name, const std::string& description = ""); + bool LoadPreset(const std::string& name); + void DeletePreset(const std::string& name); + std::vector GetPresets() const; + + // ============================================================================ + // Quick Actions + // ============================================================================ + + void ShowAll(size_t session_id); + void HideAll(size_t session_id); + void ResetToDefaults(size_t session_id); + + // ============================================================================ + // Statistics + // ============================================================================ + + size_t GetCardCount() const { return cards_.size(); } + size_t GetVisibleCardCount(size_t session_id) const; + size_t GetSessionCount() const { return session_count_; } + + // ============================================================================ + // Session Prefixing Utilities + // ============================================================================ + + /** + * @brief Generate session-aware card ID + * @param session_id Session identifier + * @param base_id Unprefixed card ID + * @return Prefixed ID if multiple sessions, otherwise base ID + * + * Examples: + * - Single session: "dungeon.room_selector" → "dungeon.room_selector" + * - Multi-session: "dungeon.room_selector" → "s0.dungeon.room_selector" + */ + std::string MakeCardId(size_t session_id, const std::string& base_id) const; + + /** + * @brief Check if card IDs should be prefixed + * @return true if session_count > 1 + */ + bool ShouldPrefixCards() const { return session_count_ > 1; } + + private: + // Core card storage (prefixed IDs → CardInfo) + std::unordered_map cards_; + + // Centralized visibility flags for cards without external flags + std::unordered_map centralized_visibility_; + + // Session tracking + size_t session_count_ = 0; + size_t active_session_ = 0; + + // Maps session_id → vector of prefixed card IDs registered for that session + std::unordered_map> session_cards_; + + // Maps session_id → (base_card_id → prefixed_card_id) + std::unordered_map> session_card_mapping_; + + // Workspace presets + std::unordered_map presets_; + + // Active category tracking + std::string active_category_; + std::vector recent_categories_; + static constexpr size_t kMaxRecentCategories = 5; + + // Helper methods + void UpdateSessionCount(); + std::string GetPrefixedCardId(size_t session_id, const std::string& base_id) const; + void UnregisterSessionCards(size_t session_id); + void SavePresetsToFile(); + void LoadPresetsFromFile(); + + // UI drawing helpers (internal) + void DrawCardMenuItem(const CardInfo& info); + void DrawCardInSidebar(const CardInfo& info, bool is_active); +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_SYSTEM_EDITOR_CARD_REGISTRY_H_ + diff --git a/src/app/editor/system/menu_orchestrator.cc b/src/app/editor/system/menu_orchestrator.cc index 85842abc..435e274b 100644 --- a/src/app/editor/system/menu_orchestrator.cc +++ b/src/app/editor/system/menu_orchestrator.cc @@ -2,25 +2,28 @@ #include "absl/strings/str_format.h" #include "app/editor/editor.h" +#include "app/editor/editor_manager.h" #include "app/editor/system/editor_registry.h" #include "app/editor/system/project_manager.h" #include "app/editor/system/rom_file_manager.h" +#include "app/editor/system/session_coordinator.h" #include "app/editor/system/toast_manager.h" #include "app/editor/ui/menu_builder.h" -#include "app/editor/system/session_coordinator.h" #include "app/gui/core/icons.h" namespace yaze { namespace editor { MenuOrchestrator::MenuOrchestrator( + EditorManager* editor_manager, MenuBuilder& menu_builder, RomFileManager& rom_manager, ProjectManager& project_manager, EditorRegistry& editor_registry, SessionCoordinator& session_coordinator, ToastManager& toast_manager) - : menu_builder_(menu_builder), + : editor_manager_(editor_manager), + menu_builder_(menu_builder), rom_manager_(rom_manager), project_manager_(project_manager), editor_registry_(editor_registry), @@ -39,6 +42,9 @@ void MenuOrchestrator::BuildMainMenu() { BuildWindowMenu(); BuildHelpMenu(); + // Draw the constructed menu + menu_builder_.Draw(); + menu_needs_refresh_ = false; } @@ -312,20 +318,28 @@ void MenuOrchestrator::RefreshMenu() { // Menu item callbacks - delegate to appropriate managers void MenuOrchestrator::OnOpenRom() { - auto status = rom_manager_.LoadRom(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to load ROM: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager's LoadRom which handles session management + if (editor_manager_) { + auto status = editor_manager_->LoadRom(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to load ROM: %s", status.message()), + ToastType::kError); + } } } void MenuOrchestrator::OnSaveRom() { - auto status = rom_manager_.SaveRom(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to save ROM: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager's SaveRom which handles editor data saving + if (editor_manager_) { + auto status = editor_manager_->SaveRom(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to save ROM: %s", status.message()), + ToastType::kError); + } else { + toast_manager_.Show("ROM saved successfully", ToastType::kSuccess); + } } } @@ -335,58 +349,75 @@ void MenuOrchestrator::OnSaveRomAs() { } void MenuOrchestrator::OnCreateProject() { - auto status = project_manager_.CreateNewProject(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to create project: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager which handles the full project creation flow + if (editor_manager_) { + auto status = editor_manager_->CreateNewProject(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to create project: %s", status.message()), + ToastType::kError); + } } } void MenuOrchestrator::OnOpenProject() { - auto status = project_manager_.OpenProject(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to open project: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager which handles ROM loading and session creation + if (editor_manager_) { + auto status = editor_manager_->OpenProject(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to open project: %s", status.message()), + ToastType::kError); + } } } void MenuOrchestrator::OnSaveProject() { - auto status = project_manager_.SaveProject(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to save project: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager which updates project with current state + if (editor_manager_) { + auto status = editor_manager_->SaveProject(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to save project: %s", status.message()), + ToastType::kError); + } else { + toast_manager_.Show("Project saved successfully", ToastType::kSuccess); + } } } void MenuOrchestrator::OnSaveProjectAs() { - auto status = project_manager_.SaveProjectAs(); - if (!status.ok()) { - toast_manager_.Show( - absl::StrFormat("Failed to save project as: %s", status.message()), - ToastType::kError); + // Delegate to EditorManager + if (editor_manager_) { + auto status = editor_manager_->SaveProjectAs(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to save project as: %s", status.message()), + ToastType::kError); + } } } // Editor-specific menu actions void MenuOrchestrator::OnSwitchToEditor(EditorType editor_type) { - editor_registry_.SwitchToEditor(editor_type); - toast_manager_.Show( - absl::StrFormat("Switched to %s", - editor_registry_.GetEditorDisplayName(editor_type)), - ToastType::kInfo); + // Delegate to EditorManager which manages editor switching + if (editor_manager_) { + editor_manager_->SwitchToEditor(editor_type); + } } void MenuOrchestrator::OnShowEditorSelection() { - // TODO: Show editor selection dialog - toast_manager_.Show("Editor Selection", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->ShowEditorSelection(); + } } void MenuOrchestrator::OnShowDisplaySettings() { - // TODO: Show display settings dialog - toast_manager_.Show("Display Settings", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->ShowDisplaySettings(); + } } // Session management menu actions @@ -408,28 +439,38 @@ void MenuOrchestrator::OnSwitchToSession(size_t session_index) { // Window management menu actions void MenuOrchestrator::OnShowAllWindows() { - // TODO: Delegate to WindowDelegate - toast_manager_.Show("Show All Windows", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->ShowAllWindows(); + } } void MenuOrchestrator::OnHideAllWindows() { - // TODO: Delegate to WindowDelegate - toast_manager_.Show("Hide All Windows", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->HideAllWindows(); + } } void MenuOrchestrator::OnResetWorkspaceLayout() { - // TODO: Delegate to WindowDelegate - toast_manager_.Show("Reset Workspace Layout", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->ResetWorkspaceLayout(); + } } void MenuOrchestrator::OnSaveWorkspaceLayout() { - // TODO: Delegate to WindowDelegate - toast_manager_.Show("Save Workspace Layout", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->SaveWorkspaceLayout(); + } } void MenuOrchestrator::OnLoadWorkspaceLayout() { - // TODO: Delegate to WindowDelegate - toast_manager_.Show("Load Workspace Layout", ToastType::kInfo); + // Delegate to EditorManager + if (editor_manager_) { + editor_manager_->LoadWorkspaceLayout(); + } } // Tool menu actions diff --git a/src/app/editor/system/menu_orchestrator.h b/src/app/editor/system/menu_orchestrator.h index 80f0c5e9..5c9ae83c 100644 --- a/src/app/editor/system/menu_orchestrator.h +++ b/src/app/editor/system/menu_orchestrator.h @@ -38,7 +38,8 @@ class ToastManager; class MenuOrchestrator { public: // Constructor takes references to the managers it coordinates with - MenuOrchestrator(MenuBuilder& menu_builder, + MenuOrchestrator(EditorManager* editor_manager, + MenuBuilder& menu_builder, RomFileManager& rom_manager, ProjectManager& project_manager, EditorRegistry& editor_registry, @@ -103,6 +104,7 @@ class MenuOrchestrator { private: // References to coordinated managers + EditorManager* editor_manager_; MenuBuilder& menu_builder_; RomFileManager& rom_manager_; ProjectManager& project_manager_; diff --git a/src/app/editor/ui/ui_coordinator.cc b/src/app/editor/ui/ui_coordinator.cc index cbf24585..0d2abcff 100644 --- a/src/app/editor/ui/ui_coordinator.cc +++ b/src/app/editor/ui/ui_coordinator.cc @@ -41,8 +41,54 @@ UICoordinator::UICoordinator( toast_manager_(toast_manager), popup_manager_(popup_manager) { - // Initialize welcome screen + // Initialize welcome screen with proper callbacks welcome_screen_ = std::make_unique(); + + // Wire welcome screen callbacks to EditorManager + welcome_screen_->SetOpenRomCallback([this]() { + if (editor_manager_) { + auto status = editor_manager_->LoadRom(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to load ROM: %s", status.message()), + ToastType::kError); + } else { + // Hide welcome screen on successful ROM load + show_welcome_screen_ = false; + welcome_screen_manually_closed_ = true; + } + } + }); + + welcome_screen_->SetNewProjectCallback([this]() { + if (editor_manager_) { + auto status = editor_manager_->CreateNewProject(); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to create project: %s", status.message()), + ToastType::kError); + } else { + // Hide welcome screen on successful project creation + show_welcome_screen_ = false; + welcome_screen_manually_closed_ = true; + } + } + }); + + welcome_screen_->SetOpenProjectCallback([this](const std::string& filepath) { + if (editor_manager_) { + auto status = editor_manager_->OpenRomOrProject(filepath); + if (!status.ok()) { + toast_manager_.Show( + absl::StrFormat("Failed to open project: %s", status.message()), + ToastType::kError); + } else { + // Hide welcome screen on successful project open + show_welcome_screen_ = false; + welcome_screen_manually_closed_ = true; + } + } + }); } void UICoordinator::DrawAllUI() { @@ -217,10 +263,28 @@ void UICoordinator::DrawLayoutPresets() { } void UICoordinator::DrawWelcomeScreen() { + // Auto-show welcome screen when no ROM is loaded (unless manually closed) + if (!show_welcome_screen_ && !welcome_screen_manually_closed_) { + // Check with EditorManager if we should show welcome screen + if (editor_manager_ && editor_manager_->GetActiveSessionCount() == 0) { + show_welcome_screen_ = true; + } + } + if (!show_welcome_screen_) return; if (welcome_screen_) { + // Update recent projects before showing + welcome_screen_->RefreshRecentProjects(); + + bool was_open = show_welcome_screen_; welcome_screen_->Show(&show_welcome_screen_); + + // Check if the welcome screen was manually closed via the close button + if (was_open && !show_welcome_screen_) { + welcome_screen_manually_closed_ = true; + welcome_screen_->MarkManuallyClosed(); + } } } @@ -247,19 +311,11 @@ void UICoordinator::HidePopup(const std::string& popup_name) { popup_manager_.Hide(popup_name.c_str()); } -void UICoordinator::ShowEditorSelection() { - show_editor_selection_ = true; -} - void UICoordinator::ShowDisplaySettings() { show_display_settings_ = true; ShowPopup("Display Settings"); } -void UICoordinator::ShowSessionSwitcher() { - show_session_switcher_ = true; -} - void UICoordinator::HideCurrentEditorCards() { // TODO: Implement card hiding logic // This would hide cards for the current editor diff --git a/src/app/editor/ui/ui_coordinator.h b/src/app/editor/ui/ui_coordinator.h index 56b04859..804c278c 100644 --- a/src/app/editor/ui/ui_coordinator.h +++ b/src/app/editor/ui/ui_coordinator.h @@ -78,26 +78,44 @@ class UICoordinator { void HidePopup(const std::string& popup_name); // UI state management - void ShowEditorSelection(); + void ShowEditorSelection() { show_editor_selection_ = true; } void ShowDisplaySettings(); - void ShowSessionSwitcher(); + void ShowSessionSwitcher() { show_session_switcher_ = true; } void HideCurrentEditorCards(); + void ToggleCardSidebar() { show_card_sidebar_ = !show_card_sidebar_; } + void ShowGlobalSearch() { show_global_search_ = true; } + void ShowCommandPalette() { show_command_palette_ = true; } + void ShowCardBrowser() { show_card_browser_ = true; } // Window visibility management void ShowAllWindows(); void HideAllWindows(); - // UI state queries + // UI state queries (EditorManager can check these) bool IsEditorSelectionVisible() const { return show_editor_selection_; } bool IsDisplaySettingsVisible() const { return show_display_settings_; } bool IsSessionSwitcherVisible() const { return show_session_switcher_; } bool IsWelcomeScreenVisible() const { return show_welcome_screen_; } + bool IsWelcomeScreenManuallyClosed() const { return welcome_screen_manually_closed_; } + bool IsGlobalSearchVisible() const { return show_global_search_; } + bool IsPerformanceDashboardVisible() const { return show_performance_dashboard_; } + bool IsCardBrowserVisible() const { return show_card_browser_; } + bool IsCommandPaletteVisible() const { return show_command_palette_; } + bool IsCardSidebarVisible() const { return show_card_sidebar_; } - // UI state setters + // UI state setters (for programmatic control) void SetEditorSelectionVisible(bool visible) { show_editor_selection_ = visible; } void SetDisplaySettingsVisible(bool visible) { show_display_settings_ = visible; } void SetSessionSwitcherVisible(bool visible) { show_session_switcher_ = visible; } void SetWelcomeScreenVisible(bool visible) { show_welcome_screen_ = visible; } + void SetWelcomeScreenManuallyClosed(bool closed) { welcome_screen_manually_closed_ = closed; } + void SetGlobalSearchVisible(bool visible) { show_global_search_ = visible; } + void SetPerformanceDashboardVisible(bool visible) { show_performance_dashboard_ = visible; } + void SetCardBrowserVisible(bool visible) { show_card_browser_ = visible; } + void SetCommandPaletteVisible(bool visible) { show_command_palette_ = visible; } + void SetCardSidebarVisible(bool visible) { show_card_sidebar_ = visible; } + void SetImGuiDemoVisible(bool visible) { show_imgui_demo_ = visible; } + void SetImGuiMetricsVisible(bool visible) { show_imgui_metrics_ = visible; } // Theme and styling helpers void ApplyMaterialDesignStyling(); @@ -115,16 +133,25 @@ class UICoordinator { ToastManager& toast_manager_; PopupManager& popup_manager_; - // UI state flags + // UI state flags (UICoordinator owns all UI visibility state) bool show_editor_selection_ = false; bool show_display_settings_ = false; bool show_session_switcher_ = false; bool show_welcome_screen_ = true; + bool welcome_screen_manually_closed_ = false; bool show_global_search_ = false; bool show_performance_dashboard_ = false; bool show_imgui_demo_ = false; bool show_imgui_metrics_ = false; bool show_test_dashboard_ = false; + bool show_card_browser_ = false; + bool show_command_palette_ = false; + bool show_emulator_ = false; + bool show_memory_editor_ = false; + bool show_asm_editor_ = false; + bool show_palette_editor_ = false; + bool show_resource_label_manager_ = false; + bool show_card_sidebar_ = false; // Welcome screen component std::unique_ptr welcome_screen_;