yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
editor_manager.cc
Go to the documentation of this file.
1#include "editor_manager.h"
2
3#include <algorithm>
4#include <chrono>
5#include <cstring>
6#include <filesystem>
7#include <memory>
8#include <sstream>
9#include <string>
10#include <vector>
11
12#define IMGUI_DEFINE_MATH_OPERATORS
13
14#include "absl/status/status.h"
15#include "absl/status/statusor.h"
16#include "absl/strings/match.h"
17#include "absl/strings/str_cat.h"
18#include "absl/strings/str_format.h"
19#include "core/features.h"
20#include "core/project.h"
21#include "app/platform/timing.h"
38#include "app/emu/emulator.h"
42#include "app/gui/core/icons.h"
43#include "app/gui/core/input.h"
45#include "app/rom.h"
47#include "imgui/imgui.h"
48#include "util/file_util.h"
49#include "util/log.h"
51
52#ifdef YAZE_ENABLE_TESTING
57#endif
58#ifdef YAZE_ENABLE_GTEST
60#endif
61
62#include "app/editor/editor.h"
66
67#ifdef YAZE_WITH_GRPC
75#endif
76
77#include "imgui/misc/cpp/imgui_stdlib.h"
78#include "util/macro.h"
79#include "yaze_config.h"
80
81namespace yaze {
82namespace editor {
83
84using util::FileDialogWrapper;
85
86namespace {
87
88std::string GetEditorName(EditorType type) {
89 return kEditorNames[static_cast<int>(type)];
90}
91
92} // namespace
93
94// Static registry of editors that use the card-based layout system
95// These editors register their cards with EditorCardManager and manage their own windows
96// They do NOT need the traditional ImGui::Begin/End wrapper - they create cards internally
100
102 if (!current_editor_) {
103 return;
104 }
105
106 // Using EditorCardRegistry directly
107 std::string category =
110}
111
113 // Using EditorCardRegistry directly
114 card_registry_.ShowCard("memory.hex_editor");
115}
116
117#ifdef YAZE_WITH_GRPC
118void EditorManager::ShowAIAgent() {
119 agent_editor_.set_active(true);
120}
121
122void EditorManager::ShowChatHistory() {
124}
125#endif
126
128 : blank_editor_set_(nullptr, &user_settings_),
129 project_manager_(&toast_manager_),
130 rom_file_manager_(&toast_manager_) {
131 std::stringstream ss;
132 ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
133 << YAZE_VERSION_PATCH;
134 ss >> version_;
135
136 // ============================================================================
137 // DELEGATION INFRASTRUCTURE INITIALIZATION
138 // ============================================================================
139 // EditorManager delegates responsibilities to specialized components:
140 // - SessionCoordinator: Multi-session UI and lifecycle management
141 // - MenuOrchestrator: Menu building and action routing
142 // - UICoordinator: UI drawing and state management
143 // - RomFileManager: ROM file I/O operations
144 // - ProjectManager: Project file operations
145 // - EditorCardRegistry: Card-based editor UI management
146 // - ShortcutConfigurator: Keyboard shortcut registration
147 // - WindowDelegate: Window layout operations
148 // - PopupManager: Modal popup/dialog management
149 //
150 // EditorManager retains:
151 // - Session storage (sessions_) and current pointers (current_rom_, etc.)
152 // - Main update loop (iterates sessions, calls editor updates)
153 // - Asset loading (Initialize/Load on all editors)
154 // - Session ID tracking (current_session_id_)
155 //
156 // INITIALIZATION ORDER (CRITICAL):
157 // 1. PopupManager - MUST be first, MenuOrchestrator/UICoordinator take ref to it
158 // 2. SessionCoordinator - Independent, can be early
159 // 3. MenuOrchestrator - Depends on PopupManager, SessionCoordinator
160 // 4. UICoordinator - Depends on PopupManager, SessionCoordinator
161 // 5. ShortcutConfigurator - Created in Initialize(), depends on all above
162 //
163 // If this order is violated, you will get SIGSEGV crashes when menu callbacks
164 // try to call popup_manager_.Show() with an uninitialized PopupManager!
165 // ============================================================================
166
167 // STEP 1: Initialize PopupManager FIRST
168 popup_manager_ = std::make_unique<PopupManager>(this);
169 popup_manager_->Initialize(); // Registers all popups with PopupID constants
170
171 // STEP 2: Initialize SessionCoordinator (independent of popups)
172 session_coordinator_ = std::make_unique<SessionCoordinator>(
173 static_cast<void*>(&sessions_), &card_registry_, &toast_manager_, &user_settings_);
174
175 // STEP 3: Initialize MenuOrchestrator (depends on popup_manager_, session_coordinator_)
176 menu_orchestrator_ = std::make_unique<MenuOrchestrator>(
179
180 session_coordinator_->SetEditorManager(this);
181
182 // STEP 4: Initialize UICoordinator (depends on popup_manager_, session_coordinator_, card_registry_)
183 ui_coordinator_ = std::make_unique<UICoordinator>(
187
188 // STEP 4.5: Initialize LayoutManager (DockBuilder layouts for editors)
189 layout_manager_ = std::make_unique<LayoutManager>();
190
191 // STEP 5: ShortcutConfigurator created later in Initialize() method
192 // It depends on all above coordinators being available
193}
194
196
198 auto& test_manager = test::TestManager::Get();
199
200#ifdef YAZE_ENABLE_TESTING
201 // Register comprehensive test suites
202 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
203 test_manager.RegisterTestSuite(
204 std::make_unique<test::PerformanceTestSuite>());
205 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
206 test_manager.RegisterTestSuite(
207 std::make_unique<test::RomDependentTestSuite>());
208
209 // Register new E2E and ZSCustomOverworld test suites
210 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
211 test_manager.RegisterTestSuite(
212 std::make_unique<test::ZSCustomOverworldTestSuite>());
213#endif
214
215 // Register Google Test suite if available
216#ifdef YAZE_ENABLE_GTEST
217 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
218#endif
219
220 // Register z3ed AI Agent test suites (requires gRPC)
221#ifdef YAZE_WITH_GRPC
223#endif
224
225 // Update resource monitoring to track Arena state
226 test_manager.UpdateResourceStats();
227}
228
229constexpr const char* kOverworldEditorName = ICON_MD_LAYERS " Overworld Editor";
230constexpr const char* kGraphicsEditorName = ICON_MD_PHOTO " Graphics Editor";
231constexpr const char* kPaletteEditorName = ICON_MD_PALETTE " Palette Editor";
232constexpr const char* kScreenEditorName = ICON_MD_SCREENSHOT " Screen Editor";
233constexpr const char* kSpriteEditorName = ICON_MD_SMART_TOY " Sprite Editor";
234constexpr const char* kMessageEditorName = ICON_MD_MESSAGE " Message Editor";
235constexpr const char* kSettingsEditorName = ICON_MD_SETTINGS " Settings Editor";
236constexpr const char* kAssemblyEditorName = ICON_MD_CODE " Assembly Editor";
237constexpr const char* kDungeonEditorName = ICON_MD_CASTLE " Dungeon Editor";
238constexpr const char* kMusicEditorName = ICON_MD_MUSIC_NOTE " Music Editor";
239
241 const std::string& filename) {
242 renderer_ = renderer;
243
244 // Inject card_registry into emulator and workspace_manager
247
248 // Point to a blank editor set when no ROM is loaded
249 // current_editor_set_ = &blank_editor_set_;
250
251 if (!filename.empty()) {
253 }
254
255 // Note: PopupManager is now initialized in constructor before MenuOrchestrator
256 // This ensures all menu callbacks can safely call popup_manager_.Show()
257
258 // Register emulator cards early (emulator Initialize might not be called)
259 // Using EditorCardRegistry directly
260 card_registry_.RegisterCard({.card_id = "emulator.cpu_debugger",
261 .display_name = "CPU Debugger",
262 .icon = ICON_MD_BUG_REPORT,
263 .category = "Emulator",
264 .priority = 10});
265 card_registry_.RegisterCard({.card_id = "emulator.ppu_viewer",
266 .display_name = "PPU Viewer",
268 .category = "Emulator",
269 .priority = 20});
270 card_registry_.RegisterCard({.card_id = "emulator.memory_viewer",
271 .display_name = "Memory Viewer",
272 .icon = ICON_MD_MEMORY,
273 .category = "Emulator",
274 .priority = 30});
275 card_registry_.RegisterCard({.card_id = "emulator.breakpoints",
276 .display_name = "Breakpoints",
277 .icon = ICON_MD_STOP,
278 .category = "Emulator",
279 .priority = 40});
280 card_registry_.RegisterCard({.card_id = "emulator.performance",
281 .display_name = "Performance",
282 .icon = ICON_MD_SPEED,
283 .category = "Emulator",
284 .priority = 50});
285 card_registry_.RegisterCard({.card_id = "emulator.ai_agent",
286 .display_name = "AI Agent",
287 .icon = ICON_MD_SMART_TOY,
288 .category = "Emulator",
289 .priority = 60});
290 card_registry_.RegisterCard({.card_id = "emulator.save_states",
291 .display_name = "Save States",
292 .icon = ICON_MD_SAVE,
293 .category = "Emulator",
294 .priority = 70});
295 card_registry_.RegisterCard({.card_id = "emulator.keyboard_config",
296 .display_name = "Keyboard Config",
297 .icon = ICON_MD_KEYBOARD,
298 .category = "Emulator",
299 .priority = 80});
300 card_registry_.RegisterCard({.card_id = "emulator.apu_debugger",
301 .display_name = "APU Debugger",
302 .icon = ICON_MD_AUDIOTRACK,
303 .category = "Emulator",
304 .priority = 90});
305 card_registry_.RegisterCard({.card_id = "emulator.audio_mixer",
306 .display_name = "Audio Mixer",
307 .icon = ICON_MD_AUDIO_FILE,
308 .category = "Emulator",
309 .priority = 100});
310
311 // Show CPU debugger and PPU viewer by default for emulator
312 card_registry_.ShowCard("emulator.cpu_debugger");
313 card_registry_.ShowCard("emulator.ppu_viewer");
314
315 // Register memory/hex editor card
316 card_registry_.RegisterCard({.card_id = "memory.hex_editor",
317 .display_name = "Hex Editor",
318 .icon = ICON_MD_MEMORY,
319 .category = "Memory",
320 .priority = 10});
321
322 // Initialize project file editor
324
325#ifdef YAZE_WITH_GRPC
326 // Initialize the agent editor as a proper Editor (configuration dashboard)
327 // TODO: pass agent editor dependencies once agent editor is modernized
328 agent_editor_.Initialize();
329 agent_editor_.InitializeWithDependencies(&toast_manager_, &proposal_drawer_,
330 nullptr);
331
332 // Initialize and connect the chat history popup
334 if (agent_editor_.GetChatWidget()) {
335 agent_editor_.GetChatWidget()->SetChatHistoryPopup(
337 }
338
339 // Set up multimodal (vision) callbacks for Gemini
340 AgentChatWidget::MultimodalCallbacks multimodal_callbacks;
341 multimodal_callbacks.capture_snapshot =
342 [this](std::filesystem::path* output_path) -> absl::Status {
343 using CaptureMode = AgentChatWidget::CaptureMode;
344
345 absl::StatusOr<yaze::test::ScreenshotArtifact> result;
346
347 // Capture based on selected mode
348 switch (agent_editor_.GetChatWidget()->capture_mode()) {
349 case CaptureMode::kFullWindow:
350 result = yaze::test::CaptureHarnessScreenshot("");
351 break;
352
353 case CaptureMode::kActiveEditor:
354 result = yaze::test::CaptureActiveWindow("");
355 if (!result.ok()) {
356 // Fallback to full window if no active window
357 result = yaze::test::CaptureHarnessScreenshot("");
358 }
359 break;
360
361 case CaptureMode::kSpecificWindow: {
362 const char* window_name =
363 agent_editor_.GetChatWidget()->specific_window_name();
364 if (window_name && std::strlen(window_name) > 0) {
365 result = yaze::test::CaptureWindowByName(window_name, "");
366 if (!result.ok()) {
367 // Fallback to active window if specific window not found
368 result = yaze::test::CaptureActiveWindow("");
369 }
370 } else {
371 result = yaze::test::CaptureActiveWindow("");
372 }
373 if (!result.ok()) {
374 result = yaze::test::CaptureHarnessScreenshot("");
375 }
376 break;
377 }
378 }
379
380 if (!result.ok()) {
381 return result.status();
382 }
383 *output_path = result->file_path;
384 return absl::OkStatus();
385 };
386 multimodal_callbacks.send_to_gemini =
387 [this](const std::filesystem::path& image_path,
388 const std::string& prompt) -> absl::Status {
389 // Get Gemini API key from environment
390 const char* api_key = std::getenv("GEMINI_API_KEY");
391 if (!api_key || std::strlen(api_key) == 0) {
392 return absl::FailedPreconditionError(
393 "GEMINI_API_KEY environment variable not set");
394 }
395
396 // Create Gemini service
397 cli::GeminiConfig config;
398 config.api_key = api_key;
399 config.model = "gemini-2.5-flash"; // Use vision-capable model
400 config.verbose = false;
401
402 cli::GeminiAIService gemini_service(config);
403
404 // Generate multimodal response
405 auto response =
406 gemini_service.GenerateMultimodalResponse(image_path.string(), prompt);
407 if (!response.ok()) {
408 return response.status();
409 }
410
411 // Add the response to chat history
412 cli::agent::ChatMessage agent_msg;
414 agent_msg.message = response->text_response;
415 agent_msg.timestamp = absl::Now();
416 agent_editor_.GetChatWidget()->SetRomContext(GetCurrentRom());
417
418 return absl::OkStatus();
419 };
420 agent_editor_.GetChatWidget()->SetMultimodalCallbacks(multimodal_callbacks);
421
422 // Set up Z3ED command callbacks for proposal management
424
425 z3ed_callbacks.accept_proposal =
426 [this](const std::string& proposal_id) -> absl::Status {
427 // Use ProposalDrawer's existing logic
429 proposal_drawer_.FocusProposal(proposal_id);
430
432 absl::StrFormat("%s View proposal %s in drawer to accept",
433 ICON_MD_PREVIEW, proposal_id),
434 ToastType::kInfo, 3.5f);
435
436 return absl::OkStatus();
437 };
438
439 z3ed_callbacks.reject_proposal =
440 [this](const std::string& proposal_id) -> absl::Status {
441 // Use ProposalDrawer's existing logic
443 proposal_drawer_.FocusProposal(proposal_id);
444
446 absl::StrFormat("%s View proposal %s in drawer to reject",
447 ICON_MD_PREVIEW, proposal_id),
448 ToastType::kInfo, 3.0f);
449
450 return absl::OkStatus();
451 };
452
453 z3ed_callbacks.list_proposals =
454 []() -> absl::StatusOr<std::vector<std::string>> {
455 // Return empty for now - ProposalDrawer handles the real list
456 return std::vector<std::string>{};
457 };
458
459 z3ed_callbacks.diff_proposal =
460 [this](const std::string& proposal_id) -> absl::StatusOr<std::string> {
461 // Open drawer to show diff
463 proposal_drawer_.FocusProposal(proposal_id);
464 return "See diff in proposal drawer";
465 };
466
467 agent_editor_.GetChatWidget()->SetZ3EDCommandCallbacks(z3ed_callbacks);
468
469 AgentChatWidget::AutomationCallbacks automation_callbacks;
470 automation_callbacks.open_harness_dashboard = [this]() {
472 };
473 automation_callbacks.show_active_tests = [this]() {
475 };
476 automation_callbacks.replay_last_plan = [this]() {
478 };
479 automation_callbacks.focus_proposal = [this](const std::string& proposal_id) {
481 proposal_drawer_.FocusProposal(proposal_id);
482 };
483 agent_editor_.GetChatWidget()->SetAutomationCallbacks(automation_callbacks);
484
485 harness_telemetry_bridge_.SetChatWidget(agent_editor_.GetChatWidget());
486 test::TestManager::Get().SetHarnessListener(&harness_telemetry_bridge_);
487#endif
488
489 // Load critical user settings first
491 if (!status_.ok()) {
492 LOG_WARN("EditorManager", "Failed to load user settings: %s",
493 status_.ToString().c_str());
494 }
495
496 // Initialize welcome screen callbacks
498 status_ = LoadRom();
499 // LoadRom() already handles closing welcome screen and showing editor selection
500 });
501
504 if (status_.ok() && ui_coordinator_) {
505 ui_coordinator_->SetWelcomeScreenVisible(false);
506 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
507 }
508 });
509
510 welcome_screen_.SetOpenProjectCallback([this](const std::string& filepath) {
511 status_ = OpenRomOrProject(filepath);
512 if (status_.ok() && ui_coordinator_) {
513 ui_coordinator_->SetWelcomeScreenVisible(false);
514 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
515 }
516 });
517
518 // Initialize editor selection dialog callback
521 SwitchToEditor(type); // Use centralized method
522 });
523
524 // Load user settings - this must happen after context is initialized
525 // Apply font scale after loading
526 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
527
528 // Apply welcome screen preference
530 ui_coordinator_->SetWelcomeScreenVisible(false);
531 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
532 }
533
534 // Defer workspace presets loading to avoid initialization crashes
535 // This will be called lazily when workspace features are accessed
536
537 // Initialize testing system only when tests are enabled
538#ifdef YAZE_ENABLE_TESTING
540#endif
541
542 // TestManager will be updated when ROMs are loaded via SetCurrentRom calls
543
544 ShortcutDependencies shortcut_deps;
545 shortcut_deps.editor_manager = this;
546 shortcut_deps.editor_registry = &editor_registry_;
547 shortcut_deps.menu_orchestrator = menu_orchestrator_.get();
548 shortcut_deps.rom_file_manager = &rom_file_manager_;
549 shortcut_deps.project_manager = &project_manager_;
550 shortcut_deps.session_coordinator = session_coordinator_.get();
551 shortcut_deps.ui_coordinator = ui_coordinator_.get();
552 shortcut_deps.workspace_manager = &workspace_manager_;
553 shortcut_deps.popup_manager = popup_manager_.get();
554 shortcut_deps.toast_manager = &toast_manager_;
555
558}
559
560void EditorManager::OpenEditorAndCardsFromFlags(const std::string& editor_name,
561 const std::string& cards_str) {
562 if (editor_name.empty()) {
563 return;
564 }
565
566 LOG_INFO("EditorManager", "Processing startup flags: editor='%s', cards='%s'",
567 editor_name.c_str(), cards_str.c_str());
568
569 EditorType editor_type_to_open = EditorType::kUnknown;
570 for (int i = 0; i < static_cast<int>(EditorType::kSettings); ++i) {
571 if (GetEditorName(static_cast<EditorType>(i)) == editor_name) {
572 editor_type_to_open = static_cast<EditorType>(i);
573 break;
574 }
575 }
576
577 if (editor_type_to_open == EditorType::kUnknown) {
578 LOG_WARN("EditorManager", "Unknown editor specified via flag: %s",
579 editor_name.c_str());
580 return;
581 }
582
583 // Activate the main editor window
584 if (auto* editor_set = GetCurrentEditorSet()) {
585 auto* editor = editor_set
586 ->active_editors_[static_cast<int>(editor_type_to_open)];
587 if (editor) {
588 editor->set_active(true);
589 }
590 }
591
592 // Handle specific cards for the Dungeon Editor
593 if (editor_type_to_open == EditorType::kDungeon && !cards_str.empty()) {
594 if (auto* editor_set = GetCurrentEditorSet()) {
595 std::stringstream ss(cards_str);
596 std::string card_name;
597 while (std::getline(ss, card_name, ',')) {
598 // Trim whitespace
599 card_name.erase(0, card_name.find_first_not_of(" \t"));
600 card_name.erase(card_name.find_last_not_of(" \t") + 1);
601
602 LOG_DEBUG("EditorManager", "Attempting to open card: '%s'",
603 card_name.c_str());
604
605 if (card_name == "Rooms List") {
606 editor_set->dungeon_editor_.show_room_selector_ = true;
607 } else if (card_name == "Room Matrix") {
608 editor_set->dungeon_editor_.show_room_matrix_ = true;
609 } else if (card_name == "Entrances List") {
610 editor_set->dungeon_editor_.show_entrances_list_ = true;
611 } else if (card_name == "Room Graphics") {
612 editor_set->dungeon_editor_.show_room_graphics_ = true;
613 } else if (card_name == "Object Editor") {
614 editor_set->dungeon_editor_.show_object_editor_ = true;
615 } else if (card_name == "Palette Editor") {
616 editor_set->dungeon_editor_.show_palette_editor_ = true;
617 } else if (absl::StartsWith(card_name, "Room ")) {
618 try {
619 int room_id = std::stoi(card_name.substr(5));
620 editor_set->dungeon_editor_.add_room(room_id);
621 } catch (const std::exception& e) {
622 LOG_WARN("EditorManager", "Invalid room ID format: %s",
623 card_name.c_str());
624 }
625 } else {
626 LOG_WARN("EditorManager", "Unknown card name for Dungeon Editor: %s",
627 card_name.c_str());
628 }
629 }
630 }
631 }
632}
633
649absl::Status EditorManager::Update() {
650
651 // Update timing manager for accurate delta time across the application
652 // This fixes animation timing issues that occur when mouse isn't moving
654
655 // Delegate to PopupManager for modal dialog rendering
656 popup_manager_->DrawPopups();
657
658 // Execute keyboard shortcuts (registered via ShortcutConfigurator)
660
661 // Delegate to ToastManager for notification rendering
663
664 // Draw editor selection dialog (managed by UICoordinator)
665 if (ui_coordinator_ && ui_coordinator_->IsEditorSelectionVisible()) {
666 bool show = true;
668 if (!show) {
669 ui_coordinator_->SetEditorSelectionVisible(false);
670 }
671 }
672
673 // Draw card browser (managed by UICoordinator)
674 if (ui_coordinator_ && ui_coordinator_->IsCardBrowserVisible()) {
675 bool show = true;
677 if (!show) {
678 ui_coordinator_->SetCardBrowserVisible(false);
679 }
680 }
681
682#ifdef YAZE_WITH_GRPC
683 // Update agent editor dashboard
684 status_ = agent_editor_.Update();
685
686 // Draw chat widget separately (always visible when active)
687 if (agent_editor_.GetChatWidget()) {
688 agent_editor_.GetChatWidget()->Draw();
689 }
690#endif
691
692 // Draw background grid effects for the entire viewport
693 if (ImGui::GetCurrentContext()) {
694 ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
695 const ImGuiViewport* viewport = ImGui::GetMainViewport();
696
697 auto& theme_manager = gui::ThemeManager::Get();
698 auto current_theme = theme_manager.GetCurrentTheme();
699 auto& bg_renderer = gui::BackgroundRenderer::Get();
700
701 // Draw grid covering the entire main viewport
702 ImVec2 grid_pos = viewport->WorkPos;
703 ImVec2 grid_size = viewport->WorkSize;
704 bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size,
705 current_theme.primary);
706 }
707
708 // Ensure TestManager always has the current ROM
709 static Rom* last_test_rom = nullptr;
710 auto* current_rom = GetCurrentRom();
711 if (last_test_rom != current_rom) {
712 LOG_DEBUG(
713 "EditorManager",
714 "EditorManager::Update - ROM changed, updating TestManager: %p -> "
715 "%p",
716 (void*)last_test_rom, (void*)current_rom);
718 last_test_rom = current_rom;
719 }
720
721 // CRITICAL: Draw UICoordinator UI components FIRST (before ROM checks)
722 // This ensures Welcome Screen, Command Palette, etc. work even without ROM loaded
723 if (ui_coordinator_) {
724 ui_coordinator_->DrawAllUI();
725 }
726
727 // Autosave timer
728 if (user_settings_.prefs().autosave_enabled && current_rom &&
729 current_rom->dirty()) {
730 autosave_timer_ += ImGui::GetIO().DeltaTime;
732 autosave_timer_ = 0.0f;
734 s.backup = true;
735 s.save_new = false;
736 auto st = current_rom->SaveToFile(s);
737 if (st.ok()) {
738 toast_manager_.Show("Autosave completed", editor::ToastType::kSuccess);
739 } else {
740 toast_manager_.Show(std::string(st.message()),
742 }
743 }
744 } else {
745 autosave_timer_ = 0.0f;
746 }
747
748 // Check if ROM is loaded before allowing editor updates
749 auto* current_editor_set = GetCurrentEditorSet();
750 if (!current_editor_set) {
751 // No ROM loaded - welcome screen shown by UICoordinator above
752 return absl::OkStatus();
753 }
754
755 // Check if current ROM is valid
756 if (!current_rom) {
757 // No ROM loaded - welcome screen shown by UICoordinator above
758 return absl::OkStatus();
759 }
760
761 // ROM is loaded and valid - don't auto-show welcome screen
762 // Welcome screen should only be shown manually at this point
763
764 // Iterate through ALL sessions to support multi-session docking
765 for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
766 auto& session = sessions_[session_idx];
767 if (!session.rom.is_loaded())
768 continue; // Skip sessions with invalid ROMs
769
770 // Use RAII SessionScope for clean context switching
771 SessionScope scope(this, session_idx);
772
773 for (auto editor : session.editors.active_editors_) {
774 if (*editor->active()) {
775 if (editor->type() == EditorType::kOverworld) {
776 auto& overworld_editor = static_cast<OverworldEditor&>(*editor);
777 if (overworld_editor.jump_to_tab() != -1) {
778 session_coordinator_->SwitchToSession(session_idx);
779 // Set the dungeon editor to the jump to tab
780 session.editors.dungeon_editor_.add_room(
781 overworld_editor.jump_to_tab());
782 overworld_editor.jump_to_tab_ = -1;
783 }
784 }
785
786 // CARD-BASED EDITORS: Don't wrap in Begin/End, they manage own windows
787 bool is_card_based_editor = IsCardBasedEditor(editor->type());
788
789 if (is_card_based_editor) {
790 // Card-based editors create their own top-level windows
791 // No parent wrapper needed - this allows independent docking
792 current_editor_ = editor;
793
794 status_ = editor->Update();
795
796 // Route editor errors to toast manager
797 if (!status_.ok()) {
798 std::string editor_name = GetEditorName(editor->type());
800 absl::StrFormat("%s Error: %s", editor_name, status_.message()),
802 }
803
804 } else {
805 // TRADITIONAL EDITORS: Wrap in Begin/End
806 std::string window_title =
807 GenerateUniqueEditorTitle(editor->type(), session_idx);
808
809 // Set window to maximize on first open
810 ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize,
811 ImGuiCond_FirstUseEver);
812 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->WorkPos,
813 ImGuiCond_FirstUseEver);
814
815 if (ImGui::Begin(window_title.c_str(), editor->active(),
816 ImGuiWindowFlags_None)) { // Allow full docking
817 // Temporarily switch context for this editor's update
818 SessionScope scope(this, session_idx);
819 current_editor_ = editor;
820
821 status_ = editor->Update();
822
823 // Route editor errors to toast manager
824 if (!status_.ok()) {
825 std::string editor_name = GetEditorName(editor->type());
826 toast_manager_.Show(absl::StrFormat("%s Error: %s", editor_name,
827 status_.message()),
829 }
830
831 // Restore context
832 }
833 ImGui::End();
834 }
835 }
836 }
837 }
838
839 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
841 }
842
843 // Always draw proposal drawer (it manages its own visibility)
845
846#ifdef YAZE_WITH_GRPC
847 // Update ROM context for agent editor
848 if (current_rom && current_rom->is_loaded()) {
849 agent_editor_.SetRomContext(current_rom);
850 }
851#endif
852
853 // Draw unified sidebar LAST so it appears on top of all other windows
854 if (ui_coordinator_ && ui_coordinator_->IsCardSidebarVisible() &&
855 current_editor_set) {
856 // Using EditorCardRegistry directly
857
858 // Collect all active card-based editors
859 std::vector<std::string> active_categories;
860 for (size_t session_idx = 0; session_idx < sessions_.size();
861 ++session_idx) {
862 auto& session = sessions_[session_idx];
863 if (!session.rom.is_loaded())
864 continue;
865
866 for (auto editor : session.editors.active_editors_) {
867 if (*editor->active() && IsCardBasedEditor(editor->type())) {
868 std::string category = EditorRegistry::GetEditorCategory(editor->type());
869 if (std::find(active_categories.begin(), active_categories.end(),
870 category) == active_categories.end()) {
871 active_categories.push_back(category);
872 }
873 }
874 }
875 }
876
877 // Determine which category to show in sidebar
878 std::string sidebar_category;
879
880 // Priority 1: Use active_category from card manager (user's last interaction)
881 if (!card_registry_.GetActiveCategory().empty() &&
882 std::find(active_categories.begin(), active_categories.end(),
884 active_categories.end()) {
885 sidebar_category = card_registry_.GetActiveCategory();
886 }
887 // Priority 2: Use first active category
888 else if (!active_categories.empty()) {
889 sidebar_category = active_categories[0];
890 card_registry_.SetActiveCategory(sidebar_category);
891 }
892
893 // Draw sidebar if we have a category
894 if (!sidebar_category.empty()) {
895 // Callback to switch editors when category button is clicked
896 auto category_switch_callback = [this](const std::string& new_category) {
897 EditorType editor_type = EditorRegistry::GetEditorTypeFromCategory(new_category);
898 if (editor_type != EditorType::kUnknown) {
899 SwitchToEditor(editor_type);
900 }
901 };
902
903 auto collapse_callback = [this]() {
904 if (ui_coordinator_) {
905 ui_coordinator_->SetCardSidebarVisible(false);
906 }
907 };
908
909 card_registry_.DrawSidebar(sidebar_category, active_categories,
910 category_switch_callback, collapse_callback);
911 }
912 }
913
914 // Draw SessionCoordinator UI components
916 session_coordinator_->DrawSessionSwitcher();
917 session_coordinator_->DrawSessionManager();
918 session_coordinator_->DrawSessionRenameDialog();
919 }
920
921 return absl::OkStatus();
922}
923
925 // Delegate to UICoordinator for clean separation of concerns
926 if (ui_coordinator_) {
927 ui_coordinator_->DrawContextSensitiveCardControl();
928 }
929}
930
943 static bool show_display_settings = false;
944
945 if (ImGui::BeginMenuBar()) {
946 // Delegate menu building to MenuOrchestrator
947 if (menu_orchestrator_) {
948 menu_orchestrator_->BuildMainMenu();
949 }
950
951 // ROM selector now drawn by UICoordinator
952 if (ui_coordinator_) {
953 ui_coordinator_->DrawRomSelector();
954 }
955
956 // Delegate menu bar extras to UICoordinator (session indicator, version display)
957 if (ui_coordinator_) {
958 ui_coordinator_->DrawMenuBarExtras();
959 }
960
961 ImGui::EndMenuBar();
962 }
963
964 if (show_display_settings) {
965 // Use the popup manager instead of a separate window
967 show_display_settings = false; // Close the old-style window
968 }
969
970 // ImGui debug windows (delegated to UICoordinator for visibility state)
971 if (ui_coordinator_ && ui_coordinator_->IsImGuiDemoVisible()) {
972 bool visible = true;
973 ImGui::ShowDemoWindow(&visible);
974 if (!visible) {
975 ui_coordinator_->SetImGuiDemoVisible(false);
976 }
977 }
978
979 if (ui_coordinator_ && ui_coordinator_->IsImGuiMetricsVisible()) {
980 bool visible = true;
981 ImGui::ShowMetricsWindow(&visible);
982 if (!visible) {
983 ui_coordinator_->SetImGuiMetricsVisible(false);
984 }
985 }
986
987 // Using EditorCardRegistry directly
988 if (auto* editor_set = GetCurrentEditorSet()) {
989 // Pass the actual visibility flag pointer so the X button works
990 bool* hex_visibility =
991 card_registry_.GetVisibilityFlag("memory.hex_editor");
992 if (hex_visibility && *hex_visibility) {
993 editor_set->memory_editor_.Update(*hex_visibility);
994 }
995
996 if (ui_coordinator_ && ui_coordinator_->IsAsmEditorVisible()) {
997 bool visible = true;
998 editor_set->assembly_editor_.Update(visible);
999 if (!visible) {
1000 ui_coordinator_->SetAsmEditorVisible(false);
1001 }
1002 }
1003 }
1004
1005 // Project file editor
1007 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
1011 if (!gfx::PerformanceDashboard::Get().IsVisible()) {
1012 ui_coordinator_->SetPerformanceDashboardVisible(false);
1013 }
1014 }
1015
1016 // Testing interface (only when tests are enabled)
1017#ifdef YAZE_ENABLE_TESTING
1018 if (show_test_dashboard_) {
1019 auto& test_manager = test::TestManager::Get();
1020 test_manager.UpdateResourceStats(); // Update monitoring data
1021 test_manager.DrawTestDashboard(&show_test_dashboard_);
1022 }
1023#endif
1024
1025 // Agent proposal drawer (right side)
1028
1029 // Agent chat history popup (left side)
1031
1032 // Welcome screen is now drawn by UICoordinator::DrawAllUI()
1033 // Removed duplicate call to avoid showing welcome screen twice
1034
1035 // TODO: Fix emulator not appearing
1036 if (ui_coordinator_ && ui_coordinator_->IsEmulatorVisible()) {
1037 if (auto* current_rom = GetCurrentRom()) {
1038 emulator_.Run(current_rom);
1039 }
1040 }
1041
1042 // Enhanced Global Search UI (managed by UICoordinator)
1043 // No longer here - handled by ui_coordinator_->DrawAllUI()
1044
1045 if (ui_coordinator_ && ui_coordinator_->IsPaletteEditorVisible()) {
1046 bool visible = true;
1047 ImGui::Begin("Palette Editor", &visible);
1048 if (auto* editor_set = GetCurrentEditorSet()) {
1049 status_ = editor_set->palette_editor_.Update();
1050 }
1051
1052 // Route palette editor errors to toast manager
1053 if (!status_.ok()) {
1055 absl::StrFormat("Palette Editor Error: %s", status_.message()),
1057 }
1058
1059 ImGui::End();
1060 if (!visible) {
1061 ui_coordinator_->SetPaletteEditorVisible(false);
1062 }
1063 }
1064
1065 if (ui_coordinator_ && ui_coordinator_->IsResourceLabelManagerVisible() && GetCurrentRom()) {
1066 bool visible = true;
1067 GetCurrentRom()->resource_label()->DisplayLabels(&visible);
1071 GetCurrentRom()->resource_label()->filename_;
1072 }
1073 if (!visible) {
1074 ui_coordinator_->SetResourceLabelManagerVisible(false);
1075 }
1076 }
1077
1078 // Workspace preset dialogs are now in UICoordinator
1079
1080 // Layout presets UI (session dialogs are drawn by SessionCoordinator at lines 907-915)
1081 if (ui_coordinator_) {
1082 ui_coordinator_->DrawLayoutPresets();
1083 }
1084}
1085
1109 if (file_name.empty()) {
1110 return absl::OkStatus();
1111 }
1112
1113 if (session_coordinator_->HasDuplicateSession(file_name)) {
1114 toast_manager_.Show("ROM already open in another session",
1116 return absl::OkStatus();
1117 }
1118
1119 // Delegate ROM loading to RomFileManager
1120 Rom temp_rom;
1121 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name));
1122
1123 auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), file_name);
1124 if (!session_or.ok()) {
1125 return session_or.status();
1126 }
1127
1129
1130#ifdef YAZE_ENABLE_TESTING
1132#endif
1133
1135 manager.AddFile(file_name);
1136 manager.Save();
1137
1139
1140 if (ui_coordinator_) {
1141 ui_coordinator_->SetWelcomeScreenVisible(false);
1143 ui_coordinator_->SetEditorSelectionVisible(true);
1144 }
1145
1146 return absl::OkStatus();
1147}
1148
1150 auto* current_rom = GetCurrentRom();
1151 auto* current_editor_set = GetCurrentEditorSet();
1152 if (!current_rom || !current_editor_set) {
1153 return absl::FailedPreconditionError("No ROM or editor set loaded");
1154 }
1155
1156 auto start_time = std::chrono::steady_clock::now();
1157
1158 // Set renderer for emulator (lazy initialization happens in Run())
1159 if (renderer_) {
1161 }
1162
1163 // Initialize all editors - this registers their cards with EditorCardRegistry
1164 // and sets up any editor-specific resources. Must be called before Load().
1165 current_editor_set->overworld_editor_.Initialize();
1166 current_editor_set->message_editor_.Initialize();
1167 current_editor_set->graphics_editor_.Initialize();
1168 current_editor_set->screen_editor_.Initialize();
1169 current_editor_set->sprite_editor_.Initialize();
1170 current_editor_set->palette_editor_.Initialize();
1171 current_editor_set->assembly_editor_.Initialize();
1172 current_editor_set->music_editor_.Initialize();
1173 current_editor_set->settings_editor_.Initialize(); // Initialize settings editor to register System cards
1174 // Initialize the dungeon editor with the renderer
1175 current_editor_set->dungeon_editor_.Initialize(renderer_, current_rom);
1176 ASSIGN_OR_RETURN(*gfx::Arena::Get().mutable_gfx_sheets(),
1177 LoadAllGraphicsData(*current_rom));
1178 RETURN_IF_ERROR(current_editor_set->overworld_editor_.Load());
1179 RETURN_IF_ERROR(current_editor_set->dungeon_editor_.Load());
1180 RETURN_IF_ERROR(current_editor_set->screen_editor_.Load());
1181 RETURN_IF_ERROR(current_editor_set->settings_editor_.Load());
1182 RETURN_IF_ERROR(current_editor_set->sprite_editor_.Load());
1183 RETURN_IF_ERROR(current_editor_set->message_editor_.Load());
1184 RETURN_IF_ERROR(current_editor_set->music_editor_.Load());
1185 RETURN_IF_ERROR(current_editor_set->palette_editor_.Load());
1186
1188
1189 auto end_time = std::chrono::steady_clock::now();
1190 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
1191 end_time - start_time);
1192 LOG_DEBUG("EditorManager", "ROM assets loaded in %lld ms", duration.count());
1193
1194 return absl::OkStatus();
1195}
1196
1213 auto* current_rom = GetCurrentRom();
1214 auto* current_editor_set = GetCurrentEditorSet();
1215 if (!current_rom || !current_editor_set) {
1216 return absl::FailedPreconditionError("No ROM or editor set loaded");
1217 }
1218
1219 // Save editor-specific data first
1220 if (core::FeatureFlags::get().kSaveDungeonMaps) {
1222 *current_rom, current_editor_set->screen_editor_.dungeon_maps_));
1223 }
1224
1225 RETURN_IF_ERROR(current_editor_set->overworld_editor_.Save());
1226
1227 if (core::FeatureFlags::get().kSaveGraphicsSheet)
1229 SaveAllGraphicsData(*current_rom, gfx::Arena::Get().gfx_sheets()));
1230
1231 // Delegate final ROM file writing to RomFileManager
1232 return rom_file_manager_.SaveRom(current_rom);
1233}
1234
1235absl::Status EditorManager::SaveRomAs(const std::string& filename) {
1236 auto* current_rom = GetCurrentRom();
1237 auto* current_editor_set = GetCurrentEditorSet();
1238 if (!current_rom || !current_editor_set) {
1239 return absl::FailedPreconditionError("No ROM or editor set loaded");
1240 }
1241
1242 if (core::FeatureFlags::get().kSaveDungeonMaps) {
1244 *current_rom, current_editor_set->screen_editor_.dungeon_maps_));
1245 }
1246
1247 RETURN_IF_ERROR(current_editor_set->overworld_editor_.Save());
1248
1249 if (core::FeatureFlags::get().kSaveGraphicsSheet)
1251 SaveAllGraphicsData(*current_rom, gfx::Arena::Get().gfx_sheets()));
1252
1253 auto save_status = rom_file_manager_.SaveRomAs(current_rom, filename);
1254 if (save_status.ok()) {
1255 size_t current_session_idx = GetCurrentSessionIndex();
1256 if (current_session_idx < sessions_.size()) {
1257 sessions_[current_session_idx].filepath = filename;
1258 }
1259
1261 manager.AddFile(filename);
1262 manager.Save();
1263 }
1264
1265 return save_status;
1266}
1267
1268absl::Status EditorManager::OpenRomOrProject(const std::string& filename) {
1269 if (filename.empty()) {
1270 return absl::OkStatus();
1271 }
1272 if (absl::StrContains(filename, ".yaze")) {
1275 } else {
1276 Rom temp_rom;
1277 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, filename));
1278
1279 auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), filename);
1280 if (!session_or.ok()) {
1281 return session_or.status();
1282 }
1283 RomSession* session = *session_or;
1284
1286
1287 // Apply project feature flags to the session
1289
1290 // Update test manager with current ROM for ROM-dependent tests (only when tests are enabled)
1291#ifdef YAZE_ENABLE_TESTING
1292 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
1293 (void*)GetCurrentRom(),
1294 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
1296#endif
1297
1298 if (auto* editor_set = GetCurrentEditorSet(); editor_set && !current_project_.code_folder.empty()) {
1299 editor_set->assembly_editor_.OpenFolder(
1301 }
1302
1304
1305 // Hide welcome screen and show editor selection when ROM is loaded
1306 ui_coordinator_->SetWelcomeScreenVisible(false);
1308 ui_coordinator_->SetEditorSelectionVisible(true);
1309 }
1310 return absl::OkStatus();
1311}
1312
1313absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
1314 // Delegate to ProjectManager
1315 auto status = project_manager_.CreateNewProject(template_name);
1316 if (status.ok()) {
1318 // Show project creation dialog
1319 popup_manager_->Show("Create New Project");
1320 }
1321 return status;
1322}
1323
1326 if (file_path.empty()) {
1327 return absl::OkStatus();
1328 }
1329
1330 project::YazeProject new_project;
1331 RETURN_IF_ERROR(new_project.Open(file_path));
1332
1333 // Validate project
1334 auto validation_status = new_project.Validate();
1335 if (!validation_status.ok()) {
1336 toast_manager_.Show(absl::StrFormat("Project validation failed: %s",
1337 validation_status.message()),
1339
1340 // Ask user if they want to repair
1341 popup_manager_->Show("Project Repair");
1342 }
1343
1344 current_project_ = std::move(new_project);
1345
1346 // Load ROM if specified in project
1347 if (!current_project_.rom_filename.empty()) {
1348 Rom temp_rom;
1351
1352 auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), current_project_.rom_filename);
1353 if (!session_or.ok()) {
1354 return session_or.status();
1355 }
1356 RomSession* session = *session_or;
1357
1359
1360 // Apply project feature flags to the session
1362
1363 // Update test manager with current ROM for ROM-dependent tests (only when tests are enabled)
1364#ifdef YAZE_ENABLE_TESTING
1365 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
1366 (void*)GetCurrentRom(),
1367 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
1369#endif
1370
1371 if (auto* editor_set = GetCurrentEditorSet(); editor_set && !current_project_.code_folder.empty()) {
1372 editor_set->assembly_editor_.OpenFolder(
1374 }
1375
1377
1378 // Hide welcome screen and show editor selection when project ROM is loaded
1379 ui_coordinator_->SetWelcomeScreenVisible(false);
1381 ui_coordinator_->SetEditorSelectionVisible(true);
1382 }
1383
1384 // Apply workspace settings
1391 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
1392
1393 // Add to recent files
1395 manager.AddFile(current_project_.filepath);
1396 manager.Save();
1397
1398 toast_manager_.Show(absl::StrFormat("Project '%s' loaded successfully",
1401
1402 return absl::OkStatus();
1403}
1404
1407 return CreateNewProject();
1408 }
1409
1410 // Update project with current settings
1412 size_t session_idx = GetCurrentSessionIndex();
1413 if (session_idx < sessions_.size()) {
1414 current_project_.feature_flags = sessions_[session_idx].feature_flags;
1415 }
1416
1423
1424 // Save recent files
1427 for (const auto& file : manager.GetRecentFiles()) {
1429 }
1430 }
1431
1432 return current_project_.Save();
1433}
1434
1436 // Get current project name for default filename
1437 std::string default_name = current_project_.project_opened()
1439 : "untitled_project";
1440
1441 auto file_path =
1442 util::FileDialogWrapper::ShowSaveFileDialog(default_name, "yaze");
1443 if (file_path.empty()) {
1444 return absl::OkStatus();
1445 }
1446
1447 // Ensure .yaze extension
1448 if (file_path.find(".yaze") == std::string::npos) {
1449 file_path += ".yaze";
1450 }
1451
1452 // Update project filepath and save
1453 std::string old_filepath = current_project_.filepath;
1454 current_project_.filepath = file_path;
1455
1456 auto save_status = current_project_.Save();
1457 if (save_status.ok()) {
1458 // Add to recent files
1460 manager.AddFile(file_path);
1461 manager.Save();
1462
1463 toast_manager_.Show(absl::StrFormat("Project saved as: %s", file_path),
1465 } else {
1466 // Restore old filepath on failure
1467 current_project_.filepath = old_filepath;
1469 absl::StrFormat("Failed to save project: %s", save_status.message()),
1471 }
1472
1473 return save_status;
1474}
1475
1476absl::Status EditorManager::ImportProject(const std::string& project_path) {
1477 project::YazeProject imported_project;
1478
1479 if (project_path.ends_with(".zsproj")) {
1480 RETURN_IF_ERROR(imported_project.ImportZScreamProject(project_path));
1482 "ZScream project imported successfully. Please configure ROM and "
1483 "folders.",
1485 } else {
1486 RETURN_IF_ERROR(imported_project.Open(project_path));
1487 }
1488
1489 current_project_ = std::move(imported_project);
1490 return absl::OkStatus();
1491}
1492
1495 return absl::FailedPreconditionError("No project is currently open");
1496 }
1497
1499 toast_manager_.Show("Project repaired successfully",
1501
1502 return absl::OkStatus();
1503}
1504
1506 if (!rom) {
1507 return absl::InvalidArgumentError("Invalid ROM pointer");
1508 }
1509
1510 for (size_t i = 0; i < sessions_.size(); ++i) {
1511 if (&sessions_[i].rom == rom) {
1512 session_coordinator_->SwitchToSession(i);
1513
1514 // Update test manager with current ROM for ROM-dependent tests
1516
1517 return absl::OkStatus();
1518 }
1519 }
1520 // If ROM wasn't found in existing sessions, treat as new session.
1521 // Copying an external ROM object is avoided; instead, fail.
1522 return absl::NotFoundError("ROM not found in existing sessions");
1523}
1524
1527 session_coordinator_->CreateNewSession();
1528
1529 // Wire editor contexts for new session
1530 if (!sessions_.empty()) {
1531 RomSession& session = sessions_.back();
1533 ConfigureEditorDependencies(&session.editors, &session.rom,
1534 session.editors.session_id());
1535 session_coordinator_->SwitchToSession(sessions_.size() - 1);
1536 }
1537 }
1538
1539 // Don't switch to the new session automatically
1541 absl::StrFormat("New session created (Session %zu)", sessions_.size()),
1543
1544 // Show session manager if user has multiple sessions now
1545 if (sessions_.size() > 2) {
1547 "Tip: Use Workspace → Sessions → Session Switcher for quick navigation",
1549 }
1550}
1551
1553 if (!GetCurrentRom()) {
1554 toast_manager_.Show("No current ROM to duplicate",
1556 return;
1557 }
1558
1560 session_coordinator_->DuplicateCurrentSession();
1561
1562 // Wire editor contexts for duplicated session
1563 if (!sessions_.empty()) {
1564 RomSession& session = sessions_.back();
1565 ConfigureEditorDependencies(&session.editors, &session.rom,
1566 session.editors.session_id());
1567 session_coordinator_->SwitchToSession(sessions_.size() - 1);
1568 }
1569 }
1570}
1571
1574 session_coordinator_->CloseCurrentSession();
1575
1576 // Update current pointers after session change -- no longer needed
1577 }
1578}
1579
1582 session_coordinator_->RemoveSession(index);
1583
1584 // Update current pointers after session change -- no longer needed
1585 }
1586}
1587
1589 if (!session_coordinator_) {
1590 return;
1591 }
1592
1593 session_coordinator_->SwitchToSession(index);
1594
1595 if (index >= sessions_.size()) {
1596 return;
1597 }
1598
1599 // This logic is now handled by SessionCoordinator and GetCurrent... methods.
1600
1601#ifdef YAZE_ENABLE_TESTING
1603#endif
1604}
1605
1608 return session_coordinator_->GetActiveSessionIndex();
1609 }
1610
1611 // Fallback to finding by ROM pointer
1612 for (size_t i = 0; i < sessions_.size(); ++i) {
1613 if (&sessions_[i].rom == GetCurrentRom() &&
1614 sessions_[i].custom_name != "[CLOSED SESSION]") {
1615 return i;
1616 }
1617 }
1618 return 0; // Default to first session if not found
1619}
1620
1623 return session_coordinator_->GetActiveSessionCount();
1624 }
1625
1626 // Fallback to counting non-closed sessions
1627 size_t count = 0;
1628 for (const auto& session : sessions_) {
1629 if (session.custom_name != "[CLOSED SESSION]") {
1630 count++;
1631 }
1632 }
1633 return count;
1634}
1635
1637 EditorType type, size_t session_index) const {
1638 const char* base_name = kEditorNames[static_cast<int>(type)];
1639
1640 // Delegate to SessionCoordinator for multi-session title generation
1642 return session_coordinator_->GenerateUniqueEditorTitle(base_name,
1643 session_index);
1644 }
1645
1646 // Fallback for single session or no coordinator
1647 return std::string(base_name);
1648}
1649
1650// ============================================================================
1651// Jump-to Functionality for Cross-Editor Navigation
1652// ============================================================================
1653
1655 if (!GetCurrentEditorSet())
1656 return;
1657
1658 // Switch to dungeon editor
1660
1661 // Open the room in the dungeon editor
1662 GetCurrentEditorSet()->dungeon_editor_.add_room(room_id);
1663}
1664
1666 if (!GetCurrentEditorSet())
1667 return;
1668
1669 // Switch to overworld editor
1671
1672 // Set the current map in the overworld editor
1673 GetCurrentEditorSet()->overworld_editor_.set_current_map(map_id);
1674}
1675
1677 auto* editor_set = GetCurrentEditorSet();
1678 if (!editor_set)
1679 return;
1680
1681 // Toggle the editor
1682 for (auto* editor : editor_set->active_editors_) {
1683 if (editor->type() == editor_type) {
1684 editor->toggle_active();
1685
1686 if (IsCardBasedEditor(editor_type)) {
1687 // Using EditorCardRegistry directly
1688
1689 if (*editor->active()) {
1690 // Editor activated - set its category
1693
1694 // Initialize default layout on first activation
1695 if (layout_manager_ && !layout_manager_->IsLayoutInitialized(editor_type)) {
1696 ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
1697 layout_manager_->InitializeEditorLayout(editor_type, dockspace_id);
1698 }
1699 } else {
1700 // Editor deactivated - switch to another active card-based editor
1701 for (auto* other : editor_set->active_editors_) {
1702 if (*other->active() && IsCardBasedEditor(other->type()) &&
1703 other != editor) {
1705 EditorRegistry::GetEditorCategory(other->type()));
1706 break;
1707 }
1708 }
1709 }
1710 }
1711 return;
1712 }
1713 }
1714
1715 // Handle non-editor-class cases
1716 if (editor_type == EditorType::kAssembly) {
1717 if (ui_coordinator_) ui_coordinator_->SetAsmEditorVisible(!ui_coordinator_->IsAsmEditorVisible());
1718 } else if (editor_type == EditorType::kEmulator) {
1719 if (ui_coordinator_) {
1720 ui_coordinator_->SetEmulatorVisible(!ui_coordinator_->IsEmulatorVisible());
1721 if (ui_coordinator_->IsEmulatorVisible()) {
1723 }
1724 }
1725 }
1726}
1727
1728// SessionScope implementation
1730 size_t session_id)
1731 : manager_(manager),
1732 prev_rom_(manager->GetCurrentRom()),
1733 prev_editor_set_(manager->GetCurrentEditorSet()),
1734 prev_session_id_(manager->GetCurrentSessionId()) {
1735
1736 // Set new session context
1737 manager_->session_coordinator_->SwitchToSession(session_id);
1738}
1739
1741 // Restore previous context
1742 manager_->session_coordinator_->SwitchToSession(prev_session_id_);
1743}
1744
1745bool EditorManager::HasDuplicateSession(const std::string& filepath) {
1746 for (const auto& session : sessions_) {
1747 if (session.filepath == filepath) {
1748 return true;
1749 }
1750 }
1751 return false;
1752}
1753
1778 size_t session_id) {
1779 if (!editor_set) {
1780 return;
1781 }
1782
1783 EditorDependencies deps;
1784 deps.rom = rom;
1785 deps.session_id = session_id;
1788 deps.popup_manager = popup_manager_.get();
1792 deps.renderer = renderer_;
1793
1794 editor_set->ApplyDependencies(deps);
1795}
1796
1797} // namespace editor
1798} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:74
static TimingManager & Get()
Definition timing.h:19
float Update()
Update the timing manager (call once per frame)
Definition timing.h:28
absl::StatusOr< AgentResponse > GenerateMultimodalResponse(const std::string &image_path, const std::string &prompt)
static Flags & get()
Definition features.h:82
void SetToastManager(ToastManager *toast_manager)
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
Get visibility flag pointer for a card.
void DrawSidebar(size_t session_id, const std::string &category, const std::vector< std::string > &active_categories={}, std::function< void(const std::string &)> on_category_switch=nullptr, std::function< void()> on_collapse=nullptr)
Draw sidebar for a category with session filtering.
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
void SetActiveCategory(const std::string &category)
Set active category (for sidebar)
void DrawCardBrowser(size_t session_id, bool *p_open)
Draw visual card browser/toggler.
void HideAllCardsInCategory(size_t session_id, const std::string &category)
Hide all cards in a category for a session.
bool ShowCard(size_t session_id, const std::string &base_card_id)
Show a card programmatically.
std::string GetActiveCategory() const
Get active category (for sidebar)
SessionScope(EditorManager *manager, size_t session_id)
The EditorManager controls the main editor window and manages the various editor classes.
std::deque< RomSession > sessions_
static bool IsCardBasedEditor(EditorType type)
std::unique_ptr< SessionCoordinator > session_coordinator_
absl::Status SaveRomAs(const std::string &filename)
project::YazeProject current_project_
void JumpToDungeonRoom(int room_id)
void SwitchToSession(size_t index)
EditorCardRegistry card_registry_
bool HasDuplicateSession(const std::string &filepath)
void SwitchToEditor(EditorType editor_type)
EditorSelectionDialog editor_selection_dialog_
std::unique_ptr< LayoutManager > layout_manager_
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const
void DrawMenuBar()
Draw the main menu bar.
void Initialize(gfx::IRenderer *renderer, const std::string &filename="")
AgentChatHistoryPopup agent_chat_history_popup_
absl::Status CreateNewProject(const std::string &template_name="Basic ROM Hack")
auto GetCurrentEditorSet() const -> EditorSet *
std::unique_ptr< MenuOrchestrator > menu_orchestrator_
ProjectFileEditor project_file_editor_
void OpenEditorAndCardsFromFlags(const std::string &editor_name, const std::string &cards_str)
void JumpToOverworldMap(int map_id)
absl::Status LoadRom()
Load a ROM file into a new or existing session.
absl::Status Update()
Main update loop for the editor application.
absl::Status ImportProject(const std::string &project_path)
size_t GetCurrentSessionId() const
ShortcutManager shortcut_manager_
EditorDependencies::SharedClipboard shared_clipboard_
WorkspaceManager workspace_manager_
auto GetCurrentRom() const -> Rom *
void ConfigureEditorDependencies(EditorSet *editor_set, Rom *rom, size_t session_id)
Injects dependencies into all editors within an EditorSet.
absl::Status OpenRomOrProject(const std::string &filename)
void RemoveSession(size_t index)
std::unique_ptr< PopupManager > popup_manager_
std::unique_ptr< UICoordinator > ui_coordinator_
absl::Status SaveRom()
Save the current ROM file.
absl::Status SetCurrentRom(Rom *rom)
static EditorType GetEditorTypeFromCategory(const std::string &category)
static bool IsCardBasedEditor(EditorType type)
static std::string GetEditorCategory(EditorType type)
void ClearRecentEditors()
Clear recent editors (for new ROM sessions)
void SetSelectionCallback(std::function< void(EditorType)> callback)
Set callback for when editor is selected.
void MarkRecentlyUsed(EditorType type)
Mark an editor as recently used.
bool Show(bool *p_open=nullptr)
Show the dialog.
Contains a complete set of editors for a single ROM instance.
size_t session_id() const
void ApplyDependencies(const EditorDependencies &dependencies)
void set_user_settings(UserSettings *settings)
EditorType type() const
Definition editor.h:152
virtual absl::Status Update()=0
Manipulates the Overworld and OverworldMap data in a Rom.
void SetToastManager(ToastManager *toast_manager)
Set toast manager for notifications.
absl::Status CreateNewProject(const std::string &template_name="")
project::YazeProject & GetCurrentProject()
void FocusProposal(const std::string &proposal_id)
absl::Status LoadRom(Rom *rom, const std::string &filename)
absl::Status SaveRom(Rom *rom)
absl::Status SaveRomAs(Rom *rom, const std::string &filename)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
void SetNewProjectCallback(std::function< void()> callback)
Set callback for creating new project.
void SetOpenRomCallback(std::function< void()> callback)
Set callback for opening ROM.
void SetOpenProjectCallback(std::function< void(const std::string &)> callback)
Set callback for opening project.
void set_card_registry(EditorCardRegistry *registry)
void set_renderer(gfx::IRenderer *renderer)
Definition emulator.h:60
void set_card_registry(editor::EditorCardRegistry *registry)
Definition emulator.h:45
void Run(Rom *rom)
Definition emulator.cc:118
static Arena & Get()
Definition arena.cc:15
Defines an abstract interface for all rendering operations.
Definition irenderer.h:35
static PerformanceDashboard & Get()
void SetVisible(bool visible)
Show/hide the dashboard.
void Update()
Update dashboard with current performance data.
void Render()
Render the performance dashboard UI.
static PerformanceProfiler & Get()
void PrintSummary() const
Print a summary of all operations to console.
static BackgroundRenderer & Get()
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:244
absl::Status ShowHarnessActiveTests()
absl::Status ShowHarnessDashboard()
void SetCurrentRom(Rom *rom)
static TestManager & Get()
absl::Status ReplayLastPlan()
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_SETTINGS
Definition icons.h:1697
#define ICON_MD_MEMORY
Definition icons.h:1193
#define ICON_MD_CODE
Definition icons.h:432
#define ICON_MD_STOP
Definition icons.h:1860
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2074
#define ICON_MD_MESSAGE
Definition icons.h:1199
#define ICON_MD_BUG_REPORT
Definition icons.h:325
#define ICON_MD_SPEED
Definition icons.h:1815
#define ICON_MD_AUDIOTRACK
Definition icons.h:211
#define ICON_MD_CASTLE
Definition icons.h:378
#define ICON_MD_SCREENSHOT
Definition icons.h:1664
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1262
#define ICON_MD_LAYERS
Definition icons.h:1066
#define ICON_MD_KEYBOARD
Definition icons.h:1026
#define ICON_MD_PREVIEW
Definition icons.h:1510
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_AUDIO_FILE
Definition icons.h:210
#define ICON_MD_PHOTO
Definition icons.h:1449
#define ICON_MD_SMART_TOY
Definition icons.h:1779
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_WARN(category, format,...)
Definition log.h:108
#define LOG_INFO(category, format,...)
Definition log.h:106
#define PRINT_IF_ERROR(expression)
Definition macro.h:27
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
constexpr const char * kDisplaySettings
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:110
constexpr const char * kDungeonEditorName
constexpr const char * kMessageEditorName
constexpr const char * kGraphicsEditorName
constexpr const char * kScreenEditorName
void ConfigureMenuShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
constexpr const char * kSettingsEditorName
constexpr const char * kMusicEditorName
constexpr const char * kOverworldEditorName
constexpr const char * kAssemblyEditorName
constexpr const char * kSpriteEditorName
void ConfigureEditorShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
constexpr const char * kPaletteEditorName
void RegisterZ3edTestSuites()
absl::Status SaveDungeonMaps(Rom &rom, std::vector< DungeonMap > &dungeon_maps)
Save the dungeon maps to the ROM.
Main namespace for the application.
Definition controller.cc:20
absl::StatusOr< std::array< gfx::Bitmap, kNumGfxSheets > > LoadAllGraphicsData(Rom &rom, bool defer_render)
This function iterates over all graphics sheets in the Rom and loads them into memory....
Definition rom.cc:175
absl::Status SaveAllGraphicsData(Rom &rom, std::array< gfx::Bitmap, kNumGfxSheets > &gfx_sheets)
Definition rom.cc:256
std::function< void(const std::string &)> focus_proposal
std::function< absl::Status(const std::filesystem::path &, const std::string &)> send_to_gemini
std::function< absl::Status(std::filesystem::path *)> capture_snapshot
std::function< absl::StatusOr< std::string >(const std::string &)> diff_proposal
std::function< absl::StatusOr< std::vector< std::string > >()> list_proposals
std::function< absl::Status(const std::string &)> accept_proposal
std::function< absl::Status(const std::string &)> reject_proposal
Unified dependency container for all editor types.
Definition editor.h:64
ToastManager * toast_manager
Definition editor.h:81
SharedClipboard * shared_clipboard
Definition editor.h:84
gfx::IRenderer * renderer
Definition editor.h:88
ShortcutManager * shortcut_manager
Definition editor.h:83
UserSettings * user_settings
Definition editor.h:85
PopupManager * popup_manager
Definition editor.h:82
EditorCardRegistry * card_registry
Definition editor.h:80
Represents a single session, containing a ROM and its associated editors.
core::FeatureFlags::Flags feature_flags
std::vector< std::string > recent_files
Definition project.h:70
Modern project structure with comprehensive settings consolidation.
Definition project.h:78
absl::Status RepairProject()
Definition project.cc:504
bool project_opened() const
Definition project.h:162
absl::Status ImportZScreamProject(const std::string &zscream_project_path)
Definition project.cc:402
std::string rom_filename
Definition project.h:86
std::string labels_filename
Definition project.h:94
std::string GetDisplayName() const
Definition project.cc:532
WorkspaceSettings workspace_settings
Definition project.h:99
absl::Status Open(const std::string &project_path)
Definition project.cc:115
std::string code_folder
Definition project.h:91
absl::Status Validate() const
Definition project.cc:461
core::FeatureFlags::Flags feature_flags
Definition project.h:98