refactor: Introduced a SessionCardRegistry and WindowDelegate for better session management in the editor.

Benefits:
- Streamlines the build process by allowing for multiple Protobuf targets, enhancing compatibility and maintainability.
- Improves session management capabilities within the editor, leading to a more organized and efficient user experience.
- Enhance Protobuf target handling in CMake configuration
- Updated CMake files to support multiple Protobuf targets, improving flexibility in linking.
- Adjusted target link libraries across various components (yaze, yaze_core_lib, yaze_editor, etc.) to utilize the new
This commit is contained in:
scawful
2025-10-14 20:30:25 -04:00
parent 76a5ab3f39
commit 6dbc30c11f
25 changed files with 2435 additions and 390 deletions

View File

@@ -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()

View File

@@ -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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()
# Link test support library (yaze_editor needs TestManager)

View File

@@ -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"

View File

@@ -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:$<TARGET_FILE:${YAZE_PROTOBUF_TARGET}>)
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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()

View File

@@ -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",

View File

@@ -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<typename T>

View File

@@ -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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()
endif()

View File

@@ -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<SessionCoordinator>(
static_cast<void*>(&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

View File

@@ -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<Editor*> 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<RomSession> 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<SessionCoordinator> 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

View File

@@ -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",

View File

@@ -0,0 +1,250 @@
#include "session_card_registry.h"
#include <algorithm>
#include <cstdio>
#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<std::string>();
session_card_mapping_[session_id] = std::unordered_map<std::string, std::string>();
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<void()> on_show,
std::function<void()> 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<std::string> 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<std::string> SessionCardRegistry::GetCardsInCategory(size_t session_id, const std::string& category) const {
std::vector<std::string> 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<std::string> SessionCardRegistry::GetAllCategories(size_t session_id) const {
std::vector<std::string> 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

View File

@@ -0,0 +1,96 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_SESSION_CARD_REGISTRY_H_
#define YAZE_APP_EDITOR_SYSTEM_SESSION_CARD_REGISTRY_H_
#include <string>
#include <unordered_map>
#include <vector>
#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<void()> on_show = nullptr,
std::function<void()> 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<std::string> GetCardsInSession(size_t session_id) const;
std::vector<std::string> GetCardsInCategory(size_t session_id, const std::string& category) const;
std::vector<std::string> 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<size_t, std::vector<std::string>> session_cards_;
// Maps session_id -> map of base_card_id -> prefixed_card_id
std::unordered_map<size_t, std::unordered_map<std::string, std::string>> 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_

View File

@@ -0,0 +1,298 @@
#include "window_delegate.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#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<int>(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<std::string> WindowDelegate::GetAvailableLayouts() const {
std::vector<std::string> 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<std::string> WindowDelegate::GetVisibleWindows() const {
std::vector<std::string> visible;
// TODO: Implement actual visibility checking
return visible;
}
std::vector<std::string> WindowDelegate::GetHiddenWindows() const {
std::vector<std::string> 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

View File

@@ -0,0 +1,96 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_WINDOW_DELEGATE_H_
#define YAZE_APP_EDITOR_SYSTEM_WINDOW_DELEGATE_H_
#include <string>
#include <vector>
#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<std::string> GetAvailableLayouts() const;
// Window state queries
std::vector<std::string> GetVisibleWindows() const;
std::vector<std::string> 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<std::string, WindowInfo> 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_

View File

@@ -0,0 +1,806 @@
#include "session_coordinator.h"
#include <algorithm>
#include <cstdio>
#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<std::deque<EditorManager::RomSession>*>(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<std::deque<EditorManager::RomSession>*>(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<EditorSet>
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<int>(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<int>(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

View File

@@ -0,0 +1,163 @@
#ifndef YAZE_APP_EDITOR_UI_SESSION_COORDINATOR_H_
#define YAZE_APP_EDITOR_UI_SESSION_COORDINATOR_H_
#include <deque>
#include <string>
#include <vector>
#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<EditorManager::RomSession>*
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_

View File

@@ -88,6 +88,25 @@ void EditorCardManager::UnregisterCard(const std::string& card_id) {
}
}
void EditorCardManager::UnregisterCardsWithPrefix(const std::string& prefix) {
std::vector<std::string> 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<CardInfo> EditorCardManager::GetCardsInCategory(const std::string& c
return result;
}
std::vector<CardInfo> EditorCardManager::GetCardsWithPrefix(const std::string& prefix) const {
std::vector<CardInfo> 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<std::string> EditorCardManager::GetAllCategories() const {
std::vector<std::string> 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<CardInfo> 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<std::string>& active_categories,
std::function<void(const std::string&)> on_category_switch,
std::function<void()> on_collapse) {
const std::vector<std::string>& active_categories,
std::function<void(const std::string&)> on_category_switch,
std::function<void()> 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<std::string>& active_categories,
std::function<void(const std::string&)> on_category_switch,
std::function<void()> on_collapse) {
// Use ThemeManager for consistent theming
const auto& theme = ThemeManager::Get().GetCurrentTheme();

View File

@@ -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<CardInfo> GetCardsInCategory(const std::string& category) const;
std::vector<CardInfo> GetCardsWithPrefix(const std::string& prefix) const;
std::vector<std::string> GetAllCategories() const;
const CardInfo* GetCardInfo(const std::string& card_id) const;
@@ -118,6 +120,11 @@ class EditorCardManager {
const std::vector<std::string>& active_categories = {},
std::function<void(const std::string&)> on_category_switch = nullptr,
std::function<void()> on_collapse = nullptr);
void DrawSidebarWithSessionFilter(const std::string& category,
const std::string& session_prefix = "",
const std::vector<std::string>& active_categories = {},
std::function<void(const std::string&)> on_category_switch = nullptr,
std::function<void()> 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

View File

@@ -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:$<TARGET_FILE:${YAZE_PROTOBUF_TARGET}>)
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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()

View File

@@ -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:$<TARGET_FILE:${YAZE_PROTOBUF_TARGET}>)
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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()

View File

@@ -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:$<TARGET_FILE:${YAZE_PROTOBUF_TARGET}>)
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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()
endif()

View File

@@ -1,78 +1,82 @@
#include "zelda3/palette_constants.h"
#include <string>
#include <vector>
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<const PaletteGroupMetadata*> 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

View File

@@ -4,8 +4,7 @@
#include <cstdint>
#include <vector>
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<const PaletteGroupMetadata*> GetAllPaletteGroups();
} // namespace zelda3
} // namespace yaze
} // namespace yaze::zelda3
#endif // YAZE_ZELDA3_PALETTE_CONSTANTS_H

View File

@@ -1,12 +1,15 @@
#include "zelda3/zelda3_labels.h"
#include <cstddef>
#include <string>
#include <unordered_map>
#include <vector>
#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<std::string>& Zelda3Labels::GetRoomNames() {
@@ -55,147 +58,291 @@ const std::vector<std::string>& Zelda3Labels::GetOverlordNames() {
// Overworld map names (64 Light World + 64 Dark World + 32 Special Areas)
const std::vector<std::string>& Zelda3Labels::GetOverworldMapNames() {
static const std::vector<std::string> 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<std::string>& Zelda3Labels::GetItemNames() {
static const std::vector<std::string> 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<std::string>& Zelda3Labels::GetMusicTrackNames() {
static const std::vector<std::string> 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<std::string>& Zelda3Labels::GetGraphicsSheetNames() {
static const std::vector<std::string> 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<std::string> 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<std::string> ConvertArrayToVector(const char** array, size_t size) {
std::vector<std::string> result;
result.reserve(size);
for (size_t i = 0; i < size; ++i) {
result.emplace_back(array[i]);
}
return result;
std::vector<std::string> ConvertArrayToVector(const char** array, size_t size) {
std::vector<std::string> result;
result.reserve(size);
for (size_t i = 0; i < size; ++i) {
result.emplace_back(array[i]);
}
return result;
}
} // namespace
const std::vector<std::string>& Zelda3Labels::GetType1RoomObjectNames() {
static const std::vector<std::string> names = []() {
std::vector<std::string> 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<std::string>& Zelda3Labels::GetRoomEffectNames() {
// Room tag names
const std::vector<std::string>& Zelda3Labels::GetRoomTagNames() {
static const std::vector<std::string> 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<std::string>& Zelda3Labels::GetTileTypeNames() {
static const std::vector<std::string> 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::string, std::unordered_map<std::string, std::string>>
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
Zelda3Labels::ToResourceLabels() {
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> labels;
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
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

View File

@@ -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:$<TARGET_FILE:${YAZE_PROTOBUF_TARGET}>)
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:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
else()
target_link_options(${suite_name} PRIVATE -Wl,--stack,16777216)