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