10#include "absl/strings/match.h"
11#include "absl/strings/str_format.h"
14#include <emscripten.h>
41#include "imgui/imgui.h"
49 EditorManager* editor_manager, RomFileManager& rom_manager,
50 ProjectManager& project_manager, EditorRegistry& editor_registry,
51 WorkspaceWindowManager& window_manager,
52 SessionCoordinator& session_coordinator, WindowDelegate& window_delegate,
53 ToastManager& toast_manager, PopupManager& popup_manager,
54 ShortcutManager& shortcut_manager)
55 : editor_manager_(editor_manager),
56 rom_manager_(rom_manager),
57 project_manager_(project_manager),
58 editor_registry_(editor_registry),
59 window_manager_(window_manager),
60 session_coordinator_(session_coordinator),
61 window_delegate_(window_delegate),
62 toast_manager_(toast_manager),
63 popup_manager_(popup_manager),
64 shortcut_manager_(shortcut_manager) {
66 welcome_screen_ = std::make_unique<WelcomeScreen>();
69 if (editor_manager_) {
70 welcome_screen_->SetUserSettings(&editor_manager_->user_settings());
74 welcome_screen_->SetOpenRomCallback([
this]() {
80 var romInput = document.getElementById(
'rom-input');
88 if (editor_manager_) {
89 auto status = editor_manager_->LoadRom();
92 absl::StrFormat(
"Failed to load ROM: %s", status.message()),
95#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
98 SetStartupSurface(StartupSurface::kDashboard);
105 welcome_screen_->SetNewProjectCallback([
this]() {
106 if (editor_manager_) {
107 auto status = editor_manager_->CreateNewProject();
110 absl::StrFormat(
"Failed to create project: %s", status.message()),
114 SetStartupSurface(StartupSurface::kDashboard);
122 welcome_screen_->SetNewProjectWithTemplateCallback(
123 [
this](
const std::string& template_name) {
124 new_project_dialog_.Open(template_name);
127 new_project_dialog_.SetCreateCallback(
128 [
this](
const std::string& template_name,
const std::string& rom_path,
129 const std::string& project_name) {
130 if (!editor_manager_)
132 auto status = editor_manager_->CreateNewProject(template_name);
135 absl::StrFormat(
"Failed to create project: %s", status.message()),
141 auto rom_status = editor_manager_->OpenRomOrProject(rom_path);
142 if (!rom_status.ok()) {
144 absl::StrFormat(
"Project created but ROM failed to load: %s",
145 rom_status.message()),
146 ToastType::kWarning);
149 toast_manager_.Show(absl::StrFormat(
"Created project \"%s\" from %s",
150 project_name, template_name),
151 ToastType::kSuccess);
152 SetStartupSurface(StartupSurface::kDashboard);
155 welcome_screen_->SetOpenProjectCallback([
this](
const std::string& filepath) {
156 if (editor_manager_) {
157 auto status = editor_manager_->OpenRomOrProject(filepath);
160 absl::StrFormat(
"Failed to open project: %s", status.message()),
164 SetStartupSurface(StartupSurface::kDashboard);
169 welcome_screen_->SetOpenAgentCallback([
this]() {
170 if (editor_manager_) {
171#ifdef YAZE_BUILD_AGENT_UI
172 editor_manager_->ShowAIAgent();
175 SetStartupSurface(StartupSurface::kEditor);
179 welcome_screen_->SetOpenProjectDialogCallback([
this]() {
180 if (editor_manager_) {
181 auto status = editor_manager_->OpenProject();
184 absl::StrFormat(
"Failed to open project: %s", status.message()),
187 SetStartupSurface(StartupSurface::kDashboard);
192 welcome_screen_->SetOpenProjectManagementCallback([
this]() {
193 if (editor_manager_) {
194 editor_manager_->ShowProjectManagement();
195 SetStartupSurface(StartupSurface::kDashboard);
199 welcome_screen_->SetOpenProjectFileEditorCallback([
this]() {
200 if (!editor_manager_) {
203 const auto* project = editor_manager_->GetCurrentProject();
204 if (!project || project->filepath.empty()) {
205 toast_manager_.Show(
"No project file to edit", ToastType::kInfo);
208 editor_manager_->ShowProjectFileEditor();
209 SetStartupSurface(StartupSurface::kDashboard);
212 welcome_screen_->SetOpenPrototypeResearchCallback([
this]() {
213 if (!editor_manager_) {
216 editor_manager_->SwitchToEditor(EditorType::kGraphics,
true);
217 window_manager_.OpenWindow(
"graphics.prototype_viewer");
218 SetWelcomeScreenVisible(
false);
219 SetWelcomeScreenManuallyClosed(
true);
222 welcome_screen_->SetOpenAssemblyEditorNoRomCallback([
this]() {
223 if (!editor_manager_) {
226 editor_manager_->SwitchToEditor(EditorType::kAssembly,
true);
227 window_manager_.OpenWindow(
"assembly.code_editor");
228 SetWelcomeScreenVisible(
false);
229 SetWelcomeScreenManuallyClosed(
true);
233void UICoordinator::SetWelcomeScreenBehavior(StartupVisibility mode) {
234 welcome_behavior_override_ = mode;
235 if (mode == StartupVisibility::kHide) {
236 welcome_screen_manually_closed_ =
true;
238 if (current_startup_surface_ == StartupSurface::kWelcome) {
239 SetStartupSurface(StartupSurface::kDashboard);
241 }
else if (mode == StartupVisibility::kShow) {
242 welcome_screen_manually_closed_ =
false;
243 SetStartupSurface(StartupSurface::kWelcome);
247void UICoordinator::SetDashboardBehavior(StartupVisibility mode) {
248 if (dashboard_behavior_override_ == mode) {
251 dashboard_behavior_override_ = mode;
252 if (mode == StartupVisibility::kShow) {
254 if (current_startup_surface_ != StartupSurface::kWelcome) {
255 SetStartupSurface(StartupSurface::kDashboard);
257 }
else if (mode == StartupVisibility::kHide) {
259 if (current_startup_surface_ == StartupSurface::kDashboard) {
260 SetStartupSurface(StartupSurface::kEditor);
265void UICoordinator::DrawBackground() {
266 if (!ImGui::GetCurrentContext()) {
270 const ImGuiViewport* viewport = ImGui::GetMainViewport();
275 ImDrawList* bg_draw_list =
276 ImGui::GetBackgroundDrawList(
const_cast<ImGuiViewport*
>(viewport));
278 auto& theme_manager = gui::ThemeManager::Get();
279 auto current_theme = theme_manager.GetCurrentTheme();
280 auto& bg_renderer = gui::BackgroundRenderer::Get();
283 ImVec2 grid_pos = viewport->WorkPos;
284 ImVec2 grid_size = viewport->WorkSize;
285 bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size,
286 current_theme.primary);
289void UICoordinator::DrawAllUI() {
297 DrawCommandPalette();
300 DrawWorkspacePresetDialogs();
304 DrawWindowManagementUI();
306#ifdef YAZE_BUILD_AGENT_UI
307 if (show_ai_agent_) {
308 if (editor_manager_) {
309 editor_manager_->ShowAIAgent();
311 show_ai_agent_ =
false;
314 if (show_chat_history_) {
315 if (editor_manager_) {
316 editor_manager_->ShowChatHistory();
318 show_chat_history_ =
false;
321 if (show_proposal_drawer_) {
322 if (editor_manager_) {
323 if (
auto* right_panel = editor_manager_->right_drawer_manager()) {
324 right_panel->OpenDrawer(RightDrawerManager::DrawerType::kProposals);
327 show_proposal_drawer_ =
false;
333 toast_manager_.Draw();
336bool UICoordinator::IsCompactLayout()
const {
337 const ImGuiViewport* viewport = ImGui::GetMainViewport();
341 const float width = viewport->WorkSize.x;
342#if defined(__APPLE__) && TARGET_OS_IOS == 1
345 static bool compact_mode =
false;
346 constexpr float kEnterCompactWidth = 900.0f;
347 constexpr float kExitCompactWidth = 940.0f;
349 compact_mode ? (width < kExitCompactWidth) : (width < kEnterCompactWidth);
352 return width < 900.0f;
360bool UICoordinator::DrawMenuBarIconButton(
const char* icon,
const char* tooltip,
363 gui::StyleColorGuard btn_guard(
364 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
365 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
366 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
370 bool clicked = ImGui::SmallButton(icon);
372 if (tooltip && ImGui::IsItemHovered()) {
373 ImGui::SetTooltip(
"%s", tooltip);
379float UICoordinator::GetMenuBarIconButtonWidth() {
381 const float frame_padding = ImGui::GetStyle().FramePadding.x;
384 return icon_width + frame_padding * 2.0f;
387void UICoordinator::DrawMenuBarExtras() {
395 auto* current_rom = editor_manager_->GetCurrentRom();
396 const std::string full_version =
397 absl::StrFormat(
"v%s", editor_manager_->version().c_str());
399 const float item_spacing = 6.0f;
400 const float padding = 8.0f;
402 auto CalcSmallButtonWidth = [](
const char* label) ->
float {
404 const float frame_padding = ImGui::GetStyle().FramePadding.x;
405 const float text_w = ImGui::CalcTextSize(label).x;
406 return text_w + frame_padding * 2.0f;
410 const ImGuiViewport* viewport = ImGui::GetMainViewport();
411 const float true_viewport_right = viewport->WorkPos.x + viewport->WorkSize.x;
415 const bool has_panel_toggles =
416 editor_manager_->right_drawer_manager() !=
nullptr;
417 float panel_buttons_width = 0.0f;
418 if (has_panel_toggles) {
419 const char* kIcons[] = {
426 constexpr size_t kIconCount =
sizeof(kIcons) /
sizeof(kIcons[0]);
428 for (
size_t i = 0; i < kIconCount; ++i) {
429 panel_buttons_width += CalcSmallButtonWidth(kIcons[i]);
430 if (i + 1 < kIconCount) {
431 panel_buttons_width += item_spacing;
438 float panel_region_width = panel_buttons_width;
441 panel_region_width +=
446 float panel_screen_x = true_viewport_right - panel_region_width;
447 if (editor_manager_->right_drawer_manager() &&
448 editor_manager_->right_drawer_manager()->IsDrawerExpanded()) {
449 panel_screen_x -= editor_manager_->right_drawer_manager()->GetDrawerWidth();
454 const float window_width = ImGui::GetWindowWidth();
455 const float window_screen_x = ImGui::GetWindowPos().x;
456 const float menu_items_end = ImGui::GetCursorPosX() + 16.0f;
459 float panel_local_x = panel_screen_x - window_screen_x;
461 std::min(window_width - padding, panel_local_x - item_spacing);
465 current_rom && current_rom->is_loaded() && current_rom->dirty();
466 bool has_multiple_sessions = session_coordinator_.HasMultipleSessions();
468 float version_width = ImGui::CalcTextSize(full_version.c_str()).x;
473 const float available_width = region_end - menu_items_end - padding;
483 (required_width + version_width + item_spacing) <= available_width;
485 required_width += version_width + item_spacing;
490 has_multiple_sessions &&
491 (required_width + session_width + item_spacing) <= available_width;
493 required_width += session_width + item_spacing;
498 has_dirty_rom && (required_width + dirty_width) <= available_width;
500 required_width += dirty_width;
504 float start_pos = std::max(menu_items_end, region_end - required_width);
509 ImGui::SameLine(start_pos);
510 gui::StyleVarGuard item_spacing_guard(ImGuiStyleVar_ItemSpacing,
511 ImVec2(item_spacing, 0.0f));
515 gui::ColoredText(full_version.c_str(), gui::GetTextDisabledVec4());
521 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
523 gui::ConvertColorToImVec4(theme.warning));
524 if (ImGui::IsItemHovered()) {
525 ImGui::SetTooltip(
"Unsaved changes: %s",
526 current_rom->short_name().c_str());
538 DrawNotificationBell(show_dirty, has_dirty_rom, show_session,
539 has_multiple_sessions);
544 if (has_panel_toggles) {
546 float menu_bar_y = ImGui::GetCursorScreenPos().y;
549 ImGui::SetCursorScreenPos(ImVec2(panel_screen_x, menu_bar_y));
552 editor_manager_->right_drawer_manager()->DrawDrawerToggleButtons();
559 "Hide menu bar (Alt to restore)")) {
560 show_menu_bar_ =
false;
565void UICoordinator::DrawMenuBarRestoreButton() {
567 if (show_menu_bar_) {
572 ImGuiWindowFlags flags =
573 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
574 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
575 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize |
576 ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings;
578 ImGui::SetNextWindowPos(ImVec2(8, 8));
579 ImGui::SetNextWindowBgAlpha(0.7f);
581 if (ImGui::Begin(
"##MenuBarRestore",
nullptr, flags)) {
582 gui::StyleColorGuard btn_guard(
583 {{ImGuiCol_Button, gui::GetSurfaceContainerVec4()},
584 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
585 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
586 {ImGuiCol_Text, gui::GetPrimaryVec4()}});
589 show_menu_bar_ =
true;
592 if (ImGui::IsItemHovered()) {
593 ImGui::SetTooltip(
"Show menu bar (Alt)");
599 if (ImGui::IsKeyPressed(ImGuiKey_LeftAlt) ||
600 ImGui::IsKeyPressed(ImGuiKey_RightAlt)) {
601 show_menu_bar_ =
true;
605void UICoordinator::DrawNotificationBell(
bool show_dirty,
bool has_dirty_rom,
607 bool has_multiple_sessions) {
608 size_t unread = toast_manager_.GetUnreadCount();
609 auto* current_rom = editor_manager_->GetCurrentRom();
610 auto* right_panel = editor_manager_->right_drawer_manager();
614 right_panel && right_panel->IsDrawerActive(
615 RightDrawerManager::DrawerType::kNotifications);
618 ImVec4 bell_text_color = (unread > 0 || is_active)
619 ? gui::GetPrimaryVec4()
620 : gui::GetTextSecondaryVec4();
621 gui::StyleColorGuard bell_guard(
622 {{ImGuiCol_Text, bell_text_color},
623 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
624 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
625 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()}});
630 right_panel->ToggleDrawer(RightDrawerManager::DrawerType::kNotifications);
631 toast_manager_.MarkAllRead();
636 if (ImGui::IsItemHovered()) {
637 ImGui::BeginTooltip();
641 gui::ColoredTextF(gui::GetPrimaryVec4(),
"%s %zu new notification%s",
645 gui::GetTextSecondaryVec4());
648 ImGui::TextDisabled(
"Click to open Notifications panel");
651 if (!show_dirty && has_dirty_rom) {
653 gui::ColoredTextF(gui::ConvertColorToImVec4(
654 gui::ThemeManager::Get().GetCurrentTheme().warning),
656 current_rom->short_name().c_str());
659 if (!show_session && has_multiple_sessions) {
660 if (!show_dirty && has_dirty_rom) {
665 gui::ColoredTextF(gui::GetTextSecondaryVec4(),
667 session_coordinator_.GetActiveSessionCount());
674void UICoordinator::DrawSessionButton() {
675 auto* current_rom = editor_manager_->GetCurrentRom();
678 gui::StyleColorGuard session_btn_guard(
679 {{ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
680 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
681 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
682 {ImGuiCol_Text, gui::GetTextSecondaryVec4()}});
685 ImVec2 button_min = ImGui::GetCursorScreenPos();
688 ImGui::OpenPopup(
"##SessionSwitcherPopup");
691 ImVec2 button_max = ImGui::GetItemRectMax();
693 if (ImGui::IsItemHovered()) {
694 std::string tooltip = current_rom && current_rom->is_loaded()
695 ? current_rom->short_name()
697 ImGui::SetTooltip(
"%s\n%zu sessions open (Ctrl+Tab)", tooltip.c_str(),
698 session_coordinator_.GetActiveSessionCount());
702 const float popup_width = 250.0f;
703 const float screen_width = ImGui::GetIO().DisplaySize.x;
704 const float popup_x =
705 std::min(button_min.x, screen_width - popup_width - 10.0f);
707 ImGui::SetNextWindowPos(ImVec2(popup_x, button_max.y + 2.0f),
708 ImGuiCond_Appearing);
711 if (ImGui::BeginPopup(
"##SessionSwitcherPopup")) {
715 for (
size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) {
716 if (session_coordinator_.IsSessionClosed(i))
720 static_cast<RomSession*
>(session_coordinator_.GetSession(i));
724 Rom*
rom = &session->rom;
725 ImGui::PushID(
static_cast<int>(i));
727 bool is_current = (
rom == current_rom);
728 std::optional<gui::StyleColorGuard> current_guard;
730 current_guard.emplace(ImGuiCol_Text, gui::GetPrimaryVec4());
739 if (ImGui::Selectable(label.c_str(), is_current)) {
740 editor_manager_->SwitchToSession(i);
756void UICoordinator::ShowSessionSwitcher() {
757 session_coordinator_.ShowSessionSwitcher();
760bool UICoordinator::IsSessionSwitcherVisible()
const {
761 return session_coordinator_.IsSessionSwitcherVisible();
764void UICoordinator::SetSessionSwitcherVisible(
bool visible) {
766 session_coordinator_.ShowSessionSwitcher();
768 session_coordinator_.HideSessionSwitcher();
773bool UICoordinator::IsEmulatorVisible()
const {
774 size_t session_id = session_coordinator_.GetActiveSessionIndex();
775 auto emulator_windows =
776 window_manager_.GetWindowsInCategory(session_id,
"Emulator");
777 for (
const auto& window : emulator_windows) {
778 if (window.visibility_flag && *window.visibility_flag) {
785void UICoordinator::SetEmulatorVisible(
bool visible) {
786 size_t session_id = session_coordinator_.GetActiveSessionIndex();
788 auto default_windows =
789 LayoutPresets::GetDefaultWindows(EditorType::kEmulator);
790 for (
const auto& window_id : default_windows) {
791 window_manager_.OpenWindow(session_id, window_id);
794 window_manager_.HideAllWindowsInCategory(session_id,
"Emulator");
802bool UICoordinator::IsAsmEditorVisible()
const {
803 size_t session_id = session_coordinator_.GetActiveSessionIndex();
804 auto windows = window_manager_.GetWindowsInCategory(session_id,
"Assembly");
805 for (
const auto& window : windows) {
806 if (window.visibility_flag && *window.visibility_flag) {
813void UICoordinator::SetAsmEditorVisible(
bool visible) {
814 size_t session_id = session_coordinator_.GetActiveSessionIndex();
816 auto default_windows =
817 LayoutPresets::GetDefaultWindows(EditorType::kAssembly);
818 for (
const auto& window_id : default_windows) {
819 window_manager_.OpenWindow(session_id, window_id);
822 window_manager_.HideAllWindowsInCategory(session_id,
"Assembly");
830void UICoordinator::DrawLayoutPresets() {
836void UICoordinator::DrawWelcomeScreen() {
845 if (!editor_manager_) {
847 "EditorManager is null - cannot check ROM state");
851 if (!welcome_screen_) {
852 LOG_ERROR(
"UICoordinator",
"WelcomeScreen object is null - cannot render");
857 auto* current_rom = editor_manager_->GetCurrentRom();
858 bool rom_is_loaded = current_rom && current_rom->is_loaded();
861 if (rom_is_loaded && current_startup_surface_ == StartupSurface::kWelcome) {
862 if (welcome_screen_manually_closed_) {
863 SetStartupSurface(StartupSurface::kEditor);
865 SetStartupSurface(StartupSurface::kDashboard);
870 if (!rom_is_loaded && current_startup_surface_ != StartupSurface::kWelcome &&
871 !welcome_screen_manually_closed_) {
872 SetStartupSurface(StartupSurface::kWelcome);
876 if (!ShouldShowWelcome()) {
881 welcome_screen_->SetContextState(rom_is_loaded,
882 project_manager_.HasActiveProject());
886 welcome_screen_->RefreshRecentProjects();
891 ShouldShowActivityBar() ? editor_manager_->GetLeftLayoutOffset() : 0.0f;
892 float right_offset = editor_manager_->GetRightLayoutOffset();
893 welcome_screen_->SetLayoutOffsets(left_offset, right_offset);
897 welcome_screen_->Show(&is_open);
902 new_project_dialog_.Draw();
906 welcome_screen_manually_closed_ =
true;
909 SetStartupSurface(StartupSurface::kDashboard);
914void UICoordinator::DrawProjectHelp() {
919void UICoordinator::DrawWorkspacePresetDialogs() {
920 if (show_save_workspace_preset_) {
921 ImGui::Begin(
"Save Workspace Preset", &show_save_workspace_preset_,
922 ImGuiWindowFlags_AlwaysAutoResize);
923 static char preset_name[128] =
"";
924 ImGui::InputText(
"Name", preset_name, IM_ARRAYSIZE(preset_name));
925 if (ImGui::Button(
"Save", gui::kDefaultModalSize)) {
926 if (strlen(preset_name) > 0) {
927 editor_manager_->SaveWorkspacePreset(preset_name);
928 toast_manager_.Show(
"Preset saved", editor::ToastType::kSuccess);
929 show_save_workspace_preset_ =
false;
930 preset_name[0] =
'\0';
934 if (ImGui::Button(
"Cancel", gui::kDefaultModalSize)) {
935 show_save_workspace_preset_ =
false;
936 preset_name[0] =
'\0';
941 if (show_load_workspace_preset_) {
942 ImGui::Begin(
"Load Workspace Preset", &show_load_workspace_preset_,
943 ImGuiWindowFlags_AlwaysAutoResize);
946 editor_manager_->RefreshWorkspacePresets();
948 if (
auto* workspace_manager = editor_manager_->workspace_manager()) {
949 for (
const auto& name : workspace_manager->workspace_presets()) {
950 if (ImGui::Selectable(
name.c_str())) {
951 editor_manager_->LoadWorkspacePreset(name);
952 toast_manager_.Show(
"Preset loaded", editor::ToastType::kSuccess);
953 show_load_workspace_preset_ =
false;
956 if (workspace_manager->workspace_presets().empty())
957 ImGui::Text(
"No presets found");
963void UICoordinator::DrawWindowManagementUI() {
968void UICoordinator::DrawAllPopups() {
970 popup_manager_.DrawPopups();
973void UICoordinator::ShowPopup(
const std::string& popup_name) {
974 popup_manager_.Show(popup_name.c_str());
977void UICoordinator::HidePopup(
const std::string& popup_name) {
978 popup_manager_.Hide(popup_name.c_str());
981void UICoordinator::ShowDisplaySettings() {
984 popup_manager_.Show(PopupID::kDisplaySettings);
991void UICoordinator::TogglePanelSidebar() {
992 window_manager_.ToggleSidebarVisibility();
995bool UICoordinator::IsPanelSidebarVisible()
const {
996 return window_manager_.IsSidebarVisible();
999void UICoordinator::SetPanelSidebarVisible(
bool visible) {
1000 window_manager_.SetSidebarVisible(visible);
1003void UICoordinator::ShowAllWindows() {
1004 window_delegate_.ShowAllWindows();
1007void UICoordinator::HideAllWindows() {
1008 window_delegate_.HideAllWindows();
1012void UICoordinator::DrawMaterialButton(
const std::string& text,
1013 const std::string& icon,
1014 const ImVec4& color,
1015 std::function<
void()> callback,
1017 std::optional<gui::StyleColorGuard> disabled_guard;
1019 disabled_guard.emplace(std::initializer_list<gui::StyleColorGuard::Entry>{
1020 {ImGuiCol_Button, gui::GetSurfaceContainerHighestVec4()},
1021 {ImGuiCol_Text, gui::GetOnSurfaceVariantVec4()}});
1024 std::string button_text =
1025 absl::StrFormat(
"%s %s", icon.c_str(), text.c_str());
1026 if (ImGui::Button(button_text.c_str())) {
1027 if (enabled && callback) {
1034void UICoordinator::CenterWindow(
const std::string& window_name) {
1035 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1036 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1039void UICoordinator::PositionWindow(
const std::string& window_name,
float x,
1041 ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Appearing);
1044void UICoordinator::SetWindowSize(
const std::string& window_name,
float width,
1046 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_FirstUseEver);
1049void UICoordinator::DrawCommandPalette() {
1050 if (!show_command_palette_)
1054 if (!command_palette_initialized_) {
1055 InitializeCommandPalette(0);
1058 using namespace ImGui;
1059 auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1061 SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing,
1062 ImVec2(0.5f, 0.5f));
1063 SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1065 bool show_palette =
true;
1066 if (Begin(absl::StrFormat(
"%s Command Palette",
ICON_MD_SEARCH).c_str(),
1067 &show_palette, ImGuiWindowFlags_NoCollapse)) {
1069 SetNextItemWidth(-100);
1070 if (IsWindowAppearing()) {
1071 SetKeyboardFocusHere();
1072 command_palette_selected_idx_ = 0;
1075 bool input_changed = InputTextWithHint(
1077 absl::StrFormat(
"%s Search commands (fuzzy matching enabled)...",
1080 command_palette_query_, IM_ARRAYSIZE(command_palette_query_));
1083 if (Button(absl::StrFormat(
"%s Clear",
ICON_MD_CLEAR).c_str())) {
1084 command_palette_query_[0] =
'\0';
1085 input_changed =
true;
1086 command_palette_selected_idx_ = 0;
1092 struct ScoredCommand {
1096 std::string shortcut;
1097 std::function<void()> callback;
1099 std::vector<ScoredCommand> scored_commands;
1101 std::string query_lower = command_palette_query_;
1102 std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(),
1105 auto score_text = [&query_lower](
const std::string& text) ->
int {
1106 std::string text_lower = text;
1107 std::transform(text_lower.begin(), text_lower.end(), text_lower.begin(),
1110 if (query_lower.empty())
1112 if (text_lower.find(query_lower) == 0)
1114 if (text_lower.find(query_lower) != std::string::npos)
1118 size_t text_idx = 0, query_idx = 0;
1120 while (text_idx < text_lower.length() &&
1121 query_idx < query_lower.length()) {
1122 if (text_lower[text_idx] == query_lower[query_idx]) {
1128 return (query_idx == query_lower.length()) ? score : 0;
1132 for (
const auto& [name, shortcut] : shortcut_manager_.GetShortcuts()) {
1133 int score = score_text(name);
1135 std::string shortcut_text =
1136 shortcut.keys.empty()
1138 : absl::StrFormat(
"(%s)",
PrintShortcut(shortcut.keys).c_str());
1139 scored_commands.push_back(
1140 {score,
name,
"Shortcuts", shortcut_text, shortcut.callback});
1145 for (
const auto& entry : command_palette_.GetAllCommands()) {
1146 int score = score_text(entry.name);
1148 score += score_text(entry.category) / 2;
1149 score += score_text(entry.description) / 4;
1152 scored_commands.push_back({score, entry.name, entry.category,
1153 entry.shortcut, entry.callback});
1158 std::sort(scored_commands.begin(), scored_commands.end(),
1159 [](
const auto& a,
const auto& b) { return a.score > b.score; });
1162 if (gui::BeginThemedTabBar(
"CommandCategories")) {
1164 absl::StrFormat(
"%s All Commands",
ICON_MD_LIST).c_str())) {
1165 if (gui::LayoutHelpers::BeginTableWithTheming(
1166 "CommandPaletteTable", 4,
1167 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1168 ImGuiTableFlags_SizingStretchProp,
1170 TableSetupColumn(
"Command", ImGuiTableColumnFlags_WidthStretch,
1172 TableSetupColumn(
"Category", ImGuiTableColumnFlags_WidthStretch,
1174 TableSetupColumn(
"Shortcut", ImGuiTableColumnFlags_WidthStretch,
1176 TableSetupColumn(
"Score", ImGuiTableColumnFlags_WidthStretch, 0.15f);
1179 for (
size_t i = 0; i < scored_commands.size(); ++i) {
1180 const auto& cmd = scored_commands[i];
1185 PushID(
static_cast<int>(i));
1187 (
static_cast<int>(i) == command_palette_selected_idx_);
1188 if (Selectable(cmd.name.c_str(), is_selected,
1189 ImGuiSelectableFlags_SpanAllColumns)) {
1190 command_palette_selected_idx_ = i;
1193 show_command_palette_ =
false;
1195 command_palette_.RecordUsage(cmd.name);
1201 gui::ColoredText(cmd.category.c_str(),
1202 gui::ConvertColorToImVec4(theme.text_secondary));
1205 gui::ColoredText(cmd.shortcut.c_str(),
1206 gui::ConvertColorToImVec4(theme.text_secondary));
1209 gui::ColoredTextF(gui::ConvertColorToImVec4(theme.text_disabled),
1213 gui::LayoutHelpers::EndTableWithTheming();
1218 if (BeginTabItem(absl::StrFormat(
"%s Recent",
ICON_MD_HISTORY).c_str())) {
1219 auto recent = command_palette_.GetRecentCommands(10);
1220 if (recent.empty()) {
1221 Text(
"No recent commands yet.");
1223 for (
const auto& entry : recent) {
1224 if (Selectable(entry.name.c_str())) {
1225 if (entry.callback) {
1227 show_command_palette_ =
false;
1228 command_palette_.RecordUsage(entry.name);
1236 if (BeginTabItem(absl::StrFormat(
"%s Frequent",
ICON_MD_STAR).c_str())) {
1237 auto frequent = command_palette_.GetFrequentCommands(10);
1238 if (frequent.empty()) {
1239 Text(
"No frequently used commands yet.");
1241 for (
const auto& entry : frequent) {
1242 if (Selectable(absl::StrFormat(
"%s (%d uses)", entry.name,
1245 if (entry.callback) {
1247 show_command_palette_ =
false;
1248 command_palette_.RecordUsage(entry.name);
1256 gui::EndThemedTabBar();
1261 Text(
"%s %zu commands | Score: fuzzy match",
ICON_MD_INFO,
1262 scored_commands.size());
1264 gui::ColoredText(
"| ↑↓=Navigate | Enter=Execute | Esc=Close",
1265 gui::ConvertColorToImVec4(theme.text_disabled));
1270 if (!show_palette) {
1271 show_command_palette_ =
false;
1273 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1274 if (config_dir.ok()) {
1275 std::filesystem::path history_file = *config_dir /
"command_history.json";
1276 command_palette_.SaveHistory(history_file.string());
1281void UICoordinator::DrawPanelFinder() {
1282 if (!show_panel_finder_)
1285 using namespace ImGui;
1286 const size_t session_id = window_manager_.GetActiveSessionId();
1289 const ImGuiViewport* viewport = GetMainViewport();
1290 ImDrawList* bg_list = GetBackgroundDrawList();
1291 bg_list->AddRectFilled(viewport->WorkPos,
1292 ImVec2(viewport->WorkPos.x + viewport->WorkSize.x,
1293 viewport->WorkPos.y + viewport->WorkSize.y),
1294 IM_COL32(0, 0, 0, 100));
1296 SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing,
1297 ImVec2(0.5f, 0.3f));
1298 if (IsCompactLayout()) {
1300 ImVec2(viewport->WorkSize.x * 0.95f, viewport->WorkSize.y * 0.70f),
1301 ImGuiCond_Appearing);
1303 SetNextWindowSize(ImVec2(600, 420), ImGuiCond_Appearing);
1306 const ImGuiWindowFlags finder_flags =
1307 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
1308 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking |
1309 ImGuiWindowFlags_NoSavedSettings;
1314 if (IsWindowAppearing()) {
1315 SetKeyboardFocusHere();
1316 panel_finder_selected_idx_ = 0;
1319 SetNextItemWidth(-1);
1320 bool input_changed = InputTextWithHint(
1322 panel_finder_query_, IM_ARRAYSIZE(panel_finder_query_));
1324 if (input_changed) {
1325 panel_finder_selected_idx_ = 0;
1331 struct WindowEntry {
1332 std::string card_id;
1333 std::string display_name;
1340 std::vector<WindowEntry> entries;
1342 std::string query(panel_finder_query_);
1345 for (
const auto& card_id :
1346 window_manager_.GetWindowsInSession(session_id)) {
1348 window_manager_.GetWindowDescriptor(session_id, card_id);
1353 if (!query.empty()) {
1354 score = CommandPalette::FuzzyScore(desc->display_name, query) +
1355 CommandPalette::FuzzyScore(desc->category, query) / 2;
1360 bool vis = desc->visibility_flag ? *desc->visibility_flag :
false;
1361 bool pin = window_manager_.IsWindowPinned(session_id, card_id);
1362 entries.push_back({card_id, desc->display_name, desc->icon,
1363 desc->category, vis, pin, score});
1367 if (query.empty()) {
1370 entries.begin(), entries.end(),
1371 [
this](
const WindowEntry& lhs,
const WindowEntry& rhs) {
1372 if (lhs.pinned != rhs.pinned)
1373 return lhs.pinned > rhs.pinned;
1374 uint64_t lhs_t = window_manager_.GetWindowMRUTime(lhs.card_id);
1375 uint64_t rhs_t = window_manager_.GetWindowMRUTime(rhs.card_id);
1377 return lhs_t > rhs_t;
1378 return lhs.display_name < rhs.display_name;
1382 std::sort(entries.begin(), entries.end(),
1383 [](
const WindowEntry& lhs,
const WindowEntry& rhs) {
1384 if (lhs.score != rhs.score)
1385 return lhs.score > rhs.score;
1386 if (lhs.pinned != rhs.pinned)
1387 return lhs.pinned > rhs.pinned;
1388 return lhs.display_name < rhs.display_name;
1394 panel_finder_selected_idx_ <
static_cast<int>(entries.size()) - 1) {
1395 panel_finder_selected_idx_++;
1397 if (
IsKeyPressed(ImGuiKey_UpArrow) && panel_finder_selected_idx_ > 0) {
1398 panel_finder_selected_idx_--;
1403 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
1405 PushStyleVar(ImGuiStyleVar_ItemSpacing,
1406 ImVec2(GetStyle().ItemSpacing.x, 10.0f));
1410 BeginChild(
"##PanelFinderList");
1411 for (
int i = 0; i < static_cast<int>(entries.size()); ++i) {
1412 const auto& entry = entries[i];
1413 bool is_selected = (i == panel_finder_selected_idx_);
1416 const char* vis_icon =
1420 std::optional<gui::StyleColorGuard> dim_guard;
1421 if (!entry.visible) {
1422 ImVec4 dimmed = GetStyleColorVec4(ImGuiCol_Text);
1424 dim_guard.emplace(ImGuiCol_Text, dimmed);
1428 absl::StrFormat(
"%s %s %s", vis_icon, entry.icon.c_str(),
1429 entry.display_name.c_str());
1433 is_touch ? std::max(GetTextLineHeightWithSpacing(), 44.0f) : 0.0f;
1435 PushID(entry.card_id.c_str());
1436 if (Selectable(label.c_str(), is_selected, ImGuiSelectableFlags_None,
1437 ImVec2(0, item_h))) {
1438 window_manager_.OpenWindow(session_id, entry.card_id);
1439 window_manager_.MarkWindowRecentlyUsed(entry.card_id);
1440 show_panel_finder_ =
false;
1443 SameLine(GetContentRegionAvail().x - 80);
1444 TextDisabled(
"%s", entry.category.c_str());
1452 if (is_selected && enter_pressed) {
1453 window_manager_.OpenWindow(session_id, entry.card_id);
1454 window_manager_.MarkWindowRecentlyUsed(entry.card_id);
1455 show_panel_finder_ =
false;
1459 if (is_selected && (
IsKeyPressed(ImGuiKey_DownArrow) ||
1473 if (!show || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
1474 show_panel_finder_ =
false;
1475 panel_finder_query_[0] =
'\0';
1479void UICoordinator::InitializeCommandPalette(
size_t session_id) {
1480 command_palette_.Clear();
1481 RefreshWorkflowActions();
1484 command_palette_.RegisterProvider(
1485 std::make_unique<PanelCommandsProvider>(&window_manager_, session_id));
1486 command_palette_.RegisterProvider(
1487 std::make_unique<WorkflowCommandsProvider>(&window_manager_, session_id));
1488 if (editor_manager_) {
1489 command_palette_.RegisterProvider(std::make_unique<SidebarCommandsProvider>(
1490 &window_manager_, &editor_manager_->user_settings(), session_id));
1494 command_palette_.RegisterProvider(std::make_unique<EditorCommandsProvider>(
1495 [
this](
const std::string& category) {
1496 auto type = EditorRegistry::GetEditorTypeFromCategory(category);
1497 if (type != EditorType::kSettings && editor_manager_) {
1498 editor_manager_->SwitchToEditor(type);
1503 command_palette_.AddCommand(
"Apply: Minimal Layout", CommandCategory::kLayout,
1504 "Switch to essential cards only",
"", [
this]() {
1505 if (editor_manager_) {
1506 editor_manager_->ApplyLayoutPreset(
"Minimal");
1510 command_palette_.AddCommand(
1511 "Apply: Logic Debugger Layout", CommandCategory::kLayout,
1512 "Switch to debug and development focused layout",
"", [
this]() {
1513 if (editor_manager_) {
1514 editor_manager_->ApplyLayoutPreset(
"Logic Debugger");
1518 command_palette_.AddCommand(
1519 "Apply: Overworld Artist Layout", CommandCategory::kLayout,
1520 "Switch to visual and overworld focused layout",
"", [
this]() {
1521 if (editor_manager_) {
1522 editor_manager_->ApplyLayoutPreset(
"Overworld Artist");
1526 command_palette_.AddCommand(
1527 "Apply: Dungeon Master Layout", CommandCategory::kLayout,
1528 "Switch to comprehensive dungeon editing layout",
"", [
this]() {
1529 if (editor_manager_) {
1530 editor_manager_->ApplyLayoutPreset(
"Dungeon Master");
1534 command_palette_.AddCommand(
1535 "Apply: Audio Engineer Layout", CommandCategory::kLayout,
1536 "Switch to music and sound editing layout",
"", [
this]() {
1537 if (editor_manager_) {
1538 editor_manager_->ApplyLayoutPreset(
"Audio Engineer");
1543 command_palette_.RegisterProvider(
1544 std::make_unique<RecentFilesCommandsProvider>(
1545 [
this](
const std::string& filepath) {
1546 if (editor_manager_) {
1547 auto status = editor_manager_->OpenRomOrProject(filepath);
1549 toast_manager_.Show(
1550 absl::StrFormat(
"Failed to open: %s", status.message()),
1557 command_palette_.RegisterProvider(
1558 std::make_unique<DungeonRoomCommandsProvider>(session_id));
1565 if (welcome_screen_) {
1566 WelcomeCommandsProvider::Callbacks welcome_callbacks;
1567 welcome_callbacks.model = &welcome_screen_->recent_projects();
1568 welcome_callbacks.template_names = {
1569 "Vanilla ROM Hack",
"ZSCustomOverworld v3",
"ZSCustomOverworld v2",
1570 "Randomizer Compatible"};
1571 welcome_callbacks.remove = [
this](
const std::string& path) {
1572 if (!welcome_screen_)
1574 welcome_screen_->recent_projects().RemoveRecent(path);
1575 welcome_screen_->RefreshRecentProjects(
true);
1576 toast_manager_.Show(
1577 absl::StrFormat(
"Removed %s from recents",
1578 std::filesystem::path(path).filename().string()),
1581 welcome_callbacks.toggle_pin = [
this](
const std::string& path) {
1582 if (!welcome_screen_)
1584 auto& model = welcome_screen_->recent_projects();
1585 bool currently_pinned =
false;
1586 for (
const auto& entry : model.entries()) {
1587 if (entry.filepath == path) {
1588 currently_pinned = entry.pinned;
1592 model.SetPinned(path, !currently_pinned);
1593 welcome_screen_->RefreshRecentProjects(
true);
1595 welcome_callbacks.undo_remove = [
this]() {
1596 if (!welcome_screen_)
1598 if (!welcome_screen_->recent_projects().UndoLastRemoval()) {
1599 toast_manager_.Show(
"Nothing to undo — the last removal has expired.",
1600 ToastType::kWarning);
1603 welcome_screen_->RefreshRecentProjects(
true);
1605 welcome_callbacks.clear_recents = [
this]() {
1606 if (!welcome_screen_)
1608 welcome_screen_->recent_projects().ClearAll();
1609 welcome_screen_->RefreshRecentProjects(
true);
1611 welcome_callbacks.create_from_template =
1612 [
this](
const std::string& template_name) {
1613 if (!editor_manager_)
1615 auto status = editor_manager_->CreateNewProject(template_name);
1617 toast_manager_.Show(absl::StrFormat(
"Failed to create project: %s",
1621 SetStartupSurface(StartupSurface::kDashboard);
1624 welcome_callbacks.dismiss_welcome = [
this]() {
1625 welcome_screen_manually_closed_ =
true;
1626 if (current_startup_surface_ == StartupSurface::kWelcome) {
1627 SetStartupSurface(StartupSurface::kDashboard);
1630 welcome_callbacks.show_welcome = [
this]() {
1631 welcome_screen_manually_closed_ =
false;
1632 SetStartupSurface(StartupSurface::kWelcome);
1634 command_palette_.RegisterProvider(std::make_unique<WelcomeCommandsProvider>(
1635 std::move(welcome_callbacks)));
1639 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1640 if (config_dir.ok()) {
1641 std::filesystem::path history_file = *config_dir /
"command_history.json";
1642 command_palette_.LoadHistory(history_file.string());
1645 command_palette_initialized_ =
true;
1648void UICoordinator::RefreshWorkflowActions() {
1649 ContentRegistry::WorkflowActions::Clear();
1651 if (editor_manager_ && editor_manager_->GetCurrentProject() &&
1652 editor_manager_->GetCurrentProject()->project_opened()) {
1653 ContentRegistry::WorkflowActions::Register(
1654 {.id =
"workflow.project.build",
1655 .group =
"Build & Run",
1656 .label =
"Build Project",
1657 .description =
"Run the active project's configured build command",
1660 .callback = [
this]() {
1661 if (editor_manager_) {
1662 editor_manager_->QueueBuildCurrentProject();
1665 ContentRegistry::WorkflowActions::Register(
1666 {.id =
"workflow.project.run",
1667 .group =
"Build & Run",
1668 .label =
"Run Project Output",
1669 .description =
"Open the active project's configured run target in a "
1673 .callback = [
this]() {
1674 if (editor_manager_) {
1675 (void)editor_manager_->RunCurrentProject();
1680#ifdef YAZE_WITH_GRPC
1681 auto* emu_backend = Application::Instance().GetEmulatorBackend();
1683 ContentRegistry::WorkflowActions::Register(
1684 {.id =
"workflow.mesen.connect",
1685 .group =
"Live Debugging",
1686 .label =
"Connect Mesen2",
1688 "Auto-discover and connect to the active Mesen2 backend",
1691 .callback = [
this]() {
1692 auto* backend = Application::Instance().GetEmulatorBackend();
1694 toast_manager_.Show(
"Mesen2 connection attempt queued",
1698 ContentRegistry::WorkflowActions::Register(
1699 {.id =
"workflow.mesen.step_over",
1700 .group =
"Live Debugging",
1701 .label =
"Mesen2 Step Over",
1702 .description =
"Execute one instruction without entering subroutines",
1705 .callback = [emu_backend]() { emu_backend->StepOver(); }});
1706 ContentRegistry::WorkflowActions::Register(
1707 {.id =
"workflow.mesen.step_out",
1708 .group =
"Live Debugging",
1709 .label =
"Mesen2 Step Out",
1710 .description =
"Run until return from the current subroutine",
1711 .shortcut =
"Shift+F11",
1713 .callback = [emu_backend]() { emu_backend->StepOut(); }});
1714 ContentRegistry::WorkflowActions::Register(
1715 {.id =
"workflow.mesen.overlay_on",
1716 .group =
"Live Debugging",
1717 .label =
"Enable Collision Overlay",
1718 .description =
"Show collision overlays in the active Mesen2 backend",
1721 .callback = [emu_backend]() {
1722 emu_backend->SetCollisionOverlay(
true);
1724 ContentRegistry::WorkflowActions::Register(
1725 {.id =
"workflow.mesen.overlay_off",
1726 .group =
"Live Debugging",
1727 .label =
"Disable Collision Overlay",
1728 .description =
"Hide collision overlays in the active Mesen2 backend",
1731 .callback = [emu_backend]() {
1732 emu_backend->SetCollisionOverlay(
false);
1738void UICoordinator::RefreshCommandPalette(
size_t session_id) {
1739 InitializeCommandPalette(session_id);
1742void UICoordinator::DrawGlobalSearch() {
1743 if (!show_global_search_)
1746 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1747 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1748 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
1750 bool show_search =
true;
1753 &show_search, ImGuiWindowFlags_NoCollapse)) {
1755 ImGui::SetNextItemWidth(-100);
1756 if (ImGui::IsWindowAppearing()) {
1757 ImGui::SetKeyboardFocusHere();
1760 bool input_changed = ImGui::InputTextWithHint(
1762 absl::StrFormat(
"%s Search everything...",
ICON_MD_SEARCH).c_str(),
1763 global_search_query_, IM_ARRAYSIZE(global_search_query_));
1766 if (ImGui::Button(absl::StrFormat(
"%s Clear",
ICON_MD_CLEAR).c_str())) {
1767 global_search_query_[0] =
'\0';
1768 input_changed =
true;
1774 if (gui::BeginThemedTabBar(
"SearchResultTabs")) {
1776 if (ImGui::BeginTabItem(
1778 auto& manager = project::RecentFilesManager::GetInstance();
1779 auto recent_files = manager.GetRecentFiles();
1781 if (ImGui::BeginTable(
"RecentFilesTable", 3,
1782 ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg |
1783 ImGuiTableFlags_SizingStretchProp)) {
1784 ImGui::TableSetupColumn(
"File", ImGuiTableColumnFlags_WidthStretch,
1786 ImGui::TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthFixed,
1788 ImGui::TableSetupColumn(
"Action", ImGuiTableColumnFlags_WidthFixed,
1790 ImGui::TableHeadersRow();
1792 for (
const auto& file : recent_files) {
1793 if (global_search_query_[0] !=
'\0' &&
1794 file.find(global_search_query_) == std::string::npos)
1797 ImGui::TableNextRow();
1798 ImGui::TableNextColumn();
1799 ImGui::Text(
"%s", util::GetFileName(file).c_str());
1801 ImGui::TableNextColumn();
1802 std::string ext = util::GetFileExtension(file);
1803 if (ext ==
"sfc" || ext ==
"smc") {
1804 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f),
"%s ROM",
1806 }
else if (ext ==
"yaze") {
1807 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.8f, 1.0f),
"%s Project",
1813 ImGui::TableNextColumn();
1814 ImGui::PushID(file.c_str());
1815 if (ImGui::Button(
"Open")) {
1816 auto status = editor_manager_->OpenRomOrProject(file);
1818 toast_manager_.Show(
1819 absl::StrCat(
"Failed to open: ", status.message()),
1822 SetGlobalSearchVisible(
false);
1829 ImGui::EndTabItem();
1833 auto* current_rom = editor_manager_->GetCurrentRom();
1834 if (current_rom && current_rom->resource_label()) {
1835 if (ImGui::BeginTabItem(
1837 auto& labels = current_rom->resource_label()->labels_;
1839 if (ImGui::BeginTable(
"LabelsTable", 3,
1840 ImGuiTableFlags_ScrollY |
1841 ImGuiTableFlags_RowBg |
1842 ImGuiTableFlags_SizingStretchProp)) {
1843 ImGui::TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthFixed,
1845 ImGui::TableSetupColumn(
"Label", ImGuiTableColumnFlags_WidthStretch,
1847 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch,
1849 ImGui::TableHeadersRow();
1851 for (
const auto& type_pair : labels) {
1852 for (
const auto& kv : type_pair.second) {
1853 if (global_search_query_[0] !=
'\0' &&
1854 kv.first.find(global_search_query_) == std::string::npos &&
1855 kv.second.find(global_search_query_) == std::string::npos)
1858 ImGui::TableNextRow();
1859 ImGui::TableNextColumn();
1860 ImGui::Text(
"%s", type_pair.first.c_str());
1862 ImGui::TableNextColumn();
1863 if (ImGui::Selectable(kv.first.c_str(),
false,
1864 ImGuiSelectableFlags_SpanAllColumns)) {
1868 ImGui::TableNextColumn();
1869 ImGui::TextDisabled(
"%s", kv.second.c_str());
1875 ImGui::EndTabItem();
1880 if (session_coordinator_.GetActiveSessionCount() > 1) {
1881 if (ImGui::BeginTabItem(
1882 absl::StrFormat(
"%s Sessions",
ICON_MD_TAB).c_str())) {
1883 ImGui::Text(
"Search and switch between active sessions:");
1885 for (
size_t i = 0; i < session_coordinator_.GetTotalSessionCount();
1887 std::string session_info =
1888 session_coordinator_.GetSessionDisplayName(i);
1889 if (session_info ==
"[CLOSED SESSION]")
1892 if (global_search_query_[0] !=
'\0' &&
1893 session_info.find(global_search_query_) == std::string::npos)
1897 (i == session_coordinator_.GetActiveSessionIndex());
1898 std::optional<gui::StyleColorGuard> current_guard;
1900 current_guard.emplace(ImGuiCol_Text,
1901 ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
1904 if (ImGui::Selectable(absl::StrFormat(
"%s %s %s",
ICON_MD_TAB,
1905 session_info.c_str(),
1906 is_current ?
"(Current)" :
"")
1909 editor_manager_->SwitchToSession(i);
1910 SetGlobalSearchVisible(
false);
1914 ImGui::EndTabItem();
1918 gui::EndThemedTabBar();
1923 ImGui::Text(
"%s Global search across all YAZE data",
ICON_MD_INFO);
1929 SetGlobalSearchVisible(
false);
1937void UICoordinator::SetStartupSurface(StartupSurface surface) {
1939 current_startup_surface_ = surface;
1942 const char* surface_names[] = {
"Welcome",
"Dashboard",
"Editor"};
1943 LOG_INFO(
"UICoordinator",
"Startup surface: %s -> %s",
1944 surface_names[
static_cast<int>(old_surface)],
1945 surface_names[
static_cast<int>(surface)]);
1949 case StartupSurface::kWelcome:
1950 show_welcome_screen_ =
true;
1951 show_editor_selection_ =
false;
1954 case StartupSurface::kDashboard:
1955 show_welcome_screen_ =
false;
1956 show_editor_selection_ =
true;
1958 case StartupSurface::kEditor:
1959 show_welcome_screen_ =
false;
1960 show_editor_selection_ =
false;
1965bool UICoordinator::ShouldShowWelcome()
const {
1967 if (welcome_behavior_override_ == StartupVisibility::kHide) {
1970 if (welcome_behavior_override_ == StartupVisibility::kShow) {
1975 return current_startup_surface_ == StartupSurface::kWelcome &&
1976 !welcome_screen_manually_closed_;
1979bool UICoordinator::ShouldShowDashboard()
const {
1981 if (dashboard_behavior_override_ == StartupVisibility::kHide) {
1984 if (dashboard_behavior_override_ == StartupVisibility::kShow) {
1989 return current_startup_surface_ == StartupSurface::kDashboard;
1992bool UICoordinator::ShouldShowActivityBar()
const {
1994 if (IsCompactLayout()) {
2000 if (current_startup_surface_ == StartupSurface::kWelcome) {
2005 if (editor_manager_) {
2006 auto* current_rom = editor_manager_->GetCurrentRom();
2007 if (!current_rom || !current_rom->is_loaded()) {
UICoordinator(EditorManager *editor_manager, RomFileManager &rom_manager, ProjectManager &project_manager, EditorRegistry &editor_registry, WorkspaceWindowManager &card_registry, SessionCoordinator &session_coordinator, WindowDelegate &window_delegate, ToastManager &toast_manager, PopupManager &popup_manager, ShortcutManager &shortcut_manager)
#define ICON_MD_NOTIFICATIONS
#define ICON_MD_FOLDER_SPECIAL
#define ICON_MD_FULLSCREEN_EXIT
#define ICON_MD_EXPAND_LESS
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_VISIBILITY
#define ICON_MD_MANAGE_SEARCH
#define ICON_MD_VISIBILITY_OFF
#define ICON_MD_DESCRIPTION
#define ICON_MD_HELP_OUTLINE
#define ICON_MD_DASHBOARD
#define ICON_MD_FIBER_MANUAL_RECORD
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_INFO(category, format,...)
Rom * rom()
Get the current ROM instance.
StartupSurface
Represents the current startup surface state.
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
ImVec4 GetTextSecondaryVec4()