yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ui_coordinator.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <filesystem>
6#include <functional>
7#include <memory>
8#include <optional>
9#include <string>
10#include <vector>
11
12#include "absl/strings/match.h"
13#include "absl/strings/str_format.h"
14
15#ifdef __EMSCRIPTEN__
16#include <emscripten.h>
17#endif
18#include "app/application.h"
20#include "app/editor/editor.h"
35#include "app/gui/core/icons.h"
36#include "app/gui/core/input.h"
38#include "app/gui/core/style.h"
43#include "core/project.h"
44#include "imgui/imgui.h"
45#include "util/file_util.h"
46#include "util/platform_paths.h"
47
48namespace yaze {
49namespace editor {
50
52 EditorManager* editor_manager, RomFileManager& rom_manager,
53 ProjectManager& project_manager, EditorRegistry& editor_registry,
54 WorkspaceWindowManager& window_manager,
55 SessionCoordinator& session_coordinator, WindowDelegate& window_delegate,
56 ToastManager& toast_manager, PopupManager& popup_manager,
57 ShortcutManager& shortcut_manager)
58 : editor_manager_(editor_manager),
59 rom_manager_(rom_manager),
60 project_manager_(project_manager),
61 editor_registry_(editor_registry),
62 window_manager_(window_manager),
63 session_coordinator_(session_coordinator),
64 window_delegate_(window_delegate),
65 toast_manager_(toast_manager),
66 popup_manager_(popup_manager),
67 shortcut_manager_(shortcut_manager) {
68 // Initialize welcome screen with proper callbacks
69 welcome_screen_ = std::make_unique<WelcomeScreen>();
70
71 // Bind persistent prefs so animation tweaks survive restart.
72 if (editor_manager_) {
73 welcome_screen_->SetUserSettings(&editor_manager_->user_settings());
74 }
75
76 // Wire welcome screen callbacks to EditorManager
77 welcome_screen_->SetOpenRomCallback([this]() {
78#ifdef __EMSCRIPTEN__
79 // In web builds, trigger the file input element directly
80 // The file input handler in app.js will handle the file selection
81 // and call LoadRomFromWeb, which will update the ROM
82 EM_ASM({
83 var romInput = document.getElementById('rom-input');
84 if (romInput) {
85 romInput.click();
86 }
87 });
88 // Don't hide welcome screen yet - it will be hidden when ROM loads
89 // (DrawWelcomeScreen auto-transitions to Dashboard on ROM load)
90#else
91 if (editor_manager_) {
92 auto status = editor_manager_->LoadRom();
93 if (!status.ok()) {
95 absl::StrFormat("Failed to load ROM: %s", status.message()),
97 }
98#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
99 else {
100 // Transition to Dashboard on successful ROM load
102 }
103#endif
104 }
105#endif
106 });
107
108 welcome_screen_->SetNewProjectCallback(
109 [this]() { new_project_dialog_.Open("Vanilla ROM Hack"); });
110
112 [this](const std::string& template_name, const std::string& rom_path,
113 const std::string& project_name) -> absl::Status {
114 if (!editor_manager_) {
115 return absl::FailedPreconditionError(
116 "Editor manager is not available");
117 }
119 template_name, rom_path, project_name);
120 if (!status.ok()) {
122 absl::StrFormat("Failed to create project: %s", status.message()),
124 return status;
125 }
126 toast_manager_.Show(absl::StrFormat("Created project \"%s\" from %s",
127 project_name, template_name),
130 return absl::OkStatus();
131 });
132
133 welcome_screen_->SetOpenProjectCallback([this](const std::string& filepath) {
134 if (editor_manager_) {
135 auto status = editor_manager_->OpenRomOrProject(filepath);
136 if (!status.ok()) {
138 absl::StrFormat("Failed to open project: %s", status.message()),
140 } else {
141 // Transition to Dashboard on successful project open
143 }
144 }
145 });
146
147 welcome_screen_->SetOpenProjectManagementCallback([this]() {
148 if (editor_manager_) {
151 }
152 });
153
154 welcome_screen_->SetComingFromZScreamCallback([this]() {
156 if (editor_manager_) {
159 }
160 });
161
162 welcome_screen_->SetOpenPrototypeResearchCallback([this]() {
163 if (!editor_manager_) {
164 return;
165 }
167 window_manager_.OpenWindow("graphics.prototype_viewer");
170 });
171
172 welcome_screen_->SetOpenAssemblyEditorNoRomCallback([this]() {
173 if (!editor_manager_) {
174 return;
175 }
177 window_manager_.OpenWindow("assembly.code_editor");
180 });
181}
182
196
198 if (dashboard_behavior_override_ == mode) {
199 return;
200 }
202 if (mode == StartupVisibility::kShow) {
203 // Only transition to dashboard if we're not in welcome
206 }
207 } else if (mode == StartupVisibility::kHide) {
208 // If hiding dashboard, transition to editor state
211 }
212 }
213}
214
216 if (!ImGui::GetCurrentContext()) {
217 return;
218 }
219
220 const ImGuiViewport* viewport = ImGui::GetMainViewport();
221 if (!viewport) {
222 return;
223 }
224
225 ImDrawList* bg_draw_list =
226 ImGui::GetBackgroundDrawList(const_cast<ImGuiViewport*>(viewport));
227
228 auto& theme_manager = gui::ThemeManager::Get();
229 auto current_theme = theme_manager.GetCurrentTheme();
230 auto& bg_renderer = gui::BackgroundRenderer::Get();
231
232 // Draw grid covering the entire main viewport
233 ImVec2 grid_pos = viewport->WorkPos;
234 ImVec2 grid_size = viewport->WorkSize;
235 bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size,
236 current_theme.primary);
237}
238
240 // Note: Theme styling is applied by ThemeManager, not here
241 // This is called from EditorManager::Update() - don't call menu bar stuff
242 // here
243
244 // Draw UI windows and dialogs
245 // Session dialogs are drawn by SessionCoordinator separately to avoid
246 // duplication
247 DrawCommandPalette(); // Ctrl+Shift+P (+ Window Finder seeds window:)
248 DrawPanelFinder(); // Legacy modal (kept for direct flag use)
249 DrawShortcutsBrowser(); // Help > Keyboard Shortcuts
250 DrawGlobalSearch(); // Ctrl+Shift+K
251 DrawWorkspacePresetDialogs(); // Save/Load workspace dialogs
252 DrawLayoutPresets(); // Layout preset dialogs
253 DrawWelcomeScreen(); // Welcome screen
254 DrawProjectHelp(); // Project help
255 DrawWindowManagementUI(); // Window management
256
257#ifdef YAZE_BUILD_AGENT_UI
258 if (show_ai_agent_) {
259 if (editor_manager_) {
260 editor_manager_->ShowAIAgent();
261 }
262 show_ai_agent_ = false;
263 }
264
265 if (show_chat_history_) {
266 if (editor_manager_) {
267 editor_manager_->ShowChatHistory();
268 }
269 show_chat_history_ = false;
270 }
271
273 if (editor_manager_) {
274 if (auto* right_panel = editor_manager_->right_drawer_manager()) {
275 right_panel->OpenDrawer(RightDrawerManager::DrawerType::kProposals);
276 }
277 }
278 show_proposal_drawer_ = false;
279 }
280#endif
281
282 // Draw popups and toasts
285}
286
288 const ImGuiViewport* viewport = ImGui::GetMainViewport();
289 if (!viewport) {
290 return false;
291 }
292 const float width = viewport->WorkSize.x;
293#if defined(__APPLE__) && TARGET_OS_IOS == 1
294 // Use hysteresis to avoid layout thrash while iPad windows are being
295 // interactively resized around the compact breakpoint.
296 static bool compact_mode = false;
297 constexpr float kEnterCompactWidth = 900.0f;
298 constexpr float kExitCompactWidth = 940.0f;
299 compact_mode =
300 compact_mode ? (width < kExitCompactWidth) : (width < kEnterCompactWidth);
301 return compact_mode;
302#else
303 return width < 900.0f;
304#endif
305}
306
307// =============================================================================
308// Menu Bar Helpers
309// =============================================================================
310
311bool UICoordinator::DrawMenuBarIconButton(const char* icon, const char* tooltip,
312 bool is_active) {
313 // Consistent button styling: transparent background, themed text
314 gui::StyleColorGuard btn_guard(
315 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
316 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
317 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
318 {ImGuiCol_Text,
320
321 bool clicked = ImGui::SmallButton(icon);
322
323 if (tooltip && ImGui::IsItemHovered()) {
324 ImGui::SetTooltip("%s", tooltip);
325 }
326
327 return clicked;
328}
329
331 // SmallButton width = text width + frame padding * 2
332 const float frame_padding = ImGui::GetStyle().FramePadding.x;
333 // Use a standard icon width (Material Design icons are uniform)
334 const float icon_width = ImGui::CalcTextSize(ICON_MD_SETTINGS).x;
335 return icon_width + frame_padding * 2.0f;
336}
337
339 // Right-aligned status cluster: dirty indicator, session, bell, sidebar.
340 // The sidebar toggle uses screen coordinates so it stays fixed when the
341 // dockspace resizes due to sidebar open/close.
342 //
343 // Layout: [●][📄▾][🔔] [sidebar][⬆]
344 // ^^^ shifts with dockspace ^^^ ^^^ fixed screen position ^^^
345
346 auto* current_rom = editor_manager_->GetCurrentRom();
347
348 const float item_spacing = 6.0f;
349 const float padding = 8.0f;
350
351 auto CalcSmallButtonWidth = [](const char* label) -> float {
352 // SmallButton width = text width + frame padding * 2
353 const float frame_padding = ImGui::GetStyle().FramePadding.x;
354 const float text_w = ImGui::CalcTextSize(label).x;
355 return text_w + frame_padding * 2.0f;
356 };
357
358 // Get TRUE viewport dimensions (not affected by dockspace resize)
359 const ImGuiViewport* viewport = ImGui::GetMainViewport();
360 const float true_viewport_right = viewport->WorkPos.x + viewport->WorkSize.x;
361
362 const bool has_panel_toggles =
364 float panel_buttons_width = 0.0f;
365 if (has_panel_toggles) {
366 panel_buttons_width = RightDrawerManager::GetSidebarToggleWidth();
367 }
368
369 // Reserve only the real button footprint so compact icon toggles do not
370 // leave a dead gap before the right edge cluster.
371 float panel_region_width = panel_buttons_width;
372#ifdef __EMSCRIPTEN__
373 // WASM hide menu bar toggle (drawn inline after panel buttons).
374 panel_region_width +=
375 CalcSmallButtonWidth(ICON_MD_EXPAND_LESS) + item_spacing;
376#endif
377
378 // Calculate screen X position for panel toggles (fixed at viewport right edge)
379 float panel_screen_x = true_viewport_right - panel_region_width;
383 }
384
385 // Available space for status cluster (dirty, session, bell) ends where the
386 // right-sidebar toggle begins.
387 const float window_width = ImGui::GetWindowWidth();
388 const float window_screen_x = ImGui::GetWindowPos().x;
389 const float menu_items_end = ImGui::GetCursorPosX() + 16.0f;
390
391 // Convert panel screen X to window-local coordinates for space calculation
392 float panel_local_x = panel_screen_x - window_screen_x;
393 float region_end =
394 std::min(window_width - padding, panel_local_x - item_spacing);
395
396 // Progressive show/hide when space is tight
397 bool has_dirty_rom =
398 current_rom && current_rom->is_loaded() && current_rom->dirty();
399 bool has_multiple_sessions = session_coordinator_.HasMultipleSessions();
400
401 float dirty_width =
402 ImGui::CalcTextSize(ICON_MD_FIBER_MANUAL_RECORD).x + item_spacing;
403 const float session_width = CalcSmallButtonWidth(ICON_MD_LAYERS);
404
405 const float available_width = region_end - menu_items_end - padding;
406
407 // Minimum required width: just the bell (always visible)
408 float required_width = CalcSmallButtonWidth(ICON_MD_NOTIFICATIONS);
409
410 // Priority (highest to lowest): Bell > Dirty > Session
411
412 // Try to fit session button (medium priority)
413 bool show_session =
414 has_multiple_sessions &&
415 (required_width + session_width + item_spacing) <= available_width;
416 if (show_session) {
417 required_width += session_width + item_spacing;
418 }
419
420 // Try to fit dirty indicator (high priority - only hide if extremely tight)
421 bool show_dirty =
422 has_dirty_rom && (required_width + dirty_width) <= available_width;
423 if (show_dirty) {
424 required_width += dirty_width;
425 }
426
427 // Calculate start position (right-align within available space)
428 float start_pos = std::max(menu_items_end, region_end - required_width);
429
430 // =========================================================================
431 // DRAW STATUS CLUSTER (shifts with dockspace)
432 // =========================================================================
433 ImGui::SameLine(start_pos);
434 gui::StyleVarGuard item_spacing_guard(ImGuiStyleVar_ItemSpacing,
435 ImVec2(item_spacing, 0.0f));
436
437 // 1. Dirty badge - warning color dot
438 if (show_dirty) {
439 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
441 gui::ConvertColorToImVec4(theme.warning));
442 if (ImGui::IsItemHovered()) {
443 ImGui::SetTooltip(tr("Unsaved changes: %s"),
444 current_rom->short_name().c_str());
445 }
446 ImGui::SameLine();
447 }
448
449 // 2. Session button - layers icon
450 if (show_session) {
452 ImGui::SameLine();
453 }
454
455 // 3. Notification bell (pass visibility flags for enhanced tooltip)
456 DrawNotificationBell(show_dirty, has_dirty_rom, show_session,
457 has_multiple_sessions);
458
459 // =========================================================================
460 // DRAW RIGHT-SIDEBAR TOGGLE (fixed screen position)
461 // =========================================================================
462 if (has_panel_toggles) {
463 float menu_bar_y = ImGui::GetCursorScreenPos().y;
464 ImGui::SetCursorScreenPos(ImVec2(panel_screen_x, menu_bar_y));
466 }
467
468#ifdef __EMSCRIPTEN__
469 // WASM toggle button - also at fixed position
470 ImGui::SameLine();
472 "Hide menu bar (Alt to restore)")) {
473 show_menu_bar_ = false;
474 }
475#endif
476}
477
479 // Only draw when menu bar is hidden (primarily for WASM builds)
480 if (show_menu_bar_) {
481 return;
482 }
483
484 // Small floating button in top-left corner to restore menu bar
485 ImGuiWindowFlags flags =
486 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
487 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
488 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize |
489 ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings;
490
491 ImGui::SetNextWindowPos(ImVec2(8, 8));
492 ImGui::SetNextWindowBgAlpha(0.7f);
493
494 if (ImGui::Begin("##MenuBarRestore", nullptr, flags)) {
495 gui::StyleColorGuard btn_guard(
496 {{ImGuiCol_Button, gui::GetSurfaceContainerVec4()},
497 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
498 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
499 {ImGuiCol_Text, gui::GetPrimaryVec4()}});
500
501 if (ImGui::Button(ICON_MD_FULLSCREEN_EXIT, ImVec2(32, 32))) {
502 show_menu_bar_ = true;
503 }
504
505 if (ImGui::IsItemHovered()) {
506 ImGui::SetTooltip(tr("Show menu bar (Alt)"));
507 }
508 }
509 ImGui::End();
510
511 // Also check for Alt key to restore menu bar
512 if (ImGui::IsKeyPressed(ImGuiKey_LeftAlt) ||
513 ImGui::IsKeyPressed(ImGuiKey_RightAlt)) {
514 show_menu_bar_ = true;
515 }
516}
517
518void UICoordinator::DrawNotificationBell(bool show_dirty, bool has_dirty_rom,
519 bool show_session,
520 bool has_multiple_sessions) {
521 size_t unread = toast_manager_.GetUnreadCount();
522 auto* current_rom = editor_manager_->GetCurrentRom();
523 auto* right_panel = editor_manager_->right_drawer_manager();
524
525 bool is_active =
526 right_panel && right_panel->IsDrawerActive(
528
530 unread > 0 || is_active)) {
531 if (right_panel) {
532 right_panel->ToggleDrawer(RightDrawerManager::DrawerType::kNotifications);
534 }
535 }
536
537 // Enhanced tooltip showing notifications + hidden status items
538 if (ImGui::IsItemHovered()) {
539 ImGui::BeginTooltip();
540
541 // Notifications
542 if (unread > 0) {
543 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s %zu new notification%s",
544 ICON_MD_NOTIFICATIONS, unread, unread == 1 ? "" : "s");
545 } else {
546 gui::ColoredText(ICON_MD_NOTIFICATIONS " No new notifications",
548 }
549
550 ImGui::TextDisabled(tr("Click to open Notifications panel"));
551
552 // Show hidden status items if any
553 if (!show_dirty && has_dirty_rom) {
554 ImGui::Separator();
556 gui::ThemeManager::Get().GetCurrentTheme().warning),
557 ICON_MD_FIBER_MANUAL_RECORD " Unsaved changes: %s",
558 current_rom->short_name().c_str());
559 }
560
561 if (!show_session && has_multiple_sessions) {
562 if (!show_dirty && has_dirty_rom) {
563 // Already had a separator
564 } else {
565 ImGui::Separator();
566 }
568 ICON_MD_LAYERS " %zu sessions active",
570 }
571
572 ImGui::EndTooltip();
573 }
574}
575
577 auto* current_rom = editor_manager_->GetCurrentRom();
578
579 // Store button position for popup anchoring
580 ImVec2 button_min = ImGui::GetCursorScreenPos();
581
582 std::string tooltip =
583 current_rom && current_rom->is_loaded()
584 ? absl::StrFormat("%s\n%zu sessions open (Ctrl+Tab)",
585 current_rom->short_name().c_str(),
587 : absl::StrFormat("No ROM loaded\n%zu sessions open (Ctrl+Tab)",
589
590 if (DrawMenuBarIconButton(ICON_MD_LAYERS, tooltip.c_str(), false)) {
591 ImGui::OpenPopup("##SessionSwitcherPopup");
592 }
593
594 ImVec2 button_max = ImGui::GetItemRectMax();
595
596 // Anchor popup to right edge - position so right edge aligns with button
597 const float popup_width = 250.0f;
598 const float screen_width = ImGui::GetIO().DisplaySize.x;
599 const float popup_x =
600 std::min(button_min.x, screen_width - popup_width - 10.0f);
601
602 ImGui::SetNextWindowPos(ImVec2(popup_x, button_max.y + 2.0f),
603 ImGuiCond_Appearing);
604
605 // Session switcher popup
606 if (ImGui::BeginPopup("##SessionSwitcherPopup")) {
607 ImGui::Text(ICON_MD_LAYERS " Sessions");
608 ImGui::Separator();
609
610 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) {
612 continue;
613
614 auto* session =
616 if (!session)
617 continue;
618
619 Rom* rom = &session->rom;
620 ImGui::PushID(static_cast<int>(i));
621
622 bool is_current = (rom == current_rom);
623 std::optional<gui::StyleColorGuard> current_guard;
624 if (is_current) {
625 current_guard.emplace(ImGuiCol_Text, gui::GetPrimaryVec4());
626 }
627
628 std::string label =
629 rom->is_loaded()
630 ? absl::StrFormat("%s %s", ICON_MD_DESCRIPTION,
631 rom->short_name().c_str())
632 : absl::StrFormat("%s Session %zu", ICON_MD_DESCRIPTION, i + 1);
633
634 if (ImGui::Selectable(label.c_str(), is_current)) {
636 }
637
638 ImGui::PopID();
639 }
640
641 ImGui::EndPopup();
642 }
643}
644
645// ============================================================================
646// Session UI Delegation
647// ============================================================================
648// All session-related UI is now managed by SessionCoordinator to eliminate
649// duplication. UICoordinator methods delegate to SessionCoordinator.
650
654
658
660 if (visible) {
662 } else {
664 }
665}
666
667void UICoordinator::ShowCommandPalette(const char* initial_query) {
668 const size_t session_id = session_coordinator_.GetActiveSessionId();
670 RefreshCommandPalette(session_id);
671 } else {
672 InitializeCommandPalette(session_id);
673 }
674 if (initial_query != nullptr) {
675 std::snprintf(command_palette_query_, sizeof(command_palette_query_), "%s",
676 initial_query);
678 }
680}
681
683 // Consolidate window discovery into the command palette (`window:` prefix).
684 ShowCommandPalette("window: ");
685}
686
688 if (visible) {
690 return;
691 }
692 show_command_palette_ = false;
693}
694
695// Emulator visibility delegates to WorkspaceWindowManager (single source of truth)
697 size_t session_id = session_coordinator_.GetActiveSessionId();
698 auto emulator_windows =
699 window_manager_.GetWindowsInCategory(session_id, "Emulator");
700 for (const auto& window : emulator_windows) {
701 if (window.visibility_flag && *window.visibility_flag) {
702 return true;
703 }
704 }
705 return false;
706}
707
709 size_t session_id = session_coordinator_.GetActiveSessionId();
710 if (visible) {
711 auto default_windows =
713 for (const auto& window_id : default_windows) {
714 window_manager_.OpenWindow(session_id, window_id);
715 }
716 } else {
717 window_manager_.HideAllWindowsInCategory(session_id, "Emulator");
718 }
719}
720
721// Assembly editor visibility: same delegation pattern as the emulator. The
722// `show_asm_editor_` boolean was a parallel source of truth that could drift
723// when the user closed a single Assembly panel via its window close button;
724// reading through the category means the user's last interaction always wins.
726 size_t session_id = session_coordinator_.GetActiveSessionId();
727 auto windows = window_manager_.GetWindowsInCategory(session_id, "Assembly");
728 for (const auto& window : windows) {
729 if (window.visibility_flag && *window.visibility_flag) {
730 return true;
731 }
732 }
733 return false;
734}
735
737 size_t session_id = session_coordinator_.GetActiveSessionId();
738 if (visible) {
739 auto default_windows =
741 for (const auto& window_id : default_windows) {
742 window_manager_.OpenWindow(session_id, window_id);
743 }
744 } else {
745 window_manager_.HideAllWindowsInCategory(session_id, "Assembly");
746 }
747}
748
749// ============================================================================
750// Layout and Window Management UI
751// ============================================================================
752
754 // TODO: [EditorManagerRefactor] Implement full layout preset UI with
755 // save/load For now, this is accessed via Window menu items that call
756 // workspace_manager directly
757}
758
760 // ============================================================================
761 // CENTRALIZED WELCOME SCREEN LOGIC (using StartupSurface state)
762 // ============================================================================
763 // Uses ShouldShowWelcome() as single source of truth
764 // Auto-transitions to Dashboard on ROM load
765 // Activity Bar hidden when welcome is visible
766 // ============================================================================
767
768 if (!editor_manager_) {
769 LOG_ERROR("UICoordinator",
770 "EditorManager is null - cannot check ROM state");
771 return;
772 }
773
774 if (!welcome_screen_) {
775 LOG_ERROR("UICoordinator", "WelcomeScreen object is null - cannot render");
776 return;
777 }
778
779 // Check ROM state and update startup surface accordingly
780 auto* current_rom = editor_manager_->GetCurrentRom();
781 bool rom_is_loaded = current_rom && current_rom->is_loaded();
782
783 // Auto-transition: ROM loaded -> Dashboard or Editor
784 if (rom_is_loaded && current_startup_surface_ == StartupSurface::kWelcome) {
787 } else {
789 }
790 }
791
792 // Auto-transition: ROM unloaded -> Welcome (reset to welcome state)
793 if (!rom_is_loaded && current_startup_surface_ != StartupSurface::kWelcome &&
796 }
797
798 // Use centralized visibility check
799 if (!ShouldShowWelcome()) {
800 // Project creation can start from the menu or command palette while the
801 // welcome surface is hidden.
803 return;
804 }
805
806 // Provide context state for first-run guidance.
807 welcome_screen_->SetContextState(rom_is_loaded);
808
809 // Update recent projects before showing (cheap no-op when the
810 // RecentFilesManager generation counter hasn't changed).
811 welcome_screen_->RefreshRecentProjects();
812
813 // Pass layout offsets so welcome screen centers within dockspace region
814 // Note: Activity Bar is hidden when welcome is shown, so left_offset = 0
815 float left_offset =
817 float right_offset = editor_manager_->GetRightLayoutOffset();
818 welcome_screen_->SetLayoutOffsets(left_offset, right_offset);
819
820 // Show the welcome screen window
821 bool is_open = true;
822 welcome_screen_->Show(&is_open);
823
824 // Draw after the welcome window so the modal layers above it.
826
827 // If user closed it via X button, respect that and transition to appropriate state
828 if (!is_open) {
830 // Transition to Dashboard if ROM loaded, stay in Editor state otherwise
831 if (rom_is_loaded) {
833 }
834 }
835}
836
838 // TODO: [EditorManagerRefactor] Implement project help dialog
839 // Show context-sensitive help based on current editor and ROM state
840}
841
844 ImGui::Begin("Save Workspace Preset", &show_save_workspace_preset_,
845 ImGuiWindowFlags_AlwaysAutoResize);
846 static char preset_name[128] = "";
847 ImGui::InputText(tr("Name"), preset_name, IM_ARRAYSIZE(preset_name));
848 if (ImGui::Button(tr("Save"), gui::kDefaultModalSize)) {
849 if (strlen(preset_name) > 0) {
853 preset_name[0] = '\0';
854 }
855 }
856 ImGui::SameLine();
857 if (ImGui::Button(tr("Cancel"), gui::kDefaultModalSize)) {
859 preset_name[0] = '\0';
860 }
861 ImGui::End();
862 }
863
865 ImGui::Begin("Load Workspace Preset", &show_load_workspace_preset_,
866 ImGuiWindowFlags_AlwaysAutoResize);
867
868 // Lazy load workspace presets when UI is accessed
870
871 if (auto* workspace_manager = editor_manager_->workspace_manager()) {
872 for (const auto& name : workspace_manager->workspace_presets()) {
873 if (ImGui::Selectable(name.c_str())) {
877 }
878 }
879 if (workspace_manager->workspace_presets().empty())
880 ImGui::Text(tr("No presets found"));
881 }
882 ImGui::End();
883 }
884}
885
887 // TODO: [EditorManagerRefactor] Implement window management dialog
888 // Provide UI for toggling window visibility, managing docking, etc.
889}
890
892 // Draw all registered popups
894}
895
896void UICoordinator::ShowPopup(const std::string& popup_name) {
897 popup_manager_.Show(popup_name.c_str());
898}
899
900void UICoordinator::HidePopup(const std::string& popup_name) {
901 popup_manager_.Hide(popup_name.c_str());
902}
903
905 // Display Settings is now a popup managed by PopupManager
906 // Delegate directly to PopupManager instead of UICoordinator
908}
909
910// ============================================================================
911// Sidebar visibility delegates to WorkspaceWindowManager
912// ============================================================================
913
917
921
925
929
933
934// Material Design component helpers
935void UICoordinator::DrawMaterialButton(const std::string& text,
936 const std::string& icon,
937 const ImVec4& color,
938 std::function<void()> callback,
939 bool enabled) {
940 std::optional<gui::StyleColorGuard> disabled_guard;
941 if (!enabled) {
942 disabled_guard.emplace(std::initializer_list<gui::StyleColorGuard::Entry>{
943 {ImGuiCol_Button, gui::GetSurfaceContainerHighestVec4()},
944 {ImGuiCol_Text, gui::GetOnSurfaceVariantVec4()}});
945 }
946
947 std::string button_text =
948 absl::StrFormat("%s %s", icon.c_str(), text.c_str());
949 if (ImGui::Button(button_text.c_str())) {
950 if (enabled && callback) {
951 callback();
952 }
953 }
954}
955
956// Layout and positioning helpers
957void UICoordinator::CenterWindow(const std::string& window_name) {
958 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
959 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
960}
961
962void UICoordinator::PositionWindow(const std::string& window_name, float x,
963 float y) {
964 ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Appearing);
965}
966
967void UICoordinator::SetWindowSize(const std::string& window_name, float width,
968 float height) {
969 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_FirstUseEver);
970}
971
973 auto config_dir = util::PlatformPaths::GetConfigDirectory();
974 if (config_dir.ok()) {
975 std::filesystem::path history_file = *config_dir / "command_history.json";
976 command_palette_.SaveHistory(history_file.string());
977 }
978}
979
980void UICoordinator::ExecutePaletteCommand(const std::string& name,
981 std::function<void()> callback) {
982 if (!callback)
983 return;
984 // Close first: the callback may reopen the palette (e.g. Window Finder
985 // seeds "window: ") or refresh providers, which must not be undone here.
986 show_command_palette_ = false;
987 callback();
988 if (command_palette_.RecordUsage(name)) {
990 }
991}
992
995 return;
996
997 // Initialize command palette on first use
1000 }
1001
1002 using namespace ImGui;
1003 auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1004
1005 SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing,
1006 ImVec2(0.5f, 0.5f));
1007 SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1008
1009 bool show_palette = true;
1010 bool close_requested = false;
1011 // Deferred so the callback runs after End() (it may open other windows).
1012 std::string pending_name;
1013 std::function<void()> pending_callback;
1014
1015 if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(),
1016 &show_palette, ImGuiWindowFlags_NoCollapse)) {
1017 const bool appearing = IsWindowAppearing();
1018 if (appearing) {
1021 }
1022
1023 // Search input owns keyboard focus; list navigation is driven by the
1024 // keys below, not by ImGui nav focus.
1025 SetNextItemWidth(-100);
1026 if (appearing || command_palette_refocus_input_) {
1027 SetKeyboardFocusHere();
1029 }
1030 bool input_changed = InputTextWithHint(
1031 "##cmd_query",
1032 absl::StrFormat("%s Search… try room 4A map 1B msg #12 drawer: "
1033 "window: layout:",
1035 .c_str(),
1037 // CallbackHistory makes the input claim Up/Down so ImGui nav does not
1038 // steal focus; the no-op callback leaves the text alone and the keys
1039 // are read below for list navigation.
1040 ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_CallbackHistory,
1041 [](ImGuiInputTextCallbackData*) { return 0; });
1042
1043 SameLine();
1044 if (Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
1045 command_palette_query_[0] = '\0';
1046 input_changed = true;
1047 }
1048 if (input_changed) {
1051 }
1052
1053 Separator();
1054
1055 // One ranked list: an optional go-to row first, then scored commands.
1056 const std::string query(command_palette_query_);
1057 std::optional<CommandEntry> goto_row;
1058 if (auto go_to = ParseGotoQuery(query)) {
1059 goto_row =
1061 }
1062 if (query != command_palette_cached_query_ ||
1067 }
1068 const int goto_offset = goto_row ? 1 : 0;
1069 auto row_at = [&](int i) -> const CommandEntry& {
1070 if (goto_row && i == 0)
1071 return *goto_row;
1072 return command_palette_cached_matches_[i - goto_offset].entry;
1073 };
1074
1075 // Keyboard navigation (mirrors the Window Finder).
1076 const int row_count =
1077 goto_offset + static_cast<int>(command_palette_cached_matches_.size());
1078 const bool palette_focused =
1079 IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
1080 if (palette_focused && row_count > 0) {
1081 constexpr int kPageStep = 10;
1083 if (IsKeyPressed(ImGuiKey_DownArrow))
1084 idx++;
1085 if (IsKeyPressed(ImGuiKey_UpArrow))
1086 idx--;
1087 if (IsKeyPressed(ImGuiKey_PageDown))
1088 idx += kPageStep;
1089 if (IsKeyPressed(ImGuiKey_PageUp))
1090 idx -= kPageStep;
1091 if (IsKeyPressed(ImGuiKey_Home, false))
1092 idx = 0;
1093 if (IsKeyPressed(ImGuiKey_End, false))
1094 idx = row_count - 1;
1095 idx = std::clamp(idx, 0, row_count - 1);
1096 if (idx != command_palette_selected_idx_) {
1099 }
1100 }
1102 row_count > 0
1103 ? std::clamp(command_palette_selected_idx_, 0, row_count - 1)
1104 : 0;
1105 if (palette_focused && (IsKeyPressed(ImGuiKey_Enter, false) ||
1106 IsKeyPressed(ImGuiKey_KeypadEnter, false))) {
1107 if (row_count > 0) {
1108 const CommandEntry& selected = row_at(command_palette_selected_idx_);
1109 if (selected.enabled && selected.callback) {
1110 pending_name = selected.name;
1111 pending_callback = selected.callback;
1112 } else {
1113 if (!selected.note.empty())
1115 // Enter deactivated the input; keep typing possible.
1117 }
1118 }
1119 }
1120 if (palette_focused && IsKeyPressed(ImGuiKey_Escape, false)) {
1121 close_requested = true;
1122 }
1123
1124 // Display results with categories
1125 if (gui::BeginThemedTabBar("CommandCategories")) {
1126 if (BeginTabItem(
1127 absl::StrFormat("%s All Commands", ICON_MD_LIST).c_str())) {
1129 "CommandPaletteTable", 3,
1130 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1131 ImGuiTableFlags_SizingStretchProp,
1132 ImVec2(0, -30))) {
1133 TableSetupScrollFreeze(0, 1);
1134 TableSetupColumn("Command", ImGuiTableColumnFlags_WidthStretch,
1135 0.55f);
1136 TableSetupColumn("Category", ImGuiTableColumnFlags_WidthStretch,
1137 0.2f);
1138 TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch,
1139 0.25f);
1140 TableHeadersRow();
1141
1142 for (int i = 0; i < row_count; ++i) {
1143 const CommandEntry& cmd = row_at(i);
1144
1145 TableNextRow();
1146 TableNextColumn();
1147
1148 PushID(i);
1149 const bool is_selected = (i == command_palette_selected_idx_);
1150 const ImGuiSelectableFlags flags =
1151 ImGuiSelectableFlags_SpanAllColumns |
1152 (cmd.enabled ? ImGuiSelectableFlags_None
1153 : ImGuiSelectableFlags_Disabled);
1154 if (Selectable(cmd.name.c_str(), is_selected, flags)) {
1156 if (cmd.enabled && cmd.callback) {
1157 pending_name = cmd.name;
1158 pending_callback = cmd.callback;
1159 }
1160 }
1161 if (is_selected && command_palette_scroll_to_selected_) {
1162 SetScrollHereY();
1164 }
1165 if (!cmd.note.empty() || !cmd.description.empty()) {
1166 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1167 SetTooltip("%s", cmd.note.empty() ? cmd.description.c_str()
1168 : cmd.note.c_str());
1169 }
1170 }
1171 PopID();
1172
1173 TableNextColumn();
1174 gui::ColoredText(cmd.category.c_str(),
1175 gui::ConvertColorToImVec4(theme.text_secondary));
1176
1177 TableNextColumn();
1178 if (!cmd.enabled && !cmd.note.empty()) {
1179 gui::ColoredText(cmd.note.c_str(),
1180 gui::ConvertColorToImVec4(theme.text_disabled));
1181 } else {
1182 gui::ColoredText(cmd.shortcut.c_str(),
1183 gui::ConvertColorToImVec4(theme.text_secondary));
1184 }
1185 }
1186
1188 }
1189 EndTabItem();
1190 }
1191
1192 if (BeginTabItem(absl::StrFormat("%s Recent", ICON_MD_HISTORY).c_str())) {
1193 auto recent = command_palette_.GetRecentCommands(10);
1194 if (recent.empty()) {
1195 Text(tr("No recent commands yet."));
1196 } else {
1197 for (const auto& entry : recent) {
1198 if (Selectable(entry.name.c_str()) && entry.callback) {
1199 pending_name = entry.name;
1200 pending_callback = entry.callback;
1201 }
1202 }
1203 }
1204 EndTabItem();
1205 }
1206
1207 if (BeginTabItem(absl::StrFormat("%s Frequent", ICON_MD_STAR).c_str())) {
1208 auto frequent = command_palette_.GetFrequentCommands(10);
1209 if (frequent.empty()) {
1210 Text(tr("No frequently used commands yet."));
1211 } else {
1212 for (const auto& entry : frequent) {
1213 if (Selectable(absl::StrFormat("%s (%d uses)", entry.name,
1214 entry.usage_count)
1215 .c_str()) &&
1216 entry.callback) {
1217 pending_name = entry.name;
1218 pending_callback = entry.callback;
1219 }
1220 }
1221 }
1222 EndTabItem();
1223 }
1224
1226 }
1227
1228 // Status bar with tips
1229 Separator();
1230 Text(tr("%s %d commands | Go to: room/map/msg <hex|#dec> | drawer: "
1231 "window: layout:"),
1232 ICON_MD_INFO, row_count);
1233 SameLine();
1234 gui::ColoredText("| ↑↓ PgUp/PgDn Home/End | Enter=Run | Esc=Close",
1235 gui::ConvertColorToImVec4(theme.text_disabled));
1236 }
1237 End();
1238
1239 if (!show_palette || close_requested) {
1240 show_command_palette_ = false;
1241 }
1242 if (pending_callback) {
1243 ExecutePaletteCommand(pending_name, std::move(pending_callback));
1244 }
1245}
1246
1248 if (!show_panel_finder_)
1249 return;
1250
1251 using namespace ImGui;
1252 const size_t session_id = window_manager_.GetActiveSessionId();
1253
1254 // B6: Responsive modal sizing with dim overlay
1255 const ImGuiViewport* viewport = GetMainViewport();
1256 ImDrawList* bg_list = GetBackgroundDrawList();
1257 bg_list->AddRectFilled(viewport->WorkPos,
1258 ImVec2(viewport->WorkPos.x + viewport->WorkSize.x,
1259 viewport->WorkPos.y + viewport->WorkSize.y),
1260 IM_COL32(0, 0, 0, 100));
1261
1262 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing,
1263 ImVec2(0.5f, 0.3f));
1264 if (IsCompactLayout()) {
1265 SetNextWindowSize(
1266 ImVec2(viewport->WorkSize.x * 0.95f, viewport->WorkSize.y * 0.70f),
1267 ImGuiCond_Appearing);
1268 } else {
1269 SetNextWindowSize(ImVec2(600, 420), ImGuiCond_Appearing);
1270 }
1271
1272 const ImGuiWindowFlags finder_flags =
1273 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
1274 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking |
1275 ImGuiWindowFlags_NoSavedSettings;
1276
1277 bool show = true;
1278 if (Begin(ICON_MD_DASHBOARD " Window Finder", &show, finder_flags)) {
1279 // Auto-focus search on open
1280 if (IsWindowAppearing()) {
1281 SetKeyboardFocusHere();
1283 }
1284
1285 SetNextItemWidth(-1);
1286 bool input_changed = InputTextWithHint(
1287 "##panel_finder_query", ICON_MD_SEARCH " Find window...",
1289
1290 if (input_changed) {
1292 }
1293
1294 Separator();
1295
1296 // Build filtered + scored list
1297 struct WindowEntry {
1298 std::string card_id;
1299 std::string display_name;
1300 std::string icon;
1301 std::string category;
1302 bool visible;
1303 bool pinned;
1304 int score;
1305 };
1306 std::vector<WindowEntry> entries;
1307
1308 std::string query(panel_finder_query_);
1309
1310 // B2: Fuzzy scoring via CommandPalette::FuzzyScore
1311 for (const auto& card_id :
1313 const auto* desc =
1314 window_manager_.GetWindowDescriptor(session_id, card_id);
1315 if (!desc) {
1316 continue;
1317 }
1318 int score = 0;
1319 if (!query.empty()) {
1320 score = CommandPalette::FuzzyScore(desc->display_name, query) +
1321 CommandPalette::FuzzyScore(desc->category, query) / 2;
1322 if (score <= 0)
1323 continue;
1324 }
1325
1326 bool vis = desc->visibility_flag ? *desc->visibility_flag : false;
1327 bool pin = window_manager_.IsWindowPinned(session_id, card_id);
1328 entries.push_back({card_id, desc->display_name, desc->icon,
1329 desc->category, vis, pin, score});
1330 }
1331
1332 // B3: MRU + fuzzy sort
1333 if (query.empty()) {
1334 // Empty query: pinned first, then MRU order (higher time = more recent)
1335 std::sort(
1336 entries.begin(), entries.end(),
1337 [this](const WindowEntry& lhs, const WindowEntry& rhs) {
1338 if (lhs.pinned != rhs.pinned)
1339 return lhs.pinned > rhs.pinned;
1340 uint64_t lhs_t = window_manager_.GetWindowMRUTime(lhs.card_id);
1341 uint64_t rhs_t = window_manager_.GetWindowMRUTime(rhs.card_id);
1342 if (lhs_t != rhs_t)
1343 return lhs_t > rhs_t;
1344 return lhs.display_name < rhs.display_name;
1345 });
1346 } else {
1347 // With query: sort by score descending, pinned tiebreaker
1348 std::sort(entries.begin(), entries.end(),
1349 [](const WindowEntry& lhs, const WindowEntry& rhs) {
1350 if (lhs.score != rhs.score)
1351 return lhs.score > rhs.score;
1352 if (lhs.pinned != rhs.pinned)
1353 return lhs.pinned > rhs.pinned;
1354 return lhs.display_name < rhs.display_name;
1355 });
1356 }
1357
1358 // Keyboard navigation
1359 if (IsKeyPressed(ImGuiKey_DownArrow) &&
1360 panel_finder_selected_idx_ < static_cast<int>(entries.size()) - 1) {
1362 }
1363 if (IsKeyPressed(ImGuiKey_UpArrow) && panel_finder_selected_idx_ > 0) {
1365 }
1366 bool enter_pressed = IsKeyPressed(ImGuiKey_Enter);
1367
1368 // B5: Touch-aware item sizing
1369 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
1370 if (is_touch) {
1371 PushStyleVar(ImGuiStyleVar_ItemSpacing,
1372 ImVec2(GetStyle().ItemSpacing.x, 10.0f));
1373 }
1374
1375 // Draw panel list
1376 BeginChild("##PanelFinderList");
1377 for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
1378 const auto& entry = entries[i];
1379 bool is_selected = (i == panel_finder_selected_idx_);
1380
1381 // B4: Visibility indicator icon
1382 const char* vis_icon =
1384
1385 // Dim hidden panels
1386 std::optional<gui::StyleColorGuard> dim_guard;
1387 if (!entry.visible) {
1388 ImVec4 dimmed = GetStyleColorVec4(ImGuiCol_Text);
1389 dimmed.w *= 0.5f;
1390 dim_guard.emplace(ImGuiCol_Text, dimmed);
1391 }
1392
1393 std::string label =
1394 absl::StrFormat("%s %s %s", vis_icon, entry.icon.c_str(),
1395 entry.display_name.c_str());
1396
1397 // Touch: ensure selectable meets 44px minimum height
1398 float item_h =
1399 is_touch ? std::max(GetTextLineHeightWithSpacing(), 44.0f) : 0.0f;
1400
1401 PushID(entry.card_id.c_str());
1402 if (Selectable(label.c_str(), is_selected, ImGuiSelectableFlags_None,
1403 ImVec2(0, item_h))) {
1404 window_manager_.OpenWindow(session_id, entry.card_id);
1406 show_panel_finder_ = false;
1407 }
1408 // Show category badge on the same line
1409 SameLine(GetContentRegionAvail().x - 80);
1410 TextDisabled("%s", entry.category.c_str());
1411 if (entry.pinned) {
1412 SameLine();
1413 TextDisabled(ICON_MD_PUSH_PIN);
1414 }
1415 PopID();
1416
1417 // Enter to activate selected
1418 if (is_selected && enter_pressed) {
1419 window_manager_.OpenWindow(session_id, entry.card_id);
1421 show_panel_finder_ = false;
1422 }
1423
1424 // Scroll selected into view
1425 if (is_selected && (IsKeyPressed(ImGuiKey_DownArrow) ||
1426 IsKeyPressed(ImGuiKey_UpArrow))) {
1427 SetScrollHereY();
1428 }
1429 }
1430 EndChild();
1431
1432 if (is_touch) {
1433 PopStyleVar();
1434 }
1435 }
1436 End();
1437
1438 // Escape or close button
1439 if (!show || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
1440 show_panel_finder_ = false;
1441 panel_finder_query_[0] = '\0';
1442 }
1443}
1444
1447 return;
1448 }
1449
1450 using namespace ImGui;
1451 const ImGuiViewport* viewport = GetMainViewport();
1452 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing,
1453 ImVec2(0.5f, 0.5f));
1454 if (IsCompactLayout()) {
1455 SetNextWindowSize(
1456 ImVec2(viewport->WorkSize.x * 0.95f, viewport->WorkSize.y * 0.80f),
1457 ImGuiCond_Appearing);
1458 } else {
1459 SetNextWindowSize(ImVec2(720, 520), ImGuiCond_Appearing);
1460 }
1461
1462 bool show = true;
1463 if (Begin(absl::StrFormat("%s Keyboard Shortcuts", ICON_MD_KEYBOARD).c_str(),
1464 &show, ImGuiWindowFlags_NoCollapse)) {
1465 if (IsWindowAppearing()) {
1466 SetKeyboardFocusHere();
1467 }
1468
1469 SetNextItemWidth(-180.0f);
1470 InputTextWithHint(
1471 "##shortcuts_query",
1472 ICON_MD_SEARCH " Filter by name, keys, or group (File, Drawers, …)",
1474 SameLine();
1475 if (Button(ICON_MD_TUNE " Edit bindings")) {
1477 if (editor_manager_) {
1479 }
1480 }
1481
1482 Separator();
1483
1484 std::string query = shortcuts_browser_query_;
1485 std::transform(query.begin(), query.end(), query.begin(), ::tolower);
1486
1487 std::map<std::string, std::vector<const Shortcut*>> grouped;
1488 static const char* kGroupOrder[] = {"File", "Edit", "View", "Drawers",
1489 "Windows", "Tools", "Layout", "Editor",
1490 "Help", "Other"};
1491
1492 for (const auto& [name, shortcut] : shortcut_manager_.GetShortcuts()) {
1493 const std::string group = InferShortcutGroup(name);
1494 const std::string keys = PrintShortcut(shortcut.keys);
1495 if (!query.empty()) {
1496 std::string hay =
1497 absl::AsciiStrToLower(absl::StrCat(name, " ", keys, " ", group));
1498 if (hay.find(query) == std::string::npos) {
1499 continue;
1500 }
1501 }
1502 grouped[group].push_back(&shortcut);
1503 }
1504
1505 BeginChild("##ShortcutsBrowserList");
1506 for (const char* group_name : kGroupOrder) {
1507 auto it = grouped.find(group_name);
1508 if (it == grouped.end() || it->second.empty()) {
1509 continue;
1510 }
1511 if (!TreeNodeEx(group_name, ImGuiTreeNodeFlags_DefaultOpen)) {
1512 continue;
1513 }
1514 if (BeginTable(
1515 absl::StrFormat("##sc_%s", group_name).c_str(), 2,
1516 ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
1517 TableSetupColumn("Action", ImGuiTableColumnFlags_WidthStretch, 0.65f);
1518 TableSetupColumn("Keys", ImGuiTableColumnFlags_WidthStretch, 0.35f);
1519 for (const Shortcut* sc : it->second) {
1520 TableNextRow();
1521 TableNextColumn();
1522 if (Selectable(sc->name.c_str(), false,
1523 ImGuiSelectableFlags_SpanAllColumns)) {
1524 if (sc->callback) {
1525 sc->callback();
1527 }
1528 }
1529 TableNextColumn();
1530 const std::string keys = PrintShortcut(sc->keys);
1531 TextDisabled("%s", keys.empty() ? "—" : keys.c_str());
1532 }
1533 EndTable();
1534 }
1535 TreePop();
1536 }
1537 EndChild();
1538
1539 Separator();
1540 TextDisabled(
1541 "%s Tip: Command Palette (Ctrl+Shift+P) also lists these actions. "
1542 "Try drawer: / window: / layout:",
1543 ICON_MD_INFO);
1544 }
1545 End();
1546
1547 if (!show || IsKeyPressed(ImGuiKey_Escape)) {
1549 shortcuts_browser_query_[0] = '\0';
1550 }
1551}
1552
1556
1557 // Register panel commands
1559 std::make_unique<PanelCommandsProvider>(&window_manager_, session_id));
1561 std::make_unique<WorkflowCommandsProvider>(&window_manager_, session_id));
1562 if (editor_manager_) {
1563 command_palette_.RegisterProvider(std::make_unique<SidebarCommandsProvider>(
1564 &window_manager_, &editor_manager_->user_settings(), session_id));
1565
1566 if (auto* drawers = editor_manager_->right_drawer_manager()) {
1568 std::make_unique<DrawerCommandsProvider>(
1569 [drawers](int drawer_type) {
1570 drawers->ToggleDrawer(
1571 static_cast<RightDrawerManager::DrawerType>(drawer_type));
1572 },
1573 [drawers]() { drawers->CycleToNextDrawer(); },
1574 [drawers]() { drawers->CycleToPreviousDrawer(); }));
1575 }
1576 }
1577
1578 // Register editor switch commands
1579 command_palette_.RegisterProvider(std::make_unique<EditorCommandsProvider>(
1580 [this](const std::string& category) {
1581 auto type = EditorRegistry::GetEditorTypeFromCategory(category);
1582 if (type != EditorType::kSettings && editor_manager_) {
1584 }
1585 }));
1586
1587 // Register layout/profile commands (includes layout: prefixes)
1588 if (editor_manager_) {
1590 std::make_unique<LayoutCommandsProvider>([this](const std::string& id) {
1591 if (!editor_manager_) {
1592 return;
1593 }
1594 if (absl::StartsWith(id, "profile:")) {
1595 editor_manager_->ApplyLayoutProfile(id.substr(8));
1596 } else if (id == "session:capture") {
1598 } else if (id == "session:restore") {
1600 } else if (id == "session:clear") {
1602 } else {
1604 }
1605 }));
1606 }
1607
1608 // Register recent files commands
1610 std::make_unique<RecentFilesCommandsProvider>(
1611 [this](const std::string& filepath) {
1612 if (editor_manager_) {
1613 auto status = editor_manager_->OpenRomOrProject(filepath);
1614 if (!status.ok()) {
1616 absl::StrFormat("Failed to open: %s", status.message()),
1618 }
1619 }
1620 }));
1621
1622 // Dungeon / overworld navigation helpers (jump by id/label). Typed go-to
1623 // queries ("room 4A", "map #27", "msg 1C") are parsed in DrawCommandPalette.
1625 std::make_unique<DungeonRoomCommandsProvider>(session_id));
1627 std::make_unique<OverworldMapCommandsProvider>(session_id));
1628
1629 // ShortcutManager commands with live keybindings. Registered through the
1630 // palette so search, dedupe and usage history treat them like any entry.
1632 std::make_unique<ShortcutCommandsProvider>(&shortcut_manager_));
1633
1634 // Welcome-screen-scoped commands: per-entry pin/remove, undo, template
1635 // creation, and visibility toggles. Recent-entry iteration pulls from the
1636 // same model the welcome screen renders, so the palette and the cards stay
1637 // in sync without a separate refresh path. We rebuild these whenever
1638 // RefreshCommandPalette() is invoked after recents mutate.
1639 if (welcome_screen_) {
1640 WelcomeCommandsProvider::Callbacks welcome_callbacks;
1641 welcome_callbacks.model = &welcome_screen_->recent_projects();
1642 welcome_callbacks.template_names = {
1643 "Vanilla ROM Hack", "ZSCustomOverworld v3", "ZSCustomOverworld v2",
1644 "Randomizer Compatible"};
1645 welcome_callbacks.remove = [this](const std::string& path) {
1646 if (!welcome_screen_)
1647 return;
1648 welcome_screen_->recent_projects().RemoveRecent(path);
1649 welcome_screen_->RefreshRecentProjects(/*force=*/true);
1651 absl::StrFormat("Removed %s from recents",
1652 std::filesystem::path(path).filename().string()),
1654 };
1655 welcome_callbacks.toggle_pin = [this](const std::string& path) {
1656 if (!welcome_screen_)
1657 return;
1658 auto& model = welcome_screen_->recent_projects();
1659 bool currently_pinned = false;
1660 for (const auto& entry : model.entries()) {
1661 if (entry.filepath == path) {
1662 currently_pinned = entry.pinned;
1663 break;
1664 }
1665 }
1666 model.SetPinned(path, !currently_pinned);
1667 welcome_screen_->RefreshRecentProjects(/*force=*/true);
1668 };
1669 welcome_callbacks.undo_remove = [this]() {
1670 if (!welcome_screen_)
1671 return;
1672 if (!welcome_screen_->recent_projects().UndoLastRemoval()) {
1673 toast_manager_.Show("Nothing to undo — the last removal has expired.",
1675 return;
1676 }
1677 welcome_screen_->RefreshRecentProjects(/*force=*/true);
1678 };
1679 welcome_callbacks.clear_recents = [this]() {
1680 if (!welcome_screen_)
1681 return;
1682 welcome_screen_->recent_projects().ClearAll();
1683 welcome_screen_->RefreshRecentProjects(/*force=*/true);
1684 };
1685 welcome_callbacks.create_from_template =
1686 [this](const std::string& template_name) {
1687 new_project_dialog_.Open(template_name);
1688 };
1689 welcome_callbacks.dismiss_welcome = [this]() {
1693 }
1694 };
1695 welcome_callbacks.show_welcome = [this]() {
1698 };
1699 command_palette_.RegisterProvider(std::make_unique<WelcomeCommandsProvider>(
1700 std::move(welcome_callbacks)));
1701 }
1702
1703 // Usage history lives in the palette across Clear()/refresh; read the
1704 // file once. Every execution saves it (ExecutePaletteCommand).
1706 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1707 if (config_dir.ok()) {
1708 std::filesystem::path history_file = *config_dir / "command_history.json";
1709 command_palette_.LoadHistory(history_file.string());
1710 }
1712 }
1713
1715}
1716
1719
1723 {.id = "workflow.project.build",
1724 .group = "Build & Run",
1725 .label = "Build Project",
1726 .description = "Run the active project's configured build command",
1727 .shortcut = "",
1728 .priority = 5,
1729 .callback = [this]() {
1730 if (editor_manager_) {
1732 }
1733 }});
1735 {.id = "workflow.project.run",
1736 .group = "Build & Run",
1737 .label = "Run Project Output",
1738 .description = "Open the active project's configured run target in a "
1739 "test session",
1740 .shortcut = "",
1741 .priority = 10,
1742 .callback = [this]() {
1743 if (editor_manager_) {
1745 }
1746 }});
1747 }
1748
1749#ifdef YAZE_WITH_GRPC
1750 auto* emu_backend = Application::Instance().GetEmulatorBackend();
1751 if (emu_backend) {
1753 {.id = "workflow.mesen.connect",
1754 .group = "Live Debugging",
1755 .label = "Connect Mesen2",
1756 .description =
1757 "Auto-discover and connect to the active Mesen2 backend",
1758 .shortcut = "",
1759 .priority = 10,
1760 .callback = [this]() {
1761 auto* backend = Application::Instance().GetEmulatorBackend();
1762 if (backend) {
1763 toast_manager_.Show("Mesen2 connection attempt queued",
1765 }
1766 }});
1768 {.id = "workflow.mesen.step_over",
1769 .group = "Live Debugging",
1770 .label = "Mesen2 Step Over",
1771 .description = "Execute one instruction without entering subroutines",
1772 .shortcut = "F10",
1773 .priority = 20,
1774 .callback = [emu_backend]() { emu_backend->StepOver(); }});
1776 {.id = "workflow.mesen.step_out",
1777 .group = "Live Debugging",
1778 .label = "Mesen2 Step Out",
1779 .description = "Run until return from the current subroutine",
1780 .shortcut = "Shift+F11",
1781 .priority = 30,
1782 .callback = [emu_backend]() { emu_backend->StepOut(); }});
1784 {.id = "workflow.mesen.overlay_on",
1785 .group = "Live Debugging",
1786 .label = "Enable Collision Overlay",
1787 .description = "Show collision overlays in the active Mesen2 backend",
1788 .shortcut = "",
1789 .priority = 40,
1790 .callback = [emu_backend]() {
1791 emu_backend->SetCollisionOverlay(true);
1792 }});
1794 {.id = "workflow.mesen.overlay_off",
1795 .group = "Live Debugging",
1796 .label = "Disable Collision Overlay",
1797 .description = "Hide collision overlays in the active Mesen2 backend",
1798 .shortcut = "",
1799 .priority = 50,
1800 .callback = [emu_backend]() {
1801 emu_backend->SetCollisionOverlay(false);
1802 }});
1803 }
1804#endif
1805}
1806
1808 InitializeCommandPalette(session_id);
1809}
1810
1813 return;
1814
1815 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1816 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1817 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1818
1819 bool show_search = true;
1820 if (ImGui::Begin(
1821 absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(),
1822 &show_search, ImGuiWindowFlags_NoCollapse)) {
1823 // Enhanced search input with focus management
1824 ImGui::SetNextItemWidth(-100);
1825 if (ImGui::IsWindowAppearing()) {
1826 ImGui::SetKeyboardFocusHere();
1827 }
1828
1829 bool input_changed = ImGui::InputTextWithHint(
1830 "##global_query",
1831 absl::StrFormat("%s Search everything...", ICON_MD_SEARCH).c_str(),
1833
1834 ImGui::SameLine();
1835 if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
1836 global_search_query_[0] = '\0';
1837 input_changed = true;
1838 }
1839
1840 ImGui::Separator();
1841
1842 // Tabbed search results for better organization
1843 if (gui::BeginThemedTabBar("SearchResultTabs")) {
1844 // Recent Files Tab
1845 if (ImGui::BeginTabItem(
1846 absl::StrFormat("%s Recent Files", ICON_MD_HISTORY).c_str())) {
1848 auto recent_files = manager.GetRecentFiles();
1849
1850 if (ImGui::BeginTable("RecentFilesTable", 3,
1851 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1852 ImGuiTableFlags_SizingStretchProp)) {
1853 ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch,
1854 0.6f);
1855 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1856 80.0f);
1857 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1858 100.0f);
1859 ImGui::TableHeadersRow();
1860
1861 for (const auto& file : recent_files) {
1862 if (global_search_query_[0] != '\0' &&
1863 file.find(global_search_query_) == std::string::npos)
1864 continue;
1865
1866 ImGui::TableNextRow();
1867 ImGui::TableNextColumn();
1868 ImGui::Text("%s", util::GetFileName(file).c_str());
1869
1870 ImGui::TableNextColumn();
1871 std::string ext = util::GetFileExtension(file);
1872 if (ext == "sfc" || ext == "smc") {
1873 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), tr("%s ROM"),
1875 } else if (ext == "yaze") {
1876 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.8f, 1.0f),
1877 tr("%s Project"), ICON_MD_FOLDER);
1878 } else {
1879 ImGui::Text(tr("%s File"), ICON_MD_DESCRIPTION);
1880 }
1881
1882 ImGui::TableNextColumn();
1883 ImGui::PushID(file.c_str());
1884 if (ImGui::Button(tr("Open"))) {
1885 auto status = editor_manager_->OpenRomOrProject(file);
1886 if (!status.ok()) {
1888 absl::StrCat("Failed to open: ", status.message()),
1890 }
1892 }
1893 ImGui::PopID();
1894 }
1895
1896 ImGui::EndTable();
1897 }
1898 ImGui::EndTabItem();
1899 }
1900
1901 // Labels Tab (only if ROM is loaded)
1902 auto* current_rom = editor_manager_->GetCurrentRom();
1903 if (current_rom && current_rom->resource_label()) {
1904 if (ImGui::BeginTabItem(
1905 absl::StrFormat("%s Labels", ICON_MD_LABEL).c_str())) {
1906 auto& labels = current_rom->resource_label()->labels_;
1907
1908 if (ImGui::BeginTable("LabelsTable", 3,
1909 ImGuiTableFlags_ScrollY |
1910 ImGuiTableFlags_RowBg |
1911 ImGuiTableFlags_SizingStretchProp)) {
1912 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1913 100.0f);
1914 ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch,
1915 0.4f);
1916 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch,
1917 0.6f);
1918 ImGui::TableHeadersRow();
1919
1920 for (const auto& type_pair : labels) {
1921 for (const auto& kv : type_pair.second) {
1922 if (global_search_query_[0] != '\0' &&
1923 kv.first.find(global_search_query_) == std::string::npos &&
1924 kv.second.find(global_search_query_) == std::string::npos)
1925 continue;
1926
1927 ImGui::TableNextRow();
1928 ImGui::TableNextColumn();
1929 ImGui::Text("%s", type_pair.first.c_str());
1930
1931 ImGui::TableNextColumn();
1932 if (ImGui::Selectable(kv.first.c_str(), false,
1933 ImGuiSelectableFlags_SpanAllColumns)) {
1934 // Future: navigate to related editor/location
1935 }
1936
1937 ImGui::TableNextColumn();
1938 ImGui::TextDisabled("%s", kv.second.c_str());
1939 }
1940 }
1941
1942 ImGui::EndTable();
1943 }
1944 ImGui::EndTabItem();
1945 }
1946 }
1947
1948 // Sessions Tab
1950 if (ImGui::BeginTabItem(
1951 absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) {
1952 ImGui::Text(tr("Search and switch between active sessions:"));
1953
1954 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount();
1955 ++i) {
1956 std::string session_info =
1958 if (session_info == "[CLOSED SESSION]")
1959 continue;
1960
1961 if (global_search_query_[0] != '\0' &&
1962 session_info.find(global_search_query_) == std::string::npos)
1963 continue;
1964
1965 bool is_current =
1967 std::optional<gui::StyleColorGuard> current_guard;
1968 if (is_current) {
1969 current_guard.emplace(ImGuiCol_Text,
1970 ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
1971 }
1972
1973 if (ImGui::Selectable(absl::StrFormat("%s %s %s", ICON_MD_TAB,
1974 session_info.c_str(),
1975 is_current ? "(Current)" : "")
1976 .c_str())) {
1977 if (!is_current) {
1980 }
1981 }
1982 }
1983 ImGui::EndTabItem();
1984 }
1985 }
1986
1988 }
1989
1990 // Status bar
1991 ImGui::Separator();
1992 ImGui::Text(tr("%s Global search across all YAZE data"), ICON_MD_INFO);
1993 }
1994 ImGui::End();
1995
1996 // Update visibility state
1997 if (!show_search) {
1999 }
2000}
2001
2002// ============================================================================
2003// Startup Surface Management (Single Source of Truth)
2004// ============================================================================
2005
2007 // Automatic moves to the dashboard (after a ROM or project load, or when
2008 // the welcome screen closes) obey the same rule as
2009 // SetEditorSelectionVisible(true): no picker when it is suppressed or an
2010 // editor is already open. Ctrl+E uses ShowEditorSelection() instead.
2011 if (surface == StartupSurface::kDashboard && !ShouldShowDashboard()) {
2012 surface = StartupSurface::kEditor;
2013 }
2015 current_startup_surface_ = surface;
2016
2017 // Log state transitions for debugging
2018 const char* surface_names[] = {"Welcome", "Dashboard", "Editor"};
2019 LOG_INFO("UICoordinator", "Startup surface: %s -> %s",
2020 surface_names[static_cast<int>(old_surface)],
2021 surface_names[static_cast<int>(surface)]);
2022
2023 // Update dependent visibility flags
2024 switch (surface) {
2026 show_welcome_screen_ = true;
2027 show_editor_selection_ = false; // Dashboard hidden
2028 // Activity Bar will be hidden (checked via ShouldShowActivityBar)
2029 break;
2031 show_welcome_screen_ = false;
2032 show_editor_selection_ = true; // Dashboard shown
2033 break;
2035 show_welcome_screen_ = false;
2036 show_editor_selection_ = false; // Dashboard hidden
2037 break;
2038 }
2039}
2040
2042 // Respect CLI overrides
2044 return false;
2045 }
2047 return true;
2048 }
2049
2050 // Default: show welcome only when in welcome state and not manually closed
2053}
2054
2056 // Consulted by SetEditorSelectionVisible, the choke point every automatic
2057 // entry into the dashboard passes through.
2058 //
2059 // This used to also require current_startup_surface_ == kDashboard, and
2060 // nothing called it at all — so --startup_dashboard=hide did nothing while
2061 // its sibling --startup_welcome worked, because ShouldShowWelcome() IS
2062 // consulted. The surface test is dropped on purpose: by the time a ROM
2063 // finishes loading the surface has already advanced past kDashboard, so
2064 // keying on it would suppress the very chooser the flag exists to govern.
2066 return false;
2067 }
2069 return true;
2070 }
2071 // Auto: the picker is for choosing a first editor. Do not cover one that
2072 // is already open (for example, File > Open while the Dungeon editor is up).
2074}
2075
2077 // Sidebar would consume the entire screen on compact (iPhone portrait)
2078 if (IsCompactLayout()) {
2079 return false;
2080 }
2081
2082 // Activity Bar hidden on cold start (welcome screen)
2083 // Only show after ROM is loaded
2085 return false;
2086 }
2087
2088 // Check if ROM is actually loaded
2089 if (editor_manager_) {
2090 auto* current_rom = editor_manager_->GetCurrentRom();
2091 if (!current_rom || !current_rom->is_loaded()) {
2092 return false;
2093 }
2094 }
2095
2096 return true;
2097}
2098
2099} // namespace editor
2100} // namespace yaze
static Application & Instance()
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
project::ResourceLabelManager * resource_label()
Definition rom.h:225
auto short_name() const
Definition rom.h:209
bool is_loaded() const
Definition rom.h:187
void SaveHistory(const std::string &filepath)
Save command usage history to disk.
void Clear()
Clear all commands (and forget every registered provider). Usage history is kept so refreshing provid...
void LoadHistory(const std::string &filepath)
Load command usage history from disk.
void RegisterProvider(std::unique_ptr< CommandProvider > provider)
std::vector< CommandEntry > GetRecentCommands(int limit=10)
bool RecordUsage(const std::string &name)
std::vector< CommandMatch > Search(const std::string &query, int64_t now_ms) const
std::vector< CommandEntry > GetFrequentCommands(int limit=10)
static int FuzzyScore(const std::string &text, const std::string &query)
Legacy name for ScoreText (kept for the Window Finder).
The EditorManager controls the main editor window and manages the various editor classes.
void SaveWorkspacePreset(const std::string &name)
void SwitchToEditor(EditorType editor_type, bool force_visible=false, bool from_dialog=false) override
Rom * GetCurrentRom() const override
WorkspaceManager * workspace_manager()
void LoadWorkspacePreset(const std::string &name)
void ShowProjectManagement()
Injects dependencies into all editors within an EditorSet.
absl::Status CreateNewProjectFromRom(const std::string &template_name, const std::string &rom_path, const std::string &project_name, const std::string &project_path=std::string())
RightDrawerManager * right_drawer_manager()
void ApplyLayoutPreset(const std::string &preset_name)
void RestoreTemporaryLayoutSnapshot(bool clear_after_restore=false)
bool ApplyLayoutProfile(const std::string &profile_id)
absl::Status LoadRom()
Load a ROM file into a new or existing session.
FeatureHintManager * feature_hint_manager()
project::YazeProject * GetCurrentProject()
absl::Status OpenRomOrProject(const std::string &filename)
Manages editor types, categories, and lifecycle.
static EditorType GetEditorTypeFromCategory(const std::string &category)
void Dismiss(std::string_view id)
Mark dismissed without showing (e.g. user opened the guide manually).
static std::vector< std::string > GetDefaultWindows(EditorType type)
void Open(const std::string &initial_template="")
void SetCreateCallback(CreateCallback cb)
void Show(const char *name)
void Hide(const char *name)
Handles all project file operations with ROM-first workflow.
bool IsDrawerActive(DrawerType type) const
Check if a specific drawer is active.
bool IsDrawerExpanded() const
Check if any drawer is currently expanded (or animating closed)
Handles all ROM file I/O operations.
High-level orchestrator for multi-session UI.
void * GetSession(size_t index) const
size_t GetActiveSessionIndex() const
Compact zero-based UI position in sessions_.
std::string GetSessionDisplayName(size_t index) const
bool IsSessionClosed(size_t index) const
size_t GetActiveSessionId() const
Stable workspace identity that is never reused while this coordinator lives.
const std::unordered_map< std::string, Shortcut > & GetShortcuts() const
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
ShortcutManager & shortcut_manager_
void DrawMaterialButton(const std::string &text, const std::string &icon, const ImVec4 &color, std::function< void()> callback, bool enabled=true)
void SetPanelSidebarVisible(bool visible)
void SetSessionSwitcherVisible(bool visible)
void SetGlobalSearchVisible(bool visible)
void HidePopup(const std::string &popup_name)
void SetStartupSurface(StartupSurface surface)
SessionCoordinator & session_coordinator_
void InitializeCommandPalette(size_t session_id)
Initialize command palette with all discoverable commands.
void RefreshCommandPalette(size_t session_id)
Refresh command palette commands (call after session switch)
void SetWelcomeScreenManuallyClosed(bool closed)
void SetEmulatorVisible(bool visible)
void ShowCommandPalette(const char *initial_query=nullptr)
void DrawNotificationBell(bool show_dirty, bool has_dirty_rom, bool show_session, bool has_multiple_sessions)
void SetAsmEditorVisible(bool visible)
void SetWelcomeScreenVisible(bool visible)
WindowDelegate & window_delegate_
bool DrawMenuBarIconButton(const char *icon, const char *tooltip, bool is_active=false)
std::string command_palette_cached_query_
void ShowPopup(const std::string &popup_name)
std::vector< CommandMatch > command_palette_cached_matches_
StartupVisibility welcome_behavior_override_
void ExecutePaletteCommand(const std::string &name, std::function< void()> callback)
StartupVisibility dashboard_behavior_override_
StartupSurface current_startup_surface_
void PositionWindow(const std::string &window_name, float x, float y)
void SetWindowSize(const std::string &window_name, float width, float height)
NewProjectDialog new_project_dialog_
void SetWelcomeScreenBehavior(StartupVisibility mode)
WorkspaceWindowManager & window_manager_
void SetDashboardBehavior(StartupVisibility mode)
void SetCommandPaletteVisible(bool visible)
std::unique_ptr< WelcomeScreen > welcome_screen_
static float GetMenuBarIconButtonWidth()
UICoordinator(EditorManager *editor_manager, RomFileManager &rom_manager, ProjectManager &project_manager, EditorRegistry &editor_registry, WorkspaceWindowManager &card_registry, SessionCoordinator &session_coordinator, WindowDelegate &window_delegate, ToastManager &toast_manager, PopupManager &popup_manager, ShortcutManager &shortcut_manager)
void CenterWindow(const std::string &window_name)
Low-level window operations with minimal dependencies.
Central registry for all editor cards with session awareness and dependency injection.
void HideAllWindowsInCategory(size_t session_id, const std::string &category)
void SetSidebarVisible(bool visible, bool notify=true)
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
const WindowDescriptor * GetWindowDescriptor(size_t session_id, const std::string &base_window_id) const
std::vector< std::string > GetWindowsInSession(size_t session_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
bool IsWindowPinned(size_t session_id, const std::string &base_window_id) const
void MarkWindowRecentlyUsed(const std::string &window_id)
static BackgroundRenderer & Get()
static void EndTableWithTheming()
static bool BeginTableWithTheming(const char *str_id, int columns, ImGuiTableFlags flags=0, const ImVec2 &outer_size=ImVec2(0, 0), float inner_width=0.0f)
RAII guard for ImGui style colors.
Definition style_guard.h:27
RAII guard for ImGui style vars.
Definition style_guard.h:68
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:598
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_NOTIFICATIONS
Definition icons.h:1335
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_STAR
Definition icons.h:1848
#define ICON_MD_FULLSCREEN_EXIT
Definition icons.h:862
#define ICON_MD_EXPAND_LESS
Definition icons.h:702
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_MANAGE_SEARCH
Definition icons.h:1172
#define ICON_MD_VISIBILITY_OFF
Definition icons.h:2102
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_DASHBOARD
Definition icons.h:517
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_PUSH_PIN
Definition icons.h:1529
#define ICON_MD_FIBER_MANUAL_RECORD
Definition icons.h:739
#define ICON_MD_HISTORY
Definition icons.h:946
#define LOG_ERROR(category, format,...)
Definition log.h:140
#define LOG_INFO(category, format,...)
Definition log.h:136
Definition input.cc:30
constexpr const char * kZScreamWelcome
constexpr const char * kComingFromZScream
constexpr const char * kDisplaySettings
StartupSurface
Represents the current startup surface state.
std::string InferShortcutGroup(absl::string_view name)
Menu-IA group for a shortcut/command name (File, View, Drawers, …).
CommandEntry BuildGotoEntry(const GotoQuery &query, size_t session_id)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
std::optional< GotoQuery > ParseGotoQuery(std::string_view query)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:140
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetSurfaceContainerHighestVec4()
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
void EndThemedTabBar()
ImVec4 GetPrimaryVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
ImVec4 GetSurfaceContainerHighVec4()
constexpr ImVec2 kDefaultModalSize
Definition input.h:21
ImVec4 GetOnSurfaceVariantVec4()
ImVec4 GetSurfaceContainerVec4()
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
Definition file_util.cc:19
std::string GetFileExtension(const std::string &filename)
Gets the file extension from a filename.
Definition file_util.cc:15
StartupVisibility
Tri-state toggle used for startup UI visibility controls.
std::function< void()> callback
Represents a single session, containing a ROM and its associated editors.
std::function< void(const std::string &) toggle_pin)
std::function< void(const std::string &) remove)
std::function< void(const std::string &) create_from_template)
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:589
bool project_opened() const
Definition project.h:505