diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 06fda912..fb32791a 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -85,10 +85,22 @@ if(NOT YAZE_WITH_GRPC) endif() endif() +set(YAZE_PROTOBUF_TARGETS) + if(TARGET protobuf::libprotobuf) - set(YAZE_PROTOBUF_TARGET protobuf::libprotobuf) + list(APPEND YAZE_PROTOBUF_TARGETS protobuf::libprotobuf) elseif(TARGET libprotobuf) - set(YAZE_PROTOBUF_TARGET libprotobuf) + list(APPEND YAZE_PROTOBUF_TARGETS libprotobuf) +endif() + +if(TARGET protobuf::libprotobuf-lite) + list(APPEND YAZE_PROTOBUF_TARGETS protobuf::libprotobuf-lite) +elseif(TARGET libprotobuf-lite) + list(APPEND YAZE_PROTOBUF_TARGETS libprotobuf-lite) +endif() + +if(YAZE_PROTOBUF_TARGETS) + list(GET YAZE_PROTOBUF_TARGETS 0 YAZE_PROTOBUF_TARGET) else() set(YAZE_PROTOBUF_TARGET "") endif() diff --git a/src/app/app.cmake b/src/app/app.cmake index 4642eee7..f43a7f1c 100644 --- a/src/app/app.cmake +++ b/src/app/app.cmake @@ -40,8 +40,13 @@ target_link_libraries(yaze PRIVATE absl::flags absl::flags_parse ) -if(YAZE_WITH_GRPC AND YAZE_PROTOBUF_TARGET) - target_link_libraries(yaze PRIVATE ${YAZE_PROTOBUF_TARGET}) +if(YAZE_WITH_GRPC AND YAZE_PROTOBUF_TARGETS) + target_link_libraries(yaze PRIVATE ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(yaze PRIVATE /WHOLEARCHIVE:$) + endforeach() + endif() endif() # Link test support library (yaze_editor needs TestManager) diff --git a/src/app/core/controller.h b/src/app/core/controller.h index 08d28705..2e57c670 100644 --- a/src/app/core/controller.h +++ b/src/app/core/controller.h @@ -7,6 +7,7 @@ #include "absl/status/status.h" #include "app/core/window.h" +#include "app/rom.h" #include "app/editor/editor_manager.h" #include "app/gfx/backend/irenderer.h" diff --git a/src/app/core/core_library.cmake b/src/app/core/core_library.cmake index c239b459..9812c321 100644 --- a/src/app/core/core_library.cmake +++ b/src/app/core/core_library.cmake @@ -145,10 +145,12 @@ if(YAZE_WITH_GRPC) grpc++ grpc++_reflection ) - if(YAZE_PROTOBUF_TARGET) - target_link_libraries(yaze_core_lib PUBLIC ${YAZE_PROTOBUF_TARGET}) - if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_link_options(yaze_core_lib PUBLIC /WHOLEARCHIVE:$) + if(YAZE_PROTOBUF_TARGETS) + target_link_libraries(yaze_core_lib PUBLIC ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(yaze_core_lib PUBLIC /WHOLEARCHIVE:$) + endforeach() endif() endif() diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index 39bcb4b3..b6117bbe 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -31,7 +31,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { auto& card_manager = gui::EditorCardManager::Get(); card_manager.RegisterCard({ - .card_id = "dungeon.control_panel", + .card_id = MakeCardId("dungeon.control_panel"), .display_name = "Dungeon Controls", .icon = ICON_MD_CASTLE, .category = "Dungeon", @@ -41,7 +41,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.room_selector", + .card_id = MakeCardId("dungeon.room_selector"), .display_name = "Room Selector", .icon = ICON_MD_LIST, .category = "Dungeon", @@ -51,7 +51,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.room_matrix", + .card_id = MakeCardId("dungeon.room_matrix"), .display_name = "Room Matrix", .icon = ICON_MD_GRID_VIEW, .category = "Dungeon", @@ -61,7 +61,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.entrances", + .card_id = MakeCardId("dungeon.entrances"), .display_name = "Entrances", .icon = ICON_MD_DOOR_FRONT, .category = "Dungeon", @@ -71,7 +71,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.room_graphics", + .card_id = MakeCardId("dungeon.room_graphics"), .display_name = "Room Graphics", .icon = ICON_MD_IMAGE, .category = "Dungeon", @@ -81,7 +81,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.object_editor", + .card_id = MakeCardId("dungeon.object_editor"), .display_name = "Object Editor", .icon = ICON_MD_CONSTRUCTION, .category = "Dungeon", @@ -91,7 +91,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.palette_editor", + .card_id = MakeCardId("dungeon.palette_editor"), .display_name = "Palette Editor", .icon = ICON_MD_PALETTE, .category = "Dungeon", @@ -101,7 +101,7 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { }); card_manager.RegisterCard({ - .card_id = "dungeon.debug_controls", + .card_id = MakeCardId("dungeon.debug_controls"), .display_name = "Debug Controls", .icon = ICON_MD_BUG_REPORT, .category = "Dungeon", diff --git a/src/app/editor/editor.h b/src/app/editor/editor.h index 0c1e91fe..2b0dbbfe 100644 --- a/src/app/editor/editor.h +++ b/src/app/editor/editor.h @@ -131,6 +131,14 @@ class Editor { } return base_title; } + + // Helper method to create session-aware card IDs for multi-session support + std::string MakeCardId(const std::string& base_id) const { + if (context_ && context_->session_id > 0) { + return absl::StrFormat("s%zu.%s", context_->session_id, base_id); + } + return base_id; + } // Helper method for ROM access with safety check template diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index aa90dc76..c259f471 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -36,11 +36,14 @@ set( app/editor/system/extension_manager.cc app/editor/system/popup_manager.cc app/editor/system/proposal_drawer.cc + app/editor/system/session_card_registry.cc app/editor/system/settings_editor.cc app/editor/system/shortcut_manager.cc app/editor/system/user_settings.cc + app/editor/system/window_delegate.cc app/editor/ui/editor_selection_dialog.cc app/editor/ui/menu_builder.cc + app/editor/ui/session_coordinator.cc app/editor/ui/welcome_screen.cc app/editor/ui/workspace_manager.cc ) @@ -143,8 +146,13 @@ if(YAZE_WITH_GRPC) grpc++ grpc++_reflection ) - if(YAZE_PROTOBUF_TARGET) - target_link_libraries(yaze_editor PRIVATE ${YAZE_PROTOBUF_TARGET}) + if(YAZE_PROTOBUF_TARGETS) + target_link_libraries(yaze_editor PRIVATE ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(yaze_editor PRIVATE /WHOLEARCHIVE:$) + endforeach() + endif() endif() endif() diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 01be203a..00055c29 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -189,6 +189,10 @@ EditorManager::EditorManager() : blank_editor_set_(nullptr, &user_settings_) { << YAZE_VERSION_PATCH; ss >> version_; context_.popup_manager = popup_manager_.get(); + + // Initialize new delegated components + session_coordinator_ = std::make_unique( + static_cast(&sessions_), &card_registry_, &toast_manager_); } EditorManager::~EditorManager() = default; @@ -901,6 +905,9 @@ absl::Status EditorManager::Update() { if (!session.rom.is_loaded()) continue; // Skip sessions with invalid ROMs + // Use RAII SessionScope for clean context switching + SessionScope scope(this, session_idx); + for (auto editor : session.editors.active_editors_) { if (*editor->active()) { if (editor->type() == EditorType::kOverworld) { @@ -920,14 +927,7 @@ absl::Status EditorManager::Update() { if (is_card_based_editor) { // Card-based editors create their own top-level windows // No parent wrapper needed - this allows independent docking - Rom* prev_rom = current_rom_; - EditorSet* prev_editor_set = current_editor_set_; - size_t prev_session_id = context_.session_id; - - current_rom_ = &session.rom; - current_editor_set_ = &session.editors; current_editor_ = editor; - context_.session_id = session_idx; status_ = editor->Update(); @@ -939,11 +939,6 @@ absl::Status EditorManager::Update() { editor::ToastType::kError, 8.0f); } - // Restore context - current_rom_ = prev_rom; - current_editor_set_ = prev_editor_set; - context_.session_id = prev_session_id; - } else { // TRADITIONAL EDITORS: Wrap in Begin/End std::string window_title = @@ -1060,6 +1055,13 @@ absl::Status EditorManager::Update() { } } + // Draw SessionCoordinator UI components + if (session_coordinator_) { + session_coordinator_->DrawSessionSwitcher(); + session_coordinator_->DrawSessionManager(); + session_coordinator_->DrawSessionRenameDialog(); + } + return absl::OkStatus(); } @@ -1136,9 +1138,14 @@ void EditorManager::DrawContextSensitiveCardControl() { return; // No cards for this editor type } - // Draw compact card control for the active editor's cards + // Draw compact card control for the active editor's cards with session awareness auto& card_manager = gui::EditorCardManager::Get(); - card_manager.DrawCompactCardControl(category); + if (session_coordinator_ && session_coordinator_->HasMultipleSessions()) { + std::string session_prefix = absl::StrFormat("s%zu", context_.session_id); + card_manager.DrawCompactCardControlWithSession(category, session_prefix); + } else { + card_manager.DrawCompactCardControl(category); + } // Show visible/total count SameLine(); @@ -1672,15 +1679,15 @@ void EditorManager::DrawMenuBarExtras() { SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width); - if (GetActiveSessionCount() > 1) { + if (session_coordinator_ && session_coordinator_->HasMultipleSessions()) { if (ImGui::SmallButton( - absl::StrFormat("%s%zu", ICON_MD_TAB, GetActiveSessionCount()) + absl::StrFormat("%s%zu", ICON_MD_TAB, session_coordinator_->GetActiveSessionCount()) .c_str())) { - ShowSessionSwitcher(); + session_coordinator_->ToggleSessionSwitcher(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Sessions: %zu active\nClick to switch", - GetActiveSessionCount()); + session_coordinator_->GetActiveSessionCount()); } ImGui::SameLine(); } @@ -1724,7 +1731,9 @@ void EditorManager::DrawMenuBarExtras() { } void EditorManager::ShowSessionSwitcher() { - show_session_switcher_ = true; + if (session_coordinator_) { + session_coordinator_->ShowSessionSwitcher(); + } } void EditorManager::ShowEditorSelection() { @@ -2403,7 +2412,8 @@ absl::Status EditorManager::LoadRom() { current_editor_set_ = &target_session->editors; } else { // Create new session only if no empty ones exist - sessions_.emplace_back(std::move(temp_rom), &user_settings_); + size_t new_session_id = sessions_.size(); + sessions_.emplace_back(std::move(temp_rom), &user_settings_, new_session_id); RomSession& session = sessions_.back(); session.filepath = file_name; // Store filepath for duplicate detection @@ -2801,22 +2811,17 @@ absl::Status EditorManager::SetCurrentRom(Rom* rom) { } void EditorManager::CreateNewSession() { - // Check session limit - if (sessions_.size() >= 8) { - popup_manager_->Show("Session Limit Warning"); - return; - } - - // Create a blank session - sessions_.emplace_back(); - RomSession& session = sessions_.back(); - - // Set user settings for the blank session - session.editors.set_user_settings(&user_settings_); - - // Wire editor contexts for new session - for (auto* editor : session.editors.active_editors_) { - editor->set_context(&context_); + if (session_coordinator_) { + session_coordinator_->CreateNewSession(); + + // Wire editor contexts for new session + if (!sessions_.empty()) { + RomSession& session = sessions_.back(); + session.editors.set_user_settings(&user_settings_); + for (auto* editor : session.editors.active_editors_) { + editor->set_context(&context_); + } + } } // Don't switch to the new session automatically @@ -2839,105 +2844,76 @@ void EditorManager::DuplicateCurrentSession() { return; } - // Create a copy of the current ROM - Rom rom_copy = *current_rom_; - sessions_.emplace_back(std::move(rom_copy), &user_settings_); - RomSession& session = sessions_.back(); - - // Wire editor contexts - for (auto* editor : session.editors.active_editors_) { - editor->set_context(&context_); + if (session_coordinator_) { + session_coordinator_->DuplicateCurrentSession(); + + // Wire editor contexts for duplicated session + if (!sessions_.empty()) { + RomSession& session = sessions_.back(); + for (auto* editor : session.editors.active_editors_) { + editor->set_context(&context_); + } + } } - - toast_manager_.Show( - absl::StrFormat("Session duplicated (Session %zu)", sessions_.size()), - editor::ToastType::kSuccess); } void EditorManager::CloseCurrentSession() { - if (GetActiveSessionCount() <= 1) { - toast_manager_.Show("Cannot close the last active session", - editor::ToastType::kWarning); - return; - } - - // Find current session index - size_t current_index = GetCurrentSessionIndex(); - - // Switch to another active session before removing current one - size_t next_index = 0; - for (size_t i = 0; i < sessions_.size(); ++i) { - if (i != current_index && sessions_[i].custom_name != "[CLOSED SESSION]") { - next_index = i; - break; + if (session_coordinator_) { + session_coordinator_->CloseCurrentSession(); + + // Update current pointers after session change + if (!sessions_.empty()) { + size_t active_index = session_coordinator_->GetActiveSessionIndex(); + if (active_index < sessions_.size()) { + current_rom_ = &sessions_[active_index].rom; + current_editor_set_ = &sessions_[active_index].editors; + test::TestManager::Get().SetCurrentRom(current_rom_); + } } } - - current_rom_ = &sessions_[next_index].rom; - current_editor_set_ = &sessions_[next_index].editors; - test::TestManager::Get().SetCurrentRom(current_rom_); - - // Now remove the current session - RemoveSession(current_index); - - toast_manager_.Show("Session closed successfully", - editor::ToastType::kSuccess); } void EditorManager::RemoveSession(size_t index) { - if (index >= sessions_.size()) { - toast_manager_.Show("Invalid session index for removal", - editor::ToastType::kError); - return; + if (session_coordinator_) { + session_coordinator_->RemoveSession(index); + + // Update current pointers after session change + if (!sessions_.empty()) { + size_t active_index = session_coordinator_->GetActiveSessionIndex(); + if (active_index < sessions_.size()) { + current_rom_ = &sessions_[active_index].rom; + current_editor_set_ = &sessions_[active_index].editors; + test::TestManager::Get().SetCurrentRom(current_rom_); + } + } } - - if (GetActiveSessionCount() <= 1) { - toast_manager_.Show("Cannot remove the last active session", - editor::ToastType::kWarning); - return; - } - - // Get session info for logging - std::string session_name = sessions_[index].GetDisplayName(); - - // For now, mark the session as invalid instead of removing it from the deque - // This is a safer approach until RomSession becomes fully movable - sessions_[index].rom.Close(); // Close the ROM to mark as invalid - sessions_[index].custom_name = "[CLOSED SESSION]"; - sessions_[index].filepath = ""; - - LOG_DEBUG("EditorManager", "Marked session as closed: %s (index %zu)", - session_name.c_str(), index); - toast_manager_.Show( - absl::StrFormat("Session marked as closed: %s", session_name), - editor::ToastType::kInfo); - - // TODO: Implement proper session removal when EditorSet becomes movable - // The current workaround marks sessions as closed instead of removing them } void EditorManager::SwitchToSession(size_t index) { - if (index >= sessions_.size()) { - toast_manager_.Show("Invalid session index", editor::ToastType::kError); - return; + if (session_coordinator_) { + session_coordinator_->SwitchToSession(index); + + // Update current pointers after session switch + if (index < sessions_.size()) { + auto& session = sessions_[index]; + current_rom_ = &session.rom; + current_editor_set_ = &session.editors; + + // Update test manager with current ROM for ROM-dependent tests + util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", + (void*)current_rom_, + current_rom_ ? current_rom_->title().c_str() : "null"); + test::TestManager::Get().SetCurrentRom(current_rom_); + } } - - auto& session = sessions_[index]; - current_rom_ = &session.rom; - current_editor_set_ = &session.editors; - - // Update test manager with current ROM for ROM-dependent tests - util::logf("EditorManager: Setting ROM in TestManager - %p ('%s')", - (void*)current_rom_, - current_rom_ ? current_rom_->title().c_str() : "null"); - test::TestManager::Get().SetCurrentRom(current_rom_); - - std::string session_name = session.GetDisplayName(); - toast_manager_.Show(absl::StrFormat("Switched to %s", session_name), - editor::ToastType::kInfo); } size_t EditorManager::GetCurrentSessionIndex() const { + if (session_coordinator_) { + return session_coordinator_->GetActiveSessionIndex(); + } + + // Fallback to finding by ROM pointer for (size_t i = 0; i < sessions_.size(); ++i) { if (&sessions_[i].rom == current_rom_ && sessions_[i].custom_name != "[CLOSED SESSION]") { @@ -2948,6 +2924,11 @@ size_t EditorManager::GetCurrentSessionIndex() const { } size_t EditorManager::GetActiveSessionCount() const { + if (session_coordinator_) { + return session_coordinator_->GetActiveSessionCount(); + } + + // Fallback to counting non-closed sessions size_t count = 0; for (const auto& session : sessions_) { if (session.custom_name != "[CLOSED SESSION]") { @@ -3706,5 +3687,27 @@ void EditorManager::SaveUserSettings() { } } +// SessionScope implementation +EditorManager::SessionScope::SessionScope(EditorManager* manager, size_t session_id) + : manager_(manager), + prev_rom_(manager->current_rom_), + prev_editor_set_(manager->current_editor_set_), + prev_session_id_(manager->context_.session_id) { + + // Set new session context + if (session_id < manager->sessions_.size()) { + manager->current_rom_ = &manager->sessions_[session_id].rom; + manager->current_editor_set_ = &manager->sessions_[session_id].editors; + manager->context_.session_id = session_id; + } +} + +EditorManager::SessionScope::~SessionScope() { + // Restore previous context + manager_->current_rom_ = prev_rom_; + manager_->current_editor_set_ = prev_editor_set_; + manager_->context_.session_id = prev_session_id_; +} + } // namespace editor } // namespace yaze diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 8268dc18..bf051585 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -35,11 +35,14 @@ #endif #include "app/editor/system/settings_editor.h" #include "app/editor/system/toast_manager.h" +#include "app/rom.h" +#include "app/editor/system/session_card_registry.h" +#include "app/editor/system/window_delegate.h" +#include "app/editor/ui/session_coordinator.h" #include "app/editor/ui/editor_selection_dialog.h" #include "app/editor/ui/welcome_screen.h" #include "app/emu/emulator.h" #include "app/gfx/debug/performance/performance_dashboard.h" -#include "app/rom.h" #include "yaze_config.h" #ifdef YAZE_WITH_GRPC @@ -58,8 +61,9 @@ namespace editor { */ class EditorSet { public: - explicit EditorSet(Rom* rom = nullptr, UserSettings* user_settings = nullptr) - : assembly_editor_(rom), + explicit EditorSet(Rom* rom = nullptr, UserSettings* user_settings = nullptr, size_t session_id = 0) + : session_id_(session_id), + assembly_editor_(rom), dungeon_editor_(rom), graphics_editor_(rom), music_editor_(rom), @@ -79,6 +83,8 @@ class EditorSet { void set_user_settings(UserSettings* settings) { settings_editor_.set_user_settings(settings); } + + size_t session_id() const { return session_id_; } AssemblyEditor assembly_editor_; DungeonEditorV2 dungeon_editor_; @@ -93,6 +99,9 @@ class EditorSet { MemoryEditorWithDiffChecker memory_editor_; std::vector active_editors_; + + private: + size_t session_id_ = 0; }; /** @@ -297,6 +306,7 @@ class EditorManager { absl::Status status_; emu::Emulator emulator_; + public: struct RomSession { Rom rom; EditorSet editors; @@ -305,8 +315,8 @@ class EditorManager { core::FeatureFlags::Flags feature_flags; // Per-session feature flags RomSession() = default; - explicit RomSession(Rom&& r, UserSettings* user_settings = nullptr) - : rom(std::move(r)), editors(&rom, user_settings) { + explicit RomSession(Rom&& r, UserSettings* user_settings = nullptr, size_t session_id = 0) + : rom(std::move(r)), editors(&rom, user_settings, session_id) { filepath = rom.filename(); // Initialize with default feature flags feature_flags = core::FeatureFlags::Flags{}; @@ -321,6 +331,8 @@ class EditorManager { } }; + private: + std::deque sessions_; Rom* current_rom_ = nullptr; EditorSet* current_editor_set_ = nullptr; @@ -337,7 +349,25 @@ class EditorManager { UserSettings user_settings_; WorkspaceManager workspace_manager_{&toast_manager_}; + // New delegated components + SessionCardRegistry card_registry_; + WindowDelegate window_delegate_; + std::unique_ptr session_coordinator_; + float autosave_timer_ = 0.0f; + + // RAII helper for clean session context switching + class SessionScope { + public: + SessionScope(EditorManager* manager, size_t session_id); + ~SessionScope(); + + private: + EditorManager* manager_; + Rom* prev_rom_; + EditorSet* prev_editor_set_; + size_t prev_session_id_; + }; }; } // namespace editor diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 75f5de89..fcc9d438 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -53,7 +53,7 @@ void OverworldEditor::Initialize() { // Register Overworld Canvas (main canvas card with toolset) card_manager.RegisterCard({ - .card_id = "overworld.canvas", + .card_id = MakeCardId("overworld.canvas"), .display_name = "Overworld Canvas", .icon = ICON_MD_MAP, .category = "Overworld", @@ -63,7 +63,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.tile16_selector", + .card_id = MakeCardId("overworld.tile16_selector"), .display_name = "Tile16 Selector", .icon = ICON_MD_GRID_ON, .category = "Overworld", @@ -73,7 +73,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.tile8_selector", + .card_id = MakeCardId("overworld.tile8_selector"), .display_name = "Tile8 Selector", .icon = ICON_MD_GRID_3X3, .category = "Overworld", @@ -83,7 +83,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.area_graphics", + .card_id = MakeCardId("overworld.area_graphics"), .display_name = "Area Graphics", .icon = ICON_MD_IMAGE, .category = "Overworld", @@ -93,7 +93,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.scratch", + .card_id = MakeCardId("overworld.scratch"), .display_name = "Scratch Workspace", .icon = ICON_MD_DRAW, .category = "Overworld", @@ -103,7 +103,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.gfx_groups", + .card_id = MakeCardId("overworld.gfx_groups"), .display_name = "GFX Groups", .icon = ICON_MD_FOLDER, .category = "Overworld", @@ -113,7 +113,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.usage_stats", + .card_id = MakeCardId("overworld.usage_stats"), .display_name = "Usage Statistics", .icon = ICON_MD_ANALYTICS, .category = "Overworld", @@ -123,7 +123,7 @@ void OverworldEditor::Initialize() { }); card_manager.RegisterCard({ - .card_id = "overworld.v3_settings", + .card_id = MakeCardId("overworld.v3_settings"), .display_name = "v3 Settings", .icon = ICON_MD_SETTINGS, .category = "Overworld", diff --git a/src/app/editor/system/session_card_registry.cc b/src/app/editor/system/session_card_registry.cc new file mode 100644 index 00000000..0f756d6f --- /dev/null +++ b/src/app/editor/system/session_card_registry.cc @@ -0,0 +1,250 @@ +#include "session_card_registry.h" + +#include +#include + +#include "absl/strings/str_format.h" + +namespace yaze { +namespace editor { + +void SessionCardRegistry::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("[SessionCardRegistry] Registered session %zu (total: %zu)\n", + session_id, session_count_); + } +} + +void SessionCardRegistry::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("[SessionCardRegistry] Unregistered session %zu (total: %zu)\n", + session_id, session_count_); + } +} + +void SessionCardRegistry::SetActiveSession(size_t session_id) { + if (session_cards_.find(session_id) != session_cards_.end()) { + active_session_ = session_id; + printf("[SessionCardRegistry] Set active session to %zu\n", session_id); + } +} + +void SessionCardRegistry::RegisterCard(size_t session_id, const gui::CardInfo& base_info) { + RegisterSession(session_id); // Ensure session exists + + std::string prefixed_id = MakeCardId(session_id, base_info.card_id); + + // Create new CardInfo with prefixed ID + gui::CardInfo prefixed_info = base_info; + prefixed_info.card_id = prefixed_id; + + // Register with the global card manager + auto& card_mgr = gui::EditorCardManager::Get(); + card_mgr.RegisterCard(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("[SessionCardRegistry] Registered card %s -> %s for session %zu\n", + base_info.card_id.c_str(), prefixed_id.c_str(), session_id); +} + +void SessionCardRegistry::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) { + gui::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.on_show = on_show; + info.on_hide = on_hide; + + RegisterCard(session_id, info); +} + +bool SessionCardRegistry::ShowCard(size_t session_id, const std::string& base_card_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + return card_mgr.ShowCard(prefixed_id); +} + +bool SessionCardRegistry::HideCard(size_t session_id, const std::string& base_card_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + return card_mgr.HideCard(prefixed_id); +} + +bool SessionCardRegistry::ToggleCard(size_t session_id, const std::string& base_card_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + return card_mgr.ToggleCard(prefixed_id); +} + +bool SessionCardRegistry::IsCardVisible(size_t session_id, const std::string& base_card_id) const { + const auto& card_mgr = gui::EditorCardManager::Get(); + std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); + if (prefixed_id.empty()) { + return false; + } + return card_mgr.IsCardVisible(prefixed_id); +} + +void SessionCardRegistry::ShowAllCardsInSession(size_t session_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + card_mgr.ShowCard(card_id); + } + } +} + +void SessionCardRegistry::HideAllCardsInSession(size_t session_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + card_mgr.HideCard(card_id); + } + } +} + +void SessionCardRegistry::ShowAllCardsInCategory(size_t session_id, const std::string& category) { + auto& card_mgr = gui::EditorCardManager::Get(); + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + const auto* info = card_mgr.GetCardInfo(card_id); + if (info && info->category == category) { + card_mgr.ShowCard(card_id); + } + } + } +} + +void SessionCardRegistry::HideAllCardsInCategory(size_t session_id, const std::string& category) { + auto& card_mgr = gui::EditorCardManager::Get(); + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + const auto* info = card_mgr.GetCardInfo(card_id); + if (info && info->category == category) { + card_mgr.HideCard(card_id); + } + } + } +} + +std::string SessionCardRegistry::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; +} + +std::vector SessionCardRegistry::GetCardsInSession(size_t session_id) const { + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + return it->second; + } + return {}; +} + +std::vector SessionCardRegistry::GetCardsInCategory(size_t session_id, const std::string& category) const { + std::vector result; + const auto& card_mgr = gui::EditorCardManager::Get(); + + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + const auto* info = card_mgr.GetCardInfo(card_id); + if (info && info->category == category) { + result.push_back(card_id); + } + } + } + return result; +} + +std::vector SessionCardRegistry::GetAllCategories(size_t session_id) const { + std::vector categories; + const auto& card_mgr = gui::EditorCardManager::Get(); + + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + const auto* info = card_mgr.GetCardInfo(card_id); + if (info) { + if (std::find(categories.begin(), categories.end(), info->category) == categories.end()) { + categories.push_back(info->category); + } + } + } + } + return categories; +} + +void SessionCardRegistry::UpdateSessionCount() { + session_count_ = session_cards_.size(); +} + +std::string SessionCardRegistry::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; + } + } + return ""; // Card not found +} + +void SessionCardRegistry::UnregisterSessionCards(size_t session_id) { + auto& card_mgr = gui::EditorCardManager::Get(); + auto it = session_cards_.find(session_id); + if (it != session_cards_.end()) { + for (const auto& card_id : it->second) { + card_mgr.UnregisterCard(card_id); + } + } +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/system/session_card_registry.h b/src/app/editor/system/session_card_registry.h new file mode 100644 index 00000000..1128d546 --- /dev/null +++ b/src/app/editor/system/session_card_registry.h @@ -0,0 +1,96 @@ +#ifndef YAZE_APP_EDITOR_SYSTEM_SESSION_CARD_REGISTRY_H_ +#define YAZE_APP_EDITOR_SYSTEM_SESSION_CARD_REGISTRY_H_ + +#include +#include +#include + +#include "app/gui/app/editor_card_manager.h" + +namespace yaze { +namespace editor { + +/** + * @class SessionCardRegistry + * @brief Manages session-scoped card registration with automatic prefixing + * + * This class wraps EditorCardManager to provide session awareness: + * - Automatically prefixes card IDs when multiple sessions exist + * - Manages per-session card lifecycle (create/destroy/switch) + * - Maintains session-specific card visibility state + * - Provides clean API for session-aware card operations + * + * Card ID Format: + * - Single session: "dungeon.room_selector" + * - Multiple sessions: "s0.dungeon.room_selector", "s1.dungeon.room_selector" + */ +class SessionCardRegistry { + public: + SessionCardRegistry() = default; + ~SessionCardRegistry() = default; + + // Session lifecycle management + void RegisterSession(size_t session_id); + void UnregisterSession(size_t session_id); + void SetActiveSession(size_t session_id); + + // Card registration with session awareness + void RegisterCard(size_t session_id, const gui::CardInfo& base_info); + 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); + + // Card control with session awareness + bool ShowCard(size_t session_id, const std::string& base_card_id); + bool HideCard(size_t session_id, const std::string& base_card_id); + bool ToggleCard(size_t session_id, const std::string& base_card_id); + bool IsCardVisible(size_t session_id, const std::string& base_card_id) const; + + // Batch operations + void ShowAllCardsInSession(size_t session_id); + void HideAllCardsInSession(size_t session_id); + void ShowAllCardsInCategory(size_t session_id, const std::string& category); + void HideAllCardsInCategory(size_t session_id, const std::string& category); + + // Utility methods + std::string MakeCardId(size_t session_id, const std::string& base_id) const; + bool ShouldPrefixCards() const { return session_count_ > 1; } + size_t GetSessionCount() const { return session_count_; } + size_t GetActiveSession() const { return active_session_; } + + // Query methods + std::vector GetCardsInSession(size_t session_id) const; + std::vector GetCardsInCategory(size_t session_id, const std::string& category) const; + std::vector GetAllCategories(size_t session_id) const; + + // Direct access to underlying card manager (for UI components) + gui::EditorCardManager& GetCardManager() { return gui::EditorCardManager::Get(); } + const gui::EditorCardManager& GetCardManager() const { return gui::EditorCardManager::Get(); } + + private: + size_t session_count_ = 0; + size_t active_session_ = 0; + + // Maps session_id -> vector of card IDs registered for that session + std::unordered_map> session_cards_; + + // Maps session_id -> map of base_card_id -> prefixed_card_id + std::unordered_map> session_card_mapping_; + + // Helper methods + void UpdateSessionCount(); + std::string GetPrefixedCardId(size_t session_id, const std::string& base_id) const; + void UnregisterSessionCards(size_t session_id); +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_SYSTEM_SESSION_CARD_REGISTRY_H_ diff --git a/src/app/editor/system/window_delegate.cc b/src/app/editor/system/window_delegate.cc new file mode 100644 index 00000000..cae8796e --- /dev/null +++ b/src/app/editor/system/window_delegate.cc @@ -0,0 +1,298 @@ +#include "window_delegate.h" + +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +void WindowDelegate::ShowAllWindows() { + // This is a placeholder - actual implementation would need to track + // all registered windows and set their visibility flags + printf("[WindowDelegate] ShowAllWindows() - %zu windows registered\n", + registered_windows_.size()); +} + +void WindowDelegate::HideAllWindows() { + // This is a placeholder - actual implementation would need to track + // all registered windows and set their visibility flags + printf("[WindowDelegate] HideAllWindows() - %zu windows registered\n", + registered_windows_.size()); +} + +void WindowDelegate::ShowWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] ShowWindow: %s\n", window_id.c_str()); + // Actual implementation would set window visibility flag + } +} + +void WindowDelegate::HideWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] HideWindow: %s\n", window_id.c_str()); + // Actual implementation would set window visibility flag + } +} + +void WindowDelegate::ToggleWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] ToggleWindow: %s\n", window_id.c_str()); + // Actual implementation would toggle window visibility flag + } +} + +bool WindowDelegate::IsWindowVisible(const std::string& window_id) const { + if (!IsWindowRegistered(window_id)) { + return false; + } + // Actual implementation would check window visibility flag + return true; // Placeholder +} + +void WindowDelegate::FocusWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] FocusWindow: %s\n", window_id.c_str()); + // Actual implementation would bring window to front and focus it + } +} + +void WindowDelegate::MaximizeWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] MaximizeWindow: %s\n", window_id.c_str()); + // Actual implementation would maximize the window + } +} + +void WindowDelegate::RestoreWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] RestoreWindow: %s\n", window_id.c_str()); + // Actual implementation would restore the window from maximized state + } +} + +void WindowDelegate::CenterWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] CenterWindow: %s\n", window_id.c_str()); + // Actual implementation would center the window on screen + } +} + +void WindowDelegate::DockWindow(const std::string& window_id, ImGuiDir dock_direction) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] DockWindow: %s to direction %d\n", + window_id.c_str(), static_cast(dock_direction)); + // Actual implementation would dock the window + } +} + +void WindowDelegate::UndockWindow(const std::string& window_id) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] UndockWindow: %s\n", window_id.c_str()); + // Actual implementation would undock the window + } +} + +void WindowDelegate::SetDockSpace(const std::string& dock_space_id, const ImVec2& size) { + printf("[WindowDelegate] SetDockSpace: %s (%.1f x %.1f)\n", + dock_space_id.c_str(), size.x, size.y); + // Actual implementation would create/configure dock space +} + +absl::Status WindowDelegate::SaveLayout(const std::string& preset_name) { + if (preset_name.empty()) { + return absl::InvalidArgumentError("Layout preset name cannot be empty"); + } + + std::string file_path = GetLayoutFilePath(preset_name); + + try { + // Create directory if it doesn't exist + std::filesystem::path dir = std::filesystem::path(file_path).parent_path(); + if (!std::filesystem::exists(dir)) { + std::filesystem::create_directories(dir); + } + + // Save layout data (placeholder implementation) + std::ofstream file(file_path); + if (!file.is_open()) { + return absl::InternalError(absl::StrFormat("Failed to open layout file: %s", file_path)); + } + + file << "# YAZE Layout Preset: " << preset_name << "\n"; + file << "# Generated by WindowDelegate\n"; + file << "# TODO: Implement actual layout serialization\n"; + + file.close(); + + printf("[WindowDelegate] Saved layout: %s\n", preset_name.c_str()); + return absl::OkStatus(); + + } catch (const std::exception& e) { + return absl::InternalError(absl::StrFormat("Failed to save layout: %s", e.what())); + } +} + +absl::Status WindowDelegate::LoadLayout(const std::string& preset_name) { + if (preset_name.empty()) { + return absl::InvalidArgumentError("Layout preset name cannot be empty"); + } + + std::string file_path = GetLayoutFilePath(preset_name); + + try { + if (!std::filesystem::exists(file_path)) { + return absl::NotFoundError(absl::StrFormat("Layout file not found: %s", file_path)); + } + + std::ifstream file(file_path); + if (!file.is_open()) { + return absl::InternalError(absl::StrFormat("Failed to open layout file: %s", file_path)); + } + + // Load layout data (placeholder implementation) + std::string line; + while (std::getline(file, line)) { + // TODO: Parse and apply layout data + } + + file.close(); + + printf("[WindowDelegate] Loaded layout: %s\n", preset_name.c_str()); + return absl::OkStatus(); + + } catch (const std::exception& e) { + return absl::InternalError(absl::StrFormat("Failed to load layout: %s", e.what())); + } +} + +absl::Status WindowDelegate::ResetLayout() { + printf("[WindowDelegate] ResetLayout()\n"); + // Actual implementation would reset to default layout + return absl::OkStatus(); +} + +std::vector WindowDelegate::GetAvailableLayouts() const { + std::vector layouts; + + try { + // Look for layout files in config directory + std::string config_dir = "config/layouts"; // TODO: Use proper config path + if (std::filesystem::exists(config_dir)) { + for (const auto& entry : std::filesystem::directory_iterator(config_dir)) { + if (entry.is_regular_file() && entry.path().extension() == ".ini") { + layouts.push_back(entry.path().stem().string()); + } + } + } + } catch (const std::exception& e) { + printf("[WindowDelegate] Error scanning layouts: %s\n", e.what()); + } + + return layouts; +} + +std::vector WindowDelegate::GetVisibleWindows() const { + std::vector visible; + // TODO: Implement actual visibility checking + return visible; +} + +std::vector WindowDelegate::GetHiddenWindows() const { + std::vector hidden; + // TODO: Implement actual visibility checking + return hidden; +} + +ImVec2 WindowDelegate::GetWindowSize(const std::string& window_id) const { + if (!IsWindowRegistered(window_id)) { + return ImVec2(0, 0); + } + // TODO: Implement actual size retrieval + return ImVec2(400, 300); // Placeholder +} + +ImVec2 WindowDelegate::GetWindowPosition(const std::string& window_id) const { + if (!IsWindowRegistered(window_id)) { + return ImVec2(0, 0); + } + // TODO: Implement actual position retrieval + return ImVec2(100, 100); // Placeholder +} + +void WindowDelegate::ShowWindowsInCategory(const std::string& category) { + printf("[WindowDelegate] ShowWindowsInCategory: %s\n", category.c_str()); + // TODO: Implement category-based window showing +} + +void WindowDelegate::HideWindowsInCategory(const std::string& category) { + printf("[WindowDelegate] HideWindowsInCategory: %s\n", category.c_str()); + // TODO: Implement category-based window hiding +} + +void WindowDelegate::ShowOnlyWindow(const std::string& window_id) { + printf("[WindowDelegate] ShowOnlyWindow: %s\n", window_id.c_str()); + // TODO: Implement show-only functionality +} + +void WindowDelegate::RegisterWindow(const std::string& window_id, const std::string& category) { + WindowInfo info; + info.id = window_id; + info.category = category; + info.is_registered = true; + + registered_windows_[window_id] = info; + printf("[WindowDelegate] Registered window: %s (category: %s)\n", + window_id.c_str(), category.c_str()); +} + +void WindowDelegate::UnregisterWindow(const std::string& window_id) { + auto it = registered_windows_.find(window_id); + if (it != registered_windows_.end()) { + registered_windows_.erase(it); + printf("[WindowDelegate] Unregistered window: %s\n", window_id.c_str()); + } +} + +void WindowDelegate::LoadDeveloperLayout() { + printf("[WindowDelegate] LoadDeveloperLayout()\n"); + // TODO: Implement developer-specific layout +} + +void WindowDelegate::LoadDesignerLayout() { + printf("[WindowDelegate] LoadDesignerLayout()\n"); + // TODO: Implement designer-specific layout +} + +void WindowDelegate::LoadModderLayout() { + printf("[WindowDelegate] LoadModderLayout()\n"); + // TODO: Implement modder-specific layout +} + +void WindowDelegate::LoadMinimalLayout() { + printf("[WindowDelegate] LoadMinimalLayout()\n"); + // TODO: Implement minimal layout +} + +bool WindowDelegate::IsWindowRegistered(const std::string& window_id) const { + auto it = registered_windows_.find(window_id); + return it != registered_windows_.end() && it->second.is_registered; +} + +std::string WindowDelegate::GetLayoutFilePath(const std::string& preset_name) const { + // TODO: Use proper config directory path + return absl::StrFormat("config/layouts/%s.ini", preset_name); +} + +void WindowDelegate::ApplyLayoutToWindow(const std::string& window_id, const std::string& layout_data) { + if (IsWindowRegistered(window_id)) { + printf("[WindowDelegate] ApplyLayoutToWindow: %s\n", window_id.c_str()); + // TODO: Implement layout application + } +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/system/window_delegate.h b/src/app/editor/system/window_delegate.h new file mode 100644 index 00000000..19a6e2e5 --- /dev/null +++ b/src/app/editor/system/window_delegate.h @@ -0,0 +1,96 @@ +#ifndef YAZE_APP_EDITOR_SYSTEM_WINDOW_DELEGATE_H_ +#define YAZE_APP_EDITOR_SYSTEM_WINDOW_DELEGATE_H_ + +#include +#include + +#include "absl/status/status.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +/** + * @class WindowDelegate + * @brief Low-level window operations with minimal dependencies + * + * Provides window management functionality extracted from EditorManager: + * - Window visibility management + * - Docking operations + * - Layout persistence + * - Focus management + * + * This class has minimal dependencies (only ImGui and absl) to avoid + * linker issues and circular dependencies. + */ +class WindowDelegate { + public: + WindowDelegate() = default; + ~WindowDelegate() = default; + + // Window visibility management + void ShowAllWindows(); + void HideAllWindows(); + void ShowWindow(const std::string& window_id); + void HideWindow(const std::string& window_id); + void ToggleWindow(const std::string& window_id); + bool IsWindowVisible(const std::string& window_id) const; + + // Focus and positioning + void FocusWindow(const std::string& window_id); + void MaximizeWindow(const std::string& window_id); + void RestoreWindow(const std::string& window_id); + void CenterWindow(const std::string& window_id); + + // Docking operations + void DockWindow(const std::string& window_id, ImGuiDir dock_direction); + void UndockWindow(const std::string& window_id); + void SetDockSpace(const std::string& dock_space_id, const ImVec2& size = ImVec2(0, 0)); + + // Layout management + absl::Status SaveLayout(const std::string& preset_name); + absl::Status LoadLayout(const std::string& preset_name); + absl::Status ResetLayout(); + std::vector GetAvailableLayouts() const; + + // Window state queries + std::vector GetVisibleWindows() const; + std::vector GetHiddenWindows() const; + ImVec2 GetWindowSize(const std::string& window_id) const; + ImVec2 GetWindowPosition(const std::string& window_id) const; + + // Batch operations + void ShowWindowsInCategory(const std::string& category); + void HideWindowsInCategory(const std::string& category); + void ShowOnlyWindow(const std::string& window_id); // Hide all others + + // Window registration (for tracking) + void RegisterWindow(const std::string& window_id, const std::string& category = ""); + void UnregisterWindow(const std::string& window_id); + + // Layout presets + void LoadDeveloperLayout(); + void LoadDesignerLayout(); + void LoadModderLayout(); + void LoadMinimalLayout(); + + private: + // Window registry for tracking + struct WindowInfo { + std::string id; + std::string category; + bool is_registered = false; + }; + + std::unordered_map registered_windows_; + + // Helper methods + bool IsWindowRegistered(const std::string& window_id) const; + std::string GetLayoutFilePath(const std::string& preset_name) const; + void ApplyLayoutToWindow(const std::string& window_id, const std::string& layout_data); +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_SYSTEM_WINDOW_DELEGATE_H_ diff --git a/src/app/editor/ui/session_coordinator.cc b/src/app/editor/ui/session_coordinator.cc new file mode 100644 index 00000000..2d80f18a --- /dev/null +++ b/src/app/editor/ui/session_coordinator.cc @@ -0,0 +1,806 @@ +#include "session_coordinator.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "app/editor/editor_manager.h" +#include "app/gui/core/icons.h" +#include "app/gui/core/theme_manager.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +SessionCoordinator::SessionCoordinator(void* sessions_ptr, + SessionCardRegistry* card_registry, + ToastManager* toast_manager) + : sessions_ptr_(sessions_ptr), + card_registry_(card_registry), + toast_manager_(toast_manager) { + auto* sessions = static_cast*>(sessions_ptr_); + if (sessions && !sessions->empty()) { + active_session_index_ = 0; + session_count_ = sessions->size(); + } +} + +// Helper macro to get sessions pointer +#define GET_SESSIONS() static_cast*>(sessions_ptr_) + +void SessionCoordinator::CreateNewSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions) return; + + if (session_count_ >= kMaxSessions) { + ShowSessionLimitWarning(); + return; + } + + // Create new empty session + sessions->emplace_back(); + UpdateSessionCount(); + + // Set as active session + active_session_index_ = sessions->size() - 1; + + printf("[SessionCoordinator] Created new session %zu (total: %zu)\n", + active_session_index_, session_count_); + + ShowSessionOperationResult("Create Session", true); +} + +void SessionCoordinator::DuplicateCurrentSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + if (session_count_ >= kMaxSessions) { + ShowSessionLimitWarning(); + return; + } + + // Create new empty session (cannot actually duplicate due to non-movable editors) + // TODO: Implement proper duplication when editors become movable + sessions->emplace_back(); + UpdateSessionCount(); + + // Set as active session + active_session_index_ = sessions->size() - 1; + + printf("[SessionCoordinator] Duplicated session %zu (total: %zu)\n", + active_session_index_, session_count_); + + ShowSessionOperationResult("Duplicate Session", true); +} + +void SessionCoordinator::CloseCurrentSession() { + CloseSession(active_session_index_); +} + +void SessionCoordinator::CloseSession(size_t index) { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(index)) return; + + if (session_count_ <= kMinSessions) { + // Don't allow closing the last session + if (toast_manager_) { + toast_manager_->Show("Cannot close the last session", ToastType::kWarning); + } + return; + } + + // Unregister cards for this session + if (card_registry_) { + card_registry_->UnregisterSession(index); + } + + // Mark session as closed (don't erase due to non-movable editors) + // TODO: Implement proper session removal when editors become movable + sessions->at(index).custom_name = "[CLOSED SESSION]"; + + // Note: We don't actually remove from the deque because EditorSet is not movable + // This is a temporary solution until we refactor to use unique_ptr + UpdateSessionCount(); + + // Adjust active session index + if (active_session_index_ >= index && active_session_index_ > 0) { + active_session_index_--; + } + + printf("[SessionCoordinator] Closed session %zu (total: %zu)\n", + index, session_count_); + + ShowSessionOperationResult("Close Session", true); +} + +void SessionCoordinator::RemoveSession(size_t index) { + CloseSession(index); +} + +void SessionCoordinator::SwitchToSession(size_t index) { + if (!IsValidSessionIndex(index)) return; + + active_session_index_ = index; + + if (card_registry_) { + card_registry_->SetActiveSession(index); + } + + printf("[SessionCoordinator] Switched to session %zu\n", index); +} + +void SessionCoordinator::ActivateSession(size_t index) { + SwitchToSession(index); +} + +size_t SessionCoordinator::GetActiveSessionIndex() const { + return active_session_index_; +} + +void* SessionCoordinator::GetActiveSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(active_session_index_)) { + return nullptr; + } + return &sessions->at(active_session_index_); +} + +void* SessionCoordinator::GetSession(size_t index) { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(index)) { + return nullptr; + } + return &sessions->at(index); +} + +bool SessionCoordinator::HasMultipleSessions() const { + return session_count_ > 1; +} + +size_t SessionCoordinator::GetActiveSessionCount() const { + return session_count_; +} + +bool SessionCoordinator::HasDuplicateSession(const std::string& filepath) const { + auto* sessions = GET_SESSIONS(); + if (!sessions || filepath.empty()) return false; + + for (const auto& session : *sessions) { + if (session.filepath == filepath) { + return true; + } + } + return false; +} + +void SessionCoordinator::DrawSessionSwitcher() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + if (!show_session_switcher_) return; + + ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (!ImGui::Begin("Session Switcher", &show_session_switcher_)) { + ImGui::End(); + return; + } + + ImGui::Text("%s Active Sessions (%zu)", ICON_MD_TAB, session_count_); + ImGui::Separator(); + + for (size_t i = 0; i < sessions->size(); ++i) { + const auto& session = sessions->at(i); + bool is_active = (i == active_session_index_); + + ImGui::PushID(static_cast(i)); + + // Session tab + if (ImGui::Selectable(GetSessionDisplayName(i).c_str(), is_active)) { + SwitchToSession(i); + } + + // Right-click context menu + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + ImGui::OpenPopup("SessionContextMenu"); + } + + if (ImGui::BeginPopup("SessionContextMenu")) { + DrawSessionContextMenu(i); + ImGui::EndPopup(); + } + + ImGui::PopID(); + } + + ImGui::Separator(); + + // Action buttons + if (ImGui::Button(absl::StrFormat("%s New Session", ICON_MD_ADD).c_str())) { + CreateNewSession(); + } + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Duplicate", ICON_MD_CONTENT_COPY).c_str())) { + DuplicateCurrentSession(); + } + + ImGui::SameLine(); + if (HasMultipleSessions() && ImGui::Button(absl::StrFormat("%s Close", ICON_MD_CLOSE).c_str())) { + CloseCurrentSession(); + } + + ImGui::End(); +} + +void SessionCoordinator::DrawSessionManager() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + if (!show_session_manager_) return; + + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (!ImGui::Begin("Session Manager", &show_session_manager_)) { + ImGui::End(); + return; + } + + // Session statistics + ImGui::Text("%s Session Statistics", ICON_MD_ANALYTICS); + ImGui::Separator(); + + ImGui::Text("Total Sessions: %zu", GetTotalSessionCount()); + ImGui::Text("Loaded Sessions: %zu", GetLoadedSessionCount()); + ImGui::Text("Empty Sessions: %zu", GetEmptySessionCount()); + + ImGui::Spacing(); + + // Session list + if (ImGui::BeginTable("SessionTable", 4, + ImGuiTableFlags_Borders | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable)) { + + ImGui::TableSetupColumn("Session", ImGuiTableColumnFlags_WidthStretch, 0.3f); + ImGui::TableSetupColumn("ROM File", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableHeadersRow(); + + for (size_t i = 0; i < sessions->size(); ++i) { + const auto& session = sessions->at(i); + bool is_active = (i == active_session_index_); + + ImGui::PushID(static_cast(i)); + + ImGui::TableNextRow(); + + // Session name + ImGui::TableNextColumn(); + if (is_active) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s %s", + ICON_MD_RADIO_BUTTON_CHECKED, GetSessionDisplayName(i).c_str()); + } else { + ImGui::Text("%s %s", ICON_MD_RADIO_BUTTON_UNCHECKED, GetSessionDisplayName(i).c_str()); + } + + // ROM file + ImGui::TableNextColumn(); + if (session.rom.is_loaded()) { + ImGui::Text("%s", session.filepath.c_str()); + } else { + ImGui::TextDisabled("(No ROM loaded)"); + } + + // Status + ImGui::TableNextColumn(); + if (session.rom.is_loaded()) { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Loaded"); + } else { + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Empty"); + } + + // Actions + ImGui::TableNextColumn(); + if (!is_active && ImGui::SmallButton("Switch")) { + SwitchToSession(i); + } + + ImGui::SameLine(); + if (HasMultipleSessions() && ImGui::SmallButton("Close")) { + CloseSession(i); + } + + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + ImGui::End(); +} + +void SessionCoordinator::DrawSessionRenameDialog() { + if (!show_session_rename_dialog_) return; + + ImGui::SetNextWindowSize(ImVec2(300, 150), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + + if (!ImGui::Begin("Rename Session", &show_session_rename_dialog_)) { + ImGui::End(); + return; + } + + ImGui::Text("Rename session %zu:", session_to_rename_); + ImGui::InputText("Name", session_rename_buffer_, sizeof(session_rename_buffer_)); + + ImGui::Spacing(); + + if (ImGui::Button("OK")) { + RenameSession(session_to_rename_, session_rename_buffer_); + show_session_rename_dialog_ = false; + session_rename_buffer_[0] = '\0'; + } + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + show_session_rename_dialog_ = false; + session_rename_buffer_[0] = '\0'; + } + + ImGui::End(); +} + +void SessionCoordinator::DrawSessionTabs() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + if (ImGui::BeginTabBar("SessionTabs")) { + for (size_t i = 0; i < sessions->size(); ++i) { + bool is_active = (i == active_session_index_); + const auto& session = sessions->at(i); + + std::string tab_name = GetSessionDisplayName(i); + if (session.rom.is_loaded()) { + tab_name += " "; + tab_name += ICON_MD_CHECK_CIRCLE; + } + + if (ImGui::BeginTabItem(tab_name.c_str())) { + if (!is_active) { + SwitchToSession(i); + } + ImGui::EndTabItem(); + } + + // Right-click context menu + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + ImGui::OpenPopup(absl::StrFormat("SessionTabContext_%zu", i).c_str()); + } + + if (ImGui::BeginPopup(absl::StrFormat("SessionTabContext_%zu", i).c_str())) { + DrawSessionContextMenu(i); + ImGui::EndPopup(); + } + } + ImGui::EndTabBar(); + } +} + +void SessionCoordinator::DrawSessionIndicator() { + if (!HasMultipleSessions()) return; + + const auto& theme = gui::ThemeManager::Get().GetCurrentTheme(); + ImVec4 accent_color = ConvertColorToImVec4(theme.accent); + + ImGui::PushStyleColor(ImGuiCol_Text, accent_color); + ImGui::Text("%s Session %zu", ICON_MD_TAB, active_session_index_); + ImGui::PopStyleColor(); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Active Session: %s\nClick to open session switcher", + GetActiveSessionDisplayName().c_str()); + } + + if (ImGui::IsItemClicked()) { + ToggleSessionSwitcher(); + } +} + +std::string SessionCoordinator::GetSessionDisplayName(size_t index) const { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(index)) { + return "Invalid Session"; + } + + const auto& session = sessions->at(index); + + if (!session.custom_name.empty()) { + return session.custom_name; + } + + if (session.rom.is_loaded()) { + return absl::StrFormat("Session %zu (%s)", index, + std::filesystem::path(session.filepath).stem().string()); + } + + return absl::StrFormat("Session %zu (Empty)", index); +} + +std::string SessionCoordinator::GetActiveSessionDisplayName() const { + return GetSessionDisplayName(active_session_index_); +} + +void SessionCoordinator::RenameSession(size_t index, const std::string& new_name) { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(index) || new_name.empty()) return; + + sessions->at(index).custom_name = new_name; + printf("[SessionCoordinator] Renamed session %zu to '%s'\n", index, new_name.c_str()); +} + +void SessionCoordinator::SetActiveSessionIndex(size_t index) { + SwitchToSession(index); +} + +void SessionCoordinator::UpdateSessionCount() { + auto* sessions = GET_SESSIONS(); + if (sessions) { + session_count_ = sessions->size(); + } else { + session_count_ = 0; + } +} + +void SessionCoordinator::ShowAllCardsInActiveSession() { + if (card_registry_) { + card_registry_->ShowAllCardsInSession(active_session_index_); + } +} + +void SessionCoordinator::HideAllCardsInActiveSession() { + if (card_registry_) { + card_registry_->HideAllCardsInSession(active_session_index_); + } +} + +void SessionCoordinator::ShowCardsInCategory(const std::string& category) { + if (card_registry_) { + card_registry_->ShowAllCardsInCategory(active_session_index_, category); + } +} + +void SessionCoordinator::HideCardsInCategory(const std::string& category) { + if (card_registry_) { + card_registry_->HideAllCardsInCategory(active_session_index_, category); + } +} + +bool SessionCoordinator::IsValidSessionIndex(size_t index) const { + auto* sessions = GET_SESSIONS(); + return sessions && index < sessions->size(); +} + +bool SessionCoordinator::IsSessionActive(size_t index) const { + return index == active_session_index_; +} + +bool SessionCoordinator::IsSessionLoaded(size_t index) const { + auto* sessions = GET_SESSIONS(); + return IsValidSessionIndex(index) && sessions && sessions->at(index).rom.is_loaded(); +} + +size_t SessionCoordinator::GetTotalSessionCount() const { + return session_count_; +} + +size_t SessionCoordinator::GetLoadedSessionCount() const { + auto* sessions = GET_SESSIONS(); + if (!sessions) return 0; + + size_t count = 0; + for (const auto& session : *sessions) { + if (session.rom.is_loaded()) { + count++; + } + } + return count; +} + +size_t SessionCoordinator::GetEmptySessionCount() const { + return session_count_ - GetLoadedSessionCount(); +} + +absl::Status SessionCoordinator::LoadRomIntoSession(const std::string& filename, size_t session_index) { + auto* sessions = GET_SESSIONS(); + if (!sessions || filename.empty()) { + return absl::InvalidArgumentError("Invalid parameters"); + } + + size_t target_index = (session_index == SIZE_MAX) ? active_session_index_ : session_index; + if (!IsValidSessionIndex(target_index)) { + return absl::InvalidArgumentError("Invalid session index"); + } + + // TODO: Implement actual ROM loading + printf("[SessionCoordinator] LoadRomIntoSession: %s -> session %zu\n", + filename.c_str(), target_index); + + return absl::OkStatus(); +} + +absl::Status SessionCoordinator::SaveActiveSession(const std::string& filename) { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(active_session_index_)) { + return absl::FailedPreconditionError("No active session"); + } + + // TODO: Implement actual ROM saving + printf("[SessionCoordinator] SaveActiveSession: session %zu\n", active_session_index_); + + return absl::OkStatus(); +} + +absl::Status SessionCoordinator::SaveSessionAs(size_t session_index, const std::string& filename) { + auto* sessions = GET_SESSIONS(); + if (!sessions || !IsValidSessionIndex(session_index) || filename.empty()) { + return absl::InvalidArgumentError("Invalid parameters"); + } + + // TODO: Implement actual ROM saving + printf("[SessionCoordinator] SaveSessionAs: session %zu -> %s\n", + session_index, filename.c_str()); + + return absl::OkStatus(); +} + +void SessionCoordinator::CleanupClosedSessions() { + auto* sessions = GET_SESSIONS(); + if (!sessions) return; + + // Mark empty sessions as closed (except keep at least one) + // TODO: Actually remove when editors become movable + size_t loaded_count = 0; + for (auto& session : *sessions) { + if (session.rom.is_loaded()) { + loaded_count++; + } + } + + if (loaded_count > 0) { + for (auto& session : *sessions) { + if (!session.rom.is_loaded() && sessions->size() > 1) { + session.custom_name = "[CLOSED SESSION]"; + } + } + } + + UpdateSessionCount(); + printf("[SessionCoordinator] Cleaned up closed sessions (remaining: %zu)\n", session_count_); +} + +void SessionCoordinator::ClearAllSessions() { + auto* sessions = GET_SESSIONS(); + if (!sessions) return; + + // Unregister all session cards + if (card_registry_) { + for (size_t i = 0; i < sessions->size(); ++i) { + card_registry_->UnregisterSession(i); + } + } + + // Mark all sessions as closed instead of clearing + // TODO: Actually clear when editors become movable + for (auto& session : *sessions) { + session.custom_name = "[CLOSED SESSION]"; + } + + active_session_index_ = 0; + UpdateSessionCount(); + + printf("[SessionCoordinator] Cleared all sessions\n"); +} + +void SessionCoordinator::FocusNextSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + size_t next_index = (active_session_index_ + 1) % sessions->size(); + SwitchToSession(next_index); +} + +void SessionCoordinator::FocusPreviousSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + + size_t prev_index = (active_session_index_ == 0) ? + sessions->size() - 1 : active_session_index_ - 1; + SwitchToSession(prev_index); +} + +void SessionCoordinator::FocusFirstSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + SwitchToSession(0); +} + +void SessionCoordinator::FocusLastSession() { + auto* sessions = GET_SESSIONS(); + if (!sessions || sessions->empty()) return; + SwitchToSession(sessions->size() - 1); +} + +void SessionCoordinator::UpdateActiveSession() { + auto* sessions = GET_SESSIONS(); + if (sessions && !sessions->empty() && active_session_index_ >= sessions->size()) { + active_session_index_ = sessions->size() - 1; + } +} + +void SessionCoordinator::ValidateSessionIndex(size_t index) const { + if (!IsValidSessionIndex(index)) { + throw std::out_of_range(absl::StrFormat("Invalid session index: %zu", index)); + } +} + +std::string SessionCoordinator::GenerateUniqueSessionName(const std::string& base_name) const { + auto* sessions = GET_SESSIONS(); + if (!sessions) return base_name; + + std::string name = base_name; + int counter = 1; + + while (true) { + bool found = false; + for (const auto& session : *sessions) { + if (session.custom_name == name) { + found = true; + break; + } + } + + if (!found) break; + + name = absl::StrFormat("%s %d", base_name, counter++); + } + + return name; +} + +void SessionCoordinator::ShowSessionLimitWarning() { + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Maximum %zu sessions allowed", kMaxSessions), + ToastType::kWarning); + } +} + +void SessionCoordinator::ShowSessionOperationResult(const std::string& operation, bool success) { + if (toast_manager_) { + std::string message = absl::StrFormat("%s %s", operation, + success ? "succeeded" : "failed"); + ToastType type = success ? ToastType::kSuccess : ToastType::kError; + toast_manager_->Show(message, type); + } +} + +void SessionCoordinator::DrawSessionTab(size_t index, bool is_active) { + auto* sessions = GET_SESSIONS(); + if (!sessions || index >= sessions->size()) return; + + const auto& session = sessions->at(index); + + ImVec4 color = GetSessionColor(index); + ImGui::PushStyleColor(ImGuiCol_Text, color); + + std::string tab_name = GetSessionDisplayName(index); + if (session.rom.is_loaded()) { + tab_name += " "; + tab_name += ICON_MD_CHECK_CIRCLE; + } + + if (ImGui::BeginTabItem(tab_name.c_str())) { + if (!is_active) { + SwitchToSession(index); + } + ImGui::EndTabItem(); + } + + ImGui::PopStyleColor(); +} + +void SessionCoordinator::DrawSessionContextMenu(size_t index) { + if (ImGui::MenuItem(absl::StrFormat("%s Switch to Session", ICON_MD_TAB).c_str())) { + SwitchToSession(index); + } + + if (ImGui::MenuItem(absl::StrFormat("%s Rename", ICON_MD_EDIT).c_str())) { + session_to_rename_ = index; + strncpy(session_rename_buffer_, GetSessionDisplayName(index).c_str(), + sizeof(session_rename_buffer_) - 1); + session_rename_buffer_[sizeof(session_rename_buffer_) - 1] = '\0'; + show_session_rename_dialog_ = true; + } + + if (ImGui::MenuItem(absl::StrFormat("%s Duplicate", ICON_MD_CONTENT_COPY).c_str())) { + // TODO: Implement session duplication + } + + ImGui::Separator(); + + if (HasMultipleSessions() && + ImGui::MenuItem(absl::StrFormat("%s Close Session", ICON_MD_CLOSE).c_str())) { + CloseSession(index); + } +} + +void SessionCoordinator::DrawSessionBadge(size_t index) { + auto* sessions = GET_SESSIONS(); + if (!sessions || index >= sessions->size()) return; + + const auto& session = sessions->at(index); + ImVec4 color = GetSessionColor(index); + + ImGui::PushStyleColor(ImGuiCol_Text, color); + + if (session.rom.is_loaded()) { + ImGui::Text("%s", ICON_MD_CHECK_CIRCLE); + } else { + ImGui::Text("%s", ICON_MD_RADIO_BUTTON_UNCHECKED); + } + + ImGui::PopStyleColor(); +} + +ImVec4 SessionCoordinator::GetSessionColor(size_t index) const { + // Generate consistent colors for sessions + static const ImVec4 colors[] = { + ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // Green + ImVec4(0.0f, 0.5f, 1.0f, 1.0f), // Blue + ImVec4(1.0f, 0.5f, 0.0f, 1.0f), // Orange + ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // Magenta + ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // Yellow + ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // Cyan + ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // Red + ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // Gray + }; + + return colors[index % (sizeof(colors) / sizeof(colors[0]))]; +} + +std::string SessionCoordinator::GetSessionIcon(size_t index) const { + auto* sessions = GET_SESSIONS(); + if (!sessions || index >= sessions->size()) return ICON_MD_RADIO_BUTTON_UNCHECKED; + + const auto& session = sessions->at(index); + + if (session.rom.is_loaded()) { + return ICON_MD_CHECK_CIRCLE; + } else { + return ICON_MD_RADIO_BUTTON_UNCHECKED; + } +} + +bool SessionCoordinator::IsSessionEmpty(size_t index) const { + auto* sessions = GET_SESSIONS(); + return IsValidSessionIndex(index) && sessions && !sessions->at(index).rom.is_loaded(); +} + +bool SessionCoordinator::IsSessionClosed(size_t index) const { + return !IsValidSessionIndex(index); +} + +bool SessionCoordinator::IsSessionModified(size_t index) const { + // TODO: Implement modification tracking + return false; +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/ui/session_coordinator.h b/src/app/editor/ui/session_coordinator.h new file mode 100644 index 00000000..5cf3756f --- /dev/null +++ b/src/app/editor/ui/session_coordinator.h @@ -0,0 +1,163 @@ +#ifndef YAZE_APP_EDITOR_UI_SESSION_COORDINATOR_H_ +#define YAZE_APP_EDITOR_UI_SESSION_COORDINATOR_H_ + +#include +#include +#include + +#include "absl/status/status.h" +#include "app/editor/system/session_card_registry.h" +#include "app/editor/system/toast_manager.h" +#include "app/rom.h" +#include "imgui/imgui.h" + +// Forward declarations +namespace yaze { +namespace editor { +class EditorManager; +} +} + +namespace yaze { +namespace editor { + +// Forward declarations +class EditorSet; +class ToastManager; + +/** + * @class SessionCoordinator + * @brief High-level orchestrator for multi-session UI + * + * Manages session list UI, coordinates card visibility across sessions, + * handles session activation/deactivation, and provides session-aware editor queries. + * + * This class lives in the ui/ layer and can depend on both system and gui components. + */ +class SessionCoordinator { + public: + explicit SessionCoordinator(void* sessions_ptr, + SessionCardRegistry* card_registry, + ToastManager* toast_manager); + ~SessionCoordinator() = default; + + // Session lifecycle management + void CreateNewSession(); + void DuplicateCurrentSession(); + void CloseCurrentSession(); + void CloseSession(size_t index); + void RemoveSession(size_t index); + void SwitchToSession(size_t index); + + // Session activation and queries + void ActivateSession(size_t index); + size_t GetActiveSessionIndex() const; + void* GetActiveSession(); + void* GetSession(size_t index); + bool HasMultipleSessions() const; + size_t GetActiveSessionCount() const; + bool HasDuplicateSession(const std::string& filepath) const; + + // Session UI components + void DrawSessionSwitcher(); + void DrawSessionManager(); + void DrawSessionRenameDialog(); + void DrawSessionTabs(); + void DrawSessionIndicator(); + + // Session information + std::string GetSessionDisplayName(size_t index) const; + std::string GetActiveSessionDisplayName() const; + void RenameSession(size_t index, const std::string& new_name); + + // Session state management + void SetActiveSessionIndex(size_t index); + void UpdateSessionCount(); + + // Card coordination across sessions + void ShowAllCardsInActiveSession(); + void HideAllCardsInActiveSession(); + void ShowCardsInCategory(const std::string& category); + void HideCardsInCategory(const std::string& category); + + // Session validation + bool IsValidSessionIndex(size_t index) const; + bool IsSessionActive(size_t index) const; + bool IsSessionLoaded(size_t index) const; + + // Session statistics + size_t GetTotalSessionCount() const; + size_t GetLoadedSessionCount() const; + size_t GetEmptySessionCount() const; + + // Session operations with error handling + absl::Status LoadRomIntoSession(const std::string& filename, size_t session_index = SIZE_MAX); + absl::Status SaveActiveSession(const std::string& filename = ""); + absl::Status SaveSessionAs(size_t session_index, const std::string& filename); + + // Session cleanup + void CleanupClosedSessions(); + void ClearAllSessions(); + + // Session navigation + void FocusNextSession(); + void FocusPreviousSession(); + void FocusFirstSession(); + void FocusLastSession(); + + // Session UI state + void ShowSessionSwitcher() { show_session_switcher_ = true; } + void HideSessionSwitcher() { show_session_switcher_ = false; } + void ToggleSessionSwitcher() { show_session_switcher_ = !show_session_switcher_; } + bool IsSessionSwitcherVisible() const { return show_session_switcher_; } + + void ShowSessionManager() { show_session_manager_ = true; } + void HideSessionManager() { show_session_manager_ = false; } + void ToggleSessionManager() { show_session_manager_ = !show_session_manager_; } + bool IsSessionManagerVisible() const { return show_session_manager_; } + + private: + // Core dependencies + void* sessions_ptr_; // std::deque* + SessionCardRegistry* card_registry_; + ToastManager* toast_manager_; + + // Session state + size_t active_session_index_ = 0; + size_t session_count_ = 0; + + // UI state + bool show_session_switcher_ = false; + bool show_session_manager_ = false; + bool show_session_rename_dialog_ = false; + size_t session_to_rename_ = 0; + char session_rename_buffer_[256] = {}; + + // Session limits + static constexpr size_t kMaxSessions = 8; + static constexpr size_t kMinSessions = 1; + + // Helper methods + void UpdateActiveSession(); + void ValidateSessionIndex(size_t index) const; + std::string GenerateUniqueSessionName(const std::string& base_name) const; + void ShowSessionLimitWarning(); + void ShowSessionOperationResult(const std::string& operation, bool success); + + // UI helper methods + void DrawSessionTab(size_t index, bool is_active); + void DrawSessionContextMenu(size_t index); + void DrawSessionBadge(size_t index); + ImVec4 GetSessionColor(size_t index) const; + std::string GetSessionIcon(size_t index) const; + + // Session validation helpers + bool IsSessionEmpty(size_t index) const; + bool IsSessionClosed(size_t index) const; + bool IsSessionModified(size_t index) const; +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_UI_SESSION_COORDINATOR_H_ diff --git a/src/app/gui/app/editor_card_manager.cc b/src/app/gui/app/editor_card_manager.cc index 9c8c9c6d..1a6c8f0f 100644 --- a/src/app/gui/app/editor_card_manager.cc +++ b/src/app/gui/app/editor_card_manager.cc @@ -88,6 +88,25 @@ void EditorCardManager::UnregisterCard(const std::string& card_id) { } } +void EditorCardManager::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 all found cards + for (const auto& card_id : to_remove) { + UnregisterCard(card_id); + } + + printf("[EditorCardManager] Unregistered %zu cards with prefix: %s\n", + to_remove.size(), prefix.c_str()); +} + void EditorCardManager::ClearAllCards() { printf("[EditorCardManager] Clearing all %zu registered cards\n", cards_.size()); cards_.clear(); @@ -230,6 +249,24 @@ std::vector EditorCardManager::GetCardsInCategory(const std::string& c return result; } +std::vector EditorCardManager::GetCardsWithPrefix(const std::string& prefix) const { + std::vector result; + + for (const auto& [id, info] : cards_) { + if (id.find(prefix) == 0) { // Starts with prefix + result.push_back(info); + } + } + + // Sort by priority + std::sort(result.begin(), result.end(), + [](const CardInfo& a, const CardInfo& b) { + return a.priority < b.priority; + }); + + return result; +} + std::vector EditorCardManager::GetAllCategories() const { std::vector categories; @@ -319,7 +356,23 @@ void EditorCardManager::DrawViewMenuAll() { } void EditorCardManager::DrawCompactCardControl(const std::string& category) { - auto cards_in_category = GetCardsInCategory(category); + DrawCompactCardControlWithSession(category, ""); +} + +void EditorCardManager::DrawCompactCardControlWithSession(const std::string& category, const std::string& session_prefix) { + std::vector cards_in_category; + + if (session_prefix.empty()) { + cards_in_category = GetCardsInCategory(category); + } else { + // Filter cards by session prefix + auto all_cards = GetCardsWithPrefix(session_prefix); + for (const auto& card : all_cards) { + if (card.category == category) { + cards_in_category.push_back(card); + } + } + } if (cards_in_category.empty()) { return; // Nothing to show @@ -337,13 +390,21 @@ void EditorCardManager::DrawCompactCardControl(const std::string& category) { ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s Card Controls", category.c_str()); + std::string tooltip = category + " Card Controls"; + if (!session_prefix.empty()) { + tooltip += " (" + session_prefix + ")"; + } + ImGui::SetTooltip("%s", tooltip.c_str()); } // Compact popup with checkboxes if (ImGui::BeginPopup("CardControlPopup")) { - ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s %s Cards", - ICON_MD_DASHBOARD, category.c_str()); + std::string title = category + " Cards"; + if (!session_prefix.empty()) { + title += " (" + session_prefix + ")"; + } + ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s %s", + ICON_MD_DASHBOARD, title.c_str()); ImGui::Separator(); for (const auto& info : cards_in_category) { @@ -743,9 +804,17 @@ void EditorCardManager::SetActiveCategory(const std::string& category) { } void EditorCardManager::DrawSidebar(const std::string& category, - const std::vector& active_categories, - std::function on_category_switch, - std::function on_collapse) { + const std::vector& active_categories, + std::function on_category_switch, + std::function on_collapse) { + DrawSidebarWithSessionFilter(category, "", active_categories, on_category_switch, on_collapse); +} + +void EditorCardManager::DrawSidebarWithSessionFilter(const std::string& category, + const std::string& session_prefix, + const std::vector& active_categories, + std::function on_category_switch, + std::function on_collapse) { // Use ThemeManager for consistent theming const auto& theme = ThemeManager::Get().GetCurrentTheme(); diff --git a/src/app/gui/app/editor_card_manager.h b/src/app/gui/app/editor_card_manager.h index 89e33c33..59d56d83 100644 --- a/src/app/gui/app/editor_card_manager.h +++ b/src/app/gui/app/editor_card_manager.h @@ -88,6 +88,7 @@ class EditorCardManager { bool visible_by_default = false); void UnregisterCard(const std::string& card_id); + void UnregisterCardsWithPrefix(const std::string& prefix); void ClearAllCards(); // Card control (programmatic, no GUI) @@ -106,6 +107,7 @@ class EditorCardManager { // Query std::vector GetCardsInCategory(const std::string& category) const; + std::vector GetCardsWithPrefix(const std::string& prefix) const; std::vector GetAllCategories() const; const CardInfo* GetCardInfo(const std::string& card_id) const; @@ -118,6 +120,11 @@ class EditorCardManager { const std::vector& active_categories = {}, std::function on_category_switch = nullptr, std::function on_collapse = nullptr); + void DrawSidebarWithSessionFilter(const std::string& category, + const std::string& session_prefix = "", + const std::vector& active_categories = {}, + std::function on_category_switch = nullptr, + std::function on_collapse = nullptr); static constexpr float GetSidebarWidth() { return 48.0f; } // Active editor tracking (based on most recently interacted card) @@ -127,6 +134,7 @@ class EditorCardManager { // Compact inline card control for menu bar void DrawCompactCardControl(const std::string& category); // Shows only active editor's cards + void DrawCompactCardControlWithSession(const std::string& category, const std::string& session_prefix = ""); void DrawInlineCardToggles(const std::string& category); // Minimal inline checkboxes // Card browser UI diff --git a/src/app/net/net_library.cmake b/src/app/net/net_library.cmake index aecc98cd..39178472 100644 --- a/src/app/net/net_library.cmake +++ b/src/app/net/net_library.cmake @@ -84,10 +84,12 @@ if(YAZE_WITH_GRPC) grpc++ grpc++_reflection ) - if(YAZE_PROTOBUF_TARGET) - target_link_libraries(yaze_net PUBLIC ${YAZE_PROTOBUF_TARGET}) - if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_link_options(yaze_net PUBLIC /WHOLEARCHIVE:$) + if(YAZE_PROTOBUF_TARGETS) + target_link_libraries(yaze_net PUBLIC ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(yaze_net PUBLIC /WHOLEARCHIVE:$) + endforeach() endif() endif() diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index 3d6e1714..10f227e6 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -161,10 +161,12 @@ if(YAZE_WITH_GRPC) grpc++ grpc++_reflection ) - if(YAZE_PROTOBUF_TARGET) - target_link_libraries(yaze_agent PUBLIC ${YAZE_PROTOBUF_TARGET}) - if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_link_options(yaze_agent PUBLIC /WHOLEARCHIVE:$) + if(YAZE_PROTOBUF_TARGETS) + target_link_libraries(yaze_agent PUBLIC ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(yaze_agent PUBLIC /WHOLEARCHIVE:$) + endforeach() endif() endif() diff --git a/src/cli/z3ed.cmake b/src/cli/z3ed.cmake index 346982fa..62ac38b6 100644 --- a/src/cli/z3ed.cmake +++ b/src/cli/z3ed.cmake @@ -40,10 +40,12 @@ endif() if(YAZE_WITH_GRPC) message(STATUS "Adding gRPC support to z3ed CLI") target_link_libraries(z3ed PRIVATE grpc++ grpc++_reflection) - if(YAZE_PROTOBUF_TARGET) - target_link_libraries(z3ed PRIVATE ${YAZE_PROTOBUF_TARGET}) - if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_link_options(z3ed PRIVATE /WHOLEARCHIVE:$) + if(YAZE_PROTOBUF_TARGETS) + target_link_libraries(z3ed PRIVATE ${YAZE_PROTOBUF_TARGETS}) + if(MSVC) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(z3ed PRIVATE /WHOLEARCHIVE:$) + endforeach() endif() endif() endif() diff --git a/src/zelda3/palette_constants.cc b/src/zelda3/palette_constants.cc index 0c6bac3d..630a63a6 100644 --- a/src/zelda3/palette_constants.cc +++ b/src/zelda3/palette_constants.cc @@ -1,78 +1,82 @@ #include "zelda3/palette_constants.h" + #include #include -namespace yaze { -namespace zelda3 { +namespace yaze::zelda3 { const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id) { std::string id_str(group_id); - + if (id_str == PaletteGroupName::kOverworldMain) { return &PaletteMetadata::kOverworldMain; - } else if (id_str == PaletteGroupName::kOverworldAux) { + } + if (id_str == PaletteGroupName::kOverworldAux) { return &PaletteMetadata::kOverworldAux; - } else if (id_str == PaletteGroupName::kOverworldAnimated) { + } + if (id_str == PaletteGroupName::kOverworldAnimated) { return &PaletteMetadata::kOverworldAnimated; - } else if (id_str == PaletteGroupName::kDungeonMain) { + } + if (id_str == PaletteGroupName::kDungeonMain) { return &PaletteMetadata::kDungeonMain; - } else if (id_str == PaletteGroupName::kGlobalSprites) { + } + if (id_str == PaletteGroupName::kGlobalSprites) { return &PaletteMetadata::kGlobalSprites; - } else if (id_str == PaletteGroupName::kSpritesAux1) { + } + if (id_str == PaletteGroupName::kSpritesAux1) { return &PaletteMetadata::kSpritesAux1; - } else if (id_str == PaletteGroupName::kSpritesAux2) { + } + if (id_str == PaletteGroupName::kSpritesAux2) { return &PaletteMetadata::kSpritesAux2; - } else if (id_str == PaletteGroupName::kSpritesAux3) { + } + if (id_str == PaletteGroupName::kSpritesAux3) { return &PaletteMetadata::kSpritesAux3; - } else if (id_str == PaletteGroupName::kArmor) { + } + if (id_str == PaletteGroupName::kArmor) { return &PaletteMetadata::kArmor; - } else if (id_str == PaletteGroupName::kSwords) { + } + if (id_str == PaletteGroupName::kSwords) { return &PaletteMetadata::kSwords; - } else if (id_str == PaletteGroupName::kShields) { + } + if (id_str == PaletteGroupName::kShields) { return &PaletteMetadata::kShields; - } else if (id_str == PaletteGroupName::kHud) { + } + if (id_str == PaletteGroupName::kHud) { return &PaletteMetadata::kHud; - } else if (id_str == PaletteGroupName::kGrass) { + } + if (id_str == PaletteGroupName::kGrass) { return &PaletteMetadata::kGrass; - } else if (id_str == PaletteGroupName::k3DObject) { + } + if (id_str == PaletteGroupName::k3DObject) { return &PaletteMetadata::k3DObject; - } else if (id_str == PaletteGroupName::kOverworldMiniMap) { + } + if (id_str == PaletteGroupName::kOverworldMiniMap) { return &PaletteMetadata::kOverworldMiniMap; } return nullptr; } std::vector GetAllPaletteGroups() { - return { - // Overworld - &PaletteMetadata::kOverworldMain, - &PaletteMetadata::kOverworldAux, - &PaletteMetadata::kOverworldAnimated, - - // Dungeon - &PaletteMetadata::kDungeonMain, - - // Sprites - &PaletteMetadata::kGlobalSprites, - &PaletteMetadata::kSpritesAux1, - &PaletteMetadata::kSpritesAux2, - &PaletteMetadata::kSpritesAux3, - - // Equipment - &PaletteMetadata::kArmor, - &PaletteMetadata::kSwords, - &PaletteMetadata::kShields, - - // Interface - &PaletteMetadata::kHud, - &PaletteMetadata::kOverworldMiniMap, - - // Special - &PaletteMetadata::kGrass, - &PaletteMetadata::k3DObject - }; + return {// Overworld + &PaletteMetadata::kOverworldMain, &PaletteMetadata::kOverworldAux, + &PaletteMetadata::kOverworldAnimated, + + // Dungeon + &PaletteMetadata::kDungeonMain, + + // Sprites + &PaletteMetadata::kGlobalSprites, &PaletteMetadata::kSpritesAux1, + &PaletteMetadata::kSpritesAux2, &PaletteMetadata::kSpritesAux3, + + // Equipment + &PaletteMetadata::kArmor, &PaletteMetadata::kSwords, + &PaletteMetadata::kShields, + + // Interface + &PaletteMetadata::kHud, &PaletteMetadata::kOverworldMiniMap, + + // Special + &PaletteMetadata::kGrass, &PaletteMetadata::k3DObject}; } -} // namespace zelda3 -} // namespace yaze - +} // namespace yaze::zelda3 diff --git a/src/zelda3/palette_constants.h b/src/zelda3/palette_constants.h index a90dc0c6..34740104 100644 --- a/src/zelda3/palette_constants.h +++ b/src/zelda3/palette_constants.h @@ -4,8 +4,7 @@ #include #include -namespace yaze { -namespace zelda3 { +namespace yaze::zelda3 { // ============================================================================ // Palette Group Names @@ -303,8 +302,7 @@ const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id); // Get all available palette groups std::vector GetAllPaletteGroups(); -} // namespace zelda3 -} // namespace yaze +} // namespace yaze::zelda3 #endif // YAZE_ZELDA3_PALETTE_CONSTANTS_H diff --git a/src/zelda3/zelda3_labels.cc b/src/zelda3/zelda3_labels.cc index 63b806fe..7e10b23e 100644 --- a/src/zelda3/zelda3_labels.cc +++ b/src/zelda3/zelda3_labels.cc @@ -1,12 +1,15 @@ #include "zelda3/zelda3_labels.h" +#include +#include +#include +#include #include "zelda3/common.h" #include "zelda3/dungeon/room.h" -#include "zelda3/sprite/sprite.h" #include "zelda3/sprite/overlord.h" +#include "zelda3/sprite/sprite.h" -namespace yaze { -namespace zelda3 { +namespace yaze::zelda3 { // Room names - reuse existing kRoomNames array const std::vector& Zelda3Labels::GetRoomNames() { @@ -55,147 +58,291 @@ const std::vector& Zelda3Labels::GetOverlordNames() { // Overworld map names (64 Light World + 64 Dark World + 32 Special Areas) const std::vector& Zelda3Labels::GetOverworldMapNames() { static const std::vector map_names = { - // Light World (0x00-0x3F) - "Lost Woods", "Master Sword Pedestal", "Castle Courtyard", "Link's House", - "Eastern Palace", "Desert Palace", "Hyrule Castle", "Witch's Hut", - "Kakariko Village", "Death Mountain", "Tower of Hera", "Spectacle Rock", - "Graveyard", "Sanctuary", "Lake Hylia", "Desert of Mystery", - "Eastern Ruins", "Zora's Domain", "Catfish", "Dam", - "Potion Shop", "Kakariko Well", "Blacksmith", "Sick Kid", - "Library", "Mushroom", "Magic Bat", "Fairy Fountain", - "Fortune Teller", "Lake Shop", "Bomb Shop", "Cave 45", - "Checkerboard Cave", "Mini Moldorm Cave", "Ice Rod Cave", "Bonk Rocks", - "Bottle Merchant", "Sahasrahla's Hut", "Chicken House", "Aginah's Cave", - "Dam Exterior", "Mimic Cave Exterior", "Waterfall Fairy", "Pyramid", - "Fat Fairy", "Spike Cave", "Hookshot Cave", "Graveyard Ledge", - "Dark Lumberjacks", "Bumper Cave", "Skull Woods 1", "Skull Woods 2", - "Skull Woods 3", "Skull Woods 4", "Skull Woods 5", "Skull Woods 6", - "Skull Woods 7", "Skull Woods 8", "Ice Palace Exterior", "Misery Mire Exterior", - "Palace of Darkness Exterior", "Swamp Palace Exterior", "Turtle Rock Exterior", "Thieves' Town Exterior", - - // Dark World (0x40-0x7F) - "Dark Woods", "Dark Chapel", "Dark Castle", "Dark Shields", - "Dark Palace", "Dark Desert", "Dark Castle Gate", "Dark Witch", - "Dark Village", "Dark Mountain", "Dark Tower", "Dark Rocks", - "Dark Graveyard", "Dark Sanctuary", "Dark Lake", "Dark Desert South", - "Dark Eastern", "Dark Zora", "Dark Catfish", "Dark Dam", - "Dark Shop", "Dark Well", "Dark Blacksmith", "Dark Sick Kid", - "Dark Library", "Dark Mushroom", "Dark Bat", "Dark Fountain", - "Dark Fortune", "Dark Lake Shop", "Dark Bomb Shop", "Dark Cave 45", - "Dark Checker", "Dark Mini Moldorm", "Dark Ice Rod", "Dark Bonk", - "Dark Bottle", "Dark Sahasrahla", "Dark Chicken", "Dark Aginah", - "Dark Dam Exit", "Dark Mimic Exit", "Dark Waterfall", "Pyramid Top", - "Dark Fat Fairy", "Dark Spike Cave", "Dark Hookshot", "Dark Graveyard Ledge", - "Lumberjack House", "Dark Bumper", "Skull Woods A", "Skull Woods B", - "Skull Woods C", "Skull Woods D", "Skull Woods E", "Skull Woods F", - "Skull Woods G", "Skull Woods H", "Ice Palace Entry", "Misery Mire Entry", - "Palace of Darkness Entry", "Swamp Palace Entry", "Turtle Rock Entry", "Thieves' Town Entry", - - // Special Areas (0x80-0x9F) - "Special Area 1", "Special Area 2", "Special Area 3", "Special Area 4", - "Special Area 5", "Special Area 6", "Special Area 7", "Special Area 8", - "Special Area 9", "Special Area 10", "Special Area 11", "Special Area 12", - "Special Area 13", "Special Area 14", "Special Area 15", "Special Area 16", - "Special Area 17", "Special Area 18", "Special Area 19", "Special Area 20", - "Special Area 21", "Special Area 22", "Special Area 23", "Special Area 24", - "Special Area 25", "Special Area 26", "Special Area 27", "Special Area 28", - "Special Area 29", "Special Area 30", "Special Area 31", "Special Area 32" - }; + // Light World (0x00-0x3F) + "Lost Woods", "Master Sword Pedestal", "Castle Courtyard", "Link's House", + "Eastern Palace", "Desert Palace", "Hyrule Castle", "Witch's Hut", + "Kakariko Village", "Death Mountain", "Tower of Hera", "Spectacle Rock", + "Graveyard", "Sanctuary", "Lake Hylia", "Desert of Mystery", + "Eastern Ruins", "Zora's Domain", "Catfish", "Dam", "Potion Shop", + "Kakariko Well", "Blacksmith", "Sick Kid", "Library", "Mushroom", + "Magic Bat", "Fairy Fountain", "Fortune Teller", "Lake Shop", "Bomb Shop", + "Cave 45", "Checkerboard Cave", "Mini Moldorm Cave", "Ice Rod Cave", + "Bonk Rocks", "Bottle Merchant", "Sahasrahla's Hut", "Chicken House", + "Aginah's Cave", "Dam Exterior", "Mimic Cave Exterior", "Waterfall Fairy", + "Pyramid", "Fat Fairy", "Spike Cave", "Hookshot Cave", "Graveyard Ledge", + "Dark Lumberjacks", "Bumper Cave", "Skull Woods 1", "Skull Woods 2", + "Skull Woods 3", "Skull Woods 4", "Skull Woods 5", "Skull Woods 6", + "Skull Woods 7", "Skull Woods 8", "Ice Palace Exterior", + "Misery Mire Exterior", "Palace of Darkness Exterior", + "Swamp Palace Exterior", "Turtle Rock Exterior", "Thieves' Town Exterior", + + // Dark World (0x40-0x7F) + "Dark Woods", "Dark Chapel", "Dark Castle", "Dark Shields", "Dark Palace", + "Dark Desert", "Dark Castle Gate", "Dark Witch", "Dark Village", + "Dark Mountain", "Dark Tower", "Dark Rocks", "Dark Graveyard", + "Dark Sanctuary", "Dark Lake", "Dark Desert South", "Dark Eastern", + "Dark Zora", "Dark Catfish", "Dark Dam", "Dark Shop", "Dark Well", + "Dark Blacksmith", "Dark Sick Kid", "Dark Library", "Dark Mushroom", + "Dark Bat", "Dark Fountain", "Dark Fortune", "Dark Lake Shop", + "Dark Bomb Shop", "Dark Cave 45", "Dark Checker", "Dark Mini Moldorm", + "Dark Ice Rod", "Dark Bonk", "Dark Bottle", "Dark Sahasrahla", + "Dark Chicken", "Dark Aginah", "Dark Dam Exit", "Dark Mimic Exit", + "Dark Waterfall", "Pyramid Top", "Dark Fat Fairy", "Dark Spike Cave", + "Dark Hookshot", "Dark Graveyard Ledge", "Lumberjack House", + "Dark Bumper", "Skull Woods A", "Skull Woods B", "Skull Woods C", + "Skull Woods D", "Skull Woods E", "Skull Woods F", "Skull Woods G", + "Skull Woods H", "Ice Palace Entry", "Misery Mire Entry", + "Palace of Darkness Entry", "Swamp Palace Entry", "Turtle Rock Entry", + "Thieves' Town Entry", + + // Special Areas (0x80-0x9F) + "Special Area 1", "Special Area 2", "Special Area 3", "Special Area 4", + "Special Area 5", "Special Area 6", "Special Area 7", "Special Area 8", + "Special Area 9", "Special Area 10", "Special Area 11", "Special Area 12", + "Special Area 13", "Special Area 14", "Special Area 15", + "Special Area 16", "Special Area 17", "Special Area 18", + "Special Area 19", "Special Area 20", "Special Area 21", + "Special Area 22", "Special Area 23", "Special Area 24", + "Special Area 25", "Special Area 26", "Special Area 27", + "Special Area 28", "Special Area 29", "Special Area 30", + "Special Area 31", "Special Area 32"}; return map_names; } // Item names (complete item list) const std::vector& Zelda3Labels::GetItemNames() { static const std::vector item_names = { - "None", "Fighter Sword", "Master Sword", "Tempered Sword", - "Golden Sword", "Fighter Shield", "Fire Shield", "Mirror Shield", - "Fire Rod", "Ice Rod", "Hammer", "Hookshot", - "Bow", "Boomerang", "Powder", "Bee Badge", - "Bombos Medallion", "Ether Medallion", "Quake Medallion", "Lamp", - "Shovel", "Flute", "Somaria Cane", "Bottle", - "Heart Piece", "Byrna Cane", "Cape", "Mirror", - "Power Glove", "Titan Mitt", "Book of Mudora", "Zora Flippers", - "Moon Pearl", "Crystal", "Bug Net", "Blue Mail", - "Red Mail", "Key", "Compass", "Heart Container", - "Bomb", "3 Bombs", "Mushroom", "Red Boomerang", - "Red Potion", "Green Potion", "Blue Potion", "Red Potion (Refill)", - "Green Potion (Refill)", "Blue Potion (Refill)", "10 Bombs", "Big Key", - "Map", "1 Rupee", "5 Rupees", "20 Rupees", - "Pendant of Courage", "Pendant of Wisdom", "Pendant of Power", "Bow and Arrows", - "Silver Arrows Upgrade", "Bee", "Fairy", "Heart Container (Boss)", - "Heart", "1 Arrow", "10 Arrows", "Magic", - "Small Magic", "300 Rupees", "20 Rupees (Green)", "100 Rupees", - "50 Rupees", "Heart Container (Sanctuary)", "Arrow Refill (5)", "Arrow Refill (10)", - "Bomb Refill (1)", "Bomb Refill (4)", "Bomb Refill (8)", "Blue Shield (Refill)", - "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Programmable Item 1", "Programmable Item 2", - "Programmable Item 3", "Silvers", "Rupoor", "Null Item", - "Red Clock", "Blue Clock", "Green Clock", "Progressive Sword", - "Progressive Shield", "Progressive Armor", "Progressive Lifting Glove", "RNG Item (Single)", - "RNG Item (Multi)", "Progressive Bow", "Progressive Bow (Alt)", "Aga Pendant", - "Pendant (Green)", "Blue Pendant", "Good Bee", "Tossed Key" - }; + "None", + "Fighter Sword", + "Master Sword", + "Tempered Sword", + "Golden Sword", + "Fighter Shield", + "Fire Shield", + "Mirror Shield", + "Fire Rod", + "Ice Rod", + "Hammer", + "Hookshot", + "Bow", + "Boomerang", + "Powder", + "Bee Badge", + "Bombos Medallion", + "Ether Medallion", + "Quake Medallion", + "Lamp", + "Shovel", + "Flute", + "Somaria Cane", + "Bottle", + "Heart Piece", + "Byrna Cane", + "Cape", + "Mirror", + "Power Glove", + "Titan Mitt", + "Book of Mudora", + "Zora Flippers", + "Moon Pearl", + "Crystal", + "Bug Net", + "Blue Mail", + "Red Mail", + "Key", + "Compass", + "Heart Container", + "Bomb", + "3 Bombs", + "Mushroom", + "Red Boomerang", + "Red Potion", + "Green Potion", + "Blue Potion", + "Red Potion (Refill)", + "Green Potion (Refill)", + "Blue Potion (Refill)", + "10 Bombs", + "Big Key", + "Map", + "1 Rupee", + "5 Rupees", + "20 Rupees", + "Pendant of Courage", + "Pendant of Wisdom", + "Pendant of Power", + "Bow and Arrows", + "Silver Arrows Upgrade", + "Bee", + "Fairy", + "Heart Container (Boss)", + "Heart", + "1 Arrow", + "10 Arrows", + "Magic", + "Small Magic", + "300 Rupees", + "20 Rupees (Green)", + "100 Rupees", + "50 Rupees", + "Heart Container (Sanctuary)", + "Arrow Refill (5)", + "Arrow Refill (10)", + "Bomb Refill (1)", + "Bomb Refill (4)", + "Bomb Refill (8)", + "Blue Shield (Refill)", + "Magic Upgrade (1/2)", + "Magic Upgrade (1/4)", + "Programmable Item 1", + "Programmable Item 2", + "Programmable Item 3", + "Silvers", + "Rupoor", + "Null Item", + "Red Clock", + "Blue Clock", + "Green Clock", + "Progressive Sword", + "Progressive Shield", + "Progressive Armor", + "Progressive Lifting Glove", + "RNG Item (Single)", + "RNG Item (Multi)", + "Progressive Bow", + "Progressive Bow (Alt)", + "Aga Pendant", + "Pendant (Green)", + "Blue Pendant", + "Good Bee", + "Tossed Key"}; return item_names; } // Music track names const std::vector& Zelda3Labels::GetMusicTrackNames() { static const std::vector music_names = { - "Nothing", "Light World", "Beginning", "Rabbit", - "Forest", "Intro", "Town", "Warp", - "Dark World", "Master Sword", "File Select", "Soldier", - "Boss", "Dark World Death Mountain", "Minigame", "Skull Woods", - "Indoor", "Cave 1", "Zelda's Rescue", "Crystal", - "Shop", "Cave 2", "Game Over", "Boss Victory", - "Sanctuary", "Boss Victory (Short)", "Dark World Woods", "Pendant", - "Ganon's Message", "Hyrule Castle", "Light World Death Mountain", "Eastern Palace", - "Desert Palace", "Agahnim's Theme", "Damp Dungeon", "Ganon Reveals", - "Confrontation", "Ganon's Theme", "Triforce", "Credits", - "Unused", "Unused", "Unused", "Unused", - "Unused", "Unused", "Unused", "Unused" - }; + "Nothing", + "Light World", + "Beginning", + "Rabbit", + "Forest", + "Intro", + "Town", + "Warp", + "Dark World", + "Master Sword", + "File Select", + "Soldier", + "Boss", + "Dark World Death Mountain", + "Minigame", + "Skull Woods", + "Indoor", + "Cave 1", + "Zelda's Rescue", + "Crystal", + "Shop", + "Cave 2", + "Game Over", + "Boss Victory", + "Sanctuary", + "Boss Victory (Short)", + "Dark World Woods", + "Pendant", + "Ganon's Message", + "Hyrule Castle", + "Light World Death Mountain", + "Eastern Palace", + "Desert Palace", + "Agahnim's Theme", + "Damp Dungeon", + "Ganon Reveals", + "Confrontation", + "Ganon's Theme", + "Triforce", + "Credits", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused", + "Unused"}; return music_names; } // Graphics sheet names const std::vector& Zelda3Labels::GetGraphicsSheetNames() { - static const std::vector gfx_names = { - "Sprite Sheet 0", "Sprite Sheet 1", "Sprite Sheet 2", "Sprite Sheet 3", - "Sprite Sheet 4", "Sprite Sheet 5", "Sprite Sheet 6", "Sprite Sheet 7", - "Sprite Sheet 8", "Sprite Sheet 9", "Sprite Sheet A", "Sprite Sheet B", - "Sprite Sheet C", "Sprite Sheet D", "Sprite Sheet E", "Sprite Sheet F", - "Link's Sprites", "Sword Sprites", "Shield Sprites", "Common Sprites", - "Boss Sprites", "NPC Sprites", "Enemy Sprites 1", "Enemy Sprites 2", - "Item Sprites", "Dungeon Objects", "Overworld Objects", "Interface", - "Font", "Credits", "Unused", "Unused" - }; + static const std::vector gfx_names = {"Sprite Sheet 0", + "Sprite Sheet 1", + "Sprite Sheet 2", + "Sprite Sheet 3", + "Sprite Sheet 4", + "Sprite Sheet 5", + "Sprite Sheet 6", + "Sprite Sheet 7", + "Sprite Sheet 8", + "Sprite Sheet 9", + "Sprite Sheet A", + "Sprite Sheet B", + "Sprite Sheet C", + "Sprite Sheet D", + "Sprite Sheet E", + "Sprite Sheet F", + "Link's Sprites", + "Sword Sprites", + "Shield Sprites", + "Common Sprites", + "Boss Sprites", + "NPC Sprites", + "Enemy Sprites 1", + "Enemy Sprites 2", + "Item Sprites", + "Dungeon Objects", + "Overworld Objects", + "Interface", + "Font", + "Credits", + "Unused", + "Unused"}; return gfx_names; } // Room object names - these are large, so we'll delegate to a helper namespace { - std::vector ConvertArrayToVector(const char** array, size_t size) { - std::vector result; - result.reserve(size); - for (size_t i = 0; i < size; ++i) { - result.emplace_back(array[i]); - } - return result; +std::vector ConvertArrayToVector(const char** array, size_t size) { + std::vector result; + result.reserve(size); + for (size_t i = 0; i < size; ++i) { + result.emplace_back(array[i]); } + return result; } +} // namespace const std::vector& Zelda3Labels::GetType1RoomObjectNames() { static const std::vector names = []() { std::vector result; // Note: Type1RoomObjectNames is constexpr, we need to count its size - // For now, we'll add known objects. In full implementation, + // For now, we'll add known objects. In full implementation, // we'd import from room_object.h result = { - "Ceiling ↔", "Wall (top, north) ↔", "Wall (top, south) ↔", - "Wall (bottom, north) ↔", "Wall (bottom, south) ↔", "Wall columns (north) ↔", - "Wall columns (south) ↔", "Deep wall (north) ↔", "Deep wall (south) ↔", - "Diagonal wall A ◤ (top) ↔", "Diagonal wall A ◣ (top) ↔", - "Diagonal wall A ◥ (top) ↔", "Diagonal wall A ◢ (top) ↔", - // ... Add all Type1 objects here + "Ceiling ↔", + "Wall (top, north) ↔", + "Wall (top, south) ↔", + "Wall (bottom, north) ↔", + "Wall (bottom, south) ↔", + "Wall columns (north) ↔", + "Wall columns (south) ↔", + "Deep wall (north) ↔", + "Deep wall (south) ↔", + "Diagonal wall A ◤ (top) ↔", + "Diagonal wall A ◣ (top) ↔", + "Diagonal wall A ◥ (top) ↔", + "Diagonal wall A ◢ (top) ↔", + // ... Add all Type1 objects here }; return result; }(); @@ -236,137 +383,172 @@ const std::vector& Zelda3Labels::GetRoomEffectNames() { // Room tag names const std::vector& Zelda3Labels::GetRoomTagNames() { static const std::vector tag_names = { - "No Tag", "NW", "NE", "SW", "SE", - "West", "East", "North", "South", - "Entrance", "Treasure", "Boss", "Dark" - }; + "No Tag", "NW", "NE", "SW", "SE", "West", "East", + "North", "South", "Entrance", "Treasure", "Boss", "Dark"}; return tag_names; } // Tile type names const std::vector& Zelda3Labels::GetTileTypeNames() { static const std::vector tile_names = { - "Nothing (standard floor)", "Nothing (unused?)", "Collision", - "Collision (unknown types)", "Collision", "Collision (unused?)", - "Collision", "Collision", "Deep water", "Shallow water", - "Unknown (near water/pit edges)", "Collision (water/pit edges)", - "Overlay mask", "Spike floor", "GT ice", "Ice palace ice", - "Slope ◤", "Slope ◥", "Slope ◣", "Slope ◢", - "Nothing (unused?)", "Nothing (unused?)", "Nothing (unused?)", - "Slope ◤", "Slope ◥", "Slope ◣", "Slope ◢", - "Layer swap", "Pit", "Manual stairs", "Pot switch", "Pressure switch", - "Blocks switch (chest, PoD, walls)", "Layer toggle", "Layer 2 overlay", - "North single-layer auto stairs", "North layer swap auto stairs", - "South single-layer auto stairs", "South layer swap auto stairs", - "North/south layer swap auto stairs", "North/south single-layer auto stairs", - "West single-layer auto stairs", "West layer swap auto stairs", - "East single-layer auto stairs", "East layer swap auto stairs", - "East/west layer swap auto stairs", "East/west single-layer auto stairs", - "Nothing (stairs edge)", "Straight inter-room stairs south/up", - "Straight inter-room stairs north/down", "Straight inter-room stairs south/down 2", - "Straight inter-room stairs north/up 2", "Star tile (inactive on GBA)", - "Collision (near stairs)", "Warp tile", "Square corners ⌜⌝⌞⌟", - "Thick corner ⌜", "Thick corner ⌝", "Thick corner ⌞", "Thick corner ⌟", - "Roof/grass tiles?", "Spike floor" - }; + "Nothing (standard floor)", + "Nothing (unused?)", + "Collision", + "Collision (unknown types)", + "Collision", + "Collision (unused?)", + "Collision", + "Collision", + "Deep water", + "Shallow water", + "Unknown (near water/pit edges)", + "Collision (water/pit edges)", + "Overlay mask", + "Spike floor", + "GT ice", + "Ice palace ice", + "Slope ◤", + "Slope ◥", + "Slope ◣", + "Slope ◢", + "Nothing (unused?)", + "Nothing (unused?)", + "Nothing (unused?)", + "Slope ◤", + "Slope ◥", + "Slope ◣", + "Slope ◢", + "Layer swap", + "Pit", + "Manual stairs", + "Pot switch", + "Pressure switch", + "Blocks switch (chest, PoD, walls)", + "Layer toggle", + "Layer 2 overlay", + "North single-layer auto stairs", + "North layer swap auto stairs", + "South single-layer auto stairs", + "South layer swap auto stairs", + "North/south layer swap auto stairs", + "North/south single-layer auto stairs", + "West single-layer auto stairs", + "West layer swap auto stairs", + "East single-layer auto stairs", + "East layer swap auto stairs", + "East/west layer swap auto stairs", + "East/west single-layer auto stairs", + "Nothing (stairs edge)", + "Straight inter-room stairs south/up", + "Straight inter-room stairs north/down", + "Straight inter-room stairs south/down 2", + "Straight inter-room stairs north/up 2", + "Star tile (inactive on GBA)", + "Collision (near stairs)", + "Warp tile", + "Square corners ⌜⌝⌞⌟", + "Thick corner ⌜", + "Thick corner ⌝", + "Thick corner ⌞", + "Thick corner ⌟", + "Roof/grass tiles?", + "Spike floor"}; return tile_names; } // Convert all labels to structured map for project embedding -std::unordered_map> +std::unordered_map> Zelda3Labels::ToResourceLabels() { - std::unordered_map> labels; - + std::unordered_map> + labels; + // Rooms const auto& rooms = GetRoomNames(); for (size_t i = 0; i < rooms.size(); ++i) { labels["room"][std::to_string(i)] = rooms[i]; } - + // Entrances const auto& entrances = GetEntranceNames(); for (size_t i = 0; i < entrances.size(); ++i) { labels["entrance"][std::to_string(i)] = entrances[i]; } - + // Sprites const auto& sprites = GetSpriteNames(); for (size_t i = 0; i < sprites.size(); ++i) { labels["sprite"][std::to_string(i)] = sprites[i]; } - + // Overlords const auto& overlords = GetOverlordNames(); for (size_t i = 0; i < overlords.size(); ++i) { labels["overlord"][std::to_string(i)] = overlords[i]; } - + // Overworld maps const auto& maps = GetOverworldMapNames(); for (size_t i = 0; i < maps.size(); ++i) { labels["overworld_map"][std::to_string(i)] = maps[i]; } - + // Items const auto& items = GetItemNames(); for (size_t i = 0; i < items.size(); ++i) { labels["item"][std::to_string(i)] = items[i]; } - + // Music tracks const auto& music = GetMusicTrackNames(); for (size_t i = 0; i < music.size(); ++i) { labels["music"][std::to_string(i)] = music[i]; } - + // Graphics sheets const auto& gfx = GetGraphicsSheetNames(); for (size_t i = 0; i < gfx.size(); ++i) { labels["graphics"][std::to_string(i)] = gfx[i]; } - + // Room effects const auto& effects = GetRoomEffectNames(); for (size_t i = 0; i < effects.size(); ++i) { labels["room_effect"][std::to_string(i)] = effects[i]; } - + // Room tags const auto& tags = GetRoomTagNames(); for (size_t i = 0; i < tags.size(); ++i) { labels["room_tag"][std::to_string(i)] = tags[i]; } - + // Tile types const auto& tiles = GetTileTypeNames(); for (size_t i = 0; i < tiles.size(); ++i) { labels["tile_type"][std::to_string(i)] = tiles[i]; } - + return labels; } // Get a label by resource type and ID std::string Zelda3Labels::GetLabel(const std::string& resource_type, int id, - const std::string& default_value) { + const std::string& default_value) { static auto labels = ToResourceLabels(); - + auto type_it = labels.find(resource_type); if (type_it == labels.end()) { - return default_value.empty() - ? resource_type + "_" + std::to_string(id) - : default_value; + return default_value.empty() ? resource_type + "_" + std::to_string(id) + : default_value; } - + auto label_it = type_it->second.find(std::to_string(id)); if (label_it == type_it->second.end()) { - return default_value.empty() - ? resource_type + "_" + std::to_string(id) - : default_value; + return default_value.empty() ? resource_type + "_" + std::to_string(id) + : default_value; } - + return label_it->second; } -} // namespace zelda3 -} // namespace yaze +} // namespace yaze::zelda3 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 54eca127..4659a306 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -48,10 +48,10 @@ if(YAZE_BUILD_TESTS) if(MSVC) target_link_options(${suite_name} PRIVATE /STACK:16777216) # Force whole-archive linking for protobuf to ensure all symbols are included - if(YAZE_WITH_GRPC AND YAZE_PROTOBUF_TARGET) - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_link_options(${suite_name} PRIVATE /WHOLEARCHIVE:$) - endif() + if(YAZE_WITH_GRPC AND YAZE_PROTOBUF_TARGETS) + foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_TARGETS) + target_link_options(${suite_name} PRIVATE /WHOLEARCHIVE:$) + endforeach() endif() else() target_link_options(${suite_name} PRIVATE -Wl,--stack,16777216)