yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
menu_orchestrator.cc
Go to the documentation of this file.
1#include "menu_orchestrator.h"
2
3#include <algorithm>
4#include <fstream>
5#include <map>
6#include <string>
7
8#include "absl/status/status.h"
9#include "absl/strings/str_format.h"
11#include "app/editor/editor.h"
27#include "app/gui/core/icons.h"
31#include "core/features.h"
32#include "core/project.h"
33#include "rom/rom.h"
34#include "util/bps.h"
35#include "util/file_util.h"
38
39// Menu shortcut labels come from ShortcutManager's live bindings through
40// MenuOrchestrator::GetShortcutForAction; never hard-code key hints here.
41
42namespace yaze {
43namespace editor {
44
45namespace {
46
47constexpr const char* kLayoutDesignerWindowId = "layout.designer";
48
49// ImGui::MenuItem draws nothing for a null shortcut; avoid passing "".
50const char* ShortcutOrNull(const std::string& label) {
51 return label.empty() ? nullptr : label.c_str();
52}
53
54// OS-window fullscreen. The ImGui SDL backends store the SDL window ID in the
55// main viewport's PlatformHandle.
56#if !defined(__EMSCRIPTEN__) && !(defined(__APPLE__) && TARGET_OS_IOS == 1)
57constexpr bool kSupportsWindowFullscreen = true;
58
59SDL_Window* GetMainSdlWindow() {
60 if (ImGui::GetCurrentContext() == nullptr) {
61 return nullptr;
62 }
63 const ImGuiViewport* viewport = ImGui::GetMainViewport();
64 if (viewport == nullptr || viewport->PlatformHandle == nullptr) {
65 return nullptr;
66 }
67 const auto window_id =
68 static_cast<Uint32>(reinterpret_cast<intptr_t>(viewport->PlatformHandle));
69 return SDL_GetWindowFromID(window_id);
70}
71
73 SDL_Window* window = GetMainSdlWindow();
74 if (window == nullptr) {
75 return false;
76 }
77#ifdef YAZE_USE_SDL3
78 return (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0;
79#else
80 return (SDL_GetWindowFlags(window) &
81 (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
82#endif
83}
84
85bool SetMainWindowFullscreen(bool fullscreen) {
86 SDL_Window* window = GetMainSdlWindow();
87 if (window == nullptr) {
88 return false;
89 }
90#ifdef YAZE_USE_SDL3
91 return SDL_SetWindowFullscreen(window, fullscreen);
92#else
93 return SDL_SetWindowFullscreen(
94 window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) == 0;
95#endif
96}
97#else
98constexpr bool kSupportsWindowFullscreen = false;
99bool IsMainWindowFullscreen() {
100 return false;
101}
102bool SetMainWindowFullscreen(bool) {
103 return false;
104}
105#endif
106
107} // namespace
108
110 EditorManager* editor_manager, MenuBuilder& menu_builder,
111 RomFileManager& rom_manager, ProjectManager& project_manager,
112 EditorRegistry& editor_registry, SessionCoordinator& session_coordinator,
113 ToastManager& toast_manager, PopupManager& popup_manager)
114 : editor_manager_(editor_manager),
115 menu_builder_(menu_builder),
116 rom_manager_(rom_manager),
117 project_manager_(project_manager),
118 editor_registry_(editor_registry),
119 session_coordinator_(session_coordinator),
120 toast_manager_(toast_manager),
121 popup_manager_(popup_manager) {}
122
124 ClearMenu();
125
126 // Build all menu sections in order
127 // Traditional order: File, Edit, View, then app-specific menus
131 // Windows menu now owns what used to live in the legacy "Window" menu
132 // (Sessions, Layout, Snapshots) in addition to the dynamic category
133 // toggles from WorkspaceWindowManager.
135 BuildToolsMenu(); // Debug menu items merged into Tools
137
138 // Draw the constructed menu
140
141 // Render any deferred modal popups owned by the menu layer. These run
142 // after the menu stack unwinds so the popup has a clean ImGui stack.
144 ImGui::OpenPopup("Save Layout Snapshot##menu_orch_save_snapshot");
146 }
147 if (ImGui::BeginPopupModal("Save Layout Snapshot##menu_orch_save_snapshot",
148 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
149 ImGui::TextUnformatted("Snapshot name:");
150 ImGui::SetNextItemWidth(320.0f);
151 const bool submitted =
152 ImGui::InputText("##snapshot_name", save_snapshot_name_buffer_,
154 ImGuiInputTextFlags_EnterReturnsTrue);
155 const bool has_name = save_snapshot_name_buffer_[0] != '\0';
156 ImGui::Separator();
157 if ((ImGui::Button("Save", ImVec2(120, 0)) || submitted) && has_name) {
158 if (editor_manager_) {
160 }
162 ImGui::CloseCurrentPopup();
163 }
164 ImGui::SameLine();
165 if (ImGui::Button("Cancel", ImVec2(120, 0))) {
167 ImGui::CloseCurrentPopup();
168 }
169 ImGui::EndPopup();
170 }
171
172 menu_needs_refresh_ = false;
173}
174
180
182 // ROM Operations
184 "Open ROM / Project...", ICON_MD_FILE_OPEN, [this]() { OnOpenRom(); },
185 GetShortcutForAction("Open").c_str());
188 .Item(
189 "Save ROM", ICON_MD_SAVE, [this]() { OnSaveRom(); },
190 GetShortcutForAction("Save").c_str(),
191 [this]() { return CanSaveRom(); })
192 .Item(
193 "Save As...", ICON_MD_SAVE_AS, [this]() { OnSaveRomAs(); },
194 GetShortcutForAction("Save As").c_str(),
195 [this]() { return CanSaveRom(); })
196 .Item(
197 "Save Scope...", ICON_MD_TUNE,
198 [this]() {
200 if (editor_manager_) {
203 }
204 },
205 nullptr, [this]() { return CanSaveRom(); })
206 .Item(
207 "Revert to Saved", ICON_MD_RESTORE, [this]() { OnRevertRom(); },
208 nullptr,
209 [this]() {
211 })
212 .Item(
213 "Close ROM", ICON_MD_CLOSE, [this]() { OnCloseRom(); }, nullptr,
214 [this]() {
216 })
217 .Separator();
218
219 // Project Operations
221 .Item("New Project", ICON_MD_CREATE_NEW_FOLDER,
222 [this]() { OnCreateProject(); })
223 .Item("Open Project Only...", ICON_MD_FOLDER_OPEN,
224 [this]() { OnOpenProject(); })
225 .Item(
226 "Save Project", ICON_MD_SAVE, [this]() { OnSaveProject(); }, nullptr,
227 [this]() { return CanSaveProject(); })
228 .Item(
229 "Save Project As...", ICON_MD_SAVE_AS,
230 [this]() { OnSaveProjectAs(); }, nullptr,
231 [this]() { return CanSaveProject(); })
232 .Item(
233 "Project Management...", ICON_MD_FOLDER_SPECIAL,
234 [this]() { OnShowProjectManagement(); }, nullptr,
235 [this]() { return CanSaveProject(); })
236 .Item(
237 "Edit Project File...", ICON_MD_DESCRIPTION,
238 [this]() { OnShowProjectFileEditor(); }, nullptr,
239 [this]() { return HasProjectFile(); })
240 .Separator();
241
242 // Settings and Quit (ROM analysis / backup / BPS live under Tools).
243 // Settings stays here on every platform. On macOS the native app menu
244 // (app_delegate.mm) also exposes Settings/Preferences; this entry is kept
245 // so the ImGui menu bar remains complete when the native menu is absent.
247 .Item(
248 "Settings", ICON_MD_SETTINGS, [this]() { OnShowSettings(); },
249 // Editor-switch shortcuts are registered as "switch.<EditorType>"
250 // (Settings moves to Ctrl/Cmd+, on claude/ui-shortcuts).
252 absl::StrFormat("switch.%d",
253 static_cast<int>(EditorType::kSettings)))
254 .c_str())
255 .Separator()
256 .Item(
257 "Quit", ICON_MD_EXIT_TO_APP, [this]() { OnQuit(); },
258 GetShortcutForAction("Quit").c_str());
259}
260
263 const auto entries = BuildRecentFileMenuEntries(recent.GetRecentFiles());
264
266 if (entries.empty()) {
267 menu_builder_.DisabledItem("No recent files");
268 } else {
269 for (const auto& entry : entries) {
270 const std::string path = entry.path;
271 const std::string label =
272 entry.exists ? entry.label : entry.label + " (missing)";
273 const bool exists = entry.exists;
275 label.c_str(), nullptr, [this, path]() { OnOpenRecentFile(path); },
276 nullptr, [exists]() { return exists; });
277 }
278 }
280 "Clear Recent", ICON_MD_DELETE_SWEEP, [this]() { OnClearRecentFiles(); },
281 nullptr, [has_entries = !entries.empty()]() { return has_entries; });
283}
284
290
292 // Undo/Redo operations - delegate to current editor
294 .Item(
295 "Undo", ICON_MD_UNDO, [this]() { OnUndo(); },
296 GetShortcutForAction("Undo").c_str(),
297 [this]() { return CurrentEditorCanUndo(); })
298 .Item(
299 "Redo", ICON_MD_REDO, [this]() { OnRedo(); },
300 GetShortcutForAction("Redo").c_str(),
301 [this]() { return CurrentEditorCanRedo(); })
302 .Separator();
303
304 // Clipboard operations - delegate to current editor
306 .Item(
307 "Cut", ICON_MD_CONTENT_CUT, [this]() { OnCut(); },
308 GetShortcutForAction("Cut").c_str(),
309 [this]() { return CurrentEditorCanCut(); })
310 .Item(
311 "Copy", ICON_MD_CONTENT_COPY, [this]() { OnCopy(); },
312 GetShortcutForAction("Copy").c_str(),
313 [this]() { return CurrentEditorCanCopy(); })
314 .Item(
315 "Paste", ICON_MD_CONTENT_PASTE, [this]() { OnPaste(); },
316 GetShortcutForAction("Paste").c_str(),
317 [this]() { return CurrentEditorCanPaste(); })
318 .Separator();
319
320 // Search operations (Find in Files moved to Tools > Global Search)
322 "Find", ICON_MD_SEARCH, [this]() { OnFind(); },
323 GetShortcutForAction("Find").c_str(),
324 [this]() { return CurrentEditorCanFind(); });
325}
326
332
336
337 // Right drawers — shared catalog with menu-bar overflow / header switcher.
340
341 // Editor selection (Switch Editor)
343 "Switch Editor...", ICON_MD_SWAP_HORIZ,
344 [this]() { OnShowEditorSelection(); },
345 GetShortcutForAction("Editor Selection").c_str(),
346 [this]() { return HasActiveRom(); });
347}
348
350 auto& themes = gui::ThemeManager::Get();
352 const auto names = themes.GetAvailableThemes();
353 if (names.empty()) {
354 menu_builder_.DisabledItem("No themes available");
355 }
356 for (const std::string& name : names) {
358 name.c_str(), nullptr,
359 [name]() {
360 auto& manager = gui::ThemeManager::Get();
361 if (manager.IsPreviewActive()) {
362 manager.EndPreview();
363 }
364 manager.ApplyTheme(name);
365 },
366 nullptr, nullptr,
367 [name]() {
369 });
370 }
372}
373
375 // Appearance/Layout controls
377 .Item(
378 "Show Sidebar", ICON_MD_VIEW_SIDEBAR,
379 [this]() {
380 if (window_manager_)
382 },
383 GetShortcutForAction("view.toggle_activity_bar").c_str(), nullptr,
384 [this]() {
386 })
387 .Item(
388 "Show Status Bar", ICON_MD_HORIZONTAL_RULE,
389 [this]() {
390 if (user_settings_) {
394 if (status_bar_) {
397 }
398 }
399 },
400 nullptr, nullptr,
401 [this]() {
403 })
404 .Separator();
405
408 .Item(
409 "Zoom In", ICON_MD_ZOOM_IN, [this]() { OnZoomIn(); },
410 GetShortcutForAction("ui.font_scale_increase").c_str(),
411 [this]() { return editor_manager_ != nullptr; })
412 .Item(
413 "Zoom Out", ICON_MD_ZOOM_OUT, [this]() { OnZoomOut(); },
414 GetShortcutForAction("ui.font_scale_decrease").c_str(),
415 [this]() { return editor_manager_ != nullptr; })
416 .Item(
417 "Reset Zoom", ICON_MD_ZOOM_OUT_MAP, [this]() { OnZoomReset(); },
418 GetShortcutForAction("ui.font_scale_reset").c_str(),
419 [this]() { return editor_manager_ != nullptr; })
420 .Item(
421 "Fullscreen", ICON_MD_FULLSCREEN, [this]() { OnToggleFullscreen(); },
422 nullptr, []() { return kSupportsWindowFullscreen; },
423 []() { return IsMainWindowFullscreen(); })
424 .Separator()
425 .Item("Display Settings", ICON_MD_DISPLAY_SETTINGS,
426 [this]() { OnShowDisplaySettings(); })
427 .Item("Welcome Screen", ICON_MD_HOME,
428 [this]() { OnShowWelcomeScreen(); });
429}
430
432 auto* drawers =
434 if (!drawers) {
435 return;
436 }
437
439 "Right Sidebar", ICON_MD_VIEW_SIDEBAR,
440 [drawers]() { drawers->ToggleLastDrawer(); }, nullptr, nullptr,
441 [drawers]() { return drawers->IsDrawerExpanded(); });
442}
443
444// Layout presets remain under Windows > Layout (AddLayoutSubmenu).
445// The former View > Layout duplicate was removed to keep one home for layout.
446
448 // Use CustomMenu to integrate dynamic panel content with the menu builder
449 menu_builder_.CustomMenu("Windows", [this]() { AddPanelsMenuItems(); });
450}
451
453 if (!window_manager_) {
454 return;
455 }
456
457 const size_t session_id = session_coordinator_.GetActiveSessionId();
458 std::string active_category = window_manager_->GetActiveCategory();
459 auto all_categories = window_manager_->GetAllCategories(session_id);
460
461 // Window Browser action at top
462 if (ImGui::MenuItem(
463 absl::StrFormat("%s Window Browser", ICON_MD_APPS).c_str(),
464 ShortcutOrNull(GetShortcutForAction("Window Browser")))) {
466 }
467 if (ImGui::MenuItem(
468 absl::StrFormat("%s Show All Windows", ICON_MD_VISIBILITY).c_str())) {
470 }
471 if (ImGui::MenuItem(
472 absl::StrFormat("%s Hide All Windows", ICON_MD_VISIBILITY_OFF)
473 .c_str())) {
475 }
476 ImGui::Separator();
477
478 // Sessions and Layout — folded in from the former "Window" top-level menu.
479 // These draw directly against ImGui (not through MenuBuilder) because
480 // CustomMenu's callback runs *inside* an already-open BeginMenu scope.
484 ImGui::Separator();
485
486 if (all_categories.empty()) {
487 ImGui::TextDisabled("No windows available");
488 return;
489 }
490
491 // Show all categories as direct submenus (no nested "All Categories" wrapper)
492 for (const auto& category : all_categories) {
493 // Mark active category with icon
494 std::string label = category;
495 if (category == active_category) {
496 label = absl::StrFormat("%s %s", ICON_MD_FOLDER_OPEN, category);
497 } else {
498 label = absl::StrFormat("%s %s", ICON_MD_FOLDER, category);
499 }
500
501 if (ImGui::BeginMenu(label.c_str())) {
502 auto cards = window_manager_->GetWindowsInCategory(session_id, category);
503 std::erase_if(cards, [](const WindowDescriptor& descriptor) {
504 return !descriptor.IsListedInWindowBrowser();
505 });
506
507 if (cards.empty()) {
508 ImGui::TextDisabled("No windows in this category");
509 } else {
510 if (ImGui::MenuItem(
511 absl::StrFormat("%s Show Category", ICON_MD_VISIBILITY)
512 .c_str())) {
513 window_manager_->ShowAllWindowsInCategory(session_id, category);
514 }
515 if (ImGui::MenuItem(
516 absl::StrFormat("%s Hide Category", ICON_MD_VISIBILITY_OFF)
517 .c_str())) {
518 window_manager_->HideAllWindowsInCategory(session_id, category);
519 }
520 ImGui::Separator();
521
522 for (const auto& card : cards) {
523 bool is_visible =
524 window_manager_->IsWindowOpen(session_id, card.card_id);
525 const char* shortcut =
526 card.shortcut_hint.empty() ? nullptr : card.shortcut_hint.c_str();
527
528 // Show icon for visible panels
529 std::string item_label =
530 is_visible
531 ? absl::StrFormat("%s %s", ICON_MD_CHECK_BOX,
532 card.display_name)
533 : absl::StrFormat("%s %s", ICON_MD_CHECK_BOX_OUTLINE_BLANK,
534 card.display_name);
535
536 if (ImGui::MenuItem(item_label.c_str(), shortcut)) {
537 window_manager_->ToggleWindow(session_id, card.card_id);
538 }
539 }
540 }
541 ImGui::EndMenu();
542 }
543 }
544}
545
551
556
560
562
563#ifdef YAZE_ENABLE_TESTING
565#endif
566
567 // ImGui Debug (moved from Debug menu)
569 .Item("ImGui Demo", ICON_MD_HELP, [this]() { OnShowImGuiDemo(); })
570 .Item("ImGui Metrics", ICON_MD_ANALYTICS,
571 [this]() { OnShowImGuiMetrics(); })
572 .EndMenu();
573}
574
576 // Search & Navigation
578 .Item(
579 "Global Search", ICON_MD_SEARCH, [this]() { OnShowGlobalSearch(); },
580 GetShortcutForAction("Global Search").c_str())
581 .Item(
582 "Command Palette", ICON_MD_SEARCH,
583 [this]() { OnShowCommandPalette(); },
584 GetShortcutForAction("Command Palette").c_str())
585 .Item(
586 "Find Window…", ICON_MD_DASHBOARD, [this]() { OnShowPanelFinder(); },
587 GetShortcutForAction("Window Finder").c_str())
588 .Item("Resource Label Manager", ICON_MD_LABEL,
589 [this]() { OnShowResourceLabelManager(); });
590}
591
594
595 std::map<std::string, std::vector<WorkflowItem>> grouped_items;
596
597 if (window_manager_) {
598 const size_t session_id = session_coordinator_.GetActiveSessionId();
599 const auto categories = window_manager_->GetAllCategories(session_id);
600 for (const auto& category : categories) {
601 for (const auto& descriptor :
602 window_manager_->GetWindowsInCategory(session_id, category)) {
603 if (descriptor.workflow_group.empty()) {
604 continue;
605 }
606 WorkflowItem item;
607 item.id = absl::StrFormat("panel.%s", descriptor.card_id);
608 item.group = descriptor.workflow_group;
609 item.label = descriptor.workflow_label.empty()
610 ? descriptor.display_name
611 : descriptor.workflow_label;
612 item.description =
613 descriptor.workflow_description.empty()
614 ? absl::StrFormat("Open %s", descriptor.display_name)
615 : descriptor.workflow_description;
616 item.shortcut = descriptor.shortcut_hint;
617 item.priority = descriptor.workflow_priority;
618 item.callback = [this, session_id, panel_id = descriptor.card_id]() {
619 if (window_manager_) {
620 window_manager_->OpenWindow(session_id, panel_id);
621 }
622 };
623 item.enabled = descriptor.enabled_condition;
624 const std::string group = item.group.empty() ? "General" : item.group;
625 grouped_items[group].push_back(std::move(item));
626 }
627 }
628 }
629
630 for (const auto& action : ContentRegistry::WorkflowActions::GetAll()) {
631 const std::string group = action.group.empty() ? "General" : action.group;
632 grouped_items[group].push_back(action);
633 }
634
635 if (grouped_items.empty()) {
636 return;
637 }
638
639 menu_builder_.BeginSubMenu("Hack Workflows", ICON_MD_ROUTE);
640 for (auto& [group, items] : grouped_items) {
641 std::sort(items.begin(), items.end(),
642 [](const WorkflowItem& lhs, const WorkflowItem& rhs) {
643 if (lhs.priority != rhs.priority) {
644 return lhs.priority < rhs.priority;
645 }
646 return lhs.label < rhs.label;
647 });
649 for (const auto& item : items) {
650 MenuBuilder::EnabledCheck enabled = item.enabled;
652 item.label.c_str(), item.callback,
653 item.shortcut.empty() ? nullptr : item.shortcut.c_str(), enabled);
654 }
656 }
657 menu_builder_.EndMenu();
658}
659
660void MenuOrchestrator::AddRomAnalysisMenuItems() {
661 // ROM Analysis (document tooling moved from File)
662 menu_builder_.BeginSubMenu("ROM Analysis", ICON_MD_STORAGE)
663 .Item(
664 "ROM Information", ICON_MD_INFO, [this]() { OnShowRomInfo(); },
665 nullptr, [this]() { return HasActiveRom(); })
666 .Item(
667 "Create Backup", ICON_MD_BACKUP, [this]() { OnCreateBackup(); },
668 nullptr, [this]() { return HasActiveRom(); })
669 .Item(
670 "ROM Backups...", ICON_MD_BACKUP,
671 [this]() { popup_manager_.Show(PopupID::kRomBackups); }, nullptr,
672 [this]() { return HasActiveRom(); })
673 .Item(
674 "Validate ROM", ICON_MD_CHECK_CIRCLE, [this]() { OnValidateRom(); },
675 nullptr, [this]() { return HasActiveRom(); })
676 .Separator()
677 .Item(
678 "Export BPS Patch...", ICON_MD_DIFFERENCE,
679 [this]() { OnExportBpsPatch(); }, nullptr,
680 [this]() { return HasActiveRom(); })
681 .Item(
682 "Apply BPS Patch...", ICON_MD_BUILD, [this]() { OnApplyBpsPatch(); },
683 nullptr, [this]() { return HasActiveRom(); })
684 .EndMenu();
685
686 // ZSCustomOverworld (moved from Debug menu)
687 menu_builder_.BeginSubMenu("ZSCustomOverworld", ICON_MD_CODE)
688 .Item(
689 "Check ROM Version", ICON_MD_INFO, [this]() { OnCheckRomVersion(); },
690 nullptr, [this]() { return HasActiveRom(); })
691 .Item(
692 "Upgrade ROM (Overworld Editor)...", ICON_MD_UPGRADE,
693 [this]() { OnOpenOverworldForUpgrade(); }, nullptr,
694 [this]() { return HasActiveRom(); })
695 .Item("Toggle Custom Loading", ICON_MD_SETTINGS,
696 [this]() { OnToggleCustomLoading(); })
697 .EndMenu();
698}
699
700void MenuOrchestrator::AddAsarIntegrationMenuItems() {
701 // Asar Integration (moved from Debug menu)
702 menu_builder_.BeginSubMenu("Asar Integration", ICON_MD_BUILD)
703 .Item("Asar Status", ICON_MD_INFO,
704 [this]() { popup_manager_.Show(PopupID::kAsarIntegration); })
705 .Item(
706 "Toggle ASM Patch", ICON_MD_CODE, [this]() { OnToggleAsarPatch(); },
707 nullptr, [this]() { return HasActiveRom(); })
708 .EndMenu();
709}
710
711void MenuOrchestrator::AddDevelopmentMenuItems() {
712 // Development Tools — right-sidebar content is toggled under View.
713 menu_builder_.BeginSubMenu("Development", ICON_MD_DEVELOPER_MODE)
714 .Item(
715 "Memory Editor", ICON_MD_MEMORY, [this]() { OnShowMemoryEditor(); },
716 nullptr, [this]() { return HasActiveRom(); })
717 .Item("Assembly Editor", ICON_MD_CODE,
718 [this]() { OnShowAssemblyEditor(); })
719 .Item("Feature Flags", ICON_MD_FLAG,
720 [this]() { popup_manager_.Show(PopupID::kFeatureFlags); })
721 .Item("Performance Dashboard", ICON_MD_SPEED,
722 [this]() { OnShowPerformanceDashboard(); })
723 .EndMenu();
724}
725
726void MenuOrchestrator::AddTestingMenuItems() {
727 // Testing (moved from Debug menu)
728 menu_builder_.BeginSubMenu("Testing", ICON_MD_SCIENCE);
729#ifdef YAZE_ENABLE_TESTING
730 menu_builder_
731 // Test suites run from the dashboard; the former "Run All/Unit/
732 // Integration/E2E" items only showed toasts and were removed.
733 .Item(
734 "Test Dashboard", ICON_MD_DASHBOARD,
735 [this]() { OnShowTestDashboard(); },
736 GetShortcutForAction("Test Dashboard").c_str());
737#else
738 menu_builder_.DisabledItem(
739 "Testing support disabled (YAZE_ENABLE_TESTING=OFF)", ICON_MD_INFO);
740#endif
741 menu_builder_.EndMenu();
742}
743
744// Sessions submenu (folded from the former top-level "Window" menu).
745// Drawn inline inside the "Windows" CustomMenu callback using raw ImGui
746// calls so the entries land in the same menu scope.
747void MenuOrchestrator::AddSessionsSubmenu() {
748 if (ImGui::BeginMenu(absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) {
749 if (ImGui::MenuItem(absl::StrFormat("%s New Session", ICON_MD_ADD).c_str(),
750 ShortcutOrNull(GetShortcutForAction("New Session")))) {
751 OnCreateNewSession();
752 }
753 if (ImGui::MenuItem(
754 absl::StrFormat("%s Duplicate Session", ICON_MD_CONTENT_COPY)
755 .c_str(),
756 nullptr, false, HasActiveRom())) {
757 OnDuplicateCurrentSession();
758 }
759 if (ImGui::MenuItem(
760 absl::StrFormat("%s Close Session", ICON_MD_CLOSE).c_str(),
761 ShortcutOrNull(GetShortcutForAction("Close Session")), false,
762 HasMultipleSessions())) {
763 OnCloseCurrentSession();
764 }
765 ImGui::Separator();
766 if (ImGui::MenuItem(
767 absl::StrFormat("%s Session Switcher", ICON_MD_SWITCH_ACCOUNT)
768 .c_str(),
769 ShortcutOrNull(GetShortcutForAction("Session Switcher")), false,
770 HasMultipleSessions())) {
771 OnShowSessionSwitcher();
772 }
773 if (ImGui::MenuItem(
774 absl::StrFormat("%s Session Manager", ICON_MD_VIEW_LIST).c_str())) {
775 OnShowSessionManager();
776 }
777 ImGui::EndMenu();
778 }
779}
780
781// Layout submenu (folded from the former top-level "Window" menu).
782// Contains Save/Load/Reset, session snapshots, and named presets.
783void MenuOrchestrator::AddLayoutSubmenu() {
784 const bool layout_enabled = HasCurrentEditor();
785 auto apply_preset = [this](const char* name) {
786 if (editor_manager_)
787 editor_manager_->ApplyLayoutPreset(name);
788 };
789 auto apply_profile = [this](const char* name) {
790 if (editor_manager_)
791 editor_manager_->ApplyLayoutProfile(name);
792 };
793
794 if (ImGui::BeginMenu(
795 absl::StrFormat("%s Layout", ICON_MD_VIEW_QUILT).c_str())) {
796 if (ImGui::MenuItem(absl::StrFormat("%s Open Layout Designer",
798 .c_str(),
799 nullptr, false, window_manager_ != nullptr)) {
800 OnShowLayoutDesigner();
801 }
802 ImGui::Separator();
803
804 if (ImGui::MenuItem(absl::StrFormat("%s Save Layout", ICON_MD_SAVE).c_str(),
805 ShortcutOrNull(GetShortcutForAction("Save Layout")))) {
806 OnSaveWorkspaceLayout();
807 }
808 if (ImGui::MenuItem(
809 absl::StrFormat("%s Load Layout", ICON_MD_FOLDER_OPEN).c_str(),
810 ShortcutOrNull(GetShortcutForAction("Load Layout")))) {
811 OnLoadWorkspaceLayout();
812 }
813 if (ImGui::MenuItem(
814 absl::StrFormat("%s Reset Layout", ICON_MD_RESET_TV).c_str(),
815 ShortcutOrNull(GetShortcutForAction("Reset Layout")))) {
816 OnResetWorkspaceLayout();
817 }
818 if (ImGui::MenuItem(
819 absl::StrFormat("%s Try Default Editor Layout", ICON_MD_REFRESH)
820 .c_str(),
821 nullptr, false, layout_enabled)) {
822 if (editor_manager_)
823 editor_manager_->ResetCurrentEditorLayout();
824 }
825 if (ImGui::MenuItem(
826 "Restore Layout Before Default", nullptr, false,
827 editor_manager_ && editor_manager_->HasPreviousEditorLayout())) {
828 editor_manager_->RestorePreviousEditorLayout();
829 }
830
831 ImGui::Separator();
832 if (ImGui::BeginMenu(
833 absl::StrFormat("%s Snapshots", ICON_MD_BOOKMARKS).c_str(),
834 layout_enabled)) {
835 if (ImGui::MenuItem(
836 absl::StrFormat("%s Save Snapshot As...", ICON_MD_BOOKMARK_ADD)
837 .c_str(),
838 nullptr, false, layout_enabled)) {
839 save_snapshot_name_buffer_[0] = '\0';
840 open_save_snapshot_modal_ = true;
841 }
842
843 // Named snapshots (session-scoped, in-memory).
844 std::vector<std::string> named =
845 editor_manager_ ? editor_manager_->ListLayoutSnapshots()
846 : std::vector<std::string>{};
847 if (!named.empty()) {
848 ImGui::Separator();
849 for (const auto& name : named) {
850 ImGui::PushID(name.c_str());
851 if (ImGui::MenuItem(
852 absl::StrFormat("%s %s", ICON_MD_RESTORE, name).c_str(),
853 nullptr, false, layout_enabled)) {
854 if (editor_manager_)
855 editor_manager_->RestoreLayoutSnapshot(name);
856 }
857 ImGui::SameLine(ImGui::GetContentRegionAvail().x);
858 if (ImGui::SmallButton(ICON_MD_DELETE)) {
859 if (editor_manager_)
860 editor_manager_->DeleteLayoutSnapshot(name);
861 }
862 ImGui::PopID();
863 }
864 }
865
866 ImGui::Separator();
867 // Legacy unnamed temporary slot (backward-compatible Capture/Restore).
868 if (ImGui::MenuItem(
869 absl::StrFormat("%s Capture Unnamed", ICON_MD_BOOKMARK_ADD)
870 .c_str(),
871 nullptr, false, layout_enabled)) {
872 if (editor_manager_)
873 editor_manager_->CaptureTemporaryLayoutSnapshot();
874 }
875 if (ImGui::MenuItem(
876 absl::StrFormat("%s Restore Unnamed", ICON_MD_RESTORE).c_str(),
877 nullptr, false, layout_enabled)) {
878 if (editor_manager_)
879 editor_manager_->RestoreTemporaryLayoutSnapshot();
880 }
881 if (ImGui::MenuItem(
882 absl::StrFormat("%s Clear Unnamed", ICON_MD_BOOKMARK_REMOVE)
883 .c_str(),
884 nullptr, false, layout_enabled)) {
885 if (editor_manager_)
886 editor_manager_->ClearTemporaryLayoutSnapshot();
887 }
888 ImGui::EndMenu();
889 }
890
891 ImGui::Separator();
892 if (ImGui::BeginMenu(
893 absl::StrFormat("%s Presets", ICON_MD_DASHBOARD).c_str())) {
894 if (ImGui::MenuItem(absl::StrFormat("%s Code", ICON_MD_CODE).c_str(),
895 nullptr, false, layout_enabled)) {
896 apply_profile("code");
897 }
898 if (ImGui::MenuItem(
899 absl::StrFormat("%s Debug", ICON_MD_BUG_REPORT).c_str(), nullptr,
900 false, layout_enabled)) {
901 apply_profile("debug");
902 }
903 if (ImGui::MenuItem(absl::StrFormat("%s Mapping", ICON_MD_MAP).c_str(),
904 nullptr, false, layout_enabled)) {
905 apply_profile("mapping");
906 }
907 if (ImGui::MenuItem(
908 absl::StrFormat("%s Chat + Agent", ICON_MD_SMART_TOY).c_str(),
909 nullptr, false, layout_enabled)) {
910 apply_profile("chat");
911 }
912 ImGui::Separator();
913 if (ImGui::MenuItem(
914 absl::StrFormat("%s Minimal", ICON_MD_VIEW_COMPACT).c_str(),
915 nullptr, false, layout_enabled)) {
916 apply_preset("Minimal");
917 }
918 if (ImGui::MenuItem(
919 absl::StrFormat("%s Developer", ICON_MD_DEVELOPER_MODE).c_str(),
920 nullptr, false, layout_enabled)) {
921 OnLoadDeveloperLayout();
922 }
923 if (ImGui::MenuItem(
924 absl::StrFormat("%s Designer", ICON_MD_DESIGN_SERVICES).c_str(),
925 nullptr, false, layout_enabled)) {
926 OnLoadDesignerLayout();
927 }
928 if (ImGui::MenuItem(absl::StrFormat("%s Modder", ICON_MD_BUILD).c_str(),
929 nullptr, false, layout_enabled)) {
930 OnLoadModderLayout();
931 }
932 if (ImGui::MenuItem(
933 absl::StrFormat("%s Overworld Expert", ICON_MD_MAP).c_str(),
934 nullptr, false, layout_enabled)) {
935 apply_preset("Overworld Expert");
936 }
937 if (ImGui::MenuItem(
938 absl::StrFormat("%s Dungeon Expert", ICON_MD_CASTLE).c_str(),
939 nullptr, false, layout_enabled)) {
940 apply_preset("Dungeon Expert");
941 }
942 if (ImGui::MenuItem(
943 absl::StrFormat("%s Testing", ICON_MD_SCIENCE).c_str(), nullptr,
944 false, layout_enabled)) {
945 apply_preset("Testing");
946 }
947 if (ImGui::MenuItem(
948 absl::StrFormat("%s Audio", ICON_MD_MUSIC_NOTE).c_str(), nullptr,
949 false, layout_enabled)) {
950 apply_preset("Audio");
951 }
952 ImGui::Separator();
953 if (ImGui::MenuItem(
954 absl::StrFormat("%s Manage Presets...", ICON_MD_TUNE).c_str())) {
955 OnShowLayoutPresets();
956 }
957 ImGui::EndMenu();
958 }
959 ImGui::EndMenu();
960 }
961}
962
963// Sidebar submenu — surfaces ActivityBar pin/hide/reorder operations so the
964// feature is discoverable from the menubar without touching the rail.
965void MenuOrchestrator::AddSidebarSubmenu() {
966 if (!user_settings_) {
967 return;
968 }
969
970 if (!ImGui::BeginMenu(
971 absl::StrFormat("%s Sidebar", ICON_MD_VIEW_SIDEBAR).c_str())) {
972 return;
973 }
974
975 auto persist = [this]() {
976 (void)user_settings_->Save();
977 };
978 auto& prefs = user_settings_->prefs();
979
980 if (ImGui::MenuItem(
981 absl::StrFormat("%s Reset Order", ICON_MD_RESTART_ALT).c_str(),
982 nullptr, false, !prefs.sidebar_order.empty())) {
983 prefs.sidebar_order.clear();
984 persist();
985 }
986 if (ImGui::MenuItem(
987 absl::StrFormat("%s Show All Categories", ICON_MD_VISIBILITY).c_str(),
988 nullptr, false, !prefs.sidebar_hidden.empty())) {
989 prefs.sidebar_hidden.clear();
990 persist();
991 }
992
993 ImGui::Separator();
994
995 const size_t session_id = session_coordinator_.GetActiveSessionId();
996 std::vector<std::string> categories;
997 if (window_manager_) {
998 categories = window_manager_->GetAllCategories(session_id);
999 }
1000
1001 // Build pinned list (in canonical category order).
1002 std::vector<std::string> pinned_list;
1003 std::vector<std::string> hidden_list;
1004 for (const auto& cat : categories) {
1005 if (cat == WorkspaceWindowManager::kDashboardCategory)
1006 continue;
1007 if (prefs.sidebar_pinned.count(cat))
1008 pinned_list.push_back(cat);
1009 if (prefs.sidebar_hidden.count(cat))
1010 hidden_list.push_back(cat);
1011 }
1012
1013 if (ImGui::BeginMenu(
1014 absl::StrFormat("%s Pinned", ICON_MD_PUSH_PIN).c_str())) {
1015 if (pinned_list.empty()) {
1016 ImGui::TextDisabled("(none)");
1017 } else {
1018 for (const auto& cat : pinned_list) {
1019 ImGui::PushID(cat.c_str());
1020 if (ImGui::MenuItem(
1021 absl::StrFormat("%s Unpin %s", ICON_MD_CLOSE, cat).c_str())) {
1022 prefs.sidebar_pinned.erase(cat);
1023 persist();
1024 }
1025 ImGui::PopID();
1026 }
1027 }
1028 ImGui::EndMenu();
1029 }
1030
1031 if (ImGui::BeginMenu(
1032 absl::StrFormat("%s Hidden", ICON_MD_VISIBILITY_OFF).c_str())) {
1033 if (hidden_list.empty()) {
1034 ImGui::TextDisabled("(none)");
1035 } else {
1036 for (const auto& cat : hidden_list) {
1037 ImGui::PushID(cat.c_str());
1038 if (ImGui::MenuItem(
1039 absl::StrFormat("%s Show %s", ICON_MD_VISIBILITY, cat)
1040 .c_str())) {
1041 prefs.sidebar_hidden.erase(cat);
1042 persist();
1043 }
1044 ImGui::PopID();
1045 }
1046 }
1047 ImGui::EndMenu();
1048 }
1049
1050 ImGui::Separator();
1051
1052 // Per-category toggles (pin/hide) for all categories. Keeps the menu
1053 // discoverable even for users who haven't right-clicked the rail.
1054 if (ImGui::BeginMenu(absl::StrFormat("%s Customize", ICON_MD_TUNE).c_str())) {
1055 if (categories.empty()) {
1056 ImGui::TextDisabled("No categories available");
1057 } else {
1058 for (const auto& cat : categories) {
1059 if (cat == WorkspaceWindowManager::kDashboardCategory)
1060 continue;
1061 ImGui::PushID(cat.c_str());
1062 const bool pinned = prefs.sidebar_pinned.count(cat) > 0;
1063 const bool hidden = prefs.sidebar_hidden.count(cat) > 0;
1064 if (ImGui::BeginMenu(cat.c_str())) {
1065 if (ImGui::MenuItem(pinned ? "Unpin from Top" : "Pin to Top", nullptr,
1066 pinned)) {
1067 if (pinned) {
1068 prefs.sidebar_pinned.erase(cat);
1069 } else {
1070 prefs.sidebar_pinned.insert(cat);
1071 }
1072 persist();
1073 }
1074 if (ImGui::MenuItem(hidden ? "Show on Sidebar" : "Hide from Sidebar",
1075 nullptr, hidden)) {
1076 if (hidden) {
1077 prefs.sidebar_hidden.erase(cat);
1078 } else {
1079 prefs.sidebar_hidden.insert(cat);
1080 }
1081 persist();
1082 }
1083 ImGui::EndMenu();
1084 }
1085 ImGui::PopID();
1086 }
1087 }
1088 ImGui::EndMenu();
1089 }
1090
1091 ImGui::EndMenu();
1092}
1093
1094void MenuOrchestrator::BuildHelpMenu() {
1095 menu_builder_.BeginMenu("Help");
1096 AddHelpMenuItems();
1097 menu_builder_.EndMenu();
1098}
1099
1100void MenuOrchestrator::AddHelpMenuItems() {
1101 // Note: Asar Integration moved to Tools menu to reduce redundancy
1102 menu_builder_
1103 .Item("Getting Started", ICON_MD_PLAY_ARROW,
1104 [this]() { OnShowGettingStarted(); })
1105 .Item("Coming from ZScream", ICON_MD_COMPARE_ARROWS,
1106 [this]() { OnShowComingFromZScream(); })
1107 .Item(
1108 "Keyboard Shortcuts", ICON_MD_KEYBOARD,
1109 [this]() {
1110 if (window_manager_) {
1111 window_manager_->TriggerShowShortcuts();
1112 }
1113 },
1114 GetShortcutForAction("Keyboard Shortcuts").c_str())
1115 .Item("Build Instructions", ICON_MD_BUILD,
1116 [this]() { OnShowBuildInstructions(); })
1117 .Item("CLI Usage", ICON_MD_TERMINAL, [this]() { OnShowCLIUsage(); })
1118 .Separator()
1119 .Item("Supported Features", ICON_MD_CHECK_CIRCLE,
1120 [this]() { OnShowSupportedFeatures(); })
1121 .Item("What's New", ICON_MD_NEW_RELEASES, [this]() { OnShowWhatsNew(); })
1122 .Separator()
1123 .Item("Troubleshooting", ICON_MD_BUILD_CIRCLE,
1124 [this]() { OnShowTroubleshooting(); })
1125 .Item("Contributing", ICON_MD_VOLUNTEER_ACTIVISM,
1126 [this]() { OnShowContributing(); })
1127 .Separator()
1128 .Item(
1129 "About", ICON_MD_INFO, [this]() { OnShowAbout(); },
1130 GetShortcutForAction("Show About").c_str());
1131
1132 menu_builder_.Separator();
1133 menu_builder_.BeginSubMenu("Language", ICON_MD_LANGUAGE);
1134 for (const std::string& locale :
1136 menu_builder_.Item(
1137 locale.c_str(), nullptr,
1138 [locale]() { i18n::LanguageManager::Get().SetLanguage(locale); },
1139 nullptr, nullptr,
1140 [locale]() {
1141 return i18n::LanguageManager::Get().GetCurrentLocale() == locale;
1142 });
1143 }
1144 menu_builder_.EndMenu();
1145}
1146
1147// Menu state management
1148void MenuOrchestrator::ClearMenu() {
1149 menu_builder_.Clear();
1150}
1151
1152void MenuOrchestrator::RefreshMenu() {
1153 menu_needs_refresh_ = true;
1154}
1155
1156// Menu item callbacks - delegate to appropriate managers
1157void MenuOrchestrator::OnOpenRom() {
1158 // Delegate to EditorManager's LoadRom which handles session management
1159 if (editor_manager_) {
1160 auto status = editor_manager_->LoadRom();
1161 if (!status.ok()) {
1162 toast_manager_.Show(
1163 absl::StrFormat("Failed to load ROM: %s", status.message()),
1164 ToastType::kError);
1165 }
1166 }
1167}
1168
1169void MenuOrchestrator::OnSaveRom() {
1170 // Delegate to EditorManager's SaveRom which handles editor data saving
1171 if (editor_manager_) {
1172 auto status = editor_manager_->SaveRom();
1173 if (!status.ok()) {
1174 if (absl::IsCancelled(status)) {
1175 return;
1176 }
1177 toast_manager_.Show(
1178 absl::StrFormat("Failed to save ROM: %s", status.message()),
1179 ToastType::kError);
1180 }
1181 }
1182}
1183
1184void MenuOrchestrator::OnSaveRomAs() {
1185 popup_manager_.Show(PopupID::kSaveAs);
1186}
1187
1188void MenuOrchestrator::OnCreateProject() {
1189 // Delegate to EditorManager which handles the full project creation flow
1190 if (editor_manager_) {
1191 auto status = editor_manager_->CreateNewProject();
1192 if (!status.ok()) {
1193 toast_manager_.Show(
1194 absl::StrFormat("Failed to create project: %s", status.message()),
1195 ToastType::kError);
1196 }
1197 }
1198}
1199
1200void MenuOrchestrator::OnOpenProject() {
1201 // Delegate to EditorManager which handles ROM loading and session creation
1202 if (editor_manager_) {
1203 auto status = editor_manager_->OpenProject();
1204 if (!status.ok()) {
1205 toast_manager_.Show(
1206 absl::StrFormat("Failed to open project: %s", status.message()),
1207 ToastType::kError);
1208 }
1209 }
1210}
1211
1212void MenuOrchestrator::OnSaveProject() {
1213 // Delegate to EditorManager which updates project with current state
1214 if (editor_manager_) {
1215 auto status = editor_manager_->SaveProject();
1216 if (!status.ok()) {
1217 toast_manager_.Show(
1218 absl::StrFormat("Failed to save project: %s", status.message()),
1219 ToastType::kError);
1220 } else {
1221 toast_manager_.Show("Project saved successfully", ToastType::kSuccess);
1222 }
1223 }
1224}
1225
1226void MenuOrchestrator::OnSaveProjectAs() {
1227 // Delegate to EditorManager
1228 if (editor_manager_) {
1229 auto status = editor_manager_->SaveProjectAs();
1230 if (!status.ok()) {
1231 toast_manager_.Show(
1232 absl::StrFormat("Failed to save project as: %s", status.message()),
1233 ToastType::kError);
1234 }
1235 }
1236}
1237
1238void MenuOrchestrator::OnShowProjectManagement() {
1239 // Show project management panel in right sidebar
1240 if (editor_manager_) {
1241 editor_manager_->ShowProjectManagement();
1242 }
1243}
1244
1245void MenuOrchestrator::OnShowProjectFileEditor() {
1246 // Open the project file editor with the current project file
1247 if (editor_manager_) {
1248 editor_manager_->ShowProjectFileEditor();
1249 }
1250}
1251
1252void MenuOrchestrator::OnOpenRecentFile(const std::string& path) {
1253 if (!editor_manager_) {
1254 return;
1255 }
1256 // OpenRomOrProject routes through the unsaved-work confirmation itself.
1257 auto status = editor_manager_->OpenRomOrProject(path);
1258 if (!status.ok()) {
1259 toast_manager_.Show(
1260 absl::StrFormat("Failed to open %s: %s", path, status.message()),
1261 ToastType::kError);
1262 }
1263}
1264
1265void MenuOrchestrator::OnClearRecentFiles() {
1267 recent.Clear();
1268 recent.Save();
1269}
1270
1271void MenuOrchestrator::OnCloseRom() {
1272 if (editor_manager_) {
1273 editor_manager_->CloseRom();
1274 }
1275}
1276
1277void MenuOrchestrator::OnRevertRom() {
1278 if (!editor_manager_) {
1279 return;
1280 }
1281 auto status = editor_manager_->RevertRomToSaved();
1282 if (!status.ok()) {
1283 toast_manager_.Show(absl::StrFormat("Revert failed: %s", status.message()),
1284 ToastType::kError, 6.0f);
1285 }
1286}
1287
1288// Edit menu actions - delegate to current editor
1289void MenuOrchestrator::OnUndo() {
1290 if (editor_manager_) {
1291 auto* current_editor = editor_manager_->GetCurrentEditor();
1292 if (current_editor) {
1293 // Capture description before undo moves the action to the redo stack
1294 std::string desc = current_editor->GetUndoDescription();
1295 auto status = current_editor->Undo();
1296 if (status.ok()) {
1297 if (!desc.empty()) {
1298 toast_manager_.Show(absl::StrFormat("Undid: %s", desc),
1299 ToastType::kInfo, 2.0f);
1300 }
1301 } else {
1302 toast_manager_.Show(
1303 absl::StrFormat("Undo failed: %s", status.message()),
1304 ToastType::kError);
1305 }
1306 }
1307 }
1308}
1309
1310void MenuOrchestrator::OnRedo() {
1311 if (editor_manager_) {
1312 auto* current_editor = editor_manager_->GetCurrentEditor();
1313 if (current_editor) {
1314 // Capture description before redo moves the action to the undo stack
1315 std::string desc = current_editor->GetRedoDescription();
1316 auto status = current_editor->Redo();
1317 if (status.ok()) {
1318 if (!desc.empty()) {
1319 toast_manager_.Show(absl::StrFormat("Redid: %s", desc),
1320 ToastType::kInfo, 2.0f);
1321 }
1322 } else {
1323 toast_manager_.Show(
1324 absl::StrFormat("Redo failed: %s", status.message()),
1325 ToastType::kError);
1326 }
1327 }
1328 }
1329}
1330
1331void MenuOrchestrator::OnCut() {
1332 if (!editor_manager_) {
1333 return;
1334 }
1335 auto* current_editor = editor_manager_->GetCurrentEditor();
1336 if (!current_editor) {
1337 return;
1338 }
1339 auto status = current_editor->Cut();
1340 if (status.ok()) {
1341 return;
1342 }
1343 if (absl::IsUnimplemented(status)) {
1344 // Editors that have not implemented Cut should override CanCut();
1345 // until then report it plainly instead of as a failure.
1346 toast_manager_.Show("Cut is not available in this editor", ToastType::kInfo,
1347 2.0f);
1348 return;
1349 }
1350 toast_manager_.Show(absl::StrFormat("Cut failed: %s", status.message()),
1351 ToastType::kError);
1352}
1353
1354void MenuOrchestrator::OnCopy() {
1355 if (!editor_manager_) {
1356 return;
1357 }
1358 auto* current_editor = editor_manager_->GetCurrentEditor();
1359 if (!current_editor) {
1360 return;
1361 }
1362 auto status = current_editor->Copy();
1363 if (status.ok()) {
1364 return;
1365 }
1366 if (absl::IsUnimplemented(status)) {
1367 // Editors that have not implemented Copy should override CanCopy();
1368 // until then report it plainly instead of as a failure.
1369 toast_manager_.Show("Copy is not available in this editor",
1370 ToastType::kInfo, 2.0f);
1371 return;
1372 }
1373 toast_manager_.Show(absl::StrFormat("Copy failed: %s", status.message()),
1374 ToastType::kError);
1375}
1376
1377void MenuOrchestrator::OnPaste() {
1378 if (!editor_manager_) {
1379 return;
1380 }
1381 auto* current_editor = editor_manager_->GetCurrentEditor();
1382 if (!current_editor) {
1383 return;
1384 }
1385 auto status = current_editor->Paste();
1386 if (status.ok()) {
1387 return;
1388 }
1389 if (absl::IsUnimplemented(status)) {
1390 // Editors that have not implemented Paste should override CanPaste();
1391 // until then report it plainly instead of as a failure.
1392 toast_manager_.Show("Paste is not available in this editor",
1393 ToastType::kInfo, 2.0f);
1394 return;
1395 }
1396 toast_manager_.Show(absl::StrFormat("Paste failed: %s", status.message()),
1397 ToastType::kError);
1398}
1399
1400void MenuOrchestrator::OnFind() {
1401 if (!editor_manager_) {
1402 return;
1403 }
1404 auto* current_editor = editor_manager_->GetCurrentEditor();
1405 if (!current_editor) {
1406 return;
1407 }
1408 auto status = current_editor->Find();
1409 if (status.ok()) {
1410 return;
1411 }
1412 if (absl::IsUnimplemented(status)) {
1413 // Editors that have not implemented Find should override CanFind();
1414 // until then report it plainly instead of as a failure.
1415 toast_manager_.Show("Find is not available in this editor",
1416 ToastType::kInfo, 2.0f);
1417 return;
1418 }
1419 toast_manager_.Show(absl::StrFormat("Find failed: %s", status.message()),
1420 ToastType::kError);
1421}
1422
1423// Editor-specific menu actions
1424void MenuOrchestrator::OnSwitchToEditor(EditorType editor_type) {
1425 // Delegate to EditorManager which manages editor switching
1426 if (editor_manager_) {
1427 editor_manager_->SwitchToEditor(editor_type);
1428 }
1429}
1430
1431void MenuOrchestrator::OnShowEditorSelection() {
1432 // Delegate to UICoordinator for editor selection dialog display
1433 if (editor_manager_) {
1434 if (auto* ui = editor_manager_->ui_coordinator()) {
1435 ui->ShowEditorSelection();
1436 }
1437 }
1438}
1439
1440void MenuOrchestrator::OnShowDisplaySettings() {
1441 popup_manager_.Show(PopupID::kDisplaySettings);
1442}
1443
1444namespace {
1445// Runs the callback registered for `action` so menu items and keyboard
1446// shortcuts share one implementation (e.g. the ui.font_scale_* actions).
1448 const std::string& action) {
1449 if (manager == nullptr) {
1450 return false;
1451 }
1452 const Shortcut* shortcut = manager->FindShortcut(action);
1453 if (shortcut == nullptr || !shortcut->callback) {
1454 return false;
1455 }
1456 shortcut->callback();
1457 return true;
1458}
1459
1460// Fallback used only when the shortcut action is not registered.
1461constexpr float kMenuFontScaleStep = 0.05f;
1462constexpr float kMenuFontScaleMin = 0.5f;
1463constexpr float kMenuFontScaleMax = 2.0f;
1464} // namespace
1465
1466void MenuOrchestrator::OnZoomIn() {
1467 if (RunShortcutAction(shortcut_manager_, "ui.font_scale_increase") ||
1468 !editor_manager_) {
1469 return;
1470 }
1471 const float scale =
1472 editor_manager_->user_settings().prefs().font_global_scale +
1473 kMenuFontScaleStep;
1474 editor_manager_->SetFontGlobalScale(
1475 std::clamp(scale, kMenuFontScaleMin, kMenuFontScaleMax));
1476}
1477
1478void MenuOrchestrator::OnZoomOut() {
1479 if (RunShortcutAction(shortcut_manager_, "ui.font_scale_decrease") ||
1480 !editor_manager_) {
1481 return;
1482 }
1483 const float scale =
1484 editor_manager_->user_settings().prefs().font_global_scale -
1485 kMenuFontScaleStep;
1486 editor_manager_->SetFontGlobalScale(
1487 std::clamp(scale, kMenuFontScaleMin, kMenuFontScaleMax));
1488}
1489
1490void MenuOrchestrator::OnZoomReset() {
1491 if (RunShortcutAction(shortcut_manager_, "ui.font_scale_reset") ||
1492 !editor_manager_) {
1493 return;
1494 }
1495 editor_manager_->SetFontGlobalScale(1.0f);
1496}
1497
1498void MenuOrchestrator::OnToggleFullscreen() {
1499 if (!SetMainWindowFullscreen(!IsMainWindowFullscreen())) {
1500 toast_manager_.Show("Fullscreen is not available for this window",
1501 ToastType::kWarning);
1502 }
1503}
1504
1505void MenuOrchestrator::OnShowHexEditor() {
1506 // Show hex editor window via WorkspaceWindowManager
1507 if (editor_manager_) {
1508 editor_manager_->window_manager().OpenWindow(
1509 editor_manager_->GetCurrentSessionId(), "Hex Editor");
1510 }
1511}
1512
1513void MenuOrchestrator::OnShowPanelBrowser() {
1514 if (editor_manager_) {
1515 if (auto* ui = editor_manager_->ui_coordinator()) {
1516 ui->SetWindowBrowserVisible(true);
1517 }
1518 }
1519}
1520
1521void MenuOrchestrator::OnShowPanelFinder() {
1522 if (editor_manager_) {
1523 if (auto* ui = editor_manager_->ui_coordinator()) {
1524 ui->ShowPanelFinder();
1525 }
1526 }
1527}
1528
1529void MenuOrchestrator::OnShowWelcomeScreen() {
1530 if (editor_manager_) {
1531 if (auto* ui = editor_manager_->ui_coordinator()) {
1532 ui->SetWelcomeScreenVisible(true);
1533 }
1534 }
1535}
1536
1537#ifdef YAZE_BUILD_AGENT_UI
1538void MenuOrchestrator::OnShowAIAgent() {
1539 if (editor_manager_) {
1540 if (auto* ui = editor_manager_->ui_coordinator()) {
1541 ui->SetAIAgentVisible(true);
1542 }
1543 }
1544}
1545
1546void MenuOrchestrator::OnShowProposalDrawer() {
1547 if (editor_manager_) {
1548 if (auto* ui = editor_manager_->ui_coordinator()) {
1549 ui->SetProposalDrawerVisible(true);
1550 }
1551 }
1552}
1553#endif
1554
1555// Session management menu actions
1556void MenuOrchestrator::OnCreateNewSession() {
1557 session_coordinator_.CreateNewSession();
1558}
1559
1560void MenuOrchestrator::OnDuplicateCurrentSession() {
1561 session_coordinator_.DuplicateCurrentSession();
1562}
1563
1564void MenuOrchestrator::OnCloseCurrentSession() {
1565 if (editor_manager_) {
1566 editor_manager_->CloseCurrentSession();
1567 } else {
1568 session_coordinator_.CloseCurrentSession();
1569 }
1570}
1571
1572void MenuOrchestrator::OnShowSessionSwitcher() {
1573 // Delegate to UICoordinator for session switcher UI
1574 if (editor_manager_) {
1575 if (auto* ui = editor_manager_->ui_coordinator()) {
1576 ui->ShowSessionSwitcher();
1577 }
1578 }
1579}
1580
1581void MenuOrchestrator::OnShowSessionManager() {
1582 popup_manager_.Show(PopupID::kSessionManager);
1583}
1584
1585// Window management menu actions
1586void MenuOrchestrator::OnShowAllWindows() {
1587 // Delegate to EditorManager
1588 if (editor_manager_) {
1589 if (auto* ui = editor_manager_->ui_coordinator()) {
1590 ui->ShowAllWindows();
1591 }
1592 }
1593}
1594
1595void MenuOrchestrator::OnHideAllWindows() {
1596 // Delegate to EditorManager
1597 if (editor_manager_) {
1598 editor_manager_->HideAllWindows();
1599 }
1600}
1601
1602void MenuOrchestrator::OnResetWorkspaceLayout() {
1603 // Queue as deferred action to avoid modifying ImGui state during menu rendering
1604 if (editor_manager_) {
1605 editor_manager_->QueueDeferredAction([this]() {
1606 editor_manager_->ResetWorkspaceLayout();
1607 toast_manager_.Show("Layout reset to default", ToastType::kInfo);
1608 });
1609 }
1610}
1611
1612void MenuOrchestrator::OnSaveWorkspaceLayout() {
1613 // Delegate to EditorManager
1614 if (editor_manager_) {
1615 editor_manager_->SaveWorkspaceLayout();
1616 }
1617}
1618
1619void MenuOrchestrator::OnLoadWorkspaceLayout() {
1620 // Delegate to EditorManager
1621 if (editor_manager_) {
1622 editor_manager_->LoadWorkspaceLayout();
1623 }
1624}
1625
1626void MenuOrchestrator::OnShowLayoutPresets() {
1627 popup_manager_.Show(PopupID::kLayoutPresets);
1628}
1629
1630void MenuOrchestrator::OnShowLayoutDesigner() {
1631 WorkspaceWindowManager* manager = window_manager_;
1632 if (manager == nullptr && editor_manager_ != nullptr) {
1633 manager = &editor_manager_->window_manager();
1634 }
1635 if (manager == nullptr) {
1636 toast_manager_.Show("Layout Designer unavailable: no window manager",
1637 ToastType::kError);
1638 return;
1639 }
1640
1641 const size_t session_id = session_coordinator_.GetActiveSessionId();
1642 if (manager->GetActiveCategory().empty() ||
1643 manager->GetActiveCategory() ==
1644 WorkspaceWindowManager::kDashboardCategory) {
1645 manager->SetActiveCategory("Settings");
1646 }
1647
1648 const bool already_open =
1649 manager->IsWindowOpen(session_id, kLayoutDesignerWindowId);
1650 if (!already_open &&
1651 !manager->OpenWindow(session_id, kLayoutDesignerWindowId)) {
1652 toast_manager_.Show(
1653 "Layout Designer is not registered in the active session",
1654 ToastType::kError);
1655 return;
1656 }
1657
1658 // Cross-editor Settings-category panels are only drawn from another editor
1659 // when pinned. Opening the designer from the menubar should make it visible
1660 // immediately instead of merely flipping an off-category visibility flag.
1661 manager->SetWindowPinned(session_id, kLayoutDesignerWindowId, true);
1662}
1663
1664void MenuOrchestrator::OnLoadDeveloperLayout() {
1665 if (editor_manager_) {
1666 editor_manager_->ApplyLayoutPreset("Developer");
1667 }
1668}
1669
1670void MenuOrchestrator::OnLoadDesignerLayout() {
1671 if (editor_manager_) {
1672 editor_manager_->ApplyLayoutPreset("Designer");
1673 }
1674}
1675
1676void MenuOrchestrator::OnLoadModderLayout() {
1677 if (editor_manager_) {
1678 editor_manager_->ApplyLayoutPreset("Modder");
1679 }
1680}
1681
1682// Tool menu actions
1683void MenuOrchestrator::OnShowGlobalSearch() {
1684 if (editor_manager_) {
1685 if (auto* ui = editor_manager_->ui_coordinator()) {
1686 ui->ShowGlobalSearch();
1687 }
1688 }
1689}
1690
1691void MenuOrchestrator::OnShowCommandPalette() {
1692 if (editor_manager_) {
1693 if (auto* ui = editor_manager_->ui_coordinator()) {
1694 ui->ShowCommandPalette();
1695 }
1696 }
1697}
1698
1699void MenuOrchestrator::OnShowPerformanceDashboard() {
1700 if (editor_manager_) {
1701 if (auto* ui = editor_manager_->ui_coordinator()) {
1702 ui->SetPerformanceDashboardVisible(true);
1703 }
1704 }
1705}
1706
1707void MenuOrchestrator::OnShowImGuiDemo() {
1708 if (editor_manager_) {
1709 editor_manager_->ShowImGuiDemo();
1710 }
1711}
1712
1713void MenuOrchestrator::OnShowImGuiMetrics() {
1714 if (editor_manager_) {
1715 editor_manager_->ShowImGuiMetrics();
1716 }
1717}
1718
1719void MenuOrchestrator::OnShowMemoryEditor() {
1720 if (editor_manager_) {
1721 editor_manager_->window_manager().OpenWindow(
1722 editor_manager_->GetCurrentSessionId(), "Memory Editor");
1723 }
1724}
1725
1726void MenuOrchestrator::OnShowResourceLabelManager() {
1727 if (editor_manager_) {
1728 if (auto* ui = editor_manager_->ui_coordinator()) {
1729 ui->SetResourceLabelManagerVisible(true);
1730 }
1731 }
1732}
1733
1734#ifdef YAZE_ENABLE_TESTING
1735void MenuOrchestrator::OnShowTestDashboard() {
1736 if (editor_manager_) {
1737 editor_manager_->ShowTestDashboard();
1738 }
1739}
1740
1741#endif
1742
1743// Help menu actions
1744void MenuOrchestrator::OnShowAbout() {
1745 popup_manager_.Show(PopupID::kAbout);
1746}
1747
1748void MenuOrchestrator::OnShowGettingStarted() {
1749 popup_manager_.Show(PopupID::kGettingStarted);
1750}
1751
1752void MenuOrchestrator::OnShowComingFromZScream() {
1753 popup_manager_.Show(PopupID::kComingFromZScream);
1754}
1755
1756void MenuOrchestrator::OnShowBuildInstructions() {
1757 popup_manager_.Show(PopupID::kBuildInstructions);
1758}
1759
1760void MenuOrchestrator::OnShowCLIUsage() {
1761 popup_manager_.Show(PopupID::kCLIUsage);
1762}
1763
1764void MenuOrchestrator::OnShowTroubleshooting() {
1765 popup_manager_.Show(PopupID::kTroubleshooting);
1766}
1767
1768void MenuOrchestrator::OnShowContributing() {
1769 popup_manager_.Show(PopupID::kContributing);
1770}
1771
1772void MenuOrchestrator::OnShowWhatsNew() {
1773 popup_manager_.Show(PopupID::kWhatsNew);
1774}
1775
1776void MenuOrchestrator::OnShowSupportedFeatures() {
1777 popup_manager_.Show(PopupID::kSupportedFeatures);
1778}
1779
1780// Additional File menu actions
1781void MenuOrchestrator::OnShowRomInfo() {
1782 popup_manager_.Show(PopupID::kRomInfo);
1783}
1784
1785void MenuOrchestrator::OnCreateBackup() {
1786 if (editor_manager_) {
1787 auto status = rom_manager_.CreateBackup(editor_manager_->GetCurrentRom());
1788 if (status.ok()) {
1789 toast_manager_.Show("Backup created successfully", ToastType::kSuccess);
1790 } else {
1791 toast_manager_.Show(
1792 absl::StrFormat("Backup failed: %s", status.message()),
1793 ToastType::kError);
1794 }
1795 }
1796}
1797
1798void MenuOrchestrator::OnValidateRom() {
1799 if (editor_manager_) {
1800 auto status = rom_manager_.ValidateRom(editor_manager_->GetCurrentRom());
1801 if (status.ok()) {
1802 toast_manager_.Show("ROM validation passed", ToastType::kSuccess);
1803 } else {
1804 toast_manager_.Show(
1805 absl::StrFormat("ROM validation failed: %s", status.message()),
1806 ToastType::kError);
1807 }
1808 }
1809}
1810
1811void MenuOrchestrator::OnShowSettings() {
1812 // Activate settings editor
1813 if (editor_manager_) {
1814 editor_manager_->SwitchToEditor(EditorType::kSettings);
1815 }
1816}
1817
1818void MenuOrchestrator::OnQuit() {
1819 if (editor_manager_) {
1820 editor_manager_->Quit();
1821 }
1822}
1823
1824// Menu item validation helpers
1825bool MenuOrchestrator::CanSaveRom() const {
1826 auto* rom = editor_manager_ ? editor_manager_->GetCurrentRom() : nullptr;
1827 return rom ? rom_manager_.IsRomLoaded(rom) : false;
1828}
1829
1830bool MenuOrchestrator::CanSaveProject() const {
1831 // A closed or pending session may leave a descriptor without owning it.
1832 return HasProjectFile() &&
1833 editor_manager_->IsCurrentProjectContextOwnedBySession(
1834 editor_manager_->GetCurrentSessionId());
1835}
1836
1837bool MenuOrchestrator::HasActiveRom() const {
1838 auto* rom = editor_manager_ ? editor_manager_->GetCurrentRom() : nullptr;
1839 return rom ? rom_manager_.IsRomLoaded(rom) : false;
1840}
1841
1842bool MenuOrchestrator::HasActiveProject() const {
1843 return project_manager_.HasActiveProject();
1844}
1845
1846bool MenuOrchestrator::HasProjectFile() const {
1847 // Check if EditorManager has a project with a valid filepath
1848 // This is separate from HasActiveProject which checks ProjectManager
1849 const auto* project =
1850 editor_manager_ ? editor_manager_->GetCurrentProject() : nullptr;
1851 return project && !project->filepath.empty();
1852}
1853
1854bool MenuOrchestrator::HasCurrentEditor() const {
1855 return editor_manager_ && editor_manager_->GetCurrentEditor() != nullptr;
1856}
1857
1858bool MenuOrchestrator::HasMultipleSessions() const {
1859 return session_coordinator_.HasMultipleSessions();
1860}
1861
1862bool MenuOrchestrator::CurrentEditorCanUndo() const {
1863 const Editor* editor =
1864 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1865 return editor != nullptr && editor->CanUndo();
1866}
1867
1868bool MenuOrchestrator::CurrentEditorCanRedo() const {
1869 const Editor* editor =
1870 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1871 return editor != nullptr && editor->CanRedo();
1872}
1873
1874bool MenuOrchestrator::CurrentEditorCanCut() const {
1875 const Editor* editor =
1876 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1877 return editor != nullptr && editor->CanCut();
1878}
1879
1880bool MenuOrchestrator::CurrentEditorCanCopy() const {
1881 const Editor* editor =
1882 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1883 return editor != nullptr && editor->CanCopy();
1884}
1885
1886bool MenuOrchestrator::CurrentEditorCanPaste() const {
1887 const Editor* editor =
1888 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1889 return editor != nullptr && editor->CanPaste();
1890}
1891
1892bool MenuOrchestrator::CurrentEditorCanFind() const {
1893 const Editor* editor =
1894 editor_manager_ ? editor_manager_->GetCurrentEditor() : nullptr;
1895 return editor != nullptr && editor->CanFind();
1896}
1897
1898// Menu item text generation
1899std::string MenuOrchestrator::GetRomFilename() const {
1900 auto* rom = editor_manager_ ? editor_manager_->GetCurrentRom() : nullptr;
1901 return rom ? rom_manager_.GetRomFilename(rom) : "";
1902}
1903
1904std::string MenuOrchestrator::GetProjectName() const {
1905 return project_manager_.GetProjectName();
1906}
1907
1908std::string MenuOrchestrator::GetCurrentEditorName() const {
1909 // TODO: Get current editor name
1910 return "Unknown Editor";
1911}
1912
1913// Shortcut labels: read from the live ShortcutManager bindings.
1914std::string MenuOrchestrator::GetShortcutForAction(
1915 const std::string& action) const {
1916 return ShortcutLabelForAction(shortcut_manager_, action);
1917}
1918
1919// ============================================================================
1920// Debug Menu Actions
1921// ============================================================================
1922
1923void MenuOrchestrator::OnCheckRomVersion() {
1924 if (!editor_manager_)
1925 return;
1926 auto* rom = editor_manager_->GetCurrentRom();
1927 if (!rom || !rom->is_loaded())
1928 return;
1929
1930 // Check ZSCustomOverworld version
1931 uint8_t version = (*rom)[zelda3::OverworldCustomASMHasBeenApplied];
1932 std::string version_str =
1933 (version == 0xFF) ? "Vanilla" : absl::StrFormat("v%d", version);
1934
1935 toast_manager_.Show(
1936 absl::StrFormat("ROM: %s | ZSCustomOverworld: %s", rom->title().c_str(),
1937 version_str.c_str()),
1938 ToastType::kInfo, 5.0f);
1939}
1940
1941void MenuOrchestrator::OnOpenOverworldForUpgrade() {
1942 // The ZSCustomOverworld upgrade itself lives in the Overworld editor
1943 // toolbar ("Upgrade"); route there instead of pretending to upgrade here.
1944 if (!editor_manager_ || !HasActiveRom())
1945 return;
1946 editor_manager_->SwitchToEditor(EditorType::kOverworld);
1947 toast_manager_.Show(
1948 "Use the Upgrade button in the Overworld toolbar to apply "
1949 "ZSCustomOverworld",
1950 ToastType::kInfo, 4.0f);
1951}
1952
1953void MenuOrchestrator::OnToggleCustomLoading() {
1954 auto& flags = core::FeatureFlags::get();
1955 flags.overworld.kLoadCustomOverworld = !flags.overworld.kLoadCustomOverworld;
1956
1957 toast_manager_.Show(
1958 absl::StrFormat(
1959 "Custom Overworld Loading: %s",
1960 flags.overworld.kLoadCustomOverworld ? "Enabled" : "Disabled"),
1961 ToastType::kInfo);
1962}
1963
1964void MenuOrchestrator::OnToggleAsarPatch() {
1965 if (!editor_manager_)
1966 return;
1967 auto* rom = editor_manager_->GetCurrentRom();
1968 if (!rom || !rom->is_loaded())
1969 return;
1970
1971 auto& flags = core::FeatureFlags::get();
1972 flags.overworld.kApplyZSCustomOverworldASM =
1973 !flags.overworld.kApplyZSCustomOverworldASM;
1974
1975 toast_manager_.Show(
1976 absl::StrFormat(
1977 "ZSCustomOverworld ASM Application: %s",
1978 flags.overworld.kApplyZSCustomOverworldASM ? "Enabled" : "Disabled"),
1979 ToastType::kInfo);
1980}
1981
1982void MenuOrchestrator::OnShowAssemblyEditor() {
1983 if (editor_manager_) {
1984 editor_manager_->SwitchToEditor(EditorType::kAssembly);
1985 }
1986}
1987
1988void MenuOrchestrator::OnExportBpsPatch() {
1989 if (!editor_manager_)
1990 return;
1991 auto* rom = editor_manager_->GetCurrentRom();
1992 if (!rom || !rom->is_loaded())
1993 return;
1994
1995 // Ask user to select the original/clean ROM to diff against
1996 auto options = util::MakeRomFileDialogOptions();
1997 std::string original_path =
1999 if (original_path.empty()) {
2000 return; // User cancelled
2001 }
2002
2003 // Load the original ROM
2004 Rom original_rom;
2005 auto load_status = original_rom.LoadFromFile(original_path);
2006 if (!load_status.ok()) {
2007 toast_manager_.Show(absl::StrFormat("Failed to load original ROM: %s",
2008 load_status.message()),
2009 ToastType::kError);
2010 return;
2011 }
2012
2013 // Generate BPS patch
2014 std::vector<uint8_t> patch_data;
2015 auto create_status =
2016 util::CreateBpsPatch(original_rom.vector(), rom->vector(), patch_data);
2017 if (!create_status.ok()) {
2018 toast_manager_.Show(absl::StrFormat("Failed to create BPS patch: %s",
2019 create_status.message()),
2020 ToastType::kError);
2021 return;
2022 }
2023
2024 // Ask user where to save the patch
2025 std::string default_name = rom->short_name() + ".bps";
2026 std::string save_path =
2028 if (save_path.empty()) {
2029 return; // User cancelled
2030 }
2031
2032 // Ensure .bps extension
2033 if (save_path.size() < 4 ||
2034 save_path.substr(save_path.size() - 4) != ".bps") {
2035 save_path += ".bps";
2036 }
2037
2038 // Write the patch file
2039 std::ofstream file(save_path, std::ios::binary);
2040 if (!file.is_open()) {
2041 toast_manager_.Show(
2042 absl::StrFormat("Failed to open file for writing: %s", save_path),
2043 ToastType::kError);
2044 return;
2045 }
2046
2047 file.write(reinterpret_cast<const char*>(patch_data.data()),
2048 patch_data.size());
2049 file.close();
2050
2051 if (file.fail()) {
2052 toast_manager_.Show(
2053 absl::StrFormat("Failed to write patch file: %s", save_path),
2054 ToastType::kError);
2055 return;
2056 }
2057
2058 toast_manager_.Show(absl::StrFormat("BPS patch exported: %s (%zu bytes)",
2059 save_path, patch_data.size()),
2060 ToastType::kSuccess);
2061}
2062
2063void MenuOrchestrator::OnApplyBpsPatch() {
2064 if (!editor_manager_)
2065 return;
2066 auto* rom = editor_manager_->GetCurrentRom();
2067 if (!rom || !rom->is_loaded())
2068 return;
2069
2070 // Ask user to select a .bps file
2072 options.filters.push_back({"BPS Patch", "bps"});
2073 std::string patch_path = util::FileDialogWrapper::ShowOpenFileDialog(options);
2074 if (patch_path.empty()) {
2075 return; // User cancelled
2076 }
2077
2078 // Read the patch file
2079 std::ifstream file(patch_path, std::ios::binary);
2080 if (!file.is_open()) {
2081 toast_manager_.Show(
2082 absl::StrFormat("Failed to open patch file: %s", patch_path),
2083 ToastType::kError);
2084 return;
2085 }
2086
2087 std::vector<uint8_t> patch_data((std::istreambuf_iterator<char>(file)),
2088 std::istreambuf_iterator<char>());
2089 file.close();
2090
2091 if (patch_data.empty()) {
2092 toast_manager_.Show("Patch file is empty", ToastType::kError);
2093 return;
2094 }
2095
2096 // Apply the patch
2097 std::vector<uint8_t> patched_rom;
2098 auto apply_status =
2099 util::ApplyBpsPatch(rom->vector(), patch_data, patched_rom);
2100 if (!apply_status.ok()) {
2101 toast_manager_.Show(absl::StrFormat("Failed to apply BPS patch: %s",
2102 apply_status.message()),
2103 ToastType::kError);
2104 return;
2105 }
2106
2107 // Load the patched data into the ROM
2108 auto load_status = rom->LoadFromData(patched_rom);
2109 if (!load_status.ok()) {
2110 toast_manager_.Show(absl::StrFormat("Failed to load patched ROM data: %s",
2111 load_status.message()),
2112 ToastType::kError);
2113 return;
2114 }
2115
2116 rom->set_dirty(true);
2117 toast_manager_.Show("BPS patch applied successfully", ToastType::kSuccess);
2118}
2119
2120} // namespace editor
2121} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:491
void set_dirty(bool dirty)
Definition rom.h:189
const auto & vector() const
Definition rom.h:205
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:606
auto short_name() const
Definition rom.h:209
bool is_loaded() const
Definition rom.h:187
auto title() const
Definition rom.h:199
static Flags & get()
Definition features.h:119
The EditorManager controls the main editor window and manages the various editor classes.
bool SaveLayoutSnapshotAs(const std::string &name)
RightDrawerManager * right_drawer_manager()
FeatureHintManager * feature_hint_manager()
Manages editor types, categories, and lifecycle.
Interface for editor classes.
Definition editor.h:249
virtual absl::Status Cut()=0
virtual absl::Status Copy()=0
virtual absl::Status Redo()=0
virtual std::string GetRedoDescription() const
Definition editor.h:299
virtual bool CanFind() const
Definition editor.h:324
virtual bool CanRedo() const
Definition editor.h:320
virtual bool CanCut() const
Definition editor.h:321
virtual absl::Status Find()=0
virtual bool CanUndo() const
Definition editor.h:319
virtual bool CanPaste() const
Definition editor.h:323
virtual absl::Status Paste()=0
virtual absl::Status Undo()=0
virtual bool CanCopy() const
Definition editor.h:322
virtual std::string GetUndoDescription() const
Definition editor.h:296
bool MaybeShow(std::string_view id)
Fluent interface for building ImGui menus with icons.
MenuBuilder & Item(const char *label, const char *icon, Callback callback, const char *shortcut=nullptr, EnabledCheck enabled=nullptr, EnabledCheck checked=nullptr)
Add a menu item.
MenuBuilder & CustomMenu(const char *label, Callback draw_callback)
Add a custom menu with a callback for drawing dynamic content.
void Draw()
Draw the menu bar (call in main menu bar)
std::function< bool()> EnabledCheck
MenuBuilder & BeginMenu(const char *label, const char *icon=nullptr)
Begin a top-level menu.
MenuBuilder & Separator()
Add a separator.
MenuBuilder & EndMenu()
End the current menu/submenu.
MenuBuilder & BeginSubMenu(const char *label, const char *icon=nullptr, EnabledCheck enabled=nullptr)
Begin a submenu.
MenuBuilder & DisabledItem(const char *label, const char *icon=nullptr)
Add a disabled item (grayed out)
SessionCoordinator & session_coordinator_
WorkspaceWindowManager * window_manager_
MenuOrchestrator(EditorManager *editor_manager, MenuBuilder &menu_builder, RomFileManager &rom_manager, ProjectManager &project_manager, EditorRegistry &editor_registry, SessionCoordinator &session_coordinator, ToastManager &toast_manager, PopupManager &popup_manager)
std::string GetShortcutForAction(const std::string &action) const
void OnOpenRecentFile(const std::string &path)
void Show(const char *name)
Handles all project file operations with ROM-first workflow.
Handles all ROM file I/O operations.
High-level orchestrator for multi-session UI.
size_t GetActiveSessionId() const
Stable workspace identity that is never reused while this coordinator lives.
const Shortcut * FindShortcut(const std::string &name) const
void SetEnabled(bool enabled)
Enable or disable the status bar.
Definition status_bar.h:71
Central registry for all editor cards with session awareness and dependency injection.
void HideAllWindowsInCategory(size_t session_id, const std::string &category)
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void SetWindowPinned(size_t session_id, const std::string &base_window_id, bool pinned)
void SetActiveCategory(const std::string &category, bool notify=true)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
std::vector< std::string > GetAllCategories(size_t session_id) const
bool ToggleWindow(size_t session_id, const std::string &base_window_id)
void ShowAllWindowsInCategory(size_t session_id, const std::string &category)
static ThemeManager & Get()
const std::string & GetCurrentThemeName() const
std::vector< std::string > GetAvailableLocales() const
void SetLanguage(const std::string &locale)
static LanguageManager & Get()
const std::string & GetCurrentLocale() const
static RecentFilesManager & GetInstance()
Definition project.h:598
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_DEVELOPER_MODE
Definition icons.h:549
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_CONTENT_CUT
Definition icons.h:466
#define ICON_MD_VOLUNTEER_ACTIVISM
Definition icons.h:2112
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_FILE_OPEN
Definition icons.h:747
#define ICON_MD_CHECK_BOX
Definition icons.h:398
#define ICON_MD_EXIT_TO_APP
Definition icons.h:699
#define ICON_MD_VIEW_QUILT
Definition icons.h:2094
#define ICON_MD_APPS
Definition icons.h:168
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_WORKSPACE_PREMIUM
Definition icons.h:2185
#define ICON_MD_BOOKMARKS
Definition icons.h:291
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_FOLDER_SPECIAL
Definition icons.h:815
#define ICON_MD_VIEW_LIST
Definition icons.h:2092
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_NEW_RELEASES
Definition icons.h:1291
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_SAVE_AS
Definition icons.h:1646
#define ICON_MD_DESIGN_SERVICES
Definition icons.h:541
#define ICON_MD_COMPARE_ARROWS
Definition icons.h:448
#define ICON_MD_RESET_TV
Definition icons.h:1601
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_DIFFERENCE
Definition icons.h:559
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_CODE
Definition icons.h:434
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_REDO
Definition icons.h:1570
#define ICON_MD_SWITCH_ACCOUNT
Definition icons.h:1913
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_LANGUAGE
Definition icons.h:1061
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_BUILD_CIRCLE
Definition icons.h:329
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_RESTORE
Definition icons.h:1605
#define ICON_MD_CONTENT_PASTE
Definition icons.h:467
#define ICON_MD_HOME
Definition icons.h:953
#define ICON_MD_FULLSCREEN
Definition icons.h:861
#define ICON_MD_ROUTE
Definition icons.h:1627
#define ICON_MD_VISIBILITY_OFF
Definition icons.h:2102
#define ICON_MD_DISPLAY_SETTINGS
Definition icons.h:587
#define ICON_MD_BOOKMARK_ADD
Definition icons.h:286
#define ICON_MD_VIEW_COMPACT
Definition icons.h:2085
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_DASHBOARD_CUSTOMIZE
Definition icons.h:518
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_SCIENCE
Definition icons.h:1656
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_TERMINAL
Definition icons.h:1951
#define ICON_MD_FLAG
Definition icons.h:784
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_HORIZONTAL_RULE
Definition icons.h:960
#define ICON_MD_CREATE_NEW_FOLDER
Definition icons.h:483
#define ICON_MD_DASHBOARD
Definition icons.h:517
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_ZOOM_OUT_MAP
Definition icons.h:2197
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_BACKUP
Definition icons.h:231
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_PUSH_PIN
Definition icons.h:1529
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_ANALYTICS
Definition icons.h:154
#define ICON_MD_HELP
Definition icons.h:933
#define ICON_MD_RESTART_ALT
Definition icons.h:1602
#define ICON_MD_CHECK_BOX_OUTLINE_BLANK
Definition icons.h:399
#define ICON_MD_VIEW_SIDEBAR
Definition icons.h:2095
#define ICON_MD_BOOKMARK_REMOVE
Definition icons.h:290
#define ICON_MD_UNDO
Definition icons.h:2039
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define ICON_MD_HISTORY
Definition icons.h:946
#define ICON_MD_DELETE_SWEEP
Definition icons.h:533
constexpr const char * kZScreamSave
constexpr const char * kSaveScope
bool RunShortcutAction(const ShortcutManager *manager, const std::string &action)
const char * ShortcutOrNull(const std::string &label)
std::vector< RecentFileMenuEntry > BuildRecentFileMenuEntries(const std::vector< std::string > &recent_paths, size_t max_entries, const RecentFileExistsFn &exists)
std::string ShortcutLabelForAction(const ShortcutManager *shortcut_manager, absl::string_view action)
absl::Status ApplyBpsPatch(const std::vector< uint8_t > &source, const std::vector< uint8_t > &patch, std::vector< uint8_t > &output)
Definition bps.cc:75
absl::Status CreateBpsPatch(const std::vector< uint8_t > &source, const std::vector< uint8_t > &target, std::vector< uint8_t > &patch)
Definition bps.cc:228
FileDialogOptions MakeRomFileDialogOptions(bool include_all_files)
Definition file_util.cc:87
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
SDL2/SDL3 compatibility layer.
std::function< void()> callback
Metadata for a dockable editor window (formerly PanelInfo)
std::vector< FileDialogFilter > filters
Definition file_util.h:17