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/str_format.h"
10
11#ifdef __EMSCRIPTEN__
12#include <emscripten.h>
13#endif
14#include "app/editor/editor.h"
26#include "app/gui/core/icons.h"
28#include "app/gui/core/style.h"
30#include "core/project.h"
31#include "imgui/imgui.h"
32#include "util/file_util.h"
33
34namespace yaze {
35namespace editor {
36
38 EditorManager* editor_manager, RomFileManager& rom_manager,
39 ProjectManager& project_manager, EditorRegistry& editor_registry,
40 PanelManager& panel_manager, SessionCoordinator& session_coordinator,
41 WindowDelegate& window_delegate, ToastManager& toast_manager,
42 PopupManager& popup_manager, ShortcutManager& shortcut_manager)
43 : editor_manager_(editor_manager),
44 rom_manager_(rom_manager),
45 project_manager_(project_manager),
46 editor_registry_(editor_registry),
47 panel_manager_(panel_manager),
48 session_coordinator_(session_coordinator),
49 window_delegate_(window_delegate),
50 toast_manager_(toast_manager),
51 popup_manager_(popup_manager),
52 shortcut_manager_(shortcut_manager) {
53 // Initialize welcome screen with proper callbacks
54 welcome_screen_ = std::make_unique<WelcomeScreen>();
55
56 // Wire welcome screen callbacks to EditorManager
57 welcome_screen_->SetOpenRomCallback([this]() {
58#ifdef __EMSCRIPTEN__
59 // In web builds, trigger the file input element directly
60 // The file input handler in app.js will handle the file selection
61 // and call LoadRomFromWeb, which will update the ROM
62 EM_ASM({
63 var romInput = document.getElementById('rom-input');
64 if (romInput) {
65 romInput.click();
66 }
67 });
68 // Don't hide welcome screen yet - it will be hidden when ROM loads
69 // (DrawWelcomeScreen auto-transitions to Dashboard on ROM load)
70#else
71 if (editor_manager_) {
72 auto status = editor_manager_->LoadRom();
73 if (!status.ok()) {
75 absl::StrFormat("Failed to load ROM: %s", status.message()),
77 } else {
78 // Transition to Dashboard on successful ROM load
80 }
81 }
82#endif
83 });
84
85 welcome_screen_->SetNewProjectCallback([this]() {
86 if (editor_manager_) {
87 auto status = editor_manager_->CreateNewProject();
88 if (!status.ok()) {
90 absl::StrFormat("Failed to create project: %s", status.message()),
92 } else {
93 // Transition to Dashboard on successful project creation
95 }
96 }
97 });
98
99 welcome_screen_->SetOpenProjectCallback([this](const std::string& filepath) {
100 if (editor_manager_) {
101 auto status = editor_manager_->OpenRomOrProject(filepath);
102 if (!status.ok()) {
104 absl::StrFormat("Failed to open project: %s", status.message()),
106 } else {
107 // Transition to Dashboard on successful project open
109 }
110 }
111 });
112
113 welcome_screen_->SetOpenAgentCallback([this]() {
114 if (editor_manager_) {
115#ifdef YAZE_BUILD_AGENT_UI
116 editor_manager_->ShowAIAgent();
117#endif
118 // Keep welcome screen visible - user may want to do other things
119 }
120 });
121}
122
136
138 if (dashboard_behavior_override_ == mode) {
139 return;
140 }
142 if (mode == StartupVisibility::kShow) {
143 // Only transition to dashboard if we're not in welcome
146 }
147 } else if (mode == StartupVisibility::kHide) {
148 // If hiding dashboard, transition to editor state
151 }
152 }
153}
154
156 if (ImGui::GetCurrentContext()) {
157 ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
158 const ImGuiViewport* viewport = ImGui::GetMainViewport();
159
160 auto& theme_manager = gui::ThemeManager::Get();
161 auto current_theme = theme_manager.GetCurrentTheme();
162 auto& bg_renderer = gui::BackgroundRenderer::Get();
163
164 // Draw grid covering the entire main viewport
165 ImVec2 grid_pos = viewport->WorkPos;
166 ImVec2 grid_size = viewport->WorkSize;
167 bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size,
168 current_theme.primary);
169 }
170}
171
173 // Note: Theme styling is applied by ThemeManager, not here
174 // This is called from EditorManager::Update() - don't call menu bar stuff
175 // here
176
177 // Draw UI windows and dialogs
178 // Session dialogs are drawn by SessionCoordinator separately to avoid
179 // duplication
180 DrawCommandPalette(); // Ctrl+Shift+P
181 DrawGlobalSearch(); // Ctrl+Shift+K
182 DrawWorkspacePresetDialogs(); // Save/Load workspace dialogs
183 DrawLayoutPresets(); // Layout preset dialogs
184 DrawWelcomeScreen(); // Welcome screen
185 DrawProjectHelp(); // Project help
186 DrawWindowManagementUI(); // Window management
187
188 // Draw popups and toasts
191}
192
193// =============================================================================
194// Menu Bar Helpers
195// =============================================================================
196
197bool UICoordinator::DrawMenuBarIconButton(const char* icon, const char* tooltip,
198 bool is_active) {
199 // Push consistent button styling
200 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
201 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
203 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
205
206 // Active state uses primary color, inactive uses secondary text
207 if (is_active) {
208 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetPrimaryVec4());
209 } else {
210 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextSecondaryVec4());
211 }
212
213 bool clicked = ImGui::SmallButton(icon);
214
215 ImGui::PopStyleColor(4);
216
217 if (tooltip && ImGui::IsItemHovered()) {
218 ImGui::SetTooltip("%s", tooltip);
219 }
220
221 return clicked;
222}
223
225 // SmallButton width = text width + frame padding * 2
226 const float frame_padding = ImGui::GetStyle().FramePadding.x;
227 // Use a standard icon width (Material Design icons are uniform)
228 const float icon_width = ImGui::CalcTextSize(ICON_MD_SETTINGS).x;
229 return icon_width + frame_padding * 2.0f;
230}
231
233 // Right-aligned status cluster: Version, dirty indicator, session, bell, panel toggles
234 // Panel toggles are positioned using SCREEN coordinates (from viewport) so they
235 // stay fixed even when the dockspace resizes due to panel open/close.
236 //
237 // Layout: [v0.x.x][●][📄▾][🔔] [panels][⬆]
238 // ^^^ shifts with dockspace ^^^ ^^^ fixed screen position ^^^
239
240 auto* current_rom = editor_manager_->GetCurrentRom();
241 const std::string full_version =
242 absl::StrFormat("v%s", editor_manager_->version().c_str());
243
244 const float item_spacing = 6.0f;
245 const float button_width = GetMenuBarIconButtonWidth();
246 const float padding = 8.0f;
247
248 // Get TRUE viewport dimensions (not affected by dockspace resize)
249 const ImGuiViewport* viewport = ImGui::GetMainViewport();
250 const float true_viewport_right = viewport->WorkPos.x + viewport->WorkSize.x;
251
252 // Calculate panel toggle region width
253 // Buttons: Project, Agent (GRPC only), Help, Settings, Properties
254 int panel_button_count = 0;
256#ifdef YAZE_WITH_GRPC
257 panel_button_count = 5; // Project, Agent, Help, Settings, Properties
258#else
259 panel_button_count = 4; // Project, Help, Settings, Properties
260#endif
261 }
262
263 float panel_region_width = 0.0f;
264 if (panel_button_count > 0) {
265 panel_region_width = (button_width * panel_button_count) +
266 (item_spacing * (panel_button_count - 1)) + padding;
267 }
268#ifdef __EMSCRIPTEN__
269 panel_region_width += button_width + item_spacing; // WASM toggle
270#endif
271
272 // Calculate screen X position for panel toggles (fixed at viewport right edge)
273 float panel_screen_x = true_viewport_right - panel_region_width;
276 panel_screen_x -= editor_manager_->right_panel_manager()->GetPanelWidth();
277 }
278
279 // Calculate available space for status cluster (version, dirty, session, bell)
280 // This ends where the panel toggle region begins
281 const float window_width = ImGui::GetWindowWidth();
282 const float window_screen_x = ImGui::GetWindowPos().x;
283 const float menu_items_end = ImGui::GetCursorPosX() + 16.0f;
284
285 // Convert panel screen X to window-local coordinates for space calculation
286 float panel_local_x = panel_screen_x - window_screen_x;
287 float region_end = std::min(window_width - padding, panel_local_x - item_spacing);
288
289 // Calculate what elements to show - progressive hiding when space is tight
290 bool has_dirty_rom = current_rom && current_rom->is_loaded() && current_rom->dirty();
291 bool has_multiple_sessions = session_coordinator_.HasMultipleSessions();
292
293 float version_width = ImGui::CalcTextSize(full_version.c_str()).x;
294 float dirty_width = ImGui::CalcTextSize(ICON_MD_FIBER_MANUAL_RECORD).x + item_spacing;
295 float session_width = button_width;
296
297 const float available_width = region_end - menu_items_end - padding;
298
299 // Minimum required width: just the bell (always visible)
300 float required_width = button_width;
301
302 // Progressive show/hide based on available space
303 // Priority (highest to lowest): Bell > Dirty > Session > Version
304
305 // Try to fit version (lowest priority - hide first when tight)
306 bool show_version = (required_width + version_width + item_spacing) <= available_width;
307 if (show_version) {
308 required_width += version_width + item_spacing;
309 }
310
311 // Try to fit session button (medium priority)
312 bool show_session = has_multiple_sessions &&
313 (required_width + session_width + item_spacing) <= available_width;
314 if (show_session) {
315 required_width += session_width + item_spacing;
316 }
317
318 // Try to fit dirty indicator (high priority - only hide if extremely tight)
319 bool show_dirty = has_dirty_rom &&
320 (required_width + dirty_width) <= available_width;
321 if (show_dirty) {
322 required_width += dirty_width;
323 }
324
325 // Calculate start position (right-align within available space)
326 float start_pos = std::max(menu_items_end, region_end - required_width);
327
328 // =========================================================================
329 // DRAW STATUS CLUSTER (shifts with dockspace)
330 // =========================================================================
331 ImGui::SameLine(start_pos);
332 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(item_spacing, 0.0f));
333
334 // 1. Version - subdued gray text
335 if (show_version) {
336 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextDisabledVec4());
337 ImGui::Text("%s", full_version.c_str());
338 ImGui::PopStyleColor();
339 ImGui::SameLine();
340 }
341
342 // 2. Dirty badge - warning color dot
343 if (show_dirty) {
344 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
345 ImGui::PushStyleColor(ImGuiCol_Text,
346 gui::ConvertColorToImVec4(theme.warning));
347 ImGui::Text(ICON_MD_FIBER_MANUAL_RECORD);
348 ImGui::PopStyleColor();
349 if (ImGui::IsItemHovered()) {
350 ImGui::SetTooltip("Unsaved changes: %s",
351 current_rom->short_name().c_str());
352 }
353 ImGui::SameLine();
354 }
355
356 // 3. Session button - layers icon
357 if (show_session) {
359 ImGui::SameLine();
360 }
361
362 // 4. Notification bell (pass visibility flags for enhanced tooltip)
363 DrawNotificationBell(show_dirty, has_dirty_rom, show_session, has_multiple_sessions);
364
365 // =========================================================================
366 // DRAW PANEL TOGGLES (fixed screen position, unaffected by dockspace resize)
367 // =========================================================================
368 if (panel_button_count > 0) {
369 // Get current Y position within menu bar
370 float menu_bar_y = ImGui::GetCursorScreenPos().y;
371
372 // Position at fixed screen coordinates
373 ImGui::SetCursorScreenPos(ImVec2(panel_screen_x, menu_bar_y));
374
375 // Draw panel toggle buttons
377 }
378
379#ifdef __EMSCRIPTEN__
380 // WASM toggle button - also at fixed position
381 ImGui::SameLine();
383 "Hide menu bar (Alt to restore)")) {
384 show_menu_bar_ = false;
385 }
386#endif
387
388 ImGui::PopStyleVar(); // ItemSpacing
389}
390
392 // Only draw when menu bar is hidden (primarily for WASM builds)
393 if (show_menu_bar_) {
394 return;
395 }
396
397 // Small floating button in top-left corner to restore menu bar
398 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
399 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
400 ImGuiWindowFlags_NoScrollbar |
401 ImGuiWindowFlags_NoCollapse |
402 ImGuiWindowFlags_AlwaysAutoResize |
403 ImGuiWindowFlags_NoBackground |
404 ImGuiWindowFlags_NoSavedSettings;
405
406 ImGui::SetNextWindowPos(ImVec2(8, 8));
407 ImGui::SetNextWindowBgAlpha(0.7f);
408
409 if (ImGui::Begin("##MenuBarRestore", nullptr, flags)) {
410 ImGui::PushStyleColor(ImGuiCol_Button, gui::GetSurfaceContainerVec4());
411 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
413 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
415 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetPrimaryVec4());
416
417 if (ImGui::Button(ICON_MD_FULLSCREEN_EXIT, ImVec2(32, 32))) {
418 show_menu_bar_ = true;
419 }
420
421 ImGui::PopStyleColor(4);
422
423 if (ImGui::IsItemHovered()) {
424 ImGui::SetTooltip("Show menu bar (Alt)");
425 }
426 }
427 ImGui::End();
428
429 // Also check for Alt key to restore menu bar
430 if (ImGui::IsKeyPressed(ImGuiKey_LeftAlt) ||
431 ImGui::IsKeyPressed(ImGuiKey_RightAlt)) {
432 show_menu_bar_ = true;
433 }
434}
435
436void UICoordinator::DrawNotificationBell(bool show_dirty, bool has_dirty_rom,
437 bool show_session, bool has_multiple_sessions) {
438 size_t unread = toast_manager_.GetUnreadCount();
439 auto* current_rom = editor_manager_->GetCurrentRom();
440 auto* right_panel = editor_manager_->right_panel_manager();
441
442 // Check if notifications panel is active
443 bool is_active = right_panel &&
445
446 // Bell icon with accent color when there are unread notifications or panel is active
447 if (unread > 0 || is_active) {
448 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetPrimaryVec4());
449 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
450 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4());
451 ImGui::PushStyleColor(ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4());
452 } else {
453 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextSecondaryVec4());
454 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
455 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4());
456 ImGui::PushStyleColor(ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4());
457 }
458
459 // Bell button - opens notifications panel in right sidebar
460 if (ImGui::SmallButton(ICON_MD_NOTIFICATIONS)) {
461 if (right_panel) {
462 right_panel->TogglePanel(RightPanelManager::PanelType::kNotifications);
464 }
465 }
466
467 ImGui::PopStyleColor(4);
468
469 // Enhanced tooltip showing notifications + hidden status items
470 if (ImGui::IsItemHovered()) {
471 ImGui::BeginTooltip();
472
473 // Notifications
474 if (unread > 0) {
475 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetPrimaryVec4());
476 ImGui::Text("%s %zu new notification%s", ICON_MD_NOTIFICATIONS,
477 unread, unread == 1 ? "" : "s");
478 ImGui::PopStyleColor();
479 } else {
480 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextSecondaryVec4());
481 ImGui::Text(ICON_MD_NOTIFICATIONS " No new notifications");
482 ImGui::PopStyleColor();
483 }
484
485 ImGui::TextDisabled("Click to open Notifications panel");
486
487 // Show hidden status items if any
488 if (!show_dirty && has_dirty_rom) {
489 ImGui::Separator();
490 ImGui::PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(
491 gui::ThemeManager::Get().GetCurrentTheme().warning));
492 ImGui::Text(ICON_MD_FIBER_MANUAL_RECORD " Unsaved changes: %s",
493 current_rom->short_name().c_str());
494 ImGui::PopStyleColor();
495 }
496
497 if (!show_session && has_multiple_sessions) {
498 if (!show_dirty && has_dirty_rom) {
499 // Already had a separator
500 } else {
501 ImGui::Separator();
502 }
503 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextSecondaryVec4());
504 ImGui::Text(ICON_MD_LAYERS " %zu sessions active",
506 ImGui::PopStyleColor();
507 }
508
509 ImGui::EndTooltip();
510 }
511}
512
514 auto* current_rom = editor_manager_->GetCurrentRom();
515
516 // Consistent button styling with other menubar buttons
517 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
518 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
520 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
522 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetTextSecondaryVec4());
523
524 // Store button position for popup anchoring
525 ImVec2 button_min = ImGui::GetCursorScreenPos();
526
527 if (ImGui::SmallButton(ICON_MD_LAYERS)) {
528 ImGui::OpenPopup("##SessionSwitcherPopup");
529 }
530
531 ImVec2 button_max = ImGui::GetItemRectMax();
532
533 ImGui::PopStyleColor(4);
534
535 if (ImGui::IsItemHovered()) {
536 std::string tooltip = current_rom && current_rom->is_loaded()
537 ? current_rom->short_name()
538 : "No ROM loaded";
539 ImGui::SetTooltip("%s\n%zu sessions open (Ctrl+Tab)", tooltip.c_str(),
541 }
542
543 // Anchor popup to right edge - position so right edge aligns with button
544 const float popup_width = 250.0f;
545 const float screen_width = ImGui::GetIO().DisplaySize.x;
546 const float popup_x = std::min(button_min.x, screen_width - popup_width - 10.0f);
547
548 ImGui::SetNextWindowPos(ImVec2(popup_x, button_max.y + 2.0f), ImGuiCond_Appearing);
549
550 // Session switcher popup
551 if (ImGui::BeginPopup("##SessionSwitcherPopup")) {
552 ImGui::Text(ICON_MD_LAYERS " Sessions");
553 ImGui::Separator();
554
555 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) {
557 continue;
558
559 auto* session = static_cast<RomSession*>(session_coordinator_.GetSession(i));
560 if (!session)
561 continue;
562
563 Rom* rom = &session->rom;
564 ImGui::PushID(static_cast<int>(i));
565
566 bool is_current = (rom == current_rom);
567 if (is_current) {
568 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetPrimaryVec4());
569 }
570
571 std::string label = rom->is_loaded()
572 ? absl::StrFormat("%s %s", ICON_MD_DESCRIPTION, rom->short_name().c_str())
573 : absl::StrFormat("%s Session %zu", ICON_MD_DESCRIPTION, i + 1);
574
575 if (ImGui::Selectable(label.c_str(), is_current)) {
577 }
578
579 if (is_current) {
580 ImGui::PopStyleColor();
581 }
582
583 ImGui::PopID();
584 }
585
586 ImGui::EndPopup();
587 }
588}
589
590// ============================================================================
591// Session UI Delegation
592// ============================================================================
593// All session-related UI is now managed by SessionCoordinator to eliminate
594// duplication. UICoordinator methods delegate to SessionCoordinator.
595
599
603
605 if (visible) {
607 } else {
609 }
610}
611
612// Emulator visibility delegates to PanelManager (single source of truth)
614 size_t session_id = session_coordinator_.GetActiveSessionIndex();
615 return panel_manager_.IsPanelVisible(session_id, "emulator.cpu_debugger");
616}
617
619 size_t session_id = session_coordinator_.GetActiveSessionIndex();
620 if (visible) {
621 panel_manager_.ShowPanel(session_id, "emulator.cpu_debugger");
622 } else {
623 panel_manager_.HidePanel(session_id, "emulator.cpu_debugger");
624 }
625}
626
627// ============================================================================
628// Layout and Window Management UI
629// ============================================================================
630
632 // TODO: [EditorManagerRefactor] Implement full layout preset UI with
633 // save/load For now, this is accessed via Window menu items that call
634 // workspace_manager directly
635}
636
638 // ============================================================================
639 // CENTRALIZED WELCOME SCREEN LOGIC (using StartupSurface state)
640 // ============================================================================
641 // Uses ShouldShowWelcome() as single source of truth
642 // Auto-transitions to Dashboard on ROM load
643 // Activity Bar hidden when welcome is visible
644 // ============================================================================
645
646 if (!editor_manager_) {
647 LOG_ERROR("UICoordinator",
648 "EditorManager is null - cannot check ROM state");
649 return;
650 }
651
652 if (!welcome_screen_) {
653 LOG_ERROR("UICoordinator", "WelcomeScreen object is null - cannot render");
654 return;
655 }
656
657 // Check ROM state and update startup surface accordingly
658 auto* current_rom = editor_manager_->GetCurrentRom();
659 bool rom_is_loaded = current_rom && current_rom->is_loaded();
660
661 // Auto-transition: ROM loaded -> Dashboard (unless manually closed)
662 if (rom_is_loaded && current_startup_surface_ == StartupSurface::kWelcome &&
665 }
666
667 // Auto-transition: ROM unloaded -> Welcome (reset to welcome state)
668 if (!rom_is_loaded && current_startup_surface_ != StartupSurface::kWelcome &&
671 }
672
673 // Use centralized visibility check
674 if (!ShouldShowWelcome()) {
675 return;
676 }
677
678 // Reset first show flag to override ImGui ini state
679 welcome_screen_->ResetFirstShow();
680
681 // Update recent projects before showing
682 welcome_screen_->RefreshRecentProjects();
683
684 // Pass layout offsets so welcome screen centers within dockspace region
685 // Note: Activity Bar is hidden when welcome is shown, so left_offset = 0
686 float left_offset = ShouldShowActivityBar() ? editor_manager_->GetLeftLayoutOffset() : 0.0f;
687 float right_offset = editor_manager_->GetRightLayoutOffset();
688 welcome_screen_->SetLayoutOffsets(left_offset, right_offset);
689
690 // Show the welcome screen window
691 bool is_open = true;
692 welcome_screen_->Show(&is_open);
693
694 // If user closed it via X button, respect that and transition to appropriate state
695 if (!is_open) {
697 // Transition to Dashboard if ROM loaded, stay in Editor state otherwise
698 if (rom_is_loaded) {
700 }
701 }
702}
703
705 // TODO: [EditorManagerRefactor] Implement project help dialog
706 // Show context-sensitive help based on current editor and ROM state
707}
708
711 ImGui::Begin("Save Workspace Preset", &show_save_workspace_preset_,
712 ImGuiWindowFlags_AlwaysAutoResize);
713 static char preset_name[128] = "";
714 ImGui::InputText("Name", preset_name, IM_ARRAYSIZE(preset_name));
715 if (ImGui::Button("Save", gui::kDefaultModalSize)) {
716 if (strlen(preset_name) > 0) {
720 preset_name[0] = '\0';
721 }
722 }
723 ImGui::SameLine();
724 if (ImGui::Button("Cancel", gui::kDefaultModalSize)) {
726 preset_name[0] = '\0';
727 }
728 ImGui::End();
729 }
730
732 ImGui::Begin("Load Workspace Preset", &show_load_workspace_preset_,
733 ImGuiWindowFlags_AlwaysAutoResize);
734
735 // Lazy load workspace presets when UI is accessed
737
738 if (auto* workspace_manager = editor_manager_->workspace_manager()) {
739 for (const auto& name : workspace_manager->workspace_presets()) {
740 if (ImGui::Selectable(name.c_str())) {
744 }
745 }
746 if (workspace_manager->workspace_presets().empty())
747 ImGui::Text("No presets found");
748 }
749 ImGui::End();
750 }
751}
752
754 // TODO: [EditorManagerRefactor] Implement window management dialog
755 // Provide UI for toggling window visibility, managing docking, etc.
756}
757
759 // Draw all registered popups
761}
762
763void UICoordinator::ShowPopup(const std::string& popup_name) {
764 popup_manager_.Show(popup_name.c_str());
765}
766
767void UICoordinator::HidePopup(const std::string& popup_name) {
768 popup_manager_.Hide(popup_name.c_str());
769}
770
772 // Display Settings is now a popup managed by PopupManager
773 // Delegate directly to PopupManager instead of UICoordinator
775}
776
778 if (!editor_manager_)
779 return;
780
781 auto* current_editor = editor_manager_->GetCurrentEditor();
782 if (!current_editor)
783 return;
784
785 std::string category =
786 editor_registry_.GetEditorCategory(current_editor->type());
787 size_t session_id = session_coordinator_.GetActiveSessionIndex();
788 panel_manager_.HideAllPanelsInCategory(session_id, category);
789
790 LOG_INFO("UICoordinator", "Hid all panels in category: %s", category.c_str());
791}
792
793// ============================================================================
794// Sidebar Visibility (delegates to PanelManager)
795// ============================================================================
796
800
804
808
812
816
817
818// Material Design component helpers
819void UICoordinator::DrawMaterialButton(const std::string& text,
820 const std::string& icon,
821 const ImVec4& color,
822 std::function<void()> callback,
823 bool enabled) {
824 if (!enabled) {
825 ImGui::PushStyleColor(ImGuiCol_Button,
827 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetOnSurfaceVariantVec4());
828 }
829
830 std::string button_text =
831 absl::StrFormat("%s %s", icon.c_str(), text.c_str());
832 if (ImGui::Button(button_text.c_str())) {
833 if (enabled && callback) {
834 callback();
835 }
836 }
837
838 if (!enabled) {
839 ImGui::PopStyleColor(2);
840 }
841}
842
843// Layout and positioning helpers
844void UICoordinator::CenterWindow(const std::string& window_name) {
845 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
846 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
847}
848
849void UICoordinator::PositionWindow(const std::string& window_name, float x,
850 float y) {
851 ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Appearing);
852}
853
854void UICoordinator::SetWindowSize(const std::string& window_name, float width,
855 float height) {
856 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_FirstUseEver);
857}
858
859
862 return;
863
864 using namespace ImGui;
865 auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
866
867 SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing,
868 ImVec2(0.5f, 0.5f));
869 SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
870
871 bool show_palette = true;
872 if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(),
873 &show_palette, ImGuiWindowFlags_NoCollapse)) {
874 // Search input with focus management
875 SetNextItemWidth(-100);
876 if (IsWindowAppearing()) {
877 SetKeyboardFocusHere();
879 }
880
881 bool input_changed = InputTextWithHint(
882 "##cmd_query",
883 absl::StrFormat("%s Search commands (fuzzy matching enabled)...",
885 .c_str(),
887
888 SameLine();
889 if (Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
890 command_palette_query_[0] = '\0';
891 input_changed = true;
893 }
894
895 Separator();
896
897 // Fuzzy filter commands with scoring
898 std::vector<std::pair<int, std::pair<std::string, std::string>>>
899 scored_commands;
900 std::string query_lower = command_palette_query_;
901 std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(),
902 ::tolower);
903
904 for (const auto& entry : shortcut_manager_.GetShortcuts()) {
905 const auto& name = entry.first;
906 const auto& shortcut = entry.second;
907
908 std::string name_lower = name;
909 std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(),
910 ::tolower);
911
912 int score = 0;
913 if (command_palette_query_[0] == '\0') {
914 score = 1; // Show all when no query
915 } else if (name_lower.find(query_lower) == 0) {
916 score = 1000; // Starts with
917 } else if (name_lower.find(query_lower) != std::string::npos) {
918 score = 500; // Contains
919 } else {
920 // Fuzzy match - characters in order
921 size_t text_idx = 0, query_idx = 0;
922 while (text_idx < name_lower.length() &&
923 query_idx < query_lower.length()) {
924 if (name_lower[text_idx] == query_lower[query_idx]) {
925 score += 10;
926 query_idx++;
927 }
928 text_idx++;
929 }
930 if (query_idx != query_lower.length())
931 score = 0;
932 }
933
934 if (score > 0) {
935 std::string shortcut_text =
936 shortcut.keys.empty()
937 ? ""
938 : absl::StrFormat("(%s)", PrintShortcut(shortcut.keys).c_str());
939 scored_commands.push_back({score, {name, shortcut_text}});
940 }
941 }
942
943 std::sort(scored_commands.begin(), scored_commands.end(),
944 [](const auto& a, const auto& b) { return a.first > b.first; });
945
946 // Display results with categories
947 if (BeginTabBar("CommandCategories")) {
948 if (BeginTabItem(
949 absl::StrFormat("%s All Commands", ICON_MD_LIST).c_str())) {
951 "CommandPaletteTable", 3,
952 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
953 ImGuiTableFlags_SizingStretchProp,
954 ImVec2(0, -30))) {
955 TableSetupColumn("Command", ImGuiTableColumnFlags_WidthStretch, 0.5f);
956 TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch,
957 0.3f);
958 TableSetupColumn("Score", ImGuiTableColumnFlags_WidthStretch, 0.2f);
959 TableHeadersRow();
960
961 for (size_t i = 0; i < scored_commands.size(); ++i) {
962 const auto& [score, cmd_pair] = scored_commands[i];
963 const auto& [command_name, shortcut_text] = cmd_pair;
964
965 TableNextRow();
966 TableNextColumn();
967
968 PushID(static_cast<int>(i));
969 bool is_selected =
970 (static_cast<int>(i) == command_palette_selected_idx_);
971 if (Selectable(command_name.c_str(), is_selected,
972 ImGuiSelectableFlags_SpanAllColumns)) {
974 const auto& shortcuts = shortcut_manager_.GetShortcuts();
975 auto it = shortcuts.find(command_name);
976 if (it != shortcuts.end() && it->second.callback) {
977 it->second.callback();
978 show_command_palette_ = false;
979 }
980 }
981 PopID();
982
983 TableNextColumn();
984 PushStyleColor(ImGuiCol_Text,
985 gui::ConvertColorToImVec4(theme.text_secondary));
986 Text("%s", shortcut_text.c_str());
987 PopStyleColor();
988
989 TableNextColumn();
990 if (score > 0) {
991 PushStyleColor(ImGuiCol_Text,
992 gui::ConvertColorToImVec4(theme.text_disabled));
993 Text("%d", score);
994 PopStyleColor();
995 }
996 }
997
999 }
1000 EndTabItem();
1001 }
1002
1003 if (BeginTabItem(absl::StrFormat("%s Recent", ICON_MD_HISTORY).c_str())) {
1004 Text("Recent commands coming soon...");
1005 EndTabItem();
1006 }
1007
1008 if (BeginTabItem(absl::StrFormat("%s Frequent", ICON_MD_STAR).c_str())) {
1009 Text("Frequent commands coming soon...");
1010 EndTabItem();
1011 }
1012
1013 EndTabBar();
1014 }
1015
1016 // Status bar with tips
1017 Separator();
1018 Text("%s %zu commands | Score: fuzzy match", ICON_MD_INFO,
1019 scored_commands.size());
1020 SameLine();
1021 PushStyleColor(ImGuiCol_Text,
1022 gui::ConvertColorToImVec4(theme.text_disabled));
1023 Text("| ↑↓=Navigate | Enter=Execute | Esc=Close");
1024 PopStyleColor();
1025 }
1026 End();
1027
1028 // Update visibility state
1029 if (!show_palette) {
1030 show_command_palette_ = false;
1031 }
1032}
1033
1036 return;
1037
1038 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1039 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1040 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1041
1042 bool show_search = true;
1043 if (ImGui::Begin(
1044 absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(),
1045 &show_search, ImGuiWindowFlags_NoCollapse)) {
1046 // Enhanced search input with focus management
1047 ImGui::SetNextItemWidth(-100);
1048 if (ImGui::IsWindowAppearing()) {
1049 ImGui::SetKeyboardFocusHere();
1050 }
1051
1052 bool input_changed = ImGui::InputTextWithHint(
1053 "##global_query",
1054 absl::StrFormat("%s Search everything...", ICON_MD_SEARCH).c_str(),
1056
1057 ImGui::SameLine();
1058 if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
1059 global_search_query_[0] = '\0';
1060 input_changed = true;
1061 }
1062
1063 ImGui::Separator();
1064
1065 // Tabbed search results for better organization
1066 if (ImGui::BeginTabBar("SearchResultTabs")) {
1067 // Recent Files Tab
1068 if (ImGui::BeginTabItem(
1069 absl::StrFormat("%s Recent Files", ICON_MD_HISTORY).c_str())) {
1071 auto recent_files = manager.GetRecentFiles();
1072
1073 if (ImGui::BeginTable("RecentFilesTable", 3,
1074 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1075 ImGuiTableFlags_SizingStretchProp)) {
1076 ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch,
1077 0.6f);
1078 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1079 80.0f);
1080 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1081 100.0f);
1082 ImGui::TableHeadersRow();
1083
1084 for (const auto& file : recent_files) {
1085 if (global_search_query_[0] != '\0' &&
1086 file.find(global_search_query_) == std::string::npos)
1087 continue;
1088
1089 ImGui::TableNextRow();
1090 ImGui::TableNextColumn();
1091 ImGui::Text("%s", util::GetFileName(file).c_str());
1092
1093 ImGui::TableNextColumn();
1094 std::string ext = util::GetFileExtension(file);
1095 if (ext == "sfc" || ext == "smc") {
1096 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s ROM",
1098 } else if (ext == "yaze") {
1099 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.8f, 1.0f), "%s Project",
1101 } else {
1102 ImGui::Text("%s File", ICON_MD_DESCRIPTION);
1103 }
1104
1105 ImGui::TableNextColumn();
1106 ImGui::PushID(file.c_str());
1107 if (ImGui::Button("Open")) {
1108 auto status = editor_manager_->OpenRomOrProject(file);
1109 if (!status.ok()) {
1111 absl::StrCat("Failed to open: ", status.message()),
1113 }
1115 }
1116 ImGui::PopID();
1117 }
1118
1119 ImGui::EndTable();
1120 }
1121 ImGui::EndTabItem();
1122 }
1123
1124 // Labels Tab (only if ROM is loaded)
1125 auto* current_rom = editor_manager_->GetCurrentRom();
1126 if (current_rom && current_rom->resource_label()) {
1127 if (ImGui::BeginTabItem(
1128 absl::StrFormat("%s Labels", ICON_MD_LABEL).c_str())) {
1129 auto& labels = current_rom->resource_label()->labels_;
1130
1131 if (ImGui::BeginTable("LabelsTable", 3,
1132 ImGuiTableFlags_ScrollY |
1133 ImGuiTableFlags_RowBg |
1134 ImGuiTableFlags_SizingStretchProp)) {
1135 ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
1136 100.0f);
1137 ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch,
1138 0.4f);
1139 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch,
1140 0.6f);
1141 ImGui::TableHeadersRow();
1142
1143 for (const auto& type_pair : labels) {
1144 for (const auto& kv : type_pair.second) {
1145 if (global_search_query_[0] != '\0' &&
1146 kv.first.find(global_search_query_) == std::string::npos &&
1147 kv.second.find(global_search_query_) == std::string::npos)
1148 continue;
1149
1150 ImGui::TableNextRow();
1151 ImGui::TableNextColumn();
1152 ImGui::Text("%s", type_pair.first.c_str());
1153
1154 ImGui::TableNextColumn();
1155 if (ImGui::Selectable(kv.first.c_str(), false,
1156 ImGuiSelectableFlags_SpanAllColumns)) {
1157 // Future: navigate to related editor/location
1158 }
1159
1160 ImGui::TableNextColumn();
1161 ImGui::TextDisabled("%s", kv.second.c_str());
1162 }
1163 }
1164
1165 ImGui::EndTable();
1166 }
1167 ImGui::EndTabItem();
1168 }
1169 }
1170
1171 // Sessions Tab
1173 if (ImGui::BeginTabItem(
1174 absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) {
1175 ImGui::Text("Search and switch between active sessions:");
1176
1177 for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount();
1178 ++i) {
1179 std::string session_info =
1181 if (session_info == "[CLOSED SESSION]")
1182 continue;
1183
1184 if (global_search_query_[0] != '\0' &&
1185 session_info.find(global_search_query_) == std::string::npos)
1186 continue;
1187
1188 bool is_current =
1190 if (is_current) {
1191 ImGui::PushStyleColor(ImGuiCol_Text,
1192 ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
1193 }
1194
1195 if (ImGui::Selectable(absl::StrFormat("%s %s %s", ICON_MD_TAB,
1196 session_info.c_str(),
1197 is_current ? "(Current)" : "")
1198 .c_str())) {
1199 if (!is_current) {
1202 }
1203 }
1204
1205 if (is_current) {
1206 ImGui::PopStyleColor();
1207 }
1208 }
1209 ImGui::EndTabItem();
1210 }
1211 }
1212
1213 ImGui::EndTabBar();
1214 }
1215
1216 // Status bar
1217 ImGui::Separator();
1218 ImGui::Text("%s Global search across all YAZE data", ICON_MD_INFO);
1219 }
1220 ImGui::End();
1221
1222 // Update visibility state
1223 if (!show_search) {
1225 }
1226}
1227
1228// ============================================================================
1229// Startup Surface Management (Single Source of Truth)
1230// ============================================================================
1231
1234 current_startup_surface_ = surface;
1235
1236 // Log state transitions for debugging
1237 const char* surface_names[] = {"Welcome", "Dashboard", "Editor"};
1238 LOG_INFO("UICoordinator", "Startup surface: %s -> %s",
1239 surface_names[static_cast<int>(old_surface)],
1240 surface_names[static_cast<int>(surface)]);
1241
1242 // Update dependent visibility flags
1243 switch (surface) {
1245 show_welcome_screen_ = true;
1246 show_editor_selection_ = false; // Dashboard hidden
1247 // Activity Bar will be hidden (checked via ShouldShowActivityBar)
1248 break;
1250 show_welcome_screen_ = false;
1251 show_editor_selection_ = true; // Dashboard shown
1252 break;
1254 show_welcome_screen_ = false;
1255 show_editor_selection_ = false; // Dashboard hidden
1256 break;
1257 }
1258}
1259
1261 // Respect CLI overrides
1263 return false;
1264 }
1266 return true;
1267 }
1268
1269 // Default: show welcome only when in welcome state and not manually closed
1272}
1273
1275 // Respect CLI overrides
1277 return false;
1278 }
1280 return true;
1281 }
1282
1283 // Default: show dashboard only when in dashboard state
1285}
1286
1288 // Activity Bar hidden on cold start (welcome screen)
1289 // Only show after ROM is loaded
1291 return false;
1292 }
1293
1294 // Check if ROM is actually loaded
1295 if (editor_manager_) {
1296 auto* current_rom = editor_manager_->GetCurrentRom();
1297 if (!current_rom || !current_rom->is_loaded()) {
1298 return false;
1299 }
1300 }
1301
1302 return true;
1303}
1304
1305} // namespace editor
1306} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto short_name() const
Definition rom.h:143
bool is_loaded() const
Definition rom.h:128
The EditorManager controls the main editor window and manages the various editor classes.
void SaveWorkspacePreset(const std::string &name)
void SwitchToSession(size_t index)
WorkspaceManager * workspace_manager()
void LoadWorkspacePreset(const std::string &name)
absl::Status CreateNewProject(const std::string &template_name="Basic ROM Hack")
auto GetCurrentEditor() const -> Editor *
absl::Status LoadRom()
Load a ROM file into a new or existing session.
auto GetCurrentRom() const -> Rom *
absl::Status OpenRomOrProject(const std::string &filename)
RightPanelManager * right_panel_manager()
Manages editor types, categories, and lifecycle.
static std::string GetEditorCategory(EditorType type)
Central registry for all editor cards with session awareness and dependency injection.
bool ShowPanel(size_t session_id, const std::string &base_card_id)
bool IsPanelVisible(size_t session_id, const std::string &base_card_id) const
void SetSidebarVisible(bool visible)
bool HidePanel(size_t session_id, const std::string &base_card_id)
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.
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
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_
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)
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()
EditorRegistry & editor_registry_
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)
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:310
#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_LABEL
Definition icons.h:1053
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_MANAGE_SEARCH
Definition icons.h:1172
#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_TAB
Definition icons.h:1930
#define ICON_MD_FOLDER
Definition icons.h:809
#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: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:23
ImVec4 GetSurfaceContainerHighestVec4()
ImVec4 GetPrimaryVec4()
ImVec4 GetTextDisabledVec4()
ImVec4 GetTextSecondaryVec4()
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.
Represents a single session, containing a ROM and its associated editors.