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
3#include <algorithm>
4#include <functional>
5#include <memory>
6#include <string>
7#include <vector>
8
9#include "absl/strings/match.h"
10#include "absl/strings/str_format.h"
11
12#ifdef __EMSCRIPTEN__
13#include <emscripten.h>
14#endif
15#include "app/application.h"
16#include "app/editor/editor.h"
29#include "app/gui/core/icons.h"
30#include "app/gui/core/input.h"
32#include "app/gui/core/style.h"
37#include "core/project.h"
38#include "imgui/imgui.h"
39#include "util/file_util.h"
40#include "util/platform_paths.h"
41
42namespace yaze {
43namespace editor {
44
46 EditorManager* editor_manager, RomFileManager& rom_manager,
47 ProjectManager& project_manager, EditorRegistry& editor_registry,
48 PanelManager& panel_manager, SessionCoordinator& session_coordinator,
49 WindowDelegate& window_delegate, ToastManager& toast_manager,
50 PopupManager& popup_manager, ShortcutManager& shortcut_manager)
51 : editor_manager_(editor_manager),
52 rom_manager_(rom_manager),
53 project_manager_(project_manager),
54 editor_registry_(editor_registry),
55 panel_manager_(panel_manager),
56 session_coordinator_(session_coordinator),
57 window_delegate_(window_delegate),
58 toast_manager_(toast_manager),
59 popup_manager_(popup_manager),
60 shortcut_manager_(shortcut_manager) {
61 // Initialize welcome screen with proper callbacks
62 welcome_screen_ = std::make_unique<WelcomeScreen>();
63
64 // Wire welcome screen callbacks to EditorManager
65 welcome_screen_->SetOpenRomCallback([this]() {
66#ifdef __EMSCRIPTEN__
67 // In web builds, trigger the file input element directly
68 // The file input handler in app.js will handle the file selection
69 // and call LoadRomFromWeb, which will update the ROM
70 EM_ASM({
71 var romInput = document.getElementById('rom-input');
72 if (romInput) {
73 romInput.click();
74 }
75 });
76 // Don't hide welcome screen yet - it will be hidden when ROM loads
77 // (DrawWelcomeScreen auto-transitions to Dashboard on ROM load)
78#else
79 if (editor_manager_) {
80 auto status = editor_manager_->LoadRom();
81 if (!status.ok()) {
83 absl::StrFormat("Failed to load ROM: %s", status.message()),
85 }
86#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
87 else {
88 // Transition to Dashboard on successful ROM load
90 }
91#endif
92 }
93#endif
94 });
95
96 welcome_screen_->SetNewProjectCallback([this]() {
97 if (editor_manager_) {
98 auto status = editor_manager_->CreateNewProject();
99 if (!status.ok()) {
101 absl::StrFormat("Failed to create project: %s", status.message()),
103 } else {
104 // Transition to Dashboard on successful project creation
106 }
107 }
108 });
109
110 welcome_screen_->SetOpenProjectCallback([this](const std::string& filepath) {
111 if (editor_manager_) {
112 auto status = editor_manager_->OpenRomOrProject(filepath);
113 if (!status.ok()) {
115 absl::StrFormat("Failed to open project: %s", status.message()),
117 } else {
118 // Transition to Dashboard on successful project open
120 }
121 }
122 });
123
124 welcome_screen_->SetOpenAgentCallback([this]() {
125 if (editor_manager_) {
126#ifdef YAZE_BUILD_AGENT_UI
127 editor_manager_->ShowAIAgent();
128#endif
129 // Exit welcome so the agent panels can be interacted with
131 }
132 });
133
134 welcome_screen_->SetOpenProjectDialogCallback([this]() {
135 if (editor_manager_) {
136 auto status = editor_manager_->OpenProject();
137 if (!status.ok()) {
139 absl::StrFormat("Failed to open project: %s", status.message()),
141 } else {
143 }
144 }
145 });
146
147 welcome_screen_->SetOpenProjectManagementCallback([this]() {
148 if (editor_manager_) {
151 }
152 });
153
154 welcome_screen_->SetOpenProjectFileEditorCallback([this]() {
155 if (!editor_manager_) {
156 return;
157 }
158 const auto* project = editor_manager_->GetCurrentProject();
159 if (!project || project->filepath.empty()) {
160 toast_manager_.Show("No project file to edit", ToastType::kInfo);
161 return;
162 }
165 });
166}
167
181
183 if (dashboard_behavior_override_ == mode) {
184 return;
185 }
187 if (mode == StartupVisibility::kShow) {
188 // Only transition to dashboard if we're not in welcome
191 }
192 } else if (mode == StartupVisibility::kHide) {
193 // If hiding dashboard, transition to editor state
196 }
197 }
198}
199
201 if (!ImGui::GetCurrentContext()) {
202 return;
203 }
204
205 const ImGuiViewport* viewport = ImGui::GetMainViewport();
206 if (!viewport) {
207 return;
208 }
209
210 ImDrawList* bg_draw_list =
211 ImGui::GetBackgroundDrawList(const_cast<ImGuiViewport*>(viewport));
212
213 auto& theme_manager = gui::ThemeManager::Get();
214 auto current_theme = theme_manager.GetCurrentTheme();
215 auto& bg_renderer = gui::BackgroundRenderer::Get();
216
217 // Draw grid covering the entire main viewport
218 ImVec2 grid_pos = viewport->WorkPos;
219 ImVec2 grid_size = viewport->WorkSize;
220 bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size,
221 current_theme.primary);
222}
223
225 // Note: Theme styling is applied by ThemeManager, not here
226 // This is called from EditorManager::Update() - don't call menu bar stuff
227 // here
228
229 // Draw UI windows and dialogs
230 // Session dialogs are drawn by SessionCoordinator separately to avoid
231 // duplication
232 DrawCommandPalette(); // Ctrl+Shift+P
233 DrawPanelFinder(); // Ctrl+P
234 DrawGlobalSearch(); // Ctrl+Shift+K
235 DrawWorkspacePresetDialogs(); // Save/Load workspace dialogs
236 DrawLayoutPresets(); // Layout preset dialogs
237 DrawWelcomeScreen(); // Welcome screen
238 DrawProjectHelp(); // Project help
239 DrawWindowManagementUI(); // Window management
240
241#ifdef YAZE_BUILD_AGENT_UI
242 if (show_ai_agent_) {
243 if (editor_manager_) {
244 editor_manager_->ShowAIAgent();
245 }
246 show_ai_agent_ = false;
247 }
248
249 if (show_chat_history_) {
250 if (editor_manager_) {
251 editor_manager_->ShowChatHistory();
252 }
253 show_chat_history_ = false;
254 }
255
257 if (editor_manager_) {
258 if (auto* right_panel = editor_manager_->right_panel_manager()) {
259 right_panel->OpenPanel(RightPanelManager::PanelType::kProposals);
260 }
261 }
262 show_proposal_drawer_ = false;
263 }
264#endif
265
266 // Draw popups and toasts
269}
270
272 const ImGuiViewport* viewport = ImGui::GetMainViewport();
273 if (!viewport) {
274 return false;
275 }
276 const float width = viewport->WorkSize.x;
277#if defined(__APPLE__) && TARGET_OS_IOS == 1
278 // Use hysteresis to avoid layout thrash while iPad windows are being
279 // interactively resized around the compact breakpoint.
280 static bool compact_mode = false;
281 constexpr float kEnterCompactWidth = 900.0f;
282 constexpr float kExitCompactWidth = 940.0f;
283 compact_mode =
284 compact_mode ? (width < kExitCompactWidth) : (width < kEnterCompactWidth);
285 return compact_mode;
286#else
287 return width < 900.0f;
288#endif
289}
290
291// =============================================================================
292// Menu Bar Helpers
293// =============================================================================
294
295bool UICoordinator::DrawMenuBarIconButton(const char* icon, const char* tooltip,
296 bool is_active) {
297 // Consistent button styling: transparent background, themed text
298 gui::StyleColorGuard btn_guard(
299 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
300 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
301 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
302 {ImGuiCol_Text,
304
305 bool clicked = ImGui::SmallButton(icon);
306
307 if (tooltip && ImGui::IsItemHovered()) {
308 ImGui::SetTooltip("%s", tooltip);
309 }
310
311 return clicked;
312}
313
315 // SmallButton width = text width + frame padding * 2
316 const float frame_padding = ImGui::GetStyle().FramePadding.x;
317 // Use a standard icon width (Material Design icons are uniform)
318 const float icon_width = ImGui::CalcTextSize(ICON_MD_SETTINGS).x;
319 return icon_width + frame_padding * 2.0f;
320}
321
323 // Right-aligned status cluster: Version, dirty indicator, session, bell, panel toggles
324 // Panel toggles are positioned using SCREEN coordinates (from viewport) so they
325 // stay fixed even when the dockspace resizes due to panel open/close.
326 //
327 // Layout: [v0.x.x][●][📄▾][🔔] [panels][⬆]
328 // ^^^ shifts with dockspace ^^^ ^^^ fixed screen position ^^^
329
330 auto* current_rom = editor_manager_->GetCurrentRom();
331 const std::string full_version =
332 absl::StrFormat("v%s", editor_manager_->version().c_str());
333
334 const float item_spacing = 6.0f;
335 const float padding = 8.0f;
336
337 auto CalcSmallButtonWidth = [](const char* label) -> float {
338 // SmallButton width = text width + frame padding * 2
339 const float frame_padding = ImGui::GetStyle().FramePadding.x;
340 const float text_w = ImGui::CalcTextSize(label).x;
341 return text_w + frame_padding * 2.0f;
342 };
343
344 // Get TRUE viewport dimensions (not affected by dockspace resize)
345 const ImGuiViewport* viewport = ImGui::GetMainViewport();
346 const float true_viewport_right = viewport->WorkPos.x + viewport->WorkSize.x;
347
348 // Calculate panel toggle region width
349 // Keep this in sync with RightPanelManager::DrawPanelToggleButtons().
350 const bool has_panel_toggles =
352 float panel_buttons_width = 0.0f;
353 if (has_panel_toggles) {
354 const char* kIcons[] = {
355 ICON_MD_FOLDER_SPECIAL, // Project
356 ICON_MD_SMART_TOY, // Agent
357 ICON_MD_HELP_OUTLINE, // Help
358 ICON_MD_SETTINGS, // Settings
359 ICON_MD_LIST_ALT, // Properties
360 };
361 constexpr size_t kIconCount = sizeof(kIcons) / sizeof(kIcons[0]);
362
363 for (size_t i = 0; i < kIconCount; ++i) {
364 panel_buttons_width += CalcSmallButtonWidth(kIcons[i]);
365 if (i + 1 < kIconCount) {
366 panel_buttons_width += item_spacing;
367 }
368 }
369 }
370
371 // Reserve only the real button footprint so compact icon toggles do not
372 // leave a dead gap before the right edge cluster.
373 float panel_region_width = panel_buttons_width;
374#ifdef __EMSCRIPTEN__
375 // WASM hide menu bar toggle (drawn inline after panel buttons).
376 panel_region_width +=
377 CalcSmallButtonWidth(ICON_MD_EXPAND_LESS) + item_spacing;
378#endif
379
380 // Calculate screen X position for panel toggles (fixed at viewport right edge)
381 float panel_screen_x = true_viewport_right - panel_region_width;
384 panel_screen_x -= editor_manager_->right_panel_manager()->GetPanelWidth();
385 }
386
387 // Calculate available space for status cluster (version, dirty, session, bell)
388 // This ends where the panel toggle region begins
389 const float window_width = ImGui::GetWindowWidth();
390 const float window_screen_x = ImGui::GetWindowPos().x;
391 const float menu_items_end = ImGui::GetCursorPosX() + 16.0f;
392
393 // Convert panel screen X to window-local coordinates for space calculation
394 float panel_local_x = panel_screen_x - window_screen_x;
395 float region_end =
396 std::min(window_width - padding, panel_local_x - item_spacing);
397
398 // Calculate what elements to show - progressive hiding when space is tight
399 bool has_dirty_rom =
400 current_rom && current_rom->is_loaded() && current_rom->dirty();
401 bool has_multiple_sessions = session_coordinator_.HasMultipleSessions();
402
403 float version_width = ImGui::CalcTextSize(full_version.c_str()).x;
404 float dirty_width =
405 ImGui::CalcTextSize(ICON_MD_FIBER_MANUAL_RECORD).x + item_spacing;
406 const float session_width = CalcSmallButtonWidth(ICON_MD_LAYERS);
407
408 const float available_width = region_end - menu_items_end - padding;
409
410 // Minimum required width: just the bell (always visible)
411 float required_width = CalcSmallButtonWidth(ICON_MD_NOTIFICATIONS);
412
413 // Progressive show/hide based on available space
414 // Priority (highest to lowest): Bell > Dirty > Session > Version
415
416 // Try to fit version (lowest priority - hide first when tight)
417 bool show_version =
418 (required_width + version_width + item_spacing) <= available_width;
419 if (show_version) {
420 required_width += version_width + item_spacing;
421 }
422
423 // Try to fit session button (medium priority)
424 bool show_session =
425 has_multiple_sessions &&
426 (required_width + session_width + item_spacing) <= available_width;
427 if (show_session) {
428 required_width += session_width + item_spacing;
429 }
430
431 // Try to fit dirty indicator (high priority - only hide if extremely tight)
432 bool show_dirty =
433 has_dirty_rom && (required_width + dirty_width) <= available_width;
434 if (show_dirty) {
435 required_width += dirty_width;
436 }
437
438 // Calculate start position (right-align within available space)
439 float start_pos = std::max(menu_items_end, region_end - required_width);
440
441 // =========================================================================
442 // DRAW STATUS CLUSTER (shifts with dockspace)
443 // =========================================================================
444 ImGui::SameLine(start_pos);
445 gui::StyleVarGuard item_spacing_guard(ImGuiStyleVar_ItemSpacing,
446 ImVec2(item_spacing, 0.0f));
447
448 // 1. Version - subdued gray text
449 if (show_version) {
450 gui::ColoredText(full_version.c_str(), gui::GetTextDisabledVec4());
451 ImGui::SameLine();
452 }
453
454 // 2. Dirty badge - warning color dot
455 if (show_dirty) {
456 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
458 gui::ConvertColorToImVec4(theme.warning));
459 if (ImGui::IsItemHovered()) {
460 ImGui::SetTooltip("Unsaved changes: %s",
461 current_rom->short_name().c_str());
462 }
463 ImGui::SameLine();
464 }
465
466 // 3. Session button - layers icon
467 if (show_session) {
469 ImGui::SameLine();
470 }
471
472 // 4. Notification bell (pass visibility flags for enhanced tooltip)
473 DrawNotificationBell(show_dirty, has_dirty_rom, show_session,
474 has_multiple_sessions);
475
476 // =========================================================================
477 // DRAW PANEL TOGGLES (fixed screen position, unaffected by dockspace resize)
478 // =========================================================================
479 if (has_panel_toggles) {
480 // Get current Y position within menu bar
481 float menu_bar_y = ImGui::GetCursorScreenPos().y;
482
483 // Position at fixed screen coordinates
484 ImGui::SetCursorScreenPos(ImVec2(panel_screen_x, menu_bar_y));
485
486 // Draw panel toggle buttons
488 }
489
490#ifdef __EMSCRIPTEN__
491 // WASM toggle button - also at fixed position
492 ImGui::SameLine();
494 "Hide menu bar (Alt to restore)")) {
495 show_menu_bar_ = false;
496 }
497#endif
498}
499
501 // Only draw when menu bar is hidden (primarily for WASM builds)
502 if (show_menu_bar_) {
503 return;
504 }
505
506 // Small floating button in top-left corner to restore menu bar
507 ImGuiWindowFlags flags =
508 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
509 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
510 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize |
511 ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings;
512
513 ImGui::SetNextWindowPos(ImVec2(8, 8));
514 ImGui::SetNextWindowBgAlpha(0.7f);
515
516 if (ImGui::Begin("##MenuBarRestore", nullptr, flags)) {
517 gui::StyleColorGuard btn_guard(
518 {{ImGuiCol_Button, gui::GetSurfaceContainerVec4()},
519 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
520 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
521 {ImGuiCol_Text, gui::GetPrimaryVec4()}});
522
523 if (ImGui::Button(ICON_MD_FULLSCREEN_EXIT, ImVec2(32, 32))) {
524 show_menu_bar_ = true;
525 }
526
527 if (ImGui::IsItemHovered()) {
528 ImGui::SetTooltip("Show menu bar (Alt)");
529 }
530 }
531 ImGui::End();
532
533 // Also check for Alt key to restore menu bar
534 if (ImGui::IsKeyPressed(ImGuiKey_LeftAlt) ||
535 ImGui::IsKeyPressed(ImGuiKey_RightAlt)) {
536 show_menu_bar_ = true;
537 }
538}
539
540void UICoordinator::DrawNotificationBell(bool show_dirty, bool has_dirty_rom,
541 bool show_session,
542 bool has_multiple_sessions) {
543 size_t unread = toast_manager_.GetUnreadCount();
544 auto* current_rom = editor_manager_->GetCurrentRom();
545 auto* right_panel = editor_manager_->right_panel_manager();
546
547 // Check if notifications panel is active
548 bool is_active =
549 right_panel &&
551
552 // Bell icon with accent color when there are unread notifications or panel is active
553 ImVec4 bell_text_color = (unread > 0 || is_active)
556 gui::StyleColorGuard bell_guard(
557 {{ImGuiCol_Text, bell_text_color},
558 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
559 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
560 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()}});
561
562 // Bell button - opens notifications panel in right sidebar
563 if (ImGui::SmallButton(ICON_MD_NOTIFICATIONS)) {
564 if (right_panel) {
565 right_panel->TogglePanel(RightPanelManager::PanelType::kNotifications);
567 }
568 }
569
570 // Enhanced tooltip showing notifications + hidden status items
571 if (ImGui::IsItemHovered()) {
572 ImGui::BeginTooltip();
573
574 // Notifications
575 if (unread > 0) {
576 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s %zu new notification%s",
577 ICON_MD_NOTIFICATIONS, unread, unread == 1 ? "" : "s");
578 } else {
579 gui::ColoredText(ICON_MD_NOTIFICATIONS " No new notifications",
581 }
582
583 ImGui::TextDisabled("Click to open Notifications panel");
584
585 // Show hidden status items if any
586 if (!show_dirty && has_dirty_rom) {
587 ImGui::Separator();
589 gui::ThemeManager::Get().GetCurrentTheme().warning),
590 ICON_MD_FIBER_MANUAL_RECORD " Unsaved changes: %s",
591 current_rom->short_name().c_str());
592 }
593
594 if (!show_session && has_multiple_sessions) {
595 if (!show_dirty && has_dirty_rom) {
596 // Already had a separator
597 } else {
598 ImGui::Separator();
599 }
601 ICON_MD_LAYERS " %zu sessions active",
603 }
604
605 ImGui::EndTooltip();
606 }
607}
608
610 auto* current_rom = editor_manager_->GetCurrentRom();
611
612 // Consistent button styling with other menubar buttons
613 gui::StyleColorGuard session_btn_guard(
614 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
615 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
616 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
617 {ImGuiCol_Text, gui::GetTextSecondaryVec4()}});
618
619 // Store button position for popup anchoring
620 ImVec2 button_min = ImGui::GetCursorScreenPos();
621
622 if (ImGui::SmallButton(ICON_MD_LAYERS)) {
623 ImGui::OpenPopup("##SessionSwitcherPopup");
624 }
625
626 ImVec2 button_max = ImGui::GetItemRectMax();
627
628 if (ImGui::IsItemHovered()) {
629 std::string tooltip = current_rom && current_rom->is_loaded()
630 ? current_rom->short_name()
631 : "No ROM loaded";
632 ImGui::SetTooltip("%s\n%zu sessions open (Ctrl+Tab)", tooltip.c_str(),
634 }
635
636 // Anchor popup to right edge - position so right edge aligns with button
637 const float popup_width = 250.0f;
638 const float screen_width = ImGui::GetIO().DisplaySize.x;
639 const float popup_x =
640 std::min(button_min.x, screen_width - popup_width - 10.0f);
641
642 ImGui::SetNextWindowPos(ImVec2(popup_x, button_max.y + 2.0f),
643 ImGuiCond_Appearing);
644
645 // Session switcher popup
646 if (ImGui::BeginPopup("##SessionSwitcherPopup")) {
647 ImGui::Text(ICON_MD_LAYERS " Sessions");
648 ImGui::Separator();
649
650 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) {
652 continue;
653
654 auto* session =
656 if (!session)
657 continue;
658
659 Rom* rom = &session->rom;
660 ImGui::PushID(static_cast<int>(i));
661
662 bool is_current = (rom == current_rom);
663 std::optional<gui::StyleColorGuard> current_guard;
664 if (is_current) {
665 current_guard.emplace(ImGuiCol_Text, gui::GetPrimaryVec4());
666 }
667
668 std::string label =
669 rom->is_loaded()
670 ? absl::StrFormat("%s %s", ICON_MD_DESCRIPTION,
671 rom->short_name().c_str())
672 : absl::StrFormat("%s Session %zu", ICON_MD_DESCRIPTION, i + 1);
673
674 if (ImGui::Selectable(label.c_str(), is_current)) {
676 }
677
678 ImGui::PopID();
679 }
680
681 ImGui::EndPopup();
682 }
683}
684
685// ============================================================================
686// Session UI Delegation
687// ============================================================================
688// All session-related UI is now managed by SessionCoordinator to eliminate
689// duplication. UICoordinator methods delegate to SessionCoordinator.
690
694
698
700 if (visible) {
702 } else {
704 }
705}
706
707// Emulator visibility delegates to PanelManager (single source of truth)
709 size_t session_id = session_coordinator_.GetActiveSessionIndex();
710 auto emulator_panels =
711 panel_manager_.GetPanelsInCategory(session_id, "Emulator");
712 for (const auto& panel : emulator_panels) {
713 if (panel.visibility_flag && *panel.visibility_flag) {
714 return true;
715 }
716 }
717 return false;
718}
719
721 size_t session_id = session_coordinator_.GetActiveSessionIndex();
722 if (visible) {
723 auto default_panels =
725 for (const auto& panel_id : default_panels) {
726 panel_manager_.ShowPanel(session_id, panel_id);
727 }
728 } else {
729 panel_manager_.HideAllPanelsInCategory(session_id, "Emulator");
730 }
731}
732
733// ============================================================================
734// Layout and Window Management UI
735// ============================================================================
736
738 // TODO: [EditorManagerRefactor] Implement full layout preset UI with
739 // save/load For now, this is accessed via Window menu items that call
740 // workspace_manager directly
741}
742
744 // ============================================================================
745 // CENTRALIZED WELCOME SCREEN LOGIC (using StartupSurface state)
746 // ============================================================================
747 // Uses ShouldShowWelcome() as single source of truth
748 // Auto-transitions to Dashboard on ROM load
749 // Activity Bar hidden when welcome is visible
750 // ============================================================================
751
752 if (!editor_manager_) {
753 LOG_ERROR("UICoordinator",
754 "EditorManager is null - cannot check ROM state");
755 return;
756 }
757
758 if (!welcome_screen_) {
759 LOG_ERROR("UICoordinator", "WelcomeScreen object is null - cannot render");
760 return;
761 }
762
763 // Check ROM state and update startup surface accordingly
764 auto* current_rom = editor_manager_->GetCurrentRom();
765 bool rom_is_loaded = current_rom && current_rom->is_loaded();
766
767 // Auto-transition: ROM loaded -> Dashboard or Editor
768 if (rom_is_loaded && current_startup_surface_ == StartupSurface::kWelcome) {
771 } else {
773 }
774 }
775
776 // Auto-transition: ROM unloaded -> Welcome (reset to welcome state)
777 if (!rom_is_loaded && current_startup_surface_ != StartupSurface::kWelcome &&
780 }
781
782 // Use centralized visibility check
783 if (!ShouldShowWelcome()) {
784 return;
785 }
786
787 // Provide context state for gating actions
788 welcome_screen_->SetContextState(rom_is_loaded,
790
791 // Reset first show flag to override ImGui ini state
792 welcome_screen_->ResetFirstShow();
793
794 // Update recent projects before showing
795 welcome_screen_->RefreshRecentProjects();
796
797 // Pass layout offsets so welcome screen centers within dockspace region
798 // Note: Activity Bar is hidden when welcome is shown, so left_offset = 0
799 float left_offset =
801 float right_offset = editor_manager_->GetRightLayoutOffset();
802 welcome_screen_->SetLayoutOffsets(left_offset, right_offset);
803
804 // Show the welcome screen window
805 bool is_open = true;
806 welcome_screen_->Show(&is_open);
807
808 // If user closed it via X button, respect that and transition to appropriate state
809 if (!is_open) {
811 // Transition to Dashboard if ROM loaded, stay in Editor state otherwise
812 if (rom_is_loaded) {
814 }
815 }
816}
817
819 // TODO: [EditorManagerRefactor] Implement project help dialog
820 // Show context-sensitive help based on current editor and ROM state
821}
822
825 ImGui::Begin("Save Workspace Preset", &show_save_workspace_preset_,
826 ImGuiWindowFlags_AlwaysAutoResize);
827 static char preset_name[128] = "";
828 ImGui::InputText("Name", preset_name, IM_ARRAYSIZE(preset_name));
829 if (ImGui::Button("Save", gui::kDefaultModalSize)) {
830 if (strlen(preset_name) > 0) {
834 preset_name[0] = '\0';
835 }
836 }
837 ImGui::SameLine();
838 if (ImGui::Button("Cancel", gui::kDefaultModalSize)) {
840 preset_name[0] = '\0';
841 }
842 ImGui::End();
843 }
844
846 ImGui::Begin("Load Workspace Preset", &show_load_workspace_preset_,
847 ImGuiWindowFlags_AlwaysAutoResize);
848
849 // Lazy load workspace presets when UI is accessed
851
852 if (auto* workspace_manager = editor_manager_->workspace_manager()) {
853 for (const auto& name : workspace_manager->workspace_presets()) {
854 if (ImGui::Selectable(name.c_str())) {
858 }
859 }
860 if (workspace_manager->workspace_presets().empty())
861 ImGui::Text("No presets found");
862 }
863 ImGui::End();
864 }
865}
866
868 // TODO: [EditorManagerRefactor] Implement window management dialog
869 // Provide UI for toggling window visibility, managing docking, etc.
870}
871
873 // Draw all registered popups
875}
876
877void UICoordinator::ShowPopup(const std::string& popup_name) {
878 popup_manager_.Show(popup_name.c_str());
879}
880
881void UICoordinator::HidePopup(const std::string& popup_name) {
882 popup_manager_.Hide(popup_name.c_str());
883}
884
886 // Display Settings is now a popup managed by PopupManager
887 // Delegate directly to PopupManager instead of UICoordinator
889}
890
891// ============================================================================
892// Sidebar Visibility (delegates to PanelManager)
893// ============================================================================
894
898
902
906
910
914
915// Material Design component helpers
916void UICoordinator::DrawMaterialButton(const std::string& text,
917 const std::string& icon,
918 const ImVec4& color,
919 std::function<void()> callback,
920 bool enabled) {
921 std::optional<gui::StyleColorGuard> disabled_guard;
922 if (!enabled) {
923 disabled_guard.emplace(std::initializer_list<gui::StyleColorGuard::Entry>{
924 {ImGuiCol_Button, gui::GetSurfaceContainerHighestVec4()},
925 {ImGuiCol_Text, gui::GetOnSurfaceVariantVec4()}});
926 }
927
928 std::string button_text =
929 absl::StrFormat("%s %s", icon.c_str(), text.c_str());
930 if (ImGui::Button(button_text.c_str())) {
931 if (enabled && callback) {
932 callback();
933 }
934 }
935}
936
937// Layout and positioning helpers
938void UICoordinator::CenterWindow(const std::string& window_name) {
939 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
940 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
941}
942
943void UICoordinator::PositionWindow(const std::string& window_name, float x,
944 float y) {
945 ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Appearing);
946}
947
948void UICoordinator::SetWindowSize(const std::string& window_name, float width,
949 float height) {
950 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_FirstUseEver);
951}
952
955 return;
956
957 // Initialize command palette on first use
959 InitializeCommandPalette(0); // Default session
960 }
961
962 using namespace ImGui;
963 auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
964
965 SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing,
966 ImVec2(0.5f, 0.5f));
967 SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
968
969 bool show_palette = true;
970 if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(),
971 &show_palette, ImGuiWindowFlags_NoCollapse)) {
972 // Search input with focus management
973 SetNextItemWidth(-100);
974 if (IsWindowAppearing()) {
975 SetKeyboardFocusHere();
977 }
978
979 bool input_changed = InputTextWithHint(
980 "##cmd_query",
981 absl::StrFormat("%s Search commands (fuzzy matching enabled)...",
983 .c_str(),
985
986 SameLine();
987 if (Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
988 command_palette_query_[0] = '\0';
989 input_changed = true;
991 }
992
993 Separator();
994
995 // Unified command list structure
996 struct ScoredCommand {
997 int score;
998 std::string name;
999 std::string category;
1000 std::string shortcut;
1001 std::function<void()> callback;
1002 };
1003 std::vector<ScoredCommand> scored_commands;
1004
1005 std::string query_lower = command_palette_query_;
1006 std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(),
1007 ::tolower);
1008
1009 auto score_text = [&query_lower](const std::string& text) -> int {
1010 std::string text_lower = text;
1011 std::transform(text_lower.begin(), text_lower.end(), text_lower.begin(),
1012 ::tolower);
1013
1014 if (query_lower.empty())
1015 return 1;
1016 if (text_lower.find(query_lower) == 0)
1017 return 1000;
1018 if (text_lower.find(query_lower) != std::string::npos)
1019 return 500;
1020
1021 // Fuzzy match
1022 size_t text_idx = 0, query_idx = 0;
1023 int score = 0;
1024 while (text_idx < text_lower.length() &&
1025 query_idx < query_lower.length()) {
1026 if (text_lower[text_idx] == query_lower[query_idx]) {
1027 score += 10;
1028 query_idx++;
1029 }
1030 text_idx++;
1031 }
1032 return (query_idx == query_lower.length()) ? score : 0;
1033 };
1034
1035 // Add shortcuts from ShortcutManager
1036 for (const auto& [name, shortcut] : shortcut_manager_.GetShortcuts()) {
1037 int score = score_text(name);
1038 if (score > 0) {
1039 std::string shortcut_text =
1040 shortcut.keys.empty()
1041 ? ""
1042 : absl::StrFormat("(%s)", PrintShortcut(shortcut.keys).c_str());
1043 scored_commands.push_back(
1044 {score, name, "Shortcuts", shortcut_text, shortcut.callback});
1045 }
1046 }
1047
1048 // Add commands from CommandPalette
1049 for (const auto& entry : command_palette_.GetAllCommands()) {
1050 int score = score_text(entry.name);
1051 // Also search category and description
1052 score += score_text(entry.category) / 2;
1053 score += score_text(entry.description) / 4;
1054
1055 if (score > 0) {
1056 scored_commands.push_back({score, entry.name, entry.category,
1057 entry.shortcut, entry.callback});
1058 }
1059 }
1060
1061 // Sort by score descending
1062 std::sort(scored_commands.begin(), scored_commands.end(),
1063 [](const auto& a, const auto& b) { return a.score > b.score; });
1064
1065 // Display results with categories
1066 if (gui::BeginThemedTabBar("CommandCategories")) {
1067 if (BeginTabItem(
1068 absl::StrFormat("%s All Commands", ICON_MD_LIST).c_str())) {
1070 "CommandPaletteTable", 4,
1071 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1072 ImGuiTableFlags_SizingStretchProp,
1073 ImVec2(0, -30))) {
1074 TableSetupColumn("Command", ImGuiTableColumnFlags_WidthStretch,
1075 0.45f);
1076 TableSetupColumn("Category", ImGuiTableColumnFlags_WidthStretch,
1077 0.2f);
1078 TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch,
1079 0.2f);
1080 TableSetupColumn("Score", ImGuiTableColumnFlags_WidthStretch, 0.15f);
1081 TableHeadersRow();
1082
1083 for (size_t i = 0; i < scored_commands.size(); ++i) {
1084 const auto& cmd = scored_commands[i];
1085
1086 TableNextRow();
1087 TableNextColumn();
1088
1089 PushID(static_cast<int>(i));
1090 bool is_selected =
1091 (static_cast<int>(i) == command_palette_selected_idx_);
1092 if (Selectable(cmd.name.c_str(), is_selected,
1093 ImGuiSelectableFlags_SpanAllColumns)) {
1095 if (cmd.callback) {
1096 cmd.callback();
1097 show_command_palette_ = false;
1098 // Record usage for frecency
1099 command_palette_.RecordUsage(cmd.name);
1100 }
1101 }
1102 PopID();
1103
1104 TableNextColumn();
1105 gui::ColoredText(cmd.category.c_str(),
1106 gui::ConvertColorToImVec4(theme.text_secondary));
1107
1108 TableNextColumn();
1109 gui::ColoredText(cmd.shortcut.c_str(),
1110 gui::ConvertColorToImVec4(theme.text_secondary));
1111
1112 TableNextColumn();
1113 gui::ColoredTextF(gui::ConvertColorToImVec4(theme.text_disabled),
1114 "%d", cmd.score);
1115 }
1116
1118 }
1119 EndTabItem();
1120 }
1121
1122 if (BeginTabItem(absl::StrFormat("%s Recent", ICON_MD_HISTORY).c_str())) {
1123 auto recent = command_palette_.GetRecentCommands(10);
1124 if (recent.empty()) {
1125 Text("No recent commands yet.");
1126 } else {
1127 for (const auto& entry : recent) {
1128 if (Selectable(entry.name.c_str())) {
1129 if (entry.callback) {
1130 entry.callback();
1131 show_command_palette_ = false;
1132 command_palette_.RecordUsage(entry.name);
1133 }
1134 }
1135 }
1136 }
1137 EndTabItem();
1138 }
1139
1140 if (BeginTabItem(absl::StrFormat("%s Frequent", ICON_MD_STAR).c_str())) {
1141 auto frequent = command_palette_.GetFrequentCommands(10);
1142 if (frequent.empty()) {
1143 Text("No frequently used commands yet.");
1144 } else {
1145 for (const auto& entry : frequent) {
1146 if (Selectable(absl::StrFormat("%s (%d uses)", entry.name,
1147 entry.usage_count)
1148 .c_str())) {
1149 if (entry.callback) {
1150 entry.callback();
1151 show_command_palette_ = false;
1152 command_palette_.RecordUsage(entry.name);
1153 }
1154 }
1155 }
1156 }
1157 EndTabItem();
1158 }
1159
1161 }
1162
1163 // Status bar with tips
1164 Separator();
1165 Text("%s %zu commands | Score: fuzzy match", ICON_MD_INFO,
1166 scored_commands.size());
1167 SameLine();
1168 gui::ColoredText("| ↑↓=Navigate | Enter=Execute | Esc=Close",
1169 gui::ConvertColorToImVec4(theme.text_disabled));
1170 }
1171 End();
1172
1173 // Update visibility state - save history when closing
1174 if (!show_palette) {
1175 show_command_palette_ = false;
1176 // Save command usage history on close
1177 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1178 if (config_dir.ok()) {
1179 std::filesystem::path history_file = *config_dir / "command_history.json";
1180 command_palette_.SaveHistory(history_file.string());
1181 }
1182 }
1183}
1184
1186 if (!show_panel_finder_)
1187 return;
1188
1189 using namespace ImGui;
1190 const size_t session_id = panel_manager_.GetActiveSessionId();
1191
1192 // B6: Responsive modal sizing with dim overlay
1193 const ImGuiViewport* viewport = GetMainViewport();
1194 ImDrawList* bg_list = GetBackgroundDrawList();
1195 bg_list->AddRectFilled(viewport->WorkPos,
1196 ImVec2(viewport->WorkPos.x + viewport->WorkSize.x,
1197 viewport->WorkPos.y + viewport->WorkSize.y),
1198 IM_COL32(0, 0, 0, 100));
1199
1200 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing,
1201 ImVec2(0.5f, 0.3f));
1202 if (IsCompactLayout()) {
1203 SetNextWindowSize(
1204 ImVec2(viewport->WorkSize.x * 0.95f, viewport->WorkSize.y * 0.70f),
1205 ImGuiCond_Appearing);
1206 } else {
1207 SetNextWindowSize(ImVec2(600, 420), ImGuiCond_Appearing);
1208 }
1209
1210 const ImGuiWindowFlags finder_flags =
1211 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
1212 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking |
1213 ImGuiWindowFlags_NoSavedSettings;
1214
1215 bool show = true;
1216 if (Begin(ICON_MD_DASHBOARD " Panel Finder", &show, finder_flags)) {
1217 // Auto-focus search on open
1218 if (IsWindowAppearing()) {
1219 SetKeyboardFocusHere();
1221 }
1222
1223 SetNextItemWidth(-1);
1224 bool input_changed = InputTextWithHint(
1225 "##panel_finder_query", ICON_MD_SEARCH " Find panel...",
1227
1228 if (input_changed) {
1230 }
1231
1232 Separator();
1233
1234 // Build filtered + scored list
1235 struct PanelEntry {
1236 std::string card_id;
1237 std::string display_name;
1238 std::string icon;
1239 std::string category;
1240 bool visible;
1241 bool pinned;
1242 int score;
1243 };
1244 std::vector<PanelEntry> entries;
1245
1246 std::string query(panel_finder_query_);
1247
1248 // B2: Fuzzy scoring via CommandPalette::FuzzyScore
1249 for (const auto& [card_id, desc] :
1251 int score = 0;
1252 if (!query.empty()) {
1253 score = CommandPalette::FuzzyScore(desc.display_name, query) +
1254 CommandPalette::FuzzyScore(desc.category, query) / 2;
1255 if (score <= 0)
1256 continue;
1257 }
1258
1259 bool vis = desc.visibility_flag ? *desc.visibility_flag : false;
1260 bool pin = panel_manager_.IsPanelPinned(card_id);
1261 entries.push_back({card_id, desc.display_name, desc.icon, desc.category,
1262 vis, pin, score});
1263 }
1264
1265 // B3: MRU + fuzzy sort
1266 if (query.empty()) {
1267 // Empty query: pinned first, then MRU order (higher time = more recent)
1268 std::sort(entries.begin(), entries.end(),
1269 [this](const PanelEntry& lhs, const PanelEntry& rhs) {
1270 if (lhs.pinned != rhs.pinned)
1271 return lhs.pinned > rhs.pinned;
1272 uint64_t lhs_t = panel_manager_.GetPanelMRUTime(lhs.card_id);
1273 uint64_t rhs_t = panel_manager_.GetPanelMRUTime(rhs.card_id);
1274 if (lhs_t != rhs_t)
1275 return lhs_t > rhs_t;
1276 return lhs.display_name < rhs.display_name;
1277 });
1278 } else {
1279 // With query: sort by score descending, pinned tiebreaker
1280 std::sort(entries.begin(), entries.end(),
1281 [](const PanelEntry& lhs, const PanelEntry& rhs) {
1282 if (lhs.score != rhs.score)
1283 return lhs.score > rhs.score;
1284 if (lhs.pinned != rhs.pinned)
1285 return lhs.pinned > rhs.pinned;
1286 return lhs.display_name < rhs.display_name;
1287 });
1288 }
1289
1290 // Keyboard navigation
1291 if (IsKeyPressed(ImGuiKey_DownArrow) &&
1292 panel_finder_selected_idx_ < static_cast<int>(entries.size()) - 1) {
1294 }
1295 if (IsKeyPressed(ImGuiKey_UpArrow) && panel_finder_selected_idx_ > 0) {
1297 }
1298 bool enter_pressed = IsKeyPressed(ImGuiKey_Enter);
1299
1300 // B5: Touch-aware item sizing
1301 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
1302 if (is_touch) {
1303 PushStyleVar(ImGuiStyleVar_ItemSpacing,
1304 ImVec2(GetStyle().ItemSpacing.x, 10.0f));
1305 }
1306
1307 // Draw panel list
1308 BeginChild("##PanelFinderList");
1309 for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
1310 const auto& entry = entries[i];
1311 bool is_selected = (i == panel_finder_selected_idx_);
1312
1313 // B4: Visibility indicator icon
1314 const char* vis_icon =
1316
1317 // Dim hidden panels
1318 std::optional<gui::StyleColorGuard> dim_guard;
1319 if (!entry.visible) {
1320 ImVec4 dimmed = GetStyleColorVec4(ImGuiCol_Text);
1321 dimmed.w *= 0.5f;
1322 dim_guard.emplace(ImGuiCol_Text, dimmed);
1323 }
1324
1325 std::string label =
1326 absl::StrFormat("%s %s %s", vis_icon, entry.icon.c_str(),
1327 entry.display_name.c_str());
1328
1329 // Touch: ensure selectable meets 44px minimum height
1330 float item_h =
1331 is_touch ? std::max(GetTextLineHeightWithSpacing(), 44.0f) : 0.0f;
1332
1333 PushID(entry.card_id.c_str());
1334 if (Selectable(label.c_str(), is_selected, ImGuiSelectableFlags_None,
1335 ImVec2(0, item_h))) {
1336 panel_manager_.ShowPanel(session_id, entry.card_id);
1338 show_panel_finder_ = false;
1339 }
1340 // Show category badge on the same line
1341 SameLine(GetContentRegionAvail().x - 80);
1342 TextDisabled("%s", entry.category.c_str());
1343 if (entry.pinned) {
1344 SameLine();
1345 TextDisabled(ICON_MD_PUSH_PIN);
1346 }
1347 PopID();
1348
1349 // Enter to activate selected
1350 if (is_selected && enter_pressed) {
1351 panel_manager_.ShowPanel(session_id, entry.card_id);
1353 show_panel_finder_ = false;
1354 }
1355
1356 // Scroll selected into view
1357 if (is_selected && (IsKeyPressed(ImGuiKey_DownArrow) ||
1358 IsKeyPressed(ImGuiKey_UpArrow))) {
1359 SetScrollHereY();
1360 }
1361 }
1362 EndChild();
1363
1364 if (is_touch) {
1365 PopStyleVar();
1366 }
1367 }
1368 End();
1369
1370 // Escape or close button
1371 if (!show || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
1372 show_panel_finder_ = false;
1373 panel_finder_query_[0] = '\0';
1374 }
1375}
1376
1379
1380 // Register panel commands
1382
1383 // Register editor switch commands
1384 command_palette_.RegisterEditorCommands([this](const std::string& category) {
1385 auto type = EditorRegistry::GetEditorTypeFromCategory(category);
1386 if (type != EditorType::kSettings && editor_manager_) {
1388 }
1389 });
1390
1391 // Register layout/profile commands
1392 command_palette_.AddCommand("Apply: Minimal Layout", CommandCategory::kLayout,
1393 "Switch to essential cards only", "", [this]() {
1394 if (editor_manager_) {
1396 }
1397 });
1398
1400 "Apply: Logic Debugger Layout", CommandCategory::kLayout,
1401 "Switch to debug and development focused layout", "", [this]() {
1402 if (editor_manager_) {
1403 editor_manager_->ApplyLayoutPreset("Logic Debugger");
1404 }
1405 });
1406
1408 "Apply: Overworld Artist Layout", CommandCategory::kLayout,
1409 "Switch to visual and overworld focused layout", "", [this]() {
1410 if (editor_manager_) {
1411 editor_manager_->ApplyLayoutPreset("Overworld Artist");
1412 }
1413 });
1414
1416 "Apply: Dungeon Master Layout", CommandCategory::kLayout,
1417 "Switch to comprehensive dungeon editing layout", "", [this]() {
1418 if (editor_manager_) {
1419 editor_manager_->ApplyLayoutPreset("Dungeon Master");
1420 }
1421 });
1422
1424 "Apply: Audio Engineer Layout", CommandCategory::kLayout,
1425 "Switch to music and sound editing layout", "", [this]() {
1426 if (editor_manager_) {
1427 editor_manager_->ApplyLayoutPreset("Audio Engineer");
1428 }
1429 });
1430
1431 // Register recent files commands
1433 [this](const std::string& filepath) {
1434 if (editor_manager_) {
1435 auto status = editor_manager_->OpenRomOrProject(filepath);
1436 if (!status.ok()) {
1438 absl::StrFormat("Failed to open: %s", status.message()),
1440 }
1441 }
1442 });
1443
1444 // Dungeon navigation helpers (room jump by id/label).
1446
1447#ifdef YAZE_WITH_GRPC
1448 // Mesen2 Integration Commands
1449 auto* emu_backend = Application::Instance().GetEmulatorBackend();
1450 if (emu_backend) {
1452 "Mesen2: Connect", CommandCategory::kTools,
1453 "Auto-discover and connect to Mesen2 socket", "", [this]() {
1454 auto* backend = Application::Instance().GetEmulatorBackend();
1455 if (backend) {
1456 // How to trigger reconnect? Re-init adapter?
1457 // For now, most adapters try to connect on init.
1458 toast_manager_.Show("Mesen2 connection attempt queued",
1460 }
1461 });
1462
1464 "Mesen2: Step Over", CommandCategory::kTools,
1465 "Execute one instruction without entering subroutines", "F10",
1466 [emu_backend]() { emu_backend->StepOver(); });
1467
1469 "Run until return from current subroutine",
1470 "Shift+F11",
1471 [emu_backend]() { emu_backend->StepOut(); });
1472
1474 "Mesen2: Enable Collision Overlay", CommandCategory::kTools,
1475 "Show collision mask in Mesen2", "",
1476 [emu_backend]() { emu_backend->SetCollisionOverlay(true); });
1477
1479 "Mesen2: Disable Collision Overlay", CommandCategory::kTools,
1480 "Hide collision mask in Mesen2", "",
1481 [emu_backend]() { emu_backend->SetCollisionOverlay(false); });
1482 }
1483#endif
1484
1485 // Load command usage history
1486 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1487 if (config_dir.ok()) {
1488 std::filesystem::path history_file = *config_dir / "command_history.json";
1489 command_palette_.LoadHistory(history_file.string());
1490 }
1491
1493}
1494
1496 InitializeCommandPalette(session_id);
1497}
1498
1501 return;
1502
1503 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1504 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1505 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1506
1507 bool show_search = true;
1508 if (ImGui::Begin(
1509 absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(),
1510 &show_search, ImGuiWindowFlags_NoCollapse)) {
1511 // Enhanced search input with focus management
1512 ImGui::SetNextItemWidth(-100);
1513 if (ImGui::IsWindowAppearing()) {
1514 ImGui::SetKeyboardFocusHere();
1515 }
1516
1517 bool input_changed = ImGui::InputTextWithHint(
1518 "##global_query",
1519 absl::StrFormat("%s Search everything...", ICON_MD_SEARCH).c_str(),
1521
1522 ImGui::SameLine();
1523 if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
1524 global_search_query_[0] = '\0';
1525 input_changed = true;
1526 }
1527
1528 ImGui::Separator();
1529
1530 // Tabbed search results for better organization
1531 if (gui::BeginThemedTabBar("SearchResultTabs")) {
1532 // Recent Files Tab
1533 if (ImGui::BeginTabItem(
1534 absl::StrFormat("%s Recent Files", ICON_MD_HISTORY).c_str())) {
1536 auto recent_files = manager.GetRecentFiles();
1537
1538 if (ImGui::BeginTable("RecentFilesTable", 3,
1539 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1540 ImGuiTableFlags_SizingStretchProp)) {
1541 ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch,
1542 0.6f);
1543 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1544 80.0f);
1545 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1546 100.0f);
1547 ImGui::TableHeadersRow();
1548
1549 for (const auto& file : recent_files) {
1550 if (global_search_query_[0] != '\0' &&
1551 file.find(global_search_query_) == std::string::npos)
1552 continue;
1553
1554 ImGui::TableNextRow();
1555 ImGui::TableNextColumn();
1556 ImGui::Text("%s", util::GetFileName(file).c_str());
1557
1558 ImGui::TableNextColumn();
1559 std::string ext = util::GetFileExtension(file);
1560 if (ext == "sfc" || ext == "smc") {
1561 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s ROM",
1563 } else if (ext == "yaze") {
1564 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.8f, 1.0f), "%s Project",
1566 } else {
1567 ImGui::Text("%s File", ICON_MD_DESCRIPTION);
1568 }
1569
1570 ImGui::TableNextColumn();
1571 ImGui::PushID(file.c_str());
1572 if (ImGui::Button("Open")) {
1573 auto status = editor_manager_->OpenRomOrProject(file);
1574 if (!status.ok()) {
1576 absl::StrCat("Failed to open: ", status.message()),
1578 }
1580 }
1581 ImGui::PopID();
1582 }
1583
1584 ImGui::EndTable();
1585 }
1586 ImGui::EndTabItem();
1587 }
1588
1589 // Labels Tab (only if ROM is loaded)
1590 auto* current_rom = editor_manager_->GetCurrentRom();
1591 if (current_rom && current_rom->resource_label()) {
1592 if (ImGui::BeginTabItem(
1593 absl::StrFormat("%s Labels", ICON_MD_LABEL).c_str())) {
1594 auto& labels = current_rom->resource_label()->labels_;
1595
1596 if (ImGui::BeginTable("LabelsTable", 3,
1597 ImGuiTableFlags_ScrollY |
1598 ImGuiTableFlags_RowBg |
1599 ImGuiTableFlags_SizingStretchProp)) {
1600 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1601 100.0f);
1602 ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch,
1603 0.4f);
1604 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch,
1605 0.6f);
1606 ImGui::TableHeadersRow();
1607
1608 for (const auto& type_pair : labels) {
1609 for (const auto& kv : type_pair.second) {
1610 if (global_search_query_[0] != '\0' &&
1611 kv.first.find(global_search_query_) == std::string::npos &&
1612 kv.second.find(global_search_query_) == std::string::npos)
1613 continue;
1614
1615 ImGui::TableNextRow();
1616 ImGui::TableNextColumn();
1617 ImGui::Text("%s", type_pair.first.c_str());
1618
1619 ImGui::TableNextColumn();
1620 if (ImGui::Selectable(kv.first.c_str(), false,
1621 ImGuiSelectableFlags_SpanAllColumns)) {
1622 // Future: navigate to related editor/location
1623 }
1624
1625 ImGui::TableNextColumn();
1626 ImGui::TextDisabled("%s", kv.second.c_str());
1627 }
1628 }
1629
1630 ImGui::EndTable();
1631 }
1632 ImGui::EndTabItem();
1633 }
1634 }
1635
1636 // Sessions Tab
1638 if (ImGui::BeginTabItem(
1639 absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) {
1640 ImGui::Text("Search and switch between active sessions:");
1641
1642 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount();
1643 ++i) {
1644 std::string session_info =
1646 if (session_info == "[CLOSED SESSION]")
1647 continue;
1648
1649 if (global_search_query_[0] != '\0' &&
1650 session_info.find(global_search_query_) == std::string::npos)
1651 continue;
1652
1653 bool is_current =
1655 std::optional<gui::StyleColorGuard> current_guard;
1656 if (is_current) {
1657 current_guard.emplace(ImGuiCol_Text,
1658 ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
1659 }
1660
1661 if (ImGui::Selectable(absl::StrFormat("%s %s %s", ICON_MD_TAB,
1662 session_info.c_str(),
1663 is_current ? "(Current)" : "")
1664 .c_str())) {
1665 if (!is_current) {
1668 }
1669 }
1670 }
1671 ImGui::EndTabItem();
1672 }
1673 }
1674
1676 }
1677
1678 // Status bar
1679 ImGui::Separator();
1680 ImGui::Text("%s Global search across all YAZE data", ICON_MD_INFO);
1681 }
1682 ImGui::End();
1683
1684 // Update visibility state
1685 if (!show_search) {
1687 }
1688}
1689
1690// ============================================================================
1691// Startup Surface Management (Single Source of Truth)
1692// ============================================================================
1693
1696 current_startup_surface_ = surface;
1697
1698 // Log state transitions for debugging
1699 const char* surface_names[] = {"Welcome", "Dashboard", "Editor"};
1700 LOG_INFO("UICoordinator", "Startup surface: %s -> %s",
1701 surface_names[static_cast<int>(old_surface)],
1702 surface_names[static_cast<int>(surface)]);
1703
1704 // Update dependent visibility flags
1705 switch (surface) {
1707 show_welcome_screen_ = true;
1708 show_editor_selection_ = false; // Dashboard hidden
1709 // Activity Bar will be hidden (checked via ShouldShowActivityBar)
1710 break;
1712 show_welcome_screen_ = false;
1713 show_editor_selection_ = true; // Dashboard shown
1714 break;
1716 show_welcome_screen_ = false;
1717 show_editor_selection_ = false; // Dashboard hidden
1718 break;
1719 }
1720}
1721
1723 // Respect CLI overrides
1725 return false;
1726 }
1728 return true;
1729 }
1730
1731 // Default: show welcome only when in welcome state and not manually closed
1734}
1735
1737 // Respect CLI overrides
1739 return false;
1740 }
1742 return true;
1743 }
1744
1745 // Default: show dashboard only when in dashboard state
1747}
1748
1750 // Sidebar would consume the entire screen on compact (iPhone portrait)
1751 if (IsCompactLayout()) {
1752 return false;
1753 }
1754
1755 // Activity Bar hidden on cold start (welcome screen)
1756 // Only show after ROM is loaded
1758 return false;
1759 }
1760
1761 // Check if ROM is actually loaded
1762 if (editor_manager_) {
1763 auto* current_rom = editor_manager_->GetCurrentRom();
1764 if (!current_rom || !current_rom->is_loaded()) {
1765 return false;
1766 }
1767 }
1768
1769 return true;
1770}
1771
1772} // namespace editor
1773} // 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:28
project::ResourceLabelManager * resource_label()
Definition rom.h:150
auto short_name() const
Definition rom.h:147
bool is_loaded() const
Definition rom.h:132
void RegisterDungeonRoomCommands(size_t session_id)
Register dungeon room navigation commands.
void RegisterPanelCommands(PanelManager *panel_manager, size_t session_id)
Register all panel toggle commands from PanelManager.
void SaveHistory(const std::string &filepath)
Save command usage history to disk.
void Clear()
Clear all commands.
void AddCommand(const std::string &name, const std::string &category, const std::string &description, const std::string &shortcut, std::function< void()> callback)
void LoadHistory(const std::string &filepath)
Load command usage history from disk.
std::vector< CommandEntry > GetAllCommands() const
Get all registered commands.
void RecordUsage(const std::string &name)
void RegisterEditorCommands(std::function< void(const std::string &)> switch_callback)
Register all editor switch commands.
std::vector< CommandEntry > GetRecentCommands(int limit=10)
void RegisterRecentFilesCommands(std::function< void(const std::string &)> open_callback)
Register commands to open recent files.
std::vector< CommandEntry > GetFrequentCommands(int limit=10)
static int FuzzyScore(const std::string &text, const std::string &query)
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
void SwitchToSession(size_t index)
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 CreateNewProject(const std::string &template_name="Basic ROM Hack")
void ApplyLayoutPreset(const std::string &preset_name)
absl::Status LoadRom()
Load a ROM file into a new or existing session.
project::YazeProject * GetCurrentProject()
absl::Status OpenRomOrProject(const std::string &filename)
RightPanelManager * right_panel_manager()
Manages editor types, categories, and lifecycle.
static EditorType GetEditorTypeFromCategory(const std::string &category)
static std::vector< std::string > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
Central registry for all editor cards with session awareness and dependency injection.
void SetSidebarVisible(bool visible, bool notify=true)
std::vector< PanelDescriptor > GetPanelsInCategory(size_t session_id, const std::string &category) const
bool ShowPanel(size_t session_id, const std::string &base_card_id)
void MarkPanelRecentlyUsed(const std::string &card_id)
Record that a panel was used (for MRU ordering in sidebar)
bool IsPanelPinned(size_t session_id, const std::string &base_card_id) const
const std::unordered_map< std::string, PanelDescriptor > & GetAllPanelDescriptors() const
Get all panel descriptors (for layout designer, panel browser, etc.)
size_t GetActiveSessionId() const
void HideAllPanelsInCategory(size_t session_id, const std::string &category)
void Show(const char *name)
void Hide(const char *name)
Handles all project file operations with ROM-first workflow.
float GetPanelWidth() const
Get the width of the panel when expanded.
bool IsPanelExpanded() const
Check if any panel is currently expanded (or animating closed)
bool DrawPanelToggleButtons()
Draw toggle buttons for the status cluster.
bool IsPanelActive(PanelType type) const
Check if a specific panel is active.
Handles all ROM file I/O operations.
High-level orchestrator for multi-session UI.
void * GetSession(size_t index) const
std::string GetSessionDisplayName(size_t index) const
bool IsSessionClosed(size_t index) const
const std::unordered_map< std::string, Shortcut > & GetShortcuts() const
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
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)
UICoordinator(EditorManager *editor_manager, RomFileManager &rom_manager, ProjectManager &project_manager, EditorRegistry &editor_registry, PanelManager &card_registry, SessionCoordinator &session_coordinator, WindowDelegate &window_delegate, ToastManager &toast_manager, PopupManager &popup_manager, ShortcutManager &shortcut_manager)
void SetEmulatorVisible(bool visible)
void DrawNotificationBell(bool show_dirty, bool has_dirty_rom, bool show_session, bool has_multiple_sessions)
WindowDelegate & window_delegate_
bool DrawMenuBarIconButton(const char *icon, const char *tooltip, bool is_active=false)
void ShowPopup(const std::string &popup_name)
ProjectManager & project_manager_
StartupVisibility welcome_behavior_override_
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)
void SetWelcomeScreenBehavior(StartupVisibility mode)
void SetDashboardBehavior(StartupVisibility mode)
std::unique_ptr< WelcomeScreen > welcome_screen_
static float GetMenuBarIconButtonWidth()
void CenterWindow(const std::string &window_name)
Low-level window operations with minimal dependencies.
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:374
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_FOLDER_SPECIAL
Definition icons.h:815
#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_LABEL
Definition icons.h:1053
#define ICON_MD_LIST_ALT
Definition icons.h:1095
#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_CLEAR
Definition icons.h:416
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_HELP_OUTLINE
Definition icons.h:935
#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_SMART_TOY
Definition icons.h:1781
#define ICON_MD_HISTORY
Definition icons.h:946
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_INFO(category, format,...)
Definition log.h:105
Definition input.cc:22
constexpr const char * kDisplaySettings
StartupSurface
Represents the current startup surface state.
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
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 GetTextDisabledVec4()
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.
static constexpr const char * kTools
static constexpr const char * kLayout
Represents a single session, containing a ROM and its associated editors.
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:365