diff --git a/bin/resources/fullscreenui/address-book-new.png b/bin/resources/fullscreenui/address-book-new.png new file mode 100644 index 0000000000..bb6bffc168 Binary files /dev/null and b/bin/resources/fullscreenui/address-book-new.png differ diff --git a/bin/resources/fullscreenui/back-icon.png b/bin/resources/fullscreenui/back-icon.png new file mode 100644 index 0000000000..438aaa39b9 Binary files /dev/null and b/bin/resources/fullscreenui/back-icon.png differ diff --git a/bin/resources/fullscreenui/desktop-mode.png b/bin/resources/fullscreenui/desktop-mode.png new file mode 100644 index 0000000000..081e031b22 Binary files /dev/null and b/bin/resources/fullscreenui/desktop-mode.png differ diff --git a/bin/resources/fullscreenui/drive-cdrom.png b/bin/resources/fullscreenui/drive-cdrom.png new file mode 100644 index 0000000000..5fa911ccd4 Binary files /dev/null and b/bin/resources/fullscreenui/drive-cdrom.png differ diff --git a/bin/resources/fullscreenui/exit.png b/bin/resources/fullscreenui/exit.png new file mode 100644 index 0000000000..d0cec9db7f Binary files /dev/null and b/bin/resources/fullscreenui/exit.png differ diff --git a/bin/resources/fullscreenui/start-bios.png b/bin/resources/fullscreenui/start-bios.png new file mode 100644 index 0000000000..bd7405269c Binary files /dev/null and b/bin/resources/fullscreenui/start-bios.png differ diff --git a/bin/resources/fullscreenui/start-file.png b/bin/resources/fullscreenui/start-file.png new file mode 100644 index 0000000000..3e27c19333 Binary files /dev/null and b/bin/resources/fullscreenui/start-file.png differ diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index de6363d8a1..09ddb5959d 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include @@ -356,7 +356,11 @@ void Host::OnCaptureStopped() { } -void Host::RequestExit(bool allow_confirm) +void Host::RequestExitApplication(bool allow_confirm) +{ +} + +void Host::RequestExitBigPicture() { } diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index fa4909aba8..eaade67454 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -215,6 +215,8 @@ void EmuThread::stopFullscreenUI() return; } + setFullscreen(false, true); + if (MTGS::IsOpen() && !VMManager::HasValidVM()) MTGS::WaitForClose(); @@ -1145,11 +1147,16 @@ void Host::CancelGameListRefresh() QMetaObject::invokeMethod(g_main_window, "cancelGameListRefresh", Qt::BlockingQueuedConnection); } -void Host::RequestExit(bool allow_confirm) +void Host::RequestExitApplication(bool allow_confirm) { QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection, Q_ARG(bool, allow_confirm)); } +void Host::RequestExitBigPicture() +{ + g_emu_thread->stopFullscreenUI(); +} + void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state) { if (!VMManager::HasValidVM()) diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index b9d83ef494..9cd2a5cb86 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #define IMGUI_DEFINE_MATH_OPERATORS @@ -1729,16 +1729,7 @@ void Achievements::ShowLoginSuccess(const rc_client_t* client) if (EmuConfig.Achievements.Notifications && MTGS::IsOpen()) { - std::string badge_path = GetUserBadgePath(user->username); - if (!FileSystem::FileExists(badge_path.c_str())) - { - char url[512]; - const int res = rc_client_user_get_image_url(user, url, std::size(url)); - if (res == RC_OK) - DownloadImage(url, badge_path); - else - ReportRCError(res, "rc_client_user_get_image_url() failed: "); - } + std::string badge_path = GetLoggedInUserBadgePath(); //: Summary for login notification. std::string title = user->display_name; @@ -1755,6 +1746,37 @@ void Achievements::ShowLoginSuccess(const rc_client_t* client) } } +const char* Achievements::GetLoggedInUserName() +{ + const rc_client_user_t* user = rc_client_get_user_info(s_client); + if (!user) [[unlikely]] + return nullptr; + + return user->username; +} + +std::string Achievements::GetLoggedInUserBadgePath() +{ + std::string badge_path; + + const rc_client_user_t* user = rc_client_get_user_info(s_client); + if (!user) [[unlikely]] + return badge_path; + + badge_path = GetUserBadgePath(user->username); + if (!FileSystem::FileExists(badge_path.c_str())) [[unlikely]] + { + char url[512]; + const int res = rc_client_user_get_image_url(user, url, std::size(url)); + if (res == RC_OK) + DownloadImage(url, badge_path); + else + ReportRCError(res, "rc_client_user_get_image_url() failed: "); + } + + return badge_path; +} + void Achievements::Logout() { if (IsActive()) @@ -2279,6 +2301,7 @@ void Achievements::DrawAchievementsWindow() ImGuiFullscreen::EndMenuButtons(); } ImGuiFullscreen::EndFullscreenWindow(); + FullscreenUI::SetStandardSelectionFooterText(true); } void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo) @@ -2657,6 +2680,7 @@ void Achievements::DrawLeaderboardsWindow() } } ImGuiFullscreen::EndFullscreenWindow(); + FullscreenUI::SetStandardSelectionFooterText(true); if (!is_leaderboard_open) { diff --git a/pcsx2/Achievements.h b/pcsx2/Achievements.h index 9c65ebb3bb..3507d40194 100644 --- a/pcsx2/Achievements.h +++ b/pcsx2/Achievements.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once @@ -109,6 +109,13 @@ namespace Achievements /// Should be called with the lock held. const std::string& GetGameTitle(); + /// Returns the logged-in user name. + const char* GetLoggedInUserName(); + + /// Returns the path to the user's profile avatar. + /// Should be called with the lock held. + std::string GetLoggedInUserBadgePath(); + /// Clears all cached state used to render the UI. void ClearUIState(); diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 8d8bc78cc0..d8ba778d0a 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -81,10 +81,6 @@ namespace Host /// Cancels game list refresh, if there is one in progress. void CancelGameListRefresh(); - /// Requests shut down and exit of the hosting application. This may not actually exit, - /// if the user cancels the shutdown confirmation. - void RequestExit(bool allow_confirm); - /// Requests shut down of the current virtual machine. void RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 5b3bad9bd0..d23ca3aa48 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -43,6 +43,7 @@ #include "imgui.h" #include "imgui_internal.h" +#include "fmt/chrono.h" #include "fmt/core.h" #include @@ -82,6 +83,7 @@ using ImGuiFullscreen::g_large_font; using ImGuiFullscreen::g_layout_padding_left; using ImGuiFullscreen::g_layout_padding_top; using ImGuiFullscreen::g_medium_font; +using ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT; using ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE; using ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE; using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT; @@ -111,6 +113,7 @@ using ImGuiFullscreen::AddNotification; using ImGuiFullscreen::BeginFullscreenColumns; using ImGuiFullscreen::BeginFullscreenColumnWindow; using ImGuiFullscreen::BeginFullscreenWindow; +using ImGuiFullscreen::BeginHorizontalMenu; using ImGuiFullscreen::BeginMenuButtons; using ImGuiFullscreen::BeginNavBar; using ImGuiFullscreen::CenterImage; @@ -120,13 +123,18 @@ using ImGuiFullscreen::DPIScale; using ImGuiFullscreen::EndFullscreenColumns; using ImGuiFullscreen::EndFullscreenColumnWindow; using ImGuiFullscreen::EndFullscreenWindow; +using ImGuiFullscreen::EndHorizontalMenu; using ImGuiFullscreen::EndMenuButtons; using ImGuiFullscreen::EndNavBar; using ImGuiFullscreen::EnumChoiceButton; using ImGuiFullscreen::FloatingButton; +using ImGuiFullscreen::ForceKeyNavEnabled; using ImGuiFullscreen::GetCachedTexture; using ImGuiFullscreen::GetCachedTextureAsync; using ImGuiFullscreen::GetPlaceholderTexture; +using ImGuiFullscreen::HorizontalMenuItem; +using ImGuiFullscreen::IsFocusResetQueued; +using ImGuiFullscreen::IsGamepadInputSource; using ImGuiFullscreen::LayoutScale; using ImGuiFullscreen::LoadTexture; using ImGuiFullscreen::MenuButton; @@ -151,6 +159,7 @@ using ImGuiFullscreen::QueueResetFocus; using ImGuiFullscreen::RangeButton; using ImGuiFullscreen::ResetFocusHere; using ImGuiFullscreen::RightAlignNavButtons; +using ImGuiFullscreen::SetFullscreenFooterText; using ImGuiFullscreen::ShowToast; using ImGuiFullscreen::ThreeWayToggleButton; using ImGuiFullscreen::ToggleButton; @@ -162,7 +171,10 @@ namespace FullscreenUI { None, Landing, + StartGame, + Exit, GameList, + GameListSettings, Settings, PauseMenu, Achievements, @@ -196,11 +208,10 @@ namespace FullscreenUI Count }; - enum class GameListPage + enum class GameListView { Grid, List, - Settings, Count }; @@ -208,16 +219,20 @@ namespace FullscreenUI // Main ////////////////////////////////////////////////////////////////////////// static void UpdateGameDetails(std::string path, std::string serial, std::string title, u32 disc_crc, u32 crc); - static void ToggleTheme(); + static bool AreAnyDialogsOpen(); static void PauseForMenuOpen(bool set_pause_menu_open); static void ClosePauseMenu(); static void OpenPauseSubMenu(PauseSubMenu submenu); + static void DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size); static void DrawLandingWindow(); + static void DrawStartGameWindow(); + static void DrawExitWindow(); static void DrawPauseMenu(MainWindowType type); static void ExitFullscreenAndOpenURL(const std::string_view& url); static void CopyTextToClipboard(std::string title, const std::string_view& text); static void DrawAboutWindow(); static void OpenAboutWindow(); + static void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel); static MainWindowType s_current_main_window = MainWindowType::None; static PauseSubMenu s_current_pause_submenu = PauseSubMenu::None; @@ -240,7 +255,6 @@ namespace FullscreenUI static bool LoadResources(); static void DestroyResources(); - static std::shared_ptr s_app_icon_texture; static std::array, static_cast(GameDatabaseSchema::Compatibility::Perfect)> s_game_compatibility_textures; static std::shared_ptr s_fallback_disc_texture; @@ -268,6 +282,7 @@ namespace FullscreenUI static void DoChangeDiscFromFile(); static void RequestChangeDisc(); static void DoRequestExit(); + static void DoDesktopMode(); static void DoToggleFullscreen(); static void ConfirmShutdownIfMemcardBusy(std::function callback); @@ -437,7 +452,7 @@ namespace FullscreenUI static void DrawGameGrid(const ImVec2& heading_size); static void HandleGameListActivate(const GameList::Entry* entry); static void HandleGameListOptions(const GameList::Entry* entry); - static void DrawGameListSettingsPage(const ImVec2& heading_size); + static void DrawGameListSettingsWindow(); static void SwitchToGameList(); static void PopulateGameListEntryList(); static GSTexture* GetTextureForGameListEntryType(GameList::EntryType type); @@ -447,7 +462,7 @@ namespace FullscreenUI // Lazily populated cover images. static std::unordered_map s_cover_image_map; static std::vector s_game_list_sorted_entries; - static GameListPage s_game_list_page = GameListPage::Grid; + static GameListView s_game_list_view = GameListView::Grid; ////////////////////////////////////////////////////////////////////////// // Achievements @@ -475,6 +490,73 @@ TinyString FullscreenUI::TimeToPrintableString(time_t t) return ret; } +void FullscreenUI::GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel) +{ + if (IsGamepadInputSource()) + { + ImGuiFullscreen::CreateFooterTextString( + dest, + std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, back_instead_of_cancel ? FSUI_VSTR("Back") : FSUI_VSTR("Cancel"))}); + } + else + { + ImGuiFullscreen::CreateFooterTextString( + dest, std::array{std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_ESC, back_instead_of_cancel ? FSUI_VSTR("Back") : FSUI_VSTR("Cancel"))}); + } +} + +void FullscreenUI::SetStandardSelectionFooterText(bool back_instead_of_cancel) +{ + SmallString text; + GetStandardSelectionFooterText(text, back_instead_of_cancel); + ImGuiFullscreen::SetFullscreenFooterText(text); +} + +void ImGuiFullscreen::GetChoiceDialogHelpText(SmallStringBase& dest) +{ + FullscreenUI::GetStandardSelectionFooterText(dest, false); +} + +void ImGuiFullscreen::GetFileSelectorHelpText(SmallStringBase& dest) +{ + if (IsGamepadInputSource()) + { + ImGuiFullscreen::CreateFooterTextString( + dest, std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Parent Directory")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); + } + else + { + ImGuiFullscreen::CreateFooterTextString( + dest, + std::array{std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_BACKSPACE, FSUI_VSTR("Parent Directory")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); + } +} + +void ImGuiFullscreen::GetInputDialogHelpText(SmallStringBase& dest) +{ + if (IsGamepadInputSource()) + { + CreateFooterTextString(dest, std::array{std::make_pair(ICON_PF_KEYBOARD, FSUI_VSTR("Enter Value")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); + } + else + { + CreateFooterTextString(dest, std::array{std::make_pair(ICON_PF_KEYBOARD, FSUI_VSTR("Enter Value")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); + } +} + ////////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////////// @@ -514,6 +596,7 @@ bool FullscreenUI::Initialize() SwitchToLanding(); } + ForceKeyNavEnabled(); return true; } @@ -524,8 +607,14 @@ bool FullscreenUI::IsInitialized() bool FullscreenUI::HasActiveWindow() { - return s_current_main_window != MainWindowType::None || s_save_state_selector_open || ImGuiFullscreen::IsChoiceDialogOpen() || - ImGuiFullscreen::IsFileSelectorOpen(); + return s_initialized && (s_current_main_window != MainWindowType::None || AreAnyDialogsOpen()); +} + +bool FullscreenUI::AreAnyDialogsOpen() +{ + return (s_save_state_selector_open || s_about_window_open || + s_input_binding_type != InputBindingInfo::Type::Unknown || ImGuiFullscreen::IsChoiceDialogOpen() || + ImGuiFullscreen::IsFileSelectorOpen()); } void FullscreenUI::CheckForConfigChanges(const Pcsx2Config& old_config) @@ -604,14 +693,6 @@ void FullscreenUI::UpdateGameDetails(std::string path, std::string serial, std:: s_current_disc_crc = disc_crc; } -void FullscreenUI::ToggleTheme() -{ - const bool new_light = !Host::GetBaseBoolSettingValue("UI", "UseLightFullscreenUITheme", false); - Host::SetBaseBoolSettingValue("UI", "UseLightFullscreenUITheme", new_light); - Host::CommitBaseSettingChanges(); - ImGuiFullscreen::SetTheme(new_light); -} - void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open) { s_was_paused_on_quick_menu_open = (VMManager::GetState() == VMState::Paused); @@ -631,6 +712,7 @@ void FullscreenUI::OpenPauseMenu() return; PauseForMenuOpen(true); + ForceKeyNavEnabled(); s_current_main_window = MainWindowType::PauseMenu; s_current_pause_submenu = PauseSubMenu::None; QueueResetFocus(); @@ -713,9 +795,18 @@ void FullscreenUI::Render() case MainWindowType::Landing: DrawLandingWindow(); break; + case MainWindowType::StartGame: + DrawStartGameWindow(); + break; + case MainWindowType::Exit: + DrawExitWindow(); + break; case MainWindowType::GameList: DrawGameListWindow(); break; + case MainWindowType::GameListSettings: + DrawGameListSettingsWindow(); + break; case MainWindowType::Settings: DrawSettingsWindow(); break; @@ -797,8 +888,6 @@ void FullscreenUI::ReturnToMainWindow() bool FullscreenUI::LoadResources() { - s_app_icon_texture = LoadTexture("icons/AppIconLarge.png"); - s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png"); s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png"); @@ -813,7 +902,6 @@ bool FullscreenUI::LoadResources() void FullscreenUI::DestroyResources() { - s_app_icon_texture.reset(); s_fallback_exe_texture.reset(); s_fallback_disc_texture.reset(); for (auto& tex : s_game_compatibility_textures) @@ -1020,7 +1108,12 @@ void FullscreenUI::RequestChangeDisc() void FullscreenUI::DoRequestExit() { - Host::RunOnCPUThread([]() { Host::RequestExit(true); }); + Host::RunOnCPUThread([]() { Host::RequestExitApplication(true); }); +} + +void FullscreenUI::DoDesktopMode() +{ + Host::RunOnCPUThread([]() { Host::RequestExitBigPicture(); }); } void FullscreenUI::DoToggleFullscreen() @@ -1051,82 +1144,247 @@ void FullscreenUI::SwitchToLanding() QueueResetFocus(); } +void FullscreenUI::DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size) +{ + const ImGuiIO& io = ImGui::GetIO(); + const ImVec2 heading_size = ImVec2( + io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f + 2.0f)); + *menu_pos = ImVec2(0.0f, heading_size.y); + *menu_size = ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)); + + if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "landing_heading", UIPrimaryColor)) + { + ImFont* const heading_font = g_large_font; + ImDrawList* const dl = ImGui::GetWindowDrawList(); + SmallString heading_str; + + ImGui::PushFont(heading_font); + ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); + + // draw branding + { + const ImVec2 logo_pos = LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING); + const ImVec2 logo_size = LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + dl->AddImage(GetCachedTexture("icons/AppIconLarge.png"), logo_pos, logo_pos + logo_size); + dl->AddText(heading_font, heading_font->FontSize, + ImVec2(logo_pos.x + logo_size.x + LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), logo_pos.y), + ImGui::GetColorU32(ImGuiCol_Text), "PCSX2"); + } + + // draw time + ImVec2 time_pos; + { + heading_str.format(FSUI_FSTR("{:%H:%M}"), fmt::localtime(std::time(nullptr))); + + const ImVec2 time_size = heading_font->CalcTextSizeA(heading_font->FontSize, FLT_MAX, 0.0f, "00:00"); + time_pos = ImVec2(heading_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) - time_size.x, + LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::RenderTextClipped(time_pos, time_pos + time_size, heading_str.c_str(), heading_str.end_ptr(), &time_size); + } + + // draw achievements info + if (Achievements::IsActive()) + { + const auto lock = Achievements::GetLock(); + const char* username = Achievements::GetLoggedInUserName(); + if (username) + { + const ImVec2 name_size = heading_font->CalcTextSizeA(heading_font->FontSize, FLT_MAX, 0.0f, username); + const ImVec2 name_pos = + ImVec2(time_pos.x - name_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), time_pos.y); + ImGui::RenderTextClipped(name_pos, name_pos + name_size, username, nullptr, &name_size); + + // TODO: should we cache this? heap allocations bad... + std::string badge_path = Achievements::GetLoggedInUserBadgePath(); + if (!badge_path.empty()) [[likely]] + { + const ImVec2 badge_size = + LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + const ImVec2 badge_pos = + ImVec2(name_pos.x - badge_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), time_pos.y); + + dl->AddImage(reinterpret_cast(GetCachedTextureAsync(badge_path)), badge_pos, + badge_pos + badge_size); + } + } + } + + ImGui::PopStyleColor(); + ImGui::PopFont(); + } + EndFullscreenWindow(); +} + void FullscreenUI::DrawLandingWindow() { - BeginFullscreenColumns(nullptr, 0.0f, true); + ImVec2 menu_pos, menu_size; + DrawLandingTemplate(&menu_pos, &menu_size); - if (BeginFullscreenColumnWindow(0.0f, -710.0f, "logo", UIPrimaryDarkColor)) - { - const float image_size = LayoutScale(380.f); - ImGui::SetCursorPos( - ImVec2((ImGui::GetWindowWidth() * 0.5f) - (image_size * 0.5f), (ImGui::GetWindowHeight() * 0.5f) - (image_size * 0.5f))); - ImGui::Image(s_app_icon_texture->GetNativeHandle(), ImVec2(image_size, image_size)); - } - EndFullscreenColumnWindow(); + ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); - if (BeginFullscreenColumnWindow(-710.0f, 0.0f, "menu", UIBackgroundColor)) + if (BeginHorizontalMenu("landing_window", menu_pos, menu_size, 4)) { ResetFocusHere(); - BeginMenuButtons(6, 0.5f); - - if (MenuButton(FSUI_ICONSTR(ICON_FA_LIST, "Game List"), FSUI_CSTR("Launch a game from images scanned from your game directories."))) + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/address-book-new.png"), FSUI_CSTR("Game List"), + FSUI_CSTR("Launch a game from images scanned from your game directories."))) { SwitchToGameList(); } - if (MenuButton(FSUI_ICONSTR(ICON_FA_FILE, "Start File"), FSUI_CSTR("Launch a game by selecting a file/disc image."))) + if (HorizontalMenuItem( + GetCachedTexture("fullscreenui/media-cdrom.png"), FSUI_CSTR("Start Game"), + FSUI_CSTR("Launch a game from a file, disc, or starts the console without any disc inserted."))) + { + s_current_main_window = MainWindowType::StartGame; + QueueResetFocus(); + } + + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/applications-system.png"), FSUI_CSTR("Settings"), + FSUI_CSTR("Changes settings for the application."))) + { + SwitchToSettings(); + } + + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit"), + FSUI_CSTR("Return to desktop mode, or exit the application.")) || + (!AreAnyDialogsOpen() && WantsToCloseMenu())) + { + s_current_main_window = MainWindowType::Exit; + QueueResetFocus(); + } + } + EndHorizontalMenu(); + + ImGui::PopStyleColor(); + + if (!AreAnyDialogsOpen()) + { + if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) + OpenAboutWindow(); + else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false) || ImGui::IsKeyPressed(ImGuiKey_F11, false)) + DoToggleFullscreen(); + } + + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_BURGER_MENU, FSUI_VSTR("About")), + std::make_pair(ICON_PF_BUTTON_X, FSUI_VSTR("Toggle Fullscreen")), + std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Exit"))}); + } + else + { + SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_F1, FSUI_VSTR("About")), + std::make_pair(ICON_PF_F11, FSUI_VSTR("Toggle Fullscreen")), + std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Exit"))}); + } +} + +void FullscreenUI::DrawStartGameWindow() +{ + ImVec2 menu_pos, menu_size; + DrawLandingTemplate(&menu_pos, &menu_size); + + ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); + + if (BeginHorizontalMenu("start_game_window", menu_pos, menu_size, 4)) + { + ResetFocusHere(); + + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-file.png"), FSUI_CSTR("Start File"), + FSUI_CSTR("Launch a game by selecting a file/disc image."))) { DoStartFile(); } - if (MenuButton(FSUI_ICONSTR(ICON_PF_MICROCHIP, "Start BIOS"), FSUI_CSTR("Start the console without any disc inserted."))) - { - DoStartBIOS(); - } - - if (MenuButton(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Start Disc"), FSUI_CSTR("Start a game from a disc in your PC's DVD drive."))) + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/drive-cdrom.png"), FSUI_CSTR("Start Disc"), + FSUI_CSTR("Start a game from a disc in your PC's DVD drive."))) { DoStartDisc(); } - if (MenuButton(FSUI_ICONSTR(ICON_FA_TOOLBOX, "Settings"), FSUI_CSTR("Change settings for the emulator."))) - SwitchToSettings(); + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-bios.png"), FSUI_CSTR("Start BIOS"), + FSUI_CSTR("Start the console without any disc inserted."))) + { + DoStartBIOS(); + } - if (MenuButton(FSUI_ICONSTR(ICON_FA_SIGN_OUT_ALT, "Exit"), FSUI_CSTR("Exits the program."))) + // https://www.iconpacks.net/free-icon/arrow-back-3783.html + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), + FSUI_CSTR("Return to the previous menu.")) || + (!AreAnyDialogsOpen() && WantsToCloseMenu())) + { + s_current_main_window = MainWindowType::Landing; + QueueResetFocus(); + } + } + EndHorizontalMenu(); + + ImGui::PopStyleColor(); + + if (!AreAnyDialogsOpen()) + { + if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) + OpenSaveStateSelector(true); + } + + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Load Global State")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); + } + else + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_F1, FSUI_VSTR("Load Global State")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); + } +} + +void FullscreenUI::DrawExitWindow() +{ + ImVec2 menu_pos, menu_size; + DrawLandingTemplate(&menu_pos, &menu_size); + + ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); + + if (BeginHorizontalMenu("exit_window", menu_pos, menu_size, 3)) + { + ResetFocusHere(); + + // https://www.iconpacks.net/free-icon/arrow-back-3783.html + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), + FSUI_CSTR("Return to the previous menu.")) || + WantsToCloseMenu()) + { + s_current_main_window = MainWindowType::Landing; + QueueResetFocus(); + } + + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit PCSX2"), + FSUI_CSTR("Completely exits the application, returning you to your desktop."))) { DoRequestExit(); } + if (HorizontalMenuItem(GetCachedTexture("fullscreenui/desktop-mode.png"), FSUI_CSTR("Desktop Mode"), + FSUI_CSTR("Exits Big Picture mode, returning to the desktop interface."))) { - ImVec2 fullscreen_pos; - if (FloatingButton(ICON_FA_WINDOW_CLOSE, 0.0f, 0.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) - DoRequestExit(); - - if (FloatingButton(ICON_FA_EXPAND, fullscreen_pos.x, 0.0f, -1.0f, -1.0f, -1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) - DoToggleFullscreen(); - - if (FloatingButton( - ICON_FA_QUESTION_CIRCLE, fullscreen_pos.x, 0.0f, -1.0f, -1.0f, -1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) - OpenAboutWindow(); - - if (FloatingButton(ICON_FA_LIGHTBULB, fullscreen_pos.x, 0.0f, -1.0f, -1.0f, -1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) - ToggleTheme(); + DoDesktopMode(); } - - EndMenuButtons(); - - const ImVec2 rev_size(g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, GIT_REV)); - ImGui::SetCursorPos( - ImVec2(ImGui::GetWindowWidth() - rev_size.x - LayoutScale(20.0f), ImGui::GetWindowHeight() - rev_size.y - LayoutScale(20.0f))); - ImGui::PushFont(g_medium_font); - ImGui::Text(GIT_REV); - ImGui::PopFont(); } + EndHorizontalMenu(); - EndFullscreenColumnWindow(); + ImGui::PopStyleColor(); - EndFullscreenColumns(); + SetStandardSelectionFooterText(true); } bool FullscreenUI::IsEditingGameSettings(SettingsInterface* bsi) @@ -2499,15 +2757,20 @@ void FullscreenUI::DrawSettingsWindow() if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup)) { - if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, false)) + if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, true) || + ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, true) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, true)) { index = (index == 0) ? (count - 1) : (index - 1); s_settings_page = pages[index]; + QueueResetFocus(); } - else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, false)) + else if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight, true) || + ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, true) || + ImGui::IsKeyPressed(ImGuiKey_RightArrow, true)) { index = (index + 1) % count; s_settings_page = pages[index]; + QueueResetFocus(); } } @@ -2539,16 +2802,20 @@ void FullscreenUI::DrawSettingsWindow() EndFullscreenWindow(); - if (BeginFullscreenWindow(ImVec2(0.0f, heading_size.y), ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y), "settings_parent", + // we have to do this here, because otherwise it uses target, and jumps a frame later. + if (IsFocusResetQueued()) + ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + + if (BeginFullscreenWindow( + ImVec2(0.0f, heading_size.y), + ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), + TinyString::from_format("settings_page_{}", static_cast(s_settings_page)).c_str(), ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, bg_alpha))) { ResetFocusHere(); - if (WantsToCloseMenu()) - { - if (ImGui::IsWindowFocused()) - ReturnToPreviousWindow(); - } + if (ImGui::IsWindowFocused() && WantsToCloseMenu()) + ReturnToPreviousWindow(); auto lock = Host::GetSettingsLock(); @@ -2620,6 +2887,21 @@ void FullscreenUI::DrawSettingsWindow() } EndFullscreenWindow(); + + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Change Page")), + std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); + } + else + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Change Page")), + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Navigate")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); + } } void FullscreenUI::DrawSummarySettingsPage() @@ -4482,7 +4764,7 @@ void FullscreenUI::DrawPatchesOrCheatsSettingsPage(bool cheats) if (cheats && s_game_cheat_unlabelled_count > 0) { ActiveButton(SmallString::from_format(master_enable ? FSUI_FSTR("{} unlabelled patch codes will automatically activate.") : - FSUI_FSTR("{} unlabelled patch codes found but not enabled."), + FSUI_FSTR("{} unlabelled patch codes found but not enabled."), s_game_cheat_unlabelled_count), false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); } @@ -4586,8 +4868,8 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) const ImVec2 subtitle_size(g_medium_font->CalcTextSizeA( g_medium_font->FontSize, std::numeric_limits::max(), -1.0f, s_current_game_subtitle.c_str())); - ImVec2 title_pos( - display_size.x - LayoutScale(10.0f + image_width + 20.0f) - title_size.x, display_size.y - LayoutScale(10.0f + image_height)); + ImVec2 title_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - title_size.x, + display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height)); ImVec2 path_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - path_size.x, title_pos.y + g_large_font->FontSize + LayoutScale(4.0f)); ImVec2 subtitle_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - subtitle_size.x, @@ -4627,8 +4909,8 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) GSTexture* const cover = GetCoverForCurrentGame(); - const ImVec2 image_min( - display_size.x - LayoutScale(10.0f + image_width), display_size.y - LayoutScale(10.0f + image_height) - rp_height); + const ImVec2 image_min(display_size.x - LayoutScale(10.0f + image_width), + display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height) - rp_height); const ImVec2 image_max(image_min.x + LayoutScale(image_width), image_min.y + LayoutScale(image_height) + rp_height); const ImRect image_rect(CenterImage( ImRect(image_min, image_max), ImVec2(static_cast(cover->GetWidth()), static_cast(cover->GetHeight())))); @@ -4674,7 +4956,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) } const ImVec2 window_size(LayoutScale(500.0f, LAYOUT_SCREEN_HEIGHT)); - const ImVec2 window_pos(0.0f, display_size.y - window_size.y); + const ImVec2 window_pos(0.0f, display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - window_size.y); if (BeginFullscreenWindow( window_pos, window_size, "pause_menu", ImVec4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f, 10.0f, ImGuiWindowFlags_NoBackground)) @@ -4816,6 +5098,19 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) // Primed achievements must come first, because we don't want the pause screen to be behind them. if (Achievements::HasAchievementsOrLeaderboards()) Achievements::DrawPauseMenuOverlays(); + + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Return To Game"))}); + } + else + { + SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), + std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Return To Game"))}); + } } void FullscreenUI::InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, s32 slot) @@ -4939,7 +5234,7 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f)); - ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowSize(io.DisplaySize - LayoutScale(0.0f, LAYOUT_FOOTER_HEIGHT)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); @@ -5197,9 +5492,11 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) ReturnToMainWindow(); break; } - - if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false))) + else if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || + ImGui::IsKeyPressed(ImGuiKey_F1, false))) + { s_save_state_selector_submenu_index = static_cast(i); + } } grid_x++; @@ -5231,6 +5528,23 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) CloseSaveStateSelector(); ReturnToPreviousWindow(); } + else + { + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD, FSUI_VSTR("Select State")), + std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Delete State")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Load State")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); + } + else + { + SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Select State")), + std::make_pair(ICON_PF_F1, FSUI_VSTR("Delete State")), std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Load State")), + std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); + } + } } bool FullscreenUI::OpenLoadStateSelectorForGameResume(const GameList::Entry* entry) @@ -5304,7 +5618,7 @@ void FullscreenUI::DrawResumeStateSelector() } } - if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Cancel"), false)) + if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Cancel"), false) || WantsToCloseMenu()) { ImGui::CloseCurrentPopup(); is_open = false; @@ -5325,6 +5639,10 @@ void FullscreenUI::DrawResumeStateSelector() s_save_state_selector_resuming = false; s_save_state_selector_game_path = {}; } + else + { + SetStandardSelectionFooterText(false); + } } void FullscreenUI::DoLoadState(std::string path) @@ -5445,41 +5763,24 @@ void FullscreenUI::DrawGameListWindow() if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", MulAlpha(UIPrimaryColor, bg_alpha))) { static constexpr float ITEM_WIDTH = 25.0f; - static constexpr const char* icons[] = {ICON_FA_BORDER_ALL, ICON_FA_LIST, ICON_FA_COG}; - static constexpr const char* titles[] = { - FSUI_NSTR("Game Grid"), - FSUI_NSTR("Game List"), - FSUI_NSTR("Game List Settings"), - }; + static constexpr const char* icons[] = {ICON_FA_BORDER_ALL, ICON_FA_LIST}; + static constexpr const char* titles[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; static constexpr u32 count = std::size(titles); BeginNavBar(); - if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup)) - { - if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, false)) - { - s_game_list_page = static_cast( - (s_game_list_page == static_cast(0)) ? (count - 1) : (static_cast(s_game_list_page) - 1)); - } - else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, false)) - { - s_game_list_page = static_cast((static_cast(s_game_list_page) + 1) % count); - } - } - if (NavButton(ICON_PF_BACKWARD, true, true)) ReturnToPreviousWindow(); - NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast(s_game_list_page)])); + NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast(s_game_list_view)])); RightAlignNavButtons(count, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); for (u32 i = 0; i < count; i++) { - if (NavButton( - icons[i], static_cast(i) == s_game_list_page, true, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + if (NavButton(icons[i], static_cast(i) == s_game_list_view, true, ITEM_WIDTH, + LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) { - s_game_list_page = static_cast(i); + s_game_list_view = static_cast(i); } } @@ -5488,17 +5789,24 @@ void FullscreenUI::DrawGameListWindow() EndFullscreenWindow(); - switch (s_game_list_page) + if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) { - case GameListPage::Grid: + s_game_list_view = (s_game_list_view == GameListView::Grid) ? GameListView::List : GameListView::Grid; + } + else if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F2)) + { + s_current_main_window = MainWindowType::GameListSettings; + QueueResetFocus(); + } + + switch (s_game_list_view) + { + case GameListView::Grid: DrawGameGrid(heading_size); break; - case GameListPage::List: + case GameListView::List: DrawGameList(heading_size); break; - case GameListPage::Settings: - DrawGameListSettingsPage(heading_size); - break; default: break; } @@ -5514,21 +5822,36 @@ void FullscreenUI::DrawGameListWindow() ImGui::End(); ImGui::PopStyleColor(); } + + if (IsGamepadInputSource()) + { + SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD, FSUI_VSTR("Select Game")), + std::make_pair(ICON_PF_BUTTON_X, FSUI_VSTR("Change View")), + std::make_pair(ICON_PF_BURGER_MENU, FSUI_VSTR("Settings")), + std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Launch Options")), + std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Start Game")), + std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); + } + else + { + SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Select Game")), + std::make_pair(ICON_PF_F1, FSUI_VSTR("Change View")), std::make_pair(ICON_PF_F2, FSUI_VSTR("Settings")), + std::make_pair(ICON_PF_F3, FSUI_VSTR("Launch Options")), std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Start Game")), + std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); + } } void FullscreenUI::DrawGameList(const ImVec2& heading_size) { - if (!BeginFullscreenColumns(nullptr, heading_size.y, true)) + if (!BeginFullscreenColumns(nullptr, heading_size.y, true, true)) { EndFullscreenColumns(); return; } - if (WantsToCloseMenu()) - { - if (ImGui::IsWindowFocused()) - ReturnToPreviousWindow(); - } + if (!AreAnyDialogsOpen() && WantsToCloseMenu()) + ReturnToPreviousWindow(); const GameList::Entry* selected_entry = nullptr; @@ -5594,7 +5917,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) selected_entry = entry; if (selected_entry && - (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false))) + (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || + ImGui::IsKeyPressed(ImGuiKey_F3, false))) { HandleGameListOptions(selected_entry); } @@ -5713,17 +6037,16 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) { ImGuiIO& io = ImGui::GetIO(); if (!BeginFullscreenWindow( - ImVec2(0.0f, heading_size.y), ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y), "game_grid", UIBackgroundColor)) + ImVec2(0.0f, heading_size.y), + ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), "game_grid", + UIBackgroundColor)) { EndFullscreenWindow(); return; } - if (WantsToCloseMenu()) - { - if (ImGui::IsWindowFocused()) - ReturnToPreviousWindow(); - } + if (!AreAnyDialogsOpen() && WantsToCloseMenu()) + ReturnToPreviousWindow(); ResetFocusHere(); BeginMenuButtons(); @@ -5794,10 +6117,14 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) ImGui::PopFont(); if (pressed) + { HandleGameListActivate(entry); - - if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false))) + } + else if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || + ImGui::IsKeyPressed(ImGuiKey_F3, false))) + { HandleGameListOptions(entry); + } } grid_x++; @@ -5871,20 +6198,43 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) }); } -void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size) +void FullscreenUI::DrawGameListSettingsWindow() { - const ImGuiIO& io = ImGui::GetIO(); - if (!BeginFullscreenWindow(ImVec2(0.0f, heading_size.y), ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y), "settings_parent", - UIBackgroundColor)) + ImGuiIO& io = ImGui::GetIO(); + ImVec2 heading_size = ImVec2( + io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f + 2.0f)); + + const float bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f; + + if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", MulAlpha(UIPrimaryColor, bg_alpha))) + { + BeginNavBar(); + + if (NavButton(ICON_FA_BACKWARD, true, true)) + { + s_current_main_window = MainWindowType::GameList; + QueueResetFocus(); + } + + NavTitle(FSUI_CSTR("Game List Settings")); + EndNavBar(); + } + + EndFullscreenWindow(); + + if (!BeginFullscreenWindow( + ImVec2(0.0f, heading_size.y), + ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), + "settings_parent", UIBackgroundColor)) { EndFullscreenWindow(); return; } - if (WantsToCloseMenu()) + if (ImGui::IsWindowFocused() && WantsToCloseMenu()) { - if (ImGui::IsWindowFocused()) - ReturnToPreviousWindow(); + s_current_main_window = MainWindowType::GameList; + QueueResetFocus(); } auto lock = Host::GetSettingsLock(); @@ -6028,12 +6378,14 @@ void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size) EndMenuButtons(); EndFullscreenWindow(); + + SetStandardSelectionFooterText(true); } void FullscreenUI::SwitchToGameList() { s_current_main_window = MainWindowType::GameList; - s_game_list_page = static_cast(Host::GetBaseIntSettingValue("UI", "DefaultFullscreenUIGameView", 0)); + s_game_list_view = static_cast(Host::GetBaseIntSettingValue("UI", "DefaultFullscreenUIGameView", 0)); { auto lock = Host::GetSettingsLock(); PopulateGameListDirectoryCache(Host::Internal::GetBaseSettingsLayer()); @@ -6109,13 +6461,13 @@ void FullscreenUI::OpenAboutWindow() void FullscreenUI::DrawAboutWindow() { - ImGui::SetNextWindowSize(LayoutScale(1000.0f, 500.0f)); + ImGui::SetNextWindowSize(LayoutScale(1000.0f, 580.0f)); ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::OpenPopup(FSUI_CSTR("About PCSX2")); ImGui::PushFont(g_large_font); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(30.0f, 30.0f)); if (ImGui::BeginPopupModal(FSUI_CSTR("About PCSX2"), &s_about_window_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) { @@ -6123,7 +6475,9 @@ void FullscreenUI::DrawAboutWindow() "PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a " "combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. " "This allows you to play PS2 games on your PC, with many additional features and benefits.")); + ImGui::NewLine(); + ImGui::TextWrapped("Version: %s", GIT_REV); ImGui::NewLine(); ImGui::TextWrapped("%s", @@ -6146,11 +6500,16 @@ void FullscreenUI::DrawAboutWindow() if (ActiveButton(FSUI_ICONSTR(ICON_FA_NEWSPAPER, "License"), false)) ExitFullscreenAndOpenURL(PCSX2_LICENSE_URL); - if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close"), false)) + if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close"), false) || WantsToCloseMenu()) { ImGui::CloseCurrentPopup(); s_about_window_open = false; } + else + { + SetStandardSelectionFooterText(true); + } + EndMenuButtons(); ImGui::EndPopup(); @@ -6195,7 +6554,10 @@ void FullscreenUI::SwitchToAchievementsWindow() return; if (s_current_main_window != MainWindowType::PauseMenu) + { PauseForMenuOpen(false); + ForceKeyNavEnabled(); + } s_current_main_window = MainWindowType::Achievements; QueueResetFocus(); @@ -6236,7 +6598,10 @@ void FullscreenUI::SwitchToLeaderboardsWindow() return; if (s_current_main_window != MainWindowType::PauseMenu) + { PauseForMenuOpen(false); + ForceKeyNavEnabled(); + } s_current_main_window = MainWindowType::Leaderboards; QueueResetFocus(); @@ -6385,7 +6750,6 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se TRANSLATE_NOOP("FullscreenUI", "Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it."); TRANSLATE_NOOP("FullscreenUI", "WARNING: Your memory card is still writing data. Shutting down now will IRREVERSIBLY DESTROY YOUR MEMORY CARD. It is strongly recommended to resume your game and let it finish writing to your memory card.\n\nDo you wish to shutdown anyways and IRREVERSIBLY DESTROY YOUR MEMORY CARD?"); TRANSLATE_NOOP("FullscreenUI", "Use Global Setting"); -TRANSLATE_NOOP("FullscreenUI", "Default"); TRANSLATE_NOOP("FullscreenUI", "Automatic binding failed, no devices are available."); TRANSLATE_NOOP("FullscreenUI", "Game title copied to clipboard."); TRANSLATE_NOOP("FullscreenUI", "Game serial copied to clipboard."); @@ -6410,12 +6774,26 @@ TRANSLATE_NOOP("FullscreenUI", "This game has no achievements."); TRANSLATE_NOOP("FullscreenUI", "This game has no leaderboards."); TRANSLATE_NOOP("FullscreenUI", "Reset System"); TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"); +TRANSLATE_NOOP("FullscreenUI", "Game List"); TRANSLATE_NOOP("FullscreenUI", "Launch a game from images scanned from your game directories."); +TRANSLATE_NOOP("FullscreenUI", "Start Game"); +TRANSLATE_NOOP("FullscreenUI", "Launch a game from a file, disc, or starts the console without any disc inserted."); +TRANSLATE_NOOP("FullscreenUI", "Settings"); +TRANSLATE_NOOP("FullscreenUI", "Changes settings for the application."); +TRANSLATE_NOOP("FullscreenUI", "Exit"); +TRANSLATE_NOOP("FullscreenUI", "Return to desktop mode, or exit the application."); +TRANSLATE_NOOP("FullscreenUI", "Start File"); TRANSLATE_NOOP("FullscreenUI", "Launch a game by selecting a file/disc image."); -TRANSLATE_NOOP("FullscreenUI", "Start the console without any disc inserted."); +TRANSLATE_NOOP("FullscreenUI", "Start Disc"); TRANSLATE_NOOP("FullscreenUI", "Start a game from a disc in your PC's DVD drive."); -TRANSLATE_NOOP("FullscreenUI", "Change settings for the emulator."); -TRANSLATE_NOOP("FullscreenUI", "Exits the program."); +TRANSLATE_NOOP("FullscreenUI", "Start BIOS"); +TRANSLATE_NOOP("FullscreenUI", "Start the console without any disc inserted."); +TRANSLATE_NOOP("FullscreenUI", "Back"); +TRANSLATE_NOOP("FullscreenUI", "Return to the previous menu."); +TRANSLATE_NOOP("FullscreenUI", "Exit PCSX2"); +TRANSLATE_NOOP("FullscreenUI", "Completely exits the application, returning you to your desktop."); +TRANSLATE_NOOP("FullscreenUI", "Desktop Mode"); +TRANSLATE_NOOP("FullscreenUI", "Exits Big Picture mode, returning to the desktop interface."); TRANSLATE_NOOP("FullscreenUI", "-"); TRANSLATE_NOOP("FullscreenUI", "No Binding"); TRANSLATE_NOOP("FullscreenUI", "Setting %s binding %s."); @@ -6436,7 +6814,6 @@ TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you minimize the window TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you open the quick menu, and unpauses when you close it."); TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed."); TRANSLATE_NOOP("FullscreenUI", "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time."); -TRANSLATE_NOOP("FullscreenUI", "When enabled, custom per-game settings will be applied. Disable to always use the global configuration."); TRANSLATE_NOOP("FullscreenUI", "Uses a light coloured theme instead of the default dark theme."); TRANSLATE_NOOP("FullscreenUI", "Game Display"); TRANSLATE_NOOP("FullscreenUI", "Automatically switches to fullscreen mode when a game is started."); @@ -6572,7 +6949,7 @@ TRANSLATE_NOOP("FullscreenUI", "Skip Draw End"); TRANSLATE_NOOP("FullscreenUI", "Auto Flush (Hardware)"); TRANSLATE_NOOP("FullscreenUI", "CPU Framebuffer Conversion"); TRANSLATE_NOOP("FullscreenUI", "Convert 4-bit and 8-bit framebuffer on the CPU instead of the GPU."); -TRANSLATE_NOOP("FullscreenUI", "Disable Depth Emulation"); +TRANSLATE_NOOP("FullscreenUI", "Disable Depth Conversion"); TRANSLATE_NOOP("FullscreenUI", "Disable the support of depth buffers in the texture cache."); TRANSLATE_NOOP("FullscreenUI", "Disable Safe Features"); TRANSLATE_NOOP("FullscreenUI", "This option disables multiple safe features."); @@ -6688,6 +7065,7 @@ TRANSLATE_NOOP("FullscreenUI", "Stores the current settings to an input profile. TRANSLATE_NOOP("FullscreenUI", "Input Sources"); TRANSLATE_NOOP("FullscreenUI", "The SDL input source supports most controllers."); TRANSLATE_NOOP("FullscreenUI", "Provides vibration and LED control support over Bluetooth."); +TRANSLATE_NOOP("FullscreenUI", "Enable/Disable the Player LED on DualSense controllers."); TRANSLATE_NOOP("FullscreenUI", "Allow SDL to use raw access to input devices."); TRANSLATE_NOOP("FullscreenUI", "The XInput source provides support for XBox 360/XBox One/XBox Series controllers."); TRANSLATE_NOOP("FullscreenUI", "Multitap"); @@ -6749,7 +7127,6 @@ TRANSLATE_NOOP("FullscreenUI", "Performs just-in-time binary translation of 32-b TRANSLATE_NOOP("FullscreenUI", "Graphics"); TRANSLATE_NOOP("FullscreenUI", "Use Debug Device"); TRANSLATE_NOOP("FullscreenUI", "Enables API-level validation of graphics commands."); -TRANSLATE_NOOP("FullscreenUI", "Settings"); TRANSLATE_NOOP("FullscreenUI", "No cheats are available for this game."); TRANSLATE_NOOP("FullscreenUI", "Cheat Codes"); TRANSLATE_NOOP("FullscreenUI", "No patches are available for this game."); @@ -6802,6 +7179,7 @@ TRANSLATE_NOOP("FullscreenUI", "A resume save state created at %s was found.\n\n TRANSLATE_NOOP("FullscreenUI", "Region: "); TRANSLATE_NOOP("FullscreenUI", "Compatibility: "); TRANSLATE_NOOP("FullscreenUI", "No Game Selected"); +TRANSLATE_NOOP("FullscreenUI", "Game List Settings"); TRANSLATE_NOOP("FullscreenUI", "Search Directories"); TRANSLATE_NOOP("FullscreenUI", "Adds a new directory to the game search list."); TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories"); @@ -6831,12 +7209,13 @@ TRANSLATE_NOOP("FullscreenUI", "Logs out of RetroAchievements."); TRANSLATE_NOOP("FullscreenUI", "Logs in to RetroAchievements."); TRANSLATE_NOOP("FullscreenUI", "Current Game"); TRANSLATE_NOOP("FullscreenUI", "{} is not a valid disc image."); +TRANSLATE_NOOP("FullscreenUI", "{:%H:%M}"); TRANSLATE_NOOP("FullscreenUI", "{0}/{1}/{2}/{3}"); TRANSLATE_NOOP("FullscreenUI", "Automatic mapping completed for {}."); TRANSLATE_NOOP("FullscreenUI", "Automatic mapping failed for {}."); TRANSLATE_NOOP("FullscreenUI", "Game settings initialized with global settings for '{}'."); TRANSLATE_NOOP("FullscreenUI", "Game settings have been cleared for '{}'."); -TRANSLATE_NOOP("FullscreenUI", "Console Port {}"); +TRANSLATE_NOOP("FullscreenUI", "Slot {}"); TRANSLATE_NOOP("FullscreenUI", "{} (Current)"); TRANSLATE_NOOP("FullscreenUI", "{} (Folder)"); TRANSLATE_NOOP("FullscreenUI", "Failed to load '{}'."); @@ -6993,6 +7372,7 @@ TRANSLATE_NOOP("FullscreenUI", "Screen Resolution"); TRANSLATE_NOOP("FullscreenUI", "Internal Resolution (Aspect Uncorrected)"); TRANSLATE_NOOP("FullscreenUI", "PNG"); TRANSLATE_NOOP("FullscreenUI", "JPEG"); +TRANSLATE_NOOP("FullscreenUI", "WebP"); TRANSLATE_NOOP("FullscreenUI", "0 (Disabled)"); TRANSLATE_NOOP("FullscreenUI", "1 (64 Max Width)"); TRANSLATE_NOOP("FullscreenUI", "2 (128 Max Width)"); @@ -7053,8 +7433,6 @@ TRANSLATE_NOOP("FullscreenUI", "Negative"); TRANSLATE_NOOP("FullscreenUI", "Positive"); TRANSLATE_NOOP("FullscreenUI", "Chop/Zero (Default)"); TRANSLATE_NOOP("FullscreenUI", "Game Grid"); -TRANSLATE_NOOP("FullscreenUI", "Game List"); -TRANSLATE_NOOP("FullscreenUI", "Game List Settings"); TRANSLATE_NOOP("FullscreenUI", "Type"); TRANSLATE_NOOP("FullscreenUI", "Serial"); TRANSLATE_NOOP("FullscreenUI", "Title"); @@ -7063,14 +7441,27 @@ TRANSLATE_NOOP("FullscreenUI", "CRC"); TRANSLATE_NOOP("FullscreenUI", "Time Played"); TRANSLATE_NOOP("FullscreenUI", "Last Played"); TRANSLATE_NOOP("FullscreenUI", "Size"); +TRANSLATE_NOOP("FullscreenUI", "Change Selection"); +TRANSLATE_NOOP("FullscreenUI", "Select"); +TRANSLATE_NOOP("FullscreenUI", "Cancel"); +TRANSLATE_NOOP("FullscreenUI", "Parent Directory"); +TRANSLATE_NOOP("FullscreenUI", "Enter Value"); +TRANSLATE_NOOP("FullscreenUI", "About"); +TRANSLATE_NOOP("FullscreenUI", "Toggle Fullscreen"); +TRANSLATE_NOOP("FullscreenUI", "Navigate"); +TRANSLATE_NOOP("FullscreenUI", "Load Global State"); +TRANSLATE_NOOP("FullscreenUI", "Default"); +TRANSLATE_NOOP("FullscreenUI", "Change Page"); TRANSLATE_NOOP("FullscreenUI", "Frequency"); +TRANSLATE_NOOP("FullscreenUI", "Return To Game"); +TRANSLATE_NOOP("FullscreenUI", "Select State"); +TRANSLATE_NOOP("FullscreenUI", "Delete State"); +TRANSLATE_NOOP("FullscreenUI", "Select Game"); +TRANSLATE_NOOP("FullscreenUI", "Change View"); +TRANSLATE_NOOP("FullscreenUI", "Launch Options"); TRANSLATE_NOOP("FullscreenUI", "Select Disc Image"); TRANSLATE_NOOP("FullscreenUI", "Select Disc Drive"); TRANSLATE_NOOP("FullscreenUI", "WARNING: Memory Card Busy"); -TRANSLATE_NOOP("FullscreenUI", "Start File"); -TRANSLATE_NOOP("FullscreenUI", "Start BIOS"); -TRANSLATE_NOOP("FullscreenUI", "Start Disc"); -TRANSLATE_NOOP("FullscreenUI", "Exit"); TRANSLATE_NOOP("FullscreenUI", "Set Input Binding"); TRANSLATE_NOOP("FullscreenUI", "Region"); TRANSLATE_NOOP("FullscreenUI", "Compatibility Rating"); @@ -7087,7 +7478,6 @@ TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss"); TRANSLATE_NOOP("FullscreenUI", "Pause On Menu"); TRANSLATE_NOOP("FullscreenUI", "Confirm Shutdown"); TRANSLATE_NOOP("FullscreenUI", "Save State On Shutdown"); -TRANSLATE_NOOP("FullscreenUI", "Enable Per-Game Settings"); TRANSLATE_NOOP("FullscreenUI", "Use Light Theme"); TRANSLATE_NOOP("FullscreenUI", "Start Fullscreen"); TRANSLATE_NOOP("FullscreenUI", "Double-Click Toggles Fullscreen"); @@ -7126,6 +7516,7 @@ TRANSLATE_NOOP("FullscreenUI", "Per-Game Configuration"); TRANSLATE_NOOP("FullscreenUI", "Copy Global Settings"); TRANSLATE_NOOP("FullscreenUI", "Enable SDL Input Source"); TRANSLATE_NOOP("FullscreenUI", "SDL DualShock 4 / DualSense Enhanced Mode"); +TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Player LED"); TRANSLATE_NOOP("FullscreenUI", "SDL Raw Input"); TRANSLATE_NOOP("FullscreenUI", "Enable XInput Input Source"); TRANSLATE_NOOP("FullscreenUI", "Enable Console Port 1 Multitap"); @@ -7174,8 +7565,6 @@ TRANSLATE_NOOP("FullscreenUI", "Leaderboards"); TRANSLATE_NOOP("FullscreenUI", "Delete Save"); TRANSLATE_NOOP("FullscreenUI", "Close Menu"); TRANSLATE_NOOP("FullscreenUI", "Default Boot"); -TRANSLATE_NOOP("FullscreenUI", "Delete State"); -TRANSLATE_NOOP("FullscreenUI", "Cancel"); TRANSLATE_NOOP("FullscreenUI", "Full Boot"); TRANSLATE_NOOP("FullscreenUI", "Reset Play Time"); TRANSLATE_NOOP("FullscreenUI", "Add Search Directory"); diff --git a/pcsx2/ImGui/FullscreenUI.h b/pcsx2/ImGui/FullscreenUI.h index afecfde497..a5038501e5 100644 --- a/pcsx2/ImGui/FullscreenUI.h +++ b/pcsx2/ImGui/FullscreenUI.h @@ -31,6 +31,7 @@ namespace FullscreenUI bool IsLeaderboardsWindowOpen(); void ReturnToPreviousWindow(); void ReturnToMainWindow(); + void SetStandardSelectionFooterText(bool back_instead_of_cancel); void Shutdown(bool clear_state); void Render(); @@ -41,6 +42,13 @@ namespace FullscreenUI // Host UI triggers from Big Picture mode. namespace Host { -void OnCoverDownloaderOpenRequested(); -void OnCreateMemoryCardOpenRequested(); -} + /// Requests shut down and exit of the hosting application. This may not actually exit, + /// if the user cancels the shutdown confirmation. + void RequestExitApplication(bool allow_confirm); + + /// Requests Big Picture mode to be shut down, returning to the desktop interface. + void RequestExitBigPicture(); + + void OnCoverDownloaderOpenRequested(); + void OnCreateMemoryCardOpenRequested(); +} // namespace Host diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp index 36bcf45cf1..c44f3e109f 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.cpp +++ b/pcsx2/ImGui/ImGuiFullscreen.cpp @@ -95,6 +95,10 @@ namespace ImGuiFullscreen static std::deque> s_texture_upload_queue; static Threading::Thread s_texture_load_thread; + static SmallString s_fullscreen_footer_text; + static SmallString s_last_fullscreen_footer_text; + static float s_fullscreen_text_change_time; + static bool s_choice_dialog_open = false; static bool s_choice_dialog_checkable = false; static std::string s_choice_dialog_title; @@ -238,6 +242,9 @@ void ImGuiFullscreen::Shutdown(bool clear_state) { s_notifications.clear(); s_background_progress_dialogs.clear(); + s_fullscreen_footer_text.clear(); + s_last_fullscreen_footer_text.clear(); + s_fullscreen_text_change_time = 0.0f; CloseInputDialog(); CloseMessageDialog(); s_choice_dialog_open = false; @@ -255,6 +262,11 @@ void ImGuiFullscreen::Shutdown(bool clear_state) s_file_selector_current_directory = {}; s_file_selector_filters.clear(); s_file_selector_items.clear(); + s_message_dialog_open = false; + s_message_dialog_title = {}; + s_message_dialog_message = {}; + s_message_dialog_buttons = {}; + s_message_dialog_callback = {}; } } @@ -309,12 +321,13 @@ std::shared_ptr ImGuiFullscreen::UploadTexture(const char* path, cons return std::shared_ptr(texture, [](GSTexture* tex) { g_gs_device->Recycle(tex); }); } -std::shared_ptr ImGuiFullscreen::LoadTexture(const char* path) +std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path) { - std::optional image(LoadTextureImage(path)); + std::string path_str(path); + std::optional image(LoadTextureImage(path_str.c_str())); if (image.has_value()) { - std::shared_ptr ret(UploadTexture(path, image.value())); + std::shared_ptr ret(UploadTexture(path_str.c_str(), image.value())); if (ret) return ret; } @@ -322,25 +335,25 @@ std::shared_ptr ImGuiFullscreen::LoadTexture(const char* path) return s_placeholder_texture; } -GSTexture* ImGuiFullscreen::GetCachedTexture(const char* name) +GSTexture* ImGuiFullscreen::GetCachedTexture(std::string_view name) { std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); if (!tex_ptr) { std::shared_ptr tex(LoadTexture(name)); - tex_ptr = s_texture_cache.Insert(name, std::move(tex)); + tex_ptr = s_texture_cache.Insert(std::string(name), std::move(tex)); } return tex_ptr->get(); } -GSTexture* ImGuiFullscreen::GetCachedTextureAsync(const char* name) +GSTexture* ImGuiFullscreen::GetCachedTextureAsync(std::string_view name) { std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); if (!tex_ptr) { // insert the placeholder - tex_ptr = s_texture_cache.Insert(name, s_placeholder_texture); + tex_ptr = s_texture_cache.Insert(std::string(name), s_placeholder_texture); // queue the actual load std::unique_lock lock(s_texture_load_mutex); @@ -484,6 +497,8 @@ void ImGuiFullscreen::EndLayout() DrawInputDialog(); DrawMessageDialog(); + DrawFullscreenFooter(); + const float notification_margin = LayoutScale(10.0f); const float spacing = LayoutScale(10.0f); const float notification_vertical_pos = GetNotificationVerticalPosition(); @@ -495,6 +510,8 @@ void ImGuiFullscreen::EndLayout() PopResetLayout(); + s_fullscreen_footer_text.clear(); + s_rendered_menu_item_border = false; s_had_hovered_menu_item = std::exchange(s_has_hovered_menu_item, false); } @@ -544,6 +561,10 @@ bool ImGuiFullscreen::ResetFocusHere() if (!s_focus_reset_queued) return false; + // don't take focus from dialogs + if (ImGui::FindBlockingModal(ImGui::GetCurrentWindow())) + return false; + s_focus_reset_queued = false; ImGui::SetWindowFocus(); @@ -551,6 +572,20 @@ bool ImGuiFullscreen::ResetFocusHere() return (GImGui->NavInputSource == ImGuiInputSource_Keyboard || GImGui->NavInputSource == ImGuiInputSource_Gamepad); } +bool ImGuiFullscreen::IsFocusResetQueued() +{ + return s_focus_reset_queued; +} + +void ImGuiFullscreen::ForceKeyNavEnabled() +{ + ImGuiContext& g = *ImGui::GetCurrentContext(); + g.ActiveIdSource = (g.ActiveIdSource == ImGuiInputSource_Mouse) ? ImGuiInputSource_Keyboard : g.ActiveIdSource; + g.NavInputSource = (g.NavInputSource == ImGuiInputSource_Mouse) ? ImGuiInputSource_Keyboard : g.ActiveIdSource; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; +} + bool ImGuiFullscreen::WantsToCloseMenu() { // Wait for the Close button to be released, THEN pressed @@ -592,11 +627,12 @@ void ImGuiFullscreen::PopPrimaryColor() ImGui::PopStyleColor(5); } -bool ImGuiFullscreen::BeginFullscreenColumns(const char* title, float pos_y, bool expand_to_screen_width) +bool ImGuiFullscreen::BeginFullscreenColumns(const char* title, float pos_y, bool expand_to_screen_width, bool footer) { ImGui::SetNextWindowPos(ImVec2(expand_to_screen_width ? 0.0f : g_layout_padding_left, pos_y)); ImGui::SetNextWindowSize(ImVec2( - expand_to_screen_width ? ImGui::GetIO().DisplaySize.x : LayoutScale(LAYOUT_SCREEN_WIDTH), ImGui::GetIO().DisplaySize.y - pos_y)); + expand_to_screen_width ? ImGui::GetIO().DisplaySize.x : LayoutScale(LAYOUT_SCREEN_WIDTH), + ImGui::GetIO().DisplaySize.y - pos_y - (footer ? LayoutScale(LAYOUT_FOOTER_HEIGHT) : 0.0f))); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); @@ -688,14 +724,103 @@ void ImGuiFullscreen::EndFullscreenWindow() ImGui::PopStyleColor(); } +bool ImGuiFullscreen::IsGamepadInputSource() +{ + return (ImGui::GetCurrentContext()->NavInputSource == ImGuiInputSource_Gamepad); +} + +void ImGuiFullscreen::CreateFooterTextString(SmallStringBase& dest, + std::span> items) +{ + dest.clear(); + for (const auto& [icon, text] : items) + { + if (!dest.empty()) + dest.append(" "); + + dest.append(icon); + dest.append(' '); + dest.append(text); + } +} + +void ImGuiFullscreen::SetFullscreenFooterText(std::string_view text) +{ + s_fullscreen_footer_text.assign(text); +} + +void ImGuiFullscreen::SetFullscreenFooterText(std::span> items) +{ + CreateFooterTextString(s_fullscreen_footer_text, items); +} + +void ImGuiFullscreen::DrawFullscreenFooter() +{ + const ImGuiIO& io = ImGui::GetIO(); + if (s_fullscreen_footer_text.empty()) + { + s_last_fullscreen_footer_text.clear(); + return; + } + + const float padding = LayoutScale(LAYOUT_FOOTER_PADDING); + const float height = LayoutScale(LAYOUT_FOOTER_HEIGHT); + + ImDrawList* dl = ImGui::GetForegroundDrawList(); + dl->AddRectFilled(ImVec2(0.0f, io.DisplaySize.y - height), io.DisplaySize, ImGui::GetColorU32(UIPrimaryColor), 0.0f); + + ImFont* const font = g_medium_font; + const float max_width = io.DisplaySize.x - padding * 2.0f; + + float prev_opacity = 0.0f; + if (!s_last_fullscreen_footer_text.empty() && s_fullscreen_footer_text != s_last_fullscreen_footer_text) + { + if (s_fullscreen_text_change_time == 0.0f) + s_fullscreen_text_change_time = 0.15f; + else + s_fullscreen_text_change_time = std::max(s_fullscreen_text_change_time - io.DeltaTime, 0.0f); + + if (s_fullscreen_text_change_time == 0.0f) + s_last_fullscreen_footer_text = s_fullscreen_footer_text; + + prev_opacity = s_fullscreen_text_change_time * (1.0f / 0.15f); + if (prev_opacity > 0.0f) + { + const ImVec2 text_size = + font->CalcTextSizeA(font->FontSize, max_width, 0.0f, s_last_fullscreen_footer_text.c_str(), + s_last_fullscreen_footer_text.end_ptr()); + dl->AddText( + font, font->FontSize, + ImVec2(io.DisplaySize.x - padding * 2.0f - text_size.x, io.DisplaySize.y - font->FontSize - padding), + ImGui::GetColorU32(ImVec4(UIPrimaryTextColor.x, UIPrimaryTextColor.y, UIPrimaryTextColor.z, prev_opacity)), + s_last_fullscreen_footer_text.c_str(), s_last_fullscreen_footer_text.end_ptr()); + } + } + else if (s_last_fullscreen_footer_text.empty()) + { + s_last_fullscreen_footer_text = s_fullscreen_footer_text; + } + + if (prev_opacity < 1.0f) + { + const ImVec2 text_size = font->CalcTextSizeA(font->FontSize, max_width, 0.0f, s_fullscreen_footer_text.c_str(), + s_fullscreen_footer_text.end_ptr()); + dl->AddText( + font, font->FontSize, + ImVec2(io.DisplaySize.x - padding * 2.0f - text_size.x, io.DisplaySize.y - font->FontSize - padding), + ImGui::GetColorU32(ImVec4(UIPrimaryTextColor.x, UIPrimaryTextColor.y, UIPrimaryTextColor.z, 1.0f - prev_opacity)), + s_fullscreen_footer_text.c_str(), s_fullscreen_footer_text.end_ptr()); + } +} + void ImGuiFullscreen::PrerenderMenuButtonBorder() { if (!s_had_hovered_menu_item) return; // updating might finish the animation - const ImVec2 min = s_menu_button_frame_min_animated.UpdateAndGetValue(); - const ImVec2 max = s_menu_button_frame_max_animated.UpdateAndGetValue(); + const ImVec2& min = s_menu_button_frame_min_animated.UpdateAndGetValue(); + const ImVec2& max = s_menu_button_frame_max_animated.UpdateAndGetValue(); const ImU32 col = ImGui::GetColorU32(ImGuiCol_ButtonHovered); const float t = std::min(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1), 1.0f); @@ -787,7 +912,7 @@ void ImGuiFullscreen::DrawMenuButtonFrame(const ImVec2& p_min, const ImVec2& p_m MENU_BACKGROUND_ANIMATION_TIME); } if (frame_max.x != s_menu_button_frame_max_animated.GetEndValue().x || - frame_max.y != s_menu_button_frame_max_animated.GetEndValue().x) + frame_max.y != s_menu_button_frame_max_animated.GetEndValue().y) { s_menu_button_frame_max_animated.Start(s_menu_button_frame_max_animated.GetCurrentValue(), frame_max, MENU_BACKGROUND_ANIMATION_TIME); @@ -1739,6 +1864,100 @@ bool ImGuiFullscreen::NavTab(const char* title, bool is_active, bool enabled /* return pressed; } +bool ImGuiFullscreen::BeginHorizontalMenu(const char* name, const ImVec2& position, const ImVec2& size, u32 num_items) +{ + s_menu_button_index = 0; + + const float item_padding = LayoutScale(LAYOUT_HORIZONTAL_MENU_PADDING); + const float item_width = LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_WIDTH); + const float item_spacing = LayoutScale(30.0f); + const float menu_width = static_cast(num_items) * (item_width + item_spacing) - item_spacing; + const float menu_height = LayoutScale(LAYOUT_HORIZONTAL_MENU_HEIGHT); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(item_padding, item_padding)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, LayoutScale(1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(item_spacing, 0.0f)); + + if (!BeginFullscreenWindow(position, size, name, UIBackgroundColor, 0.0f, 0.0f)) + return false; + + ImGui::SetCursorPos(ImVec2((size.x - menu_width) * 0.5f, (size.y - menu_height) * 0.5f)); + + PrerenderMenuButtonBorder(); + return true; +} + +void ImGuiFullscreen::EndHorizontalMenu() +{ + ImGui::PopStyleVar(4); + EndFullscreenWindow(); +} + +bool ImGuiFullscreen::HorizontalMenuItem(GSTexture* icon, const char* title, const char* description) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImVec2 pos = window->DC.CursorPos; + const ImVec2 size = LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_WIDTH, LAYOUT_HORIZONTAL_MENU_HEIGHT); + ImRect bb = ImRect(pos, pos + size); + + const ImGuiID id = window->GetID(title); + ImGui::ItemSize(size); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool held; + bool hovered; + const bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); + if (hovered) + { + const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f); + + const float t = static_cast(std::min(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1), 1.0)); + ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t)); + + DrawMenuButtonFrame(bb.Min, bb.Max, col, true, 0.0f); + + ImGui::PopStyleColor(); + } + + const ImGuiStyle& style = ImGui::GetStyle(); + bb.Min += style.FramePadding; + bb.Max -= style.FramePadding; + + const float avail_width = bb.Max.x - bb.Min.x; + const float icon_size = LayoutScale(150.0f); + const ImVec2 icon_pos = bb.Min + ImVec2((avail_width - icon_size) * 0.5f, 0.0f); + + ImDrawList* dl = ImGui::GetWindowDrawList(); + dl->AddImage(reinterpret_cast(icon), icon_pos, icon_pos + ImVec2(icon_size, icon_size)); + + ImFont* title_font = g_large_font; + const ImVec2 title_size = title_font->CalcTextSizeA(title_font->FontSize, avail_width, 0.0f, title); + const ImVec2 title_pos = + ImVec2(bb.Min.x + (avail_width - title_size.x) * 0.5f, icon_pos.y + icon_size + LayoutScale(10.0f)); + const ImVec4 title_bb = ImVec4(title_pos.x, title_pos.y, title_pos.x + title_size.x, title_pos.y + title_size.y); + + dl->AddText(title_font, title_font->FontSize, title_pos, ImGui::GetColorU32(ImGuiCol_Text), title, nullptr, 0.0f, + &title_bb); + + ImFont* desc_font = g_medium_font; + const ImVec2 desc_size = desc_font->CalcTextSizeA(desc_font->FontSize, avail_width, avail_width, description); + const ImVec2 desc_pos = ImVec2(bb.Min.x + (avail_width - desc_size.x) * 0.5f, title_bb.w + LayoutScale(10.0f)); + const ImVec4 desc_bb = ImVec4(desc_pos.x, desc_pos.y, desc_pos.x + desc_size.x, desc_pos.y + desc_size.y); + + dl->AddText(desc_font, desc_font->FontSize, desc_pos, ImGui::GetColorU32(ImGuiCol_Text), description, nullptr, + avail_width, &desc_bb); + + ImGui::SameLine(); + + s_menu_button_index++; + return pressed; +} + void ImGuiFullscreen::PopulateFileSelectorItems() { s_file_selector_items.clear(); @@ -1817,21 +2036,22 @@ bool ImGuiFullscreen::IsFileSelectorOpen() return s_file_selector_open; } -void ImGuiFullscreen::OpenFileSelector( - const char* title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters, std::string initial_directory) +void ImGuiFullscreen::OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters, std::string initial_directory) { if (s_file_selector_open) CloseFileSelector(); s_file_selector_open = true; s_file_selector_directory = select_directory; - s_file_selector_title = StringUtil::StdStringFromFormat("%s##file_selector", title); + s_file_selector_title = fmt::format("{}##file_selector", title); s_file_selector_callback = std::move(callback); s_file_selector_filters = std::move(filters); if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str())) initial_directory = FileSystem::GetWorkingDirectory(); SetFileSelectorDirectory(std::move(initial_directory)); + QueueResetFocus(); } void ImGuiFullscreen::CloseFileSelector() @@ -1856,7 +2076,8 @@ void ImGuiFullscreen::DrawFileSelector() return; ImGui::SetNextWindowSize(LayoutScale(1000.0f, 680.0f)); - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowPos((ImGui::GetIO().DisplaySize - LayoutScale(0.0f, LAYOUT_FOOTER_HEIGHT)) * 0.5f, + ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::OpenPopup(s_file_selector_title.c_str()); FileSelectorItem* selected = nullptr; @@ -1878,6 +2099,7 @@ void ImGuiFullscreen::DrawFileSelector() ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); BeginMenuButtons(); + ResetFocusHere(); if (!s_file_selector_current_directory.empty()) { @@ -1912,11 +2134,15 @@ void ImGuiFullscreen::DrawFileSelector() ImGui::PopStyleVar(3); ImGui::PopFont(); + if (is_open) + GetFileSelectorHelpText(s_fullscreen_footer_text); + if (selected) { if (selected->is_file) { s_file_selector_callback(selected->full_path); + QueueResetFocus(); } else { @@ -1933,6 +2159,18 @@ void ImGuiFullscreen::DrawFileSelector() s_file_selector_callback(no_path); CloseFileSelector(); } + else + { + if (ImGui::IsKeyPressed(ImGuiKey_Backspace, false) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false)) + { + if (!s_file_selector_items.empty() && s_file_selector_items.front().display_name == ICON_FA_FOLDER_OPEN + " ") + { + SetFileSelectorDirectory(std::move(s_file_selector_items.front().full_path)); + QueueResetFocus(); + } + } + } } bool ImGuiFullscreen::IsChoiceDialogOpen() @@ -1940,16 +2178,17 @@ bool ImGuiFullscreen::IsChoiceDialogOpen() return s_choice_dialog_open; } -void ImGuiFullscreen::OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback) +void ImGuiFullscreen::OpenChoiceDialog(std::string_view title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback) { if (s_choice_dialog_open) CloseChoiceDialog(); s_choice_dialog_open = true; s_choice_dialog_checkable = checkable; - s_choice_dialog_title = StringUtil::StdStringFromFormat("%s##choice_dialog", title); + s_choice_dialog_title = fmt::format("{}##choice_dialog", title); s_choice_dialog_options = std::move(options); s_choice_dialog_callback = std::move(callback); + QueueResetFocus(); } void ImGuiFullscreen::CloseChoiceDialog() @@ -1985,7 +2224,8 @@ void ImGuiFullscreen::DrawChoiceDialog() LayoutScale(450.0f), title_height + LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + (LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f)) * static_cast(s_choice_dialog_options.size())); ImGui::SetNextWindowSize(ImVec2(width, height)); - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowPos((ImGui::GetIO().DisplaySize - LayoutScale(0.0f, LAYOUT_FOOTER_HEIGHT)) * 0.5f, + ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::OpenPopup(s_choice_dialog_title.c_str()); bool is_open = !WantsToCloseMenu(); @@ -1997,6 +2237,7 @@ void ImGuiFullscreen::DrawChoiceDialog() ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); BeginMenuButtons(); + ResetFocusHere(); if (s_choice_dialog_checkable) { @@ -2057,6 +2298,10 @@ void ImGuiFullscreen::DrawChoiceDialog() s_choice_dialog_callback(-1, no_string, false); CloseChoiceDialog(); } + else + { + GetChoiceDialogHelpText(s_fullscreen_footer_text); + } } @@ -2074,6 +2319,7 @@ void ImGuiFullscreen::OpenInputStringDialog( s_input_dialog_caption = std::move(caption); s_input_dialog_ok_text = std::move(ok_button_text); s_input_dialog_callback = std::move(callback); + QueueResetFocus(); } void ImGuiFullscreen::DrawInputDialog() @@ -2082,7 +2328,8 @@ void ImGuiFullscreen::DrawInputDialog() return; ImGui::SetNextWindowSize(LayoutScale(700.0f, 0.0f)); - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowPos((ImGui::GetIO().DisplaySize - LayoutScale(0.0f, LAYOUT_FOOTER_HEIGHT)) * 0.5f, + ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::OpenPopup(s_input_dialog_title.c_str()); ImGui::PushFont(g_large_font); @@ -2098,6 +2345,7 @@ void ImGuiFullscreen::DrawInputDialog() if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { + ResetFocusHere(); ImGui::TextWrapped("%s", s_input_dialog_message.c_str()); BeginMenuButtons(); @@ -2143,6 +2391,8 @@ void ImGuiFullscreen::DrawInputDialog() } if (!is_open) CloseInputDialog(); + else + GetInputDialogHelpText(s_fullscreen_footer_text); ImGui::PopStyleColor(4); ImGui::PopStyleVar(3); @@ -2179,6 +2429,7 @@ void ImGuiFullscreen::OpenConfirmMessageDialog( s_message_dialog_callback = std::move(callback); s_message_dialog_buttons[0] = std::move(yes_button_text); s_message_dialog_buttons[1] = std::move(no_button_text); + QueueResetFocus(); } void ImGuiFullscreen::OpenInfoMessageDialog( @@ -2191,6 +2442,7 @@ void ImGuiFullscreen::OpenInfoMessageDialog( s_message_dialog_message = std::move(message); s_message_dialog_callback = std::move(callback); s_message_dialog_buttons[0] = std::move(button_text); + QueueResetFocus(); } void ImGuiFullscreen::OpenMessageDialog(std::string title, std::string message, MessageDialogCallback callback, @@ -2205,6 +2457,7 @@ void ImGuiFullscreen::OpenMessageDialog(std::string title, std::string message, s_message_dialog_buttons[0] = std::move(first_button_text); s_message_dialog_buttons[1] = std::move(second_button_text); s_message_dialog_buttons[2] = std::move(third_button_text); + QueueResetFocus(); } void ImGuiFullscreen::CloseMessageDialog() @@ -2217,6 +2470,7 @@ void ImGuiFullscreen::CloseMessageDialog() s_message_dialog_message = {}; s_message_dialog_buttons = {}; s_message_dialog_callback = {}; + QueueResetFocus(); } void ImGuiFullscreen::DrawMessageDialog() @@ -2248,6 +2502,7 @@ void ImGuiFullscreen::DrawMessageDialog() if (ImGui::BeginPopupModal(win_id, &is_open, flags)) { BeginMenuButtons(); + ResetFocusHere(); ImGui::TextWrapped("%s", s_message_dialog_message.c_str()); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(20.0f)); @@ -2289,6 +2544,10 @@ void ImGuiFullscreen::DrawMessageDialog() func(result.value_or(1) == 0); } } + else + { + GetChoiceDialogHelpText(s_fullscreen_footer_text); + } } static float s_notification_vertical_position = 0.15f; @@ -2648,7 +2907,7 @@ void ImGuiFullscreen::DrawToast() ImFont* message_font = g_medium_font; const float padding = LayoutScale(20.0f); const float total_padding = padding * 2.0f; - const float margin = LayoutScale(20.0f); + const float margin = LayoutScale(20.0f + (s_fullscreen_footer_text.empty() ? 0.0f : LAYOUT_FOOTER_HEIGHT)); const float spacing = s_toast_title.empty() ? 0.0f : LayoutScale(10.0f); const ImVec2 display_size(ImGui::GetIO().DisplaySize); const ImVec2 title_size(s_toast_title.empty() ? ImVec2(0.0f, 0.0f) : diff --git a/pcsx2/ImGui/ImGuiFullscreen.h b/pcsx2/ImGui/ImGuiFullscreen.h index 058255b0f3..573940aad5 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.h +++ b/pcsx2/ImGui/ImGuiFullscreen.h @@ -1,14 +1,18 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once + #include "common/Pcsx2Defs.h" + #include "IconsFontAwesome5.h" #include "imgui.h" #include "imgui_internal.h" + #include #include #include +#include #include #include #include @@ -30,6 +34,11 @@ namespace ImGuiFullscreen static constexpr float LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY = 26.0f; static constexpr float LAYOUT_MENU_BUTTON_X_PADDING = 15.0f; static constexpr float LAYOUT_MENU_BUTTON_Y_PADDING = 10.0f; + static constexpr float LAYOUT_FOOTER_PADDING = 10.0f; + static constexpr float LAYOUT_FOOTER_HEIGHT = LAYOUT_MEDIUM_FONT_SIZE + LAYOUT_FOOTER_PADDING * 2.0f; + static constexpr float LAYOUT_HORIZONTAL_MENU_HEIGHT = 320.0f; + static constexpr float LAYOUT_HORIZONTAL_MENU_PADDING = 30.0f; + static constexpr float LAYOUT_HORIZONTAL_MENU_ITEM_WIDTH = 250.0f; extern ImFont* g_standard_font; extern ImFont* g_medium_font; @@ -106,9 +115,9 @@ namespace ImGuiFullscreen /// Texture cache. const std::shared_ptr& GetPlaceholderTexture(); - std::shared_ptr LoadTexture(const char* path); - GSTexture* GetCachedTexture(const char* name); - GSTexture* GetCachedTextureAsync(const char* name); + std::shared_ptr LoadTexture(std::string_view path); + GSTexture* GetCachedTexture(std::string_view name); + GSTexture* GetCachedTextureAsync(std::string_view name); bool InvalidateCachedTexture(const std::string& path); void UploadAsyncTextures(); @@ -120,6 +129,9 @@ namespace ImGuiFullscreen void QueueResetFocus(); bool ResetFocusHere(); + bool IsFocusResetQueued(); + void ForceKeyNavEnabled(); + bool WantsToCloseMenu(); void ResetCloseMenuIfNeeded(); @@ -128,7 +140,7 @@ namespace ImGuiFullscreen void DrawWindowTitle(const char* title); - bool BeginFullscreenColumns(const char* title = nullptr, float pos_y = 0.0f, bool expand_to_screen_width = false); + bool BeginFullscreenColumns(const char* title = nullptr, float pos_y = 0.0f, bool expand_to_screen_width = false, bool footer = false); void EndFullscreenColumns(); bool BeginFullscreenColumnWindow(float start, float end, const char* name, const ImVec4& background = UIBackgroundColor); @@ -140,6 +152,12 @@ namespace ImGuiFullscreen const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, float padding = 0.0f, ImGuiWindowFlags flags = 0); void EndFullscreenWindow(); + bool IsGamepadInputSource(); + void CreateFooterTextString(SmallStringBase& dest, std::span> items); + void SetFullscreenFooterText(std::string_view text); + void SetFullscreenFooterText(std::span> items); + void DrawFullscreenFooter(); + void PrerenderMenuButtonBorder(); void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT); @@ -209,17 +227,21 @@ namespace ImGuiFullscreen bool NavTab(const char* title, bool is_active, bool enabled, float width, float height, const ImVec4& background, ImFont* font = g_large_font); + bool BeginHorizontalMenu(const char* name, const ImVec2& position, const ImVec2& size, u32 num_items); + void EndHorizontalMenu(); + bool HorizontalMenuItem(GSTexture* icon, const char* title, const char* description); + using FileSelectorCallback = std::function; using FileSelectorFilters = std::vector; bool IsFileSelectorOpen(); - void OpenFileSelector(const char* title, bool select_directory, FileSelectorCallback callback, + void OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters = FileSelectorFilters(), std::string initial_directory = std::string()); void CloseFileSelector(); using ChoiceDialogCallback = std::function; using ChoiceDialogOptions = std::vector>; bool IsChoiceDialogOpen(); - void OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback); + void OpenChoiceDialog(std::string_view title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback); void CloseChoiceDialog(); using InputStringDialogCallback = std::function; @@ -253,4 +275,9 @@ namespace ImGuiFullscreen void ShowToast(std::string title, std::string message, float duration = 10.0f); void ClearToast(); + + // Message callbacks. + void GetChoiceDialogHelpText(SmallStringBase& dest); + void GetFileSelectorHelpText(SmallStringBase& dest); + void GetInputDialogHelpText(SmallStringBase& dest); } // namespace ImGuiFullscreen diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index cc9844af87..cfb130b687 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -489,8 +489,8 @@ ImFont* ImGuiManager::AddFixedFont(float size) bool ImGuiManager::AddIconFonts(float size) { // clang-format off - static constexpr ImWchar range_fa[] = { 0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf025,0xf027,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf059,0xf059,0xf05e,0xf05e,0xf063,0xf063,0xf065,0xf065,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf121,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf188,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2d0,0xf2d0,0xf2f1,0xf2f2,0xf2f5,0xf2f5,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf51f,0xf51f,0xf543,0xf543,0xf547,0xf547,0xf54c,0xf54c,0xf552,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; - static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221a,0x227a,0x227d,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x2380,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe001,0xe001,0xff21,0xff3a,0x0,0x0 }; + static constexpr ImWchar range_fa[] = { 0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf025,0xf027,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04a,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf121,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf188,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2d0,0xf2d0,0xf2f1,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf51f,0xf51f,0xf543,0xf543,0xf547,0xf547,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf756,0xf756,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; + static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221a,0x227a,0x227f,0x2284,0x2284,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x2380,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe001,0xe001,0xff21,0xff3a,0x0,0x0 }; // clang-format on { diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index b6f66e5db1..edc2b9f2b6 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -161,7 +161,11 @@ void Host::OnCaptureStopped() { } -void Host::RequestExit(bool save_state_if_running) +void Host::RequestExitApplication(bool allow_confirm) +{ +} + +void Host::RequestExitBigPicture() { }