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 <cstring>
8
9// C++ standard library headers
10#include <algorithm>
11#include <chrono>
12#include <cstdlib>
13#include <exception>
14#include <filesystem>
15#include <fstream>
16#include <functional>
17#include <initializer_list>
18#include <memory>
19#include <optional>
20#include <sstream>
21#include <string>
22#include <unordered_set>
23#include <utility>
24#include <vector>
25
26#ifdef __APPLE__
27#include <TargetConditionals.h>
28#endif
29
30// Third-party library headers
31#define IMGUI_DEFINE_MATH_OPERATORS
32#include "absl/status/status.h"
33#include "absl/status/statusor.h"
34#include "absl/strings/ascii.h"
35#include "absl/strings/match.h"
36#include "absl/strings/str_format.h"
37#include "absl/strings/str_join.h"
38#include "absl/strings/str_split.h"
39#include "absl/strings/string_view.h"
40#include "imgui/imgui.h"
41
42// Project headers
43#include "app/application.h"
45#include "app/editor/editor.h"
60#include "app/emu/emulator.h"
65#include "app/gui/core/icons.h"
69#include "app/platform/timing.h"
71#include "core/features.h"
72#include "core/project.h"
73#include "core/rom_settings.h"
82#include "rom/rom.h"
83#include "rom/rom_diff.h"
84#include "startup_flags.h"
85#include "util/file_util.h"
86#include "util/log.h"
87#include "util/macro.h"
88#include "util/rom_hash.h"
89#include "yaze_config.h"
95#include "zelda3/game_data.h"
101#include "zelda3/sprite/sprite.h"
102
103// Conditional platform headers
104#ifdef __EMSCRIPTEN__
108#endif
109
110// Conditional test headers
111#ifdef YAZE_ENABLE_TESTING
117#endif
118#ifdef YAZE_ENABLE_GTEST
120#endif
121#ifdef YAZE_WITH_GRPC
123#endif
124
125// Conditional agent UI headers
126#ifdef YAZE_BUILD_AGENT_UI
128#endif
129
130namespace yaze::editor {
131
132namespace {
133// TODO: Move to EditorRegistry
134std::string GetEditorName(EditorType type) {
135 return kEditorNames[static_cast<int>(type)];
136}
137
138
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
150 return !project.custom_objects_folder.empty() ||
151 !project.custom_object_files.empty();
152}
153
154std::vector<std::string> ValidateRomAddressOverrides(
155 const core::RomAddressOverrides& overrides, const Rom& rom) {
156 std::vector<std::string> warnings;
157 if (overrides.addresses.empty()) {
158 return warnings;
159 }
160
161 const auto rom_size = rom.size();
162 auto warn = [&](const std::string& message) {
163 warnings.push_back(message);
164 };
165
166 auto check_range = [&](const std::string& label, uint32_t addr, size_t span) {
167 const size_t addr_size = static_cast<size_t>(addr);
168 if (addr_size >= rom_size || addr_size + span > rom_size) {
169 warn(absl::StrFormat("ROM override '%s' out of range: 0x%X (size 0x%X)",
170 label, addr, rom_size));
171 }
172 };
173
174 for (const auto& [key, value] : overrides.addresses) {
175 check_range(key, value, 1);
176 }
177
180 // Defaults match the Oracle expanded message bank ($2F8000-$2FFFFF).
181 // Kept local to avoid pulling message editor constants into core validation.
182 constexpr uint32_t kExpandedMessageStartDefault = 0x178000;
183 constexpr uint32_t kExpandedMessageEndDefault = 0x17FFFF;
184 const uint32_t start =
186 .value_or(kExpandedMessageStartDefault);
187 const uint32_t end =
189 .value_or(kExpandedMessageEndDefault);
190 if (start >= rom_size || end >= rom_size) {
191 warn(absl::StrFormat(
192 "Expanded message range out of ROM bounds: 0x%X-0x%X", start, end));
193 } else if (end < start) {
194 warn(absl::StrFormat("Expanded message range invalid: 0x%X-0x%X", start,
195 end));
196 }
197 }
198
199 if (auto hook =
201 check_range(core::RomAddressKey::kExpandedMusicHook, *hook, 4);
202 if (*hook < rom_size) {
203 auto opcode = rom.ReadByte(*hook);
204 if (opcode.ok() && opcode.value() != 0x22) {
205 warn(absl::StrFormat(
206 "Expanded music hook at 0x%X is not a JSL opcode (0x%02X)", *hook,
207 opcode.value()));
208 }
209 }
210 }
211
212 if (auto main =
215 }
216
217 if (auto aux = overrides.GetAddress(core::RomAddressKey::kExpandedMusicAux)) {
218 check_range(core::RomAddressKey::kExpandedMusicAux, *aux, 4);
219 }
220
221 if (HasAnyOverride(overrides,
226 const uint32_t marker =
229 const uint32_t magic =
232 check_range("overworld_ptr_marker", marker, 1);
233 if (marker < rom_size) {
234 if (rom.data()[marker] != static_cast<uint8_t>(magic)) {
235 warn(absl::StrFormat(
236 "Overworld expanded pointer marker mismatch at 0x%X: expected "
237 "0x%02X, found 0x%02X",
238 marker, static_cast<uint8_t>(magic), rom.data()[marker]));
239 }
240 }
241 }
242
243 if (HasAnyOverride(overrides,
248 const uint32_t flag_addr =
249 overrides
252 check_range("overworld_entrance_flag_expanded", flag_addr, 1);
253 if (flag_addr < rom_size && rom.data()[flag_addr] == 0xB8) {
254 warn(
255 absl::StrFormat("Overworld entrance flag at 0x%X is still 0xB8 "
256 "(vanilla); expanded entrance tables may be ignored",
257 flag_addr));
258 }
259 }
260
261 return warnings;
262}
263
264std::optional<EditorType> ParseEditorTypeFromString(absl::string_view name) {
265 const std::string lower = absl::AsciiStrToLower(std::string(name));
266 for (int i = 0; i < static_cast<int>(EditorType::kSettings) + 1; ++i) {
267 const std::string candidate = absl::AsciiStrToLower(
268 std::string(GetEditorName(static_cast<EditorType>(i))));
269 if (candidate == lower) {
270 return static_cast<EditorType>(i);
271 }
272 }
273 return std::nullopt;
274}
275
276std::string StripSessionPrefix(absl::string_view panel_id) {
277 if (panel_id.size() > 2 && panel_id[0] == 's' &&
278 absl::ascii_isdigit(panel_id[1])) {
279 const size_t dot = panel_id.find('.');
280 if (dot != absl::string_view::npos) {
281 return std::string(panel_id.substr(dot + 1));
282 }
283 }
284 return std::string(panel_id);
285}
286
288 namespace fs = std::filesystem;
289
290 std::vector<fs::path> roots;
291 if (const char* home = std::getenv("HOME");
292 home != nullptr && std::strlen(home) > 0) {
293 roots.push_back(fs::path(home) / "src" / "hobby" / "oracle-of-secrets");
294 }
295
296 std::vector<fs::path> candidates;
297 std::unordered_set<std::string> seen;
298 auto add_candidate = [&](const fs::path& path) {
299 std::error_code ec;
300 if (!fs::exists(path, ec) || ec) {
301 return;
302 }
303 std::string ext = path.extension().string();
304 absl::AsciiStrToLower(&ext);
305 if (ext != ".yaze" && ext != ".yazeproj") {
306 return;
307 }
308 const std::string normalized = fs::weakly_canonical(path, ec).string();
309 const std::string key = normalized.empty() ? path.string() : normalized;
310 if (key.empty() || seen.count(key) > 0) {
311 return;
312 }
313 seen.insert(key);
314 candidates.push_back(path);
315 };
316
317 for (const auto& root : roots) {
318 std::error_code ec;
319 if (!fs::exists(root, ec) || ec) {
320 continue;
321 }
322
323 // Priority candidates first.
324 add_candidate(root / "Oracle-of-Secrets.yaze");
325 add_candidate(root / "Oracle of Secrets.yaze");
326 add_candidate(root / "Oracle-of-Secrets.yazeproj");
327 add_candidate(root / "Oracle of Secrets.yazeproj");
328 add_candidate(root / "Roms" / "Oracle of Secrets.yaze");
329
330 // Also detect additional local project files nearby.
331 fs::recursive_directory_iterator it(
332 root, fs::directory_options::skip_permission_denied, ec);
333 fs::recursive_directory_iterator end;
334 if (ec) {
335 continue;
336 }
337 for (; it != end; it.increment(ec)) {
338 if (ec) {
339 ec.clear();
340 continue;
341 }
342 if (it.depth() > 2) {
343 it.disable_recursion_pending();
344 continue;
345 }
346 add_candidate(it->path());
347 }
348 }
349
350 if (candidates.empty()) {
351 return;
352 }
353
355 // Add in reverse so the first candidate remains most recent.
356 for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
357 manager.AddFile(it->string());
358 }
359 manager.Save();
360}
361
362} // namespace
363
364// Static registry of editors that use the card-based layout system
365// These editors register their cards with EditorPanelManager and manage their
366// own windows They do NOT need the traditional ImGui::Begin/End wrapper - they
367// create cards internally
371
375
376void EditorManager::ApplyLayoutPreset(const std::string& preset_name) {
378}
379
380bool EditorManager::ApplyLayoutProfile(const std::string& profile_id) {
381 if (!layout_manager_) {
382 return false;
383 }
384
385 const size_t session_id = GetCurrentSessionId();
386 const EditorType current_type =
388
389 LayoutProfile applied_profile;
390 if (!layout_manager_->ApplyBuiltInProfile(profile_id, session_id,
391 current_type, &applied_profile)) {
392 return false;
393 }
394
395 if (applied_profile.open_agent_chat && right_panel_manager_) {
397 const float default_width = RightPanelManager::GetDefaultPanelWidth(
399 right_panel_manager_->SetPanelWidth(
401 std::max(default_width, 480.0f));
402 }
403
405 absl::StrFormat("Layout Profile: %s", applied_profile.label),
407 return true;
408}
409
411 if (!layout_manager_) {
412 return;
413 }
414 layout_manager_->CaptureTemporarySessionLayout(GetCurrentSessionId());
415 toast_manager_.Show("Captured temporary layout snapshot", ToastType::kInfo);
416}
417
418void EditorManager::RestoreTemporaryLayoutSnapshot(bool clear_after_restore) {
419 if (!layout_manager_) {
420 return;
421 }
422
423 if (layout_manager_->RestoreTemporarySessionLayout(GetCurrentSessionId(),
424 clear_after_restore)) {
425 toast_manager_.Show("Restored temporary layout snapshot",
427 } else {
428 toast_manager_.Show("No temporary layout snapshot available",
430 }
431}
432
434 if (!layout_manager_) {
435 return;
436 }
437 layout_manager_->ClearTemporarySessionLayout();
438 toast_manager_.Show("Cleared temporary layout snapshot", ToastType::kInfo);
439}
440
442 if (!layout_manager_) {
443 return;
444 }
445
447 layout_manager_->SetProjectLayoutKey(
449 } else {
450 layout_manager_->UseGlobalLayouts();
451 }
452}
453
462
463#ifdef YAZE_BUILD_AGENT_UI
464void EditorManager::ShowAIAgent() {
465 // Apply saved agent settings from the current project when opening the Agent
466 // UI to respect the user's preferred provider/model.
467 // TODO: Implement LoadAgentSettingsFromProject in AgentChat or AgentEditor
471 for (const auto& panel_id :
472 LayoutPresets::GetDefaultPanels(EditorType::kAgent)) {
473 panel_manager_.ShowPanel(panel_id);
474 }
475}
476
477void EditorManager::ShowChatHistory() {
479}
480#endif
481
483 : project_manager_(&toast_manager_), rom_file_manager_(&toast_manager_) {
484 std::stringstream ss;
485 ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
486 << YAZE_VERSION_PATCH;
487 ss >> version_;
488
489 // Initialize Core Context
490 editor_context_ = std::make_unique<GlobalEditorContext>(event_bus_);
493
497
498 // STEP 5: ShortcutConfigurator created later in Initialize() method
499 // It depends on all above coordinators being available
500}
501
503 // STEP 1: Initialize PopupManager FIRST
504 popup_manager_ = std::make_unique<PopupManager>(this);
505 popup_manager_->Initialize(); // Registers all popups with PopupID constants
506
507 // STEP 2: Initialize SessionCoordinator (independent of popups)
508 session_coordinator_ = std::make_unique<SessionCoordinator>(
510 session_coordinator_->SetEditorRegistry(&editor_registry_);
511
512 // STEP 3: Initialize MenuOrchestrator (depends on popup_manager_,
513 // session_coordinator_)
514 menu_orchestrator_ = std::make_unique<MenuOrchestrator>(
517
518 // Wire up card registry for Panels submenu in View menu
519 menu_orchestrator_->SetPanelManager(&panel_manager_);
520 menu_orchestrator_->SetStatusBar(&status_bar_);
521 menu_orchestrator_->SetUserSettings(&user_settings_);
522
523 session_coordinator_->SetEditorManager(this);
524 session_coordinator_->SetEventBus(&event_bus_); // Enable event publishing
526 &event_bus_); // Global event bus access
527
528 // STEP 3.5: Initialize RomLifecycleManager (depends on popup_manager_,
529 // session_coordinator_, rom_file_manager_, toast_manager_)
531 .rom_file_manager = &rom_file_manager_,
532 .session_coordinator = session_coordinator_.get(),
534 .popup_manager = popup_manager_.get(),
535 .project = &current_project_,
536 });
537
538 // STEP 4: Initialize UICoordinator (depends on popup_manager_,
539 // session_coordinator_, panel_manager_)
540 ui_coordinator_ = std::make_unique<UICoordinator>(
544
545 // STEP 4.5: Initialize LayoutManager (DockBuilder layouts for editors)
546 layout_manager_ = std::make_unique<LayoutManager>();
547 layout_manager_->SetPanelManager(&panel_manager_);
548 layout_manager_->UseGlobalLayouts();
551 [this](const std::string& preset_name) {
552 ApplyLayoutPreset(preset_name);
553 });
555 [this](const std::string& preset_name) {
556 ApplyLayoutPreset(preset_name);
557 });
558
559 // STEP 4.6: Initialize RightPanelManager (right-side sliding panels)
560 right_panel_manager_ = std::make_unique<RightPanelManager>();
561 right_panel_manager_->SetToastManager(&toast_manager_);
562 right_panel_manager_->SetProposalDrawer(&proposal_drawer_);
564 right_panel_manager_->SetShortcutManager(&shortcut_manager_);
566 [this](const std::string& prompt) {
567#if defined(YAZE_BUILD_AGENT_UI)
568 auto* agent_editor = agent_ui_.GetAgentEditor();
569 if (!agent_editor) {
570 return;
571 }
572 auto* chat = agent_editor->GetAgentChat();
573 if (!chat) {
574 return;
575 }
576 chat->set_active(true);
577 chat->SendMessage(prompt);
578#else
579 (void)prompt;
580#endif
581 },
582 [this]() {
583#if defined(YAZE_BUILD_AGENT_UI)
585 right_panel_manager_->OpenPanel(
587 }
588#endif
589 });
592 right_panel_manager_->TogglePanel(
594 } else {
596 }
597 });
598
599 // Initialize ProjectManagementPanel for project/version management
600 project_management_panel_ = std::make_unique<ProjectManagementPanel>();
601 project_management_panel_->SetToastManager(&toast_manager_);
602 project_management_panel_->SetSwapRomCallback([this]() {
603 // Prompt user to select a new ROM for the project
606 if (!rom_path.empty()) {
608 auto status = current_project_.Save();
609 if (status.ok()) {
610 toast_manager_.Show("Project ROM updated. Reload to apply changes.",
612 } else {
613 toast_manager_.Show("Failed to update project ROM", ToastType::kError);
614 }
615 }
616 });
617 project_management_panel_->SetReloadRomCallback([this]() {
620 auto status = LoadProjectWithRom();
621 if (!status.ok()) {
623 absl::StrFormat("Failed to reload ROM: %s", status.message()),
625 }
626 }
627 });
628 project_management_panel_->SetSaveProjectCallback([this]() {
629 auto status = SaveProject();
630 if (status.ok()) {
631 toast_manager_.Show("Project saved", ToastType::kSuccess);
632 } else {
634 absl::StrFormat("Failed to save project: %s", status.message()),
636 }
637 });
638 project_management_panel_->SetBrowseFolderCallback(
639 [this](const std::string& type) {
641 if (!folder_path.empty()) {
642 if (type == "code") {
643 current_project_.code_folder = folder_path;
644 // Update assembly editor path
645 if (auto* editor_set = GetCurrentEditorSet()) {
646// iOS: avoid blocking folder enumeration on the UI thread.
647#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
648 editor_set->OpenAssemblyFolder(folder_path);
649#endif
650 panel_manager_.SetFileBrowserPath("Assembly", folder_path);
651 }
652 } else if (type == "assets") {
653 current_project_.assets_folder = folder_path;
654 }
655 toast_manager_.Show(absl::StrFormat("%s folder set: %s", type.c_str(),
656 folder_path.c_str()),
658 }
659 });
660 right_panel_manager_->SetProjectManagementPanel(
662
663 // STEP 4.6.1: Initialize LayoutCoordinator (facade for layout operations)
665 layout_deps.layout_manager = layout_manager_.get();
666 layout_deps.panel_manager = &panel_manager_;
667 layout_deps.ui_coordinator = ui_coordinator_.get();
668 layout_deps.toast_manager = &toast_manager_;
669 layout_deps.status_bar = &status_bar_;
670 layout_deps.right_panel_manager = right_panel_manager_.get();
671 layout_coordinator_.Initialize(layout_deps);
672
673 // STEP 4.6.2: Initialize EditorActivator (editor switching and jump navigation)
674 EditorActivator::Dependencies activator_deps;
675 activator_deps.panel_manager = &panel_manager_;
676 activator_deps.layout_manager = layout_manager_.get();
677 activator_deps.ui_coordinator = ui_coordinator_.get();
678 activator_deps.right_panel_manager = right_panel_manager_.get();
679 activator_deps.toast_manager = &toast_manager_;
680 activator_deps.event_bus = &event_bus_;
681 activator_deps.ensure_editor_assets_loaded = [this](EditorType type) {
682 return EnsureEditorAssetsLoaded(type);
683 };
684 activator_deps.get_current_editor_set = [this]() {
685 return GetCurrentEditorSet();
686 };
687 activator_deps.get_current_session_id = [this]() {
688 return GetCurrentSessionId();
689 };
690 activator_deps.queue_deferred_action = [this](std::function<void()> action) {
691 QueueDeferredAction(std::move(action));
692 };
693 editor_activator_.Initialize(activator_deps);
694
695 // STEP 4.7: Initialize ActivityBar
696 activity_bar_ = std::make_unique<ActivityBar>(
698 [this]() -> bool {
699 if (auto* editor_set = GetCurrentEditorSet()) {
700 if (auto* dungeon_editor = editor_set->GetEditorAs<DungeonEditorV2>(
702 return dungeon_editor->IsWorkbenchWorkflowEnabled();
703 }
704 }
705 return false;
706 },
707 [this](bool enabled) {
708 if (auto* editor_set = GetCurrentEditorSet()) {
709 if (auto* dungeon_editor = editor_set->GetEditorAs<DungeonEditorV2>(
711 dungeon_editor->QueueWorkbenchWorkflowMode(enabled);
712 }
713 }
714 });
715
716 // PanelHost is the declarative panel registration surface used by
717 // editor/runtime systems.
718 panel_host_ = std::make_unique<PanelHost>(&panel_manager_);
719
720 // Wire up EventBus to PanelManager for action event publishing
722}
723
725 // Auto-register panels from ContentRegistry (Unified Panel System)
726 auto registry_panels = ContentRegistry::Panels::CreateAll();
727 for (auto& panel : registry_panels) {
728 panel_manager_.RegisterRegistryPanel(std::move(panel));
729 }
730
731 // STEP 4.8: Initialize DashboardPanel
732 dashboard_panel_ = std::make_unique<DashboardPanel>(this);
733
734 if (panel_host_) {
735 PanelDefinition dashboard_definition;
736 dashboard_definition.id = "dashboard.main";
737 dashboard_definition.display_name = "Dashboard";
738 dashboard_definition.icon = ICON_MD_DASHBOARD;
739 dashboard_definition.category = "Dashboard";
740 dashboard_definition.window_title = " Dashboard";
741 dashboard_definition.shortcut_hint = "F1";
742 dashboard_definition.priority = 0;
743 dashboard_definition.visibility_flag = dashboard_panel_->visibility_flag();
744 panel_host_->RegisterPanel(dashboard_definition);
745 } else {
747 {.card_id = "dashboard.main",
748 .display_name = "Dashboard",
749 .window_title = " Dashboard",
750 .icon = ICON_MD_DASHBOARD,
751 .category = "Dashboard",
752 .shortcut_hint = "F1",
753 .visibility_flag = dashboard_panel_->visibility_flag(),
754 .priority = 0});
755 }
756}
757
759 // Subscribe to session lifecycle events via EventBus
760 // (replaces SessionObserver pattern)
762 [this](const SessionSwitchedEvent& e) {
763 HandleSessionSwitched(e.new_index, e.session);
764 });
765
767 [this](const SessionCreatedEvent& e) {
768 HandleSessionCreated(e.index, e.session);
769 });
770
772 [this](const SessionClosedEvent& e) { HandleSessionClosed(e.index); });
773
775 HandleSessionRomLoaded(e.session_id, e.rom);
776 });
777
778 // Subscribe to FrameGuiBeginEvent for ImGui-safe deferred action processing
779 // This replaces scattered manual processing calls with event-driven execution
781 ui_sync_frame_id_.fetch_add(1, std::memory_order_relaxed);
782
783 // Process LayoutCoordinator's deferred actions
785
786 // Process EditorManager's deferred actions
787 if (!deferred_actions_.empty()) {
788 std::vector<std::function<void()>> actions_to_execute;
789 actions_to_execute.swap(deferred_actions_);
790 const int processed_count = static_cast<int>(actions_to_execute.size());
791 for (auto& action : actions_to_execute) {
792 action();
793 }
794 if (processed_count > 0) {
795 const int remaining = pending_editor_deferred_actions_.fetch_sub(
796 processed_count, std::memory_order_relaxed) -
797 processed_count;
798 if (remaining < 0) {
799 pending_editor_deferred_actions_.store(0, std::memory_order_relaxed);
800 }
801 }
802 }
803 });
804
805 // Subscribe to UIActionRequestEvent for activity bar actions
806 // This replaces direct callbacks from PanelManager
808 [this](const UIActionRequestEvent& e) {
809 HandleUIActionRequest(e.action);
810 });
811
813 [this](const PanelVisibilityChangedEvent& e) {
814 if (e.category.empty() ||
815 e.category == PanelManager::kDashboardCategory) {
816 return;
817 }
818 auto& prefs = user_settings_.prefs();
819 prefs.panel_visibility_state[e.category][e.base_panel_id] = e.visible;
820 settings_dirty_ = true;
822 });
823}
824
826 // EventBus subscriptions are automatically cleaned up when event_bus_ is
827 // destroyed (owned by this class). No manual unsubscription needed.
828}
829
830// ============================================================================
831// Session Event Handlers (EventBus subscribers)
832// ============================================================================
833
835 auto& provider = zelda3::GetResourceLabels();
838
839 auto merge_labels =
842 for (const auto& [type, labels] : src) {
843 auto& out = (*dst)[type];
844 for (const auto& [key, value] : labels) {
845 out[key] = value;
846 }
847 }
848 };
849
850 merged_labels.clear();
851 std::vector<std::string> source_parts;
852
853 // 1) ROM-local labels as baseline for active session.
854 if (auto* rom = GetCurrentRom();
855 rom && rom->resource_label() && !rom->resource_label()->labels_.empty()) {
856 merge_labels(&merged_labels, rom->resource_label()->labels_);
857 source_parts.push_back("rom");
858 }
859
860 // 2) Oracle project registry labels from hack manifest.
864 merge_labels(
865 &merged_labels,
867 source_parts.push_back("registry");
868 }
869
870 // 3) Explicit project labels override all other sources.
873 merge_labels(&merged_labels, current_project_.resource_labels);
874 source_parts.push_back("project");
875 }
876
877 auto* label_source = merged_labels.empty() ? &empty_labels : &merged_labels;
878 std::string source =
879 source_parts.empty() ? "empty" : absl::StrJoin(source_parts, "+");
880
881 provider.SetProjectLabels(label_source);
882 provider.SetHackManifest(current_project_.project_opened() &&
885 : nullptr);
886
887 const bool prefer_hmagic =
891 provider.SetPreferHMagicNames(prefer_hmagic);
892
893 LOG_DEBUG("EditorManager",
894 "ResourceLabelProvider refreshed (source=%s, project_open=%s)",
895 source.c_str(),
896 current_project_.project_opened() ? "true" : "false");
897}
898
900 RomSession* session) {
901 // Update RightPanelManager with the new session's settings editor
902 if (right_panel_manager_ && session) {
903 right_panel_manager_->SetSettingsPanel(session->editors.GetSettingsPanel());
904 }
905
906 // Update properties panel with new ROM
907 if (session) {
909 }
910
911 // Update ContentRegistry context with current session's ROM and GameData
912 ContentRegistry::Context::SetRom(session ? &session->rom : nullptr);
914 : nullptr);
915
916 // Keep room/sprite labels in sync with active session context.
918
919 const std::string category = panel_manager_.GetActiveCategory();
920 if (!category.empty() && category != PanelManager::kDashboardCategory) {
921 auto it = user_settings_.prefs().panel_visibility_state.find(category);
922 if (it != user_settings_.prefs().panel_visibility_state.end()) {
923 panel_manager_.RestoreVisibilityState(new_index, it->second);
924 }
925 }
926
927#ifdef YAZE_ENABLE_TESTING
928 test::TestManager::Get().SetCurrentRom(session ? &session->rom : nullptr);
929#endif
930
931 LOG_DEBUG("EditorManager", "Session switched to %zu via EventBus", new_index);
932}
933
937 LOG_INFO("EditorManager", "Session %zu created via EventBus", index);
938}
939
941 // Update ContentRegistry - it will be set to new active ROM on next switch
942 // If no sessions remain, clear the context
944 session_coordinator_->GetTotalSessionCount() == 0) {
946 }
947
948 // Avoid dangling/stale label map pointers after session close.
950
951#ifdef YAZE_ENABLE_TESTING
952 // Update test manager - it will get the new current ROM on next switch
954#endif
955
956 LOG_INFO("EditorManager", "Session %zu closed via EventBus", index);
957}
958
960 auto* session =
961 static_cast<RomSession*>(session_coordinator_->GetSession(index));
962 ResetAssetState(session);
963
964 // Update ContentRegistry when ROM is loaded (if this is the active session)
965 if (rom && session_coordinator_ &&
966 index == session_coordinator_->GetActiveSessionIndex()) {
968 // Also update GameData from the session
969 if (session) {
970 ContentRegistry::Context::SetGameData(&session->game_data);
971 }
973 }
974
975#ifdef YAZE_ENABLE_TESTING
976 if (rom) {
978 }
979#endif
980
981 LOG_INFO("EditorManager", "ROM loaded in session %zu via EventBus", index);
982}
983
985 using Action = UIActionRequestEvent::Action;
986 switch (action) {
987 case Action::kShowEmulator:
988 if (ui_coordinator_) {
989 ui_coordinator_->SetEmulatorVisible(true);
990 }
991 break;
992
993 case Action::kShowSettings:
994 // Toggle Settings panel in sidebar
996 right_panel_manager_->TogglePanel(
998 } else {
1000 }
1001 break;
1002
1003 case Action::kShowPanelBrowser:
1004 if (ui_coordinator_) {
1005 ui_coordinator_->ShowPanelBrowser();
1006 }
1007 break;
1008
1009 case Action::kShowSearch:
1010 if (ui_coordinator_) {
1011 ui_coordinator_->ShowGlobalSearch();
1012 }
1013 break;
1014
1015 case Action::kShowShortcuts:
1016 // Shortcut configuration is part of Settings
1018 break;
1019
1020 case Action::kShowCommandPalette:
1021 if (ui_coordinator_) {
1022 ui_coordinator_->ShowCommandPalette();
1023 }
1024 break;
1025
1026 case Action::kShowHelp:
1028 // Toggle Help panel in sidebar
1030 } else if (popup_manager_) {
1031 // Fallback to "About" dialog if sidebar not available
1033 }
1034 break;
1035
1036 case Action::kShowAgentChatSidebar:
1038 right_panel_manager_->OpenPanel(
1040 } else {
1042 }
1043 break;
1044
1045 case Action::kShowAgentProposalsSidebar:
1047 right_panel_manager_->OpenPanel(
1049 }
1050 break;
1051
1052 case Action::kOpenRom: {
1053 auto status = LoadRom();
1054 if (!status.ok()) {
1056 std::string("Open failed: ") + std::string(status.message()),
1058 }
1059 } break;
1060
1061 case Action::kSaveRom:
1062 if (GetCurrentRom() && GetCurrentRom()->is_loaded()) {
1063 auto status = SaveRom();
1064 if (status.ok()) {
1065 toast_manager_.Show("ROM saved successfully", ToastType::kSuccess);
1066 } else if (!absl::IsCancelled(status)) {
1068 std::string("Save failed: ") + std::string(status.message()),
1070 }
1071 }
1072 break;
1073
1074 case Action::kUndo:
1075 if (auto* current_editor = GetCurrentEditor()) {
1076 auto status = current_editor->Undo();
1077 if (!status.ok()) {
1079 std::string("Undo failed: ") + std::string(status.message()),
1081 }
1082 }
1083 break;
1084
1085 case Action::kRedo:
1086 if (auto* current_editor = GetCurrentEditor()) {
1087 auto status = current_editor->Redo();
1088 if (!status.ok()) {
1090 std::string("Redo failed: ") + std::string(status.message()),
1092 }
1093 }
1094 break;
1095
1096 case Action::kResetLayout:
1098 break;
1099 }
1100}
1101
1103 auto& test_manager = test::TestManager::Get();
1104
1105#ifdef YAZE_ENABLE_TESTING
1106 // Register comprehensive test suites
1107 test_manager.RegisterTestSuite(
1108 std::make_unique<test::CoreSystemsTestSuite>());
1109 test_manager.RegisterTestSuite(std::make_unique<test::IntegratedTestSuite>());
1110 test_manager.RegisterTestSuite(
1111 std::make_unique<test::PerformanceTestSuite>());
1112 test_manager.RegisterTestSuite(std::make_unique<test::UITestSuite>());
1113 test_manager.RegisterTestSuite(
1114 std::make_unique<test::RomDependentTestSuite>());
1115
1116 // Register new E2E and ZSCustomOverworld test suites
1117 test_manager.RegisterTestSuite(std::make_unique<test::E2ETestSuite>());
1118 test_manager.RegisterTestSuite(
1119 std::make_unique<test::ZSCustomOverworldTestSuite>());
1120#endif
1121
1122 // Register Google Test suite if available
1123#ifdef YAZE_ENABLE_GTEST
1124 test_manager.RegisterTestSuite(std::make_unique<test::UnitTestSuite>());
1125#endif
1126
1127 // Register z3ed AI Agent test suites (requires gRPC)
1128#ifdef YAZE_WITH_GRPC
1130#endif
1131
1132 // Update resource monitoring to track Arena state
1133 test_manager.UpdateResourceStats();
1134}
1135
1137 const std::string& filename) {
1138 renderer_ = renderer;
1140 SeedOracleProjectInRecents();
1141
1142 // Inject card_registry into emulator and workspace_manager
1145
1146 // Point to a blank editor set when no ROM is loaded
1147 // current_editor_set_ = &blank_editor_set_;
1148
1149 if (!filename.empty()) {
1151 }
1152
1153 // Note: PopupManager is now initialized in constructor before
1154 // MenuOrchestrator This ensures all menu callbacks can safely call
1155 // popup_manager_.Show()
1156
1160
1161 // Apply sidebar state from settings AFTER registering callbacks
1162 // This triggers the callbacks but they should be safe now
1164 /*notify=*/false);
1166 /*notify=*/false);
1168 user_settings_.prefs().sidebar_panel_width, /*notify=*/false);
1171 /*notify=*/false);
1172 {
1173 const std::string category = GetPreferredStartupCategory(
1175 if (!category.empty()) {
1176 panel_manager_.SetActiveCategory(category, /*notify=*/false);
1178 auto it = user_settings_.prefs().panel_visibility_state.find(category);
1179 if (it != user_settings_.prefs().panel_visibility_state.end()) {
1181 panel_manager_.GetActiveSessionId(), it->second);
1182 }
1183 }
1184 }
1185
1189 }
1190
1191 // Initialize testing system only when tests are enabled
1192#ifdef YAZE_ENABLE_TESTING
1194#endif
1195
1196 // TestManager will be updated when ROMs are loaded via SetCurrentRom calls
1197
1199}
1200
1202 // Register emulator panels early (emulator Initialize might not be called).
1203 const std::vector<PanelDefinition> panel_definitions = {
1204 {.id = "emulator.cpu_debugger",
1205 .display_name = "CPU Debugger",
1206 .icon = ICON_MD_BUG_REPORT,
1207 .category = "Emulator",
1208 .priority = 10},
1209 {.id = "emulator.ppu_viewer",
1210 .display_name = "PPU Viewer",
1212 .category = "Emulator",
1213 .priority = 20},
1214 {.id = "emulator.memory_viewer",
1215 .display_name = "Memory Viewer",
1216 .icon = ICON_MD_MEMORY,
1217 .category = "Emulator",
1218 .priority = 30},
1219 {.id = "emulator.breakpoints",
1220 .display_name = "Breakpoints",
1221 .icon = ICON_MD_STOP,
1222 .category = "Emulator",
1223 .priority = 40},
1224 {.id = "emulator.performance",
1225 .display_name = "Performance",
1226 .icon = ICON_MD_SPEED,
1227 .category = "Emulator",
1228 .priority = 50},
1229 {.id = "emulator.ai_agent",
1230 .display_name = "AI Agent",
1231 .icon = ICON_MD_SMART_TOY,
1232 .category = "Emulator",
1233 .priority = 60},
1234 {.id = "emulator.save_states",
1235 .display_name = "Save States",
1236 .icon = ICON_MD_SAVE,
1237 .category = "Emulator",
1238 .priority = 70},
1239 {.id = "emulator.keyboard_config",
1240 .display_name = "Keyboard Config",
1241 .icon = ICON_MD_KEYBOARD,
1242 .category = "Emulator",
1243 .priority = 80},
1244 {.id = "emulator.virtual_controller",
1245 .display_name = "Virtual Controller",
1246 .icon = ICON_MD_SPORTS_ESPORTS,
1247 .category = "Emulator",
1248 .priority = 85},
1249 {.id = "emulator.apu_debugger",
1250 .display_name = "APU Debugger",
1251 .icon = ICON_MD_AUDIOTRACK,
1252 .category = "Emulator",
1253 .priority = 90},
1254 {.id = "emulator.audio_mixer",
1255 .display_name = "Audio Mixer",
1256 .icon = ICON_MD_AUDIO_FILE,
1257 .category = "Emulator",
1258 .priority = 100},
1259 {.id = "memory.hex_editor",
1260 .display_name = "Hex Editor",
1261 .icon = ICON_MD_MEMORY,
1262 .category = "Memory",
1263 .window_title = ICON_MD_MEMORY " Hex Editor",
1264 .priority = 10,
1265 .legacy_ids = {"Memory Editor"}},
1266 };
1267
1268 if (panel_host_) {
1269 panel_host_->RegisterPanels(panel_definitions);
1270 return;
1271 }
1272
1273 for (const auto& definition : panel_definitions) {
1274 PanelDescriptor descriptor;
1275 descriptor.card_id = definition.id;
1276 descriptor.display_name = definition.display_name;
1277 descriptor.window_title = definition.window_title;
1278 descriptor.icon = definition.icon;
1279 descriptor.category = definition.category;
1280 descriptor.shortcut_hint = definition.shortcut_hint;
1281 descriptor.priority = definition.priority;
1282 descriptor.scope = definition.scope;
1283 descriptor.panel_category = definition.panel_category;
1284 descriptor.context_scope = definition.context_scope;
1285 descriptor.visibility_flag = definition.visibility_flag;
1286 descriptor.on_show = definition.on_show;
1287 descriptor.on_hide = definition.on_hide;
1288
1289 for (const auto& legacy_id : definition.legacy_ids) {
1290 panel_manager_.RegisterPanelAlias(legacy_id, definition.id);
1291 }
1292
1293 panel_manager_.RegisterPanel(descriptor);
1294 if (definition.visible_by_default) {
1295 panel_manager_.ShowPanel(definition.id);
1296 }
1297 }
1298}
1299
1301 // Initialize project file editor
1303
1304 // Initialize agent UI (no-op when agent UI is disabled)
1308
1309 // Note: Unified gRPC Server is started from Application::Initialize()
1310 // after gRPC infrastructure is properly set up
1311
1312 // Load critical user settings first
1314 if (!status_.ok()) {
1315 LOG_WARN("EditorManager", "Failed to load user settings: %s",
1316 status_.ToString().c_str());
1317 }
1318
1319 auto& prefs = user_settings_.prefs();
1320 prefs.switch_motion_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1322 prefs.reduced_motion,
1323 gui::Animator::ClampMotionProfile(prefs.switch_motion_profile));
1324
1326
1329 right_panel_manager_->ResetPanelWidths();
1331 right_panel_manager_->SerializePanelWidths();
1332 } else {
1333 right_panel_manager_->RestorePanelWidths(
1335 }
1336 right_panel_manager_->SetPanelWidthChangedCallback(
1337 [this](RightPanelManager::PanelType, float) {
1338 if (!right_panel_manager_) {
1339 return;
1340 }
1341 user_settings_.prefs().right_panel_widths =
1342 right_panel_manager_->SerializePanelWidths();
1343 settings_dirty_ = true;
1345 });
1346 }
1348 // Apply sprite naming preference globally.
1351
1353
1354 // Apply font scale after loading (only if ImGui context exists)
1355 if (ImGui::GetCurrentContext() != nullptr) {
1356 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
1357 } else {
1358 LOG_WARN("EditorManager",
1359 "ImGui context not available; skipping FontGlobalScale update");
1360 }
1361
1362 // Initialize WASM control and session APIs for browser/agent integration
1363#ifdef __EMSCRIPTEN__
1366 LOG_INFO("EditorManager", "WASM Control and Session APIs initialized");
1367#endif
1368}
1369
1375
1377 // Initialize ROM load options dialog callbacks
1379 [this](const RomLoadOptionsDialog::LoadOptions& options) {
1380 // Apply feature flags from dialog
1381 auto& flags = core::FeatureFlags::get();
1382 flags.overworld.kSaveOverworldMaps = options.save_overworld_maps;
1383 flags.overworld.kSaveOverworldEntrances =
1385 flags.overworld.kSaveOverworldExits = options.save_overworld_exits;
1386 flags.overworld.kSaveOverworldItems = options.save_overworld_items;
1387 flags.overworld.kLoadCustomOverworld = options.enable_custom_overworld;
1388 flags.kSaveDungeonMaps = options.save_dungeon_maps;
1389 flags.kSaveAllPalettes = options.save_all_palettes;
1390 flags.kSaveGfxGroups = options.save_gfx_groups;
1391
1392 // Create project if requested
1393 if (options.create_project && !options.project_name.empty()) {
1396 options.project_name, options.project_path);
1397 if (!status.ok()) {
1398 toast_manager_.Show("Failed to create project", ToastType::kError);
1399 } else {
1400 toast_manager_.Show("Project created: " + options.project_name,
1402 }
1403 }
1404
1405 // Close dialog and show editor selection
1406 show_rom_load_options_ = false;
1407 if (ui_coordinator_) {
1408 ui_coordinator_->SetEditorSelectionVisible(true);
1409 }
1410
1411 LOG_INFO("EditorManager", "ROM load options applied: preset=%s",
1412 options.selected_preset.c_str());
1413 });
1414}
1415
1417 // Initialize welcome screen callbacks
1419
1422 if (status_.ok() && ui_coordinator_) {
1423 ui_coordinator_->SetWelcomeScreenVisible(false);
1424 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1425 }
1426 });
1427
1429 [this](const std::string& template_name) {
1430 status_ = CreateNewProject(template_name);
1431 if (status_.ok() && ui_coordinator_) {
1432 ui_coordinator_->SetWelcomeScreenVisible(false);
1433 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1434 }
1435 });
1436
1437 welcome_screen_.SetOpenProjectCallback([this](const std::string& filepath) {
1438 status_ = OpenRomOrProject(filepath);
1439 if (status_.ok() && ui_coordinator_) {
1440 ui_coordinator_->SetWelcomeScreenVisible(false);
1441 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1442 }
1443 });
1444
1446#ifdef YAZE_BUILD_AGENT_UI
1447 ShowAIAgent();
1448#endif
1449 });
1450
1452 status_ = OpenProject();
1453 if (status_.ok() && ui_coordinator_) {
1454 ui_coordinator_->SetWelcomeScreenVisible(false);
1455 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1456 } else if (!status_.ok()) {
1458 absl::StrFormat("Failed to open project: %s", status_.message()),
1460 }
1461 });
1462
1464 [this]() { ShowProjectManagement(); });
1465
1467 if (current_project_.filepath.empty()) {
1468 toast_manager_.Show("No project file to edit", ToastType::kInfo);
1469 return;
1470 }
1472 });
1473
1474 // Apply welcome screen preference
1476 ui_coordinator_->SetWelcomeScreenVisible(false);
1477 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
1478 }
1479}
1480
1482 // Utility callbacks removed - now handled via EventBus
1483
1485 [this](bool visible, bool expanded) {
1486 user_settings_.prefs().sidebar_visible = visible;
1487 user_settings_.prefs().sidebar_panel_expanded = expanded;
1488 settings_dirty_ = true;
1490 });
1493 settings_dirty_ = true;
1495 });
1497 [this](float width) {
1499 settings_dirty_ = true;
1501 });
1502
1504 [this](const std::string& category) {
1505 if (category.empty() || category == PanelManager::kDashboardCategory) {
1506 return;
1507 }
1510
1511 const auto& prefs = user_settings_.prefs();
1512 auto it = prefs.panel_visibility_state.find(category);
1513 if (it != prefs.panel_visibility_state.end()) {
1515 panel_manager_.GetActiveSessionId(), it->second);
1516 } else {
1517 // No saved visibility state for this category yet.
1518 //
1519 // Apply LayoutPresets defaults only when this category has *no*
1520 // visible panels (editors may have already shown their own defaults,
1521 // e.g. Dungeon Workbench).
1522 const size_t session_id = panel_manager_.GetActiveSessionId();
1523 bool any_visible = false;
1524 for (const auto& desc :
1525 panel_manager_.GetPanelsInCategory(session_id, category)) {
1526 if (desc.visibility_flag && *desc.visibility_flag) {
1527 any_visible = true;
1528 break;
1529 }
1530 }
1531
1532 if (!any_visible) {
1533 const EditorType type =
1535 for (const auto& panel_id : LayoutPresets::GetDefaultPanels(type)) {
1536 panel_manager_.ShowPanel(session_id, panel_id);
1537 }
1538 }
1539 }
1540
1541 settings_dirty_ = true;
1543 });
1544
1546 [this](const std::string& category) -> Editor* {
1547 return ResolveEditorForCategory(category);
1548 });
1549
1550 panel_manager_.SetOnPanelClickedCallback([this](const std::string& category) {
1552 if (type != EditorType::kSettings && type != EditorType::kUnknown) {
1553 SwitchToEditor(type, true);
1554 }
1555 });
1556
1558 [this](const std::string& category) {
1559 if (ui_coordinator_) {
1560 ui_coordinator_->SetStartupSurface(StartupSurface::kEditor);
1561 }
1562
1563 if (category == "Agent") {
1564#ifdef YAZE_BUILD_AGENT_UI
1565 ShowAIAgent();
1566#endif
1567 return;
1568 }
1569
1571 if (type != EditorType::kSettings) {
1572 SwitchToEditor(type, true);
1573 }
1574 });
1575
1577
1579 [this](const std::string& category, const std::string& path) {
1580 if (category == "Assembly") {
1581 if (auto* editor_set = GetCurrentEditorSet()) {
1582 editor_set->ChangeActiveAssemblyFile(path);
1584 }
1585 }
1586 });
1587}
1588
1590 ShortcutDependencies shortcut_deps;
1591 shortcut_deps.editor_manager = this;
1592 shortcut_deps.editor_registry = &editor_registry_;
1593 shortcut_deps.menu_orchestrator = menu_orchestrator_.get();
1594 shortcut_deps.rom_file_manager = &rom_file_manager_;
1595 shortcut_deps.project_manager = &project_manager_;
1596 shortcut_deps.session_coordinator = session_coordinator_.get();
1597 shortcut_deps.ui_coordinator = ui_coordinator_.get();
1598 shortcut_deps.workspace_manager = &workspace_manager_;
1599 shortcut_deps.popup_manager = popup_manager_.get();
1600 shortcut_deps.toast_manager = &toast_manager_;
1601 shortcut_deps.panel_manager = &panel_manager_;
1602 shortcut_deps.user_settings = &user_settings_;
1603
1607}
1608
1610 const std::string& editor_name, const std::string& panels_str) {
1611 const bool has_editor = !editor_name.empty();
1612 const bool has_panels = !panels_str.empty();
1613
1614 if (!has_editor && !has_panels) {
1615 return;
1616 }
1617
1618 LOG_INFO("EditorManager",
1619 "Processing startup flags: editor='%s', panels='%s'",
1620 editor_name.c_str(), panels_str.c_str());
1621
1622 std::optional<EditorType> editor_type_to_open =
1623 has_editor ? ParseEditorTypeFromString(editor_name) : std::nullopt;
1624 if (has_editor && !editor_type_to_open.has_value()) {
1625 LOG_WARN("EditorManager", "Unknown editor specified via flag: %s",
1626 editor_name.c_str());
1627 } else if (editor_type_to_open.has_value()) {
1628 // Use EditorActivator to ensure layouts and default panels are initialized
1629 SwitchToEditor(*editor_type_to_open, true, /*from_dialog=*/true);
1630 }
1631
1632 // Open panels via PanelManager - works for any editor type
1633 if (!has_panels) {
1634 return;
1635 }
1636
1637 const size_t session_id = GetCurrentSessionId();
1638 std::string last_known_category = panel_manager_.GetActiveCategory();
1639 bool applied_category_from_panel = false;
1640
1641 for (absl::string_view token :
1642 absl::StrSplit(panels_str, ',', absl::SkipWhitespace())) {
1643 if (token.empty()) {
1644 continue;
1645 }
1646 std::string panel_name = std::string(absl::StripAsciiWhitespace(token));
1647 LOG_DEBUG("EditorManager", "Attempting to open panel: '%s'",
1648 panel_name.c_str());
1649
1650 const std::string lower_name = absl::AsciiStrToLower(panel_name);
1651 if (lower_name == "welcome" || lower_name == "welcome_screen") {
1652 if (ui_coordinator_) {
1653 ui_coordinator_->SetWelcomeScreenBehavior(StartupVisibility::kShow);
1654 }
1655 continue;
1656 }
1657 if (lower_name == "dashboard" || lower_name == "dashboard.main" ||
1658 lower_name == "editor_selection") {
1659 if (dashboard_panel_) {
1660 dashboard_panel_->Show();
1661 }
1662 if (ui_coordinator_) {
1663 ui_coordinator_->SetDashboardBehavior(StartupVisibility::kShow);
1664 }
1666 /*notify=*/false);
1667 continue;
1668 }
1669
1670 // Special case: "Room <id>" opens a dungeon room
1671 if (absl::StartsWith(panel_name, "Room ")) {
1672 if (auto* editor_set = GetCurrentEditorSet()) {
1673 try {
1674 int room_id = std::stoi(panel_name.substr(5));
1676 JumpToRoomRequestEvent::Create(room_id, session_id));
1677 } catch (const std::exception& e) {
1678 LOG_WARN("EditorManager", "Invalid room ID format: %s",
1679 panel_name.c_str());
1680 }
1681 }
1682 continue;
1683 }
1684
1685 std::optional<std::string> resolved_panel;
1686 if (panel_manager_.GetPanelDescriptor(session_id, panel_name)) {
1687 resolved_panel = panel_name;
1688 } else {
1689 for (const auto& [prefixed_id, descriptor] :
1691 const std::string base_id = StripSessionPrefix(prefixed_id);
1692 const std::string card_lower = absl::AsciiStrToLower(base_id);
1693 const std::string display_lower =
1694 absl::AsciiStrToLower(descriptor.display_name);
1695
1696 if (card_lower == lower_name || display_lower == lower_name) {
1697 resolved_panel = base_id;
1698 break;
1699 }
1700 }
1701 }
1702
1703 if (!resolved_panel.has_value()) {
1704 LOG_WARN("EditorManager",
1705 "Unknown panel '%s' from --open_panels (known count: %zu)",
1706 panel_name.c_str(),
1708 continue;
1709 }
1710
1711 if (panel_manager_.ShowPanel(session_id, *resolved_panel)) {
1712 const auto* descriptor =
1713 panel_manager_.GetPanelDescriptor(session_id, *resolved_panel);
1714 if (descriptor && !applied_category_from_panel &&
1715 descriptor->category != PanelManager::kDashboardCategory) {
1716 panel_manager_.SetActiveCategory(descriptor->category);
1717 applied_category_from_panel = true;
1718 } else if (!applied_category_from_panel && descriptor &&
1719 descriptor->category.empty() && !last_known_category.empty()) {
1720 panel_manager_.SetActiveCategory(last_known_category);
1721 }
1722 } else {
1723 LOG_WARN("EditorManager", "Failed to show panel '%s'",
1724 resolved_panel->c_str());
1725 }
1726 }
1727}
1728
1735
1737 constexpr int kTargetRevision =
1739 if (!user_settings_.ApplyPanelLayoutDefaultsRevision(kTargetRevision)) {
1740 return;
1741 }
1742
1744 settings_dirty_ = true;
1746
1747 LOG_INFO("EditorManager",
1748 "Applied panel layout defaults migration revision %d",
1749 kTargetRevision);
1750}
1751
1753 const std::string& saved_category,
1754 const std::vector<std::string>& available_categories) const {
1755 // If saved category is valid and not Emulator, use it directly
1756 if (!saved_category.empty() && saved_category != "Emulator") {
1757 // Validate it exists in available_categories if the list is provided
1758 if (available_categories.empty()) {
1759 return saved_category;
1760 }
1761 for (const auto& cat : available_categories) {
1762 if (cat == saved_category)
1763 return saved_category;
1764 }
1765 }
1766 // Pick first non-Emulator category from available list
1767 for (const auto& cat : available_categories) {
1768 if (cat != "Emulator")
1769 return cat;
1770 }
1771 return {};
1772}
1773
1777
1779 if (ui_coordinator_) {
1780 ui_coordinator_->SetWelcomeScreenBehavior(welcome_mode_override_);
1781 ui_coordinator_->SetDashboardBehavior(dashboard_mode_override_);
1782 }
1783
1785 const bool sidebar_visible =
1787 panel_manager_.SetSidebarVisible(sidebar_visible, /*notify=*/false);
1788 if (ui_coordinator_) {
1789 ui_coordinator_->SetPanelSidebarVisible(sidebar_visible);
1790 }
1791 }
1792
1793 // Force sidebar panel to collapse if Welcome Screen or Dashboard is explicitly shown
1794 // This prevents visual overlap/clutter on startup
1797 panel_manager_.SetPanelExpanded(false, /*notify=*/false);
1798 }
1799
1800 if (dashboard_panel_) {
1802 dashboard_panel_->Hide();
1804 dashboard_panel_->Show();
1805 }
1806 }
1807}
1808
1810 ApplyStartupVisibility(config);
1811 // Handle startup editor and panels
1812 std::string panels_str;
1813 for (size_t i = 0; i < config.open_panels.size(); ++i) {
1814 if (i > 0)
1815 panels_str += ",";
1816 panels_str += config.open_panels[i];
1817 }
1818 OpenEditorAndPanelsFromFlags(config.startup_editor, panels_str);
1819
1820 // Handle jump targets
1821 if (config.jump_to_room >= 0) {
1824 }
1825 if (config.jump_to_map >= 0) {
1828 }
1829}
1830
1831absl::Status EditorManager::LoadAssetsForMode(uint64_t loading_handle) {
1832 switch (asset_load_mode_) {
1834 return LoadAssetsLazy(loading_handle);
1837 default:
1838 return LoadAssets(loading_handle);
1839 }
1840}
1841
1843 if (!session) {
1844 return;
1845 }
1846 session->game_data_loaded = false;
1847 session->editor_initialized.fill(false);
1848 session->editor_assets_loaded.fill(false);
1849}
1850
1852 EditorType type) {
1853 if (!session) {
1854 return;
1855 }
1856 const size_t index = EditorTypeIndex(type);
1857 if (index < session->editor_initialized.size()) {
1858 session->editor_initialized[index] = true;
1859 }
1860}
1861
1863 if (!session) {
1864 return;
1865 }
1866 const size_t index = EditorTypeIndex(type);
1867 if (index < session->editor_assets_loaded.size()) {
1868 session->editor_assets_loaded[index] = true;
1869 }
1870}
1871
1873 switch (type) {
1881 return true;
1882 default:
1883 return false;
1884 }
1885}
1886
1890
1892 EditorSet* editor_set) const {
1893 return editor_set ? editor_set->GetEditor(type) : nullptr;
1894}
1895
1897 if (category.empty() || category == PanelManager::kDashboardCategory) {
1898 return nullptr;
1899 }
1900
1901 auto* editor_set = GetCurrentEditorSet();
1902 if (!editor_set) {
1903 return nullptr;
1904 }
1905
1907 switch (type) {
1908 case EditorType::kAgent:
1909#ifdef YAZE_BUILD_AGENT_UI
1910 return agent_ui_.GetAgentEditor();
1911#else
1912 return nullptr;
1913#endif
1917 return nullptr;
1918 default:
1919 return GetEditorByType(type, editor_set);
1920 }
1921}
1922
1923void EditorManager::SyncEditorContextForCategory(const std::string& category) {
1924 if (Editor* resolved = ResolveEditorForCategory(category)) {
1925 SetCurrentEditor(resolved);
1926 } else if (!category.empty() &&
1928 LOG_DEBUG("EditorManager", "No editor context available for category '%s'",
1929 category.c_str());
1930 }
1931}
1932
1934 EditorSet* editor_set,
1935 Rom* rom) {
1936 if (!editor_set) {
1937 return absl::FailedPreconditionError("No editor set available");
1938 }
1939
1940 auto* editor = GetEditorByType(type, editor_set);
1941 if (!editor) {
1942 return absl::OkStatus();
1943 }
1944 editor->Initialize();
1945 return absl::OkStatus();
1946}
1947
1949 auto* session = session_coordinator_
1950 ? session_coordinator_->GetActiveRomSession()
1951 : nullptr;
1952 if (!session) {
1953 return absl::FailedPreconditionError("No active session");
1954 }
1955 if (session->game_data_loaded) {
1956 return absl::OkStatus();
1957 }
1958 if (!session->rom.is_loaded()) {
1959 return absl::FailedPreconditionError("ROM not loaded");
1960 }
1961
1962 RETURN_IF_ERROR(zelda3::LoadGameData(session->rom, session->game_data));
1963 *gfx::Arena::Get().mutable_gfx_sheets() = session->game_data.gfx_bitmaps;
1964
1965 auto* game_data = &session->game_data;
1966 auto* editor_set = &session->editors;
1967 for (EditorType type :
1971 if (auto* editor = editor_set->GetEditor(type)) {
1972 editor->SetGameData(game_data);
1973 }
1974 }
1975
1977 session->game_data_loaded = true;
1978
1979 return absl::OkStatus();
1980}
1981
1984 return absl::OkStatus();
1985 }
1986
1987 if (type == EditorType::kUnknown) {
1988 return absl::OkStatus();
1989 }
1990
1991 auto* session = session_coordinator_
1992 ? session_coordinator_->GetActiveRomSession()
1993 : nullptr;
1994 if (!session || !session->rom.is_loaded()) {
1995 return absl::OkStatus();
1996 }
1997
1998 const size_t index = EditorTypeIndex(type);
1999 if (index >= session->editor_initialized.size()) {
2000 return absl::InvalidArgumentError("Invalid editor type");
2001 }
2002
2003 if (EditorInitRequiresGameData(type)) {
2005 }
2006
2007 if (!session->editor_initialized[index]) {
2009 InitializeEditorForType(type, &session->editors, &session->rom));
2010 MarkEditorInitialized(session, type);
2011 }
2012
2013 if (EditorRequiresGameData(type)) {
2015 }
2016
2017 if (!session->editor_assets_loaded[index]) {
2018 auto* editor = GetEditorByType(type, &session->editors);
2019 if (editor) {
2020 RETURN_IF_ERROR(editor->Load());
2021 }
2022 MarkEditorLoaded(session, type);
2023 }
2024
2025 return absl::OkStatus();
2026}
2027
2045 ProcessInput();
2047 DrawInterface();
2048
2049 return status_;
2050}
2051
2053 // Space/focus transitions can leave mid-animation surfaces ghosted.
2054 // Reset transient animation state to a stable endpoint.
2057 right_panel_manager_->OnHostVisibilityChanged(visible);
2058 }
2059}
2060
2062 // Update timing manager for accurate delta time across the application
2064
2065 // Execute keyboard shortcuts (registered via ShortcutConfigurator)
2067}
2068
2070 status_ = absl::OkStatus();
2071 // Check for layout rebuild requests and execute if needed (delegated to LayoutCoordinator)
2072 bool is_emulator_visible =
2073 ui_coordinator_ && ui_coordinator_->IsEmulatorVisible();
2074 EditorType current_type =
2076 layout_coordinator_.ProcessLayoutRebuild(current_type, is_emulator_visible);
2077
2078 // Periodic user settings auto-save
2079 if (settings_dirty_) {
2080 const float elapsed = TimingManager::Get().GetElapsedTime();
2081 if (elapsed - settings_dirty_timestamp_ >= 1.0f) {
2082 auto save_status = user_settings_.Save();
2083 if (!save_status.ok()) {
2084 LOG_WARN("EditorManager", "Failed to save user settings: %s",
2085 save_status.ToString().c_str());
2086 }
2087 settings_dirty_ = false;
2088 }
2089 }
2090
2091 // Update agent editor dashboard
2093
2094 // Ensure TestManager always has the current ROM
2095 static Rom* last_test_rom = nullptr;
2096 auto* current_rom = GetCurrentRom();
2097 if (last_test_rom != current_rom) {
2098 LOG_DEBUG(
2099 "EditorManager",
2100 "EditorManager::Update - ROM changed, updating TestManager: %p -> %p",
2101 (void*)last_test_rom, (void*)current_rom);
2103 last_test_rom = current_rom;
2104 }
2105
2106 // Autosave timer
2107 if (user_settings_.prefs().autosave_enabled && current_rom &&
2108 current_rom->dirty()) {
2109 autosave_timer_ += ImGui::GetIO().DeltaTime;
2111 autosave_timer_ = 0.0f;
2113 s.backup = true;
2114 s.save_new = false;
2115 auto st = current_rom->SaveToFile(s);
2116 if (st.ok()) {
2117 toast_manager_.Show("Autosave completed", editor::ToastType::kSuccess);
2118 } else {
2119 toast_manager_.Show(std::string(st.message()),
2121 }
2122 }
2123 } else {
2124 autosave_timer_ = 0.0f;
2125 }
2126
2127 // Update ROM context for agent UI
2128 if (current_rom && current_rom->is_loaded()) {
2129 agent_ui_.SetRomContext(current_rom);
2131 if (auto* editor_set = GetCurrentEditorSet()) {
2132 agent_ui_.SetAsarWrapperContext(editor_set->GetAsarWrapper());
2133 }
2134 }
2135
2136 // Delegate session updates to SessionCoordinator
2138 session_coordinator_->UpdateSessions();
2139 }
2140}
2141
2143
2144 // Draw editor selection dialog (managed by UICoordinator)
2145 if (ui_coordinator_ && ui_coordinator_->IsEditorSelectionVisible()) {
2146 dashboard_panel_->Show();
2147 dashboard_panel_->Draw();
2148 if (!dashboard_panel_->IsVisible()) {
2149 ui_coordinator_->SetEditorSelectionVisible(false);
2150 }
2151 }
2152
2153 // Draw ROM load options dialog (ZSCustomOverworld, feature flags, project)
2156 }
2157
2158 // Draw panel browser (managed by UICoordinator)
2159 if (ui_coordinator_ && ui_coordinator_->IsPanelBrowserVisible()) {
2160 bool show = true;
2161 if (activity_bar_) {
2162 activity_bar_->DrawPanelBrowser(GetCurrentSessionId(), &show);
2163 }
2164 if (!show) {
2165 ui_coordinator_->SetPanelBrowserVisible(false);
2166 }
2167 }
2168
2169 // Draw background grid effects
2170 if (ui_coordinator_) {
2171 ui_coordinator_->DrawBackground();
2172 }
2173
2174 // Draw UICoordinator UI components (Welcome Screen, Command Palette, etc.)
2175 if (ui_coordinator_) {
2176 ui_coordinator_->DrawAllUI();
2177 }
2178
2179 // Handle Welcome screen early-exit for rendering
2180 if (ui_coordinator_ && ui_coordinator_->ShouldShowWelcome()) {
2182 right_panel_manager_->ClosePanel();
2183 }
2184 return;
2185 }
2186
2189 RunEmulator();
2190
2191 // Draw sidebar
2192 if (ui_coordinator_ && ui_coordinator_->IsPanelSidebarVisible()) {
2193 auto all_categories = EditorRegistry::GetAllEditorCategories();
2194 std::unordered_set<std::string> active_editor_categories;
2195
2196 if (auto* current_editor_set = GetCurrentEditorSet()) {
2198 for (size_t session_idx = 0;
2199 session_idx < session_coordinator_->GetTotalSessionCount();
2200 ++session_idx) {
2201 auto* session = static_cast<RomSession*>(
2202 session_coordinator_->GetSession(session_idx));
2203 if (!session || !session->rom.is_loaded()) {
2204 continue;
2205 }
2206
2207 for (auto* editor : session->editors.active_editors_) {
2208 if (*editor->active() && IsPanelBasedEditor(editor->type())) {
2209 std::string category =
2210 EditorRegistry::GetEditorCategory(editor->type());
2211 active_editor_categories.insert(category);
2212 }
2213 }
2214 }
2215
2216 if (ui_coordinator_->IsEmulatorVisible()) {
2217 active_editor_categories.insert("Emulator");
2218 }
2219 }
2220 }
2221
2222 std::string sidebar_category = panel_manager_.GetActiveCategory();
2223 if (sidebar_category.empty() && !all_categories.empty()) {
2224 sidebar_category = GetPreferredStartupCategory("", all_categories);
2225 if (!sidebar_category.empty()) {
2226 panel_manager_.SetActiveCategory(sidebar_category, /*notify=*/false);
2227 SyncEditorContextForCategory(sidebar_category);
2229 sidebar_category);
2230 if (it != user_settings_.prefs().panel_visibility_state.end()) {
2232 panel_manager_.GetActiveSessionId(), it->second);
2233 }
2234 }
2235 }
2236
2237 auto has_rom_callback = [this]() -> bool {
2238 auto* rom = GetCurrentRom();
2239 return rom && rom->is_loaded();
2240 };
2241
2242 if (activity_bar_ && ui_coordinator_->ShouldShowActivityBar()) {
2243 activity_bar_->Render(GetCurrentSessionId(), sidebar_category,
2244 all_categories, active_editor_categories,
2245 has_rom_callback);
2246 }
2247 }
2248
2249 // Draw right panel
2252 right_panel_manager_->Draw();
2253 }
2254
2255 // Update and draw status bar
2259 session_coordinator_->GetActiveSessionCount());
2260 }
2261
2262 bool has_agent_info = false;
2263#if defined(YAZE_BUILD_AGENT_UI)
2264 if (auto* agent_editor = agent_ui_.GetAgentEditor()) {
2265 auto* chat = agent_editor->GetAgentChat();
2266 const auto* ctx = agent_ui_.GetContext();
2267 if (ctx) {
2268 const auto& config = ctx->agent_config();
2269 bool active = chat && *chat->active();
2270 status_bar_.SetAgentInfo(config.ai_provider, config.ai_model, active);
2271 has_agent_info = true;
2272 }
2273 }
2274#endif
2275 if (!has_agent_info) {
2277 }
2278 status_bar_.Draw();
2279
2280 // Check if ROM is loaded before drawing panels
2281 auto* current_editor_set = GetCurrentEditorSet();
2282 if (!current_editor_set || !GetCurrentRom()) {
2283 if (panel_manager_.GetActiveCategory() == "Agent") {
2285 }
2286 return;
2287 }
2288
2289 // Central panel drawing
2291
2292 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
2294 }
2295
2296 // Draw SessionCoordinator UI components
2298 session_coordinator_->DrawSessionSwitcher();
2299 session_coordinator_->DrawSessionManager();
2300 session_coordinator_->DrawSessionRenameDialog();
2301 }
2302}
2303
2305 if (ImGui::BeginMenuBar()) {
2306 // Consistent button styling for sidebar toggle
2307 {
2308 const bool sidebar_visible = panel_manager_.IsSidebarVisible();
2309 gui::StyleColorGuard sidebar_btn_guard(
2310 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
2311 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
2312 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
2313 {ImGuiCol_Text, sidebar_visible ? gui::GetPrimaryVec4()
2315
2316 const char* icon = sidebar_visible ? ICON_MD_MENU_OPEN : ICON_MD_MENU;
2317 if (ImGui::SmallButton(icon)) {
2319 }
2320 }
2321
2322 if (ImGui::IsItemHovered()) {
2323 const char* tooltip = panel_manager_.IsSidebarVisible()
2324 ? "Hide Activity Bar (Ctrl+B)"
2325 : "Show Activity Bar (Ctrl+B)";
2326 ImGui::SetTooltip("%s", tooltip);
2327 }
2328
2329 // Delegate menu building to MenuOrchestrator
2330 if (menu_orchestrator_) {
2331 menu_orchestrator_->BuildMainMenu();
2332 }
2333
2334 // Delegate menu bar extras to UICoordinator
2335 if (ui_coordinator_) {
2336 ui_coordinator_->DrawMenuBarExtras();
2337 }
2338
2339 ImGui::EndMenuBar();
2340 }
2341}
2342
2344
2345 // ImGui debug windows
2346 if (ui_coordinator_) {
2347 if (ui_coordinator_->IsImGuiDemoVisible()) {
2348 bool visible = true;
2349 ImGui::ShowDemoWindow(&visible);
2350 if (!visible)
2351 ui_coordinator_->SetImGuiDemoVisible(false);
2352 }
2353
2354 if (ui_coordinator_->IsImGuiMetricsVisible()) {
2355 bool visible = true;
2356 ImGui::ShowMetricsWindow(&visible);
2357 if (!visible)
2358 ui_coordinator_->SetImGuiMetricsVisible(false);
2359 }
2360 }
2361
2362 // Legacy window-based editors
2363 if (auto* editor_set = GetCurrentEditorSet()) {
2364 bool* hex_visibility =
2365 panel_manager_.GetVisibilityFlag("memory.hex_editor");
2366 if (hex_visibility) {
2367 if (auto* editor = editor_set->GetEditor(EditorType::kHex)) {
2368 // Keep the legacy panel visibility flag in sync with the window close
2369 // button (ImGui::Begin will toggle Editor::active_).
2370 editor->set_active(*hex_visibility);
2371 editor->Update();
2372 *hex_visibility = *editor->active();
2373 }
2374 }
2375
2376 if (ui_coordinator_ && ui_coordinator_->IsAsmEditorVisible()) {
2377 if (auto* editor = editor_set->GetEditor(EditorType::kAssembly)) {
2378 editor->set_active(true);
2379 editor->Update();
2380 if (!*editor->active()) {
2381 ui_coordinator_->SetAsmEditorVisible(false);
2382 }
2383 }
2384 }
2385 }
2386
2387 // Project and performance tools
2389
2390 if (ui_coordinator_ && ui_coordinator_->IsPerformanceDashboardVisible()) {
2394 if (!gfx::PerformanceDashboard::Get().IsVisible()) {
2395 ui_coordinator_->SetPerformanceDashboardVisible(false);
2396 }
2397 }
2398
2399#ifdef YAZE_ENABLE_TESTING
2400 if (show_test_dashboard_) {
2402 test::TestManager::Get().DrawTestDashboard(&show_test_dashboard_);
2403 }
2404#endif
2405}
2406
2408 // Update proposal drawer context
2410
2411 // Agent UI popups
2413
2414 // Resource label management
2415 if (ui_coordinator_ && ui_coordinator_->IsResourceLabelManagerVisible() &&
2416 GetCurrentRom()) {
2417 bool visible = true;
2423 }
2424 if (!visible)
2425 ui_coordinator_->SetResourceLabelManagerVisible(false);
2426 }
2427
2428 // Layout presets
2429 if (ui_coordinator_) {
2430 ui_coordinator_->DrawLayoutPresets();
2431 }
2432}
2433
2435 if (auto* current_rom = GetCurrentRom()) {
2436 if (ui_coordinator_ && ui_coordinator_->IsEmulatorVisible()) {
2437 emulator_.Run(current_rom);
2438 } else if (emulator_.running() && emulator_.is_snes_initialized()) {
2441 } else {
2443 }
2444 }
2445 }
2446}
2447
2471 auto load_from_path = [this](const std::string& file_name) -> absl::Status {
2472 if (file_name.empty()) {
2473 return absl::OkStatus();
2474 }
2475
2476 // Check if this is a project file - route to project loading
2477 if (absl::EndsWith(file_name, ".yaze") ||
2478 absl::EndsWith(file_name, ".zsproj") ||
2479 absl::EndsWith(file_name, ".yazeproj")) {
2480 return OpenRomOrProject(file_name);
2481 }
2482
2483 if (session_coordinator_->HasDuplicateSession(file_name)) {
2484 toast_manager_.Show("ROM already open in another session",
2486 return absl::OkStatus();
2487 }
2488
2489 // Delegate ROM loading to RomFileManager
2490 Rom temp_rom;
2491 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name));
2492
2493 auto session_or = session_coordinator_->CreateSessionFromRom(
2494 std::move(temp_rom), file_name);
2495 if (!session_or.ok()) {
2496 return session_or.status();
2497 }
2498
2502
2506
2507 // Keep ResourceLabelProvider in sync with the newly-active ROM session
2508 // before any editors/assets query room/sprite names.
2510
2511#ifdef YAZE_ENABLE_TESTING
2513#endif
2514
2516 const auto& recent_files = manager.GetRecentFiles();
2517 const bool is_first_time_rom_path =
2518 std::find(recent_files.begin(), recent_files.end(), file_name) ==
2519 recent_files.end();
2520 manager.AddFile(file_name);
2521 manager.Save();
2522
2524
2525 if (ui_coordinator_) {
2526 ui_coordinator_->SetWelcomeScreenVisible(false);
2527
2528 // Show ROM load options and bias new ROM paths toward project creation.
2529 rom_load_options_dialog_.Open(GetCurrentRom(), is_first_time_rom_path);
2531 }
2532
2533 return absl::OkStatus();
2534 };
2535
2536#if defined(__APPLE__) && TARGET_OS_IOS == 1
2539 [this, load_from_path](const std::string& file_name) {
2540 auto status = load_from_path(file_name);
2541 if (!status.ok()) {
2543 absl::StrFormat("Failed to load ROM: %s", status.message()),
2545 }
2546 });
2547 return absl::OkStatus();
2548#else
2551 return load_from_path(file_name);
2552#endif
2553}
2554
2555absl::Status EditorManager::LoadAssets(uint64_t passed_handle) {
2556 auto* current_rom = GetCurrentRom();
2557 auto* current_editor_set = GetCurrentEditorSet();
2558 if (!current_rom || !current_editor_set) {
2559 return absl::FailedPreconditionError("No ROM or editor set loaded");
2560 }
2561
2562 auto* current_session = session_coordinator_->GetActiveRomSession();
2563 if (!current_session) {
2564 return absl::FailedPreconditionError("No active ROM session");
2565 }
2566 ResetAssetState(current_session);
2567
2568 auto start_time = std::chrono::steady_clock::now();
2569
2570#ifdef __EMSCRIPTEN__
2571 // Use passed handle if provided, otherwise create new one
2572 auto loading_handle =
2573 passed_handle != 0
2574 ? static_cast<app::platform::WasmLoadingManager::LoadingHandle>(
2575 passed_handle)
2576 : app::platform::WasmLoadingManager::BeginLoading(
2577 "Loading Editor Assets");
2578
2579 // Progress starts at 10% (ROM already loaded), goes to 100%
2580 constexpr float kStartProgress = 0.10f;
2581 constexpr float kEndProgress = 1.0f;
2582 constexpr int kTotalSteps = 11; // Graphics + 8 editors + profiler + finish
2583 int current_step = 0;
2584 auto update_progress = [&](const std::string& message) {
2585 current_step++;
2586 float progress =
2587 kStartProgress + (kEndProgress - kStartProgress) *
2588 (static_cast<float>(current_step) / kTotalSteps);
2589 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, progress);
2590 app::platform::WasmLoadingManager::UpdateMessage(loading_handle, message);
2591 };
2592 // RAII guard to ensure loading indicator is closed even on early return
2593 auto cleanup_loading = [&]() {
2594 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2595 };
2596 struct LoadingGuard {
2597 std::function<void()> cleanup;
2598 bool dismissed = false;
2599 ~LoadingGuard() {
2600 if (!dismissed)
2601 cleanup();
2602 }
2603 void dismiss() { dismissed = true; }
2604 } loading_guard{cleanup_loading};
2605#else
2606 (void)passed_handle; // Unused on non-WASM
2607#endif
2608
2609 // Set renderer for emulator (lazy initialization happens in Run())
2610 if (renderer_) {
2612 }
2613
2614 // Initialize all editors - this registers their cards with PanelManager
2615 // and sets up any editor-specific resources. Must be called before Load().
2616 struct InitStep {
2617 EditorType type;
2618 bool mark_loaded;
2619 };
2620 const InitStep init_steps[] = {
2622 {EditorType::kGraphics, false}, {EditorType::kScreen, false},
2623 {EditorType::kSprite, false}, {EditorType::kPalette, false},
2624 {EditorType::kAssembly, true}, {EditorType::kMusic, false},
2625 {EditorType::kDungeon, false},
2626 };
2627 for (const auto& step : init_steps) {
2628 if (auto* editor = current_editor_set->GetEditor(step.type)) {
2629 editor->Initialize();
2630 MarkEditorInitialized(current_session, step.type);
2631 if (step.mark_loaded) {
2632 MarkEditorLoaded(current_session, step.type);
2633 }
2634 }
2635 }
2636
2637#ifdef __EMSCRIPTEN__
2638 update_progress("Loading graphics sheets...");
2639#endif
2640 // Load all Zelda3-specific data (metadata, palettes, gfx groups, graphics)
2642 zelda3::LoadGameData(*current_rom, current_session->game_data));
2643 current_session->game_data_loaded = true;
2644
2645 // Copy loaded graphics to Arena for global access
2647 current_session->game_data.gfx_bitmaps;
2648
2649 // Propagate GameData to all editors that need it
2650 auto* game_data = &current_session->game_data;
2651 for (EditorType type :
2655 if (auto* editor = current_editor_set->GetEditor(type)) {
2656 editor->SetGameData(game_data);
2657 }
2658 }
2659
2660 struct LoadStep {
2661 EditorType type;
2662 const char* progress_message;
2663 };
2664 const LoadStep load_steps[] = {
2665 {EditorType::kOverworld, "Loading overworld..."},
2666 {EditorType::kDungeon, "Loading dungeons..."},
2667 {EditorType::kScreen, "Loading screen editor..."},
2668 {EditorType::kGraphics, "Loading graphics editor..."},
2669 {EditorType::kSprite, "Loading sprites..."},
2670 {EditorType::kMessage, "Loading messages..."},
2671 {EditorType::kMusic, "Loading music..."},
2672 {EditorType::kPalette, "Loading palettes..."},
2673 };
2674 for (const auto& step : load_steps) {
2675#ifdef __EMSCRIPTEN__
2676 update_progress(step.progress_message);
2677#endif
2678 if (auto* editor = current_editor_set->GetEditor(step.type)) {
2679 RETURN_IF_ERROR(editor->Load());
2680 MarkEditorLoaded(current_session, step.type);
2681 }
2682 }
2683
2684#ifdef __EMSCRIPTEN__
2685 update_progress("Finishing up...");
2686#endif
2687
2688 // Set up RightPanelManager with session's settings editor
2690 auto* settings = current_editor_set->GetSettingsPanel();
2691 right_panel_manager_->SetSettingsPanel(settings);
2692 }
2693
2694 // Apply user preferences to status bar
2696
2698
2699#ifdef __EMSCRIPTEN__
2700 // Dismiss the guard and manually close - we completed successfully
2701 loading_guard.dismiss();
2702 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2703#endif
2704
2705 auto end_time = std::chrono::steady_clock::now();
2706 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
2707 end_time - start_time);
2708 LOG_DEBUG("EditorManager", "ROM assets loaded in %lld ms", duration.count());
2709
2710 return absl::OkStatus();
2711}
2712
2713absl::Status EditorManager::LoadAssetsLazy(uint64_t passed_handle) {
2714 auto* current_rom = GetCurrentRom();
2715 auto* current_editor_set = GetCurrentEditorSet();
2716 if (!current_rom || !current_editor_set) {
2717 return absl::FailedPreconditionError("No ROM or editor set loaded");
2718 }
2719
2720 auto* current_session = session_coordinator_->GetActiveRomSession();
2721 if (!current_session) {
2722 return absl::FailedPreconditionError("No active ROM session");
2723 }
2724 ResetAssetState(current_session);
2725
2726#ifdef __EMSCRIPTEN__
2727 // Use passed handle if provided, otherwise create new one
2728 auto loading_handle =
2729 passed_handle != 0
2730 ? static_cast<app::platform::WasmLoadingManager::LoadingHandle>(
2731 passed_handle)
2732 : app::platform::WasmLoadingManager::BeginLoading(
2733 "Loading ROM (lazy assets)");
2734 auto cleanup_loading = [&]() {
2735 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2736 };
2737 struct LoadingGuard {
2738 std::function<void()> cleanup;
2739 bool dismissed = false;
2740 ~LoadingGuard() {
2741 if (!dismissed) {
2742 cleanup();
2743 }
2744 }
2745 void dismiss() { dismissed = true; }
2746 } loading_guard{cleanup_loading};
2747#else
2748 (void)passed_handle; // Unused on non-WASM
2749#endif
2750
2751 // Set renderer for emulator (lazy initialization happens in Run())
2752 if (renderer_) {
2754 }
2755
2756 // Wire settings panel to right panel manager for the current session.
2758 auto* settings = current_editor_set->GetSettingsPanel();
2759 right_panel_manager_->SetSettingsPanel(settings);
2760 }
2761
2762 // Apply user preferences to status bar
2764
2765#ifdef __EMSCRIPTEN__
2766 loading_guard.dismiss();
2767 app::platform::WasmLoadingManager::EndLoading(loading_handle);
2768#endif
2769
2770 LOG_INFO("EditorManager", "Lazy asset mode: editor assets deferred");
2771 return absl::OkStatus();
2772}
2773
2792
2796
2798 auto* current_rom = GetCurrentRom();
2799 auto* current_editor_set = GetCurrentEditorSet();
2800 if (!current_rom || !current_editor_set) {
2801 return absl::FailedPreconditionError("No ROM or editor set loaded");
2802 }
2803
2804 // --- State machine checks (delegated to RomLifecycleManager) ---
2806 return absl::CancelledError("Save pending confirmation");
2807 }
2808
2810
2812 return absl::CancelledError("Save pending confirmation");
2813 }
2814
2815 const bool pot_items_enabled =
2818 !rom_lifecycle_.ShouldSuppressPotItemSave() && pot_items_enabled) {
2819 const int loaded_rooms = current_editor_set->LoadedDungeonRoomCount();
2820 const int total_rooms = current_editor_set->TotalDungeonRoomCount();
2821 if (loaded_rooms < total_rooms) {
2822 rom_lifecycle_.SetPotItemConfirmPending(total_rooms - loaded_rooms,
2823 total_rooms);
2824 if (popup_manager_) {
2826 }
2828 absl::StrFormat(
2829 "Save paused: pot items enabled with %d unloaded rooms",
2832 return absl::CancelledError("Pot item save confirmation required");
2833 }
2834 }
2835
2836 const bool bypass_confirm = rom_lifecycle_.ShouldBypassPotItemConfirm();
2837 const bool suppress_pot_items = rom_lifecycle_.ShouldSuppressPotItemSave();
2839
2840 struct PotItemFlagGuard {
2841 bool restore = false;
2842 bool previous = false;
2843 ~PotItemFlagGuard() {
2844 if (restore) {
2845 core::FeatureFlags::get().dungeon.kSavePotItems = previous;
2846 }
2847 }
2848 } pot_item_guard;
2849
2850 if (suppress_pot_items) {
2851 pot_item_guard.previous = core::FeatureFlags::get().dungeon.kSavePotItems;
2852 pot_item_guard.restore = true;
2854 } else if (bypass_confirm) {
2855 // Explicitly allow pot item save once after confirmation.
2856 }
2857
2858 // --- Backup policy setup ---
2866 } else {
2868 user_settings_.prefs().backup_before_save, "", 20, true, 14);
2869 }
2870
2871 // --- Save editor-specific data ---
2872 if (auto* editor = current_editor_set->GetEditor(EditorType::kScreen)) {
2873 RETURN_IF_ERROR(editor->Save());
2874 }
2875
2876 if (auto* editor = current_editor_set->GetEditor(EditorType::kDungeon)) {
2877 RETURN_IF_ERROR(editor->Save());
2878 }
2879
2880 if (auto* editor = current_editor_set->GetEditor(EditorType::kOverworld)) {
2881 RETURN_IF_ERROR(editor->Save());
2882 }
2883
2884 if (core::FeatureFlags::get().kSaveMessages) {
2886 if (auto* editor = current_editor_set->GetEditor(EditorType::kMessage)) {
2887 RETURN_IF_ERROR(editor->Save());
2888 }
2889 }
2890
2891 if (core::FeatureFlags::get().kSaveGraphicsSheet) {
2893 *current_rom, gfx::Arena::Get().gfx_sheets()));
2894 }
2895
2896 // Oracle guardrails: refuse to write obviously corrupted ROM layouts.
2898
2899 // --- Write conflict check (ASM-owned address protection) ---
2903 std::vector<std::pair<uint32_t, uint32_t>> write_ranges;
2904 bool diff_computed = false;
2905
2906 if (!current_rom->filename().empty()) {
2907 std::ifstream file(current_rom->filename(), std::ios::binary);
2908 if (file.is_open()) {
2909 file.seekg(0, std::ios::end);
2910 const std::streampos end = file.tellg();
2911 if (end >= 0) {
2912 std::vector<uint8_t> disk_data(static_cast<size_t>(end));
2913 file.seekg(0, std::ios::beg);
2914 file.read(reinterpret_cast<char*>(disk_data.data()),
2915 static_cast<std::streamsize>(disk_data.size()));
2916 if (file) {
2917 diff_computed = true;
2918 auto diff = yaze::rom::ComputeDiffRanges(
2919 disk_data, current_rom->vector());
2920 if (!diff.ranges.empty()) {
2921 LOG_DEBUG("EditorManager",
2922 "ROM save diff: %zu bytes changed in %zu range(s)",
2923 diff.total_bytes_changed, diff.ranges.size());
2924 write_ranges = std::move(diff.ranges);
2925 }
2926 }
2927 }
2928 }
2929 }
2930
2931 if (write_ranges.empty() && !diff_computed) {
2932 write_ranges = current_editor_set->CollectDungeonWriteRanges();
2933 }
2934
2935 if (!write_ranges.empty()) {
2936 auto conflicts =
2938 if (!conflicts.empty()) {
2939 rom_lifecycle_.SetPendingWriteConflicts(std::move(conflicts));
2940 if (popup_manager_) {
2942 }
2944 absl::StrFormat(
2945 "Save paused: %zu write conflict(s) with ASM hooks",
2948 return absl::CancelledError(
2949 "Write conflict confirmation required");
2950 }
2951 }
2952 } else {
2953 // Bypass is single-use, set by the warning popup.
2955 }
2956 }
2957
2958 // Delegate final ROM file writing to RomFileManager
2959 auto save_status = rom_file_manager_.SaveRom(current_rom);
2960 if (save_status.ok()) {
2961 // Write-confirm bypass is single-use. Clear it after a successful save.
2963 }
2964 return save_status;
2965}
2966
2967absl::Status EditorManager::SaveRomAs(const std::string& filename) {
2968 auto* current_rom = GetCurrentRom();
2969 if (!current_rom) {
2970 return absl::FailedPreconditionError("No ROM loaded");
2971 }
2972
2973 // Reuse SaveRom() logic for editor-specific data saving
2975
2976 // Now save to the new filename
2977 auto save_status = rom_file_manager_.SaveRomAs(current_rom, filename);
2978 if (save_status.ok()) {
2979 // Update session filepath
2981 auto* session = session_coordinator_->GetActiveRomSession();
2982 if (session) {
2983 session->filepath = filename;
2984 }
2985 }
2986
2987 // Add to recent files
2989 manager.AddFile(filename);
2990 manager.Save();
2991 }
2992
2993 return save_status;
2994}
2995
2996absl::Status EditorManager::OpenRomOrProject(const std::string& filename) {
2997 LOG_INFO("EditorManager", "OpenRomOrProject called with: '%s'",
2998 filename.c_str());
2999 if (filename.empty()) {
3000 LOG_INFO("EditorManager", "Empty filename provided, skipping load.");
3001 return absl::OkStatus();
3002 }
3003
3004#ifdef __EMSCRIPTEN__
3005 // Start loading indicator early for WASM builds
3006 auto loading_handle =
3007 app::platform::WasmLoadingManager::BeginLoading("Loading ROM");
3008 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3009 "Reading ROM file...");
3010 // RAII guard to ensure loading indicator is closed even on early return
3011 struct LoadingGuard {
3012 app::platform::WasmLoadingManager::LoadingHandle handle;
3013 bool dismissed = false;
3014 ~LoadingGuard() {
3015 if (!dismissed)
3016 app::platform::WasmLoadingManager::EndLoading(handle);
3017 }
3018 void dismiss() { dismissed = true; }
3019 } loading_guard{loading_handle};
3020#endif
3021
3022 if (absl::EndsWith(filename, ".yaze") ||
3023 absl::EndsWith(filename, ".zsproj") ||
3024 absl::EndsWith(filename, ".yazeproj")) {
3025 // Open the project file
3028
3029 // Initialize VersionManager for the project
3031 std::make_unique<core::VersionManager>(&current_project_);
3032 version_manager_->InitializeGit(); // Try to init git if configured
3033
3034 // Load ROM directly from project - don't prompt user
3035 return LoadProjectWithRom();
3036 } else {
3037#ifdef __EMSCRIPTEN__
3038 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.05f);
3039 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3040 "Loading ROM data...");
3041#endif
3042 Rom temp_rom;
3043 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, filename));
3044
3045 auto session_or = session_coordinator_->CreateSessionFromRom(
3046 std::move(temp_rom), filename);
3047 if (!session_or.ok()) {
3048 return session_or.status();
3049 }
3050 RomSession* session = *session_or;
3051
3055
3056 // Apply project feature flags to both session and global singleton
3059
3060 // Keep ResourceLabelProvider in sync with the active ROM session before
3061 // editors register room/sprite labels.
3063
3064 // Update test manager with current ROM for ROM-dependent tests (only when
3065 // tests are enabled)
3066#ifdef YAZE_ENABLE_TESTING
3067 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
3068 (void*)GetCurrentRom(),
3069 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
3071#endif
3072
3073 if (auto* editor_set = GetCurrentEditorSet();
3074 editor_set && !current_project_.code_folder.empty()) {
3075 const std::string absolute_code_folder =
3077 // iOS: avoid blocking the main thread during project open / scene updates.
3078 // Large iCloud-backed projects can trigger watchdog termination if we
3079 // eagerly enumerate folders here.
3080#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3081 editor_set->OpenAssemblyFolder(absolute_code_folder);
3082#endif
3083 // Also set the sidebar file browser path (refresh happens during UI draw).
3084 panel_manager_.SetFileBrowserPath("Assembly", absolute_code_folder);
3085 }
3086
3087#ifdef __EMSCRIPTEN__
3088 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, 0.10f);
3089 app::platform::WasmLoadingManager::UpdateMessage(loading_handle,
3090 "Initializing editors...");
3091 // Pass the loading handle to LoadAssets and dismiss our guard
3092 // LoadAssets will manage closing the indicator when done
3093 loading_guard.dismiss();
3094 RETURN_IF_ERROR(LoadAssetsForMode(loading_handle));
3095#else
3097#endif
3098
3099 // Hide welcome screen and show editor selection when ROM is loaded
3100 ui_coordinator_->SetWelcomeScreenVisible(false);
3101 // dashboard_panel_->ClearRecentEditors();
3102 ui_coordinator_->SetEditorSelectionVisible(true);
3103
3104 // Set Dashboard category to suppress panel drawing until user selects an editor
3106 /*notify=*/false);
3107 }
3108 return absl::OkStatus();
3109}
3110
3111absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
3112 // Delegate to ProjectManager
3113 auto status = project_manager_.CreateNewProject(template_name);
3114 if (status.ok()) {
3117
3118 // Trigger ROM selection dialog - projects need a ROM to be useful
3119 // LoadRom() opens file dialog and shows ROM load options when ROM is loaded
3120 status = LoadRom();
3121#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3122 if (status.ok() && ui_coordinator_) {
3123 ui_coordinator_->SetWelcomeScreenVisible(false);
3124 ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
3125 }
3126#endif
3127 }
3128 return status;
3129}
3130
3132 auto open_project_from_path =
3133 [this](const std::string& file_path) -> absl::Status {
3134 if (file_path.empty()) {
3135 return absl::OkStatus();
3136 }
3137
3138 project::YazeProject new_project;
3139 RETURN_IF_ERROR(new_project.Open(file_path));
3140
3141 // Validate project
3142 auto validation_status = new_project.Validate();
3143 if (!validation_status.ok()) {
3144 toast_manager_.Show(absl::StrFormat("Project validation failed: %s",
3145 validation_status.message()),
3147
3148 // Ask user if they want to repair
3149 popup_manager_->Show("Project Repair");
3150 }
3151
3152 current_project_ = std::move(new_project);
3154
3155 // Initialize VersionManager for the project
3157 std::make_unique<core::VersionManager>(&current_project_);
3158 version_manager_->InitializeGit();
3159
3160 return LoadProjectWithRom();
3161 };
3162
3163#if defined(__APPLE__) && TARGET_OS_IOS == 1
3164 // On iOS, route project selection through the SwiftUI overlay document picker
3165 // so we get open-in-place + security-scoped access for iCloud Drive bundles.
3166 platform::ios::PostOverlayCommand("open_project");
3167 return absl::OkStatus();
3168#else
3170 return open_project_from_path(file_path);
3171#endif
3172}
3173
3175 // Check if project has a ROM file specified
3176 if (current_project_.rom_filename.empty()) {
3177 // No ROM specified - prompt user to select one
3179 "Project has no ROM file configured. Please select a ROM.",
3181#if defined(__APPLE__) && TARGET_OS_IOS == 1
3184 [this](const std::string& rom_path) {
3185 if (rom_path.empty()) {
3186 return;
3187 }
3188 current_project_.rom_filename = rom_path;
3189 auto save_status = current_project_.Save();
3190 if (!save_status.ok()) {
3192 absl::StrFormat("Failed to update project ROM: %s",
3193 save_status.message()),
3195 return;
3196 }
3197 auto status = LoadProjectWithRom();
3198 if (!status.ok()) {
3200 absl::StrFormat("Failed to load project ROM: %s",
3201 status.message()),
3203 }
3204 });
3205 return absl::OkStatus();
3206#else
3209 if (rom_path.empty()) {
3210 return absl::OkStatus();
3211 }
3212 current_project_.rom_filename = rom_path;
3213 // Save updated project
3215#endif
3216 }
3217
3218 // Load ROM from project
3219 Rom temp_rom;
3220 auto load_status =
3222 if (!load_status.ok()) {
3223 // ROM file not found or invalid - prompt user to select new ROM
3225 absl::StrFormat("Could not load ROM '%s': %s. Please select a new ROM.",
3226 current_project_.rom_filename, load_status.message()),
3228#if defined(__APPLE__) && TARGET_OS_IOS == 1
3231 [this](const std::string& rom_path) {
3232 if (rom_path.empty()) {
3233 return;
3234 }
3235 current_project_.rom_filename = rom_path;
3236 auto save_status = current_project_.Save();
3237 if (!save_status.ok()) {
3239 absl::StrFormat("Failed to update project ROM: %s",
3240 save_status.message()),
3242 return;
3243 }
3244 auto status = LoadProjectWithRom();
3245 if (!status.ok()) {
3247 absl::StrFormat("Failed to load project ROM: %s",
3248 status.message()),
3250 }
3251 });
3252 return absl::OkStatus();
3253#else
3256 if (rom_path.empty()) {
3257 return absl::OkStatus();
3258 }
3259 current_project_.rom_filename = rom_path;
3261 RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, rom_path));
3262#endif
3263 }
3264
3265 auto session_or = session_coordinator_->CreateSessionFromRom(
3266 std::move(temp_rom), current_project_.rom_filename);
3267 if (!session_or.ok()) {
3268 return session_or.status();
3269 }
3270 RomSession* session = *session_or;
3271
3275
3276 // Auto-enable custom object rendering when a project defines custom object
3277 // data but the stale feature flag is off.
3278 if (ProjectUsesCustomObjects(current_project_) &&
3281 LOG_WARN("EditorManager",
3282 "Project has custom object data but 'enable_custom_objects' was "
3283 "disabled. Enabling at runtime.");
3284 toast_manager_.Show("Custom object rendering auto-enabled for this project",
3286 }
3287
3288 // Apply project feature flags to both session and global singleton.
3292#if !defined(NDEBUG)
3293 LOG_INFO(
3294 "EditorManager",
3295 "Feature flags applied: kEnableCustomObjects=%s, "
3296 "custom_objects_folder='%s', custom_object_files=%zu entries",
3300#endif
3301
3307 } else {
3309 }
3314 }
3325
3326 if (auto* rom = GetCurrentRom(); rom && rom->is_loaded()) {
3327 if (IsRomHashMismatch()) {
3329 "Project ROM hash mismatch detected. Check ROM Identity settings.",
3331 }
3332 auto warnings = ValidateRomAddressOverrides(
3334 if (!warnings.empty()) {
3335 for (const auto& warning : warnings) {
3336 LOG_WARN("EditorManager", "%s", warning.c_str());
3337 }
3338 toast_manager_.Show(absl::StrFormat("ROM override warnings: %d (see log)",
3339 warnings.size()),
3341 }
3342 }
3343
3344 // Update test manager with current ROM for ROM-dependent tests (only when
3345 // tests are enabled)
3346#ifdef YAZE_ENABLE_TESTING
3347 LOG_DEBUG("EditorManager", "Setting ROM in TestManager - %p ('%s')",
3348 (void*)GetCurrentRom(),
3349 GetCurrentRom() ? GetCurrentRom()->title().c_str() : "null");
3351#endif
3352
3353 if (auto* editor_set = GetCurrentEditorSet();
3354 editor_set && !current_project_.code_folder.empty()) {
3355 const std::string absolute_code_folder =
3357 // iOS: avoid blocking the main thread during project open / scene updates.
3358#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
3359 editor_set->OpenAssemblyFolder(absolute_code_folder);
3360#endif
3361 // Also set the sidebar file browser path (refresh happens during UI draw).
3362 panel_manager_.SetFileBrowserPath("Assembly", absolute_code_folder);
3363 }
3364
3365 // Initialize labels before loading editor assets so room lists / command
3366 // palette entries resolve Oracle project labels on first render.
3368
3370
3371 // Hide welcome screen and show editor selection when project ROM is loaded
3372 if (ui_coordinator_) {
3373 ui_coordinator_->SetWelcomeScreenVisible(false);
3374 ui_coordinator_->SetEditorSelectionVisible(true);
3375 }
3376
3377 // Set Dashboard category to suppress panel drawing until user selects an editor
3379 /*notify=*/false);
3380
3381 // Apply workspace settings
3390 ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale;
3391
3392 // Publish project context for Oracle panels and other consumers
3394
3395 // Add to recent files
3397 manager.AddFile(current_project_.filepath);
3398 manager.Save();
3399
3400 // Update project management panel with loaded project
3403 project_management_panel_->SetVersionManager(version_manager_.get());
3405 }
3406
3407 toast_manager_.Show(absl::StrFormat("Project '%s' loaded successfully",
3410
3411 return absl::OkStatus();
3412}
3413
3416 return CreateNewProject();
3417 }
3418
3419 // Update project with current settings
3422 auto* session = session_coordinator_->GetActiveRomSession();
3423 if (session) {
3424 current_project_.feature_flags = session->feature_flags;
3425 }
3426 }
3427
3436
3437 // Save recent files
3440 for (const auto& file : manager.GetRecentFiles()) {
3442 }
3443 }
3444
3445 return current_project_.Save();
3446}
3447
3449 PotItemSaveDecision decision) {
3450 // Map EditorManager enum to RomLifecycleManager enum
3451 auto lifecycle_decision =
3453 static_cast<int>(decision));
3455
3456 if (decision == PotItemSaveDecision::kCancel) {
3457 toast_manager_.Show("Save cancelled", ToastType::kInfo);
3458 return;
3459 }
3460
3461 auto status = SaveRom();
3462 if (status.ok()) {
3463 toast_manager_.Show("ROM saved successfully", ToastType::kSuccess);
3464 } else if (!absl::IsCancelled(status)) {
3466 absl::StrFormat("Failed to save ROM: %s", status.message()),
3468 }
3469}
3470
3472 // Get current project name for default filename
3473 std::string default_name = current_project_.project_opened()
3475 : "untitled_project";
3476
3477 auto file_path =
3478 util::FileDialogWrapper::ShowSaveFileDialog(default_name, "yaze");
3479 if (file_path.empty()) {
3480 return absl::OkStatus();
3481 }
3482
3483 // Ensure a project extension.
3484 if (!(absl::EndsWith(file_path, ".yaze") ||
3485 absl::EndsWith(file_path, ".yazeproj"))) {
3486 file_path += ".yaze";
3487 }
3488
3489 // Update project filepath and save
3490 std::string old_filepath = current_project_.filepath;
3491 current_project_.filepath = file_path;
3492
3493 auto save_status = current_project_.Save();
3494 if (save_status.ok()) {
3496
3497 // Add to recent files
3499 manager.AddFile(file_path);
3500 manager.Save();
3501
3502 toast_manager_.Show(absl::StrFormat("Project saved as: %s", file_path),
3504 } else {
3505 // Restore old filepath on failure
3506 current_project_.filepath = old_filepath;
3508 absl::StrFormat("Failed to save project: %s", save_status.message()),
3510 }
3511
3512 return save_status;
3513}
3514
3515absl::Status EditorManager::ImportProject(const std::string& project_path) {
3516 // Delegate to ProjectManager for import logic
3518 // Sync local project reference
3521 return absl::OkStatus();
3522}
3523
3526 return absl::FailedPreconditionError("No project is currently open");
3527 }
3528
3530 toast_manager_.Show("Project repaired successfully",
3532
3533 return absl::OkStatus();
3534}
3535
3537 if (auto* editor_set = GetCurrentEditorSet()) {
3538 return editor_set->GetOverworldData();
3539 }
3540 return nullptr;
3541}
3542
3544 if (!rom) {
3545 return absl::InvalidArgumentError("Invalid ROM pointer");
3546 }
3547
3548 // We need to find the session that owns this ROM.
3549 // This is inefficient but SetCurrentRom is rare.
3551 for (size_t i = 0; i < session_coordinator_->GetTotalSessionCount(); ++i) {
3552 auto* session =
3553 static_cast<RomSession*>(session_coordinator_->GetSession(i));
3554 if (session && &session->rom == rom) {
3555 session_coordinator_->SwitchToSession(i);
3556 // Update test manager with current ROM for ROM-dependent tests
3560 return absl::OkStatus();
3561 }
3562 }
3563 }
3564 // If ROM wasn't found in existing sessions, treat as new session.
3565 // Copying an external ROM object is avoided; instead, fail.
3566 return absl::NotFoundError("ROM not found in existing sessions");
3567}
3568
3573
3577
3578// IsRomHashMismatch() is now inline in editor_manager.h delegating to
3579// rom_lifecycle_.
3580
3581std::vector<RomFileManager::BackupEntry> EditorManager::GetRomBackups() const {
3583}
3584
3585absl::Status EditorManager::RestoreRomBackup(const std::string& backup_path) {
3586 auto* rom = GetCurrentRom();
3587 if (!rom) {
3588 return absl::FailedPreconditionError("No ROM loaded");
3589 }
3590 const std::string original_filename = rom->filename();
3591 RETURN_IF_ERROR(rom_file_manager_.LoadRom(rom, backup_path));
3592 if (!original_filename.empty()) {
3593 rom->set_filename(original_filename);
3594 }
3595
3597 if (auto* session = session_coordinator_->GetActiveRomSession()) {
3598 ResetAssetState(session);
3599 }
3600 }
3602 return LoadAssetsForMode();
3603}
3604
3608
3609// ConfirmRomWrite() and CancelRomWriteConfirm() are now inline in
3610// editor_manager.h delegating to rom_lifecycle_.
3611
3614 session_coordinator_->CreateNewSession();
3615 // Toast messages are now shown by SessionCoordinator
3616 }
3617}
3618
3621 session_coordinator_->DuplicateCurrentSession();
3622 }
3623}
3624
3627 session_coordinator_->CloseCurrentSession();
3628 }
3629}
3630
3633 session_coordinator_->RemoveSession(index);
3634 }
3635}
3636
3639 // Delegate to SessionCoordinator - cross-cutting concerns
3640 // are handled by OnSessionSwitched() observer callback
3641 session_coordinator_->SwitchToSession(index);
3642 }
3643}
3644
3646 return session_coordinator_ ? session_coordinator_->GetActiveSessionIndex()
3647 : 0;
3648}
3649
3651 UiSyncState state;
3652 state.frame_id = ui_sync_frame_id_.load(std::memory_order_relaxed);
3653
3654 int pending_editor =
3655 pending_editor_deferred_actions_.load(std::memory_order_relaxed);
3656 if (pending_editor < 0) {
3657 pending_editor = 0;
3658 }
3659 state.pending_editor_actions = pending_editor;
3660
3661 int pending_layout = layout_coordinator_.PendingDeferredActionCount();
3662 if (pending_layout < 0) {
3663 pending_layout = 0;
3664 }
3665 state.pending_layout_actions = pending_layout;
3667 layout_manager_ ? layout_manager_->IsRebuildRequested() : false;
3668 return state;
3669}
3670
3672 return session_coordinator_ ? session_coordinator_->GetActiveSessionCount()
3673 : 0;
3674}
3675
3677 EditorType type, size_t session_index) const {
3678 const char* base_name = kEditorNames[static_cast<int>(type)];
3679 return session_coordinator_ ? session_coordinator_->GenerateUniqueEditorTitle(
3680 base_name, session_index)
3681 : std::string(base_name);
3682}
3683
3684void EditorManager::SwitchToEditor(EditorType editor_type, bool force_visible,
3685 bool from_dialog) {
3686 // Special case: Agent editor requires EditorManager-specific handling
3687#ifdef YAZE_BUILD_AGENT_UI
3688 if (editor_type == EditorType::kAgent) {
3689 ShowAIAgent();
3690 return;
3691 }
3692#endif
3693
3694 auto status = EnsureEditorAssetsLoaded(editor_type);
3695 if (!status.ok()) {
3697 absl::StrFormat("Failed to prepare %s: %s",
3698 kEditorNames[static_cast<int>(editor_type)],
3699 status.message()),
3701 }
3702
3703 // Delegate all other editor switching to EditorActivator
3704 editor_activator_.SwitchToEditor(editor_type, force_visible, from_dialog);
3705}
3706
3708 if (!ui_coordinator_) {
3709 return;
3710 }
3711 ui_coordinator_->SetEditorSelectionVisible(false);
3712 ui_coordinator_->SetStartupSurface(StartupSurface::kEditor);
3713}
3714
3716 if (!session)
3717 return;
3719 ConfigureEditorDependencies(&session->editors, &session->rom,
3720 session->editors.session_id());
3721}
3722
3723// SessionScope implementation
3725 size_t session_id)
3726 : manager_(manager),
3727 prev_rom_(manager->GetCurrentRom()),
3728 prev_editor_set_(manager->GetCurrentEditorSet()),
3729 prev_session_id_(manager->GetCurrentSessionId()) {
3730 // Set new session context
3731 manager_->session_coordinator_->SwitchToSession(session_id);
3732}
3733
3735 // Restore previous context
3736 manager_->session_coordinator_->SwitchToSession(prev_session_id_);
3737}
3738
3739bool EditorManager::HasDuplicateSession(const std::string& filepath) {
3740 return session_coordinator_ &&
3741 session_coordinator_->HasDuplicateSession(filepath);
3742}
3743
3770 // Update project panel context before showing
3773 project_management_panel_->SetVersionManager(version_manager_.get());
3775 }
3777 }
3778}
3779
3781 // Load the current project file into the editor
3782 if (!current_project_.filepath.empty()) {
3784 if (!status.ok()) {
3786 absl::StrFormat("Failed to load project file: %s", status.message()),
3788 return;
3789 }
3790 }
3791 // Set the project pointer for label import functionality
3793 // Activate the editor window
3795}
3796
3798 size_t session_id) {
3799 if (!editor_set) {
3800 return;
3801 }
3802
3803 EditorDependencies deps;
3804 deps.rom = rom;
3805 deps.session_id = session_id;
3808 deps.popup_manager = popup_manager_.get();
3812 deps.project = &current_project_;
3813 deps.version_manager = version_manager_.get();
3814 deps.global_context = editor_context_.get();
3815 deps.status_bar = &status_bar_;
3816 deps.renderer = renderer_;
3817 deps.emulator = &emulator_;
3818 deps.custom_data = this;
3819
3820 editor_set->ApplyDependencies(deps);
3821
3822 // If configuring the active session, update the properties panel
3823 if (session_id == GetCurrentSessionId()) {
3825 }
3826}
3827
3828} // namespace yaze::editor
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
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
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.
static RomSettings & Get()
void SetAddressOverrides(const RomAddressOverrides &overrides)
void set_active(bool active)
Definition agent_chat.h:71
AgentConfigState & agent_config()
void Initialize(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, RightPanelManager *right_panel_manager, PanelManager *panel_manager, UserSettings *user_settings)
void SetProjectContext(project::YazeProject *project)
void ApplyUserSettingsDefaults(bool force=false)
void SetAsarWrapperContext(core::AsarWrapper *asar_wrapper)
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_
std::unique_ptr< PanelHost > panel_host_
RomLifecycleManager rom_lifecycle_
StartupVisibility welcome_mode_override_
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_
void SetCurrentEditor(Editor *editor) override
absl::Status LoadAssetsForMode(uint64_t loading_handle=0)
std::string GetPreferredStartupCategory(const std::string &saved_category, const std::vector< std::string > &available_categories) const
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.
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)
auto GetCurrentEditor() const -> Editor *
std::unique_ptr< RightPanelManager > right_panel_manager_
absl::Status LoadAssetsLazy(uint64_t loading_handle=0)
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::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::Status ImportProject(const std::string &project_path)
void QueueDeferredAction(std::function< void()> action)
SelectionPropertiesPanel selection_properties_panel_
void ConfigureSession(RomSession *session) override
std::unique_ptr< ActivityBar > activity_bar_
ShortcutManager shortcut_manager_
void MarkEditorInitialized(RomSession *session, EditorType type)
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_
absl::Status OpenRomOrProject(const std::string &filename)
void RemoveSession(size_t index)
absl::Status CheckOracleRomSafetyPreSave(Rom *rom)
UiSyncState GetUiSyncStateSnapshot() 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)
void HandleSessionCreated(size_t index, RomSession *session)
bool EditorRequiresGameData(EditorType type) const
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 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)
SettingsPanel * GetSettingsPanel() const
Interface for editor classes.
Definition editor.h:236
virtual absl::Status Redo()=0
EditorType type() const
Definition editor.h:283
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 > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
void SetPanelExpanded(bool expanded, bool notify=true)
void SetOnCategorySelectedCallback(std::function< void(const std::string &)> callback)
void SetSidebarStateChangedCallback(std::function< void(bool, bool)> cb)
void SetFileBrowserPath(const std::string &category, const std::string &path)
void SetSidebarVisible(bool visible, bool notify=true)
const PanelDescriptor * GetPanelDescriptor(size_t session_id, const std::string &base_card_id) const
void SetCategoryChangedCallback(std::function< void(const std::string &)> cb)
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
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 SetActiveCategory(const std::string &category, bool notify=true)
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
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 SetOnPanelClickedCallback(std::function< void(const std::string &)> callback)
void SetEditorResolver(std::function< Editor *(const std::string &)> resolver)
std::vector< PanelDescriptor > GetPanelsInCategory(size_t session_id, const std::string &category) const
bool ShowPanel(size_t session_id, const std::string &base_card_id)
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
std::string GetActiveCategory() const
void EnableFileBrowser(const std::string &category, const std::string &root_path="")
void SetEventBus(EventBus *event_bus)
void RegisterRegistryPanelsForSession(size_t session_id)
Register descriptors for all registry panels in a session.
void SetPanelBrowserCategoryWidth(float width, bool notify=true)
void DrawAllVisiblePanels()
Draw all visible EditorPanel instances (central drawing)
void SetStoredSidePanelWidth(float width, bool notify=false)
static constexpr const char * kDashboardCategory
void RegisterRegistryPanel(std::unique_ptr< EditorPanel > panel)
Register a ContentRegistry-managed EditorPanel instance.
const std::unordered_map< std::string, PanelDescriptor > & GetAllPanelDescriptors() const
Get all panel descriptors (for layout designer, panel browser, etc.)
void SetPanelBrowserCategoryWidthChangedCallback(std::function< void(float)> cb)
void SetSidePanelWidthChangedCallback(std::function< void(float)> cb)
void SetFileClickedCallback(std::function< void(const std::string &category, const std::string &path)> callback)
size_t GetActiveSessionId() const
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.
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 GetDefaultPanelWidth(PanelType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific panel 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).
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)
void SetSessionInfo(size_t session_id, size_t total_sessions)
Set session information.
Definition status_bar.cc:75
void SetRom(Rom *rom)
Set the current ROM for dirty status and filename display.
Definition status_bar.h:59
void SetEnabled(bool enabled)
Enable or disable the status bar.
Definition status_bar.h:53
void Initialize(GlobalEditorContext *context)
Definition status_bar.cc:30
void Draw()
Draw the status bar.
void SetAgentToggleCallback(std::function< void()> callback)
Definition status_bar.h:144
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 SetOpenRomCallback(std::function< void()> callback)
Set callback for opening 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_layout_manager(LayoutManager *manager)
void set_panel_manager(PanelManager *manager)
bool is_audio_focus_mode() const
Definition emulator.h:109
void set_renderer(gfx::IRenderer *renderer)
Definition emulator.h:98
bool is_snes_initialized() const
Definition emulator.h:127
void Run(Rom *rom)
Definition emulator.cc:431
auto running() const -> bool
Definition emulator.h:60
void set_panel_manager(editor::PanelManager *manager)
Definition emulator.h:50
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:95
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
Definition animator.cc:105
void ClearAllAnimations()
Definition animator.cc:89
RAII guard for ImGui style colors.
Definition style_guard.h:27
static RecentFilesManager & GetInstance()
Definition project.h:374
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...
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_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_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 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 Clear()
Clear all context state.
void SetCurrentProject(::yaze::project::YazeProject *project)
Set the current project instance.
std::vector< std::unique_ptr< EditorPanel > > CreateAll()
Create new instances of all registered panels.
constexpr const char * kAbout
constexpr const char * kWriteConflictWarning
constexpr const char * kDungeonPotItemSaveConfirm
bool HasAnyOverride(const core::RomAddressOverrides &overrides, std::initializer_list< const char * > keys)
bool ProjectUsesCustomObjects(const project::YazeProject &project)
std::string StripSessionPrefix(absl::string_view panel_id)
std::vector< std::string > ValidateRomAddressOverrides(const core::RomAddressOverrides &overrides, const Rom &rom)
std::optional< EditorType > ParseEditorTypeFromString(absl::string_view name)
Editors are the view controllers for the application.
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:217
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:226
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:301
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:180
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:181
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:163
project::YazeProject * project
Definition editor.h:167
GlobalEditorContext * global_context
Definition editor.h:169
SharedClipboard * shared_clipboard
Definition editor.h:178
gfx::IRenderer * renderer
Definition editor.h:183
ShortcutManager * shortcut_manager
Definition editor.h:177
core::VersionManager * version_manager
Definition editor.h:168
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.
Declarative registration contract for editor panels.
Definition panel_host.h:22
Metadata for an editor panel (formerly PanelInfo)
std::function< void()> on_show
PanelContextScope context_scope
std::function< void()> on_hide
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
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:365
std::vector< std::string > recent_files
Definition project.h:83
Modern project structure with comprehensive settings consolidation.
Definition project.h:120
std::string rom_backup_folder
Definition project.h:129
std::unordered_map< int, std::vector< std::string > > custom_object_files
Definition project.h:145
std::string custom_objects_folder
Definition project.h:140
absl::Status RepairProject()
Definition project.cc:1226
std::string MakeStorageKey(absl::string_view suffix) const
Definition project.cc:452
bool project_opened() const
Definition project.h:285
core::HackManifest hack_manifest
Definition project.h:160
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:153
std::string assets_folder
Definition project.h:135
std::string labels_filename
Definition project.h:137
std::string GetDisplayName() const
Definition project.cc:1260
WorkspaceSettings workspace_settings
Definition project.h:149
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1287
absl::Status Open(const std::string &project_path)
Definition project.cc:295
absl::Status Validate() const
Definition project.cc:1170
core::FeatureFlags::Flags feature_flags
Definition project.h:148
core::RomAddressOverrides rom_address_overrides
Definition project.h:151