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// Related header
2#include "editor_manager.h"
3
4// C system headers
5#include <cctype>
6#include <cstdint>
7#include <cstdio>
8#include <cstring>
9#include <ctime>
10
11// C++ standard library headers
12#include <algorithm>
13#include <chrono>
14#include <cstdlib>
15#include <exception>
16#include <filesystem>
17#include <fstream>
18#include <functional>
19#include <initializer_list>
20#include <memory>
21#include <optional>
22#include <sstream>
23#include <string>
24#include <unordered_set>
25#include <utility>
26#include <vector>
27
28#ifdef __APPLE__
29#include <TargetConditionals.h>
30#endif
31
32// Third-party library headers
33#define IMGUI_DEFINE_MATH_OPERATORS
34#include "absl/status/status.h"
35#include "absl/status/statusor.h"
36#include "absl/strings/ascii.h"
37#include "absl/strings/match.h"
38#include "absl/strings/str_format.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/str_split.h"
41#include "absl/strings/string_view.h"
42#include "imgui/imgui.h"
43
44// Project headers
45#include "app/application.h"
47#include "app/editor/editor.h"
66#include "app/emu/emulator.h"
71#include "app/gui/core/icons.h"
75#include "app/platform/timing.h"
77#include "core/features.h"
78#include "core/project.h"
79#include "core/rom_settings.h"
88#include "rom/rom.h"
89#include "rom/rom_diff.h"
90#include "startup_flags.h"
91#include "util/file_util.h"
92#include "util/log.h"
93#include "util/macro.h"
94#include "util/rom_hash.h"
95#include "yaze_config.h"
101#include "zelda3/game_data.h"
107#include "zelda3/sprite/sprite.h"
108
109// Conditional platform headers
110#ifdef __EMSCRIPTEN__
114#endif
115
116// Conditional test headers
117#ifdef YAZE_ENABLE_TESTING
123#endif
124#ifdef YAZE_ENABLE_GTEST
126#endif
127#ifdef YAZE_WITH_GRPC
129#endif
130
131// Conditional agent UI headers
132#ifdef YAZE_BUILD_AGENT_UI
134#endif
135
136namespace yaze::editor {
137
138namespace {
140 std::initializer_list<const char*> keys) {
141 for (const char* key : keys) {
142 if (overrides.addresses.find(key) != overrides.addresses.end()) {
143 return true;
144 }
145 }
146 return false;
147}
148
149std::string LastNonEmptyLine(const std::string& text) {
150 std::vector<std::string> lines = absl::StrSplit(text, '\n');
151 for (auto it = lines.rbegin(); it != lines.rend(); ++it) {
152 std::string line = std::string(*it);
153 absl::StripAsciiWhitespace(&line);
154 if (!line.empty()) {
155 return line;
156 }
157 }
158 return "";
159}
160
162 ProjectManagementPanel* project_panel,
163 const ProjectWorkflowStatus& status) {
165 if (status_bar != nullptr) {
166 status_bar->SetBuildStatus(status);
167 }
168 if (project_panel != nullptr) {
169 project_panel->SetBuildStatus(status);
170 }
171}
172
174 ProjectManagementPanel* project_panel,
175 const ProjectWorkflowStatus& status) {
177 if (status_bar != nullptr) {
178 status_bar->SetRunStatus(status);
179 }
180 if (project_panel != nullptr) {
181 project_panel->SetRunStatus(status);
182 }
183}
184
185void AppendWorkflowHistoryEntry(const std::string& kind,
186 const ProjectWorkflowStatus& status,
187 const std::string& output_log) {
189 {.kind = kind,
190 .status = status,
191 .output_log = output_log,
192 .timestamp = std::chrono::system_clock::now()});
193}
194
196 return !project.custom_objects_folder.empty() ||
197 !project.custom_object_files.empty();
198}
199
200constexpr int kTrackCustomObjectId = 0x31;
201
203 std::string* warning) {
204 if (project == nullptr) {
205 return false;
206 }
207 if (project->custom_objects_folder.empty()) {
208 return false;
209 }
210 if (project->custom_object_files.find(kTrackCustomObjectId) !=
211 project->custom_object_files.end()) {
212 return false;
213 }
214
215 const auto& defaults =
218 if (defaults.empty()) {
219 return false;
220 }
221
222 project->custom_object_files[kTrackCustomObjectId] = defaults;
223 if (warning != nullptr) {
224 *warning = absl::StrFormat(
225 "Project defines custom_objects_folder but is missing "
226 "custom_object_files[0x%02X]. Seeded default mapping (%d entries).",
227 kTrackCustomObjectId, static_cast<int>(defaults.size()));
228 }
229 return true;
230}
231
232std::vector<std::string> ValidateRomAddressOverrides(
233 const core::RomAddressOverrides& overrides, const Rom& rom) {
234 std::vector<std::string> warnings;
235 if (overrides.addresses.empty()) {
236 return warnings;
237 }
238
239 const auto rom_size = rom.size();
240 auto warn = [&](const std::string& message) {
241 warnings.push_back(message);
242 };
243
244 auto check_range = [&](const std::string& label, uint32_t addr, size_t span) {
245 const size_t addr_size = static_cast<size_t>(addr);
246 if (addr_size >= rom_size || addr_size + span > rom_size) {
247 warn(absl::StrFormat("ROM override '%s' out of range: 0x%X (size 0x%X)",
248 label, addr, rom_size));
249 }
250 };
251
252 for (const auto& [key, value] : overrides.addresses) {
253 check_range(key, value, 1);
254 }
255
258 // Defaults match the Oracle expanded message bank ($2F8000-$2FFFFF).
259 // Kept local to avoid pulling message editor constants into core validation.
260 constexpr uint32_t kExpandedMessageStartDefault = 0x178000;
261 constexpr uint32_t kExpandedMessageEndDefault = 0x17FFFF;
262 const uint32_t start =
264 .value_or(kExpandedMessageStartDefault);
265 const uint32_t end =
267 .value_or(kExpandedMessageEndDefault);
268 if (start >= rom_size || end >= rom_size) {
269 warn(absl::StrFormat(
270 "Expanded message range out of ROM bounds: 0x%X-0x%X", start, end));
271 } else if (end < start) {
272 warn(absl::StrFormat("Expanded message range invalid: 0x%X-0x%X", start,
273 end));
274 }
275 }
276
277 if (auto hook =
279 check_range(core::RomAddressKey::kExpandedMusicHook, *hook, 4);
280 if (*hook < rom_size) {
281 auto opcode = rom.ReadByte(*hook);
282 if (opcode.ok() && opcode.value() != 0x22) {
283 warn(absl::StrFormat(
284 "Expanded music hook at 0x%X is not a JSL opcode (0x%02X)", *hook,
285 opcode.value()));
286 }
287 }
288 }
289
290 if (auto main =
293 }
294
295 if (auto aux = overrides.GetAddress(core::RomAddressKey::kExpandedMusicAux)) {
296 check_range(core::RomAddressKey::kExpandedMusicAux, *aux, 4);
297 }
298
299 if (HasAnyOverride(overrides,
304 const uint32_t marker =
307 const uint32_t magic =
310 check_range("overworld_ptr_marker", marker, 1);
311 if (marker < rom_size) {
312 if (rom.data()[marker] != static_cast<uint8_t>(magic)) {
313 warn(absl::StrFormat(
314 "Overworld expanded pointer marker mismatch at 0x%X: expected "
315 "0x%02X, found 0x%02X",
316 marker, static_cast<uint8_t>(magic), rom.data()[marker]));
317 }
318 }
319 }
320
321 if (HasAnyOverride(overrides,
326 const uint32_t flag_addr =
327 overrides
330 check_range("overworld_entrance_flag_expanded", flag_addr, 1);
331 if (flag_addr < rom_size && rom.data()[flag_addr] == 0xB8) {
332 warn(
333 absl::StrFormat("Overworld entrance flag at 0x%X is still 0xB8 "
334 "(vanilla); expanded entrance tables may be ignored",
335 flag_addr));
336 }
337 }
338
339 return warnings;
340}
341
342std::optional<EditorType> ParseEditorTypeFromString(absl::string_view name) {
343 const std::string lower = absl::AsciiStrToLower(std::string(name));
344 for (int i = 0; i < static_cast<int>(EditorType::kSettings) + 1; ++i) {
345 const std::string candidate = absl::AsciiStrToLower(
347 if (candidate == lower) {
348 return static_cast<EditorType>(i);
349 }
350 }
351 return std::nullopt;
352}
353
354std::string StripSessionPrefix(absl::string_view panel_id) {
355 if (panel_id.size() > 2 && panel_id[0] == 's' &&
356 absl::ascii_isdigit(panel_id[1])) {
357 const size_t dot = panel_id.find('.');
358 if (dot != absl::string_view::npos) {
359 return std::string(panel_id.substr(dot + 1));
360 }
361 }
362 return std::string(panel_id);
363}
364
366 namespace fs = std::filesystem;
367
368 std::vector<fs::path> roots;
369 if (const char* home = std::getenv("HOME");
370 home != nullptr && std::strlen(home) > 0) {
371 roots.push_back(fs::path(home) / "src" / "hobby" / "oracle-of-secrets");
372 }
373
374 std::vector<fs::path> candidates;
375 std::unordered_set<std::string> seen;
376 auto add_candidate = [&](const fs::path& path) {
377 std::error_code ec;
378 if (!fs::exists(path, ec) || ec) {
379 return;
380 }
381 std::string ext = path.extension().string();
382 absl::AsciiStrToLower(&ext);
383 if (ext != ".yaze" && ext != ".yazeproj") {
384 return;
385 }
386 const std::string normalized = fs::weakly_canonical(path, ec).string();
387 const std::string key = normalized.empty() ? path.string() : normalized;
388 if (key.empty() || seen.count(key) > 0) {
389 return;
390 }
391 seen.insert(key);
392 candidates.push_back(path);
393 };
394
395 for (const auto& root : roots) {
396 std::error_code ec;
397 if (!fs::exists(root, ec) || ec) {
398 continue;
399 }
400
401 // Priority candidates first.
402 add_candidate(root / "Oracle-of-Secrets.yaze");
403 add_candidate(root / "Oracle of Secrets.yaze");
404 add_candidate(root / "Oracle-of-Secrets.yazeproj");
405 add_candidate(root / "Oracle of Secrets.yazeproj");
406 add_candidate(root / "Roms" / "Oracle of Secrets.yaze");
407
408 // Also detect additional local project files nearby.
409 fs::recursive_directory_iterator it(
410 root, fs::directory_options::skip_permission_denied, ec);
411 fs::recursive_directory_iterator end;
412 if (ec) {
413 continue;
414 }
415 for (; it != end; it.increment(ec)) {
416 if (ec) {
417 ec.clear();
418 continue;
419 }
420 if (it.depth() > 2) {
421 it.disable_recursion_pending();
422 continue;
423 }
424 add_candidate(it->path());
425 }
426 }
427
428 if (candidates.empty()) {
429 return;
430 }
431
433 // Add in reverse so the first candidate remains most recent.
434 for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
435 manager.AddFile(it->string());
436 }
437 manager.Save();
438}
439
440} // namespace
441
442// Static registry of editors that use the card-based layout system
443// These editors register their cards with WorkspaceWindowManager and manage their
444// own windows They do NOT need the traditional ImGui::Begin/End wrapper - they
445// create cards internally
449
453
454void EditorManager::ApplyLayoutPreset(const std::string& preset_name) {
456}
457
458bool EditorManager::ApplyLayoutProfile(const std::string& profile_id) {
459 if (!layout_manager_) {
460 return false;
461 }
462
463 const size_t session_id = GetCurrentSessionId();
464 const EditorType current_type =
466
467 LayoutProfile applied_profile;
468 if (!layout_manager_->ApplyBuiltInProfile(profile_id, session_id,
469 current_type, &applied_profile)) {
470 return false;
471 }
472
473 if (applied_profile.open_agent_chat && right_drawer_manager_) {
474 right_drawer_manager_->OpenDrawer(
476 const float default_width = RightDrawerManager::GetDefaultDrawerWidth(
478 right_drawer_manager_->SetDrawerWidth(
480 std::max(default_width, 480.0f));
481 }
482
484 absl::StrFormat("Layout Profile: %s", applied_profile.label),
486 return true;
487}
488
490 if (!layout_manager_) {
491 return;
492 }
493 layout_manager_->CaptureTemporarySessionLayout(GetCurrentSessionId());
494 toast_manager_.Show("Captured temporary layout snapshot", ToastType::kInfo);
495}
496
497void EditorManager::RestoreTemporaryLayoutSnapshot(bool clear_after_restore) {
498 if (!layout_manager_) {
499 return;
500 }
501
502 if (layout_manager_->RestoreTemporarySessionLayout(GetCurrentSessionId(),
503 clear_after_restore)) {
504 toast_manager_.Show("Restored temporary layout snapshot",
506 } else {
507 toast_manager_.Show("No temporary layout snapshot available",
509 }
510}
511
513 if (!layout_manager_) {
514 return;
515 }
516 layout_manager_->ClearTemporarySessionLayout();
517 toast_manager_.Show("Cleared temporary layout snapshot", ToastType::kInfo);
518}
519
520bool EditorManager::SaveLayoutSnapshotAs(const std::string& name) {
521 if (!layout_manager_)
522 return false;
523 if (name.empty()) {
524 toast_manager_.Show("Snapshot name cannot be empty", ToastType::kWarning);
525 return false;
526 }
527 const bool ok =
528 layout_manager_->SaveNamedSnapshot(name, GetCurrentSessionId());
529 if (ok) {
530 toast_manager_.Show(absl::StrFormat("Saved snapshot '%s'", name),
532 } else {
533 toast_manager_.Show(absl::StrFormat("Failed to save snapshot '%s'", name),
535 }
536 return ok;
537}
538
539bool EditorManager::RestoreLayoutSnapshot(const std::string& name,
540 bool remove_after_restore) {
541 if (!layout_manager_)
542 return false;
543 const bool ok = layout_manager_->RestoreNamedSnapshot(
544 name, GetCurrentSessionId(), remove_after_restore);
545 if (ok) {
546 toast_manager_.Show(absl::StrFormat("Restored snapshot '%s'", name),
548 } else {
549 toast_manager_.Show(absl::StrFormat("Snapshot '%s' not available", name),
551 }
552 return ok;
553}
554
555bool EditorManager::DeleteLayoutSnapshot(const std::string& name) {
556 if (!layout_manager_)
557 return false;
558 const bool ok = layout_manager_->DeleteNamedSnapshot(name);
559 if (ok) {
560 toast_manager_.Show(absl::StrFormat("Deleted snapshot '%s'", name),
562 }
563 return ok;
564}
565
566std::vector<std::string> EditorManager::ListLayoutSnapshots() const {
567 if (!layout_manager_)
568 return {};
569 return layout_manager_->ListNamedSnapshots(GetCurrentSessionId());
570}
571
573 if (!layout_manager_) {
574 return;
575 }
576
578 layout_manager_->SetProjectLayoutKey(
580 } else {
581 layout_manager_->UseGlobalLayouts();
582 }
583}
584
593
594#ifdef YAZE_BUILD_AGENT_UI
595void EditorManager::ShowAIAgent() {
596 // Apply saved agent settings from the current project when opening the Agent
597 // UI to respect the user's preferred provider/model.
598 // TODO: Implement LoadAgentSettingsFromProject in AgentChat or AgentEditor
602 for (const auto& window_id :
603 LayoutPresets::GetDefaultWindows(EditorType::kAgent)) {
604 window_manager_.OpenWindow(window_id);
605 }
606}
607
608void EditorManager::ShowChatHistory() {
610}
611#endif
612
614 : project_manager_(&toast_manager_), rom_file_manager_(&toast_manager_) {
615 std::stringstream ss;
616 ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
617 << YAZE_VERSION_PATCH;
618 ss >> version_;
619
620 // Initialize Core Context
621 editor_context_ = std::make_unique<GlobalEditorContext>(event_bus_);
624
628
629 // STEP 5: ShortcutConfigurator created later in Initialize() method
630 // It depends on all above coordinators being available
631}
632
635
636 // STEP 1: Initialize PopupManager FIRST
637 popup_manager_ = std::make_unique<PopupManager>(this);
638 popup_manager_->Initialize(); // Registers all popups with PopupID constants
639
640 // STEP 2: Initialize SessionCoordinator (independent of popups)
641 session_coordinator_ = std::make_unique<SessionCoordinator>(
643 session_coordinator_->SetEditorRegistry(&editor_registry_);
644
645 // STEP 3: Initialize MenuOrchestrator (depends on popup_manager_,
646 // session_coordinator_)
647 menu_orchestrator_ = std::make_unique<MenuOrchestrator>(
650
651 // Wire up the window manager for the View menu window listing
652 menu_orchestrator_->SetWindowManager(&window_manager_);
653 menu_orchestrator_->SetStatusBar(&status_bar_);
654 menu_orchestrator_->SetUserSettings(&user_settings_);
655
656 session_coordinator_->SetEditorManager(this);
657 session_coordinator_->SetEventBus(&event_bus_); // Enable event publishing
659 &event_bus_); // Global event bus access
660
661 // STEP 3.5: Initialize RomLifecycleManager (depends on popup_manager_,
662 // session_coordinator_, rom_file_manager_, toast_manager_)
664 .rom_file_manager = &rom_file_manager_,
665 .session_coordinator = session_coordinator_.get(),
667 .popup_manager = popup_manager_.get(),
668 .project = &current_project_,
669 });
670
671 // STEP 4: Initialize UICoordinator (depends on popup_manager_,
672 // session_coordinator_, window_manager_)
673 ui_coordinator_ = std::make_unique<UICoordinator>(
677
678 // STEP 4.5: Initialize LayoutManager (DockBuilder layouts for editors)
679 layout_manager_ = std::make_unique<LayoutManager>();
680 layout_manager_->SetWindowManager(&window_manager_);
681 layout_manager_->UseGlobalLayouts();
684 [this](const std::string& preset_name) {
685 ApplyLayoutPreset(preset_name);
686 });
688 [this](const std::string& preset_name) {
689 ApplyLayoutPreset(preset_name);
690 });
691
692 // STEP 4.6: Initialize RightDrawerManager (right-side sliding drawers)
693 right_drawer_manager_ = std::make_unique<RightDrawerManager>();
694 right_drawer_manager_->SetToastManager(&toast_manager_);
695 right_drawer_manager_->SetProposalDrawer(&proposal_drawer_);
697 right_drawer_manager_->SetShortcutManager(&shortcut_manager_);
699 [this](const std::string& prompt) {
700#if defined(YAZE_BUILD_AGENT_UI)
701 auto* agent_editor = agent_ui_.GetAgentEditor();
702 if (!agent_editor) {
703 return;
704 }
705 auto* chat = agent_editor->GetAgentChat();
706 if (!chat) {
707 return;
708 }
709 chat->set_active(true);
710 chat->SendMessage(prompt);
711#else
712 (void)prompt;
713#endif
714 },
715 [this]() {
716#if defined(YAZE_BUILD_AGENT_UI)
718 right_drawer_manager_->OpenDrawer(
720 }
721#endif
722 });
725 right_drawer_manager_->ToggleDrawer(
727 } else {
729 }
730 });
731
732 // Initialize ProjectManagementPanel for project/version management
733 project_management_panel_ = std::make_unique<ProjectManagementPanel>();
734 project_management_panel_->SetToastManager(&toast_manager_);
736 std::make_unique<workflow::ProjectWorkflowOutputPanel>());
737 project_management_panel_->SetSwapRomCallback([this]() {
738 // Prompt user to select a new ROM for the project
741 if (!rom_path.empty()) {
743 auto status = current_project_.Save();
744 if (status.ok()) {
745 toast_manager_.Show("Project ROM updated. Reload to apply changes.",
747 } else {
748 toast_manager_.Show("Failed to update project ROM", ToastType::kError);
749 }
750 }
751 });
752 project_management_panel_->SetReloadRomCallback([this]() {
755 auto status = LoadProjectWithRom();
756 if (!status.ok()) {
758 absl::StrFormat("Failed to reload ROM: %s", status.message()),
760 }
761 }
762 });
763 project_management_panel_->SetSaveProjectCallback([this]() {
764 auto status = SaveProject();
765 if (status.ok()) {
766 toast_manager_.Show("Project saved", ToastType::kSuccess);
767 } else {
769 absl::StrFormat("Failed to save project: %s", status.message()),
771 }
772 });
773 project_management_panel_->SetBrowseFolderCallback(
774 [this](const std::string& type) {
776 if (!folder_path.empty()) {
777 if (type == "code") {
778 current_project_.code_folder = folder_path;
779 // Update assembly editor path
780 if (auto* editor_set = GetCurrentEditorSet()) {
781// iOS: avoid blocking folder enumeration on the UI thread.
782#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
783 editor_set->OpenAssemblyFolder(folder_path);
784#endif
785 window_manager_.SetFileBrowserPath("Assembly", folder_path);
786 }
787 } else if (type == "assets") {
788 current_project_.assets_folder = folder_path;
789 }
790 toast_manager_.Show(absl::StrFormat("%s folder set: %s", type.c_str(),
791 folder_path.c_str()),
793 }
794 });
795 project_management_panel_->SetBuildProjectCallback(
796 [this]() { QueueBuildCurrentProject(); });
797 project_management_panel_->SetCancelBuildCallback(
798 [this]() { CancelQueuedProjectBuild(); });
799 project_management_panel_->SetRunProjectCallback(
800 [this]() { (void)RunCurrentProject(); });
802 [this]() { QueueBuildCurrentProject(); });
804 [this]() { (void)RunCurrentProject(); });
806 window_manager_.OpenWindow("workflow.output");
807 window_manager_.MarkWindowRecentlyUsed("workflow.output");
808 });
810 [this]() { CancelQueuedProjectBuild(); });
811 right_drawer_manager_->SetProjectManagementPanel(
813
814 // STEP 4.6.1: Initialize LayoutCoordinator (facade for layout operations)
816 layout_deps.layout_manager = layout_manager_.get();
817 layout_deps.window_manager = &window_manager_;
818 layout_deps.ui_coordinator = ui_coordinator_.get();
819 layout_deps.toast_manager = &toast_manager_;
820 layout_deps.status_bar = &status_bar_;
822 layout_coordinator_.Initialize(layout_deps);
823
824 // STEP 4.6.2: Initialize EditorActivator (editor switching and jump navigation)
825 EditorActivator::Dependencies activator_deps;
826 activator_deps.window_manager = &window_manager_;
827 activator_deps.layout_manager = layout_manager_.get();
828 activator_deps.ui_coordinator = ui_coordinator_.get();
829 activator_deps.right_drawer_manager = right_drawer_manager_.get();
830 activator_deps.toast_manager = &toast_manager_;
831 activator_deps.event_bus = &event_bus_;
832 activator_deps.ensure_editor_assets_loaded = [this](EditorType type) {
833 return EnsureEditorAssetsLoaded(type);
834 };
835 activator_deps.get_current_editor_set = [this]() {
836 return GetCurrentEditorSet();
837 };
838 activator_deps.get_current_session_id = [this]() {
839 return GetCurrentSessionId();
840 };
841 activator_deps.queue_deferred_action = [this](std::function<void()> action) {
842 QueueDeferredAction(std::move(action));
843 };
844 editor_activator_.Initialize(activator_deps);
845
846 // STEP 4.7: Initialize ActivityBar
847 activity_bar_ = std::make_unique<ActivityBar>(
849 [this]() -> bool {
850 if (auto* editor_set = GetCurrentEditorSet()) {
851 if (auto* dungeon_editor = editor_set->GetEditorAs<DungeonEditorV2>(
853 return dungeon_editor->IsWorkbenchWorkflowEnabled();
854 }
855 }
856 return false;
857 },
858 [this](bool enabled) {
859 if (auto* editor_set = GetCurrentEditorSet()) {
860 if (auto* dungeon_editor = editor_set->GetEditorAs<DungeonEditorV2>(
862 dungeon_editor->QueueWorkbenchWorkflowMode(enabled);
863 }
864 }
865 });
866
867 // Wire per-user sidebar prefs so right-click / drag mutate persisted state.
868 activity_bar_->SetUserSettings(&user_settings_);
869
870 // Populate the MoreActions registry with the default rail entries. External
871 // callers can Register/Unregister further entries at runtime.
872 auto& registry = activity_bar_->actions_registry();
873 registry.Register({"command_palette",
874 "Command Palette",
877 {}});
878 registry.Register({"keyboard_shortcuts",
879 "Keyboard Shortcuts",
882 {}});
883 registry.Register({"open_rom_project",
884 "Open ROM / Project",
886 [this]() { window_manager_.TriggerOpenRom(); },
887 {}});
888 registry.Register({"settings",
889 "Settings",
891 [this]() { window_manager_.TriggerShowSettings(); },
892 {}});
893
894 // WindowHost is the declarative window registration surface used by
895 // editor/runtime systems.
896 window_host_ = std::make_unique<WindowHost>(&window_manager_);
897
898 // Wire up EventBus to WorkspaceWindowManager for action event publishing
900}
901
903 // Auto-register panels from ContentRegistry (Unified Panel System)
904 auto registry_panels = ContentRegistry::Panels::CreateAll();
905 for (auto& panel : registry_panels) {
907 }
908
909 // STEP 4.8: Initialize DashboardPanel
910 dashboard_panel_ = std::make_unique<DashboardPanel>(this);
911
912 if (window_host_) {
913 WindowDefinition dashboard_definition;
914 dashboard_definition.id = "dashboard.main";
915 dashboard_definition.display_name = "Dashboard";
916 dashboard_definition.icon = ICON_MD_DASHBOARD;
917 dashboard_definition.category = "Dashboard";
918 dashboard_definition.window_title = " Dashboard";
919 dashboard_definition.shortcut_hint = "F1";
920 dashboard_definition.priority = 0;
921 dashboard_definition.visibility_flag = dashboard_panel_->visibility_flag();
922 window_host_->RegisterWindow(dashboard_definition);
923 } else {
925 {.card_id = "dashboard.main",
926 .display_name = "Dashboard",
927 .window_title = " Dashboard",
928 .icon = ICON_MD_DASHBOARD,
929 .category = "Dashboard",
930 .shortcut_hint = "F1",
931 .visibility_flag = dashboard_panel_->visibility_flag(),
932 .priority = 0});
933 }
934}
935
937 // Subscribe to session lifecycle events via EventBus
938 // (replaces SessionObserver pattern)
940 [this](const SessionSwitchedEvent& e) {
941 HandleSessionSwitched(e.new_index, e.session);
942 });
943
945 [this](const SessionCreatedEvent& e) {
946 HandleSessionCreated(e.index, e.session);
947 });
948
950 [this](const SessionClosedEvent& e) { HandleSessionClosed(e.index); });
951
953 HandleSessionRomLoaded(e.session_id, e.rom);
954 });
955
956 // Subscribe to FrameGuiBeginEvent for ImGui-safe deferred action processing
957 // This replaces scattered manual processing calls with event-driven execution
959 ui_sync_frame_id_.fetch_add(1, std::memory_order_relaxed);
960
961 // Process LayoutCoordinator's deferred actions
963
964 // Process EditorManager's deferred actions
965 if (!deferred_actions_.empty()) {
966 std::vector<std::function<void()>> actions_to_execute;
967 actions_to_execute.swap(deferred_actions_);
968 const int processed_count = static_cast<int>(actions_to_execute.size());
969 for (auto& action : actions_to_execute) {
970 action();
971 }
972 if (processed_count > 0) {
973 const int remaining = pending_editor_deferred_actions_.fetch_sub(
974 processed_count, std::memory_order_relaxed) -
975 processed_count;
976 if (remaining < 0) {
977 pending_editor_deferred_actions_.store(0, std::memory_order_relaxed);
978 }
979 }
980 }
981 });
982
983 // Subscribe to UIActionRequestEvent for activity bar actions
984 // This replaces direct callbacks from WorkspaceWindowManager
986 [this](const UIActionRequestEvent& e) {
987 HandleUIActionRequest(e.action);
988 });
989
991 [this](const PanelVisibilityChangedEvent& e) {
992 if (e.category.empty() ||
994 return;
995 }
996 auto& prefs = user_settings_.prefs();
997 prefs.panel_visibility_state[e.category][e.base_panel_id] = e.visible;
998 settings_dirty_ = true;
1000 });
1001}
1002
1004 // ThemeManager is a singleton that outlives us. Clear the theme-changed
1005 // callback so it stops calling back into a destroyed EditorManager via the
1006 // captured `this` pointer. Matters for unit tests that construct and destroy
1007 // multiple EditorManager instances in the same process.
1009
1010 // EventBus subscriptions are automatically cleaned up when event_bus_ is
1011 // destroyed (owned by this class). No manual unsubscription needed.
1012}
1013
1014// ============================================================================
1015// Session Event Handlers (EventBus subscribers)
1016// ============================================================================
1017
1019 auto& provider = zelda3::GetResourceLabels();
1022
1023 auto merge_labels =
1026 for (const auto& [type, labels] : src) {
1027 auto& out = (*dst)[type];
1028 for (const auto& [key, value] : labels) {
1029 out[key] = value;
1030 }
1031 }
1032 };
1033
1034 merged_labels.clear();
1035 std::vector<std::string> source_parts;
1036
1037 // 1) ROM-local labels as baseline for active session.
1038 if (auto* rom = GetCurrentRom();
1039 rom && rom->resource_label() && !rom->resource_label()->labels_.empty()) {
1040 merge_labels(&merged_labels, rom->resource_label()->labels_);
1041 source_parts.push_back("rom");
1042 }
1043
1044 // 2) Project registry labels from the active hack manifest.
1048 merge_labels(
1049 &merged_labels,
1051 source_parts.push_back("registry");
1052 }
1053
1054 // 3) Explicit project labels override all other sources.
1057 merge_labels(&merged_labels, current_project_.resource_labels);
1058 source_parts.push_back("project");
1059 }
1060
1061 auto* label_source = merged_labels.empty() ? &empty_labels : &merged_labels;
1062 std::string source =
1063 source_parts.empty() ? "empty" : absl::StrJoin(source_parts, "+");
1064
1065 provider.SetProjectLabels(label_source);
1066 provider.SetHackManifest(current_project_.project_opened() &&
1069 : nullptr);
1070
1071 const bool prefer_hmagic =
1075 provider.SetPreferHMagicNames(prefer_hmagic);
1076
1077 LOG_DEBUG("EditorManager",
1078 "ResourceLabelProvider refreshed (source=%s, project_open=%s)",
1079 source.c_str(),
1080 current_project_.project_opened() ? "true" : "false");
1081}
1082
1084 RomSession* session) {
1085 // Update RightDrawerManager with the new session's settings editor
1086 if (right_drawer_manager_ && session) {
1087 right_drawer_manager_->SetSettingsPanel(
1088 session->editors.GetSettingsPanel());
1089 }
1090
1091 // Update properties panel with new ROM
1092 if (session) {
1094 }
1095
1096 // Update ContentRegistry context with current session's ROM and GameData
1097 ContentRegistry::Context::SetRom(session ? &session->rom : nullptr);
1099 : nullptr);
1100
1101 // Keep room/sprite labels in sync with active session context.
1103
1104 const std::string category = window_manager_.GetActiveCategory();
1105 if (!category.empty() &&
1107 auto it = user_settings_.prefs().panel_visibility_state.find(category);
1108 if (it != user_settings_.prefs().panel_visibility_state.end()) {
1109 window_manager_.RestoreVisibilityState(new_index, it->second);
1110 }
1111 }
1112
1113#ifdef YAZE_ENABLE_TESTING
1114 test::TestManager::Get().SetCurrentRom(session ? &session->rom : nullptr);
1115#endif
1116
1117 LOG_DEBUG("EditorManager", "Session switched to %zu via EventBus", new_index);
1118}
1119
1123 LOG_INFO("EditorManager", "Session %zu created via EventBus", index);
1124}
1125
1127 // Update ContentRegistry - it will be set to new active ROM on next switch
1128 // If no sessions remain, clear the context
1130 session_coordinator_->GetTotalSessionCount() == 0) {
1132 }
1133
1134 // Avoid dangling/stale label map pointers after session close.
1136
1137#ifdef YAZE_ENABLE_TESTING
1138 // Update test manager - it will get the new current ROM on next switch
1140#endif
1141
1142 LOG_INFO("EditorManager", "Session %zu closed via EventBus", index);
1143}
1144
1146 auto* session =
1147 static_cast<RomSession*>(session_coordinator_->GetSession(index));
1148 ResetAssetState(session);
1149
1150 // Update ContentRegistry when ROM is loaded (if this is the active session)
1151 if (rom && session_coordinator_ &&
1152 index == session_coordinator_->GetActiveSessionIndex()) {
1154 // Also update GameData from the session
1155 if (session) {
1156 ContentRegistry::Context::SetGameData(&session->game_data);
1157 }
1159 }
1160
1161#ifdef YAZE_ENABLE_TESTING
1162 if (rom) {
1164 }
1165#endif
1166
1167 LOG_INFO("EditorManager", "ROM loaded in session %zu via EventBus", index);
1168}
1169
1171 using Action = UIActionRequestEvent::Action;
1172 switch (action) {
1173 case Action::kShowEmulator:
1174 if (ui_coordinator_) {
1175 ui_coordinator_->SetEmulatorVisible(true);
1176 }
1177 break;
1178
1179 case Action::kShowSettings:
1180 // Toggle Settings panel in sidebar
1182 right_drawer_manager_->ToggleDrawer(
1184 } else {
1186 }
1187 break;
1188
1189 case Action::kShowPanelBrowser:
1190 if (ui_coordinator_) {
1191 ui_coordinator_->ShowWindowBrowser();
1192 }
1193 break;
1194
1195 case Action::kShowSearch:
1196 if (ui_coordinator_) {
1197 ui_coordinator_->ShowGlobalSearch();
1198 }
1199 break;
1200
1201 case Action::kShowShortcuts:
1202 // Shortcut configuration is part of Settings
1204 break;
1205
1206 case Action::kShowCommandPalette:
1207 if (ui_coordinator_) {
1208 ui_coordinator_->ShowCommandPalette();
1209 }
1210 break;
1211
1212 case Action::kShowHelp:
1214 // Toggle Help panel in sidebar
1215 right_drawer_manager_->ToggleDrawer(
1217 } else if (popup_manager_) {
1218 // Fallback to "About" dialog if sidebar not available
1220 }
1221 break;
1222
1223 case Action::kShowAgentChatSidebar:
1225 right_drawer_manager_->OpenDrawer(
1227 } else {
1229 }
1230 break;
1231
1232 case Action::kShowAgentProposalsSidebar:
1234 right_drawer_manager_->OpenDrawer(
1236 }
1237 break;
1238
1239 case Action::kOpenRom: {
1240 auto status = LoadRom();
1241 if (!status.ok()) {
1243 std::string("Open failed: ") + std::string(status.message()),
1245 }
1246 } break;
1247
1248 case Action::kSaveRom:
1249 if (GetCurrentRom() && GetCurrentRom()->is_loaded()) {
1250 auto status = SaveRom();
1251 if (status.ok()) {
1252 toast_manager_.Show("ROM saved successfully", ToastType::kSuccess);
1253 } else if (!absl::IsCancelled(status)) {
1255 std::string("Save failed: ") + std::string(status.message()),
1257 }
1258 }
1259 break;
1260
1261 case Action::kUndo:
1262 if (auto* current_editor = GetCurrentEditor()) {
1263 auto status = current_editor->Undo();
1264 if (!status.ok()) {
1266 std::string("Undo failed: ") + std::string(status.message()),
1268 }
1269 }
1270 break;
1271
1272 case Action::kRedo:
1273 if (auto* current_editor = GetCurrentEditor()) {
1274 auto status = current_editor->Redo();
1275 if (!status.ok()) {
1277 std::string("Redo failed: ") + std::string(status.message()),
1279 }
1280 }
1281 break;
1282
1283 case Action::kResetLayout:
1285 break;
1286 }
1287}
1288
1290 auto& test_manager = test::TestManager::Get();
1291
1292#ifdef YAZE_ENABLE_TESTING
1293 // Register comprehensive test suites
1294 test_manager.RegisterTestSuite(
1295 std::make_unique<test::CoreSystemsTestSuite>());
1296 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
1297 test_manager.RegisterTestSuite(
1298 std::make_unique<test::PerformanceTestSuite>());
1299 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
1300 test_manager.RegisterTestSuite(
1301 std::make_unique<test::RomDependentTestSuite>());
1302
1303 // Register new E2E and ZSCustomOverworld test suites
1304 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
1305 test_manager.RegisterTestSuite(
1306 std::make_unique<test::ZSCustomOverworldTestSuite>());
1307#endif
1308
1309 // Register Google Test suite if available
1310#ifdef YAZE_ENABLE_GTEST
1311 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
1312#endif
1313
1314 // Register z3ed AI Agent test suites (requires gRPC)
1315#ifdef YAZE_WITH_GRPC
1317#endif
1318
1319 // Update resource monitoring to track Arena state
1320 test_manager.UpdateResourceStats();
1321}
1322
1324 const std::string& filename) {
1325 renderer_ = renderer;
1327 SeedOracleProjectInRecents();
1328
1329 // Inject the window manager into emulator and workspace_manager
1332
1333 // Point to a blank editor set when no ROM is loaded
1334 // current_editor_set_ = &blank_editor_set_;
1335
1336 if (!filename.empty()) {
1338 }
1339
1340 // Note: PopupManager is now initialized in constructor before
1341 // MenuOrchestrator This ensures all menu callbacks can safely call
1342 // popup_manager_.Show()
1343
1347
1348 // Apply sidebar state from settings AFTER registering callbacks
1349 // This triggers the callbacks but they should be safe now
1351 /*notify=*/false);
1354 /*notify=*/false);
1356 user_settings_.prefs().sidebar_panel_width, /*notify=*/false);
1359 /*notify=*/false);
1360 {
1361 const bool prefer_dashboard_only =
1363 startup_editor_hint_.empty() && startup_panel_hints_.empty();
1364 if (!prefer_dashboard_only) {
1365 const std::string category = GetPreferredStartupCategory(
1367 if (!category.empty()) {
1368 window_manager_.SetActiveCategory(category, /*notify=*/false);
1370 auto it = user_settings_.prefs().panel_visibility_state.find(category);
1371 if (it != user_settings_.prefs().panel_visibility_state.end()) {
1373 window_manager_.GetActiveSessionId(), it->second);
1374 }
1375 }
1376 }
1377 }
1378
1382 }
1383
1384 // Initialize testing system only when tests are enabled
1385#ifdef YAZE_ENABLE_TESTING
1387#endif
1388
1389 // TestManager will be updated when ROMs are loaded via SetCurrentRom calls
1390
1392}
1393
1395 // Register emulator panels early (emulator Initialize might not be called).
1396 const std::vector<WindowDefinition> panel_definitions = {
1397 {.id = "emulator.cpu_debugger",
1398 .display_name = "CPU Debugger",
1399 .icon = ICON_MD_BUG_REPORT,
1400 .category = "Emulator",
1401 .priority = 10},
1402 {.id = "emulator.ppu_viewer",
1403 .display_name = "PPU Viewer",
1405 .category = "Emulator",
1406 .priority = 20},
1407 {.id = "emulator.memory_viewer",
1408 .display_name = "Memory Viewer",
1409 .icon = ICON_MD_MEMORY,
1410 .category = "Emulator",
1411 .priority = 30},
1412 {.id = "emulator.breakpoints",
1413 .display_name = "Breakpoints",
1414 .icon = ICON_MD_STOP,
1415 .category = "Emulator",
1416 .priority = 40},
1417 {.id = "emulator.performance",
1418 .display_name = "Performance",
1419 .icon = ICON_MD_SPEED,
1420 .category = "Emulator",
1421 .priority = 50},
1422 {.id = "emulator.ai_agent",
1423 .display_name = "AI Agent",
1424 .icon = ICON_MD_SMART_TOY,
1425 .category = "Emulator",
1426 .priority = 60},
1427 {.id = "emulator.save_states",
1428 .display_name = "Save States",
1429 .icon = ICON_MD_SAVE,
1430 .category = "Emulator",
1431 .priority = 70},
1432 {.id = "emulator.keyboard_config",
1433 .display_name = "Keyboard Config",
1434 .icon = ICON_MD_KEYBOARD,
1435 .category = "Emulator",
1436 .priority = 80},
1437 {.id = "emulator.virtual_controller",
1438 .display_name = "Virtual Controller",
1439 .icon = ICON_MD_SPORTS_ESPORTS,
1440 .category = "Emulator",
1441 .priority = 85},
1442 {.id = "emulator.apu_debugger",
1443 .display_name = "APU Debugger",
1444 .icon = ICON_MD_AUDIOTRACK,
1445 .category = "Emulator",
1446 .priority = 90},
1447 {.id = "emulator.audio_mixer",
1448 .display_name = "Audio Mixer",
1449 .icon = ICON_MD_AUDIO_FILE,
1450 .category = "Emulator",
1451 .priority = 100},
1452 {.id = "memory.hex_editor",
1453 .display_name = "Hex Editor",
1454 .icon = ICON_MD_MEMORY,
1455 .category = "Memory",
1456 .window_title = ICON_MD_MEMORY " Hex Editor",
1457 .priority = 10,
1458 .legacy_ids = {"Memory Editor"}},
1459 };
1460
1461 if (window_host_) {
1462 window_host_->RegisterPanels(panel_definitions);
1463 return;
1464 }
1465
1466 for (const auto& definition : panel_definitions) {
1467 WindowDescriptor descriptor;
1468 descriptor.card_id = definition.id;
1469 descriptor.display_name = definition.display_name;
1470 descriptor.window_title = definition.window_title;
1471 descriptor.icon = definition.icon;
1472 descriptor.category = definition.category;
1473 descriptor.shortcut_hint = definition.shortcut_hint;
1474 descriptor.priority = definition.priority;
1475 descriptor.scope = definition.scope;
1476 descriptor.window_lifecycle = definition.window_lifecycle;
1477 descriptor.context_scope = definition.context_scope;
1478 descriptor.visibility_flag = definition.visibility_flag;
1479 descriptor.on_show = definition.on_show;
1480 descriptor.on_hide = definition.on_hide;
1481
1482 for (const auto& legacy_id : definition.legacy_ids) {
1483 window_manager_.RegisterPanelAlias(legacy_id, definition.id);
1484 }
1485
1486 window_manager_.RegisterWindow(descriptor);
1487 if (definition.visible_by_default) {
1488 window_manager_.OpenWindow(definition.id);
1489 }
1490 }
1491}
1492
1494 // Initialize project file editor
1496
1497 // Initialize agent UI (no-op when agent UI is disabled)
1501
1502 // Note: Unified gRPC Server is started from Application::Initialize()
1503 // after gRPC infrastructure is properly set up
1504
1505 // Load critical user settings first
1507 if (!status_.ok()) {
1508 LOG_WARN("EditorManager", "Failed to load user settings: %s",
1509 status_.ToString().c_str());
1510 }
1511
1512 // Wire theme persistence. Any successful theme application (selector click,
1513 // command-palette switch, programmatic call) stamps the name into prefs and
1514 // rides the existing debounced-save path. Decouples ThemeManager (singleton
1515 // in app/gui) from UserSettings (editor-layer) — ThemeManager holds only a
1516 // std::function.
1518 [this](const std::string& theme_name) {
1519 user_settings_.prefs().last_theme_name = theme_name;
1520 settings_dirty_ = true;
1522 });
1523
1524 auto& prefs = user_settings_.prefs();
1525 prefs.switch_motion_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1527 prefs.reduced_motion,
1528 gui::Animator::ClampMotionProfile(prefs.switch_motion_profile));
1529
1531
1534 right_drawer_manager_->ResetDrawerWidths();
1536 right_drawer_manager_->SerializeDrawerWidths();
1537 } else {
1538 right_drawer_manager_->RestoreDrawerWidths(
1540 }
1541 right_drawer_manager_->SetDrawerWidthChangedCallback(
1542 [this](RightDrawerManager::DrawerType, float) {
1543 if (!right_drawer_manager_) {
1544 return;
1545 }
1546 user_settings_.prefs().right_panel_widths =
1547 right_drawer_manager_->SerializeDrawerWidths();
1548 settings_dirty_ = true;
1550 });
1551 }
1553 // Apply sprite naming preference globally.
1556
1558
1559 // Apply font scale after loading (only if ImGui context exists)
1560 if (ImGui::GetCurrentContext() != nullptr) {
1561 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
1562 } else {
1563 LOG_WARN("EditorManager",
1564 "ImGui context not available; skipping FontGlobalScale update");
1565 }
1566
1567 // Restore the user's last theme. Empty (first run) or "Custom Accent"
1568 // (transient generated themes without a matching discovered preset) both
1569 // skip restoration and keep the built-in default (Classic YAZE / YAZE Tre).
1570 const auto& last_theme = user_settings_.prefs().last_theme_name;
1571 if (!last_theme.empty() && last_theme != "Custom Accent") {
1572 gui::ThemeManager::Get().ApplyTheme(last_theme);
1573 }
1574
1575 // Initialize WASM control and session APIs for browser/agent integration
1576#ifdef __EMSCRIPTEN__
1579 LOG_INFO("EditorManager", "WASM Control and Session APIs initialized");
1580#endif
1581}
1582
1588
1590 // Initialize ROM load options dialog callbacks
1592 [this](const RomLoadOptionsDialog::LoadOptions& options) {
1593 // Apply feature flags from dialog
1594 auto& flags = core::FeatureFlags::get();
1595 flags.overworld.kSaveOverworldMaps = options.save_overworld_maps;
1596 flags.overworld.kSaveOverworldEntrances =
1598 flags.overworld.kSaveOverworldExits = options.save_overworld_exits;
1599 flags.overworld.kSaveOverworldItems = options.save_overworld_items;
1600 flags.overworld.kLoadCustomOverworld = options.enable_custom_overworld;
1601 flags.kSaveDungeonMaps = options.save_dungeon_maps;
1602 flags.kSaveAllPalettes = options.save_all_palettes;
1603 flags.kSaveGfxGroups = options.save_gfx_groups;
1604
1605 // Create project if requested
1606 if (options.create_project && !options.project_name.empty()) {
1609 options.project_name, options.project_path);
1610 if (!status.ok()) {
1611 toast_manager_.Show("Failed to create project", ToastType::kError);
1612 } else {
1613 toast_manager_.Show("Project created: " + options.project_name,
1615 }
1616 }
1617
1618 // Close dialog and show editor selection
1619 show_rom_load_options_ = false;
1620 if (ui_coordinator_) {
1621 ui_coordinator_->SetEditorSelectionVisible(true);
1622 }
1623
1624 LOG_INFO("EditorManager", "ROM load options applied: preset=%s",
1625 options.selected_preset.c_str());
1626 });
1627}
1628
1630 // Initialize welcome screen callbacks
1632
1635 if (status_.ok() && ui_coordinator_) {
1636 ui_coordinator_->SetWelcomeScreenVisible(false);
1637 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1638 }
1639 });
1640
1642 [this](const std::string& template_name) {
1643 status_ = CreateNewProject(template_name);
1644 if (status_.ok() && ui_coordinator_) {
1645 ui_coordinator_->SetWelcomeScreenVisible(false);
1646 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1647 }
1648 });
1649
1650 welcome_screen_.SetOpenProjectCallback([this](const std::string& filepath) {
1651 status_ = OpenRomOrProject(filepath);
1652 if (status_.ok() && ui_coordinator_) {
1653 ui_coordinator_->SetWelcomeScreenVisible(false);
1654 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1655 }
1656 });
1657
1659#ifdef YAZE_BUILD_AGENT_UI
1660 ShowAIAgent();
1661#endif
1662 });
1663
1666 window_manager_.OpenWindow("graphics.prototype_viewer");
1667 if (ui_coordinator_) {
1668 ui_coordinator_->SetWelcomeScreenVisible(false);
1669 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1670 }
1671 });
1672
1675 window_manager_.OpenWindow("assembly.code_editor");
1676 if (ui_coordinator_) {
1677 ui_coordinator_->SetWelcomeScreenVisible(false);
1678 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1679 }
1680 });
1681
1683 status_ = OpenProject();
1684 if (status_.ok() && ui_coordinator_) {
1685 ui_coordinator_->SetWelcomeScreenVisible(false);
1686 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1687 } else if (!status_.ok()) {
1689 absl::StrFormat("Failed to open project: %s", status_.message()),
1691 }
1692 });
1693
1695 [this]() { ShowProjectManagement(); });
1696
1698 if (current_project_.filepath.empty()) {
1699 toast_manager_.Show("No project file to edit", ToastType::kInfo);
1700 return;
1701 }
1703 });
1704
1705 // Apply welcome screen preference
1707 ui_coordinator_->SetWelcomeScreenVisible(false);
1708 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1709 }
1710}
1711
1713 // Utility callbacks removed - now handled via EventBus
1714
1716 [this](bool visible, bool expanded) {
1717 user_settings_.prefs().sidebar_visible = visible;
1718 user_settings_.prefs().sidebar_panel_expanded = expanded;
1719 settings_dirty_ = true;
1721 });
1724 settings_dirty_ = true;
1726 });
1728 [this](float width) {
1730 settings_dirty_ = true;
1732 });
1733
1735 const std::string& category) {
1736 if (category.empty() ||
1738 return;
1739 }
1742
1743 const auto& prefs = user_settings_.prefs();
1744 auto it = prefs.panel_visibility_state.find(category);
1745 if (it != prefs.panel_visibility_state.end()) {
1747 window_manager_.GetActiveSessionId(), it->second);
1748 } else {
1749 // No saved visibility state for this category yet.
1750 //
1751 // Apply LayoutPresets defaults only when this category has *no*
1752 // visible panels (editors may have already shown their own defaults,
1753 // e.g. Dungeon Workbench).
1754 const size_t session_id = window_manager_.GetActiveSessionId();
1755 bool any_visible = false;
1756 for (const auto& desc :
1757 window_manager_.GetWindowsInCategory(session_id, category)) {
1758 if (desc.visibility_flag && *desc.visibility_flag) {
1759 any_visible = true;
1760 break;
1761 }
1762 }
1763
1764 if (!any_visible) {
1765 const EditorType type =
1767 for (const auto& window_id : LayoutPresets::GetDefaultWindows(type)) {
1768 window_manager_.OpenWindow(session_id, window_id);
1769 }
1770 }
1771 }
1772
1773 settings_dirty_ = true;
1775 });
1776
1778 [this](const std::string& category) -> Editor* {
1779 Editor* editor = ResolveEditorForCategory(category);
1781 return editor;
1782 });
1783
1785 [this](const std::string& category) {
1787 if (type != EditorType::kSettings && type != EditorType::kUnknown) {
1788 SwitchToEditor(type, true);
1789 }
1790 });
1791
1793 [this](const std::string& category) {
1794 if (ui_coordinator_) {
1795 ui_coordinator_->SetStartupSurface(StartupSurface::kEditor);
1796 }
1797
1798 if (category == "Agent") {
1799#ifdef YAZE_BUILD_AGENT_UI
1800 ShowAIAgent();
1801#endif
1802 return;
1803 }
1804
1806 if (type != EditorType::kSettings) {
1807 SwitchToEditor(type, true);
1808 }
1809 });
1810
1812
1814 [this](const std::string& category, const std::string& path) {
1815 if (category == "Assembly") {
1816 if (auto* editor_set = GetCurrentEditorSet()) {
1817 editor_set->ChangeActiveAssemblyFile(path);
1819 }
1820 }
1821 });
1822}
1823
1825 ShortcutDependencies shortcut_deps;
1826 shortcut_deps.editor_manager = this;
1827 shortcut_deps.editor_registry = &editor_registry_;
1828 shortcut_deps.menu_orchestrator = menu_orchestrator_.get();
1829 shortcut_deps.rom_file_manager = &rom_file_manager_;
1830 shortcut_deps.project_manager = &project_manager_;
1831 shortcut_deps.session_coordinator = session_coordinator_.get();
1832 shortcut_deps.ui_coordinator = ui_coordinator_.get();
1833 shortcut_deps.workspace_manager = &workspace_manager_;
1834 shortcut_deps.popup_manager = popup_manager_.get();
1835 shortcut_deps.toast_manager = &toast_manager_;
1836 shortcut_deps.window_manager = &window_manager_;
1837 shortcut_deps.user_settings = &user_settings_;
1838
1842}
1843
1845 const std::string& editor_name, const std::string& panels_str) {
1846 const bool has_editor = !editor_name.empty();
1847 const bool has_panels = !panels_str.empty();
1848
1849 if (!has_editor && !has_panels) {
1850 return;
1851 }
1852
1853 LOG_INFO("EditorManager",
1854 "Processing startup flags: editor='%s', panels='%s'",
1855 editor_name.c_str(), panels_str.c_str());
1856
1857 std::optional<EditorType> editor_type_to_open =
1858 has_editor ? ParseEditorTypeFromString(editor_name) : std::nullopt;
1859 if (has_editor && !editor_type_to_open.has_value()) {
1860 LOG_WARN("EditorManager", "Unknown editor specified via flag: %s",
1861 editor_name.c_str());
1862 } else if (editor_type_to_open.has_value()) {
1863 // Use EditorActivator to ensure layouts and default panels are initialized
1864 SwitchToEditor(*editor_type_to_open, true, /*from_dialog=*/true);
1865 }
1866
1867 // Open windows via WorkspaceWindowManager - works for any editor type
1868 if (!has_panels) {
1869 return;
1870 }
1871
1872 const size_t session_id = GetCurrentSessionId();
1873 std::string last_known_category = window_manager_.GetActiveCategory();
1874 bool applied_category_from_panel = false;
1875
1876 for (absl::string_view token :
1877 absl::StrSplit(panels_str, ',', absl::SkipWhitespace())) {
1878 if (token.empty()) {
1879 continue;
1880 }
1881 std::string panel_name = std::string(absl::StripAsciiWhitespace(token));
1882 LOG_DEBUG("EditorManager", "Attempting to open panel: '%s'",
1883 panel_name.c_str());
1884
1885 const std::string lower_name = absl::AsciiStrToLower(panel_name);
1886 if (lower_name == "welcome" || lower_name == "welcome_screen") {
1887 if (ui_coordinator_) {
1888 ui_coordinator_->SetWelcomeScreenBehavior(StartupVisibility::kShow);
1889 }
1890 continue;
1891 }
1892 if (lower_name == "dashboard" || lower_name == "dashboard.main" ||
1893 lower_name == "editor_selection") {
1894 if (dashboard_panel_) {
1895 dashboard_panel_->Show();
1896 }
1897 if (ui_coordinator_) {
1898 ui_coordinator_->SetDashboardBehavior(StartupVisibility::kShow);
1899 }
1902 /*notify=*/false);
1903 continue;
1904 }
1905
1906 // Special case: "Room <id>" opens a dungeon room
1907 if (absl::StartsWith(panel_name, "Room ")) {
1908 if (auto* editor_set = GetCurrentEditorSet()) {
1909 try {
1910 int room_id = std::stoi(panel_name.substr(5));
1912 JumpToRoomRequestEvent::Create(room_id, session_id));
1913 } catch (const std::exception& e) {
1914 LOG_WARN("EditorManager", "Invalid room ID format: %s",
1915 panel_name.c_str());
1916 }
1917 }
1918 continue;
1919 }
1920
1921 std::optional<std::string> resolved_panel;
1922 if (window_manager_.GetWindowDescriptor(session_id, panel_name)) {
1923 resolved_panel = panel_name;
1924 } else {
1925 for (const auto& [prefixed_id, descriptor] :
1927 const std::string base_id = StripSessionPrefix(prefixed_id);
1928 const std::string card_lower = absl::AsciiStrToLower(base_id);
1929 const std::string display_lower =
1930 absl::AsciiStrToLower(descriptor.display_name);
1931
1932 if (card_lower == lower_name || display_lower == lower_name) {
1933 resolved_panel = base_id;
1934 break;
1935 }
1936 }
1937 }
1938
1939 if (!resolved_panel.has_value()) {
1940 LOG_WARN("EditorManager",
1941 "Unknown panel '%s' from --open_panels (known count: %zu)",
1942 panel_name.c_str(),
1944 continue;
1945 }
1946
1947 if (window_manager_.OpenWindow(session_id, *resolved_panel)) {
1948 const auto* descriptor =
1949 window_manager_.GetWindowDescriptor(session_id, *resolved_panel);
1950 if (descriptor != nullptr) {
1951 const EditorType type =
1952 EditorRegistry::GetEditorTypeFromCategory(descriptor->category);
1953 const auto ensure_status = EnsureEditorAssetsLoaded(type);
1954 if (!ensure_status.ok()) {
1955 LOG_WARN("EditorManager", "Failed to load assets for panel '%s': %s",
1956 resolved_panel->c_str(), ensure_status.message().data());
1957 }
1958 }
1959 if (descriptor && !applied_category_from_panel &&
1960 descriptor->category != WorkspaceWindowManager::kDashboardCategory) {
1961 window_manager_.SetActiveCategory(descriptor->category);
1962 applied_category_from_panel = true;
1963 } else if (!applied_category_from_panel && descriptor &&
1964 descriptor->category.empty() && !last_known_category.empty()) {
1965 window_manager_.SetActiveCategory(last_known_category);
1966 }
1967 } else {
1968 LOG_WARN("EditorManager", "Failed to show panel '%s'",
1969 resolved_panel->c_str());
1970 }
1971 }
1972}
1973
1980
1988
1990 constexpr int kTargetRevision =
1992 if (!user_settings_.ApplyPanelLayoutDefaultsRevision(kTargetRevision)) {
1993 return;
1994 }
1995
1997 settings_dirty_ = true;
1999
2000 LOG_INFO("EditorManager",
2001 "Applied panel layout defaults migration revision %d",
2002 kTargetRevision);
2003}
2004
2006 const std::string& saved_category,
2007 const std::vector<std::string>& available_categories) const {
2008 // If saved category is valid and not Emulator, use it directly
2009 if (!saved_category.empty() && saved_category != "Emulator") {
2010 // Validate it exists in available_categories if the list is provided
2011 if (available_categories.empty()) {
2012 return saved_category;
2013 }
2014 for (const auto& cat : available_categories) {
2015 if (cat == saved_category)
2016 return saved_category;
2017 }
2018 }
2019 // Pick first non-Emulator category from available list
2020 for (const auto& cat : available_categories) {
2021 if (cat != "Emulator")
2022 return cat;
2023 }
2024 return {};
2025}
2026
2030
2032 if (ui_coordinator_) {
2033 ui_coordinator_->SetWelcomeScreenBehavior(welcome_mode_override_);
2034 ui_coordinator_->SetDashboardBehavior(dashboard_mode_override_);
2035 }
2036
2038 const bool sidebar_visible =
2040 window_manager_.SetSidebarVisible(sidebar_visible, /*notify=*/false);
2041 if (ui_coordinator_) {
2042 ui_coordinator_->SetPanelSidebarVisible(sidebar_visible);
2043 }
2044 }
2045
2046 // Force sidebar panel to collapse if Welcome Screen or Dashboard is explicitly shown
2047 // This prevents visual overlap/clutter on startup
2050 window_manager_.SetSidebarExpanded(false, /*notify=*/false);
2051 }
2052
2053 if (dashboard_panel_) {
2055 dashboard_panel_->Hide();
2057 dashboard_panel_->Show();
2058 }
2059 }
2060}
2061
2063 ApplyStartupVisibility(config);
2064 // Handle startup editor and panels
2065 std::string panels_str;
2066 for (size_t i = 0; i < config.open_panels.size(); ++i) {
2067 if (i > 0)
2068 panels_str += ",";
2069 panels_str += config.open_panels[i];
2070 }
2071 OpenEditorAndPanelsFromFlags(config.startup_editor, panels_str);
2072
2073 // Handle jump targets
2074 if (config.jump_to_room >= 0) {
2077 }
2078 if (config.jump_to_map >= 0) {
2081 }
2082}
2083
2084absl::Status EditorManager::LoadAssetsForMode(uint64_t loading_handle) {
2085 switch (asset_load_mode_) {
2087 return LoadAssetsLazy(loading_handle);
2090 default:
2091 return LoadAssets(loading_handle);
2092 }
2093}
2094
2096 if (!session) {
2097 return;
2098 }
2099 session->game_data_loaded = false;
2100 session->editor_initialized.fill(false);
2101 session->editor_assets_loaded.fill(false);
2102}
2103
2105 EditorType type) {
2106 if (!session) {
2107 return;
2108 }
2109 const size_t index = EditorTypeIndex(type);
2110 if (index < session->editor_initialized.size()) {
2111 session->editor_initialized[index] = true;
2112 }
2113}
2114
2116 if (!session) {
2117 return;
2118 }
2119 const size_t index = EditorTypeIndex(type);
2120 if (index < session->editor_assets_loaded.size()) {
2121 session->editor_assets_loaded[index] = true;
2122 }
2123}
2124
2126 switch (type) {
2134 return true;
2135 default:
2136 return false;
2137 }
2138}
2139
2143
2145 EditorSet* editor_set) const {
2146 std::unordered_set<EditorType> types;
2147
2148 auto add_type = [&types](EditorType type) {
2149 switch (type) {
2153 case EditorType::kAgent:
2154 return;
2155 default:
2156 types.insert(type);
2157 return;
2158 }
2159 };
2160
2161 auto add_category = [&](const std::string& category) {
2162 if (category.empty() ||
2164 return;
2165 }
2167 };
2168
2169 const size_t session_id = GetCurrentSessionId();
2170 bool used_startup_hints = false;
2171
2172 if (!startup_editor_hint_.empty()) {
2173 if (auto startup_type = ParseEditorTypeFromString(startup_editor_hint_)) {
2174 add_type(*startup_type);
2175 used_startup_hints = true;
2176 }
2177 }
2178
2179 for (const auto& panel_name : startup_panel_hints_) {
2180 if (panel_name.empty()) {
2181 continue;
2182 }
2183
2184 if (const auto* descriptor =
2185 window_manager_.GetWindowDescriptor(session_id, panel_name)) {
2186 add_category(descriptor->category);
2187 used_startup_hints = true;
2188 continue;
2189 }
2190
2191 const std::string lower_name = absl::AsciiStrToLower(panel_name);
2192 for (const auto& [prefixed_id, descriptor] :
2194 const std::string base_id = StripSessionPrefix(prefixed_id);
2195 const std::string card_lower = absl::AsciiStrToLower(base_id);
2196 const std::string display_lower =
2197 absl::AsciiStrToLower(descriptor.display_name);
2198 if (card_lower == lower_name || display_lower == lower_name) {
2199 add_category(descriptor.category);
2200 used_startup_hints = true;
2201 break;
2202 }
2203 }
2204 }
2205
2206 if (used_startup_hints) {
2207 return std::vector<EditorType>(types.begin(), types.end());
2208 }
2209
2211 return {};
2212 }
2213
2214 if (editor_set) {
2215 for (auto* editor : editor_set->active_editors_) {
2216 if (editor != nullptr && *editor->active()) {
2217 add_type(editor->type());
2218 }
2219 }
2220 }
2221
2222 if (current_editor_ != nullptr) {
2223 add_type(current_editor_->type());
2224 }
2225
2226 add_category(window_manager_.GetActiveCategory());
2227
2228 for (const auto& window_id :
2230 if (const auto* descriptor =
2231 window_manager_.GetWindowDescriptor(session_id, window_id)) {
2232 add_category(descriptor->category);
2233 }
2234 }
2235
2236 return std::vector<EditorType>(types.begin(), types.end());
2237}
2238
2240 EditorSet* editor_set) const {
2241 return editor_set ? editor_set->GetEditor(type) : nullptr;
2242}
2243
2245 if (category.empty() ||
2247 return nullptr;
2248 }
2249
2250 auto* editor_set = GetCurrentEditorSet();
2251 if (!editor_set) {
2252 return nullptr;
2253 }
2254
2256 switch (type) {
2257 case EditorType::kAgent:
2258#ifdef YAZE_BUILD_AGENT_UI
2259 return agent_ui_.GetAgentEditor();
2260#else
2261 return nullptr;
2262#endif
2266 return nullptr;
2267 default:
2268 return GetEditorByType(type, editor_set);
2269 }
2270}
2271
2272void EditorManager::SyncEditorContextForCategory(const std::string& category) {
2273 if (Editor* resolved = ResolveEditorForCategory(category)) {
2274 SetCurrentEditor(resolved);
2275 } else if (!category.empty() &&
2277 LOG_DEBUG("EditorManager", "No editor context available for category '%s'",
2278 category.c_str());
2279 }
2280}
2281
2283 EditorSet* editor_set,
2284 Rom* rom) {
2285 if (!editor_set) {
2286 return absl::FailedPreconditionError("No editor set available");
2287 }
2288
2289 auto* editor = GetEditorByType(type, editor_set);
2290 if (!editor) {
2291 return absl::OkStatus();
2292 }
2293 editor->Initialize();
2294 return absl::OkStatus();
2295}
2296
2298 auto* session = session_coordinator_
2299 ? session_coordinator_->GetActiveRomSession()
2300 : nullptr;
2301 if (!session) {
2302 return absl::FailedPreconditionError("No active session");
2303 }
2304 if (session->game_data_loaded) {
2305 return absl::OkStatus();
2306 }
2307 if (!session->rom.is_loaded()) {
2308 return absl::FailedPreconditionError("ROM not loaded");
2309 }
2310
2311 RETURN_IF_ERROR(zelda3::LoadGameData(session->rom, session->game_data));
2312 *gfx::Arena::Get().mutable_gfx_sheets() = session->game_data.gfx_bitmaps;
2313
2314 auto* game_data = &session->game_data;
2315 auto* editor_set = &session->editors;
2316 editor_set->SetGameData(game_data);
2317
2319 session->game_data_loaded = true;
2320
2321 return absl::OkStatus();
2322}
2323
2325 if (type == EditorType::kUnknown) {
2326 return absl::OkStatus();
2327 }
2328
2329 auto* session = session_coordinator_
2330 ? session_coordinator_->GetActiveRomSession()
2331 : nullptr;
2332 if (!session) {
2333 return absl::OkStatus();
2334 }
2335
2336 if (!session->rom.is_loaded()) {
2338 return absl::OkStatus();
2339 }
2340 const size_t index = EditorTypeIndex(type);
2341 if (index >= session->editor_initialized.size()) {
2342 return absl::InvalidArgumentError("Invalid editor type");
2343 }
2344 if (EditorInitRequiresGameData(type)) {
2345 return absl::OkStatus();
2346 }
2347 if (!session->editor_initialized[index]) {
2349 InitializeEditorForType(type, &session->editors, &session->rom));
2350 MarkEditorInitialized(session, type);
2351 }
2352 return absl::OkStatus();
2353 }
2354
2355 const size_t index = EditorTypeIndex(type);
2356 if (index >= session->editor_initialized.size()) {
2357 return absl::InvalidArgumentError("Invalid editor type");
2358 }
2359
2360 if (EditorInitRequiresGameData(type)) {
2362 }
2363
2364 if (!session->editor_initialized[index]) {
2366 InitializeEditorForType(type, &session->editors, &session->rom));
2367 MarkEditorInitialized(session, type);
2368 }
2369
2370 if (EditorRequiresGameData(type)) {
2372 }
2373
2374 if (!session->editor_assets_loaded[index]) {
2375 auto* editor = GetEditorByType(type, &session->editors);
2376 if (editor) {
2377 RETURN_IF_ERROR(editor->Load());
2378 }
2379 MarkEditorLoaded(session, type);
2380 }
2381
2382 return absl::OkStatus();
2383}
2384
2402 ProcessInput();
2404 DrawInterface();
2405
2406 return status_;
2407}
2408
2410 // Space/focus transitions can leave mid-animation surfaces ghosted.
2411 // Reset transient animation state to a stable endpoint.
2414 right_drawer_manager_->OnHostVisibilityChanged(visible);
2415 }
2416}
2417
2419 // Update timing manager for accurate delta time across the application
2421
2422 // Execute keyboard shortcuts (registered via ShortcutConfigurator)
2424}
2425
2427 status_ = absl::OkStatus();
2429 if (ui_coordinator_) {
2430 ui_coordinator_->RefreshWorkflowActions();
2431 }
2432 // Check for layout rebuild requests and execute if needed (delegated to LayoutCoordinator)
2433 bool is_emulator_visible =
2434 ui_coordinator_ && ui_coordinator_->IsEmulatorVisible();
2435 EditorType current_type =
2437 layout_coordinator_.ProcessLayoutRebuild(current_type, is_emulator_visible);
2438
2439 // Periodic user settings auto-save
2440 if (settings_dirty_) {
2441 const float elapsed = TimingManager::Get().GetElapsedTime();
2442 if (elapsed - settings_dirty_timestamp_ >= 1.0f) {
2443 auto save_status = user_settings_.Save();
2444 if (!save_status.ok()) {
2445 LOG_WARN("EditorManager", "Failed to save user settings: %s",
2446 save_status.ToString().c_str());
2447 }
2448 settings_dirty_ = false;
2449 }
2450 }
2451
2452 // Update agent editor dashboard
2454
2455 // Ensure TestManager always has the current ROM
2456 static Rom* last_test_rom = nullptr;
2457 auto* current_rom = GetCurrentRom();
2458 if (last_test_rom != current_rom) {
2459 LOG_DEBUG(
2460 "EditorManager",
2461 "EditorManager::Update - ROM changed, updating TestManager: %p -> %p",
2462 (void*)last_test_rom, (void*)current_rom);
2464 last_test_rom = current_rom;
2465 }
2466
2467 // Autosave timer
2468 if (user_settings_.prefs().autosave_enabled && current_rom &&
2469 current_rom->dirty()) {
2470 autosave_timer_ += ImGui::GetIO().DeltaTime;
2472 autosave_timer_ = 0.0f;
2474 s.backup = true;
2475 s.save_new = false;
2476 auto st = current_rom->SaveToFile(s);
2477 if (st.ok()) {
2478 toast_manager_.Show("Autosave completed", editor::ToastType::kSuccess);
2479 } else {
2480 toast_manager_.Show(std::string(st.message()),
2482 }
2483 }
2484 } else {
2485 autosave_timer_ = 0.0f;
2486 }
2487
2488 // Update ROM context for agent UI
2489 if (current_rom && current_rom->is_loaded()) {
2490 agent_ui_.SetRomContext(current_rom);
2492 if (auto* editor_set = GetCurrentEditorSet()) {
2493 agent_ui_.SetAsarWrapperContext(editor_set->GetAsarWrapper());
2494 // Backend-agnostic symbol feed. Works for ASAR and z3dk alike; tools
2495 // prefer this pointer over reaching through the wrapper.
2497 &editor_set->GetAssemblySymbols());
2498 }
2499 }
2500
2501 // Delegate session updates to SessionCoordinator
2503 session_coordinator_->UpdateSessions();
2504 }
2505}
2506
2508 if (!active_project_build_) {
2509 return;
2510 }
2511
2512 const auto snapshot = active_project_build_->GetSnapshot();
2513 UpdateBuildWorkflowStatus(
2516 snapshot.running
2517 ? "Build running"
2518 : (snapshot.status.ok() ? "Build succeeded" : "Build failed"),
2519 snapshot.output_tail.empty()
2520 ? (snapshot.running
2521 ? std::string(
2522 "Running the configured project build command")
2523 : std::string(snapshot.status.message()))
2524 : snapshot.output_tail,
2525 snapshot.running
2527 : (snapshot.status.ok() ? ProjectWorkflowState::kSuccess
2529 snapshot.output_tail, snapshot.running));
2531 project_management_panel_->SetBuildLogOutput(snapshot.output);
2532 }
2534
2535 if (snapshot.running || active_project_build_reported_) {
2536 return;
2537 }
2538
2540 if (!snapshot.status.ok()) {
2541 AppendWorkflowHistoryEntry(
2542 "Build",
2543 MakeBuildStatus("Build failed", std::string(snapshot.status.message()),
2544 ProjectWorkflowState::kFailure, snapshot.output_tail,
2545 false),
2546 snapshot.output);
2548 absl::StrFormat("Build failed: %s", snapshot.status.message()),
2549 snapshot.status.code() == absl::StatusCode::kCancelled
2552 return;
2553 }
2554
2555 AppendWorkflowHistoryEntry(
2556 "Build",
2558 "Build succeeded",
2559 snapshot.output_tail.empty() ? std::string("Project build completed")
2560 : snapshot.output_tail,
2561 ProjectWorkflowState::kSuccess, snapshot.output_tail, false),
2562 snapshot.output);
2563 toast_manager_.Show(snapshot.output_tail.empty()
2564 ? "Project build completed"
2565 : absl::StrFormat("Project build completed: %s",
2566 snapshot.output_tail),
2568}
2569
2571
2572 // Draw editor selection dialog (managed by UICoordinator)
2573 if (ui_coordinator_ && ui_coordinator_->IsEditorSelectionVisible()) {
2574 dashboard_panel_->Show();
2575 dashboard_panel_->Draw();
2576 if (!dashboard_panel_->IsVisible()) {
2577 ui_coordinator_->SetEditorSelectionVisible(false);
2578 }
2579 }
2580
2581 // Draw ROM load options dialog (ZSCustomOverworld, feature flags, project)
2584 }
2585
2586 // Draw window browser (managed by UICoordinator)
2587 if (ui_coordinator_ && ui_coordinator_->IsWindowBrowserVisible()) {
2588 bool show = true;
2589 if (activity_bar_) {
2590 activity_bar_->DrawWindowBrowser(GetCurrentSessionId(), &show);
2591 }
2592 if (!show) {
2593 ui_coordinator_->SetWindowBrowserVisible(false);
2594 }
2595 }
2596
2597 // Draw background grid effects
2598 if (ui_coordinator_) {
2599 ui_coordinator_->DrawBackground();
2600 }
2601
2602 // Draw UICoordinator UI components (Welcome Screen, Command Palette, etc.)
2603 if (ui_coordinator_) {
2604 ui_coordinator_->DrawAllUI();
2605 }
2606
2607 // Handle Welcome screen early-exit for rendering
2608 if (ui_coordinator_ && ui_coordinator_->ShouldShowWelcome()) {
2610 right_drawer_manager_->CloseDrawer();
2611 }
2612 return;
2613 }
2614
2617 RunEmulator();
2618
2619 // Draw sidebar
2620 if (ui_coordinator_ && ui_coordinator_->IsPanelSidebarVisible()) {
2621 auto all_categories = EditorRegistry::GetAllEditorCategories();
2622 std::unordered_set<std::string> active_editor_categories;
2623
2624 if (auto* current_editor_set = GetCurrentEditorSet()) {
2626 for (size_t session_idx = 0;
2627 session_idx < session_coordinator_->GetTotalSessionCount();
2628 ++session_idx) {
2629 auto* session = static_cast<RomSession*>(
2630 session_coordinator_->GetSession(session_idx));
2631 if (!session || !session->rom.is_loaded()) {
2632 continue;
2633 }
2634
2635 for (auto* editor : session->editors.active_editors_) {
2636 if (*editor->active() && IsPanelBasedEditor(editor->type())) {
2637 std::string category =
2638 EditorRegistry::GetEditorCategory(editor->type());
2639 active_editor_categories.insert(category);
2640 }
2641 }
2642 }
2643
2644 if (ui_coordinator_->IsEmulatorVisible()) {
2645 active_editor_categories.insert("Emulator");
2646 }
2647 }
2648 }
2649
2650 const bool prefer_dashboard_only =
2652 startup_editor_hint_.empty() && startup_panel_hints_.empty();
2653 std::string sidebar_category = window_manager_.GetActiveCategory();
2654 if (!prefer_dashboard_only && sidebar_category.empty() &&
2655 !all_categories.empty()) {
2656 sidebar_category = GetPreferredStartupCategory("", all_categories);
2657 if (!sidebar_category.empty()) {
2658 window_manager_.SetActiveCategory(sidebar_category, /*notify=*/false);
2659 SyncEditorContextForCategory(sidebar_category);
2661 sidebar_category);
2662 if (it != user_settings_.prefs().panel_visibility_state.end()) {
2664 window_manager_.GetActiveSessionId(), it->second);
2665 }
2666 }
2667 }
2668
2669 auto has_rom_callback = [this]() -> bool {
2670 auto* rom = GetCurrentRom();
2671 return rom && rom->is_loaded();
2672 };
2673
2674 if (activity_bar_ && ui_coordinator_->ShouldShowActivityBar()) {
2675 auto is_rom_dirty_callback = [this]() -> bool {
2676 auto* rom = GetCurrentRom();
2677 return rom && rom->is_loaded() && rom->dirty();
2678 };
2679 activity_bar_->Render(GetCurrentSessionId(), sidebar_category,
2680 all_categories, active_editor_categories,
2681 has_rom_callback, is_rom_dirty_callback);
2682 }
2683 }
2684
2685 // Draw right panel
2688 right_drawer_manager_->Draw();
2689 }
2690
2691 // Update and draw status bar
2695 session_coordinator_->GetActiveSessionCount());
2696 }
2697
2698 bool has_agent_info = false;
2699#if defined(YAZE_BUILD_AGENT_UI)
2700 if (auto* agent_editor = agent_ui_.GetAgentEditor()) {
2701 auto* chat = agent_editor->GetAgentChat();
2702 const auto* ctx = agent_ui_.GetContext();
2703 if (ctx) {
2704 const auto& config = ctx->agent_config();
2705 bool active = chat && *chat->active();
2706 status_bar_.SetAgentInfo(config.ai_provider, config.ai_model, active);
2707 has_agent_info = true;
2708 }
2709 }
2710#endif
2711 if (!has_agent_info) {
2713 }
2716 }
2717
2718 // Editor-aware context: let the active editor push its own mode/custom
2719 // segments for this frame without wiping event-driven cursor/selection/zoom
2720 // state that older editors still publish through the event bus.
2722 if (current_editor_) {
2724 }
2725
2726 status_bar_.Draw();
2727
2728 // Check if ROM is loaded before drawing panels
2729 auto* current_editor_set = GetCurrentEditorSet();
2730 if (!current_editor_set || !GetCurrentRom()) {
2731 if (window_manager_.GetActiveCategory() == "Agent") {
2733 }
2734 return;
2735 }
2736
2737 // Central workspace window drawing
2739
2740 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
2742 }
2743
2744 // Draw SessionCoordinator UI components
2746 session_coordinator_->DrawSessionSwitcher();
2747 session_coordinator_->DrawSessionManager();
2748 session_coordinator_->DrawSessionRenameDialog();
2749 }
2750}
2751
2753 if (ImGui::BeginMenuBar()) {
2754 // Consistent button styling for sidebar toggle
2755 {
2756 const bool sidebar_visible = window_manager_.IsSidebarVisible();
2757 gui::StyleColorGuard sidebar_btn_guard(
2758 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
2759 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
2760 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
2761 {ImGuiCol_Text, sidebar_visible ? gui::GetPrimaryVec4()
2763
2764 const char* icon = sidebar_visible ? ICON_MD_MENU_OPEN : ICON_MD_MENU;
2765 if (ImGui::SmallButton(icon)) {
2767 }
2768 }
2769
2770 if (ImGui::IsItemHovered()) {
2771 const char* tooltip = window_manager_.IsSidebarVisible()
2772 ? "Hide Activity Bar (Ctrl+B)"
2773 : "Show Activity Bar (Ctrl+B)";
2774 ImGui::SetTooltip("%s", tooltip);
2775 }
2776
2777 // Delegate menu building to MenuOrchestrator
2778 if (menu_orchestrator_) {
2779 menu_orchestrator_->BuildMainMenu();
2780 }
2781
2782 // Delegate menu bar extras to UICoordinator
2783 if (ui_coordinator_) {
2784 ui_coordinator_->DrawMenuBarExtras();
2785 }
2786
2787 ImGui::EndMenuBar();
2788 }
2789}
2790
2792
2793 // ImGui debug windows
2794 if (ui_coordinator_) {
2795 if (ui_coordinator_->IsImGuiDemoVisible()) {
2796 bool visible = true;
2797 ImGui::ShowDemoWindow(&visible);
2798 if (!visible)
2799 ui_coordinator_->SetImGuiDemoVisible(false);
2800 }
2801
2802 if (ui_coordinator_->IsImGuiMetricsVisible()) {
2803 bool visible = true;
2804 ImGui::ShowMetricsWindow(&visible);
2805 if (!visible)
2806 ui_coordinator_->SetImGuiMetricsVisible(false);
2807 }
2808 }
2809
2810 // Legacy window-based editors
2811 if (auto* editor_set = GetCurrentEditorSet()) {
2812 bool* hex_visibility =
2813 window_manager_.GetWindowVisibilityFlag("memory.hex_editor");
2814 if (hex_visibility) {
2815 if (auto* editor = editor_set->GetEditor(EditorType::kHex)) {
2816 // Keep the legacy panel visibility flag in sync with the window close
2817 // button (ImGui::Begin will toggle Editor::active_).
2818 editor->set_active(*hex_visibility);
2819 editor->Update();
2820 *hex_visibility = *editor->active();
2821 }
2822 }
2823
2824 if (ui_coordinator_ && ui_coordinator_->IsAsmEditorVisible()) {
2825 if (auto* editor = editor_set->GetEditor(EditorType::kAssembly)) {
2826 editor->set_active(true);
2827 editor->Update();
2828 if (!*editor->active()) {
2829 ui_coordinator_->SetAsmEditorVisible(false);
2830 }
2831 }
2832 }
2833 }
2834
2835 // Project and performance tools
2837
2838 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
2842 if (!gfx::PerformanceDashboard::Get().IsVisible()) {
2843 ui_coordinator_->SetPerformanceDashboardVisible(false);
2844 }
2845 }
2846
2847#ifdef YAZE_ENABLE_TESTING
2848 if (show_test_dashboard_) {
2850 test::TestManager::Get().DrawTestDashboard(&show_test_dashboard_);
2851 }
2852#endif
2853}
2854
2856 // Update proposal drawer context
2858
2859 // Agent UI popups
2861
2862 // Resource label management
2863 if (ui_coordinator_ && ui_coordinator_->IsResourceLabelManagerVisible() &&
2864 GetCurrentRom()) {
2865 bool visible = true;
2871 }
2872 if (!visible)
2873 ui_coordinator_->SetResourceLabelManagerVisible(false);
2874 }
2875
2876 // Layout presets
2877 if (ui_coordinator_) {
2878 ui_coordinator_->DrawLayoutPresets();
2879 }
2880}
2881
2883 auto* current_rom = GetCurrentRom();
2884 if (!current_rom)
2885 return;
2886
2887 // Visibility gates *rendering*, not *ticking*. Run(rom) is the lazy-init +
2888 // render path; the SNES only starts (running_=true, snes_initialized_=true)
2889 // after Run(rom) fires while the emulator panel is visible. Once running,
2890 // switching to another editor hides the panel but must NOT freeze the game —
2891 // the tick-only branch below keeps audio + frame state alive.
2892 if (ui_coordinator_ && ui_coordinator_->IsEmulatorVisible()) {
2893 emulator_.Run(current_rom);
2894 } else if (emulator_.running() && emulator_.is_snes_initialized()) {
2897 } else {
2899 }
2900 }
2901}
2902
2909
2933 auto load_from_path = [this](const std::string& file_name) -> absl::Status {
2934 if (file_name.empty()) {
2935 return absl::OkStatus();
2936 }
2937
2938 // Check if this is a project file - route to project loading
2939 if (absl::EndsWith(file_name, ".yaze") ||
2940 absl::EndsWith(file_name, ".zsproj") ||
2941 absl::EndsWith(file_name, ".yazeproj")) {
2942 return OpenRomOrProject(file_name);
2943 }
2944
2945 if (session_coordinator_->HasDuplicateSession(file_name)) {
2946 toast_manager_.Show("ROM already open in another session",
2948 return absl::OkStatus();
2949 }
2950
2951 // Delegate ROM loading to RomFileManager
2952 Rom temp_rom;
2953 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name));
2954
2955 auto session_or = session_coordinator_->CreateSessionFromRom(
2956 std::move(temp_rom), file_name);
2957 if (!session_or.ok()) {
2958 return session_or.status();
2959 }
2960
2964
2968
2969 // Keep ResourceLabelProvider in sync with the newly-active ROM session
2970 // before any editors/assets query room/sprite names.
2972
2973#ifdef YAZE_ENABLE_TESTING
2975#endif
2976
2978 const auto& recent_files = manager.GetRecentFiles();
2979 const bool is_first_time_rom_path =
2980 std::find(recent_files.begin(), recent_files.end(), file_name) ==
2981 recent_files.end();
2982 manager.AddFile(file_name);
2983 manager.Save();
2984
2986
2987 if (ui_coordinator_) {
2988 ui_coordinator_->SetWelcomeScreenVisible(false);
2989
2990 // Show ROM load options and bias new ROM paths toward project creation.
2991 rom_load_options_dialog_.Open(GetCurrentRom(), is_first_time_rom_path);
2993 }
2994
2995 return absl::OkStatus();
2996 };
2997
2998#if defined(__APPLE__) && TARGET_OS_IOS == 1
2999 // On iOS, route through the SwiftUI overlay document picker to get proper
3000 // security-scoped access to iCloud Drive and Files app locations. This
3001 // mirrors how OpenProject() works on iOS and supports cloud ROMs.
3002 // The SwiftUI picker calls YazeIOSBridge.loadRomAtPath: after importing the
3003 // ROM to a persistent sandbox directory.
3005 return absl::OkStatus();
3006#else
3009 return load_from_path(file_name);
3010#endif
3011}
3012
3013absl::Status EditorManager::LoadAssets(uint64_t passed_handle) {
3014 auto* current_rom = GetCurrentRom();
3015 auto* current_editor_set = GetCurrentEditorSet();
3016 if (!current_rom || !current_editor_set) {
3017 return absl::FailedPreconditionError("No ROM or editor set loaded");
3018 }
3019
3020 auto* current_session = session_coordinator_->GetActiveRomSession();
3021 if (!current_session) {
3022 return absl::FailedPreconditionError("No active ROM session");
3023 }
3024 ResetAssetState(current_session);
3025
3026 auto start_time = std::chrono::steady_clock::now();
3027
3028#ifdef __EMSCRIPTEN__
3029 // Use passed handle if provided, otherwise create new one
3030 auto loading_handle =
3031 passed_handle != 0
3032 ? static_cast<app::platform::WasmLoadingManager::LoadingHandle>(
3033 passed_handle)
3034 : app::platform::WasmLoadingManager::BeginLoading(
3035 "Loading Editor Assets");
3036
3037 // Progress starts at 10% (ROM already loaded), goes to 100%
3038 constexpr float kStartProgress = 0.10f;
3039 constexpr float kEndProgress = 1.0f;
3040 constexpr int kTotalSteps = 11; // Graphics + 8 editors + profiler + finish
3041 int current_step = 0;
3042 auto update_progress = [&](const std::string& message) {
3043 current_step++;
3044 float progress =
3045 kStartProgress + (kEndProgress - kStartProgress) *
3046 (static_cast<float>(current_step) / kTotalSteps);
3047 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, progress);
3048 app::platform::WasmLoadingManager::UpdateMessage(loading_handle, message);
3049 };
3050 // RAII guard to ensure loading indicator is closed even on early return
3051 auto cleanup_loading = [&]() {
3052 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3053 };
3054 struct LoadingGuard {
3055 std::function<void()> cleanup;
3056 bool dismissed = false;
3057 ~LoadingGuard() {
3058 if (!dismissed)
3059 cleanup();
3060 }
3061 void dismiss() { dismissed = true; }
3062 } loading_guard{cleanup_loading};
3063#else
3064 (void)passed_handle; // Unused on non-WASM
3065#endif
3066
3067 // Set renderer for emulator (lazy initialization happens in Run())
3068 if (renderer_) {
3070 }
3071
3072 const auto preload_editor_list = CollectEditorsToPreload(current_editor_set);
3073 const std::unordered_set<EditorType> preload_types(
3074 preload_editor_list.begin(), preload_editor_list.end());
3075
3076 // Initialize only the editors needed for the current startup surface. This
3077 // registers their windows and sets up editor-specific resources before Load().
3078 struct InitStep {
3079 EditorType type;
3080 bool mark_loaded;
3081 };
3082 const InitStep init_steps[] = {
3084 {EditorType::kGraphics, false}, {EditorType::kScreen, false},
3085 {EditorType::kSprite, false}, {EditorType::kPalette, false},
3086 {EditorType::kAssembly, true}, {EditorType::kMusic, false},
3087 {EditorType::kDungeon, false},
3088 };
3089 for (const auto& step : init_steps) {
3090 if (!preload_types.contains(step.type)) {
3091 continue;
3092 }
3093 if (auto* editor = current_editor_set->GetEditor(step.type)) {
3094 editor->Initialize();
3095 MarkEditorInitialized(current_session, step.type);
3096 if (step.mark_loaded) {
3097 MarkEditorLoaded(current_session, step.type);
3098 }
3099 }
3100 }
3101
3102#ifdef __EMSCRIPTEN__
3103 update_progress("Loading graphics sheets...");
3104#endif
3105 // Load all Zelda3-specific data (metadata, palettes, gfx groups, graphics)
3107 zelda3::LoadGameData(*current_rom, current_session->game_data));
3108 current_session->game_data_loaded = true;
3109
3110 // Copy loaded graphics to Arena for global access
3112 current_session->game_data.gfx_bitmaps;
3113
3114 // Propagate GameData to editors that already exist; future editors inherit it
3115 // on first construction via EditorSet.
3116 auto* game_data = &current_session->game_data;
3117 current_editor_set->SetGameData(game_data);
3118
3119 struct LoadStep {
3120 EditorType type;
3121 const char* progress_message;
3122 };
3123 const LoadStep load_steps[] = {
3124 {EditorType::kOverworld, "Loading overworld..."},
3125 {EditorType::kDungeon, "Loading dungeons..."},
3126 {EditorType::kScreen, "Loading screen editor..."},
3127 {EditorType::kGraphics, "Loading graphics editor..."},
3128 {EditorType::kSprite, "Loading sprites..."},
3129 {EditorType::kMessage, "Loading messages..."},
3130 {EditorType::kMusic, "Loading music..."},
3131 {EditorType::kPalette, "Loading palettes..."},
3132 };
3133 for (const auto& step : load_steps) {
3134 if (!preload_types.contains(step.type)) {
3135 continue;
3136 }
3137#ifdef __EMSCRIPTEN__
3138 update_progress(step.progress_message);
3139#endif
3140 if (auto* editor = current_editor_set->GetEditor(step.type)) {
3141 RETURN_IF_ERROR(editor->Load());
3142 MarkEditorLoaded(current_session, step.type);
3143 }
3144 }
3145
3146#ifdef __EMSCRIPTEN__
3147 update_progress("Finishing up...");
3148#endif
3149
3150 // Set up RightDrawerManager with session's settings editor
3152 auto* settings = current_editor_set->GetSettingsPanel();
3153 right_drawer_manager_->SetSettingsPanel(settings);
3154 }
3155
3156 // Apply user preferences to status bar
3158
3160
3161#ifdef __EMSCRIPTEN__
3162 // Dismiss the guard and manually close - we completed successfully
3163 loading_guard.dismiss();
3164 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3165#endif
3166
3167 auto end_time = std::chrono::steady_clock::now();
3168 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
3169 end_time - start_time);
3170 LOG_DEBUG("EditorManager", "ROM assets loaded in %lld ms", duration.count());
3171
3172 return absl::OkStatus();
3173}
3174
3175absl::Status EditorManager::LoadAssetsLazy(uint64_t passed_handle) {
3176 auto* current_rom = GetCurrentRom();
3177 auto* current_editor_set = GetCurrentEditorSet();
3178 if (!current_rom || !current_editor_set) {
3179 return absl::FailedPreconditionError("No ROM or editor set loaded");
3180 }
3181
3182 auto* current_session = session_coordinator_->GetActiveRomSession();
3183 if (!current_session) {
3184 return absl::FailedPreconditionError("No active ROM session");
3185 }
3186 ResetAssetState(current_session);
3187
3188#ifdef __EMSCRIPTEN__
3189 // Use passed handle if provided, otherwise create new one
3190 auto loading_handle =
3191 passed_handle != 0
3192 ? static_cast<app::platform::WasmLoadingManager::LoadingHandle>(
3193 passed_handle)
3194 : app::platform::WasmLoadingManager::BeginLoading(
3195 "Loading ROM (lazy assets)");
3196 auto cleanup_loading = [&]() {
3197 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3198 };
3199 struct LoadingGuard {
3200 std::function<void()> cleanup;
3201 bool dismissed = false;
3202 ~LoadingGuard() {
3203 if (!dismissed) {
3204 cleanup();
3205 }
3206 }
3207 void dismiss() { dismissed = true; }
3208 } loading_guard{cleanup_loading};
3209#else
3210 (void)passed_handle; // Unused on non-WASM
3211#endif
3212
3213 // Set renderer for emulator (lazy initialization happens in Run())
3214 if (renderer_) {
3216 }
3217
3218 // Wire settings panel to right panel manager for the current session.
3220 auto* settings = current_editor_set->GetSettingsPanel();
3221 right_drawer_manager_->SetSettingsPanel(settings);
3222 }
3223
3224 // Apply user preferences to status bar
3226
3227#ifdef __EMSCRIPTEN__
3228 loading_guard.dismiss();
3229 app::platform::WasmLoadingManager::EndLoading(loading_handle);
3230#endif
3231
3232 LOG_INFO("EditorManager", "Lazy asset mode: editor assets deferred");
3233 return absl::OkStatus();
3234}
3235
3254
3258
3260 auto* current_rom = GetCurrentRom();
3261 auto* current_editor_set = GetCurrentEditorSet();
3262 if (!current_rom || !current_editor_set) {
3263 return absl::FailedPreconditionError("No ROM or editor set loaded");
3264 }
3265
3266 // --- State machine checks (delegated to RomLifecycleManager) ---
3268 return absl::CancelledError("Save pending confirmation");
3269 }
3270
3272
3274 return absl::CancelledError("Save pending confirmation");
3275 }
3276
3277 const bool pot_items_enabled =
3280 !rom_lifecycle_.ShouldSuppressPotItemSave() && pot_items_enabled) {
3281 const int loaded_rooms = current_editor_set->LoadedDungeonRoomCount();
3282 const int total_rooms = current_editor_set->TotalDungeonRoomCount();
3283 if (loaded_rooms < total_rooms) {
3284 rom_lifecycle_.SetPotItemConfirmPending(total_rooms - loaded_rooms,
3285 total_rooms);
3286 if (popup_manager_) {
3288 }
3290 absl::StrFormat(
3291 "Save paused: pot items enabled with %d unloaded rooms",
3294 return absl::CancelledError("Pot item save confirmation required");
3295 }
3296 }
3297
3298 const bool bypass_confirm = rom_lifecycle_.ShouldBypassPotItemConfirm();
3299 const bool suppress_pot_items = rom_lifecycle_.ShouldSuppressPotItemSave();
3301
3302 struct PotItemFlagGuard {
3303 bool restore = false;
3304 bool previous = false;
3305 ~PotItemFlagGuard() {
3306 if (restore) {
3307 core::FeatureFlags::get().dungeon.kSavePotItems = previous;
3308 }
3309 }
3310 } pot_item_guard;
3311
3312 if (suppress_pot_items) {
3313 pot_item_guard.previous = core::FeatureFlags::get().dungeon.kSavePotItems;
3314 pot_item_guard.restore = true;
3316 } else if (bypass_confirm) {
3317 // Explicitly allow pot item save once after confirmation.
3318 }
3319
3320 // --- Backup policy setup ---
3328 } else {
3330 user_settings_.prefs().backup_before_save, "", 20, true, 14);
3331 }
3332
3333 // --- Save editor-specific data ---
3334 if (auto* editor = current_editor_set->GetEditor(EditorType::kScreen)) {
3335 RETURN_IF_ERROR(editor->Save());
3336 }
3337
3338 if (auto* editor = current_editor_set->GetEditor(EditorType::kDungeon)) {
3339 RETURN_IF_ERROR(editor->Save());
3340 }
3341
3342 if (auto* editor = current_editor_set->GetEditor(EditorType::kOverworld)) {
3343 RETURN_IF_ERROR(editor->Save());
3344 }
3345
3346 if (core::FeatureFlags::get().kSaveMessages) {
3348 if (auto* editor = current_editor_set->GetEditor(EditorType::kMessage)) {
3349 RETURN_IF_ERROR(editor->Save());
3350 }
3351 }
3352
3353 if (core::FeatureFlags::get().kSaveGraphicsSheet) {
3355 *current_rom, gfx::Arena::Get().gfx_sheets()));
3356 }
3357
3358 // Oracle guardrails: refuse to write obviously corrupted ROM layouts.
3360
3361 // --- Write conflict check (ASM-owned address protection) ---
3365 std::vector<std::pair<uint32_t, uint32_t>> write_ranges;
3366 bool diff_computed = false;
3367
3368 if (!current_rom->filename().empty()) {
3369 std::ifstream file(current_rom->filename(), std::ios::binary);
3370 if (file.is_open()) {
3371 file.seekg(0, std::ios::end);
3372 const std::streampos end = file.tellg();
3373 if (end >= 0) {
3374 std::vector<uint8_t> disk_data(static_cast<size_t>(end));
3375 file.seekg(0, std::ios::beg);
3376 file.read(reinterpret_cast<char*>(disk_data.data()),
3377 static_cast<std::streamsize>(disk_data.size()));
3378 if (file) {
3379 diff_computed = true;
3380 auto diff = yaze::rom::ComputeDiffRanges(disk_data,
3381 current_rom->vector());
3382 if (!diff.ranges.empty()) {
3383 LOG_DEBUG("EditorManager",
3384 "ROM save diff: %zu bytes changed in %zu range(s)",
3385 diff.total_bytes_changed, diff.ranges.size());
3386 write_ranges = std::move(diff.ranges);
3387 }
3388 }
3389 }
3390 }
3391 }
3392
3393 if (write_ranges.empty() && !diff_computed) {
3394 write_ranges = current_editor_set->CollectDungeonWriteRanges();
3395 }
3396
3397 if (!write_ranges.empty()) {
3398 auto conflicts =
3400 if (!conflicts.empty()) {
3401 rom_lifecycle_.SetPendingWriteConflicts(std::move(conflicts));
3402 if (popup_manager_) {
3404 }
3406 absl::StrFormat(
3407 "Save paused: %zu write conflict(s) with ASM hooks",
3410 return absl::CancelledError("Write conflict confirmation required");
3411 }
3412 }
3413 } else {
3414 // Bypass is single-use, set by the warning popup.
3416 }
3417 }
3418
3419 // Delegate final ROM file writing to RomFileManager
3420 auto save_status = rom_file_manager_.SaveRom(current_rom);
3421 if (save_status.ok()) {
3422 // Write-confirm bypass is single-use. Clear it after a successful save.
3424 }
3425 return save_status;
3426}
3427
3428absl::Status EditorManager::SaveRomAs(const std::string& filename) {
3429 auto* current_rom = GetCurrentRom();
3430 if (!current_rom) {
3431 return absl::FailedPreconditionError("No ROM loaded");
3432 }
3433
3434 // Reuse SaveRom() logic for editor-specific data saving
3436
3437 // Now save to the new filename
3438 auto save_status = rom_file_manager_.SaveRomAs(current_rom, filename);
3439 if (save_status.ok()) {
3440 // Update session filepath
3442 auto* session = session_coordinator_->GetActiveRomSession();
3443 if (session) {
3444 session->filepath = filename;
3445 }
3446 }
3447
3448 // Add to recent files
3450 manager.AddFile(filename);
3451 manager.Save();
3452 }
3453
3454 return save_status;
3455}
3456
3457absl::Status EditorManager::OpenRomOrProject(const std::string& filename) {
3458 LOG_INFO("EditorManager", "OpenRomOrProject called with: '%s'",
3459 filename.c_str());
3460 if (filename.empty()) {
3461 LOG_INFO("EditorManager", "Empty filename provided, skipping load.");
3462 return absl::OkStatus();
3463 }
3464
3465#ifdef __EMSCRIPTEN__
3466 // Start loading indicator early for WASM builds
3467 auto loading_handle =
3468 app::platform::WasmLoadingManager::BeginLoading("Loading ROM");
3469 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3470 "Reading ROM file...");
3471 // RAII guard to ensure loading indicator is closed even on early return
3472 struct LoadingGuard {
3473 app::platform::WasmLoadingManager::LoadingHandle handle;
3474 bool dismissed = false;
3475 ~LoadingGuard() {
3476 if (!dismissed)
3477 app::platform::WasmLoadingManager::EndLoading(handle);
3478 }
3479 void dismiss() { dismissed = true; }
3480 } loading_guard{loading_handle};
3481#endif
3482
3483 if (absl::EndsWith(filename, ".yaze") ||
3484 absl::EndsWith(filename, ".zsproj") ||
3485 absl::EndsWith(filename, ".yazeproj")) {
3486 // Open the project file
3490
3491 // Initialize VersionManager for the project
3493 std::make_unique<core::VersionManager>(&current_project_);
3494 version_manager_->InitializeGit(); // Try to init git if configured
3495
3496 // Load ROM directly from project - don't prompt user
3497 return LoadProjectWithRom();
3498 } else {
3499#ifdef __EMSCRIPTEN__
3500 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.05f);
3501 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3502 "Loading ROM data...");
3503#endif
3504 Rom temp_rom;
3505 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, filename));
3507
3508 auto session_or = session_coordinator_->CreateSessionFromRom(
3509 std::move(temp_rom), filename);
3510 if (!session_or.ok()) {
3511 return session_or.status();
3512 }
3513 RomSession* session = *session_or;
3514
3518
3519 // Apply project feature flags to both session and global singleton
3522
3523 // Keep ResourceLabelProvider in sync with the active ROM session before
3524 // editors register room/sprite labels.
3526
3527 // Update test manager with current ROM for ROM-dependent tests (only when
3528 // tests are enabled)
3529#ifdef YAZE_ENABLE_TESTING
3530 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
3531 (void*)GetCurrentRom(),
3532 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
3534#endif
3535
3536 if (auto* editor_set = GetCurrentEditorSet();
3537 editor_set && !current_project_.code_folder.empty()) {
3538 const std::string absolute_code_folder =
3540 // iOS: avoid blocking the main thread during project open / scene updates.
3541 // Large iCloud-backed projects can trigger watchdog termination if we
3542 // eagerly enumerate folders here.
3543#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3544 editor_set->OpenAssemblyFolder(absolute_code_folder);
3545#endif
3546 // Also set the sidebar file browser path (refresh happens during UI draw).
3547 window_manager_.SetFileBrowserPath("Assembly", absolute_code_folder);
3548 }
3549
3550#ifdef __EMSCRIPTEN__
3551 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.10f);
3552 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3553 "Initializing editors...");
3554 // Pass the loading handle to LoadAssets and dismiss our guard
3555 // LoadAssets will manage closing the indicator when done
3556 loading_guard.dismiss();
3557 RETURN_IF_ERROR(LoadAssetsForMode(loading_handle));
3558#else
3560#endif
3561
3562 // Hide welcome screen and show editor selection when ROM is loaded
3563 ui_coordinator_->SetWelcomeScreenVisible(false);
3564 // dashboard_panel_->ClearRecentEditors();
3565 ui_coordinator_->SetEditorSelectionVisible(true);
3566
3567 // Set Dashboard category to suppress panel drawing until user selects an editor
3570 /*notify=*/false);
3571 }
3572 return absl::OkStatus();
3573}
3574
3575absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
3576 // Delegate to ProjectManager
3577 auto status = project_manager_.CreateNewProject(template_name);
3578 if (status.ok()) {
3581
3582 // Trigger ROM selection dialog - projects need a ROM to be useful
3583 // LoadRom() opens file dialog and shows ROM load options when ROM is loaded
3584 status = LoadRom();
3585#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3586 if (status.ok() && ui_coordinator_) {
3587 ui_coordinator_->SetWelcomeScreenVisible(false);
3588 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
3589 }
3590#endif
3591 }
3592 return status;
3593}
3594
3596 auto open_project_from_path =
3597 [this](const std::string& file_path) -> absl::Status {
3598 if (file_path.empty()) {
3599 return absl::OkStatus();
3600 }
3601
3602 project::YazeProject new_project;
3603 RETURN_IF_ERROR(new_project.Open(file_path));
3604
3605 // Validate project
3606 auto validation_status = new_project.Validate();
3607 if (!validation_status.ok()) {
3608 toast_manager_.Show(absl::StrFormat("Project validation failed: %s",
3609 validation_status.message()),
3611
3612 // Ask user if they want to repair
3613 popup_manager_->Show("Project Repair");
3614 }
3615
3616 current_project_ = std::move(new_project);
3618
3619 // Initialize VersionManager for the project
3621 std::make_unique<core::VersionManager>(&current_project_);
3622 version_manager_->InitializeGit();
3623
3624 return LoadProjectWithRom();
3625 };
3626
3627#if defined(__APPLE__) && TARGET_OS_IOS == 1
3628 // On iOS, route project selection through the SwiftUI overlay document picker
3629 // so we get open-in-place + security-scoped access for iCloud Drive bundles.
3630 platform::ios::PostOverlayCommand("open_project");
3631 return absl::OkStatus();
3632#else
3634 return open_project_from_path(file_path);
3635#endif
3636}
3637
3639 // Check if project has a ROM file specified
3640 if (current_project_.rom_filename.empty()) {
3641 // No ROM specified - prompt user to select one
3643 "Project has no ROM file configured. Please select a ROM.",
3645#if defined(__APPLE__) && TARGET_OS_IOS == 1
3646 // Guard: if the project lives inside a .yazeproj bundle the ROM path
3647 // defaults to bundle/rom, which may not exist yet because iCloud hasn't
3648 // finished the download. Popping the file picker here would let a
3649 // temporary path overwrite the correct bundle path in project.yaze.
3650 // Show guidance and let the user reopen the project once the download
3651 // is complete.
3652 {
3653 auto bundle_parent =
3654 std::filesystem::path(current_project_.filepath).parent_path();
3655 if (bundle_parent.extension() == ".yazeproj") {
3657 "ROM is downloading from iCloud. Reopen the project in a few "
3658 "seconds.",
3659 ToastType::kInfo, 6.0f);
3660 return absl::OkStatus();
3661 }
3662 }
3665 [this](const std::string& rom_path) {
3666 if (rom_path.empty()) {
3667 return;
3668 }
3669 current_project_.rom_filename = rom_path;
3670 auto save_status = current_project_.Save();
3671 if (!save_status.ok()) {
3673 absl::StrFormat("Failed to update project ROM: %s",
3674 save_status.message()),
3676 return;
3677 }
3678 auto status = LoadProjectWithRom();
3679 if (!status.ok()) {
3681 absl::StrFormat("Failed to load project ROM: %s",
3682 status.message()),
3684 }
3685 });
3686 return absl::OkStatus();
3687#else
3690 if (rom_path.empty()) {
3691 return absl::OkStatus();
3692 }
3693 current_project_.rom_filename = rom_path;
3694 // Save updated project
3696#endif
3697 }
3698
3699 // Load ROM from project
3700 Rom temp_rom;
3701 auto load_status =
3703 if (!load_status.ok()) {
3704 // ROM file not found or invalid - prompt user to select new ROM
3706 absl::StrFormat("Could not load ROM '%s': %s. Please select a new ROM.",
3707 current_project_.rom_filename, load_status.message()),
3709#if defined(__APPLE__) && TARGET_OS_IOS == 1
3710 // If the ROM is inside a .yazeproj bundle its path may not be readable
3711 // yet because iCloud hasn't finished downloading it. Saving any other
3712 // path here would corrupt the project file with a temporary location.
3713 // Show guidance and bail without touching current_project_.rom_filename.
3714 {
3715 auto rom_parent =
3716 std::filesystem::path(current_project_.rom_filename).parent_path();
3717 if (rom_parent.extension() == ".yazeproj") {
3719 "ROM is still downloading from iCloud. Try reopening the project "
3720 "in a moment.",
3721 ToastType::kInfo, 6.0f);
3722 return absl::OkStatus();
3723 }
3724 }
3727 [this](const std::string& rom_path) {
3728 if (rom_path.empty()) {
3729 return;
3730 }
3731 current_project_.rom_filename = rom_path;
3732 auto save_status = current_project_.Save();
3733 if (!save_status.ok()) {
3735 absl::StrFormat("Failed to update project ROM: %s",
3736 save_status.message()),
3738 return;
3739 }
3740 auto status = LoadProjectWithRom();
3741 if (!status.ok()) {
3743 absl::StrFormat("Failed to load project ROM: %s",
3744 status.message()),
3746 }
3747 });
3748 return absl::OkStatus();
3749#else
3752 if (rom_path.empty()) {
3753 return absl::OkStatus();
3754 }
3755 current_project_.rom_filename = rom_path;
3757 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, rom_path));
3758#endif
3759 }
3760
3762
3763 auto session_or = session_coordinator_->CreateSessionFromRom(
3764 std::move(temp_rom), current_project_.rom_filename);
3765 if (!session_or.ok()) {
3766 return session_or.status();
3767 }
3768 RomSession* session = *session_or;
3769
3773
3774 // Auto-enable custom object rendering when a project defines custom object
3775 // data but the stale feature flag is off.
3776 if (ProjectUsesCustomObjects(current_project_) &&
3779 LOG_WARN("EditorManager",
3780 "Project has custom object data but 'enable_custom_objects' was "
3781 "disabled. Enabling at runtime.");
3782 toast_manager_.Show("Custom object rendering auto-enabled for this project",
3784 }
3785 std::string legacy_mapping_warning;
3786 if (SeedLegacyTrackObjectMapping(&current_project_,
3787 &legacy_mapping_warning)) {
3788 LOG_WARN("EditorManager", "%s", legacy_mapping_warning.c_str());
3790 "Seeded default custom object mapping for object 0x31 (save project "
3791 "to persist)",
3793 }
3794
3795 // Apply project feature flags to both session and global singleton.
3799#if !defined(NDEBUG)
3800 LOG_INFO(
3801 "EditorManager",
3802 "Feature flags applied: kEnableCustomObjects=%s, "
3803 "custom_objects_folder='%s', custom_object_files=%zu entries",
3807#endif
3808
3814 } else {
3816 }
3821 } else {
3822 // Avoid inheriting stale singleton state from previous projects.
3824 }
3835
3836 if (auto* rom = GetCurrentRom(); rom && rom->is_loaded()) {
3837 if (IsRomHashMismatch()) {
3839 "Project ROM hash mismatch detected. Check ROM Identity settings.",
3841 }
3842 auto warnings = ValidateRomAddressOverrides(
3844 if (!warnings.empty()) {
3845 for (const auto& warning : warnings) {
3846 LOG_WARN("EditorManager", "%s", warning.c_str());
3847 }
3848 toast_manager_.Show(absl::StrFormat("ROM override warnings: %d (see log)",
3849 warnings.size()),
3851 }
3852 }
3853
3854 // Update test manager with current ROM for ROM-dependent tests (only when
3855 // tests are enabled)
3856#ifdef YAZE_ENABLE_TESTING
3857 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
3858 (void*)GetCurrentRom(),
3859 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
3861#endif
3862
3863 if (auto* editor_set = GetCurrentEditorSet();
3864 editor_set && !current_project_.code_folder.empty()) {
3865 const std::string absolute_code_folder =
3867 // iOS: avoid blocking the main thread during project open / scene updates.
3868#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3869 editor_set->OpenAssemblyFolder(absolute_code_folder);
3870#endif
3871 // Also set the sidebar file browser path (refresh happens during UI draw).
3872 window_manager_.SetFileBrowserPath("Assembly", absolute_code_folder);
3873 }
3874
3875 // Initialize labels before loading editor assets so room lists / command
3876 // palette entries resolve project registry labels on first render.
3878
3880
3881 // Hide welcome screen and show editor selection when project ROM is loaded
3882 if (ui_coordinator_) {
3883 ui_coordinator_->SetWelcomeScreenVisible(false);
3884 ui_coordinator_->SetEditorSelectionVisible(true);
3885 }
3886
3887 // Set Dashboard category to suppress panel drawing until user selects an editor
3889 /*notify=*/false);
3890
3891 // Apply workspace settings
3900 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
3901
3903
3911 project_management_panel_->SetBuildLogOutput("");
3912 }
3914 active_project_build_.reset();
3916
3917 // Publish project context for hack workflow panels and other consumers
3919
3920 // Add to recent files
3922 manager.AddFile(current_project_.filepath);
3923 manager.Save();
3924
3925 // Update project management panel with loaded project
3928 project_management_panel_->SetVersionManager(version_manager_.get());
3930 }
3931
3932 toast_manager_.Show(absl::StrFormat("Project '%s' loaded successfully",
3935
3936 return absl::OkStatus();
3937}
3938
3941 return CreateNewProject();
3942 }
3943
3944 // Update project with current settings
3947 auto* session = session_coordinator_->GetActiveRomSession();
3948 if (session) {
3949 current_project_.feature_flags = session->feature_flags;
3950 }
3951 }
3952
3961
3962 // Save recent files
3965 for (const auto& file : manager.GetRecentFiles()) {
3967 }
3968 }
3969
3970 return current_project_.Save();
3971}
3972
3974 PotItemSaveDecision decision) {
3975 // Map EditorManager enum to RomLifecycleManager enum
3976 auto lifecycle_decision =
3978 static_cast<int>(decision));
3980
3981 if (decision == PotItemSaveDecision::kCancel) {
3982 toast_manager_.Show("Save cancelled", ToastType::kInfo);
3983 return;
3984 }
3985
3986 auto status = SaveRom();
3987 if (status.ok()) {
3988 toast_manager_.Show("ROM saved successfully", ToastType::kSuccess);
3989 } else if (!absl::IsCancelled(status)) {
3991 absl::StrFormat("Failed to save ROM: %s", status.message()),
3993 }
3994}
3995
3997 // Get current project name for default filename
3998 std::string default_name = current_project_.project_opened()
4000 : "untitled_project";
4001
4002 auto file_path =
4003 util::FileDialogWrapper::ShowSaveFileDialog(default_name, "yaze");
4004 if (file_path.empty()) {
4005 return absl::OkStatus();
4006 }
4007
4008 // Ensure a project extension.
4009 if (!(absl::EndsWith(file_path, ".yaze") ||
4010 absl::EndsWith(file_path, ".yazeproj"))) {
4011 file_path += ".yaze";
4012 }
4013
4014 // Update project filepath and save
4015 std::string old_filepath = current_project_.filepath;
4016 current_project_.filepath = file_path;
4017
4018 auto save_status = current_project_.Save();
4019 if (save_status.ok()) {
4021
4022 // Add to recent files
4024 manager.AddFile(file_path);
4025 manager.Save();
4026
4027 toast_manager_.Show(absl::StrFormat("Project saved as: %s", file_path),
4029 } else {
4030 // Restore old filepath on failure
4031 current_project_.filepath = old_filepath;
4033 absl::StrFormat("Failed to save project: %s", save_status.message()),
4035 }
4036
4037 return save_status;
4038}
4039
4040absl::StatusOr<std::string> EditorManager::ResolveProjectBuildCommand() const {
4042 return absl::FailedPreconditionError("No project open");
4043 }
4044
4045 std::string command = current_project_.build_script;
4046 if (command.empty() && current_project_.hack_manifest.loaded()) {
4048 }
4049 if (command.empty()) {
4050 return absl::NotFoundError("Project does not define a build command");
4051 }
4052 return command;
4053}
4054
4055absl::StatusOr<std::string> EditorManager::ResolveProjectRunTarget() const {
4057 return absl::FailedPreconditionError("No project open");
4058 }
4059
4060 std::string target;
4063 }
4064 if (target.empty()) {
4066 }
4067 if (target.empty()) {
4068 return absl::NotFoundError("Project does not define a run target ROM");
4069 }
4070 return current_project_.GetAbsolutePath(target);
4071}
4072
4074 const std::string& summary, const std::string& detail,
4075 ProjectWorkflowState state, const std::string& output_tail,
4076 bool can_cancel) const {
4077 return {.visible = true,
4078 .can_cancel = can_cancel,
4079 .label = "Build",
4080 .summary = summary,
4081 .detail = detail,
4082 .output_tail = output_tail,
4083 .state = state};
4084}
4085
4087 const std::string& summary, const std::string& detail,
4088 ProjectWorkflowState state) const {
4089 return {.visible = true,
4090 .label = "Run",
4091 .summary = summary,
4092 .detail = detail,
4093 .state = state};
4094}
4095
4096absl::StatusOr<std::string> EditorManager::RunProjectBuildCommand() {
4097 auto command_or = ResolveProjectBuildCommand();
4098 if (!command_or.ok()) {
4099 return command_or.status();
4100 }
4101
4102 const std::string command = *command_or;
4103 const std::filesystem::path project_root =
4104 std::filesystem::path(current_project_.filepath).parent_path();
4106 auto start_status = task.Start(command, project_root.string());
4107 if (!start_status.ok()) {
4108 return start_status;
4109 }
4110 auto wait_status = task.Wait();
4111 const auto snapshot = task.GetSnapshot();
4112 if (!wait_status.ok()) {
4113 return wait_status;
4114 }
4115 const std::string summary = LastNonEmptyLine(snapshot.output);
4116 return summary.empty() ? command : summary;
4117}
4118
4120 const std::string running_detail =
4121 "Running the configured project build command";
4122 UpdateBuildWorkflowStatus(
4124 MakeBuildStatus("Build running", running_detail,
4125 ProjectWorkflowState::kRunning, "", false));
4126
4127 auto result_or = RunProjectBuildCommand();
4128 if (!result_or.ok()) {
4129 UpdateBuildWorkflowStatus(
4131 MakeBuildStatus("Build failed",
4132 std::string(result_or.status().message()),
4135 absl::StrFormat("Build unavailable: %s", result_or.status().message()),
4137 return result_or.status();
4138 }
4139
4140 const std::string summary = *result_or;
4141 UpdateBuildWorkflowStatus(&status_bar_, project_management_panel_.get(),
4142 MakeBuildStatus("Build succeeded", summary,
4145 summary.empty() ? "Project build completed"
4146 : absl::StrFormat("Project build completed: %s", summary),
4148 LOG_INFO("EditorManager", "Project build completed: %s", summary.c_str());
4149 return absl::OkStatus();
4150}
4151
4154 const auto snapshot = active_project_build_->GetSnapshot();
4155 if (snapshot.running) {
4156 toast_manager_.Show("A project build is already running",
4158 return;
4159 }
4160 }
4161
4162 auto command_or = ResolveProjectBuildCommand();
4163 if (!command_or.ok()) {
4164 UpdateBuildWorkflowStatus(
4166 MakeBuildStatus("Build unavailable",
4167 std::string(command_or.status().message()),
4170 absl::StrFormat("Build unavailable: %s", command_or.status().message()),
4172 return;
4173 }
4174
4175 const std::filesystem::path project_root =
4176 std::filesystem::path(current_project_.filepath).parent_path();
4177 active_project_build_ = std::make_unique<BackgroundCommandTask>();
4178 auto start_status =
4179 active_project_build_->Start(*command_or, project_root.string());
4180 if (!start_status.ok()) {
4181 UpdateBuildWorkflowStatus(
4183 MakeBuildStatus("Build unavailable",
4184 std::string(start_status.message()),
4187 absl::StrFormat("Build unavailable: %s", start_status.message()),
4189 active_project_build_.reset();
4190 return;
4191 }
4192
4194 UpdateBuildWorkflowStatus(
4196 MakeBuildStatus("Build running",
4197 "Running the configured project build command",
4200 project_management_panel_->SetBuildLogOutput("");
4201 }
4203}
4204
4206 if (!active_project_build_) {
4207 return;
4208 }
4209
4210 const auto snapshot = active_project_build_->GetSnapshot();
4211 if (!snapshot.running) {
4212 return;
4213 }
4214
4215 active_project_build_->Cancel();
4216 UpdateBuildWorkflowStatus(
4218 MakeBuildStatus("Cancelling build", "Stopping the active project build",
4219 ProjectWorkflowState::kRunning, snapshot.output_tail,
4220 false));
4221}
4222
4224 UpdateRunWorkflowStatus(
4226 MakeRunStatus("Run queued",
4227 "Preparing the project output for emulator reload",
4229
4230 auto run_target_or = ResolveProjectRunTarget();
4231 if (!run_target_or.ok()) {
4232 UpdateRunWorkflowStatus(
4234 MakeRunStatus("Run unavailable",
4235 std::string(run_target_or.status().message()),
4237 toast_manager_.Show(absl::StrFormat("Run unavailable: %s",
4238 run_target_or.status().message()),
4240 return run_target_or.status();
4241 }
4242 const std::string run_target = *run_target_or;
4243 if (!std::filesystem::exists(run_target)) {
4244 UpdateRunWorkflowStatus(&status_bar_, project_management_panel_.get(),
4245 MakeRunStatus("Run target missing", run_target,
4247 toast_manager_.Show("Run target ROM not found. Build the project first.",
4249 return absl::NotFoundError("Run target ROM not found");
4250 }
4251
4252#ifdef YAZE_WITH_GRPC
4253 if (auto* emulator_backend = Application::Instance().GetEmulatorBackend()) {
4254 auto load_status = emulator_backend->LoadRom(run_target);
4255 if (load_status.ok()) {
4256 AppendWorkflowHistoryEntry(
4257 "Run",
4258 MakeRunStatus("Reloaded in backend", run_target,
4260 "");
4261 UpdateRunWorkflowStatus(&status_bar_, project_management_panel_.get(),
4262 MakeRunStatus("Reloaded in backend", run_target,
4264 if (ui_coordinator_) {
4265 ui_coordinator_->SetEmulatorVisible(true);
4266 }
4268 absl::StrFormat("Reloaded project output in emulator backend: %s",
4269 run_target),
4271 return absl::OkStatus();
4272 }
4273 }
4274#endif
4275
4276 Rom temp_rom;
4277 auto load_rom_status = rom_file_manager_.LoadRom(&temp_rom, run_target);
4278 if (!load_rom_status.ok()) {
4279 UpdateRunWorkflowStatus(
4281 MakeRunStatus("Run load failed", std::string(load_rom_status.message()),
4283 toast_manager_.Show(absl::StrFormat("Failed to load run target ROM: %s",
4284 load_rom_status.message()),
4286 return load_rom_status;
4287 }
4288
4289 auto reload_status = emulator_.ReloadRuntimeRom(temp_rom.vector());
4290 if (!reload_status.ok()) {
4291 UpdateRunWorkflowStatus(
4293 MakeRunStatus("Reload failed", std::string(reload_status.message()),
4295 toast_manager_.Show(absl::StrFormat("Failed to reload emulator runtime: %s",
4296 reload_status.message()),
4298 return reload_status;
4299 }
4300
4301 if (ui_coordinator_) {
4302 ui_coordinator_->SetEmulatorVisible(true);
4303 }
4304 AppendWorkflowHistoryEntry("Run",
4305 MakeRunStatus("Reloaded in emulator", run_target,
4307 "");
4308 UpdateRunWorkflowStatus(&status_bar_, project_management_panel_.get(),
4309 MakeRunStatus("Reloaded in emulator", run_target,
4312 absl::StrFormat("Reloaded project output in emulator: %s", run_target),
4314 return absl::OkStatus();
4315}
4316
4317absl::Status EditorManager::ImportProject(const std::string& project_path) {
4318 // Delegate to ProjectManager for import logic
4320 // Sync local project reference
4324 return absl::OkStatus();
4325}
4326
4329 return absl::FailedPreconditionError("No project is currently open");
4330 }
4331
4333 toast_manager_.Show("Project repaired successfully",
4335
4336 return absl::OkStatus();
4337}
4338
4340 if (auto* editor_set = GetCurrentEditorSet()) {
4341 return editor_set->GetOverworldData();
4342 }
4343 return nullptr;
4344}
4345
4347 if (!rom) {
4348 return absl::InvalidArgumentError("Invalid ROM pointer");
4349 }
4350
4351 // We need to find the session that owns this ROM.
4352 // This is inefficient but SetCurrentRom is rare.
4354 for (size_t i = 0; i < session_coordinator_->GetTotalSessionCount(); ++i) {
4355 auto* session =
4356 static_cast<RomSession*>(session_coordinator_->GetSession(i));
4357 if (session && &session->rom == rom) {
4358 session_coordinator_->SwitchToSession(i);
4359 // Update test manager with current ROM for ROM-dependent tests
4363 return absl::OkStatus();
4364 }
4365 }
4366 }
4367 // If ROM wasn't found in existing sessions, treat as new session.
4368 // Copying an external ROM object is avoided; instead, fail.
4369 return absl::NotFoundError("ROM not found in existing sessions");
4370}
4371
4376
4380
4381// IsRomHashMismatch() is now inline in editor_manager.h delegating to
4382// rom_lifecycle_.
4383
4384std::vector<RomFileManager::BackupEntry> EditorManager::GetRomBackups() const {
4386}
4387
4388absl::Status EditorManager::RestoreRomBackup(const std::string& backup_path) {
4389 auto* rom = GetCurrentRom();
4390 if (!rom) {
4391 return absl::FailedPreconditionError("No ROM loaded");
4392 }
4393 const std::string original_filename = rom->filename();
4394 RETURN_IF_ERROR(rom_file_manager_.LoadRom(rom, backup_path));
4395 if (!original_filename.empty()) {
4396 rom->set_filename(original_filename);
4397 }
4398
4400 if (auto* session = session_coordinator_->GetActiveRomSession()) {
4401 ResetAssetState(session);
4402 }
4403 }
4405 return LoadAssetsForMode();
4406}
4407
4411
4412// ConfirmRomWrite() and CancelRomWriteConfirm() are now inline in
4413// editor_manager.h delegating to rom_lifecycle_.
4414
4417 session_coordinator_->CreateNewSession();
4418 // Toast messages are now shown by SessionCoordinator
4419 }
4420}
4421
4424 session_coordinator_->DuplicateCurrentSession();
4425 }
4426}
4427
4430 session_coordinator_->CloseCurrentSession();
4431 }
4432}
4433
4436 session_coordinator_->RemoveSession(index);
4437 }
4438}
4439
4442 // Delegate to SessionCoordinator - cross-cutting concerns
4443 // are handled by OnSessionSwitched() observer callback
4444 session_coordinator_->SwitchToSession(index);
4445 }
4446}
4447
4449 return session_coordinator_ ? session_coordinator_->GetActiveSessionIndex()
4450 : 0;
4451}
4452
4454 UiSyncState state;
4455 state.frame_id = ui_sync_frame_id_.load(std::memory_order_relaxed);
4456
4457 int pending_editor =
4458 pending_editor_deferred_actions_.load(std::memory_order_relaxed);
4459 if (pending_editor < 0) {
4460 pending_editor = 0;
4461 }
4462 state.pending_editor_actions = pending_editor;
4463
4464 int pending_layout = layout_coordinator_.PendingDeferredActionCount();
4465 if (pending_layout < 0) {
4466 pending_layout = 0;
4467 }
4468 state.pending_layout_actions = pending_layout;
4470 layout_manager_ ? layout_manager_->IsRebuildRequested() : false;
4471 return state;
4472}
4473
4475 return session_coordinator_ ? session_coordinator_->GetActiveSessionCount()
4476 : 0;
4477}
4478
4480 EditorType type, size_t session_index) const {
4481 const char* base_name = kEditorNames[static_cast<int>(type)];
4482 return session_coordinator_ ? session_coordinator_->GenerateUniqueEditorTitle(
4483 base_name, session_index)
4484 : std::string(base_name);
4485}
4486
4487void EditorManager::SwitchToEditor(EditorType editor_type, bool force_visible,
4488 bool from_dialog) {
4489 // Special case: Agent editor requires EditorManager-specific handling
4490#ifdef YAZE_BUILD_AGENT_UI
4491 if (editor_type == EditorType::kAgent) {
4492 ShowAIAgent();
4493 return;
4494 }
4495#endif
4496
4497 auto status = EnsureEditorAssetsLoaded(editor_type);
4498 if (!status.ok()) {
4500 absl::StrFormat("Failed to prepare %s: %s",
4501 kEditorNames[static_cast<int>(editor_type)],
4502 status.message()),
4504 }
4505
4506 // Delegate all other editor switching to EditorActivator
4507 editor_activator_.SwitchToEditor(editor_type, force_visible, from_dialog);
4508}
4509
4511 if (!ui_coordinator_) {
4512 return;
4513 }
4514 ui_coordinator_->SetEditorSelectionVisible(false);
4515 ui_coordinator_->SetStartupSurface(StartupSurface::kEditor);
4516}
4517
4519 if (!session)
4520 return;
4522 ConfigureEditorDependencies(&session->editors, &session->rom,
4523 session->editors.session_id());
4524}
4525
4526// SessionScope implementation
4528 size_t session_id)
4529 : manager_(manager),
4530 prev_rom_(manager->GetCurrentRom()),
4531 prev_editor_set_(manager->GetCurrentEditorSet()),
4532 prev_session_id_(manager->GetCurrentSessionId()) {
4533 // Set new session context
4534 manager_->session_coordinator_->SwitchToSession(session_id);
4535}
4536
4538 // Restore previous context
4539 manager_->session_coordinator_->SwitchToSession(prev_session_id_);
4540}
4541
4542bool EditorManager::HasDuplicateSession(const std::string& filepath) {
4543 return session_coordinator_ &&
4544 session_coordinator_->HasDuplicateSession(filepath);
4545}
4546
4573 // Update project panel context before showing
4576 project_management_panel_->SetVersionManager(version_manager_.get());
4578 }
4579 right_drawer_manager_->ToggleDrawer(
4581 }
4582}
4583
4585 // Load the current project file into the editor
4586 if (!current_project_.filepath.empty()) {
4588 if (!status.ok()) {
4590 absl::StrFormat("Failed to load project file: %s", status.message()),
4592 return;
4593 }
4594 }
4595 // Set the project pointer for label import functionality
4597 // Activate the editor window
4599}
4600
4602 size_t session_id) {
4603 if (!editor_set) {
4604 return;
4605 }
4606
4607 EditorDependencies deps;
4608 deps.rom = rom;
4609 deps.session_id = session_id;
4612 deps.popup_manager = popup_manager_.get();
4616 deps.project = &current_project_;
4617 deps.version_manager = version_manager_.get();
4618 deps.global_context = editor_context_.get();
4619 deps.status_bar = &status_bar_;
4620 deps.renderer = renderer_;
4621 deps.emulator = &emulator_;
4622 deps.custom_data = this;
4623 deps.gfx_group_workspace = editor_set->gfx_group_workspace();
4624
4625 editor_set->ApplyDependencies(deps);
4626
4627 // If configuring the active session, update the properties panel
4628 if (session_id == GetCurrentSessionId()) {
4630 }
4631}
4632
4633} // namespace yaze::editor
static Application & Instance()
void Publish(const T &event)
Definition event_bus.h:35
HandlerId Subscribe(std::function< void(const T &)> handler)
Definition event_bus.h:22
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
project::ResourceLabelManager * resource_label()
Definition rom.h:150
auto filename() const
Definition rom.h:145
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:408
const auto & vector() const
Definition rom.h:143
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool dirty() const
Definition rom.h:133
auto set_filename(std::string_view name)
Definition rom.h:146
bool is_loaded() const
Definition rom.h:132
static TimingManager & Get()
Definition timing.h:20
float Update()
Update the timing manager (call once per frame)
Definition timing.h:29
float GetElapsedTime() const
Get total elapsed time since first update.
Definition timing.h:70
static void Initialize(editor::EditorManager *)
static void Initialize(editor::EditorManager *)
static Flags & get()
Definition features.h:118
const ProjectRegistry & project_registry() const
bool HasProjectRegistry() const
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
bool loaded() const
Check if the manifest has been loaded.
const BuildPipeline & build_pipeline() const
static RomSettings & Get()
void SetAddressOverrides(const RomAddressOverrides &overrides)
void set_active(bool active)
Definition agent_chat.h:71
AgentConfigState & agent_config()
void SetProjectContext(project::YazeProject *project)
void ApplyUserSettingsDefaults(bool force=false)
void SetAssemblySymbolTableContext(const std::map< std::string, core::AsarSymbol > *table)
void Initialize(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, RightDrawerManager *right_drawer_manager, WorkspaceWindowManager *window_manager, UserSettings *user_settings)
void SetAsarWrapperContext(core::AsarWrapper *asar_wrapper)
absl::Status Start(const std::string &command, const std::string &directory)
DungeonEditorV2 - Simplified dungeon editor using component delegation.
void SwitchToEditor(EditorType type, bool force_visible=false, bool from_dialog=false)
Switch to an editor, optionally forcing visibility.
void Initialize(const Dependencies &deps)
SessionScope(EditorManager *manager, size_t session_id)
The EditorManager controls the main editor window and manages the various editor classes.
std::unique_ptr< SessionCoordinator > session_coordinator_
RomLifecycleManager rom_lifecycle_
StartupVisibility welcome_mode_override_
std::vector< EditorType > CollectEditorsToPreload(EditorSet *editor_set) const
void SwitchToEditor(EditorType editor_type, bool force_visible=false, bool from_dialog=false) override
std::unique_ptr< GlobalEditorContext > editor_context_
absl::Status SaveRomAs(const std::string &filename)
project::YazeProject current_project_
std::unique_ptr< RightDrawerManager > right_drawer_manager_
void SetCurrentEditor(Editor *editor) override
bool SaveLayoutSnapshotAs(const std::string &name)
absl::Status LoadAssetsForMode(uint64_t loading_handle=0)
std::string GetPreferredStartupCategory(const std::string &saved_category, const std::vector< std::string > &available_categories) const
absl::StatusOr< std::string > RunProjectBuildCommand()
void SwitchToSession(size_t index)
absl::Status RestoreRomBackup(const std::string &backup_path)
Rom * GetCurrentRom() const override
bool HasDuplicateSession(const std::string &filepath)
std::unique_ptr< LayoutManager > layout_manager_
std::unique_ptr< DashboardPanel > dashboard_panel_
void HandleSessionClosed(size_t index)
void ResetAssetState(RomSession *session)
void ProcessStartupActions(const AppConfig &config)
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const
std::vector< editor::RomFileManager::BackupEntry > GetRomBackups() const
absl::Status CheckRomWritePolicy()
Save the current ROM file.
std::vector< std::string > ListLayoutSnapshots() const
SharedClipboard shared_clipboard_
bool EditorInitRequiresGameData(EditorType type) const
void SyncEditorContextForCategory(const std::string &category)
void ShowProjectManagement()
Injects dependencies into all editors within an EditorSet.
Editor * ResolveEditorForCategory(const std::string &category)
void Initialize(gfx::IRenderer *renderer, const std::string &filename="")
void MarkEditorLoaded(RomSession *session, EditorType type)
absl::Status CreateNewProject(const std::string &template_name="Basic ROM Hack")
absl::Status InitializeEditorForType(EditorType type, EditorSet *editor_set, Rom *rom)
LayoutCoordinator layout_coordinator_
absl::Status LoadAssets(uint64_t loading_handle=0)
auto GetCurrentEditorSet() const -> EditorSet *
std::unique_ptr< MenuOrchestrator > menu_orchestrator_
void HandleUIActionRequest(UIActionRequestEvent::Action action)
void ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
void HandleSessionRomLoaded(size_t index, Rom *rom)
ProjectFileEditor project_file_editor_
void HandleHostVisibilityChanged(bool visible)
void ApplyLayoutPreset(const std::string &preset_name)
void RestoreTemporaryLayoutSnapshot(bool clear_after_restore=false)
void ApplyStartupVisibility(const AppConfig &config)
WorkspaceWindowManager window_manager_
auto GetCurrentEditor() const -> Editor *
void SetStartupLoadHints(const AppConfig &config)
absl::Status LoadAssetsLazy(uint64_t loading_handle=0)
absl::StatusOr< std::string > ResolveProjectBuildCommand() const
void SetAssetLoadMode(AssetLoadMode mode)
void DismissEditorSelection() override
std::atomic< uint64_t > ui_sync_frame_id_
bool ApplyLayoutProfile(const std::string &profile_id)
std::unique_ptr< core::VersionManager > version_manager_
Editor * GetEditorByType(EditorType type, EditorSet *editor_set) const
StartupVisibility sidebar_mode_override_
RomLoadOptionsDialog rom_load_options_dialog_
absl::Status LoadRom()
Load a ROM file into a new or existing session.
std::vector< std::string > startup_panel_hints_
std::vector< std::function< void()> > deferred_actions_
void OpenEditorAndPanelsFromFlags(const std::string &editor_name, const std::string &panels_str)
StartupVisibility dashboard_mode_override_
static bool IsPanelBasedEditor(EditorType type)
absl::Status Update()
Main update loop for the editor application.
absl::StatusOr< std::string > ResolveProjectRunTarget() const
absl::Status ImportProject(const std::string &project_path)
ProjectWorkflowStatus MakeRunStatus(const std::string &summary, const std::string &detail, ProjectWorkflowState state) const
void QueueDeferredAction(std::function< void()> action)
SelectionPropertiesPanel selection_properties_panel_
bool DeleteLayoutSnapshot(const std::string &name)
void ConfigureSession(RomSession *session) override
std::unique_ptr< ActivityBar > activity_bar_
ShortcutManager shortcut_manager_
bool RestoreLayoutSnapshot(const std::string &name, bool remove_after_restore=false)
void MarkEditorInitialized(RomSession *session, EditorType type)
std::unique_ptr< BackgroundCommandTask > active_project_build_
WorkspaceManager workspace_manager_
yaze::zelda3::Overworld * overworld() const
void ConfigureEditorDependencies(EditorSet *editor_set, Rom *rom, size_t session_id)
std::unique_ptr< ProjectManagementPanel > project_management_panel_
std::unique_ptr< workflow::HackWorkflowBackend > hack_workflow_backend_
absl::Status OpenRomOrProject(const std::string &filename)
void RemoveSession(size_t index)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
UiSyncState GetUiSyncStateSnapshot() const
ProjectWorkflowStatus MakeBuildStatus(const std::string &summary, const std::string &detail, ProjectWorkflowState state, const std::string &output_tail="", bool can_cancel=false) const
void HandleSessionSwitched(size_t new_index, RomSession *session)
absl::Status EnsureEditorAssetsLoaded(EditorType type)
std::unique_ptr< PopupManager > popup_manager_
std::atomic< int > pending_editor_deferred_actions_
std::unique_ptr< UICoordinator > ui_coordinator_
EditorActivator editor_activator_
absl::Status SetCurrentRom(Rom *rom)
std::unique_ptr< WindowHost > window_host_
void HandleSessionCreated(size_t index, RomSession *session)
bool EditorRequiresGameData(EditorType type) const
static bool UpdateAllowedWithoutLoadedRom(EditorType type)
static EditorType GetEditorTypeFromCategory(const std::string &category)
static std::vector< std::string > GetAllEditorCategories()
Get all editor categories in display order for sidebar.
static bool IsPanelBasedEditor(EditorType type)
static std::string GetEditorName(EditorType type)
static std::string GetEditorCategory(EditorType type)
Contains a complete set of editors for a single ROM instance.
size_t session_id() const
void ApplyDependencies(const EditorDependencies &dependencies)
Editor * GetEditor(EditorType type) const
void set_user_settings(UserSettings *settings)
GfxGroupWorkspaceState * gfx_group_workspace()
SettingsPanel * GetSettingsPanel() const
std::vector< Editor * > active_editors_
Interface for editor classes.
Definition editor.h:240
virtual void ContributeStatus(StatusBar *)
Definition editor.h:291
virtual absl::Status Redo()=0
EditorType type() const
Definition editor.h:293
virtual absl::Status Undo()=0
void ProcessDeferredActions()
Process all queued deferred actions.
void ResetWorkspaceLayout()
Reset the workspace layout to defaults.
void ProcessLayoutRebuild(EditorType current_editor_type, bool is_emulator_visible)
Process pending layout rebuild requests.
void InitializeEditorLayout(EditorType type)
Initialize layout for an editor type on first activation.
void ResetCurrentEditorLayout(EditorType editor_type, size_t session_id)
Reset current editor layout to its default configuration.
int PendingDeferredActionCount() const
Approximate pending deferred layout actions for sync diagnostics.
void ApplyLayoutPreset(const std::string &preset_name, size_t session_id)
Apply a named layout preset.
void Initialize(const Dependencies &deps)
Initialize with all dependencies.
Centralized definition of default layouts per editor.
static std::vector< std::string > GetDefaultWindows(EditorType type)
absl::Status LoadFile(const std::string &filepath)
Load a project file into the editor.
void SetProject(project::YazeProject *project)
Set the project pointer for label import operations.
void set_active(bool active)
Set whether the editor window is active.
void SetToastManager(ToastManager *toast_manager)
Set toast manager for notifications.
Panel for managing project settings, ROM versions, and snapshots.
void SetBuildStatus(const ProjectWorkflowStatus &status)
void SetRunStatus(const ProjectWorkflowStatus &status)
absl::Status FinalizeProjectCreation(const std::string &project_name, const std::string &project_path)
Complete project creation after ROM is loaded.
absl::Status SetProjectRom(const std::string &rom_path)
Set the ROM for the current project.
absl::Status CreateNewProject(const std::string &template_name="")
absl::Status ImportProject(const std::string &project_path)
project::YazeProject & GetCurrentProject()
static float GetDefaultDrawerWidth(DrawerType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific drawer type.
void SetBackupRetentionCount(int count)
void SetBackupBeforeSave(bool enabled)
void SetBackupFolder(const std::string &folder)
absl::Status LoadRom(Rom *rom, const std::string &filename)
absl::Status SaveRom(Rom *rom)
void SetBackupKeepDaily(bool enabled)
absl::Status SaveRomAs(Rom *rom, const std::string &filename)
void UpdateCurrentRomHash(Rom *rom)
Recompute the hash of the current ROM.
std::vector< RomFileManager::BackupEntry > GetRomBackups(Rom *rom) const
void SetPotItemConfirmPending(int unloaded_rooms, int total_rooms)
Set pot-item confirmation pending (called by SaveRom when needed).
absl::Status CheckRomOpenPolicy(Rom *rom)
Validate that the loaded ROM is a safe project target to open/edit.
PotItemSaveDecision ResolvePotItemSaveConfirmation(PotItemSaveDecision decision)
const std::vector< core::WriteConflict > & pending_write_conflicts() const
void SetPendingWriteConflicts(std::vector< core::WriteConflict > conflicts)
absl::Status CheckRomWritePolicy(Rom *rom)
Enforce project write policy; may set pending_rom_write_confirm.
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
Run Oracle-specific ROM safety preflight before saving.
void ApplyDefaultBackupPolicy(bool enabled, const std::string &folder, int retention_count, bool keep_daily, int keep_daily_days)
Apply default backup policy from user settings.
void Open(Rom *rom, const std::string &rom_filename)
Open the dialog after ROM detection.
void SetConfirmCallback(std::function< void(const LoadOptions &)> callback)
Set callback for when options are confirmed.
void Draw(bool *p_open)
Draw the dialog (wrapper around Show)
void SetAgentCallbacks(std::function< void(const std::string &)> send_callback, std::function< void()> focus_callback)
A session-aware status bar displayed at the bottom of the application.
Definition status_bar.h:54
void SetSessionInfo(size_t session_id, size_t total_sessions)
Set session information.
void SetBuildStatus(const ProjectWorkflowStatus &status)
Definition status_bar.h:174
void SetRunStatus(const ProjectWorkflowStatus &status)
Definition status_bar.h:177
void SetRom(Rom *rom)
Set the current ROM for dirty status and filename display.
Definition status_bar.h:74
void SetEnabled(bool enabled)
Enable or disable the status bar.
Definition status_bar.h:68
void Initialize(GlobalEditorContext *context)
Definition status_bar.cc:90
void ClearEditorContributions()
Clear frame-scoped editor contributions.
void Draw()
Draw the status bar.
void SetAgentToggleCallback(std::function< void()> callback)
Definition status_bar.h:182
void SetAgentInfo(const std::string &provider, const std::string &model, bool active)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
bool ApplyPanelLayoutDefaultsRevision(int target_revision)
static constexpr int kLatestPanelLayoutDefaultsRevision
void SetNewProjectCallback(std::function< void()> callback)
Set callback for creating new project.
void SetOpenAgentCallback(std::function< void()> callback)
Set callback for opening AI Agent.
void SetOpenAssemblyEditorNoRomCallback(std::function< void()> callback)
Open the assembly editor for file/folder work (no ROM required).
void SetOpenRomCallback(std::function< void()> callback)
Set callback for opening ROM.
void SetOpenPrototypeResearchCallback(std::function< void()> callback)
Open the graphics editor focused on prototype research (no ROM).
void SetOpenProjectDialogCallback(std::function< void()> callback)
Set callback for opening the project file dialog.
void SetOpenProjectManagementCallback(std::function< void()> callback)
Set callback for showing project management.
void SetOpenProjectFileEditorCallback(std::function< void()> callback)
Set callback for showing the project file editor.
void SetNewProjectWithTemplateCallback(std::function< void(const std::string &)> callback)
Set callback for creating project with template.
void SetOpenProjectCallback(std::function< void(const std::string &)> callback)
Set callback for opening project.
void set_apply_preset_callback(std::function< void(const std::string &)> callback)
void set_apply_preset_callback(std::function< void(const std::string &)> callback)
void set_window_manager(WorkspaceWindowManager *manager)
void set_layout_manager(LayoutManager *manager)
void EnableFileBrowser(const std::string &category, const std::string &root_path="")
void SetSidebarVisible(bool visible, bool notify=true)
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void SetEditorResolver(std::function< Editor *(const std::string &)> resolver)
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
void SetPanelBrowserCategoryWidthChangedCallback(std::function< void(float)> cb)
const WindowDescriptor * GetWindowDescriptor(size_t session_id, const std::string &base_window_id) const
const std::unordered_map< std::string, WindowDescriptor > & GetAllWindowDescriptors() const
Get all panel descriptors (for layout designer, panel browser, etc.)
void SetActiveCategory(const std::string &category, bool notify=true)
void SetSidebarStateChangedCallback(std::function< void(bool, bool)> cb)
void RegisterRegistryWindowContent(std::unique_ptr< WindowContent > window)
Register a ContentRegistry-managed WindowContent instance.
void RegisterWindowContent(std::unique_ptr< WindowContent > window)
Register a WindowContent instance for central drawing.
static constexpr const char * kDashboardCategory
std::vector< std::string > GetVisibleWindowIds(size_t session_id) const
Get list of currently visible panel IDs for a session.
void RegisterWindow(size_t session_id, const WindowDescriptor &descriptor)
void SetStoredSidePanelWidth(float width, bool notify=false)
void SetCategoryChangedCallback(std::function< void(const std::string &)> cb)
void SetFileBrowserPath(const std::string &category, const std::string &path)
void SetOnWindowCategorySelectedCallback(std::function< void(const std::string &)> callback)
void SetOnWindowClickedCallback(std::function< void(const std::string &)> callback)
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void SetSidePanelWidthChangedCallback(std::function< void(float)> cb)
void SetWindowBrowserCategoryWidth(float width, bool notify=true)
void RegisterRegistryWindowContentsForSession(size_t session_id)
Register descriptors for all registry window contents in a session.
void SetFileClickedCallback(std::function< void(const std::string &category, const std::string &path)> callback)
void SetSidebarExpanded(bool expanded, bool notify=true)
bool * GetWindowVisibilityFlag(size_t session_id, const std::string &base_window_id)
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
void MarkWindowRecentlyUsed(const std::string &window_id)
void set_window_manager(editor::WorkspaceWindowManager *manager)
Definition emulator.h:51
bool is_audio_focus_mode() const
Definition emulator.h:111
void set_renderer(gfx::IRenderer *renderer)
Definition emulator.h:100
bool is_snes_initialized() const
Definition emulator.h:129
void Run(Rom *rom)
Definition emulator.cc:462
auto running() const -> bool
Definition emulator.h:61
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
Definition emulator.cc:258
auto mutable_gfx_sheets()
Get mutable reference to all graphics sheets.
Definition arena.h:178
static Arena & Get()
Definition arena.cc:21
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
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 MotionProfile ClampMotionProfile(int raw_profile)
Definition animator.cc:110
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
Definition animator.cc:120
void ClearWorkspaceTransitionState()
Definition animator.cc:89
RAII guard for ImGui style colors.
Definition style_guard.h:27
void ApplyTheme(const std::string &theme_name)
void SetOnThemeChangedCallback(ThemeChangedCallback callback)
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:425
void SetCurrentRom(Rom *rom)
void DrawTestDashboard(bool *show_dashboard=nullptr)
static TestManager & Get()
static void ShowOpenFileDialogAsync(const FileDialogOptions &options, std::function< void(const std::string &)> callback)
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...
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
static const std::vector< std::string > & DefaultSubtypeFilenamesForObject(int object_id)
void SetObjectFileMap(const std::unordered_map< int, std::vector< std::string > > &map)
static CustomObjectManager & Get()
void Initialize(const std::string &custom_objects_folder)
static DrawRoutineRegistry & Get()
Represents the full Overworld data, light and dark world.
Definition overworld.h:261
std::unordered_map< std::string, LabelMap > ProjectLabels
int main(int argc, char **argv)
Definition emu.cc:43
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_STOP
Definition icons.h:1862
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_AUDIOTRACK
Definition icons.h:213
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_TERMINAL
Definition icons.h:1951
#define ICON_MD_DASHBOARD
Definition icons.h:517
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_MENU
Definition icons.h:1196
#define ICON_MD_SPORTS_ESPORTS
Definition icons.h:1826
#define ICON_MD_AUDIO_FILE
Definition icons.h:212
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define ICON_MD_MENU_OPEN
Definition icons.h:1198
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define PRINT_IF_ERROR(expression)
Definition macro.h:28
constexpr char kOverworldExpandedPtrHigh[]
constexpr char kOverworldEntrancePosExpanded[]
constexpr char kExpandedMusicHook[]
constexpr char kExpandedMusicMain[]
constexpr char kOverworldExpandedPtrMagic[]
constexpr char kExpandedMessageEnd[]
constexpr char kOverworldExpandedPtrMarker[]
constexpr char kOverworldEntranceMapExpanded[]
constexpr char kOverworldEntranceFlagExpanded[]
constexpr char kExpandedMessageStart[]
constexpr char kOverworldEntranceIdExpanded[]
constexpr char kOverworldExpandedPtrLow[]
constexpr char kExpandedMusicAux[]
void SetGlobalContext(GlobalEditorContext *ctx)
void SetShowWorkflowOutputCallback(std::function< void()> callback)
void SetEditorWindowContext(const std::string &category, Editor *editor)
void SetEventBus(::yaze::EventBus *bus)
Set the current EventBus instance.
void SetRom(Rom *rom)
Set the current ROM instance.
void SetGameData(::yaze::zelda3::GameData *data)
Set the current game data instance.
void AppendWorkflowHistory(const ProjectWorkflowHistoryEntry &entry)
void SetRunProjectWorkflowCallback(std::function< void()> callback)
void SetBuildWorkflowStatus(const ProjectWorkflowStatus &status)
void SetCancelBuildWorkflowCallback(std::function< void()> callback)
void SetBuildWorkflowLog(const std::string &output)
void SetStartBuildWorkflowCallback(std::function< void()> callback)
void SetRunWorkflowStatus(const ProjectWorkflowStatus &status)
void SetHackWorkflowBackend(workflow::HackWorkflowBackend *backend)
void Clear()
Clear all context state.
void SetCurrentProject(::yaze::project::YazeProject *project)
Set the current project instance.
std::vector< std::unique_ptr< WindowContent > > CreateAll()
Create new instances of all registered panels.
constexpr const char * kAbout
constexpr const char * kWriteConflictWarning
constexpr const char * kDungeonPotItemSaveConfirm
void UpdateBuildWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
void UpdateRunWorkflowStatus(StatusBar *status_bar, ProjectManagementPanel *project_panel, const ProjectWorkflowStatus &status)
std::string LastNonEmptyLine(const std::string &text)
bool HasAnyOverride(const core::RomAddressOverrides &overrides, std::initializer_list< const char * > keys)
bool ProjectUsesCustomObjects(const project::YazeProject &project)
void AppendWorkflowHistoryEntry(const std::string &kind, const ProjectWorkflowStatus &status, const std::string &output_log)
std::string StripSessionPrefix(absl::string_view panel_id)
bool SeedLegacyTrackObjectMapping(project::YazeProject *project, std::string *warning)
std::vector< std::string > ValidateRomAddressOverrides(const core::RomAddressOverrides &overrides, const Rom &rom)
std::optional< EditorType > ParseEditorTypeFromString(absl::string_view name)
std::unique_ptr< HackWorkflowBackend > CreateHackWorkflowBackendForProject(const project::YazeProject *project)
Editors are the view controllers for the application.
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:221
void ConfigureMenuShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void RegisterDefaultEditorFactories(EditorRegistry *registry)
void ConfigurePanelShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
Register configurable panel shortcuts from user settings.
size_t EditorTypeIndex(EditorType type)
Definition editor.h:230
void ConfigureEditorShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
ImVec4 GetSurfaceContainerHighestVec4()
ImVec4 GetPrimaryVec4()
ImVec4 GetTextSecondaryVec4()
Animator & GetAnimator()
Definition animator.cc:318
ImVec4 GetSurfaceContainerHighVec4()
void PostOverlayCommand(const char *command)
DiffSummary ComputeDiffRanges(const std::vector< uint8_t > &before, const std::vector< uint8_t > &after)
Definition rom_diff.cc:11
void RegisterZ3edTestSuites()
FileDialogOptions MakeRomFileDialogOptions(bool include_all_files)
Definition file_util.cc:87
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Definition game_data.cc:123
constexpr int kExpandedPtrTableMarker
Definition overworld.h:182
absl::Status SaveAllGraphicsData(Rom &rom, const std::array< gfx::Bitmap, kNumGfxSheets > &sheets)
Saves all graphics sheets back to ROM.
Definition game_data.cc:629
void SetPreferHmagicSpriteNames(bool prefer)
Definition sprite.cc:273
constexpr uint8_t kExpandedPtrTableMagic
Definition overworld.h:183
constexpr int kOverworldEntranceExpandedFlagPos
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
AssetLoadMode
Asset loading mode for editor resources.
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Configuration options for the application startup.
Definition application.h:26
std::string startup_editor
Definition application.h:38
StartupVisibility welcome_mode
Definition application.h:32
std::vector< std::string > open_panels
Definition application.h:40
StartupVisibility sidebar_mode
Definition application.h:34
StartupVisibility dashboard_mode
Definition application.h:33
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > all_resource_labels
std::unordered_map< std::string, uint32_t > addresses
std::optional< uint32_t > GetAddress(const std::string &key) const
std::function< EditorSet *()> get_current_editor_set
std::function< void(std::function< void()>)> queue_deferred_action
std::function< absl::Status(EditorType)> ensure_editor_assets_loaded
Unified dependency container for all editor types.
Definition editor.h:164
project::YazeProject * project
Definition editor.h:168
GlobalEditorContext * global_context
Definition editor.h:170
SharedClipboard * shared_clipboard
Definition editor.h:181
gfx::IRenderer * renderer
Definition editor.h:186
ShortcutManager * shortcut_manager
Definition editor.h:180
core::VersionManager * version_manager
Definition editor.h:169
WorkspaceWindowManager * window_manager
Definition editor.h:176
GfxGroupWorkspaceState * gfx_group_workspace
Definition editor.h:173
Published after ImGui::NewFrame and dockspace creation.
static JumpToMapRequestEvent Create(int map, size_t session=0)
static JumpToRoomRequestEvent Create(int room, size_t session=0)
All dependencies required by LayoutCoordinator.
Built-in workflow-oriented layout profiles.
Published when panel visibility changes.
Published when a ROM is successfully loaded into a session.
Definition core_events.h:27
Represents a single session, containing a ROM and its associated editors.
core::FeatureFlags::Flags feature_flags
std::array< bool, kEditorTypeCount > editor_initialized
zelda3::GameData game_data
std::array< bool, kEditorTypeCount > editor_assets_loaded
Published when a session is closed.
Published when a new session is created.
Published when the active session changes.
Definition core_events.h:88
Activity bar or menu action request.
std::unordered_map< std::string, float > right_panel_widths
std::unordered_map< std::string, std::unordered_map< std::string, bool > > panel_visibility_state
std::unordered_map< std::string, bool > pinned_panels
Declarative registration contract for editor windows.
Definition panel_host.h:22
Metadata for a dockable editor window (formerly PanelInfo)
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:416
std::vector< std::string > recent_files
Definition project.h:85
Modern project structure with comprehensive settings consolidation.
Definition project.h:164
std::string rom_backup_folder
Definition project.h:173
std::unordered_map< int, std::vector< std::string > > custom_object_files
Definition project.h:189
std::string custom_objects_folder
Definition project.h:184
absl::Status RepairProject()
Definition project.cc:1258
std::string MakeStorageKey(absl::string_view suffix) const
Definition project.cc:484
bool project_opened() const
Definition project.h:332
core::HackManifest hack_manifest
Definition project.h:204
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:197
std::string assets_folder
Definition project.h:179
std::string labels_filename
Definition project.h:181
std::string GetDisplayName() const
Definition project.cc:1292
WorkspaceSettings workspace_settings
Definition project.h:193
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1319
absl::Status Open(const std::string &project_path)
Definition project.cc:323
absl::Status Validate() const
Definition project.cc:1202
core::FeatureFlags::Flags feature_flags
Definition project.h:192
core::RomAddressOverrides rom_address_overrides
Definition project.h:195