fullscreen_ui.cpp (340421B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) 3 4 #include "fullscreen_ui.h" 5 #include "achievements.h" 6 #include "bios.h" 7 #include "cheats.h" 8 #include "controller.h" 9 #include "game_list.h" 10 #include "gpu.h" 11 #include "host.h" 12 #include "settings.h" 13 #include "system.h" 14 15 #include "scmversion/scmversion.h" 16 17 #include "util/cd_image.h" 18 #include "util/gpu_device.h" 19 #include "util/imgui_fullscreen.h" 20 #include "util/imgui_manager.h" 21 #include "util/ini_settings_interface.h" 22 #include "util/input_manager.h" 23 #include "util/postprocessing.h" 24 25 #include "common/error.h" 26 #include "common/file_system.h" 27 #include "common/log.h" 28 #include "common/path.h" 29 #include "common/small_string.h" 30 #include "common/string_util.h" 31 #include "common/timer.h" 32 33 #include "IconsFontAwesome5.h" 34 #include "IconsPromptFont.h" 35 #include "fmt/chrono.h" 36 #include "imgui.h" 37 #include "imgui_internal.h" 38 39 #include <atomic> 40 #include <bitset> 41 #include <unordered_map> 42 #include <utility> 43 #include <vector> 44 45 Log_SetChannel(FullscreenUI); 46 47 #define TR_CONTEXT "FullscreenUI" 48 49 namespace { 50 template<size_t L> 51 class IconStackString : public SmallStackString<L> 52 { 53 public: 54 ALWAYS_INLINE IconStackString(const char* icon, const char* str) 55 { 56 SmallStackString<L>::format("{} {}", icon, Host::TranslateToStringView(TR_CONTEXT, str)); 57 } 58 ALWAYS_INLINE IconStackString(const char* icon, const char* str, const char* suffix) 59 { 60 SmallStackString<L>::format("{} {}##{}", icon, Host::TranslateToStringView(TR_CONTEXT, str), suffix); 61 } 62 }; 63 } // namespace 64 65 #define FSUI_ICONSTR(icon, str) IconStackString<128>(icon, str).c_str() 66 #define FSUI_STR(str) Host::TranslateToString(TR_CONTEXT, str) 67 #define FSUI_CSTR(str) Host::TranslateToCString(TR_CONTEXT, str) 68 #define FSUI_VSTR(str) Host::TranslateToStringView(TR_CONTEXT, str) 69 #define FSUI_FSTR(str) fmt::runtime(Host::TranslateToStringView(TR_CONTEXT, str)) 70 #define FSUI_NSTR(str) str 71 72 using ImGuiFullscreen::FocusResetType; 73 using ImGuiFullscreen::g_large_font; 74 using ImGuiFullscreen::g_layout_padding_left; 75 using ImGuiFullscreen::g_layout_padding_top; 76 using ImGuiFullscreen::g_medium_font; 77 using ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT; 78 using ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE; 79 using ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE; 80 using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT; 81 using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY; 82 using ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING; 83 using ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING; 84 using ImGuiFullscreen::LAYOUT_SCREEN_HEIGHT; 85 using ImGuiFullscreen::LAYOUT_SCREEN_WIDTH; 86 using ImGuiFullscreen::UIBackgroundColor; 87 using ImGuiFullscreen::UIBackgroundHighlightColor; 88 using ImGuiFullscreen::UIBackgroundLineColor; 89 using ImGuiFullscreen::UIBackgroundTextColor; 90 using ImGuiFullscreen::UIDisabledColor; 91 using ImGuiFullscreen::UIPrimaryColor; 92 using ImGuiFullscreen::UIPrimaryDarkColor; 93 using ImGuiFullscreen::UIPrimaryLightColor; 94 using ImGuiFullscreen::UIPrimaryLineColor; 95 using ImGuiFullscreen::UIPrimaryTextColor; 96 using ImGuiFullscreen::UISecondaryColor; 97 using ImGuiFullscreen::UISecondaryStrongColor; 98 using ImGuiFullscreen::UISecondaryTextColor; 99 using ImGuiFullscreen::UISecondaryWeakColor; 100 using ImGuiFullscreen::UITextHighlightColor; 101 102 using ImGuiFullscreen::ActiveButton; 103 using ImGuiFullscreen::AddNotification; 104 using ImGuiFullscreen::BeginFullscreenColumns; 105 using ImGuiFullscreen::BeginFullscreenColumnWindow; 106 using ImGuiFullscreen::BeginFullscreenWindow; 107 using ImGuiFullscreen::BeginHorizontalMenu; 108 using ImGuiFullscreen::BeginMenuButtons; 109 using ImGuiFullscreen::BeginNavBar; 110 using ImGuiFullscreen::CenterImage; 111 using ImGuiFullscreen::CloseChoiceDialog; 112 using ImGuiFullscreen::CloseFileSelector; 113 using ImGuiFullscreen::CreateTextureFromImage; 114 using ImGuiFullscreen::DefaultActiveButton; 115 using ImGuiFullscreen::DrawShadowedText; 116 using ImGuiFullscreen::EndFullscreenColumns; 117 using ImGuiFullscreen::EndFullscreenColumnWindow; 118 using ImGuiFullscreen::EndFullscreenWindow; 119 using ImGuiFullscreen::EndHorizontalMenu; 120 using ImGuiFullscreen::EndMenuButtons; 121 using ImGuiFullscreen::EndNavBar; 122 using ImGuiFullscreen::EnumChoiceButton; 123 using ImGuiFullscreen::FloatingButton; 124 using ImGuiFullscreen::ForceKeyNavEnabled; 125 using ImGuiFullscreen::GetCachedTexture; 126 using ImGuiFullscreen::GetCachedTextureAsync; 127 using ImGuiFullscreen::GetPlaceholderTexture; 128 using ImGuiFullscreen::HorizontalMenuItem; 129 using ImGuiFullscreen::IsFocusResetFromWindowChange; 130 using ImGuiFullscreen::IsFocusResetQueued; 131 using ImGuiFullscreen::IsGamepadInputSource; 132 using ImGuiFullscreen::LayoutScale; 133 using ImGuiFullscreen::LoadTexture; 134 using ImGuiFullscreen::MenuButton; 135 using ImGuiFullscreen::MenuButtonFrame; 136 using ImGuiFullscreen::MenuButtonWithoutSummary; 137 using ImGuiFullscreen::MenuButtonWithValue; 138 using ImGuiFullscreen::MenuHeading; 139 using ImGuiFullscreen::MenuHeadingButton; 140 using ImGuiFullscreen::MenuImageButton; 141 using ImGuiFullscreen::ModAlpha; 142 using ImGuiFullscreen::MulAlpha; 143 using ImGuiFullscreen::NavButton; 144 using ImGuiFullscreen::NavTab; 145 using ImGuiFullscreen::NavTitle; 146 using ImGuiFullscreen::OpenChoiceDialog; 147 using ImGuiFullscreen::OpenConfirmMessageDialog; 148 using ImGuiFullscreen::OpenFileSelector; 149 using ImGuiFullscreen::OpenInputStringDialog; 150 using ImGuiFullscreen::PopPrimaryColor; 151 using ImGuiFullscreen::PushPrimaryColor; 152 using ImGuiFullscreen::QueueResetFocus; 153 using ImGuiFullscreen::RangeButton; 154 using ImGuiFullscreen::ResetFocusHere; 155 using ImGuiFullscreen::RightAlignNavButtons; 156 using ImGuiFullscreen::SetFullscreenFooterText; 157 using ImGuiFullscreen::ShowToast; 158 using ImGuiFullscreen::ThreeWayToggleButton; 159 using ImGuiFullscreen::ToggleButton; 160 using ImGuiFullscreen::WantsToCloseMenu; 161 162 #ifndef __ANDROID__ 163 namespace FullscreenUI { 164 enum class MainWindowType 165 { 166 None, 167 Landing, 168 StartGame, 169 Exit, 170 GameList, 171 GameListSettings, 172 Settings, 173 PauseMenu, 174 Achievements, 175 Leaderboards, 176 }; 177 178 enum class PauseSubMenu 179 { 180 None, 181 Exit, 182 Achievements, 183 }; 184 185 enum class SettingsPage 186 { 187 Summary, 188 Interface, 189 Console, 190 Emulation, 191 BIOS, 192 Controller, 193 Hotkey, 194 MemoryCards, 195 Display, 196 PostProcessing, 197 Audio, 198 Achievements, 199 Advanced, 200 Count 201 }; 202 203 enum class GameListView 204 { 205 Grid, 206 List, 207 Count 208 }; 209 210 struct PostProcessingStageInfo 211 { 212 std::string name; 213 std::vector<PostProcessing::ShaderOption> options; 214 }; 215 216 ////////////////////////////////////////////////////////////////////////// 217 // Main 218 ////////////////////////////////////////////////////////////////////////// 219 static void PauseForMenuOpen(bool set_pause_menu_open); 220 static bool AreAnyDialogsOpen(); 221 static void ClosePauseMenu(); 222 static void OpenPauseSubMenu(PauseSubMenu submenu); 223 static void DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size); 224 static void DrawLandingWindow(); 225 static void DrawStartGameWindow(); 226 static void DrawExitWindow(); 227 static void DrawPauseMenu(); 228 static void ExitFullscreenAndOpenURL(std::string_view url); 229 static void CopyTextToClipboard(std::string title, std::string_view text); 230 static void DrawAboutWindow(); 231 static void OpenAboutWindow(); 232 static void FixStateIfPaused(); 233 static void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel); 234 235 static MainWindowType s_current_main_window = MainWindowType::None; 236 static PauseSubMenu s_current_pause_submenu = PauseSubMenu::None; 237 static std::string s_current_game_subtitle; 238 static bool s_initialized = false; 239 static bool s_tried_to_initialize = false; 240 static bool s_pause_menu_was_open = false; 241 static bool s_was_paused_on_quick_menu_open = false; 242 static bool s_about_window_open = false; 243 244 ////////////////////////////////////////////////////////////////////////// 245 // Resources 246 ////////////////////////////////////////////////////////////////////////// 247 static bool LoadResources(); 248 static void DestroyResources(); 249 250 static std::shared_ptr<GPUTexture> s_app_icon_texture; 251 static std::array<std::shared_ptr<GPUTexture>, static_cast<u32>(GameDatabase::CompatibilityRating::Count)> 252 s_game_compatibility_textures; 253 static std::shared_ptr<GPUTexture> s_fallback_disc_texture; 254 static std::shared_ptr<GPUTexture> s_fallback_exe_texture; 255 static std::shared_ptr<GPUTexture> s_fallback_psf_texture; 256 static std::shared_ptr<GPUTexture> s_fallback_playlist_texture; 257 258 ////////////////////////////////////////////////////////////////////////// 259 // Landing 260 ////////////////////////////////////////////////////////////////////////// 261 static void SwitchToLanding(); 262 static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters(); 263 static void DoStartPath(std::string path, std::string state = std::string(), 264 std::optional<bool> fast_boot = std::nullopt); 265 static void DoResume(); 266 static void DoStartFile(); 267 static void DoStartBIOS(); 268 static void DoStartDisc(std::string path); 269 static void DoStartDisc(); 270 static void DoToggleFastForward(); 271 static void ConfirmIfSavingMemoryCards(std::string_view action, std::function<void(bool)> callback); 272 static void RequestShutdown(bool save_state); 273 static void RequestReset(); 274 static void DoChangeDiscFromFile(); 275 static void DoChangeDisc(); 276 static void DoRequestExit(); 277 static void DoDesktopMode(); 278 static void DoToggleFullscreen(); 279 static void DoCheatsMenu(); 280 static void DoToggleAnalogMode(); 281 282 ////////////////////////////////////////////////////////////////////////// 283 // Settings 284 ////////////////////////////////////////////////////////////////////////// 285 286 static constexpr double INPUT_BINDING_TIMEOUT_SECONDS = 5.0; 287 288 static void SwitchToSettings(); 289 static void SwitchToGameSettings(); 290 static void SwitchToGameSettings(const GameList::Entry* entry); 291 static void SwitchToGameSettingsForPath(const std::string& path); 292 static void SwitchToGameSettingsForSerial(std::string_view serial); 293 static void DrawSettingsWindow(); 294 static void DrawSummarySettingsPage(); 295 static void DrawInterfaceSettingsPage(); 296 static void DrawBIOSSettingsPage(); 297 static void DrawConsoleSettingsPage(); 298 static void DrawEmulationSettingsPage(); 299 static void DrawDisplaySettingsPage(); 300 static void DrawPostProcessingSettingsPage(); 301 static void DrawAudioSettingsPage(); 302 static void DrawMemoryCardSettingsPage(); 303 static void DrawControllerSettingsPage(); 304 static void DrawHotkeySettingsPage(); 305 static void DrawAchievementsSettingsPage(); 306 static void DrawAdvancedSettingsPage(); 307 308 static bool IsEditingGameSettings(SettingsInterface* bsi); 309 static SettingsInterface* GetEditingSettingsInterface(); 310 static SettingsInterface* GetEditingSettingsInterface(bool game_settings); 311 static void SetSettingsChanged(SettingsInterface* bsi); 312 static bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value); 313 static s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value); 314 static u32 GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key, u32 default_value); 315 static float GetEffectiveFloatSetting(SettingsInterface* bsi, const char* section, const char* key, 316 float default_value); 317 static TinyString GetEffectiveTinyStringSetting(SettingsInterface* bsi, const char* section, const char* key, 318 const char* default_value); 319 static void DoCopyGameSettings(); 320 static void DoClearGameSettings(); 321 static void CopyGlobalControllerSettingsToGame(); 322 static void ResetControllerSettings(); 323 static void DoLoadInputProfile(); 324 static void DoSaveInputProfile(); 325 static void DoSaveNewInputProfile(); 326 static void DoSaveInputProfile(const std::string& name); 327 328 static bool DrawToggleSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 329 const char* key, bool default_value, bool enabled = true, bool allow_tristate = true, 330 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, 331 ImFont* summary_font = g_medium_font); 332 static void DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 333 const char* key, int default_value, const char* const* options, size_t option_count, 334 bool translate_options, int option_offset = 0, bool enabled = true, 335 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, 336 ImFont* summary_font = g_medium_font, const char* tr_context = TR_CONTEXT); 337 static void DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 338 const char* key, int default_value, int min_value, int max_value, 339 const char* format = "%d", bool enabled = true, 340 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, 341 ImFont* summary_font = g_medium_font); 342 static void DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 343 const char* key, int default_value, int min_value, int max_value, int step_value, 344 const char* format = "%d", bool enabled = true, 345 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 346 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 347 static void DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 348 const char* key, float default_value, float min_value, float max_value, 349 const char* format = "%f", float multiplier = 1.0f, bool enabled = true, 350 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 351 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 352 static void DrawFloatSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 353 const char* key, float default_value, float min_value, float max_value, 354 float step_value, float multiplier, const char* format = "%f", bool enabled = true, 355 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 356 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 357 #if 0 358 static void DrawIntRectSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 359 const char* left_key, int default_left, const char* top_key, int default_top, 360 const char* right_key, int default_right, const char* bottom_key, int default_bottom, 361 int min_value, int max_value, const char* format = "%d", bool enabled = true, 362 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, 363 ImFont* summary_font = g_medium_font); 364 static void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 365 const char* key, const char* default_value, const char* const* options, 366 const char* const* option_values, size_t option_count, bool enabled = true, 367 float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 368 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 369 #endif 370 template<typename DataType, typename SizeType> 371 static void DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 372 const char* key, DataType default_value, 373 std::optional<DataType> (*from_string_function)(const char* str), 374 const char* (*to_string_function)(DataType value), 375 const char* (*to_display_string_function)(DataType value), SizeType option_count, 376 bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 377 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 378 static void DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 379 const char* key, float default_value, const char* const* options, 380 const float* option_values, size_t option_count, bool translate_options, 381 bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 382 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 383 static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key, 384 const std::string& runtime_var, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 385 ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); 386 387 static void PopulateGraphicsAdapterList(); 388 static void PopulateGameListDirectoryCache(SettingsInterface* si); 389 static void PopulatePostProcessingChain(SettingsInterface* si, const char* section); 390 static void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, std::string_view section, 391 std::string_view key, std::string_view display_name); 392 static void DrawInputBindingWindow(); 393 static void DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section, 394 const char* name, const char* display_name, const char* icon_name, 395 bool show_type = true); 396 static void ClearInputBindingVariables(); 397 static void StartAutomaticBinding(u32 port); 398 399 static SettingsPage s_settings_page = SettingsPage::Interface; 400 static std::unique_ptr<INISettingsInterface> s_game_settings_interface; 401 static std::unique_ptr<GameList::Entry> s_game_settings_entry; 402 static std::vector<std::pair<std::string, bool>> s_game_list_directories_cache; 403 static GPUDevice::AdapterInfoList s_graphics_adapter_list_cache; 404 static std::vector<std::string> s_fullscreen_mode_list_cache; 405 static std::vector<PostProcessingStageInfo> s_postprocessing_stages; 406 static std::vector<const HotkeyInfo*> s_hotkey_list_cache; 407 static std::atomic_bool s_settings_changed{false}; 408 static std::atomic_bool s_game_settings_changed{false}; 409 static InputBindingInfo::Type s_input_binding_type = InputBindingInfo::Type::Unknown; 410 static std::string s_input_binding_section; 411 static std::string s_input_binding_key; 412 static std::string s_input_binding_display_name; 413 static std::vector<InputBindingKey> s_input_binding_new_bindings; 414 static std::vector<std::pair<InputBindingKey, std::pair<float, float>>> s_input_binding_value_ranges; 415 static Common::Timer s_input_binding_timer; 416 417 ////////////////////////////////////////////////////////////////////////// 418 // Save State List 419 ////////////////////////////////////////////////////////////////////////// 420 struct SaveStateListEntry 421 { 422 std::string title; 423 std::string summary; 424 std::string path; 425 std::unique_ptr<GPUTexture> preview_texture; 426 time_t timestamp; 427 s32 slot; 428 bool global; 429 }; 430 431 static void InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, const std::string& serial, s32 slot, 432 bool global); 433 static bool InitializeSaveStateListEntryFromSerial(SaveStateListEntry* li, const std::string& serial, s32 slot, 434 bool global); 435 static bool InitializeSaveStateListEntryFromPath(SaveStateListEntry* li, std::string path, s32 slot, bool global); 436 static void ClearSaveStateEntryList(); 437 static u32 PopulateSaveStateListEntries(const std::string& title, const std::string& serial); 438 static bool OpenLoadStateSelectorForGame(const std::string& game_path); 439 static bool OpenSaveStateSelector(bool is_loading); 440 static void CloseSaveStateSelector(); 441 static void DrawSaveStateSelector(bool is_loading); 442 static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry); 443 static void DrawResumeStateSelector(); 444 static void DoLoadState(std::string path); 445 static void DoSaveState(s32 slot, bool global); 446 447 static std::vector<SaveStateListEntry> s_save_state_selector_slots; 448 static std::string s_save_state_selector_game_path; 449 static s32 s_save_state_selector_submenu_index = -1; 450 static bool s_save_state_selector_open = false; 451 static bool s_save_state_selector_loading = true; 452 static bool s_save_state_selector_resuming = false; 453 454 ////////////////////////////////////////////////////////////////////////// 455 // Game List 456 ////////////////////////////////////////////////////////////////////////// 457 static void DrawGameListWindow(); 458 static void DrawGameList(const ImVec2& heading_size); 459 static void DrawGameGrid(const ImVec2& heading_size); 460 static void HandleGameListActivate(const GameList::Entry* entry); 461 static void HandleGameListOptions(const GameList::Entry* entry); 462 static void HandleSelectDiscForDiscSet(std::string_view disc_set_name); 463 static void DrawGameListSettingsWindow(); 464 static void SwitchToGameList(); 465 static void PopulateGameListEntryList(); 466 static GPUTexture* GetTextureForGameListEntryType(GameList::EntryType type); 467 static GPUTexture* GetGameListCover(const GameList::Entry* entry); 468 static GPUTexture* GetCoverForCurrentGame(); 469 470 // Lazily populated cover images. 471 static std::unordered_map<std::string, std::string> s_cover_image_map; 472 static std::vector<const GameList::Entry*> s_game_list_sorted_entries; 473 static GameListView s_game_list_view = GameListView::Grid; 474 } // namespace FullscreenUI 475 476 ////////////////////////////////////////////////////////////////////////// 477 // Utility 478 ////////////////////////////////////////////////////////////////////////// 479 480 void FullscreenUI::TimeToPrintableString(SmallStringBase* str, time_t t) 481 { 482 struct tm lt = {}; 483 #ifdef _MSC_VER 484 localtime_s(<, &t); 485 #else 486 localtime_r(&t, <); 487 #endif 488 489 char buf[256]; 490 std::strftime(buf, sizeof(buf), "%c", <); 491 str->assign(buf); 492 } 493 494 void FullscreenUI::GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel) 495 { 496 if (IsGamepadInputSource()) 497 { 498 ImGuiFullscreen::CreateFooterTextString( 499 dest, 500 std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), 501 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 502 std::make_pair(ICON_PF_BUTTON_B, back_instead_of_cancel ? FSUI_VSTR("Back") : FSUI_VSTR("Cancel"))}); 503 } 504 else 505 { 506 ImGuiFullscreen::CreateFooterTextString( 507 dest, std::array{std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), 508 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), 509 std::make_pair(ICON_PF_ESC, back_instead_of_cancel ? FSUI_VSTR("Back") : FSUI_VSTR("Cancel"))}); 510 } 511 } 512 513 void FullscreenUI::SetStandardSelectionFooterText(bool back_instead_of_cancel) 514 { 515 SmallString text; 516 GetStandardSelectionFooterText(text, back_instead_of_cancel); 517 ImGuiFullscreen::SetFullscreenFooterText(text); 518 } 519 520 void ImGuiFullscreen::GetChoiceDialogHelpText(SmallStringBase& dest) 521 { 522 FullscreenUI::GetStandardSelectionFooterText(dest, false); 523 } 524 525 void ImGuiFullscreen::GetFileSelectorHelpText(SmallStringBase& dest) 526 { 527 if (IsGamepadInputSource()) 528 { 529 ImGuiFullscreen::CreateFooterTextString( 530 dest, std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), 531 std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Parent Directory")), 532 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 533 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); 534 } 535 else 536 { 537 ImGuiFullscreen::CreateFooterTextString( 538 dest, 539 std::array{std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), 540 std::make_pair(ICON_PF_BACKSPACE, FSUI_VSTR("Parent Directory")), 541 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); 542 } 543 } 544 545 void ImGuiFullscreen::GetInputDialogHelpText(SmallStringBase& dest) 546 { 547 if (IsGamepadInputSource()) 548 { 549 CreateFooterTextString(dest, std::array{std::make_pair(ICON_PF_KEYBOARD, FSUI_VSTR("Enter Value")), 550 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 551 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); 552 } 553 else 554 { 555 CreateFooterTextString(dest, std::array{std::make_pair(ICON_PF_KEYBOARD, FSUI_VSTR("Enter Value")), 556 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), 557 std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); 558 } 559 } 560 561 ////////////////////////////////////////////////////////////////////////// 562 // Main 563 ////////////////////////////////////////////////////////////////////////// 564 565 bool FullscreenUI::Initialize() 566 { 567 if (s_initialized) 568 return true; 569 570 if (s_tried_to_initialize) 571 return false; 572 573 ImGuiFullscreen::SetTheme(Host::GetBaseBoolSettingValue("Main", "UseLightFullscreenUITheme", false)); 574 ImGuiFullscreen::UpdateLayoutScale(); 575 576 if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("images/placeholder.png") || 577 !LoadResources()) 578 { 579 DestroyResources(); 580 ImGuiFullscreen::Shutdown(); 581 s_tried_to_initialize = true; 582 return false; 583 } 584 585 s_initialized = true; 586 s_current_main_window = MainWindowType::None; 587 s_current_pause_submenu = PauseSubMenu::None; 588 s_pause_menu_was_open = false; 589 s_was_paused_on_quick_menu_open = false; 590 s_about_window_open = false; 591 s_hotkey_list_cache = InputManager::GetHotkeyList(); 592 593 if (!System::IsValid()) 594 SwitchToLanding(); 595 596 if (!System::IsRunning()) 597 Host::OnIdleStateChanged(); 598 599 ForceKeyNavEnabled(); 600 return true; 601 } 602 603 bool FullscreenUI::IsInitialized() 604 { 605 return s_initialized; 606 } 607 608 bool FullscreenUI::HasActiveWindow() 609 { 610 return s_initialized && (s_current_main_window != MainWindowType::None || AreAnyDialogsOpen()); 611 } 612 613 bool FullscreenUI::AreAnyDialogsOpen() 614 { 615 return (s_save_state_selector_open || s_about_window_open || 616 s_input_binding_type != InputBindingInfo::Type::Unknown || ImGuiFullscreen::IsChoiceDialogOpen() || 617 ImGuiFullscreen::IsFileSelectorOpen()); 618 } 619 620 void FullscreenUI::CheckForConfigChanges(const Settings& old_settings) 621 { 622 if (!IsInitialized()) 623 return; 624 625 // If achievements got disabled, we might have the menu open... 626 // That means we're going to be reading achievement state. 627 if (old_settings.achievements_enabled && !g_settings.achievements_enabled) 628 { 629 if (s_current_main_window == MainWindowType::Achievements || s_current_main_window == MainWindowType::Leaderboards) 630 ReturnToPreviousWindow(); 631 } 632 } 633 634 void FullscreenUI::OnSystemStarted() 635 { 636 if (!IsInitialized()) 637 return; 638 639 s_current_main_window = MainWindowType::None; 640 QueueResetFocus(FocusResetType::ViewChanged); 641 } 642 643 void FullscreenUI::OnSystemPaused() 644 { 645 // noop 646 } 647 648 void FullscreenUI::OnSystemResumed() 649 { 650 // get rid of pause menu if we unpaused another way 651 if (s_current_main_window == MainWindowType::PauseMenu) 652 ClosePauseMenu(); 653 } 654 655 void FullscreenUI::OnSystemDestroyed() 656 { 657 if (!IsInitialized()) 658 return; 659 660 s_pause_menu_was_open = false; 661 s_was_paused_on_quick_menu_open = false; 662 s_current_pause_submenu = PauseSubMenu::None; 663 SwitchToLanding(); 664 } 665 666 void FullscreenUI::OnRunningGameChanged() 667 { 668 if (!IsInitialized()) 669 return; 670 671 const std::string& path = System::GetDiscPath(); 672 const std::string& serial = System::GetGameSerial(); 673 if (!serial.empty()) 674 s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path)); 675 else 676 s_current_game_subtitle = {}; 677 } 678 679 void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open) 680 { 681 s_was_paused_on_quick_menu_open = (System::GetState() == System::State::Paused); 682 if (!s_was_paused_on_quick_menu_open) 683 Host::RunOnCPUThread([]() { System::PauseSystem(true); }); 684 685 s_pause_menu_was_open |= set_pause_menu_open; 686 } 687 688 void FullscreenUI::OpenPauseMenu() 689 { 690 if (!System::IsValid()) 691 return; 692 693 if (!Initialize() || s_current_main_window != MainWindowType::None) 694 return; 695 696 PauseForMenuOpen(true); 697 s_current_main_window = MainWindowType::PauseMenu; 698 s_current_pause_submenu = PauseSubMenu::None; 699 QueueResetFocus(FocusResetType::ViewChanged); 700 ForceKeyNavEnabled(); 701 FixStateIfPaused(); 702 } 703 704 void FullscreenUI::FixStateIfPaused() 705 { 706 if (!System::IsValid() || System::IsRunning()) 707 return; 708 709 // When we're paused, we won't have trickled the key up event for escape yet. Do it now. 710 ImGui::UpdateInputEvents(false); 711 712 Host::OnIdleStateChanged(); 713 Host::RunOnCPUThread([]() { 714 if (System::IsValid()) 715 { 716 // Why twice? To clear the "wants keyboard input" flag. 717 System::InvalidateDisplay(); 718 System::InvalidateDisplay(); 719 } 720 }); 721 } 722 723 void FullscreenUI::ClosePauseMenu() 724 { 725 if (!IsInitialized() || !System::IsValid()) 726 return; 727 728 if (System::GetState() == System::State::Paused && !s_was_paused_on_quick_menu_open) 729 Host::RunOnCPUThread([]() { System::PauseSystem(false); }); 730 731 s_current_main_window = MainWindowType::None; 732 s_current_pause_submenu = PauseSubMenu::None; 733 s_pause_menu_was_open = false; 734 QueueResetFocus(FocusResetType::ViewChanged); 735 FixStateIfPaused(); 736 } 737 738 void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu) 739 { 740 s_current_main_window = MainWindowType::PauseMenu; 741 s_current_pause_submenu = submenu; 742 QueueResetFocus(FocusResetType::ViewChanged); 743 } 744 745 void FullscreenUI::Shutdown() 746 { 747 Achievements::ClearUIState(); 748 CloseSaveStateSelector(); 749 s_cover_image_map.clear(); 750 s_game_list_sorted_entries = {}; 751 s_game_list_directories_cache = {}; 752 s_postprocessing_stages = {}; 753 s_fullscreen_mode_list_cache = {}; 754 s_graphics_adapter_list_cache = {}; 755 s_hotkey_list_cache = {}; 756 s_current_game_subtitle = {}; 757 DestroyResources(); 758 ImGuiFullscreen::Shutdown(); 759 s_initialized = false; 760 s_tried_to_initialize = false; 761 } 762 763 void FullscreenUI::Render() 764 { 765 if (!s_initialized) 766 return; 767 768 ImGuiFullscreen::UploadAsyncTextures(); 769 770 ImGuiFullscreen::BeginLayout(); 771 772 // Primed achievements must come first, because we don't want the pause screen to be behind them. 773 if (s_current_main_window == MainWindowType::None) 774 Achievements::DrawGameOverlays(); 775 776 switch (s_current_main_window) 777 { 778 case MainWindowType::Landing: 779 DrawLandingWindow(); 780 break; 781 case MainWindowType::StartGame: 782 DrawStartGameWindow(); 783 break; 784 case MainWindowType::Exit: 785 DrawExitWindow(); 786 break; 787 case MainWindowType::GameList: 788 DrawGameListWindow(); 789 break; 790 case MainWindowType::GameListSettings: 791 DrawGameListSettingsWindow(); 792 break; 793 case MainWindowType::Settings: 794 DrawSettingsWindow(); 795 break; 796 case MainWindowType::PauseMenu: 797 DrawPauseMenu(); 798 break; 799 case MainWindowType::Achievements: 800 Achievements::DrawAchievementsWindow(); 801 break; 802 case MainWindowType::Leaderboards: 803 Achievements::DrawLeaderboardsWindow(); 804 break; 805 default: 806 break; 807 } 808 809 if (s_save_state_selector_open) 810 { 811 if (s_save_state_selector_resuming) 812 DrawResumeStateSelector(); 813 else 814 DrawSaveStateSelector(s_save_state_selector_loading); 815 } 816 817 if (s_about_window_open) 818 DrawAboutWindow(); 819 820 if (s_input_binding_type != InputBindingInfo::Type::Unknown) 821 DrawInputBindingWindow(); 822 823 ImGuiFullscreen::EndLayout(); 824 825 if (s_settings_changed.load(std::memory_order_relaxed)) 826 { 827 Host::CommitBaseSettingChanges(); 828 Host::RunOnCPUThread([]() { System::ApplySettings(false); }); 829 s_settings_changed.store(false, std::memory_order_release); 830 } 831 if (s_game_settings_changed.load(std::memory_order_relaxed)) 832 { 833 if (s_game_settings_interface) 834 { 835 Error error; 836 s_game_settings_interface->RemoveEmptySections(); 837 838 if (s_game_settings_interface->IsEmpty()) 839 { 840 if (FileSystem::FileExists(s_game_settings_interface->GetFileName().c_str()) && 841 !FileSystem::DeleteFile(s_game_settings_interface->GetFileName().c_str(), &error)) 842 { 843 ImGuiFullscreen::OpenInfoMessageDialog( 844 FSUI_STR("Error"), fmt::format(FSUI_FSTR("An error occurred while deleting empty game settings:\n{}"), 845 error.GetDescription())); 846 } 847 } 848 else 849 { 850 if (!s_game_settings_interface->Save(&error)) 851 { 852 ImGuiFullscreen::OpenInfoMessageDialog( 853 FSUI_STR("Error"), 854 fmt::format(FSUI_FSTR("An error occurred while saving game settings:\n{}"), error.GetDescription())); 855 } 856 } 857 858 if (System::IsValid()) 859 Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); }); 860 } 861 s_game_settings_changed.store(false, std::memory_order_release); 862 } 863 864 ImGuiFullscreen::ResetCloseMenuIfNeeded(); 865 } 866 867 void FullscreenUI::InvalidateCoverCache() 868 { 869 if (!IsInitialized()) 870 return; 871 872 Host::RunOnCPUThread([]() { s_cover_image_map.clear(); }); 873 } 874 875 void FullscreenUI::ReturnToPreviousWindow() 876 { 877 if (System::IsValid() && s_pause_menu_was_open) 878 { 879 s_current_main_window = MainWindowType::PauseMenu; 880 QueueResetFocus(FocusResetType::ViewChanged); 881 } 882 else 883 { 884 ReturnToMainWindow(); 885 } 886 } 887 888 void FullscreenUI::ReturnToMainWindow() 889 { 890 ClosePauseMenu(); 891 s_current_main_window = System::IsValid() ? MainWindowType::None : MainWindowType::Landing; 892 FixStateIfPaused(); 893 } 894 895 bool FullscreenUI::LoadResources() 896 { 897 s_app_icon_texture = LoadTexture("images/duck.png"); 898 899 s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png"); 900 s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png"); 901 s_fallback_psf_texture = LoadTexture("fullscreenui/multimedia-player.png"); 902 s_fallback_playlist_texture = LoadTexture("fullscreenui/address-book-new.png"); 903 904 for (u32 i = 0; i < static_cast<u32>(GameDatabase::CompatibilityRating::Count); i++) 905 s_game_compatibility_textures[i] = LoadTexture(TinyString::from_format("fullscreenui/star-{}.png", i).c_str()); 906 907 return true; 908 } 909 910 void FullscreenUI::DestroyResources() 911 { 912 s_app_icon_texture.reset(); 913 s_fallback_playlist_texture.reset(); 914 s_fallback_psf_texture.reset(); 915 s_fallback_exe_texture.reset(); 916 s_fallback_disc_texture.reset(); 917 for (auto& tex : s_game_compatibility_textures) 918 tex.reset(); 919 } 920 921 ////////////////////////////////////////////////////////////////////////// 922 // Utility 923 ////////////////////////////////////////////////////////////////////////// 924 925 ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters() 926 { 927 return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", 928 "*.psexe", "*.ps-exe", "*.exe", "*.psf", "*.minipsf", "*.m3u", "*.pbp"}; 929 } 930 931 void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot) 932 { 933 if (System::IsValid()) 934 return; 935 936 SystemBootParameters params; 937 params.filename = std::move(path); 938 params.save_state = std::move(state); 939 params.override_fast_boot = std::move(fast_boot); 940 Host::RunOnCPUThread([params = std::move(params)]() { 941 if (System::IsValid()) 942 return; 943 944 Error error; 945 if (!System::BootSystem(std::move(params), &error)) 946 { 947 Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"), 948 fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error.GetDescription())); 949 } 950 }); 951 } 952 953 void FullscreenUI::DoResume() 954 { 955 std::string path = System::GetMostRecentResumeSaveStatePath(); 956 if (path.empty()) 957 { 958 ShowToast({}, FSUI_CSTR("No resume save state found.")); 959 return; 960 } 961 962 SaveStateListEntry slentry; 963 if (!InitializeSaveStateListEntryFromPath(&slentry, std::move(path), -1, false)) 964 return; 965 966 CloseSaveStateSelector(); 967 s_save_state_selector_slots.push_back(std::move(slentry)); 968 s_save_state_selector_game_path = {}; 969 s_save_state_selector_loading = true; 970 s_save_state_selector_open = true; 971 s_save_state_selector_resuming = true; 972 } 973 974 void FullscreenUI::DoStartFile() 975 { 976 auto callback = [](const std::string& path) { 977 if (!path.empty()) 978 DoStartPath(path); 979 980 CloseFileSelector(); 981 }; 982 983 OpenFileSelector(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), false, std::move(callback), 984 GetDiscImageFilters()); 985 } 986 987 void FullscreenUI::DoStartBIOS() 988 { 989 DoStartDisc(std::string()); 990 } 991 992 void FullscreenUI::DoStartDisc(std::string path) 993 { 994 Host::RunOnCPUThread([path = std::move(path)]() mutable { 995 if (System::IsValid()) 996 return; 997 998 Error error; 999 SystemBootParameters params; 1000 params.filename = std::move(path); 1001 if (!System::BootSystem(std::move(params), &error)) 1002 { 1003 Host::ReportErrorAsync(TRANSLATE_SV("System", "Error"), 1004 fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error.GetDescription())); 1005 } 1006 }); 1007 } 1008 1009 void FullscreenUI::DoStartDisc() 1010 { 1011 std::vector<std::pair<std::string, std::string>> devices = CDImage::GetDeviceList(); 1012 if (devices.empty()) 1013 { 1014 ShowToast(std::string(), 1015 FSUI_STR("Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient " 1016 "permissions to access it.")); 1017 return; 1018 } 1019 1020 // if there's only one, select it automatically 1021 if (devices.size() == 1) 1022 { 1023 DoStartDisc(std::move(devices.front().first)); 1024 return; 1025 } 1026 1027 ImGuiFullscreen::ChoiceDialogOptions options; 1028 std::vector<std::string> paths; 1029 options.reserve(devices.size()); 1030 paths.reserve(paths.size()); 1031 for (auto& [path, name] : devices) 1032 { 1033 options.emplace_back(std::move(name), false); 1034 paths.push_back(std::move(path)); 1035 } 1036 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Drive"), false, std::move(options), 1037 [paths = std::move(paths)](s32 index, const std::string&, bool) mutable { 1038 if (index < 0) 1039 return; 1040 1041 DoStartDisc(std::move(paths[index])); 1042 CloseChoiceDialog(); 1043 }); 1044 } 1045 1046 void FullscreenUI::ConfirmIfSavingMemoryCards(std::string_view action, std::function<void(bool)> callback) 1047 { 1048 if (!System::IsSavingMemoryCards()) 1049 { 1050 callback(true); 1051 return; 1052 } 1053 1054 OpenConfirmMessageDialog( 1055 FSUI_ICONSTR(ICON_PF_MEMORY_CARD, "Memory Card Busy"), 1056 fmt::format(FSUI_FSTR("WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY " 1057 "DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to " 1058 "finish saving.\n\nDo you want to {0} anyway?"), 1059 action), 1060 std::move(callback), 1061 fmt::format( 1062 fmt::runtime(FSUI_ICONSTR(ICON_FA_EXCLAMATION_TRIANGLE, "Yes, {} now and risk memory card corruption.")), action), 1063 FSUI_ICONSTR(ICON_FA_PLAY, "No, resume the game.")); 1064 } 1065 1066 void FullscreenUI::RequestShutdown(bool save_state) 1067 { 1068 ConfirmIfSavingMemoryCards(FSUI_VSTR("shut down"), [save_state](bool result) { 1069 if (result) 1070 Host::RunOnCPUThread([save_state]() { Host::RequestSystemShutdown(false, save_state); }); 1071 else 1072 ClosePauseMenu(); 1073 }); 1074 } 1075 1076 void FullscreenUI::RequestReset() 1077 { 1078 ConfirmIfSavingMemoryCards(FSUI_VSTR("reset"), [](bool result) { 1079 if (result) 1080 Host::RunOnCPUThread(System::ResetSystem); 1081 else 1082 ClosePauseMenu(); 1083 }); 1084 } 1085 1086 void FullscreenUI::DoToggleFastForward() 1087 { 1088 Host::RunOnCPUThread([]() { 1089 if (!System::IsValid()) 1090 return; 1091 1092 System::SetFastForwardEnabled(!System::IsFastForwardEnabled()); 1093 }); 1094 } 1095 1096 void FullscreenUI::DoChangeDiscFromFile() 1097 { 1098 ConfirmIfSavingMemoryCards(FSUI_VSTR("change disc"), [](bool result) { 1099 if (!result) 1100 { 1101 ClosePauseMenu(); 1102 return; 1103 } 1104 1105 auto callback = [](const std::string& path) { 1106 if (!path.empty()) 1107 { 1108 if (!GameList::IsScannableFilename(path)) 1109 { 1110 ShowToast({}, 1111 fmt::format(FSUI_FSTR("{} is not a valid disc image."), FileSystem::GetDisplayNameFromPath(path))); 1112 } 1113 else 1114 { 1115 Host::RunOnCPUThread([path]() { System::InsertMedia(path.c_str()); }); 1116 } 1117 } 1118 1119 CloseFileSelector(); 1120 ReturnToPreviousWindow(); 1121 }; 1122 1123 OpenFileSelector(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), false, std::move(callback), 1124 GetDiscImageFilters(), std::string(Path::GetDirectory(System::GetDiscPath()))); 1125 }); 1126 } 1127 1128 void FullscreenUI::DoChangeDisc() 1129 { 1130 ImGuiFullscreen::ChoiceDialogOptions options; 1131 1132 if (System::HasMediaSubImages()) 1133 { 1134 const u32 current_index = System::GetMediaSubImageIndex(); 1135 const u32 count = System::GetMediaSubImageCount(); 1136 options.reserve(count + 1); 1137 options.emplace_back(FSUI_STR("From File..."), false); 1138 1139 for (u32 i = 0; i < count; i++) 1140 options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index); 1141 1142 auto callback = [](s32 index, const std::string& title, bool checked) { 1143 if (index == 0) 1144 { 1145 CloseChoiceDialog(); 1146 DoChangeDiscFromFile(); 1147 return; 1148 } 1149 else if (index > 0) 1150 { 1151 System::SwitchMediaSubImage(static_cast<u32>(index - 1)); 1152 } 1153 1154 CloseChoiceDialog(); 1155 ReturnToPreviousWindow(); 1156 }; 1157 1158 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options), 1159 std::move(callback)); 1160 1161 return; 1162 } 1163 1164 if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty()) 1165 { 1166 const auto lock = GameList::GetLock(); 1167 auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials); 1168 if (matches.size() > 1) 1169 { 1170 options.reserve(matches.size() + 1); 1171 options.emplace_back(FSUI_STR("From File..."), false); 1172 1173 std::vector<std::string> paths; 1174 paths.reserve(matches.size()); 1175 1176 const std::string& current_path = System::GetDiscPath(); 1177 for (auto& [title, glentry] : matches) 1178 { 1179 options.emplace_back(std::move(title), current_path == glentry->path); 1180 paths.push_back(glentry->path); 1181 } 1182 1183 auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) { 1184 if (index == 0) 1185 { 1186 CloseChoiceDialog(); 1187 DoChangeDiscFromFile(); 1188 return; 1189 } 1190 else if (index > 0) 1191 { 1192 System::InsertMedia(paths[index - 1].c_str()); 1193 } 1194 1195 CloseChoiceDialog(); 1196 ReturnToMainWindow(); 1197 }; 1198 1199 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options), 1200 std::move(callback)); 1201 1202 return; 1203 } 1204 } 1205 1206 DoChangeDiscFromFile(); 1207 } 1208 1209 void FullscreenUI::DoCheatsMenu() 1210 { 1211 CheatList* cl = System::GetCheatList(); 1212 if (!cl) 1213 { 1214 if (!System::LoadCheatListFromDatabase() || ((cl = System::GetCheatList()) == nullptr)) 1215 { 1216 Host::AddKeyedOSDMessage("load_cheat_list", 1217 fmt::format(FSUI_FSTR("No cheats found for {}."), System::GetGameTitle()), 10.0f); 1218 ReturnToPreviousWindow(); 1219 return; 1220 } 1221 } 1222 1223 ImGuiFullscreen::ChoiceDialogOptions options; 1224 options.reserve(cl->GetCodeCount()); 1225 for (u32 i = 0; i < cl->GetCodeCount(); i++) 1226 { 1227 const CheatCode& cc = cl->GetCode(i); 1228 options.emplace_back(cc.description.c_str(), cc.enabled); 1229 } 1230 1231 auto callback = [](s32 index, const std::string& title, bool checked) { 1232 if (index < 0) 1233 { 1234 ReturnToPreviousWindow(); 1235 return; 1236 } 1237 1238 CheatList* cl = System::GetCheatList(); 1239 if (!cl) 1240 return; 1241 1242 const CheatCode& cc = cl->GetCode(static_cast<u32>(index)); 1243 if (cc.activation == CheatCode::Activation::Manual) 1244 cl->ApplyCode(static_cast<u32>(index)); 1245 else 1246 System::SetCheatCodeState(static_cast<u32>(index), checked); 1247 }; 1248 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_FROWN, "Cheat List"), true, std::move(options), std::move(callback)); 1249 } 1250 1251 void FullscreenUI::DoToggleAnalogMode() 1252 { 1253 // hacky way to toggle analog mode 1254 for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) 1255 { 1256 Controller* ctrl = System::GetController(i); 1257 if (!ctrl) 1258 continue; 1259 1260 const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctrl->GetType()); 1261 if (!cinfo) 1262 continue; 1263 1264 for (const Controller::ControllerBindingInfo& bi : cinfo->bindings) 1265 { 1266 if (std::strcmp(bi.name, "Analog") == 0) 1267 { 1268 ctrl->SetBindState(bi.bind_index, 1.0f); 1269 ctrl->SetBindState(bi.bind_index, 0.0f); 1270 break; 1271 } 1272 } 1273 } 1274 } 1275 1276 void FullscreenUI::DoRequestExit() 1277 { 1278 Host::RunOnCPUThread([]() { Host::RequestExitApplication(true); }); 1279 } 1280 1281 void FullscreenUI::DoDesktopMode() 1282 { 1283 Host::RunOnCPUThread([]() { Host::RequestExitBigPicture(); }); 1284 } 1285 1286 void FullscreenUI::DoToggleFullscreen() 1287 { 1288 Host::RunOnCPUThread([]() { Host::SetFullscreen(!Host::IsFullscreen()); }); 1289 } 1290 1291 ////////////////////////////////////////////////////////////////////////// 1292 // Landing Window 1293 ////////////////////////////////////////////////////////////////////////// 1294 1295 void FullscreenUI::SwitchToLanding() 1296 { 1297 s_current_main_window = MainWindowType::Landing; 1298 QueueResetFocus(FocusResetType::ViewChanged); 1299 } 1300 1301 void FullscreenUI::DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size) 1302 { 1303 const ImGuiIO& io = ImGui::GetIO(); 1304 const ImVec2 heading_size = 1305 ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + 1306 (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); 1307 *menu_pos = ImVec2(0.0f, heading_size.y); 1308 *menu_size = ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)); 1309 1310 if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "landing_heading", UIPrimaryColor)) 1311 { 1312 ImFont* const heading_font = g_large_font; 1313 ImDrawList* const dl = ImGui::GetWindowDrawList(); 1314 SmallString heading_str; 1315 1316 ImGui::PushFont(heading_font); 1317 ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); 1318 1319 // draw branding 1320 { 1321 const ImVec2 logo_pos = LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING); 1322 const ImVec2 logo_size = LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 1323 dl->AddImage(s_app_icon_texture.get(), logo_pos, logo_pos + logo_size); 1324 dl->AddText(heading_font, heading_font->FontSize, 1325 ImVec2(logo_pos.x + logo_size.x + LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), logo_pos.y), 1326 ImGui::GetColorU32(ImGuiCol_Text), "DuckStation"); 1327 } 1328 1329 // draw time 1330 ImVec2 time_pos; 1331 { 1332 heading_str.format(FSUI_FSTR("{:%H:%M}"), fmt::localtime(std::time(nullptr))); 1333 1334 const ImVec2 time_size = heading_font->CalcTextSizeA(heading_font->FontSize, FLT_MAX, 0.0f, "00:00"); 1335 time_pos = ImVec2(heading_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) - time_size.x, 1336 LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING)); 1337 ImGui::RenderTextClipped(time_pos, time_pos + time_size, heading_str.c_str(), heading_str.end_ptr(), &time_size); 1338 } 1339 1340 // draw achievements info 1341 if (Achievements::IsActive()) 1342 { 1343 const auto lock = Achievements::GetLock(); 1344 const char* username = Achievements::GetLoggedInUserName(); 1345 if (username) 1346 { 1347 const ImVec2 name_size = heading_font->CalcTextSizeA(heading_font->FontSize, FLT_MAX, 0.0f, username); 1348 const ImVec2 name_pos = 1349 ImVec2(time_pos.x - name_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), time_pos.y); 1350 ImGui::RenderTextClipped(name_pos, name_pos + name_size, username, nullptr, &name_size); 1351 1352 // TODO: should we cache this? heap allocations bad... 1353 std::string badge_path = Achievements::GetLoggedInUserBadgePath(); 1354 if (!badge_path.empty()) [[likely]] 1355 { 1356 const ImVec2 badge_size = 1357 LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 1358 const ImVec2 badge_pos = 1359 ImVec2(name_pos.x - badge_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING), time_pos.y); 1360 1361 dl->AddImage(reinterpret_cast<ImTextureID>(GetCachedTextureAsync(badge_path)), badge_pos, 1362 badge_pos + badge_size); 1363 } 1364 } 1365 } 1366 1367 ImGui::PopStyleColor(); 1368 ImGui::PopFont(); 1369 } 1370 EndFullscreenWindow(); 1371 } 1372 1373 void FullscreenUI::DrawLandingWindow() 1374 { 1375 ImVec2 menu_pos, menu_size; 1376 DrawLandingTemplate(&menu_pos, &menu_size); 1377 1378 ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); 1379 1380 if (BeginHorizontalMenu("landing_window", menu_pos, menu_size, 4)) 1381 { 1382 ResetFocusHere(); 1383 1384 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/address-book-new.png"), FSUI_CSTR("Game List"), 1385 FSUI_CSTR("Launch a game from images scanned from your game directories."))) 1386 { 1387 SwitchToGameList(); 1388 } 1389 1390 if (HorizontalMenuItem( 1391 GetCachedTexture("fullscreenui/media-cdrom.png"), FSUI_CSTR("Start Game"), 1392 FSUI_CSTR("Launch a game from a file, disc, or starts the console without any disc inserted."))) 1393 { 1394 s_current_main_window = MainWindowType::StartGame; 1395 QueueResetFocus(FocusResetType::ViewChanged); 1396 } 1397 1398 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/applications-system.png"), FSUI_CSTR("Settings"), 1399 FSUI_CSTR("Changes settings for the application."))) 1400 { 1401 SwitchToSettings(); 1402 } 1403 1404 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit"), 1405 FSUI_CSTR("Return to desktop mode, or exit the application.")) || 1406 (!AreAnyDialogsOpen() && WantsToCloseMenu())) 1407 { 1408 s_current_main_window = MainWindowType::Exit; 1409 QueueResetFocus(FocusResetType::ViewChanged); 1410 } 1411 } 1412 EndHorizontalMenu(); 1413 1414 ImGui::PopStyleColor(); 1415 1416 if (!AreAnyDialogsOpen()) 1417 { 1418 if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) 1419 OpenAboutWindow(); 1420 else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F3, false)) 1421 DoResume(); 1422 else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false) || ImGui::IsKeyPressed(ImGuiKey_F11, false)) 1423 DoToggleFullscreen(); 1424 } 1425 1426 if (IsGamepadInputSource()) 1427 { 1428 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_BURGER_MENU, FSUI_VSTR("About")), 1429 std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Resume Last Session")), 1430 std::make_pair(ICON_PF_BUTTON_X, FSUI_VSTR("Toggle Fullscreen")), 1431 std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), 1432 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 1433 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Exit"))}); 1434 } 1435 else 1436 { 1437 SetFullscreenFooterText(std::array{ 1438 std::make_pair(ICON_PF_F1, FSUI_VSTR("About")), std::make_pair(ICON_PF_F3, FSUI_VSTR("Resume Last Session")), 1439 std::make_pair(ICON_PF_F11, FSUI_VSTR("Toggle Fullscreen")), 1440 std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Navigate")), 1441 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Exit"))}); 1442 } 1443 } 1444 1445 void FullscreenUI::DrawStartGameWindow() 1446 { 1447 ImVec2 menu_pos, menu_size; 1448 DrawLandingTemplate(&menu_pos, &menu_size); 1449 1450 ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); 1451 1452 if (BeginHorizontalMenu("start_game_window", menu_pos, menu_size, 4)) 1453 { 1454 ResetFocusHere(); 1455 1456 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-file.png"), FSUI_CSTR("Start File"), 1457 FSUI_CSTR("Launch a game by selecting a file/disc image."))) 1458 { 1459 DoStartFile(); 1460 } 1461 1462 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/drive-cdrom.png"), FSUI_CSTR("Start Disc"), 1463 FSUI_CSTR("Start a game from a disc in your PC's DVD drive."))) 1464 { 1465 DoStartDisc(); 1466 } 1467 1468 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-bios.png"), FSUI_CSTR("Start BIOS"), 1469 FSUI_CSTR("Start the console without any disc inserted."))) 1470 { 1471 DoStartBIOS(); 1472 } 1473 1474 // https://www.iconpacks.net/free-icon/arrow-back-3783.html 1475 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), 1476 FSUI_CSTR("Return to the previous menu.")) || 1477 (!AreAnyDialogsOpen() && WantsToCloseMenu())) 1478 { 1479 s_current_main_window = MainWindowType::Landing; 1480 QueueResetFocus(FocusResetType::ViewChanged); 1481 } 1482 } 1483 EndHorizontalMenu(); 1484 1485 ImGui::PopStyleColor(); 1486 1487 if (!AreAnyDialogsOpen()) 1488 { 1489 if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) 1490 OpenSaveStateSelector(true); 1491 } 1492 1493 if (IsGamepadInputSource()) 1494 { 1495 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), 1496 std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Load Global State")), 1497 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 1498 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); 1499 } 1500 else 1501 { 1502 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Navigate")), 1503 std::make_pair(ICON_PF_F1, FSUI_VSTR("Load Global State")), 1504 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), 1505 std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); 1506 } 1507 } 1508 1509 void FullscreenUI::DrawExitWindow() 1510 { 1511 ImVec2 menu_pos, menu_size; 1512 DrawLandingTemplate(&menu_pos, &menu_size); 1513 1514 ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); 1515 1516 if (BeginHorizontalMenu("exit_window", menu_pos, menu_size, 3)) 1517 { 1518 ResetFocusHere(); 1519 1520 // https://www.iconpacks.net/free-icon/arrow-back-3783.html 1521 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), 1522 FSUI_CSTR("Return to the previous menu.")) || 1523 WantsToCloseMenu()) 1524 { 1525 s_current_main_window = MainWindowType::Landing; 1526 QueueResetFocus(FocusResetType::ViewChanged); 1527 } 1528 1529 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit DuckStation"), 1530 FSUI_CSTR("Completely exits the application, returning you to your desktop."))) 1531 { 1532 DoRequestExit(); 1533 } 1534 1535 if (HorizontalMenuItem(GetCachedTexture("fullscreenui/desktop-mode.png"), FSUI_CSTR("Desktop Mode"), 1536 FSUI_CSTR("Exits Big Picture mode, returning to the desktop interface."))) 1537 { 1538 DoDesktopMode(); 1539 } 1540 } 1541 EndHorizontalMenu(); 1542 1543 ImGui::PopStyleColor(); 1544 1545 SetStandardSelectionFooterText(true); 1546 } 1547 1548 bool FullscreenUI::IsEditingGameSettings(SettingsInterface* bsi) 1549 { 1550 return (bsi == s_game_settings_interface.get()); 1551 } 1552 1553 SettingsInterface* FullscreenUI::GetEditingSettingsInterface() 1554 { 1555 return s_game_settings_interface ? s_game_settings_interface.get() : Host::Internal::GetBaseSettingsLayer(); 1556 } 1557 1558 SettingsInterface* FullscreenUI::GetEditingSettingsInterface(bool game_settings) 1559 { 1560 return (game_settings && s_game_settings_interface) ? s_game_settings_interface.get() : 1561 Host::Internal::GetBaseSettingsLayer(); 1562 } 1563 1564 void FullscreenUI::SetSettingsChanged(SettingsInterface* bsi) 1565 { 1566 if (bsi && bsi == s_game_settings_interface.get()) 1567 s_game_settings_changed.store(true, std::memory_order_release); 1568 else 1569 s_settings_changed.store(true, std::memory_order_release); 1570 } 1571 1572 bool FullscreenUI::GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, 1573 bool default_value) 1574 { 1575 if (IsEditingGameSettings(bsi)) 1576 { 1577 std::optional<bool> value = bsi->GetOptionalBoolValue(section, key, std::nullopt); 1578 if (value.has_value()) 1579 return value.value(); 1580 } 1581 1582 return Host::Internal::GetBaseSettingsLayer()->GetBoolValue(section, key, default_value); 1583 } 1584 1585 s32 FullscreenUI::GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, 1586 s32 default_value) 1587 { 1588 if (IsEditingGameSettings(bsi)) 1589 { 1590 std::optional<s32> value = bsi->GetOptionalIntValue(section, key, std::nullopt); 1591 if (value.has_value()) 1592 return value.value(); 1593 } 1594 1595 return Host::Internal::GetBaseSettingsLayer()->GetIntValue(section, key, default_value); 1596 } 1597 1598 u32 FullscreenUI::GetEffectiveUIntSetting(SettingsInterface* bsi, const char* section, const char* key, 1599 u32 default_value) 1600 { 1601 if (IsEditingGameSettings(bsi)) 1602 { 1603 std::optional<u32> value = bsi->GetOptionalUIntValue(section, key, std::nullopt); 1604 if (value.has_value()) 1605 return value.value(); 1606 } 1607 1608 return Host::Internal::GetBaseSettingsLayer()->GetUIntValue(section, key, default_value); 1609 } 1610 1611 float FullscreenUI::GetEffectiveFloatSetting(SettingsInterface* bsi, const char* section, const char* key, 1612 float default_value) 1613 { 1614 if (IsEditingGameSettings(bsi)) 1615 { 1616 std::optional<float> value = bsi->GetOptionalFloatValue(section, key, std::nullopt); 1617 if (value.has_value()) 1618 return value.value(); 1619 } 1620 1621 return Host::Internal::GetBaseSettingsLayer()->GetFloatValue(section, key, default_value); 1622 } 1623 1624 TinyString FullscreenUI::GetEffectiveTinyStringSetting(SettingsInterface* bsi, const char* section, const char* key, 1625 const char* default_value) 1626 { 1627 TinyString ret; 1628 std::optional<TinyString> value; 1629 1630 if (IsEditingGameSettings(bsi)) 1631 value = bsi->GetOptionalTinyStringValue(section, key, std::nullopt); 1632 1633 if (value.has_value()) 1634 ret = std::move(value.value()); 1635 else 1636 ret = Host::Internal::GetBaseSettingsLayer()->GetTinyStringValue(section, key, default_value); 1637 1638 return ret; 1639 } 1640 1641 void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section, 1642 const char* name, const char* display_name, const char* icon_name, 1643 bool show_type) 1644 { 1645 if (type == InputBindingInfo::Type::Pointer) 1646 return; 1647 1648 TinyString title; 1649 title.format("{}/{}", section, name); 1650 1651 SmallString value = bsi->GetSmallStringValue(section, name); 1652 const bool oneline = value.count('&') <= 1; 1653 1654 ImRect bb; 1655 bool visible, hovered, clicked; 1656 clicked = MenuButtonFrame(title, true, 1657 oneline ? ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY : 1658 ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, 1659 &visible, &hovered, &bb.Min, &bb.Max); 1660 if (!visible) 1661 return; 1662 1663 if (oneline) 1664 InputManager::PrettifyInputBinding(value); 1665 1666 if (show_type) 1667 { 1668 if (icon_name) 1669 { 1670 title.format("{} {}", icon_name, display_name); 1671 } 1672 else 1673 { 1674 switch (type) 1675 { 1676 case InputBindingInfo::Type::Button: 1677 title.format(ICON_FA_DOT_CIRCLE " {}", display_name); 1678 break; 1679 case InputBindingInfo::Type::Axis: 1680 case InputBindingInfo::Type::HalfAxis: 1681 title.format(ICON_FA_BULLSEYE " {}", display_name); 1682 break; 1683 case InputBindingInfo::Type::Motor: 1684 title.format(ICON_FA_BELL " {}", display_name); 1685 break; 1686 case InputBindingInfo::Type::Macro: 1687 title.format(ICON_FA_PIZZA_SLICE " {}", display_name); 1688 break; 1689 default: 1690 title = display_name; 1691 break; 1692 } 1693 } 1694 } 1695 1696 const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f); 1697 1698 if (oneline) 1699 { 1700 ImGui::PushFont(g_large_font); 1701 1702 const ImVec2 value_size(ImGui::CalcTextSize(value.empty() ? FSUI_CSTR("-") : value.c_str(), nullptr)); 1703 const float text_end = bb.Max.x - value_size.x; 1704 const ImRect title_bb(bb.Min, ImVec2(text_end, midpoint)); 1705 1706 ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, show_type ? title.c_str() : display_name, nullptr, nullptr, 1707 ImVec2(0.0f, 0.0f), &title_bb); 1708 ImGui::RenderTextClipped(bb.Min, bb.Max, value.empty() ? FSUI_CSTR("-") : value.c_str(), nullptr, &value_size, 1709 ImVec2(1.0f, 0.5f), &bb); 1710 ImGui::PopFont(); 1711 } 1712 else 1713 { 1714 const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint)); 1715 const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), bb.Max); 1716 1717 ImGui::PushFont(g_large_font); 1718 ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, show_type ? title.c_str() : display_name, nullptr, nullptr, 1719 ImVec2(0.0f, 0.0f), &title_bb); 1720 ImGui::PopFont(); 1721 1722 ImGui::PushFont(g_medium_font); 1723 ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, value.empty() ? FSUI_CSTR("No Binding") : value.c_str(), 1724 nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); 1725 ImGui::PopFont(); 1726 } 1727 1728 if (clicked) 1729 { 1730 BeginInputBinding(bsi, type, section, name, display_name); 1731 } 1732 else if (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false)) 1733 { 1734 bsi->DeleteValue(section, name); 1735 SetSettingsChanged(bsi); 1736 } 1737 } 1738 1739 void FullscreenUI::ClearInputBindingVariables() 1740 { 1741 s_input_binding_type = InputBindingInfo::Type::Unknown; 1742 s_input_binding_section = {}; 1743 s_input_binding_key = {}; 1744 s_input_binding_display_name = {}; 1745 s_input_binding_new_bindings = {}; 1746 s_input_binding_value_ranges = {}; 1747 } 1748 1749 void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, std::string_view section, 1750 std::string_view key, std::string_view display_name) 1751 { 1752 if (s_input_binding_type != InputBindingInfo::Type::Unknown) 1753 { 1754 InputManager::RemoveHook(); 1755 ClearInputBindingVariables(); 1756 } 1757 1758 s_input_binding_type = type; 1759 s_input_binding_section = section; 1760 s_input_binding_key = key; 1761 s_input_binding_display_name = display_name; 1762 s_input_binding_new_bindings = {}; 1763 s_input_binding_value_ranges = {}; 1764 s_input_binding_timer.Reset(); 1765 1766 const bool game_settings = IsEditingGameSettings(bsi); 1767 1768 InputManager::SetHook([game_settings](InputBindingKey key, float value) -> InputInterceptHook::CallbackResult { 1769 if (s_input_binding_type == InputBindingInfo::Type::Unknown) 1770 return InputInterceptHook::CallbackResult::StopProcessingEvent; 1771 1772 // holding the settings lock here will protect the input binding list 1773 auto lock = Host::GetSettingsLock(); 1774 1775 float initial_value = value; 1776 float min_value = value; 1777 auto it = std::find_if(s_input_binding_value_ranges.begin(), s_input_binding_value_ranges.end(), 1778 [key](const auto& it) { return it.first.bits == key.bits; }); 1779 if (it != s_input_binding_value_ranges.end()) 1780 { 1781 initial_value = it->second.first; 1782 min_value = it->second.second = std::min(it->second.second, value); 1783 } 1784 else 1785 { 1786 s_input_binding_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value)); 1787 } 1788 1789 const float abs_value = std::abs(value); 1790 const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f); 1791 1792 for (InputBindingKey& other_key : s_input_binding_new_bindings) 1793 { 1794 // if this key is in our new binding list, it's a "release", and we're done 1795 if (other_key.MaskDirection() == key.MaskDirection()) 1796 { 1797 // for pedals, we wait for it to go back to near its starting point to commit the binding 1798 if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f))) 1799 { 1800 // did we go the full range? 1801 if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f) 1802 other_key.modifier = InputModifier::FullAxis; 1803 1804 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 1805 const std::string new_binding(InputManager::ConvertInputBindingKeysToString( 1806 s_input_binding_type, s_input_binding_new_bindings.data(), s_input_binding_new_bindings.size())); 1807 bsi->SetStringValue(s_input_binding_section.c_str(), s_input_binding_key.c_str(), new_binding.c_str()); 1808 SetSettingsChanged(bsi); 1809 ClearInputBindingVariables(); 1810 return InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent; 1811 } 1812 1813 // otherwise, keep waiting 1814 return InputInterceptHook::CallbackResult::StopProcessingEvent; 1815 } 1816 } 1817 1818 // new binding, add it to the list, but wait for a decent distance first, and then wait for release 1819 if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f))) 1820 { 1821 InputBindingKey key_to_add = key; 1822 key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None; 1823 key_to_add.invert = reverse_threshold; 1824 s_input_binding_new_bindings.push_back(key_to_add); 1825 } 1826 1827 return InputInterceptHook::CallbackResult::StopProcessingEvent; 1828 }); 1829 } 1830 1831 void FullscreenUI::DrawInputBindingWindow() 1832 { 1833 DebugAssert(s_input_binding_type != InputBindingInfo::Type::Unknown); 1834 1835 const double time_remaining = INPUT_BINDING_TIMEOUT_SECONDS - s_input_binding_timer.GetTimeSeconds(); 1836 if (time_remaining <= 0.0) 1837 { 1838 InputManager::RemoveHook(); 1839 ClearInputBindingVariables(); 1840 return; 1841 } 1842 1843 const char* title = FSUI_ICONSTR(ICON_FA_GAMEPAD, "Set Input Binding"); 1844 ImGui::SetNextWindowSize(LayoutScale(500.0f, 0.0f)); 1845 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 1846 ImGui::OpenPopup(title); 1847 1848 ImGui::PushFont(g_large_font); 1849 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 1850 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 1851 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 1852 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 1853 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 1854 1855 if (ImGui::BeginPopupModal(title, nullptr, 1856 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs)) 1857 { 1858 ImGui::TextWrapped("%s", SmallString::from_format(FSUI_FSTR("Setting {} binding {}."), s_input_binding_section, 1859 s_input_binding_display_name) 1860 .c_str()); 1861 ImGui::TextUnformatted(FSUI_CSTR("Push a controller button or axis now.")); 1862 ImGui::NewLine(); 1863 ImGui::TextUnformatted(SmallString::from_format(FSUI_FSTR("Timing out in {:.0f} seconds..."), time_remaining)); 1864 ImGui::EndPopup(); 1865 } 1866 1867 ImGui::PopStyleVar(4); 1868 ImGui::PopFont(); 1869 } 1870 1871 bool FullscreenUI::DrawToggleSetting(SettingsInterface* bsi, const char* title, const char* summary, 1872 const char* section, const char* key, bool default_value, bool enabled, 1873 bool allow_tristate, float height, ImFont* font, ImFont* summary_font) 1874 { 1875 if (!allow_tristate || !IsEditingGameSettings(bsi)) 1876 { 1877 bool value = bsi->GetBoolValue(section, key, default_value); 1878 if (!ToggleButton(title, summary, &value, enabled, height, font, summary_font)) 1879 return false; 1880 1881 bsi->SetBoolValue(section, key, value); 1882 } 1883 else 1884 { 1885 std::optional<bool> value(false); 1886 if (!bsi->GetBoolValue(section, key, &value.value())) 1887 value.reset(); 1888 if (!ThreeWayToggleButton(title, summary, &value, enabled, height, font, summary_font)) 1889 return false; 1890 1891 if (value.has_value()) 1892 bsi->SetBoolValue(section, key, value.value()); 1893 else 1894 bsi->DeleteValue(section, key); 1895 } 1896 1897 SetSettingsChanged(bsi); 1898 return true; 1899 } 1900 1901 void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, 1902 const char* section, const char* key, int default_value, 1903 const char* const* options, size_t option_count, bool translate_options, 1904 int option_offset, bool enabled, float height, ImFont* font, ImFont* summary_font, 1905 const char* tr_context) 1906 { 1907 const bool game_settings = IsEditingGameSettings(bsi); 1908 1909 if (options && option_count == 0) 1910 { 1911 while (options[option_count] != nullptr) 1912 option_count++; 1913 } 1914 1915 const std::optional<int> value = 1916 bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value)); 1917 const int index = value.has_value() ? (value.value() - option_offset) : std::numeric_limits<int>::min(); 1918 const char* value_text = 1919 (value.has_value()) ? 1920 ((index < 0 || static_cast<size_t>(index) >= option_count) ? 1921 FSUI_CSTR("Unknown") : 1922 (translate_options ? Host::TranslateToCString(tr_context, options[index]) : options[index])) : 1923 FSUI_CSTR("Use Global Setting"); 1924 1925 if (MenuButtonWithValue(title, summary, value_text, enabled, height, font, summary_font)) 1926 { 1927 ImGuiFullscreen::ChoiceDialogOptions cd_options; 1928 cd_options.reserve(option_count + 1); 1929 if (game_settings) 1930 cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value()); 1931 for (size_t i = 0; i < option_count; i++) 1932 { 1933 cd_options.emplace_back(translate_options ? Host::TranslateToString(tr_context, options[i]) : 1934 std::string(options[i]), 1935 (i == static_cast<size_t>(index))); 1936 } 1937 OpenChoiceDialog(title, false, std::move(cd_options), 1938 [game_settings, section = TinyString(section), key = TinyString(key), 1939 option_offset](s32 index, const std::string& title, bool checked) { 1940 if (index >= 0) 1941 { 1942 auto lock = Host::GetSettingsLock(); 1943 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 1944 if (game_settings) 1945 { 1946 if (index == 0) 1947 bsi->DeleteValue(section, key); 1948 else 1949 bsi->SetIntValue(section, key, index - 1 + option_offset); 1950 } 1951 else 1952 { 1953 bsi->SetIntValue(section, key, index + option_offset); 1954 } 1955 1956 SetSettingsChanged(bsi); 1957 } 1958 1959 CloseChoiceDialog(); 1960 }); 1961 } 1962 } 1963 1964 void FullscreenUI::DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, 1965 const char* section, const char* key, int default_value, int min_value, 1966 int max_value, const char* format, bool enabled, float height, ImFont* font, 1967 ImFont* summary_font) 1968 { 1969 const bool game_settings = IsEditingGameSettings(bsi); 1970 const std::optional<int> value = 1971 bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value)); 1972 const SmallString value_text = 1973 value.has_value() ? SmallString::from_sprintf(format, value.value()) : SmallString(FSUI_VSTR("Use Global Setting")); 1974 1975 if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) 1976 ImGui::OpenPopup(title); 1977 1978 ImGui::SetNextWindowSize(LayoutScale(500.0f, 192.0f)); 1979 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 1980 1981 ImGui::PushFont(g_large_font); 1982 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 1983 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 1984 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 1985 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 1986 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 1987 1988 bool is_open = true; 1989 if (ImGui::BeginPopupModal(title, &is_open, 1990 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 1991 { 1992 BeginMenuButtons(); 1993 1994 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 1995 ImGui::SetNextItemWidth(end); 1996 s32 dlg_value = static_cast<s32>(value.value_or(default_value)); 1997 if (ImGui::SliderInt("##value", &dlg_value, min_value, max_value, format, ImGuiSliderFlags_NoInput)) 1998 { 1999 if (IsEditingGameSettings(bsi) && dlg_value == default_value) 2000 bsi->DeleteValue(section, key); 2001 else 2002 bsi->SetIntValue(section, key, dlg_value); 2003 2004 SetSettingsChanged(bsi); 2005 } 2006 2007 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2008 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 2009 ImVec2(0.5f, 0.0f))) 2010 { 2011 ImGui::CloseCurrentPopup(); 2012 } 2013 EndMenuButtons(); 2014 2015 ImGui::EndPopup(); 2016 } 2017 2018 ImGui::PopStyleVar(4); 2019 ImGui::PopFont(); 2020 } 2021 2022 void FullscreenUI::DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, 2023 const char* section, const char* key, float default_value, float min_value, 2024 float max_value, const char* format, float multiplier, bool enabled, 2025 float height, ImFont* font, ImFont* summary_font) 2026 { 2027 const bool game_settings = IsEditingGameSettings(bsi); 2028 const std::optional<float> value = 2029 bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value)); 2030 const SmallString value_text = value.has_value() ? SmallString::from_sprintf(format, value.value() * multiplier) : 2031 SmallString(FSUI_VSTR("Use Global Setting")); 2032 2033 if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) 2034 ImGui::OpenPopup(title); 2035 2036 ImGui::SetNextWindowSize(LayoutScale(500.0f, 192.0f)); 2037 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 2038 2039 ImGui::PushFont(g_large_font); 2040 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 2041 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 2042 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 2043 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 2044 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 2045 2046 bool is_open = true; 2047 if (ImGui::BeginPopupModal(title, &is_open, 2048 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 2049 { 2050 BeginMenuButtons(); 2051 2052 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 2053 ImGui::SetNextItemWidth(end); 2054 float dlg_value = value.value_or(default_value) * multiplier; 2055 if (ImGui::SliderFloat("##value", &dlg_value, min_value * multiplier, max_value * multiplier, format, 2056 ImGuiSliderFlags_NoInput)) 2057 { 2058 dlg_value /= multiplier; 2059 2060 if (IsEditingGameSettings(bsi) && dlg_value == default_value) 2061 bsi->DeleteValue(section, key); 2062 else 2063 bsi->SetFloatValue(section, key, dlg_value); 2064 2065 SetSettingsChanged(bsi); 2066 } 2067 2068 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2069 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 2070 ImVec2(0.5f, 0.0f))) 2071 { 2072 ImGui::CloseCurrentPopup(); 2073 } 2074 EndMenuButtons(); 2075 2076 ImGui::EndPopup(); 2077 } 2078 2079 ImGui::PopStyleVar(4); 2080 ImGui::PopFont(); 2081 } 2082 2083 void FullscreenUI::DrawFloatSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, 2084 const char* section, const char* key, float default_value, float min_value, 2085 float max_value, float step_value, float multiplier, const char* format, 2086 bool enabled, float height, ImFont* font, ImFont* summary_font) 2087 { 2088 const bool game_settings = IsEditingGameSettings(bsi); 2089 const std::optional<float> value = 2090 bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value)); 2091 const SmallString value_text = value.has_value() ? SmallString::from_sprintf(format, value.value() * multiplier) : 2092 SmallString(FSUI_VSTR("Use Global Setting")); 2093 2094 static bool manual_input = false; 2095 2096 if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) 2097 { 2098 ImGui::OpenPopup(title); 2099 manual_input = false; 2100 } 2101 2102 ImGui::SetNextWindowSize(LayoutScale(500.0f, 192.0f)); 2103 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 2104 2105 ImGui::PushFont(g_large_font); 2106 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 2107 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 2108 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 2109 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 2110 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 2111 2112 bool is_open = true; 2113 if (ImGui::BeginPopupModal(title, &is_open, 2114 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 2115 { 2116 BeginMenuButtons(); 2117 2118 float dlg_value = value.value_or(default_value) * multiplier; 2119 bool dlg_value_changed = false; 2120 2121 char str_value[32]; 2122 std::snprintf(str_value, std::size(str_value), format, dlg_value); 2123 2124 if (manual_input) 2125 { 2126 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 2127 ImGui::SetNextItemWidth(end); 2128 2129 // round trip to drop any suffixes (e.g. percent) 2130 if (auto tmp_value = StringUtil::FromChars<float>(str_value); tmp_value.has_value()) 2131 { 2132 std::snprintf(str_value, std::size(str_value), 2133 ((tmp_value.value() - std::floor(tmp_value.value())) < 0.01f) ? "%.0f" : "%f", tmp_value.value()); 2134 } 2135 2136 if (ImGui::InputText("##value", str_value, std::size(str_value), ImGuiInputTextFlags_CharsDecimal)) 2137 { 2138 dlg_value = StringUtil::FromChars<float>(str_value).value_or(dlg_value); 2139 dlg_value_changed = true; 2140 } 2141 2142 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2143 } 2144 else 2145 { 2146 const ImVec2& padding(ImGui::GetStyle().FramePadding); 2147 ImVec2 button_pos(ImGui::GetCursorPos()); 2148 2149 // Align value text in middle. 2150 ImGui::SetCursorPosY( 2151 button_pos.y + 2152 ((LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + padding.y * 2.0f) - g_large_font->FontSize) * 0.5f); 2153 ImGui::TextUnformatted(str_value); 2154 2155 float step = 0; 2156 if (FloatingButton(ICON_FA_CHEVRON_UP, padding.x, button_pos.y, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font, 2157 &button_pos, true)) 2158 { 2159 step = step_value; 2160 } 2161 if (FloatingButton(ICON_FA_CHEVRON_DOWN, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2162 g_large_font, &button_pos, true)) 2163 { 2164 step = -step_value; 2165 } 2166 if (FloatingButton(ICON_FA_KEYBOARD, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2167 g_large_font, &button_pos)) 2168 { 2169 manual_input = true; 2170 } 2171 if (FloatingButton(ICON_FA_TRASH, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2172 g_large_font, &button_pos)) 2173 { 2174 dlg_value = default_value * multiplier; 2175 dlg_value_changed = true; 2176 } 2177 2178 if (step != 0) 2179 { 2180 dlg_value += step * multiplier; 2181 dlg_value_changed = true; 2182 } 2183 2184 ImGui::SetCursorPosY(button_pos.y + (padding.y * 2.0f) + 2185 LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + 10.0f)); 2186 } 2187 2188 if (dlg_value_changed) 2189 { 2190 dlg_value = std::clamp(dlg_value / multiplier, min_value, max_value); 2191 if (IsEditingGameSettings(bsi) && dlg_value == default_value) 2192 bsi->DeleteValue(section, key); 2193 else 2194 bsi->SetFloatValue(section, key, dlg_value); 2195 2196 SetSettingsChanged(bsi); 2197 } 2198 2199 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 2200 ImVec2(0.5f, 0.0f))) 2201 { 2202 ImGui::CloseCurrentPopup(); 2203 } 2204 EndMenuButtons(); 2205 2206 ImGui::EndPopup(); 2207 } 2208 2209 ImGui::PopStyleVar(4); 2210 ImGui::PopFont(); 2211 } 2212 2213 #if 0 2214 void FullscreenUI::DrawIntRectSetting(SettingsInterface* bsi, const char* title, const char* summary, 2215 const char* section, const char* left_key, int default_left, const char* top_key, 2216 int default_top, const char* right_key, int default_right, const char* bottom_key, 2217 int default_bottom, int min_value, int max_value, const char* format, 2218 bool enabled, float height, ImFont* font, ImFont* summary_font) 2219 { 2220 const bool game_settings = IsEditingGameSettings(bsi); 2221 const std::optional<int> left_value = 2222 bsi->GetOptionalIntValue(section, left_key, game_settings ? std::nullopt : std::optional<int>(default_left)); 2223 const std::optional<int> top_value = 2224 bsi->GetOptionalIntValue(section, top_key, game_settings ? std::nullopt : std::optional<int>(default_top)); 2225 const std::optional<int> right_value = 2226 bsi->GetOptionalIntValue(section, right_key, game_settings ? std::nullopt : std::optional<int>(default_right)); 2227 const std::optional<int> bottom_value = 2228 bsi->GetOptionalIntValue(section, bottom_key, game_settings ? std::nullopt : std::optional<int>(default_bottom)); 2229 const SmallString value_text = SmallString::from_format( 2230 "{}/{}/{}/{}", 2231 left_value.has_value() ? TinyString::from_sprintf(format, left_value.value()) : TinyString(FSUI_VSTR("Default")), 2232 top_value.has_value() ? TinyString::from_sprintf(format, top_value.value()) : TinyString(FSUI_VSTR("Default")), 2233 right_value.has_value() ? TinyString::from_sprintf(format, right_value.value()) : TinyString(FSUI_VSTR("Default")), 2234 bottom_value.has_value() ? TinyString::from_sprintf(format, bottom_value.value()) : TinyString(FSUI_VSTR("Default"))); 2235 2236 if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) 2237 ImGui::OpenPopup(title); 2238 2239 ImGui::SetNextWindowSize(LayoutScale(500.0f, 370.0f)); 2240 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 2241 2242 ImGui::PushFont(g_large_font); 2243 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 2244 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 2245 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 2246 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 2247 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 2248 2249 bool is_open = true; 2250 if (ImGui::BeginPopupModal(title, &is_open, 2251 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 2252 { 2253 s32 dlg_left_value = static_cast<s32>(left_value.value_or(default_left)); 2254 s32 dlg_top_value = static_cast<s32>(top_value.value_or(default_top)); 2255 s32 dlg_right_value = static_cast<s32>(right_value.value_or(default_right)); 2256 s32 dlg_bottom_value = static_cast<s32>(bottom_value.value_or(default_bottom)); 2257 2258 BeginMenuButtons(); 2259 2260 const float midpoint = LayoutScale(150.0f); 2261 const float end = (ImGui::GetCurrentWindow()->WorkRect.GetWidth() - midpoint) + ImGui::GetStyle().WindowPadding.x; 2262 ImGui::TextUnformatted("Left: "); 2263 ImGui::SameLine(midpoint); 2264 ImGui::SetNextItemWidth(end); 2265 const bool left_modified = 2266 ImGui::SliderInt("##left", &dlg_left_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); 2267 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2268 ImGui::TextUnformatted("Top: "); 2269 ImGui::SameLine(midpoint); 2270 ImGui::SetNextItemWidth(end); 2271 const bool top_modified = 2272 ImGui::SliderInt("##top", &dlg_top_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); 2273 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2274 ImGui::TextUnformatted("Right: "); 2275 ImGui::SameLine(midpoint); 2276 ImGui::SetNextItemWidth(end); 2277 const bool right_modified = 2278 ImGui::SliderInt("##right", &dlg_right_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); 2279 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2280 ImGui::TextUnformatted("Bottom: "); 2281 ImGui::SameLine(midpoint); 2282 ImGui::SetNextItemWidth(end); 2283 const bool bottom_modified = 2284 ImGui::SliderInt("##bottom", &dlg_bottom_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); 2285 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2286 if (left_modified) 2287 { 2288 if (IsEditingGameSettings(bsi) && dlg_left_value == default_left) 2289 bsi->DeleteValue(section, left_key); 2290 else 2291 bsi->SetIntValue(section, left_key, dlg_left_value); 2292 } 2293 if (top_modified) 2294 { 2295 if (IsEditingGameSettings(bsi) && dlg_top_value == default_top) 2296 bsi->DeleteValue(section, top_key); 2297 else 2298 bsi->SetIntValue(section, top_key, dlg_top_value); 2299 } 2300 if (right_modified) 2301 { 2302 if (IsEditingGameSettings(bsi) && dlg_right_value == default_right) 2303 bsi->DeleteValue(section, right_key); 2304 else 2305 bsi->SetIntValue(section, right_key, dlg_right_value); 2306 } 2307 if (bottom_modified) 2308 { 2309 if (IsEditingGameSettings(bsi) && dlg_bottom_value == default_bottom) 2310 bsi->DeleteValue(section, bottom_key); 2311 else 2312 bsi->SetIntValue(section, bottom_key, dlg_bottom_value); 2313 } 2314 2315 if (left_modified || top_modified || right_modified || bottom_modified) 2316 SetSettingsChanged(bsi); 2317 2318 if (MenuButtonWithoutSummary("OK", true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, ImVec2(0.5f, 0.0f))) 2319 { 2320 ImGui::CloseCurrentPopup(); 2321 } 2322 EndMenuButtons(); 2323 2324 ImGui::EndPopup(); 2325 } 2326 2327 ImGui::PopStyleVar(4); 2328 ImGui::PopFont(); 2329 } 2330 #endif 2331 2332 void FullscreenUI::DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, 2333 const char* section, const char* key, int default_value, int min_value, 2334 int max_value, int step_value, const char* format, bool enabled, float height, 2335 ImFont* font, ImFont* summary_font) 2336 { 2337 const bool game_settings = IsEditingGameSettings(bsi); 2338 const std::optional<int> value = 2339 bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(default_value)); 2340 TinyString value_text; 2341 if (value.has_value()) 2342 value_text.sprintf(format, value.value()); 2343 else 2344 value_text = FSUI_VSTR("Use Global Setting"); 2345 2346 static bool manual_input = false; 2347 2348 if (MenuButtonWithValue(title, summary, value_text, enabled, height, font, summary_font)) 2349 { 2350 ImGui::OpenPopup(title); 2351 manual_input = false; 2352 } 2353 2354 ImGui::SetNextWindowSize(LayoutScale(500.0f, 192.0f)); 2355 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 2356 2357 ImGui::PushFont(g_large_font); 2358 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 2359 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 2360 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 2361 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 2362 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 2363 2364 bool is_open = true; 2365 if (ImGui::BeginPopupModal(title, &is_open, 2366 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 2367 { 2368 BeginMenuButtons(); 2369 2370 s32 dlg_value = static_cast<s32>(value.value_or(default_value)); 2371 bool dlg_value_changed = false; 2372 2373 char str_value[32]; 2374 std::snprintf(str_value, std::size(str_value), format, dlg_value); 2375 2376 if (manual_input) 2377 { 2378 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 2379 ImGui::SetNextItemWidth(end); 2380 2381 std::snprintf(str_value, std::size(str_value), "%d", dlg_value); 2382 if (ImGui::InputText("##value", str_value, std::size(str_value), ImGuiInputTextFlags_CharsDecimal)) 2383 { 2384 dlg_value = StringUtil::FromChars<s32>(str_value).value_or(dlg_value); 2385 dlg_value_changed = true; 2386 } 2387 2388 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 2389 } 2390 else 2391 { 2392 const ImVec2& padding(ImGui::GetStyle().FramePadding); 2393 ImVec2 button_pos(ImGui::GetCursorPos()); 2394 2395 // Align value text in middle. 2396 ImGui::SetCursorPosY( 2397 button_pos.y + 2398 ((LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + padding.y * 2.0f) - g_large_font->FontSize) * 0.5f); 2399 ImGui::TextUnformatted(str_value); 2400 2401 s32 step = 0; 2402 if (FloatingButton(ICON_FA_CHEVRON_UP, padding.x, button_pos.y, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font, 2403 &button_pos, true)) 2404 { 2405 step = step_value; 2406 } 2407 if (FloatingButton(ICON_FA_CHEVRON_DOWN, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2408 g_large_font, &button_pos, true)) 2409 { 2410 step = -step_value; 2411 } 2412 if (FloatingButton(ICON_FA_KEYBOARD, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2413 g_large_font, &button_pos)) 2414 { 2415 manual_input = true; 2416 } 2417 if (FloatingButton(ICON_FA_TRASH, button_pos.x - padding.x, button_pos.y, -1.0f, -1.0f, -1.0f, 0.0f, true, 2418 g_large_font, &button_pos)) 2419 { 2420 dlg_value = default_value; 2421 dlg_value_changed = true; 2422 } 2423 2424 if (step != 0) 2425 { 2426 dlg_value += step; 2427 dlg_value_changed = true; 2428 } 2429 2430 ImGui::SetCursorPosY(button_pos.y + (padding.y * 2.0f) + 2431 LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + 10.0f)); 2432 } 2433 2434 if (dlg_value_changed) 2435 { 2436 dlg_value = std::clamp(dlg_value, min_value, max_value); 2437 if (IsEditingGameSettings(bsi) && dlg_value == default_value) 2438 bsi->DeleteValue(section, key); 2439 else 2440 bsi->SetIntValue(section, key, dlg_value); 2441 2442 SetSettingsChanged(bsi); 2443 } 2444 2445 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 2446 ImVec2(0.5f, 0.0f))) 2447 { 2448 ImGui::CloseCurrentPopup(); 2449 } 2450 EndMenuButtons(); 2451 2452 ImGui::EndPopup(); 2453 } 2454 2455 ImGui::PopStyleVar(4); 2456 ImGui::PopFont(); 2457 } 2458 2459 #if 0 2460 void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, 2461 const char* section, const char* key, const char* default_value, 2462 const char* const* options, const char* const* option_values, 2463 size_t option_count, bool enabled, float height, ImFont* font, 2464 ImFont* summary_font) 2465 { 2466 const bool game_settings = IsEditingGameSettings(bsi); 2467 const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue( 2468 section, key, game_settings ? std::nullopt : std::optional<const char*>(default_value))); 2469 2470 if (option_count == 0) 2471 { 2472 // select from null entry 2473 while (options && options[option_count] != nullptr) 2474 option_count++; 2475 } 2476 2477 size_t index = option_count; 2478 if (value.has_value()) 2479 { 2480 for (size_t i = 0; i < option_count; i++) 2481 { 2482 if (value == option_values[i]) 2483 { 2484 index = i; 2485 break; 2486 } 2487 } 2488 } 2489 2490 if (MenuButtonWithValue(title, summary, 2491 value.has_value() ? ((index < option_count) ? options[index] : "Unknown") : 2492 "Use Global Setting", 2493 enabled, height, font, summary_font)) 2494 { 2495 ImGuiFullscreen::ChoiceDialogOptions cd_options; 2496 cd_options.reserve(option_count + 1); 2497 if (game_settings) 2498 cd_options.emplace_back("Use Global Setting", !value.has_value()); 2499 for (size_t i = 0; i < option_count; i++) 2500 cd_options.emplace_back(options[i], (value.has_value() && i == static_cast<size_t>(index))); 2501 OpenChoiceDialog(title, false, std::move(cd_options), 2502 [game_settings, section, key, option_values](s32 index, const std::string& title, bool checked) { 2503 if (index >= 0) 2504 { 2505 auto lock = Host::GetSettingsLock(); 2506 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 2507 if (game_settings) 2508 { 2509 if (index == 0) 2510 bsi->DeleteValue(section, key); 2511 else 2512 bsi->SetStringValue(section, key, option_values[index - 1]); 2513 } 2514 else 2515 { 2516 bsi->SetStringValue(section, key, option_values[index]); 2517 } 2518 2519 SetSettingsChanged(bsi); 2520 } 2521 2522 CloseChoiceDialog(); 2523 }); 2524 } 2525 } 2526 #endif 2527 2528 template<typename DataType, typename SizeType> 2529 void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, 2530 const char* key, DataType default_value, 2531 std::optional<DataType> (*from_string_function)(const char* str), 2532 const char* (*to_string_function)(DataType value), 2533 const char* (*to_display_string_function)(DataType value), SizeType option_count, 2534 bool enabled /*= true*/, 2535 float height /*= ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT*/, 2536 ImFont* font /*= g_large_font*/, ImFont* summary_font /*= g_medium_font*/) 2537 { 2538 const bool game_settings = IsEditingGameSettings(bsi); 2539 const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue( 2540 section, key, game_settings ? std::nullopt : std::optional<const char*>(to_string_function(default_value)))); 2541 2542 const std::optional<DataType> typed_value(value.has_value() ? from_string_function(value->c_str()) : std::nullopt); 2543 2544 if (MenuButtonWithValue(title, summary, 2545 typed_value.has_value() ? to_display_string_function(typed_value.value()) : 2546 FSUI_CSTR("Use Global Setting"), 2547 enabled, height, font, summary_font)) 2548 { 2549 ImGuiFullscreen::ChoiceDialogOptions cd_options; 2550 cd_options.reserve(static_cast<u32>(option_count) + 1); 2551 if (game_settings) 2552 cd_options.emplace_back(FSUI_CSTR("Use Global Setting"), !value.has_value()); 2553 for (u32 i = 0; i < static_cast<u32>(option_count); i++) 2554 cd_options.emplace_back(to_display_string_function(static_cast<DataType>(i)), 2555 (typed_value.has_value() && i == static_cast<u32>(typed_value.value()))); 2556 OpenChoiceDialog(title, false, std::move(cd_options), 2557 [section = TinyString(section), key = TinyString(key), to_string_function, 2558 game_settings](s32 index, const std::string& title, bool checked) { 2559 if (index >= 0) 2560 { 2561 auto lock = Host::GetSettingsLock(); 2562 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 2563 if (game_settings) 2564 { 2565 if (index == 0) 2566 bsi->DeleteValue(section, key); 2567 else 2568 bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index - 1))); 2569 } 2570 else 2571 { 2572 bsi->SetStringValue(section, key, to_string_function(static_cast<DataType>(index))); 2573 } 2574 2575 SetSettingsChanged(bsi); 2576 } 2577 2578 CloseChoiceDialog(); 2579 }); 2580 } 2581 } 2582 void FullscreenUI::DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, 2583 const char* section, const char* key, float default_value, 2584 const char* const* options, const float* option_values, size_t option_count, 2585 bool translate_options, bool enabled, float height, ImFont* font, 2586 ImFont* summary_font) 2587 { 2588 const bool game_settings = IsEditingGameSettings(bsi); 2589 const std::optional<float> value( 2590 bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional<float>(default_value))); 2591 2592 if (option_count == 0) 2593 { 2594 // select from null entry 2595 while (options && options[option_count] != nullptr) 2596 option_count++; 2597 } 2598 2599 size_t index = option_count; 2600 if (value.has_value()) 2601 { 2602 for (size_t i = 0; i < option_count; i++) 2603 { 2604 if (value == option_values[i]) 2605 { 2606 index = i; 2607 break; 2608 } 2609 } 2610 } 2611 2612 if (MenuButtonWithValue( 2613 title, summary, 2614 value.has_value() ? 2615 ((index < option_count) ? 2616 (translate_options ? Host::TranslateToCString(TR_CONTEXT, options[index]) : options[index]) : 2617 FSUI_CSTR("Unknown")) : 2618 FSUI_CSTR("Use Global Setting"), 2619 enabled, height, font, summary_font)) 2620 { 2621 ImGuiFullscreen::ChoiceDialogOptions cd_options; 2622 cd_options.reserve(option_count + 1); 2623 if (game_settings) 2624 cd_options.emplace_back(FSUI_CSTR("Use Global Setting"), !value.has_value()); 2625 for (size_t i = 0; i < option_count; i++) 2626 { 2627 cd_options.emplace_back(translate_options ? Host::TranslateToString(TR_CONTEXT, options[i]) : 2628 std::string(options[i]), 2629 (value.has_value() && i == static_cast<size_t>(index))); 2630 } 2631 OpenChoiceDialog(title, false, std::move(cd_options), 2632 [game_settings, section = TinyString(section), key = TinyString(key), 2633 option_values](s32 index, const std::string& title, bool checked) { 2634 if (index >= 0) 2635 { 2636 auto lock = Host::GetSettingsLock(); 2637 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 2638 if (game_settings) 2639 { 2640 if (index == 0) 2641 bsi->DeleteValue(section, key); 2642 else 2643 bsi->SetFloatValue(section, key, option_values[index - 1]); 2644 } 2645 else 2646 { 2647 bsi->SetFloatValue(section, key, option_values[index]); 2648 } 2649 2650 SetSettingsChanged(bsi); 2651 } 2652 2653 CloseChoiceDialog(); 2654 }); 2655 } 2656 } 2657 2658 void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key, 2659 const std::string& runtime_var, 2660 float height /* = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT */, 2661 ImFont* font /* = g_large_font */, ImFont* summary_font /* = g_medium_font */) 2662 { 2663 if (MenuButton(title, runtime_var.c_str())) 2664 { 2665 OpenFileSelector(title, true, 2666 [game_settings = IsEditingGameSettings(bsi), section = TinyString(section), 2667 key = TinyString(key)](const std::string& dir) { 2668 if (dir.empty()) 2669 return; 2670 2671 auto lock = Host::GetSettingsLock(); 2672 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 2673 std::string relative_path(Path::MakeRelative(dir, EmuFolders::DataRoot)); 2674 bsi->SetStringValue(section.c_str(), key.c_str(), relative_path.c_str()); 2675 SetSettingsChanged(bsi); 2676 2677 Host::RunOnCPUThread(&EmuFolders::Update); 2678 s_cover_image_map.clear(); 2679 2680 CloseFileSelector(); 2681 }); 2682 } 2683 } 2684 2685 void FullscreenUI::StartAutomaticBinding(u32 port) 2686 { 2687 std::vector<std::pair<std::string, std::string>> devices(InputManager::EnumerateDevices()); 2688 if (devices.empty()) 2689 { 2690 ShowToast({}, FSUI_STR("Automatic mapping failed, no devices are available.")); 2691 return; 2692 } 2693 2694 std::vector<std::string> names; 2695 ImGuiFullscreen::ChoiceDialogOptions options; 2696 options.reserve(devices.size()); 2697 names.reserve(devices.size()); 2698 for (auto& [name, display_name] : devices) 2699 { 2700 names.push_back(std::move(name)); 2701 options.emplace_back(std::move(display_name), false); 2702 } 2703 OpenChoiceDialog(FSUI_CSTR("Select Device"), false, std::move(options), 2704 [port, names = std::move(names)](s32 index, const std::string& title, bool checked) { 2705 if (index < 0) 2706 return; 2707 2708 const std::string& name = names[index]; 2709 auto lock = Host::GetSettingsLock(); 2710 SettingsInterface* bsi = GetEditingSettingsInterface(); 2711 const bool result = 2712 InputManager::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name)); 2713 SetSettingsChanged(bsi); 2714 2715 // and the toast needs to happen on the UI thread. 2716 ShowToast({}, result ? fmt::format(FSUI_FSTR("Automatic mapping completed for {}."), name) : 2717 fmt::format(FSUI_FSTR("Automatic mapping failed for {}."), name)); 2718 CloseChoiceDialog(); 2719 }); 2720 } 2721 2722 void FullscreenUI::SwitchToSettings() 2723 { 2724 s_game_settings_entry.reset(); 2725 s_game_settings_interface.reset(); 2726 2727 PopulateGraphicsAdapterList(); 2728 PopulatePostProcessingChain(GetEditingSettingsInterface(), PostProcessing::Config::DISPLAY_CHAIN_SECTION); 2729 2730 s_current_main_window = MainWindowType::Settings; 2731 s_settings_page = SettingsPage::Interface; 2732 } 2733 2734 void FullscreenUI::SwitchToGameSettingsForSerial(std::string_view serial) 2735 { 2736 s_game_settings_entry.reset(); 2737 s_game_settings_interface = std::make_unique<INISettingsInterface>(System::GetGameSettingsPath(serial)); 2738 s_game_settings_interface->Load(); 2739 s_current_main_window = MainWindowType::Settings; 2740 s_settings_page = SettingsPage::Summary; 2741 QueueResetFocus(FocusResetType::ViewChanged); 2742 } 2743 2744 void FullscreenUI::SwitchToGameSettings() 2745 { 2746 if (System::GetGameSerial().empty()) 2747 return; 2748 2749 auto lock = GameList::GetLock(); 2750 const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath()); 2751 if (!entry) 2752 { 2753 SwitchToGameSettingsForSerial(System::GetGameSerial()); 2754 return; 2755 } 2756 2757 SwitchToGameSettings(entry); 2758 } 2759 2760 void FullscreenUI::SwitchToGameSettingsForPath(const std::string& path) 2761 { 2762 auto lock = GameList::GetLock(); 2763 const GameList::Entry* entry = GameList::GetEntryForPath(path); 2764 if (entry) 2765 SwitchToGameSettings(entry); 2766 } 2767 2768 void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry) 2769 { 2770 SwitchToGameSettingsForSerial(entry->serial); 2771 s_game_settings_entry = std::make_unique<GameList::Entry>(*entry); 2772 } 2773 2774 void FullscreenUI::PopulateGraphicsAdapterList() 2775 { 2776 const GPURenderer renderer = 2777 Settings::ParseRendererName(GetEffectiveTinyStringSetting(GetEditingSettingsInterface(false), "GPU", "Renderer", 2778 Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)) 2779 .c_str()) 2780 .value_or(Settings::DEFAULT_GPU_RENDERER); 2781 2782 s_graphics_adapter_list_cache = GPUDevice::GetAdapterListForAPI(Settings::GetRenderAPIForRenderer(renderer)); 2783 } 2784 2785 void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si) 2786 { 2787 s_game_list_directories_cache.clear(); 2788 for (std::string& dir : si->GetStringList("GameList", "Paths")) 2789 s_game_list_directories_cache.emplace_back(std::move(dir), false); 2790 for (std::string& dir : si->GetStringList("GameList", "RecursivePaths")) 2791 s_game_list_directories_cache.emplace_back(std::move(dir), true); 2792 } 2793 2794 void FullscreenUI::DoCopyGameSettings() 2795 { 2796 if (!s_game_settings_interface) 2797 return; 2798 2799 Settings temp_settings; 2800 temp_settings.Load(*GetEditingSettingsInterface(false), *GetEditingSettingsInterface(false)); 2801 temp_settings.Save(*s_game_settings_interface, true); 2802 SetSettingsChanged(s_game_settings_interface.get()); 2803 2804 ShowToast("Game Settings Copied", fmt::format(FSUI_FSTR("Game settings initialized with global settings for '{}'."), 2805 Path::GetFileTitle(s_game_settings_interface->GetFileName()))); 2806 } 2807 2808 void FullscreenUI::DoClearGameSettings() 2809 { 2810 if (!s_game_settings_interface) 2811 return; 2812 2813 s_game_settings_interface->Clear(); 2814 if (!s_game_settings_interface->GetFileName().empty()) 2815 FileSystem::DeleteFile(s_game_settings_interface->GetFileName().c_str()); 2816 2817 SetSettingsChanged(s_game_settings_interface.get()); 2818 2819 ShowToast("Game Settings Cleared", fmt::format(FSUI_FSTR("Game settings have been cleared for '{}'."), 2820 Path::GetFileTitle(s_game_settings_interface->GetFileName()))); 2821 } 2822 2823 void FullscreenUI::DrawSettingsWindow() 2824 { 2825 ImGuiIO& io = ImGui::GetIO(); 2826 const ImVec2 heading_size = 2827 ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + 2828 (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); 2829 2830 const float bg_alpha = System::IsValid() ? (s_settings_page == SettingsPage::PostProcessing ? 0.50f : 0.90f) : 1.0f; 2831 2832 if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category", 2833 ImVec4(UIPrimaryColor.x, UIPrimaryColor.y, UIPrimaryColor.z, bg_alpha))) 2834 { 2835 static constexpr float ITEM_WIDTH = 25.0f; 2836 2837 static constexpr const char* global_icons[] = { 2838 ICON_FA_TV, ICON_FA_DICE_D20, ICON_FA_COGS, ICON_PF_MICROCHIP, 2839 ICON_PF_PICTURE, ICON_FA_MAGIC, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, 2840 ICON_PF_KEYBOARD_ALT, ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; 2841 static constexpr const char* per_game_icons[] = {ICON_FA_PARAGRAPH, ICON_FA_HDD, ICON_FA_COGS, 2842 ICON_PF_PICTURE, ICON_PF_SOUND, ICON_PF_GAMEPAD_ALT, 2843 ICON_PF_MEMORY_CARD, ICON_FA_TROPHY, ICON_FA_EXCLAMATION_TRIANGLE}; 2844 static constexpr SettingsPage global_pages[] = { 2845 SettingsPage::Interface, SettingsPage::Console, SettingsPage::Emulation, SettingsPage::BIOS, 2846 SettingsPage::Display, SettingsPage::PostProcessing, SettingsPage::Audio, SettingsPage::Controller, 2847 SettingsPage::Hotkey, SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced}; 2848 static constexpr SettingsPage per_game_pages[] = { 2849 SettingsPage::Summary, SettingsPage::Console, SettingsPage::Emulation, 2850 SettingsPage::Display, SettingsPage::Audio, SettingsPage::Controller, 2851 SettingsPage::MemoryCards, SettingsPage::Achievements, SettingsPage::Advanced}; 2852 static constexpr std::array<const char*, static_cast<u32>(SettingsPage::Count)> titles = { 2853 {FSUI_NSTR("Summary"), FSUI_NSTR("Interface Settings"), FSUI_NSTR("Console Settings"), 2854 FSUI_NSTR("Emulation Settings"), FSUI_NSTR("BIOS Settings"), FSUI_NSTR("Controller Settings"), 2855 FSUI_NSTR("Hotkey Settings"), FSUI_NSTR("Memory Card Settings"), FSUI_NSTR("Graphics Settings"), 2856 FSUI_NSTR("Post-Processing Settings"), FSUI_NSTR("Audio Settings"), FSUI_NSTR("Achievements Settings"), 2857 FSUI_NSTR("Advanced Settings")}}; 2858 2859 const bool game_settings = IsEditingGameSettings(GetEditingSettingsInterface()); 2860 const u32 count = 2861 game_settings ? static_cast<u32>(std::size(per_game_pages)) : static_cast<u32>(std::size(global_pages)); 2862 const char* const* icons = game_settings ? per_game_icons : global_icons; 2863 const SettingsPage* pages = game_settings ? per_game_pages : global_pages; 2864 u32 index = 0; 2865 for (u32 i = 0; i < count; i++) 2866 { 2867 if (pages[i] == s_settings_page) 2868 { 2869 index = i; 2870 break; 2871 } 2872 } 2873 2874 BeginNavBar(); 2875 2876 if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup)) 2877 { 2878 if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, true) || 2879 ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, true) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, true)) 2880 { 2881 index = (index == 0) ? (count - 1) : (index - 1); 2882 s_settings_page = pages[index]; 2883 QueueResetFocus(FocusResetType::Other); 2884 } 2885 else if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight, true) || 2886 ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, true) || 2887 ImGui::IsKeyPressed(ImGuiKey_RightArrow, true)) 2888 { 2889 index = (index + 1) % count; 2890 s_settings_page = pages[index]; 2891 QueueResetFocus(FocusResetType::Other); 2892 } 2893 } 2894 2895 if (NavButton(ICON_FA_BACKWARD, true, true)) 2896 ReturnToPreviousWindow(); 2897 2898 if (s_game_settings_entry) 2899 NavTitle(s_game_settings_entry->title.c_str()); 2900 else 2901 NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast<u32>(pages[index])])); 2902 2903 RightAlignNavButtons(count, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 2904 2905 for (u32 i = 0; i < count; i++) 2906 { 2907 if (NavButton(icons[i], i == index, true, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 2908 { 2909 s_settings_page = pages[i]; 2910 QueueResetFocus(FocusResetType::Other); 2911 } 2912 } 2913 2914 EndNavBar(); 2915 } 2916 2917 EndFullscreenWindow(); 2918 2919 // we have to do this here, because otherwise it uses target, and jumps a frame later. 2920 // don't do it for popups opening/closing, otherwise we lose our position 2921 if (IsFocusResetFromWindowChange()) 2922 ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f)); 2923 2924 if (BeginFullscreenWindow( 2925 ImVec2(0.0f, heading_size.y), 2926 ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), 2927 TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_page)).c_str(), 2928 ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, bg_alpha), 0.0f, 2929 ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f))) 2930 { 2931 ResetFocusHere(); 2932 2933 if (ImGui::IsWindowFocused() && WantsToCloseMenu()) 2934 ReturnToPreviousWindow(); 2935 2936 auto lock = Host::GetSettingsLock(); 2937 2938 switch (s_settings_page) 2939 { 2940 case SettingsPage::Summary: 2941 DrawSummarySettingsPage(); 2942 break; 2943 2944 case SettingsPage::Interface: 2945 DrawInterfaceSettingsPage(); 2946 break; 2947 2948 case SettingsPage::BIOS: 2949 DrawBIOSSettingsPage(); 2950 break; 2951 2952 case SettingsPage::Emulation: 2953 DrawEmulationSettingsPage(); 2954 break; 2955 2956 case SettingsPage::Console: 2957 DrawConsoleSettingsPage(); 2958 break; 2959 2960 case SettingsPage::Display: 2961 DrawDisplaySettingsPage(); 2962 break; 2963 2964 case SettingsPage::PostProcessing: 2965 DrawPostProcessingSettingsPage(); 2966 break; 2967 2968 case SettingsPage::Audio: 2969 DrawAudioSettingsPage(); 2970 break; 2971 2972 case SettingsPage::MemoryCards: 2973 DrawMemoryCardSettingsPage(); 2974 break; 2975 2976 case SettingsPage::Controller: 2977 DrawControllerSettingsPage(); 2978 break; 2979 2980 case SettingsPage::Hotkey: 2981 DrawHotkeySettingsPage(); 2982 break; 2983 2984 case SettingsPage::Achievements: 2985 DrawAchievementsSettingsPage(); 2986 break; 2987 2988 case SettingsPage::Advanced: 2989 DrawAdvancedSettingsPage(); 2990 break; 2991 2992 default: 2993 break; 2994 } 2995 } 2996 2997 EndFullscreenWindow(); 2998 2999 if (IsGamepadInputSource()) 3000 { 3001 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_LEFT_RIGHT, FSUI_VSTR("Change Page")), 3002 std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Navigate")), 3003 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 3004 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); 3005 } 3006 else 3007 { 3008 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, FSUI_VSTR("Change Page")), 3009 std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Navigate")), 3010 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), 3011 std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); 3012 } 3013 } 3014 3015 void FullscreenUI::DrawSummarySettingsPage() 3016 { 3017 BeginMenuButtons(); 3018 3019 MenuHeading(FSUI_CSTR("Details")); 3020 3021 if (s_game_settings_entry) 3022 { 3023 if (MenuButton(FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"), s_game_settings_entry->title.c_str(), true)) 3024 CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."), s_game_settings_entry->title); 3025 if (MenuButton(FSUI_ICONSTR(ICON_FA_PAGER, "Serial"), s_game_settings_entry->serial.c_str(), true)) 3026 CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), s_game_settings_entry->serial); 3027 if (MenuButton(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Type"), 3028 GameList::GetEntryTypeDisplayName(s_game_settings_entry->type), true)) 3029 { 3030 CopyTextToClipboard(FSUI_STR("Game type copied to clipboard."), 3031 GameList::GetEntryTypeDisplayName(s_game_settings_entry->type)); 3032 } 3033 if (MenuButton(FSUI_ICONSTR(ICON_FA_BOX, "Region"), 3034 Settings::GetDiscRegionDisplayName(s_game_settings_entry->region), true)) 3035 { 3036 CopyTextToClipboard(FSUI_STR("Game region copied to clipboard."), 3037 Settings::GetDiscRegionDisplayName(s_game_settings_entry->region)); 3038 } 3039 if (MenuButton(FSUI_ICONSTR(ICON_FA_STAR, "Compatibility Rating"), 3040 GameDatabase::GetCompatibilityRatingDisplayName(s_game_settings_entry->compatibility), true)) 3041 { 3042 CopyTextToClipboard(FSUI_STR("Game compatibility rating copied to clipboard."), 3043 GameDatabase::GetCompatibilityRatingDisplayName(s_game_settings_entry->compatibility)); 3044 } 3045 if (MenuButton(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Path"), s_game_settings_entry->path.c_str(), true)) 3046 { 3047 CopyTextToClipboard(FSUI_STR("Game path copied to clipboard."), s_game_settings_entry->path); 3048 } 3049 } 3050 else 3051 { 3052 MenuButton(FSUI_ICONSTR(ICON_FA_BAN, "Details unavailable for game not scanned in game list."), ""); 3053 } 3054 3055 MenuHeading(FSUI_CSTR("Options")); 3056 3057 if (MenuButton(FSUI_ICONSTR(ICON_FA_COPY, "Copy Settings"), 3058 FSUI_CSTR("Copies the current global settings to this game."))) 3059 { 3060 DoCopyGameSettings(); 3061 } 3062 if (MenuButton(FSUI_ICONSTR(ICON_FA_TRASH, "Clear Settings"), FSUI_CSTR("Clears all settings set for this game."))) 3063 { 3064 DoClearGameSettings(); 3065 } 3066 3067 EndMenuButtons(); 3068 } 3069 3070 void FullscreenUI::DrawInterfaceSettingsPage() 3071 { 3072 SettingsInterface* bsi = GetEditingSettingsInterface(); 3073 3074 BeginMenuButtons(); 3075 3076 MenuHeading(FSUI_CSTR("Behavior")); 3077 3078 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PAUSE, "Pause On Start"), 3079 FSUI_CSTR("Pauses the emulator when a game is started."), "Main", "StartPaused", false); 3080 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_VIDEO, "Pause On Focus Loss"), 3081 FSUI_CSTR("Pauses the emulator when you minimize the window or switch to another " 3082 "application, and unpauses when you switch back."), 3083 "Main", "PauseOnFocusLoss", false); 3084 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_GAMEPAD, "Pause On Controller Disconnection"), 3085 FSUI_CSTR("Pauses the emulator when a controller with bindings is disconnected."), "Main", 3086 "PauseOnControllerDisconnection", false); 3087 DrawToggleSetting( 3088 bsi, FSUI_ICONSTR(ICON_FA_POWER_OFF, "Confirm Power Off"), 3089 FSUI_CSTR("Determines whether a prompt will be displayed to confirm shutting down the emulator/game " 3090 "when the hotkey is pressed."), 3091 "Main", "ConfirmPowerOff", true); 3092 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SAVE, "Save State On Exit"), 3093 FSUI_CSTR("Automatically saves the emulator state when powering down or exiting. You can then " 3094 "resume directly from where you left off next time."), 3095 "Main", "SaveStateOnExit", true); 3096 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Start Fullscreen"), 3097 FSUI_CSTR("Automatically switches to fullscreen mode when the program is started."), "Main", 3098 "StartFullscreen", false); 3099 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MOUSE, "Double-Click Toggles Fullscreen"), 3100 FSUI_CSTR("Switches between full screen and windowed when the window is double-clicked."), "Main", 3101 "DoubleClickTogglesFullscreen", true); 3102 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MOUSE_POINTER, "Hide Cursor In Fullscreen"), 3103 FSUI_CSTR("Hides the mouse pointer/cursor when the emulator is in fullscreen mode."), "Main", 3104 "HideCursorInFullscreen", true); 3105 DrawToggleSetting( 3106 bsi, FSUI_ICONSTR(ICON_FA_MAGIC, "Inhibit Screensaver"), 3107 FSUI_CSTR("Prevents the screen saver from activating and the host from sleeping while emulation is running."), 3108 "Main", "InhibitScreensaver", true); 3109 3110 if (DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PAINT_BRUSH, "Use Light Theme"), 3111 FSUI_CSTR("Uses a light coloured theme instead of the default dark theme."), "Main", 3112 "UseLightFullscreenUITheme", false)) 3113 { 3114 ImGuiFullscreen::SetTheme(bsi->GetBoolValue("Main", "UseLightFullscreenUITheme", false)); 3115 } 3116 3117 { 3118 // Have to do this the annoying way, because it's host-derived. 3119 const auto language_list = Host::GetAvailableLanguageList(); 3120 TinyString current_language = bsi->GetTinyStringValue("Main", "Language", ""); 3121 const char* current_language_name = "Unknown"; 3122 for (const auto& [language, code] : language_list) 3123 { 3124 if (current_language == code) 3125 current_language_name = language; 3126 } 3127 if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_LANGUAGE, "UI Language"), 3128 FSUI_CSTR("Chooses the language used for UI elements."), current_language_name)) 3129 { 3130 ImGuiFullscreen::ChoiceDialogOptions options; 3131 for (const auto& [language, code] : language_list) 3132 options.emplace_back(fmt::format("{} [{}]", language, code), (current_language == code)); 3133 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_LANGUAGE, "UI Language"), false, std::move(options), 3134 [language_list](s32 index, const std::string& title, bool checked) { 3135 if (static_cast<u32>(index) >= language_list.size()) 3136 return; 3137 3138 Host::RunOnCPUThread( 3139 [language = language_list[index].second]() { Host::ChangeLanguage(language); }); 3140 ImGuiFullscreen::CloseChoiceDialog(); 3141 }); 3142 } 3143 } 3144 3145 MenuHeading(FSUI_CSTR("Integration")); 3146 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_CHARGING_STATION, "Enable Discord Presence"), 3147 FSUI_CSTR("Shows the game you are currently playing as part of your profile in Discord."), "Main", 3148 "EnableDiscordPresence", false); 3149 3150 MenuHeading(FSUI_CSTR("On-Screen Display")); 3151 DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "OSD Scale"), 3152 FSUI_CSTR("Determines how large the on-screen messages and monitor are."), "Display", 3153 "OSDScale", 100, 25, 500, 1, "%d%%"); 3154 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST, "Show OSD Messages"), 3155 FSUI_CSTR("Shows on-screen-display messages when events occur."), "Display", "ShowOSDMessages", 3156 true); 3157 DrawToggleSetting( 3158 bsi, FSUI_ICONSTR(ICON_FA_CLOCK, "Show Speed"), 3159 FSUI_CSTR( 3160 "Shows the current emulation speed of the system in the top-right corner of the display as a percentage."), 3161 "Display", "ShowSpeed", false); 3162 DrawToggleSetting( 3163 bsi, FSUI_ICONSTR(ICON_FA_RULER, "Show FPS"), 3164 FSUI_CSTR("Shows the number of frames (or v-syncs) displayed per second by the system in the top-right " 3165 "corner of the display."), 3166 "Display", "ShowFPS", false); 3167 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BARS, "Show GPU Statistics"), 3168 FSUI_CSTR("Shows information about the emulated GPU in the top-right corner of the display."), 3169 "Display", "ShowGPUStatistics", false); 3170 DrawToggleSetting( 3171 bsi, FSUI_ICONSTR(ICON_FA_STOPWATCH, "Show Latency Statistics"), 3172 FSUI_CSTR("Shows information about input and audio latency in the top-right corner of the display."), "Display", 3173 "ShowLatencyStatistics", false); 3174 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BATTERY_HALF, "Show CPU Usage"), 3175 FSUI_CSTR("Shows the host's CPU usage based on threads in the top-right corner of the display."), 3176 "Display", "ShowCPU", false); 3177 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SPINNER, "Show GPU Usage"), 3178 FSUI_CSTR("Shows the host's GPU usage in the top-right corner of the display."), "Display", 3179 "ShowGPU", false); 3180 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_RULER_HORIZONTAL, "Show Frame Times"), 3181 FSUI_CSTR("Shows a visual history of frame times in the upper-left corner of the display."), 3182 "Display", "ShowFrameTimes", false); 3183 DrawToggleSetting( 3184 bsi, FSUI_ICONSTR(ICON_FA_RULER_VERTICAL, "Show Resolution"), 3185 FSUI_CSTR("Shows the current rendering resolution of the system in the top-right corner of the display."), 3186 "Display", "ShowResolution", false); 3187 DrawToggleSetting( 3188 bsi, FSUI_ICONSTR(ICON_FA_GAMEPAD, "Show Controller Input"), 3189 FSUI_CSTR("Shows the current controller state of the system in the bottom-left corner of the display."), "Display", 3190 "ShowInputs", false); 3191 3192 EndMenuButtons(); 3193 } 3194 3195 void FullscreenUI::DrawBIOSSettingsPage() 3196 { 3197 static constexpr const std::array config_keys = {"", "PathNTSCJ", "PathNTSCU", "PathPAL"}; 3198 3199 SettingsInterface* bsi = GetEditingSettingsInterface(); 3200 const bool game_settings = IsEditingGameSettings(bsi); 3201 3202 BeginMenuButtons(); 3203 3204 MenuHeading(FSUI_CSTR("BIOS Selection")); 3205 3206 for (u32 i = 0; i < static_cast<u32>(ConsoleRegion::Count); i++) 3207 { 3208 const ConsoleRegion region = static_cast<ConsoleRegion>(i); 3209 if (region == ConsoleRegion::Auto) 3210 continue; 3211 3212 TinyString title; 3213 title.format(FSUI_FSTR("BIOS for {}"), Settings::GetConsoleRegionDisplayName(region)); 3214 3215 const std::optional<SmallString> filename(bsi->GetOptionalSmallStringValue( 3216 "BIOS", config_keys[i], game_settings ? std::nullopt : std::optional<const char*>(""))); 3217 3218 if (MenuButtonWithValue(title, 3219 SmallString::from_format(FSUI_FSTR("BIOS to use when emulating {} consoles."), 3220 Settings::GetConsoleRegionDisplayName(region)), 3221 filename.has_value() ? (filename->empty() ? FSUI_CSTR("Auto-Detect") : filename->c_str()) : 3222 FSUI_CSTR("Use Global Setting"))) 3223 { 3224 ImGuiFullscreen::ChoiceDialogOptions options; 3225 auto images = BIOS::FindBIOSImagesInDirectory(EmuFolders::Bios.c_str()); 3226 options.reserve(images.size() + 2); 3227 if (IsEditingGameSettings(bsi)) 3228 options.emplace_back(FSUI_STR("Use Global Setting"), !filename.has_value()); 3229 options.emplace_back(FSUI_STR("Auto-Detect"), filename.has_value() && filename->empty()); 3230 for (auto& [path, info] : images) 3231 { 3232 const bool selected = (filename.has_value() && filename.value() == path); 3233 options.emplace_back(std::move(path), selected); 3234 } 3235 3236 OpenChoiceDialog(title, false, std::move(options), 3237 [game_settings, i](s32 index, const std::string& path, bool checked) { 3238 if (index >= 0) 3239 { 3240 auto lock = Host::GetSettingsLock(); 3241 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 3242 if (game_settings && index == 0) 3243 bsi->DeleteValue("BIOS", config_keys[i]); 3244 else 3245 bsi->SetStringValue("BIOS", config_keys[i], path.c_str()); 3246 SetSettingsChanged(bsi); 3247 } 3248 CloseChoiceDialog(); 3249 }); 3250 } 3251 } 3252 3253 DrawFolderSetting(bsi, FSUI_CSTR("BIOS Directory"), "BIOS", "SearchDirectory", EmuFolders::Bios); 3254 3255 MenuHeading(FSUI_CSTR("Patches")); 3256 3257 DrawToggleSetting(bsi, FSUI_CSTR("Enable Fast Boot"), 3258 FSUI_CSTR("Patches the BIOS to skip the boot animation. Safe to enable."), "BIOS", "PatchFastBoot", 3259 Settings::DEFAULT_FAST_BOOT_VALUE); 3260 DrawToggleSetting(bsi, FSUI_CSTR("Enable TTY Logging"), 3261 FSUI_CSTR("Logs BIOS calls to printf(). Not all games contain debugging messages."), "BIOS", 3262 "TTYLogging", false); 3263 3264 EndMenuButtons(); 3265 } 3266 3267 void FullscreenUI::DrawConsoleSettingsPage() 3268 { 3269 static constexpr const std::array cdrom_read_speeds = { 3270 FSUI_NSTR("None (Double Speed)"), FSUI_NSTR("2x (Quad Speed)"), FSUI_NSTR("3x (6x Speed)"), 3271 FSUI_NSTR("4x (8x Speed)"), FSUI_NSTR("5x (10x Speed)"), FSUI_NSTR("6x (12x Speed)"), 3272 FSUI_NSTR("7x (14x Speed)"), FSUI_NSTR("8x (16x Speed)"), FSUI_NSTR("9x (18x Speed)"), 3273 FSUI_NSTR("10x (20x Speed)"), 3274 }; 3275 3276 static constexpr const std::array cdrom_seek_speeds = { 3277 FSUI_NSTR("Infinite/Instantaneous"), 3278 FSUI_NSTR("None (Normal Speed)"), 3279 FSUI_NSTR("2x"), 3280 FSUI_NSTR("3x"), 3281 FSUI_NSTR("4x"), 3282 FSUI_NSTR("5x"), 3283 FSUI_NSTR("6x"), 3284 FSUI_NSTR("7x"), 3285 FSUI_NSTR("8x"), 3286 FSUI_NSTR("9x"), 3287 FSUI_NSTR("10x"), 3288 }; 3289 3290 SettingsInterface* bsi = GetEditingSettingsInterface(); 3291 3292 BeginMenuButtons(); 3293 3294 MenuHeading(FSUI_CSTR("Console Settings")); 3295 3296 DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_GLOBE, "Region"), FSUI_CSTR("Determines the emulated hardware type."), 3297 "Console", "Region", Settings::DEFAULT_CONSOLE_REGION, &Settings::ParseConsoleRegionName, 3298 &Settings::GetConsoleRegionName, &Settings::GetConsoleRegionDisplayName, ConsoleRegion::Count); 3299 DrawToggleSetting( 3300 bsi, FSUI_ICONSTR(ICON_FA_MEMORY, "Enable 8MB RAM"), 3301 FSUI_CSTR("Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles."), 3302 "Console", "Enable8MBRAM", false); 3303 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MAGIC, "Disable All Enhancements"), 3304 FSUI_CSTR("Temporarily disables all enhancements, useful when testing."), "Main", 3305 "DisableAllEnhancements", false); 3306 DrawToggleSetting( 3307 bsi, FSUI_ICONSTR(ICON_FA_FROWN, "Enable Cheats"), 3308 FSUI_CSTR("Automatically loads and applies cheats on game start. Cheats can break games and saves."), "Console", 3309 "EnableCheats", false); 3310 3311 MenuHeading(FSUI_CSTR("CPU Emulation")); 3312 3313 DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_BOLT, "Execution Mode"), 3314 FSUI_CSTR("Determines how the emulated CPU executes instructions."), "CPU", "ExecutionMode", 3315 Settings::DEFAULT_CPU_EXECUTION_MODE, &Settings::ParseCPUExecutionMode, 3316 &Settings::GetCPUExecutionModeName, &Settings::GetCPUExecutionModeDisplayName, 3317 CPUExecutionMode::Count); 3318 3319 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TACHOMETER_ALT, "Enable Overclocking"), 3320 FSUI_CSTR("When this option is chosen, the clock speed set below will be used."), "CPU", 3321 "OverclockEnable", false); 3322 3323 const bool oc_enable = GetEffectiveBoolSetting(bsi, "CPU", "OverclockEnable", false); 3324 if (oc_enable) 3325 { 3326 u32 oc_numerator = GetEffectiveUIntSetting(bsi, "CPU", "OverclockNumerator", 1); 3327 u32 oc_denominator = GetEffectiveUIntSetting(bsi, "CPU", "OverclockDenominator", 1); 3328 s32 oc_percent = static_cast<s32>(Settings::CPUOverclockFractionToPercent(oc_numerator, oc_denominator)); 3329 if (RangeButton(FSUI_ICONSTR(ICON_FA_TACHOMETER_ALT, "Overclocking Percentage"), 3330 FSUI_CSTR("Selects the percentage of the normal clock speed the emulated hardware will run at."), 3331 &oc_percent, 10, 1000, 10, "%d%%")) 3332 { 3333 Settings::CPUOverclockPercentToFraction(oc_percent, &oc_numerator, &oc_denominator); 3334 bsi->SetUIntValue("CPU", "OverclockNumerator", oc_numerator); 3335 bsi->SetUIntValue("CPU", "OverclockDenominator", oc_denominator); 3336 SetSettingsChanged(bsi); 3337 } 3338 } 3339 3340 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MICROCHIP, "Enable Recompiler ICache"), 3341 FSUI_CSTR("Makes games run closer to their console framerate, at a small cost to performance."), 3342 "CPU", "RecompilerICache", false); 3343 3344 MenuHeading(FSUI_CSTR("CD-ROM Emulation")); 3345 3346 DrawIntListSetting( 3347 bsi, FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Read Speedup"), 3348 FSUI_CSTR( 3349 "Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others."), 3350 "CDROM", "ReadSpeedup", 1, cdrom_read_speeds.data(), cdrom_read_speeds.size(), true, 1); 3351 DrawIntListSetting( 3352 bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "Seek Speedup"), 3353 FSUI_CSTR( 3354 "Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others."), 3355 "CDROM", "SeekSpeedup", 1, cdrom_seek_speeds.data(), cdrom_seek_speeds.size(), true); 3356 3357 DrawIntRangeSetting( 3358 bsi, FSUI_ICONSTR(ICON_FA_FAST_FORWARD, "Readahead Sectors"), 3359 FSUI_CSTR("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread."), 3360 "CDROM", "ReadaheadSectors", Settings::DEFAULT_CDROM_READAHEAD_SECTORS, 0, 32, FSUI_CSTR("%d sectors")); 3361 3362 DrawToggleSetting( 3363 bsi, FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Preload Images to RAM"), 3364 FSUI_CSTR("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay."), 3365 "CDROM", "LoadImageToRAM", false); 3366 DrawToggleSetting( 3367 bsi, FSUI_ICONSTR(ICON_FA_VEST_PATCHES, "Apply Image Patches"), 3368 FSUI_CSTR("Automatically applies patches to disc images when they are present, currently only PPF is supported."), 3369 "CDROM", "LoadImagePatches", false); 3370 3371 EndMenuButtons(); 3372 } 3373 3374 void FullscreenUI::DrawEmulationSettingsPage() 3375 { 3376 static constexpr const std::array emulation_speed_values = { 3377 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.25f, 1.5f, 3378 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 3379 }; 3380 static constexpr const std::array emulation_speed_titles = { 3381 FSUI_NSTR("Unlimited"), 3382 "10% [6 FPS (NTSC) / 5 FPS (PAL)]", 3383 FSUI_NSTR("20% [12 FPS (NTSC) / 10 FPS (PAL)]"), 3384 FSUI_NSTR("30% [18 FPS (NTSC) / 15 FPS (PAL)]"), 3385 FSUI_NSTR("40% [24 FPS (NTSC) / 20 FPS (PAL)]"), 3386 FSUI_NSTR("50% [30 FPS (NTSC) / 25 FPS (PAL)]"), 3387 FSUI_NSTR("60% [36 FPS (NTSC) / 30 FPS (PAL)]"), 3388 FSUI_NSTR("70% [42 FPS (NTSC) / 35 FPS (PAL)]"), 3389 FSUI_NSTR("80% [48 FPS (NTSC) / 40 FPS (PAL)]"), 3390 FSUI_NSTR("90% [54 FPS (NTSC) / 45 FPS (PAL)]"), 3391 FSUI_NSTR("100% [60 FPS (NTSC) / 50 FPS (PAL)]"), 3392 FSUI_NSTR("125% [75 FPS (NTSC) / 62 FPS (PAL)]"), 3393 FSUI_NSTR("150% [90 FPS (NTSC) / 75 FPS (PAL)]"), 3394 FSUI_NSTR("175% [105 FPS (NTSC) / 87 FPS (PAL)]"), 3395 FSUI_NSTR("200% [120 FPS (NTSC) / 100 FPS (PAL)]"), 3396 FSUI_NSTR("250% [150 FPS (NTSC) / 125 FPS (PAL)]"), 3397 FSUI_NSTR("300% [180 FPS (NTSC) / 150 FPS (PAL)]"), 3398 FSUI_NSTR("350% [210 FPS (NTSC) / 175 FPS (PAL)]"), 3399 FSUI_NSTR("400% [240 FPS (NTSC) / 200 FPS (PAL)]"), 3400 FSUI_NSTR("450% [270 FPS (NTSC) / 225 FPS (PAL)]"), 3401 FSUI_NSTR("500% [300 FPS (NTSC) / 250 FPS (PAL)]"), 3402 FSUI_NSTR("600% [360 FPS (NTSC) / 300 FPS (PAL)]"), 3403 FSUI_NSTR("700% [420 FPS (NTSC) / 350 FPS (PAL)]"), 3404 FSUI_NSTR("800% [480 FPS (NTSC) / 400 FPS (PAL)]"), 3405 FSUI_NSTR("900% [540 FPS (NTSC) / 450 FPS (PAL)]"), 3406 FSUI_NSTR("1000% [600 FPS (NTSC) / 500 FPS (PAL)]"), 3407 }; 3408 3409 SettingsInterface* bsi = GetEditingSettingsInterface(); 3410 3411 BeginMenuButtons(); 3412 3413 MenuHeading(FSUI_CSTR("Speed Control")); 3414 DrawFloatListSetting( 3415 bsi, FSUI_ICONSTR(ICON_FA_STOPWATCH, "Emulation Speed"), 3416 FSUI_CSTR("Sets the target emulation speed. It is not guaranteed that this speed will be reached on all systems."), 3417 "Main", "EmulationSpeed", 1.0f, emulation_speed_titles.data(), emulation_speed_values.data(), 3418 emulation_speed_titles.size(), true); 3419 DrawFloatListSetting( 3420 bsi, FSUI_ICONSTR(ICON_FA_BOLT, "Fast Forward Speed"), 3421 FSUI_CSTR("Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems."), 3422 "Main", "FastForwardSpeed", 0.0f, emulation_speed_titles.data(), emulation_speed_values.data(), 3423 emulation_speed_titles.size(), true); 3424 DrawFloatListSetting( 3425 bsi, FSUI_ICONSTR(ICON_FA_BOLT, "Turbo Speed"), 3426 FSUI_CSTR("Sets the turbo speed. It is not guaranteed that this speed will be reached on all systems."), "Main", 3427 "TurboSpeed", 2.0f, emulation_speed_titles.data(), emulation_speed_values.data(), emulation_speed_titles.size(), 3428 true); 3429 3430 MenuHeading(FSUI_CSTR("Latency Control")); 3431 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TV, "Vertical Sync (VSync)"), 3432 FSUI_CSTR("Synchronizes presentation of the console's frames to the host. GSync/FreeSync users " 3433 "should enable Optimal Frame Pacing instead."), 3434 "Display", "VSync", false); 3435 3436 DrawToggleSetting( 3437 bsi, FSUI_ICONSTR(ICON_FA_LIGHTBULB, "Sync To Host Refresh Rate"), 3438 FSUI_CSTR("Adjusts the emulation speed so the console's refresh rate matches the host when VSync is enabled."), 3439 "Main", "SyncToHostRefreshRate", false); 3440 3441 DrawToggleSetting( 3442 bsi, FSUI_ICONSTR(ICON_FA_TACHOMETER_ALT, "Optimal Frame Pacing"), 3443 FSUI_CSTR("Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, " 3444 "such as GSync/FreeSync. Disable if you are having speed or sound issues."), 3445 "Display", "OptimalFramePacing", false); 3446 3447 const bool optimal_frame_pacing_active = GetEffectiveBoolSetting(bsi, "Display", "OptimalFramePacing", false); 3448 DrawToggleSetting( 3449 bsi, FSUI_ICONSTR(ICON_FA_STOPWATCH_20, "Reduce Input Latency"), 3450 FSUI_CSTR("Reduces input latency by delaying the start of frame until closer to the presentation time."), "Display", 3451 "PreFrameSleep", false, optimal_frame_pacing_active); 3452 3453 DrawToggleSetting( 3454 bsi, FSUI_ICONSTR(ICON_FA_CHARGING_STATION, "Skip Duplicate Frame Display"), 3455 FSUI_CSTR("Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."), 3456 "Display", "SkipPresentingDuplicateFrames", false, 3457 !(GetEffectiveBoolSetting(bsi, "Display", "VSync", false) && 3458 GetEffectiveBoolSetting(bsi, "Main", "SyncToHostRefreshRate", false))); 3459 3460 const bool pre_frame_sleep_active = 3461 (optimal_frame_pacing_active && GetEffectiveBoolSetting(bsi, "Display", "PreFrameSleep", false)); 3462 DrawFloatRangeSetting( 3463 bsi, FSUI_ICONSTR(ICON_FA_BATTERY_FULL, "Frame Time Buffer"), 3464 FSUI_CSTR("Specifies the amount of buffer time added, which reduces the additional sleep time introduced."), 3465 "Display", "PreFrameSleepBuffer", Settings::DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER, 0.0f, 20.0f, "%.1f", 1.0f, 3466 pre_frame_sleep_active); 3467 3468 MenuHeading(FSUI_CSTR("Runahead/Rewind")); 3469 3470 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BACKWARD, "Enable Rewinding"), 3471 FSUI_CSTR("Saves state periodically so you can rewind any mistakes while playing."), "Main", 3472 "RewindEnable", false); 3473 DrawFloatRangeSetting( 3474 bsi, FSUI_ICONSTR(ICON_FA_SAVE, "Rewind Save Frequency"), 3475 FSUI_CSTR("How often a rewind state will be created. Higher frequencies have greater system requirements."), "Main", 3476 "RewindFrequency", 10.0f, 0.0f, 3600.0f, FSUI_CSTR("%.2f Seconds")); 3477 DrawIntRangeSetting( 3478 bsi, FSUI_ICONSTR(ICON_FA_GLASS_WHISKEY, "Rewind Save Slots"), 3479 FSUI_CSTR("How many saves will be kept for rewinding. Higher values have greater memory requirements."), "Main", 3480 "RewindSaveSlots", 10, 1, 10000, FSUI_CSTR("%d Frames")); 3481 3482 const s32 runahead_frames = GetEffectiveIntSetting(bsi, "Main", "RunaheadFrameCount", 0); 3483 const bool runahead_enabled = (runahead_frames > 0); 3484 const bool rewind_enabled = GetEffectiveBoolSetting(bsi, "Main", "RewindEnable", false); 3485 3486 static constexpr const std::array runahead_options = { 3487 FSUI_NSTR("Disabled"), FSUI_NSTR("1 Frame"), FSUI_NSTR("2 Frames"), FSUI_NSTR("3 Frames"), 3488 FSUI_NSTR("4 Frames"), FSUI_NSTR("5 Frames"), FSUI_NSTR("6 Frames"), FSUI_NSTR("7 Frames"), 3489 FSUI_NSTR("8 Frames"), FSUI_NSTR("9 Frames"), FSUI_NSTR("10 Frames")}; 3490 3491 DrawIntListSetting( 3492 bsi, FSUI_ICONSTR(ICON_FA_RUNNING, "Runahead"), 3493 FSUI_CSTR( 3494 "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."), 3495 "Main", "RunaheadFrameCount", 0, runahead_options.data(), runahead_options.size(), true); 3496 3497 TinyString rewind_summary; 3498 if (runahead_enabled) 3499 { 3500 rewind_summary = FSUI_VSTR("Rewind is disabled because runahead is enabled. Runahead will significantly increase " 3501 "system requirements."); 3502 } 3503 else if (rewind_enabled) 3504 { 3505 const u32 resolution_scale = GetEffectiveUIntSetting(bsi, "GPU", "ResolutionScale", 1); 3506 const float rewind_frequency = GetEffectiveFloatSetting(bsi, "Main", "RewindFrequency", 10.0f); 3507 const s32 rewind_save_slots = GetEffectiveIntSetting(bsi, "Main", "RewindSaveSlots", 10); 3508 const float duration = 3509 ((rewind_frequency <= std::numeric_limits<float>::epsilon()) ? (1.0f / 60.0f) : rewind_frequency) * 3510 static_cast<float>(rewind_save_slots); 3511 3512 u64 ram_usage, vram_usage; 3513 System::CalculateRewindMemoryUsage(rewind_save_slots, resolution_scale, &ram_usage, &vram_usage); 3514 rewind_summary.format( 3515 FSUI_FSTR("Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM."), 3516 rewind_save_slots, duration, ram_usage / 1048576, vram_usage / 1048576); 3517 } 3518 else 3519 { 3520 rewind_summary = FSUI_VSTR("Rewind is not enabled. Please note that enabling rewind may significantly increase " 3521 "system requirements."); 3522 } 3523 3524 ActiveButton(rewind_summary, false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font); 3525 3526 EndMenuButtons(); 3527 } 3528 3529 void FullscreenUI::CopyGlobalControllerSettingsToGame() 3530 { 3531 SettingsInterface* dsi = GetEditingSettingsInterface(true); 3532 SettingsInterface* ssi = GetEditingSettingsInterface(false); 3533 3534 InputManager::CopyConfiguration(dsi, *ssi, true, true, false); 3535 SetSettingsChanged(dsi); 3536 3537 ShowToast(std::string(), FSUI_STR("Per-game controller configuration initialized with global settings.")); 3538 } 3539 3540 void FullscreenUI::DoLoadInputProfile() 3541 { 3542 std::vector<std::string> profiles = InputManager::GetInputProfileNames(); 3543 if (profiles.empty()) 3544 { 3545 ShowToast(std::string(), FSUI_STR("No input profiles available.")); 3546 return; 3547 } 3548 3549 ImGuiFullscreen::ChoiceDialogOptions coptions; 3550 coptions.reserve(profiles.size()); 3551 for (std::string& name : profiles) 3552 coptions.emplace_back(std::move(name), false); 3553 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Load Profile"), false, std::move(coptions), 3554 [](s32 index, const std::string& title, bool checked) { 3555 if (index < 0) 3556 return; 3557 3558 INISettingsInterface ssi(System::GetInputProfilePath(title)); 3559 if (!ssi.Load()) 3560 { 3561 ShowToast(std::string(), fmt::format(FSUI_FSTR("Failed to load '{}'."), title)); 3562 CloseChoiceDialog(); 3563 return; 3564 } 3565 3566 auto lock = Host::GetSettingsLock(); 3567 SettingsInterface* dsi = GetEditingSettingsInterface(); 3568 InputManager::CopyConfiguration(dsi, ssi, true, true, IsEditingGameSettings(dsi)); 3569 SetSettingsChanged(dsi); 3570 ShowToast(std::string(), fmt::format(FSUI_FSTR("Input profile '{}' loaded."), title)); 3571 CloseChoiceDialog(); 3572 }); 3573 } 3574 3575 void FullscreenUI::DoSaveInputProfile(const std::string& name) 3576 { 3577 INISettingsInterface dsi(System::GetInputProfilePath(name)); 3578 3579 auto lock = Host::GetSettingsLock(); 3580 SettingsInterface* ssi = GetEditingSettingsInterface(); 3581 InputManager::CopyConfiguration(&dsi, *ssi, true, true, IsEditingGameSettings(ssi)); 3582 if (dsi.Save()) 3583 ShowToast(std::string(), fmt::format(FSUI_FSTR("Input profile '{}' saved."), name)); 3584 else 3585 ShowToast(std::string(), fmt::format(FSUI_FSTR("Failed to save input profile '{}'."), name)); 3586 } 3587 3588 void FullscreenUI::DoSaveNewInputProfile() 3589 { 3590 OpenInputStringDialog(FSUI_ICONSTR(ICON_FA_SAVE, "Save Profile"), 3591 FSUI_STR("Enter the name of the input profile you wish to create."), std::string(), 3592 FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Create"), [](std::string title) { 3593 if (!title.empty()) 3594 DoSaveInputProfile(title); 3595 }); 3596 } 3597 3598 void FullscreenUI::DoSaveInputProfile() 3599 { 3600 std::vector<std::string> profiles = InputManager::GetInputProfileNames(); 3601 if (profiles.empty()) 3602 { 3603 DoSaveNewInputProfile(); 3604 return; 3605 } 3606 3607 ImGuiFullscreen::ChoiceDialogOptions coptions; 3608 coptions.reserve(profiles.size() + 1); 3609 coptions.emplace_back(FSUI_STR("Create New..."), false); 3610 for (std::string& name : profiles) 3611 coptions.emplace_back(std::move(name), false); 3612 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_SAVE, "Save Profile"), false, std::move(coptions), 3613 [](s32 index, const std::string& title, bool checked) { 3614 if (index < 0) 3615 return; 3616 3617 if (index > 0) 3618 { 3619 DoSaveInputProfile(title); 3620 CloseChoiceDialog(); 3621 } 3622 else 3623 { 3624 CloseChoiceDialog(); 3625 DoSaveNewInputProfile(); 3626 } 3627 }); 3628 } 3629 3630 void FullscreenUI::ResetControllerSettings() 3631 { 3632 SettingsInterface* dsi = GetEditingSettingsInterface(); 3633 3634 Settings::SetDefaultControllerConfig(*dsi); 3635 ShowToast(std::string(), FSUI_STR("Controller settings reset to default.")); 3636 } 3637 3638 void FullscreenUI::DrawControllerSettingsPage() 3639 { 3640 BeginMenuButtons(); 3641 3642 SettingsInterface* bsi = GetEditingSettingsInterface(); 3643 const bool game_settings = IsEditingGameSettings(bsi); 3644 3645 MenuHeading(FSUI_CSTR("Configuration")); 3646 3647 if (IsEditingGameSettings(bsi)) 3648 { 3649 if (DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Per-Game Configuration"), 3650 FSUI_CSTR("Uses game-specific settings for controllers for this game."), "ControllerPorts", 3651 "UseGameSettingsForController", false, IsEditingGameSettings(bsi), false)) 3652 { 3653 // did we just enable per-game for the first time? 3654 if (bsi->GetBoolValue("ControllerPorts", "UseGameSettingsForController", false) && 3655 !bsi->GetBoolValue("ControllerPorts", "GameSettingsInitialized", false)) 3656 { 3657 bsi->SetBoolValue("ControllerPorts", "GameSettingsInitialized", true); 3658 CopyGlobalControllerSettingsToGame(); 3659 } 3660 } 3661 } 3662 3663 if (IsEditingGameSettings(bsi) && !bsi->GetBoolValue("ControllerPorts", "UseGameSettingsForController", false)) 3664 { 3665 // nothing to edit.. 3666 EndMenuButtons(); 3667 return; 3668 } 3669 3670 if (IsEditingGameSettings(bsi)) 3671 { 3672 if (MenuButton(FSUI_ICONSTR(ICON_FA_COPY, "Copy Global Settings"), 3673 FSUI_CSTR("Copies the global controller configuration to this game."))) 3674 CopyGlobalControllerSettingsToGame(); 3675 } 3676 else 3677 { 3678 if (MenuButton(FSUI_ICONSTR(ICON_FA_DUMPSTER_FIRE, "Reset Settings"), 3679 FSUI_CSTR("Resets all configuration to defaults (including bindings)."))) 3680 { 3681 ResetControllerSettings(); 3682 } 3683 } 3684 3685 if (MenuButton(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Load Profile"), 3686 FSUI_CSTR("Replaces these settings with a previously saved input profile."))) 3687 { 3688 DoLoadInputProfile(); 3689 } 3690 if (MenuButton(FSUI_ICONSTR(ICON_FA_SAVE, "Save Profile"), 3691 FSUI_CSTR("Stores the current settings to an input profile."))) 3692 { 3693 DoSaveInputProfile(); 3694 } 3695 3696 MenuHeading(FSUI_CSTR("Input Sources")); 3697 3698 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable SDL Input Source"), 3699 FSUI_CSTR("The SDL input source supports most controllers."), "InputSources", "SDL", true, true, 3700 false); 3701 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WIFI, "SDL DualShock 4 / DualSense Enhanced Mode"), 3702 FSUI_CSTR("Provides vibration and LED control support over Bluetooth."), "InputSources", 3703 "SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false); 3704 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIGHTBULB, "SDL DualSense Player LED"), 3705 FSUI_CSTR("Enable/Disable the Player LED on DualSense controllers."), "InputSources", 3706 "SDLPS5PlayerLED", false, bsi->GetBoolValue("InputSources", "SDLControllerEnhancedMode", true), 3707 false); 3708 #ifdef _WIN32 3709 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COG, "Enable XInput Input Source"), 3710 FSUI_CSTR("The XInput source provides support for XBox 360/XBox One/XBox Series controllers."), 3711 "InputSources", "XInput", false); 3712 #endif 3713 3714 MenuHeading(FSUI_CSTR("Multitap")); 3715 DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_PLUS_SQUARE, "Multitap Mode"), 3716 FSUI_CSTR("Enables an additional three controller slots on each port. Not supported in all games."), 3717 "ControllerPorts", "MultitapMode", Settings::DEFAULT_MULTITAP_MODE, &Settings::ParseMultitapModeName, 3718 &Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count); 3719 3720 // load mtap settings 3721 MultitapMode mtap_mode = g_settings.multitap_mode; 3722 if (IsEditingGameSettings(bsi)) 3723 { 3724 mtap_mode = Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str()) 3725 .value_or(g_settings.multitap_mode); 3726 } 3727 const std::array<bool, 2> mtap_enabled = { 3728 {(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts), 3729 (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}}; 3730 3731 // we reorder things a little to make it look less silly for mtap 3732 static constexpr const std::array<char, 4> mtap_slot_names = {{'A', 'B', 'C', 'D'}}; 3733 static constexpr const std::array<u32, NUM_CONTROLLER_AND_CARD_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}}; 3734 3735 // create the ports 3736 for (u32 global_slot : mtap_port_order) 3737 { 3738 const auto [mtap_port, mtap_slot] = Controller::ConvertPadToPortAndSlot(global_slot); 3739 const bool is_mtap_port = Controller::PortAndSlotIsMultitap(mtap_port, mtap_slot); 3740 if (is_mtap_port && !mtap_enabled[mtap_port]) 3741 continue; 3742 3743 if (mtap_enabled[mtap_port]) 3744 { 3745 MenuHeading(TinyString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_PLUG, "Controller Port {}{}")), 3746 mtap_port + 1, mtap_slot_names[mtap_slot])); 3747 } 3748 else 3749 { 3750 MenuHeading( 3751 TinyString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_PLUG, "Controller Port {}")), mtap_port + 1)); 3752 } 3753 3754 const TinyString section = TinyString::from_format("Pad{}", global_slot + 1); 3755 const TinyString type = 3756 bsi->GetTinyStringValue(section.c_str(), "Type", Controller::GetDefaultPadType(global_slot)); 3757 const Controller::ControllerInfo* ci = Controller::GetControllerInfo(type); 3758 if (MenuButton(TinyString::from_format("{}##type{}", FSUI_ICONSTR(ICON_FA_GAMEPAD, "Controller Type"), global_slot), 3759 ci ? Host::TranslateToCString("ControllerType", ci->display_name) : FSUI_CSTR("Unknown"))) 3760 { 3761 std::vector<std::pair<std::string, std::string>> raw_options(Controller::GetControllerTypeNames()); 3762 ImGuiFullscreen::ChoiceDialogOptions options; 3763 options.reserve(raw_options.size()); 3764 for (auto& it : raw_options) 3765 { 3766 options.emplace_back(std::move(it.second), type == it.first); 3767 } 3768 OpenChoiceDialog(TinyString::from_format(FSUI_FSTR("Port {} Controller Type"), global_slot + 1), false, 3769 std::move(options), 3770 [game_settings, section, 3771 raw_options = std::move(raw_options)](s32 index, const std::string& title, bool checked) { 3772 if (index < 0) 3773 return; 3774 3775 auto lock = Host::GetSettingsLock(); 3776 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 3777 bsi->SetStringValue(section.c_str(), "Type", raw_options[index].first.c_str()); 3778 SetSettingsChanged(bsi); 3779 CloseChoiceDialog(); 3780 }); 3781 } 3782 3783 if (!ci || ci->bindings.empty()) 3784 continue; 3785 3786 if (MenuButton(FSUI_ICONSTR(ICON_FA_MAGIC, "Automatic Mapping"), 3787 FSUI_CSTR("Attempts to map the selected port to a chosen controller."))) 3788 { 3789 StartAutomaticBinding(global_slot); 3790 } 3791 3792 for (const Controller::ControllerBindingInfo& bi : ci->bindings) 3793 { 3794 DrawInputBindingButton(bsi, bi.type, section.c_str(), bi.name, 3795 Host::TranslateToCString(ci->name, bi.display_name), bi.icon_name, true); 3796 } 3797 3798 if (mtap_enabled[mtap_port]) 3799 { 3800 MenuHeading(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_MICROCHIP, "Controller Port {}{} Macros")), 3801 mtap_port + 1, mtap_slot_names[mtap_slot])); 3802 } 3803 else 3804 { 3805 MenuHeading(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_MICROCHIP, "Controller Port {} Macros")), 3806 mtap_port + 1)); 3807 } 3808 3809 for (u32 macro_index = 0; macro_index < InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++) 3810 { 3811 DrawInputBindingButton(bsi, InputBindingInfo::Type::Macro, section.c_str(), 3812 TinyString::from_format("Macro{}", macro_index + 1), 3813 TinyString::from_format(FSUI_FSTR("Macro {} Trigger"), macro_index + 1), nullptr); 3814 DrawToggleSetting(bsi, 3815 TinyString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Macro {} Press To Toggle")), 3816 macro_index + 1), 3817 nullptr, section.c_str(), TinyString::from_format("Macro{}Toggle", macro_index + 1), false, 3818 true, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 3819 3820 SmallString binds_string = 3821 bsi->GetSmallStringValue(section.c_str(), fmt::format("Macro{}Binds", macro_index + 1).c_str()); 3822 TinyString pretty_binds_string; 3823 if (!binds_string.empty()) 3824 { 3825 for (const std::string_view& bind : StringUtil::SplitString(binds_string, '&', true)) 3826 { 3827 const char* dispname = nullptr; 3828 for (const Controller::ControllerBindingInfo& bi : ci->bindings) 3829 { 3830 if (bind == bi.name) 3831 { 3832 dispname = bi.icon_name ? bi.icon_name : Host::TranslateToCString(ci->name, bi.display_name); 3833 break; 3834 } 3835 } 3836 pretty_binds_string.append_format("{}{}", pretty_binds_string.empty() ? "" : " ", dispname); 3837 } 3838 } 3839 if (MenuButtonWithValue( 3840 TinyString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_KEYBOARD, "Macro {} Buttons")), macro_index + 1), 3841 nullptr, pretty_binds_string.empty() ? FSUI_CSTR("-") : pretty_binds_string.c_str(), true, 3842 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 3843 { 3844 std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true)); 3845 ImGuiFullscreen::ChoiceDialogOptions options; 3846 for (const Controller::ControllerBindingInfo& bi : ci->bindings) 3847 { 3848 if (bi.type != InputBindingInfo::Type::Button && bi.type != InputBindingInfo::Type::Axis && 3849 bi.type != InputBindingInfo::Type::HalfAxis) 3850 { 3851 continue; 3852 } 3853 options.emplace_back(Host::TranslateToString(ci->name, bi.display_name), 3854 std::any_of(buttons_split.begin(), buttons_split.end(), 3855 [bi](const std::string_view& it) { return (it == bi.name); })); 3856 } 3857 3858 OpenChoiceDialog( 3859 TinyString::from_format(FSUI_FSTR("Select Macro {} Binds"), macro_index + 1), true, std::move(options), 3860 [game_settings, section, macro_index, ci](s32 index, const std::string& title, bool checked) { 3861 // convert display name back to bind name 3862 std::string_view to_modify; 3863 for (const Controller::ControllerBindingInfo& bi : ci->bindings) 3864 { 3865 if (title == Host::TranslateToStringView(ci->name, bi.display_name)) 3866 { 3867 to_modify = bi.name; 3868 break; 3869 } 3870 } 3871 if (to_modify.empty()) 3872 { 3873 // wtf? 3874 return; 3875 } 3876 3877 auto lock = Host::GetSettingsLock(); 3878 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 3879 const TinyString key = TinyString::from_format("Macro{}Binds", macro_index + 1); 3880 3881 std::string binds_string = bsi->GetStringValue(section.c_str(), key.c_str()); 3882 std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true)); 3883 auto it = std::find(buttons_split.begin(), buttons_split.end(), to_modify); 3884 if (checked) 3885 { 3886 if (it == buttons_split.end()) 3887 buttons_split.push_back(to_modify); 3888 } 3889 else 3890 { 3891 if (it != buttons_split.end()) 3892 buttons_split.erase(it); 3893 } 3894 3895 binds_string = StringUtil::JoinString(buttons_split.begin(), buttons_split.end(), " & "); 3896 if (binds_string.empty()) 3897 bsi->DeleteValue(section.c_str(), key.c_str()); 3898 else 3899 bsi->SetStringValue(section.c_str(), key.c_str(), binds_string.c_str()); 3900 }); 3901 } 3902 3903 const TinyString freq_key = TinyString::from_format("Macro{}Frequency", macro_index + 1); 3904 const SmallString freq_title = 3905 SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_LIGHTBULB, "Macro {} Frequency")), macro_index + 1); 3906 s32 frequency = bsi->GetIntValue(section.c_str(), freq_key.c_str(), 0); 3907 SmallString freq_summary; 3908 if (frequency == 0) 3909 freq_summary = FSUI_VSTR("Disabled"); 3910 else 3911 freq_summary.format(FSUI_FSTR("{} Frames"), frequency); 3912 if (MenuButtonWithValue(freq_title, nullptr, freq_summary, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 3913 ImGui::OpenPopup(freq_title); 3914 3915 ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); 3916 3917 ImGui::PushFont(g_large_font); 3918 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 3919 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 3920 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 3921 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 3922 3923 if (ImGui::BeginPopupModal(freq_title, nullptr, 3924 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 3925 { 3926 ImGui::SetNextItemWidth(LayoutScale(450.0f)); 3927 if (ImGui::SliderInt("##value", &frequency, 0, 60, FSUI_CSTR("Toggle every %d frames"), 3928 ImGuiSliderFlags_NoInput)) 3929 { 3930 if (frequency == 0) 3931 bsi->DeleteValue(section.c_str(), freq_key.c_str()); 3932 else 3933 bsi->SetIntValue(section.c_str(), freq_key.c_str(), frequency); 3934 } 3935 3936 BeginMenuButtons(); 3937 if (MenuButton(FSUI_CSTR("OK"), nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 3938 ImGui::CloseCurrentPopup(); 3939 EndMenuButtons(); 3940 3941 ImGui::EndPopup(); 3942 } 3943 3944 ImGui::PopStyleVar(3); 3945 ImGui::PopFont(); 3946 } 3947 3948 if (!ci->settings.empty()) 3949 { 3950 if (mtap_enabled[mtap_port]) 3951 { 3952 MenuHeading( 3953 SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_SLIDERS_H, "Controller Port {}{} Settings")), 3954 mtap_port + 1, mtap_slot_names[mtap_slot])); 3955 } 3956 else 3957 { 3958 MenuHeading(SmallString::from_format( 3959 fmt::runtime(FSUI_ICONSTR(ICON_FA_SLIDERS_H, "Controller Port {} Settings")), mtap_port + 1)); 3960 } 3961 3962 for (const SettingInfo& si : ci->settings) 3963 { 3964 TinyString title; 3965 title.format(ICON_FA_COG "{}", Host::TranslateToStringView(ci->name, si.display_name)); 3966 const char* description = Host::TranslateToCString(ci->name, si.description); 3967 switch (si.type) 3968 { 3969 case SettingInfo::Type::Boolean: 3970 DrawToggleSetting(bsi, title, description, section.c_str(), si.name, si.BooleanDefaultValue(), true, false); 3971 break; 3972 case SettingInfo::Type::Integer: 3973 DrawIntRangeSetting(bsi, title, description, section.c_str(), si.name, si.IntegerDefaultValue(), 3974 si.IntegerMinValue(), si.IntegerMaxValue(), si.format, true); 3975 break; 3976 case SettingInfo::Type::IntegerList: 3977 DrawIntListSetting(bsi, title, description, section.c_str(), si.name, si.IntegerDefaultValue(), si.options, 3978 0, true, si.IntegerMinValue(), true, LAYOUT_MENU_BUTTON_HEIGHT, g_large_font, 3979 g_medium_font, ci->name); 3980 break; 3981 case SettingInfo::Type::Float: 3982 DrawFloatSpinBoxSetting(bsi, title, description, section.c_str(), si.name, si.FloatDefaultValue(), 3983 si.FloatMinValue(), si.FloatMaxValue(), si.FloatStepValue(), si.multiplier, 3984 si.format, true); 3985 break; 3986 default: 3987 break; 3988 } 3989 } 3990 } 3991 } 3992 3993 EndMenuButtons(); 3994 } 3995 3996 void FullscreenUI::DrawHotkeySettingsPage() 3997 { 3998 SettingsInterface* bsi = GetEditingSettingsInterface(); 3999 4000 BeginMenuButtons(); 4001 4002 const HotkeyInfo* last_category = nullptr; 4003 for (const HotkeyInfo* hotkey : s_hotkey_list_cache) 4004 { 4005 if (!last_category || std::strcmp(hotkey->category, last_category->category) != 0) 4006 { 4007 MenuHeading(Host::TranslateToCString("Hotkeys", hotkey->category)); 4008 last_category = hotkey; 4009 } 4010 4011 DrawInputBindingButton(bsi, InputBindingInfo::Type::Button, "Hotkeys", hotkey->name, 4012 Host::TranslateToCString("Hotkeys", hotkey->display_name), nullptr, false); 4013 } 4014 4015 EndMenuButtons(); 4016 } 4017 4018 void FullscreenUI::DrawMemoryCardSettingsPage() 4019 { 4020 static constexpr const std::array type_keys = {"Card1Type", "Card2Type"}; 4021 static constexpr const std::array path_keys = {"Card1Path", "Card2Path"}; 4022 4023 SettingsInterface* bsi = GetEditingSettingsInterface(); 4024 const bool game_settings = IsEditingGameSettings(bsi); 4025 4026 BeginMenuButtons(); 4027 4028 MenuHeading(FSUI_CSTR("Settings and Operations")); 4029 DrawFolderSetting(bsi, FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Memory Card Directory"), "MemoryCards", "Directory", 4030 EmuFolders::MemoryCards); 4031 4032 if (!game_settings && MenuButton(FSUI_ICONSTR(ICON_FA_MAGIC, "Reset Memory Card Directory"), 4033 FSUI_CSTR("Resets memory card directory to default (user directory)."))) 4034 { 4035 bsi->SetStringValue("MemoryCards", "Directory", "memcards"); 4036 SetSettingsChanged(bsi); 4037 } 4038 4039 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "Use Single Card For Multi-Disc Games"), 4040 FSUI_CSTR("When playing a multi-disc game and using per-game (title) memory cards, " 4041 "use a single memory card for all discs."), 4042 "MemoryCards", "UsePlaylistTitle", true); 4043 4044 for (u32 i = 0; i < 2; i++) 4045 { 4046 MenuHeading(TinyString::from_format(FSUI_FSTR("Memory Card Port {}"), i + 1)); 4047 4048 const MemoryCardType default_type = 4049 (i == 0) ? Settings::DEFAULT_MEMORY_CARD_1_TYPE : Settings::DEFAULT_MEMORY_CARD_2_TYPE; 4050 DrawEnumSetting( 4051 bsi, TinyString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_SD_CARD, "Memory Card {} Type")), i + 1), 4052 SmallString::from_format(FSUI_FSTR("Sets which sort of memory card image will be used for slot {}."), i + 1), 4053 "MemoryCards", type_keys[i], default_type, &Settings::ParseMemoryCardTypeName, &Settings::GetMemoryCardTypeName, 4054 &Settings::GetMemoryCardTypeDisplayName, MemoryCardType::Count); 4055 4056 const MemoryCardType effective_type = 4057 Settings::ParseMemoryCardTypeName( 4058 GetEffectiveTinyStringSetting(bsi, "MemoryCards", type_keys[i], Settings::GetMemoryCardTypeName(default_type)) 4059 .c_str()) 4060 .value_or(default_type); 4061 const bool is_shared = (effective_type == MemoryCardType::Shared); 4062 std::optional<SmallString> path_value(bsi->GetOptionalSmallStringValue( 4063 "MemoryCards", path_keys[i], 4064 IsEditingGameSettings(bsi) ? std::nullopt : 4065 std::optional<const char*>((i == 0) ? "shared_card_1.mcd" : "shared_card_2.mcd"))); 4066 4067 TinyString title; 4068 title.format("{}##card_name_{}", FSUI_ICONSTR(ICON_FA_FILE, "Shared Card Name"), i); 4069 if (MenuButtonWithValue(title, 4070 FSUI_CSTR("The selected memory card image will be used in shared mode for this slot."), 4071 path_value.has_value() ? path_value->c_str() : FSUI_CSTR("Use Global Setting"), is_shared)) 4072 { 4073 ImGuiFullscreen::ChoiceDialogOptions options; 4074 std::vector<std::string> names; 4075 if (IsEditingGameSettings(bsi)) 4076 options.emplace_back("Use Global Setting", !path_value.has_value()); 4077 if (path_value.has_value() && !path_value->empty()) 4078 { 4079 options.emplace_back(fmt::format("{} (Current)", path_value.value()), true); 4080 names.emplace_back(path_value.value().view()); 4081 } 4082 4083 FileSystem::FindResultsArray results; 4084 FileSystem::FindFiles(EmuFolders::MemoryCards.c_str(), "*.mcd", 4085 FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, 4086 &results); 4087 for (FILESYSTEM_FIND_DATA& ffd : results) 4088 { 4089 const bool selected = (path_value.has_value() && path_value.value() == ffd.FileName); 4090 options.emplace_back(std::move(ffd.FileName), selected); 4091 } 4092 4093 OpenChoiceDialog( 4094 title, false, std::move(options), 4095 [game_settings = IsEditingGameSettings(bsi), i](s32 index, const std::string& title, bool checked) { 4096 if (index < 0) 4097 return; 4098 4099 auto lock = Host::GetSettingsLock(); 4100 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 4101 if (game_settings && index == 0) 4102 { 4103 bsi->DeleteValue("MemoryCards", path_keys[i]); 4104 } 4105 else 4106 { 4107 if (game_settings) 4108 index--; 4109 bsi->SetStringValue("MemoryCards", path_keys[i], title.c_str()); 4110 } 4111 SetSettingsChanged(bsi); 4112 CloseChoiceDialog(); 4113 }); 4114 } 4115 } 4116 4117 EndMenuButtons(); 4118 } 4119 4120 void FullscreenUI::DrawDisplaySettingsPage() 4121 { 4122 static constexpr const std::array resolution_scales = { 4123 FSUI_NSTR("Automatic based on window size"), 4124 FSUI_NSTR("1x"), 4125 FSUI_NSTR("2x"), 4126 FSUI_NSTR("3x (for 720p)"), 4127 FSUI_NSTR("4x"), 4128 FSUI_NSTR("5x (for 1080p)"), 4129 FSUI_NSTR("6x (for 1440p)"), 4130 FSUI_NSTR("7x"), 4131 FSUI_NSTR("8x"), 4132 FSUI_NSTR("9x (for 4K)"), 4133 FSUI_NSTR("10x"), 4134 FSUI_NSTR("11x"), 4135 FSUI_NSTR("12x"), 4136 FSUI_NSTR("13x"), 4137 FSUI_NSTR("14x"), 4138 FSUI_NSTR("15x"), 4139 FSUI_NSTR("16x"), 4140 }; 4141 4142 SettingsInterface* bsi = GetEditingSettingsInterface(); 4143 const bool game_settings = IsEditingGameSettings(bsi); 4144 const u32 resolution_scale = GetEffectiveUIntSetting(bsi, "GPU", "ResolutionScale", 1); 4145 4146 BeginMenuButtons(); 4147 4148 MenuHeading(FSUI_CSTR("Device Settings")); 4149 4150 DrawEnumSetting(bsi, FSUI_CSTR("GPU Renderer"), 4151 FSUI_CSTR("Chooses the backend to use for rendering the console/game visuals."), "GPU", "Renderer", 4152 Settings::DEFAULT_GPU_RENDERER, &Settings::ParseRendererName, &Settings::GetRendererName, 4153 &Settings::GetRendererDisplayName, GPURenderer::Count); 4154 4155 const GPURenderer renderer = 4156 Settings::ParseRendererName( 4157 GetEffectiveTinyStringSetting(bsi, "GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)) 4158 .c_str()) 4159 .value_or(Settings::DEFAULT_GPU_RENDERER); 4160 const bool is_hardware = (renderer != GPURenderer::Software); 4161 4162 std::optional<SmallString> current_adapter = 4163 bsi->GetOptionalSmallStringValue("GPU", "Adapter", game_settings ? std::nullopt : std::optional<const char*>("")); 4164 4165 if (MenuButtonWithValue(FSUI_CSTR("GPU Adapter"), FSUI_CSTR("Selects the GPU to use for rendering."), 4166 current_adapter.has_value() ? 4167 (current_adapter->empty() ? FSUI_CSTR("Default") : current_adapter->c_str()) : 4168 FSUI_CSTR("Use Global Setting"))) 4169 { 4170 ImGuiFullscreen::ChoiceDialogOptions options; 4171 options.reserve(s_graphics_adapter_list_cache.size() + 2); 4172 if (game_settings) 4173 options.emplace_back(FSUI_STR("Use Global Setting"), !current_adapter.has_value()); 4174 options.emplace_back(FSUI_STR("Default"), current_adapter.has_value() && current_adapter->empty()); 4175 for (const GPUDevice::AdapterInfo& adapter : s_graphics_adapter_list_cache) 4176 { 4177 const bool checked = (current_adapter.has_value() && current_adapter.value() == adapter.name); 4178 options.emplace_back(adapter.name, checked); 4179 } 4180 4181 auto callback = [game_settings](s32 index, const std::string& title, bool checked) { 4182 if (index < 0) 4183 return; 4184 4185 const char* value; 4186 if (game_settings && index == 0) 4187 value = nullptr; 4188 else if ((!game_settings && index == 0) || (game_settings && index == 1)) 4189 value = ""; 4190 else 4191 value = title.c_str(); 4192 4193 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 4194 if (!value) 4195 bsi->DeleteValue("GPU", "Adapter"); 4196 else 4197 bsi->SetStringValue("GPU", "Adapter", value); 4198 SetSettingsChanged(bsi); 4199 ShowToast(std::string(), FSUI_STR("GPU adapter will be applied after restarting."), 10.0f); 4200 CloseChoiceDialog(); 4201 }; 4202 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_TV, "GPU Adapter"), false, std::move(options), std::move(callback)); 4203 } 4204 4205 const bool true_color_enabled = (is_hardware && GetEffectiveBoolSetting(bsi, "GPU", "TrueColor", false)); 4206 const bool pgxp_enabled = (is_hardware && GetEffectiveBoolSetting(bsi, "GPU", "PGXPEnable", false)); 4207 const bool texture_correction_enabled = 4208 (pgxp_enabled && GetEffectiveBoolSetting(bsi, "GPU", "PGXPTextureCorrection", true)); 4209 4210 MenuHeading(FSUI_CSTR("Rendering")); 4211 4212 if (is_hardware) 4213 { 4214 DrawIntListSetting( 4215 bsi, FSUI_CSTR("Internal Resolution"), 4216 FSUI_CSTR("Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution."), 4217 "GPU", "ResolutionScale", 1, resolution_scales.data(), resolution_scales.size(), true, 0); 4218 4219 DrawEnumSetting(bsi, FSUI_CSTR("Downsampling"), 4220 FSUI_CSTR("Downsamples the rendered image prior to displaying it. Can improve " 4221 "overall image quality in mixed 2D/3D games."), 4222 "GPU", "DownsampleMode", Settings::DEFAULT_GPU_DOWNSAMPLE_MODE, &Settings::ParseDownsampleModeName, 4223 &Settings::GetDownsampleModeName, &Settings::GetDownsampleModeDisplayName, GPUDownsampleMode::Count, 4224 (renderer != GPURenderer::Software)); 4225 if (Settings::ParseDownsampleModeName( 4226 GetEffectiveTinyStringSetting(bsi, "GPU", "DownsampleMode", 4227 Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE)) 4228 .c_str()) 4229 .value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE) == GPUDownsampleMode::Box) 4230 { 4231 DrawIntRangeSetting(bsi, FSUI_CSTR("Downsampling Display Scale"), 4232 FSUI_CSTR("Selects the resolution scale that will be applied to the final image. 1x will " 4233 "downsample to the original console resolution."), 4234 "GPU", "DownsampleScale", 1, 1, GPU::MAX_RESOLUTION_SCALE, "%dx"); 4235 } 4236 4237 DrawEnumSetting(bsi, FSUI_CSTR("Texture Filtering"), 4238 FSUI_CSTR("Smooths out the blockiness of magnified textures on 3D objects."), "GPU", 4239 "TextureFilter", Settings::DEFAULT_GPU_TEXTURE_FILTER, &Settings::ParseTextureFilterName, 4240 &Settings::GetTextureFilterName, &Settings::GetTextureFilterDisplayName, GPUTextureFilter::Count); 4241 4242 DrawEnumSetting(bsi, FSUI_CSTR("Sprite Texture Filtering"), 4243 FSUI_CSTR("Smooths out the blockiness of magnified textures on 2D objects."), "GPU", 4244 "SpriteTextureFilter", Settings::DEFAULT_GPU_TEXTURE_FILTER, &Settings::ParseTextureFilterName, 4245 &Settings::GetTextureFilterName, &Settings::GetTextureFilterDisplayName, GPUTextureFilter::Count); 4246 } 4247 4248 DrawEnumSetting(bsi, FSUI_CSTR("Aspect Ratio"), 4249 FSUI_CSTR("Changes the aspect ratio used to display the console's output to the screen."), "Display", 4250 "AspectRatio", Settings::DEFAULT_DISPLAY_ASPECT_RATIO, &Settings::ParseDisplayAspectRatio, 4251 &Settings::GetDisplayAspectRatioName, &Settings::GetDisplayAspectRatioDisplayName, 4252 DisplayAspectRatio::Count); 4253 4254 DrawEnumSetting( 4255 bsi, FSUI_CSTR("Deinterlacing Mode"), 4256 FSUI_CSTR( 4257 "Determines which algorithm is used to convert interlaced frames to progressive for display on your system."), 4258 "Display", "DeinterlacingMode", Settings::DEFAULT_DISPLAY_DEINTERLACING_MODE, 4259 &Settings::ParseDisplayDeinterlacingMode, &Settings::GetDisplayDeinterlacingModeName, 4260 &Settings::GetDisplayDeinterlacingModeDisplayName, DisplayDeinterlacingMode::Count); 4261 4262 DrawEnumSetting(bsi, FSUI_CSTR("Crop Mode"), 4263 FSUI_CSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."), 4264 "Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode, 4265 &Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::Count); 4266 4267 DrawEnumSetting( 4268 bsi, FSUI_CSTR("Scaling"), 4269 FSUI_CSTR("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."), 4270 "Display", "Scaling", Settings::DEFAULT_DISPLAY_SCALING, &Settings::ParseDisplayScaling, 4271 &Settings::GetDisplayScalingName, &Settings::GetDisplayScalingDisplayName, DisplayScalingMode::Count); 4272 4273 if (is_hardware) 4274 { 4275 DrawToggleSetting(bsi, FSUI_CSTR("True Color Rendering"), 4276 FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU", 4277 "TrueColor", true); 4278 } 4279 4280 DrawToggleSetting(bsi, FSUI_CSTR("Widescreen Rendering"), 4281 FSUI_CSTR("Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."), 4282 "GPU", "WidescreenHack", false); 4283 4284 if (is_hardware) 4285 { 4286 DrawToggleSetting( 4287 bsi, FSUI_CSTR("PGXP Geometry Correction"), 4288 FSUI_CSTR("Reduces \"wobbly\" polygons by attempting to preserve the fractional component through memory " 4289 "transfers."), 4290 "GPU", "PGXPEnable", false); 4291 4292 DrawToggleSetting(bsi, FSUI_CSTR("PGXP Depth Buffer"), 4293 FSUI_CSTR("Reduces polygon Z-fighting through depth testing. Low compatibility with games."), 4294 "GPU", "PGXPDepthBuffer", false, pgxp_enabled && texture_correction_enabled); 4295 } 4296 4297 DrawToggleSetting( 4298 bsi, FSUI_CSTR("Force 4:3 For FMVs"), 4299 FSUI_CSTR("Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."), "Display", 4300 "Force4_3For24Bit", false); 4301 4302 DrawToggleSetting(bsi, FSUI_CSTR("FMV Chroma Smoothing"), 4303 FSUI_CSTR("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs."), 4304 "GPU", "ChromaSmoothing24Bit", false); 4305 4306 DrawToggleSetting( 4307 bsi, FSUI_CSTR("Disable Interlacing"), 4308 FSUI_CSTR("Disables interlaced rendering and display in the GPU. Some games can render in 480p this way, " 4309 "but others will break."), 4310 "GPU", "DisableInterlacing", true); 4311 4312 DrawToggleSetting( 4313 bsi, FSUI_CSTR("Force NTSC Timings"), 4314 FSUI_CSTR("Forces PAL games to run at NTSC timings, i.e. 60hz. Some PAL games will run at their \"normal\" " 4315 "speeds, while others will break."), 4316 "GPU", "ForceNTSCTimings", false); 4317 4318 MenuHeading(FSUI_CSTR("Advanced")); 4319 4320 std::optional<SmallString> strvalue = bsi->GetOptionalSmallStringValue( 4321 "GPU", "FullscreenMode", game_settings ? std::nullopt : std::optional<const char*>("")); 4322 4323 if (MenuButtonWithValue( 4324 FSUI_CSTR("Fullscreen Resolution"), FSUI_CSTR("Selects the resolution to use in fullscreen modes."), 4325 strvalue.has_value() ? (strvalue->empty() ? FSUI_CSTR("Borderless Fullscreen") : strvalue->c_str()) : 4326 FSUI_CSTR("Use Global Setting"))) 4327 { 4328 const GPUDevice::AdapterInfo* selected_adapter = nullptr; 4329 if (current_adapter.has_value()) 4330 { 4331 for (const GPUDevice::AdapterInfo& ai : s_graphics_adapter_list_cache) 4332 { 4333 if (ai.name == current_adapter->view()) 4334 { 4335 selected_adapter = &ai; 4336 break; 4337 } 4338 } 4339 } 4340 else 4341 { 4342 if (!s_graphics_adapter_list_cache.empty()) 4343 selected_adapter = &s_graphics_adapter_list_cache.front(); 4344 } 4345 4346 ImGuiFullscreen::ChoiceDialogOptions options; 4347 options.reserve((selected_adapter ? selected_adapter->fullscreen_modes.size() : 0) + 2); 4348 if (game_settings) 4349 options.emplace_back(FSUI_STR("Use Global Setting"), !strvalue.has_value()); 4350 options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty()); 4351 if (selected_adapter) 4352 { 4353 for (const std::string& mode : selected_adapter->fullscreen_modes) 4354 { 4355 const bool checked = (strvalue.has_value() && strvalue.value() == mode); 4356 options.emplace_back(mode, checked); 4357 } 4358 } 4359 4360 auto callback = [game_settings](s32 index, const std::string& title, bool checked) { 4361 if (index < 0) 4362 return; 4363 4364 const char* value; 4365 if (game_settings && index == 0) 4366 value = nullptr; 4367 else if ((!game_settings && index == 0) || (game_settings && index == 1)) 4368 value = ""; 4369 else 4370 value = title.c_str(); 4371 4372 SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); 4373 if (!value) 4374 bsi->DeleteValue("GPU", "FullscreenMode"); 4375 else 4376 bsi->SetStringValue("GPU", "FullscreenMode", value); 4377 SetSettingsChanged(bsi); 4378 ShowToast(std::string(), FSUI_STR("Resolution change will be applied after restarting."), 10.0f); 4379 CloseChoiceDialog(); 4380 }; 4381 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_TV, "Fullscreen Resolution"), false, std::move(options), std::move(callback)); 4382 } 4383 4384 DrawEnumSetting(bsi, FSUI_CSTR("Screen Position"), 4385 FSUI_CSTR("Determines the position on the screen when black borders must be added."), "Display", 4386 "Alignment", Settings::DEFAULT_DISPLAY_ALIGNMENT, &Settings::ParseDisplayAlignment, 4387 &Settings::GetDisplayAlignmentName, &Settings::GetDisplayAlignmentDisplayName, 4388 DisplayAlignment::Count); 4389 4390 DrawEnumSetting(bsi, FSUI_CSTR("Screen Rotation"), FSUI_CSTR("Determines the rotation of the simulated TV screen."), 4391 "Display", "Rotation", Settings::DEFAULT_DISPLAY_ROTATION, &Settings::ParseDisplayRotation, 4392 &Settings::GetDisplayRotationName, &Settings::GetDisplayRotationDisplayName, DisplayRotation::Count); 4393 4394 if (is_hardware) 4395 { 4396 DrawEnumSetting(bsi, FSUI_CSTR("Line Detection"), 4397 FSUI_CSTR("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization " 4398 "behavior, filling in gaps introduced by upscaling."), 4399 "GPU", "LineDetectMode", Settings::DEFAULT_GPU_LINE_DETECT_MODE, &Settings::ParseLineDetectModeName, 4400 &Settings::GetLineDetectModeName, &Settings::GetLineDetectModeDisplayName, GPULineDetectMode::Count, 4401 resolution_scale > 1); 4402 4403 DrawToggleSetting( 4404 bsi, FSUI_CSTR("True Color Debanding"), 4405 FSUI_CSTR("Applies modern dithering techniques to further smooth out gradients when true color is enabled."), 4406 "GPU", "Debanding", false, true_color_enabled); 4407 4408 DrawToggleSetting( 4409 bsi, FSUI_CSTR("Scaled Dithering"), 4410 FSUI_CSTR("Scales the dithering pattern with the internal rendering resolution, making it less noticeable. " 4411 "Usually safe to enable."), 4412 "GPU", "ScaledDithering", true, !true_color_enabled); 4413 4414 DrawToggleSetting(bsi, FSUI_CSTR("Accurate Blending"), 4415 FSUI_CSTR("Forces blending to be done in the shader at 16-bit precision, when not using true " 4416 "color. Non-trivial performance impact, and unnecessary for most games."), 4417 "GPU", "AccurateBlending", false, !true_color_enabled); 4418 4419 const GPUTextureFilter texture_filtering = 4420 Settings::ParseTextureFilterName( 4421 GetEffectiveTinyStringSetting(bsi, "GPU", "TextureFilter", 4422 Settings::GetTextureFilterName(Settings::DEFAULT_GPU_TEXTURE_FILTER))) 4423 .value_or(Settings::DEFAULT_GPU_TEXTURE_FILTER); 4424 4425 DrawToggleSetting( 4426 bsi, FSUI_CSTR("Round Upscaled Texture Coordinates"), 4427 FSUI_CSTR("Rounds texture coordinates instead of flooring when upscaling. Can fix misaligned " 4428 "textures in some games, but break others, and is incompatible with texture filtering."), 4429 "GPU", "ForceRoundTextureCoordinates", false, 4430 resolution_scale > 1 && texture_filtering == GPUTextureFilter::Nearest); 4431 4432 DrawToggleSetting( 4433 bsi, FSUI_CSTR("Use Software Renderer For Readbacks"), 4434 FSUI_CSTR("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result " 4435 "in greater performance."), 4436 "GPU", "UseSoftwareRendererForReadbacks", false); 4437 } 4438 4439 DrawToggleSetting( 4440 bsi, FSUI_CSTR("Stretch Display Vertically"), 4441 FSUI_CSTR("Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally."), 4442 "Display", "StretchVertically", false); 4443 4444 DrawToggleSetting( 4445 bsi, FSUI_CSTR("Disable Mailbox Presentation"), 4446 FSUI_CSTR("Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. " 4447 "Usually results in worse frame pacing."), 4448 "Display", "DisableMailboxPresentation", false); 4449 4450 switch (renderer) 4451 { 4452 #ifdef _WIN32 4453 case GPURenderer::HardwareD3D11: 4454 { 4455 DrawToggleSetting( 4456 bsi, FSUI_CSTR("Use Blit Swap Chain"), 4457 FSUI_CSTR("Uses a blit presentation model instead of flipping. This may be needed on some systems."), "Display", 4458 "UseBlitSwapChain", false); 4459 } 4460 break; 4461 #endif 4462 4463 #ifdef ENABLE_VULKAN 4464 case GPURenderer::HardwareVulkan: 4465 { 4466 DrawToggleSetting(bsi, FSUI_CSTR("Threaded Presentation"), 4467 FSUI_CSTR("Presents frames on a background thread when fast forwarding or vsync is disabled."), 4468 "GPU", "ThreadedPresentation", true); 4469 } 4470 break; 4471 #endif 4472 4473 case GPURenderer::Software: 4474 { 4475 DrawToggleSetting(bsi, FSUI_CSTR("Threaded Rendering"), 4476 FSUI_CSTR("Uses a second thread for drawing graphics. Speed boost, and safe to use."), "GPU", 4477 "UseThread", true); 4478 } 4479 break; 4480 4481 default: 4482 break; 4483 } 4484 4485 if (is_hardware && pgxp_enabled) 4486 { 4487 MenuHeading(FSUI_CSTR("PGXP (Precision Geometry Transform Pipeline)")); 4488 4489 DrawToggleSetting( 4490 bsi, FSUI_CSTR("Perspective Correct Textures"), 4491 FSUI_CSTR("Uses perspective-correct interpolation for texture coordinates, straightening out warped textures."), 4492 "GPU", "PGXPTextureCorrection", true, pgxp_enabled); 4493 DrawToggleSetting( 4494 bsi, FSUI_CSTR("Perspective Correct Colors"), 4495 FSUI_CSTR("Uses perspective-correct interpolation for colors, which can improve visuals in some games."), "GPU", 4496 "PGXPColorCorrection", false, pgxp_enabled); 4497 DrawToggleSetting( 4498 bsi, FSUI_CSTR("Culling Correction"), 4499 FSUI_CSTR("Increases the precision of polygon culling, reducing the number of holes in geometry."), "GPU", 4500 "PGXPCulling", true, pgxp_enabled); 4501 DrawToggleSetting( 4502 bsi, FSUI_CSTR("Preserve Projection Precision"), 4503 FSUI_CSTR("Adds additional precision to PGXP data post-projection. May improve visuals in some games."), "GPU", 4504 "PGXPPreserveProjFP", false, pgxp_enabled); 4505 4506 DrawToggleSetting(bsi, FSUI_CSTR("CPU Mode"), 4507 FSUI_CSTR("Uses PGXP for all instructions, not just memory operations."), "GPU", "PGXPCPU", false, 4508 pgxp_enabled); 4509 4510 DrawToggleSetting(bsi, FSUI_CSTR("Vertex Cache"), 4511 FSUI_CSTR("Uses screen positions to resolve PGXP data. May improve visuals in some games."), 4512 "GPU", "PGXPVertexCache", pgxp_enabled); 4513 4514 DrawToggleSetting( 4515 bsi, FSUI_CSTR("Disable on 2D Polygons"), 4516 FSUI_CSTR("Uses native resolution coordinates for 2D polygons, instead of precise coordinates. Can " 4517 "fix misaligned UI in some games, but otherwise should be left disabled."), 4518 "GPU", "PGXPDisableOn2DPolygons", false, pgxp_enabled); 4519 4520 DrawFloatRangeSetting( 4521 bsi, FSUI_CSTR("Geometry Tolerance"), 4522 FSUI_CSTR("Sets a threshold for discarding precise values when exceeded. May help with glitches in some games."), 4523 "GPU", "PGXPTolerance", -1.0f, -1.0f, 10.0f, "%.1f", pgxp_enabled); 4524 4525 DrawFloatRangeSetting( 4526 bsi, FSUI_CSTR("Depth Clear Threshold"), 4527 FSUI_CSTR("Sets a threshold for discarding the emulated depth buffer. May help in some games."), "GPU", 4528 "PGXPDepthBuffer", Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD, 0.0f, 4096.0f, "%.1f", pgxp_enabled); 4529 } 4530 4531 MenuHeading(FSUI_CSTR("Capture")); 4532 4533 DrawEnumSetting(bsi, FSUI_CSTR("Screenshot Size"), 4534 FSUI_CSTR("Determines the size of screenshots created by DuckStation."), "Display", "ScreenshotMode", 4535 Settings::DEFAULT_DISPLAY_SCREENSHOT_MODE, &Settings::ParseDisplayScreenshotMode, 4536 &Settings::GetDisplayScreenshotModeName, &Settings::GetDisplayScreenshotModeDisplayName, 4537 DisplayScreenshotMode::Count); 4538 DrawEnumSetting(bsi, FSUI_CSTR("Screenshot Format"), 4539 FSUI_CSTR("Determines the format that screenshots will be saved/compressed with."), "Display", 4540 "ScreenshotFormat", Settings::DEFAULT_DISPLAY_SCREENSHOT_FORMAT, 4541 &Settings::ParseDisplayScreenshotFormat, &Settings::GetDisplayScreenshotFormatName, 4542 &Settings::GetDisplayScreenshotFormatDisplayName, DisplayScreenshotFormat::Count); 4543 DrawIntRangeSetting(bsi, FSUI_CSTR("Screenshot Quality"), 4544 FSUI_CSTR("Selects the quality at which screenshots will be compressed."), "Display", 4545 "ScreenshotQuality", Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY, 1, 100, "%d%%"); 4546 4547 MenuHeading(FSUI_CSTR("Texture Replacements")); 4548 4549 DrawToggleSetting(bsi, FSUI_CSTR("Enable VRAM Write Texture Replacement"), 4550 FSUI_CSTR("Enables the replacement of background textures in supported games."), 4551 "TextureReplacements", "EnableVRAMWriteReplacements", false); 4552 DrawToggleSetting(bsi, FSUI_CSTR("Preload Replacement Textures"), 4553 FSUI_CSTR("Loads all replacement texture to RAM, reducing stuttering at runtime."), 4554 "TextureReplacements", "PreloadTextures", false); 4555 DrawToggleSetting(bsi, FSUI_CSTR("Use Old MDEC Routines"), 4556 FSUI_CSTR("Enables the older, less accurate MDEC decoding routines. May be required for old " 4557 "replacement backgrounds to match/load."), 4558 "Hacks", "UseOldMDECRoutines", false); 4559 4560 DrawToggleSetting(bsi, FSUI_CSTR("Dump Replaceable VRAM Writes"), 4561 FSUI_CSTR("Writes textures which can be replaced to the dump directory."), "TextureReplacements", 4562 "DumpVRAMWrites", false); 4563 DrawToggleSetting(bsi, FSUI_CSTR("Set VRAM Write Dump Alpha Channel"), 4564 FSUI_CSTR("Clears the mask/transparency bit in VRAM write dumps."), "TextureReplacements", 4565 "DumpVRAMWriteForceAlphaChannel", true); 4566 4567 EndMenuButtons(); 4568 } 4569 4570 void FullscreenUI::PopulatePostProcessingChain(SettingsInterface* si, const char* section) 4571 { 4572 const u32 stages = PostProcessing::Config::GetStageCount(*si, section); 4573 s_postprocessing_stages.clear(); 4574 s_postprocessing_stages.reserve(stages); 4575 for (u32 i = 0; i < stages; i++) 4576 { 4577 PostProcessingStageInfo psi; 4578 psi.name = PostProcessing::Config::GetStageShaderName(*si, section, i); 4579 psi.options = PostProcessing::Config::GetStageOptions(*si, section, i); 4580 s_postprocessing_stages.push_back(std::move(psi)); 4581 } 4582 } 4583 4584 enum 4585 { 4586 POSTPROCESSING_ACTION_NONE = 0, 4587 POSTPROCESSING_ACTION_REMOVE, 4588 POSTPROCESSING_ACTION_MOVE_UP, 4589 POSTPROCESSING_ACTION_MOVE_DOWN, 4590 }; 4591 4592 void FullscreenUI::DrawPostProcessingSettingsPage() 4593 { 4594 SettingsInterface* bsi = GetEditingSettingsInterface(); 4595 static constexpr const char* section = PostProcessing::Config::DISPLAY_CHAIN_SECTION; 4596 4597 BeginMenuButtons(); 4598 4599 MenuHeading(FSUI_CSTR("Controls")); 4600 4601 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MAGIC, "Enable Post Processing"), 4602 FSUI_CSTR("If not enabled, the current post processing chain will be ignored."), "PostProcessing", 4603 "Enabled", false); 4604 4605 if (MenuButton(FSUI_ICONSTR(ICON_FA_SEARCH, "Reload Shaders"), 4606 FSUI_CSTR("Reloads the shaders from disk, applying any changes."), 4607 bsi->GetBoolValue("PostProcessing", "Enabled", false))) 4608 { 4609 if (System::IsValid() && PostProcessing::ReloadShaders()) 4610 ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded.")); 4611 } 4612 4613 MenuHeading(FSUI_CSTR("Operations")); 4614 4615 if (MenuButton(FSUI_ICONSTR(ICON_FA_PLUS, "Add Shader"), FSUI_CSTR("Adds a new shader to the chain."))) 4616 { 4617 std::vector<std::pair<std::string, std::string>> shaders = PostProcessing::GetAvailableShaderNames(); 4618 ImGuiFullscreen::ChoiceDialogOptions options; 4619 options.reserve(shaders.size()); 4620 for (auto& [display_name, name] : shaders) 4621 options.emplace_back(std::move(display_name), false); 4622 4623 OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_PLUS, "Add Shader"), false, std::move(options), 4624 [shaders = std::move(shaders)](s32 index, const std::string& title, bool checked) { 4625 if (index < 0 || static_cast<u32>(index) >= shaders.size()) 4626 return; 4627 4628 const std::string& shader_name = shaders[index].second; 4629 SettingsInterface* bsi = GetEditingSettingsInterface(); 4630 Error error; 4631 if (PostProcessing::Config::AddStage(*bsi, section, shader_name, &error)) 4632 { 4633 ShowToast(std::string(), fmt::format(FSUI_FSTR("Shader {} added as stage {}."), title, 4634 PostProcessing::Config::GetStageCount(*bsi, section))); 4635 PopulatePostProcessingChain(bsi, section); 4636 SetSettingsChanged(bsi); 4637 } 4638 else 4639 { 4640 ShowToast(std::string(), 4641 fmt::format(FSUI_FSTR("Failed to load shader {}. It may be invalid.\nError was:"), 4642 title, error.GetDescription())); 4643 } 4644 4645 CloseChoiceDialog(); 4646 }); 4647 } 4648 4649 if (MenuButton(FSUI_ICONSTR(ICON_FA_TIMES, "Clear Shaders"), FSUI_CSTR("Clears a shader from the chain."))) 4650 { 4651 OpenConfirmMessageDialog( 4652 FSUI_ICONSTR(ICON_FA_TIMES, "Clear Shaders"), 4653 FSUI_CSTR("Are you sure you want to clear the current post-processing chain? All configuration will be lost."), 4654 [](bool confirmed) { 4655 if (!confirmed) 4656 return; 4657 4658 SettingsInterface* bsi = GetEditingSettingsInterface(); 4659 PostProcessing::Config::ClearStages(*bsi, section); 4660 PopulatePostProcessingChain(bsi, section); 4661 SetSettingsChanged(bsi); 4662 ShowToast(std::string(), FSUI_STR("Post-processing chain cleared.")); 4663 }); 4664 } 4665 4666 u32 postprocessing_action = POSTPROCESSING_ACTION_NONE; 4667 u32 postprocessing_action_index = 0; 4668 4669 SmallString str; 4670 SmallString tstr; 4671 for (u32 stage_index = 0; stage_index < static_cast<u32>(s_postprocessing_stages.size()); stage_index++) 4672 { 4673 PostProcessingStageInfo& si = s_postprocessing_stages[stage_index]; 4674 4675 ImGui::PushID(stage_index); 4676 str.format(FSUI_FSTR("Stage {}: {}"), stage_index + 1, si.name); 4677 MenuHeading(str); 4678 4679 if (MenuButton(FSUI_ICONSTR(ICON_FA_TIMES, "Remove From Chain"), FSUI_CSTR("Removes this shader from the chain."))) 4680 { 4681 postprocessing_action = POSTPROCESSING_ACTION_REMOVE; 4682 postprocessing_action_index = stage_index; 4683 } 4684 4685 if (MenuButton(FSUI_ICONSTR(ICON_FA_ARROW_UP, "Move Up"), 4686 FSUI_CSTR("Moves this shader higher in the chain, applying it earlier."), (stage_index > 0))) 4687 { 4688 postprocessing_action = POSTPROCESSING_ACTION_MOVE_UP; 4689 postprocessing_action_index = stage_index; 4690 } 4691 4692 if (MenuButton(FSUI_ICONSTR(ICON_FA_ARROW_DOWN, "Move Down"), 4693 FSUI_CSTR("Moves this shader lower in the chain, applying it later."), 4694 (stage_index != (s_postprocessing_stages.size() - 1)))) 4695 { 4696 postprocessing_action = POSTPROCESSING_ACTION_MOVE_DOWN; 4697 postprocessing_action_index = stage_index; 4698 } 4699 4700 for (PostProcessing::ShaderOption& opt : si.options) 4701 { 4702 if (opt.ui_name.empty()) 4703 continue; 4704 4705 switch (opt.type) 4706 { 4707 case PostProcessing::ShaderOption::Type::Bool: 4708 { 4709 bool value = (opt.value[0].int_value != 0); 4710 tstr.format(ICON_FA_COGS "{}", opt.ui_name); 4711 if (ToggleButton(tstr, 4712 (opt.default_value[0].int_value != 0) ? FSUI_CSTR("Default: Enabled") : 4713 FSUI_CSTR("Default: Disabled"), 4714 &value)) 4715 { 4716 opt.value[0].int_value = (value != 0); 4717 PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); 4718 SetSettingsChanged(bsi); 4719 } 4720 } 4721 break; 4722 4723 case PostProcessing::ShaderOption::Type::Float: 4724 { 4725 tstr.format(ICON_FA_RULER_VERTICAL "{}##{}", opt.ui_name, opt.name); 4726 str.format(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].float_value, 4727 opt.default_value[0].float_value, opt.min_value[0].float_value, opt.max_value[0].float_value); 4728 if (MenuButton(tstr, str)) 4729 ImGui::OpenPopup(tstr); 4730 4731 ImGui::SetNextWindowSize(LayoutScale(500.0f, 190.0f)); 4732 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 4733 4734 ImGui::PushFont(g_large_font); 4735 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 4736 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 4737 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 4738 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 4739 4740 bool is_open = true; 4741 if (ImGui::BeginPopupModal(tstr, &is_open, 4742 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 4743 { 4744 BeginMenuButtons(); 4745 4746 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 4747 4748 #if 0 4749 for (u32 i = 0; i < opt.vector_size; i++) 4750 { 4751 static constexpr const char* components[] = { "X", "Y", "Z", "W" }; 4752 if (opt.vector_size == 1) 4753 tstr.Assign("##value"); 4754 else 4755 tstr.Fmt("{}##value{}", components[i], i); 4756 4757 ImGui::SetNextItemWidth(end); 4758 if (ImGui::SliderFloat(tstr, &opt.value[i].float_value, opt.min_value[i].float_value, 4759 opt.max_value[i].float_value, "%f", ImGuiSliderFlags_NoInput)) 4760 { 4761 SavePostProcessingChain(); 4762 } 4763 } 4764 #else 4765 ImGui::SetNextItemWidth(end); 4766 4767 bool changed = false; 4768 switch (opt.vector_size) 4769 { 4770 case 1: 4771 { 4772 changed = ImGui::SliderFloat("##value", &opt.value[0].float_value, opt.min_value[0].float_value, 4773 opt.max_value[0].float_value); 4774 } 4775 break; 4776 4777 case 2: 4778 { 4779 changed = ImGui::SliderFloat2("##value", &opt.value[0].float_value, opt.min_value[0].float_value, 4780 opt.max_value[0].float_value); 4781 } 4782 break; 4783 4784 case 3: 4785 { 4786 changed = ImGui::SliderFloat3("##value", &opt.value[0].float_value, opt.min_value[0].float_value, 4787 opt.max_value[0].float_value); 4788 } 4789 break; 4790 4791 case 4: 4792 { 4793 changed = ImGui::SliderFloat4("##value", &opt.value[0].float_value, opt.min_value[0].float_value, 4794 opt.max_value[0].float_value); 4795 } 4796 break; 4797 } 4798 4799 if (changed) 4800 { 4801 PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); 4802 SetSettingsChanged(bsi); 4803 } 4804 #endif 4805 4806 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 4807 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 4808 ImVec2(0.5f, 0.0f))) 4809 { 4810 ImGui::CloseCurrentPopup(); 4811 } 4812 EndMenuButtons(); 4813 4814 ImGui::EndPopup(); 4815 } 4816 4817 ImGui::PopStyleVar(3); 4818 ImGui::PopFont(); 4819 } 4820 break; 4821 4822 case PostProcessing::ShaderOption::Type::Int: 4823 { 4824 tstr.format(ICON_FA_RULER_VERTICAL "{}##{}", opt.ui_name, opt.name); 4825 str.format(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].int_value, 4826 opt.default_value[0].int_value, opt.min_value[0].int_value, opt.max_value[0].int_value); 4827 if (MenuButton(tstr, str)) 4828 ImGui::OpenPopup(tstr); 4829 4830 ImGui::SetNextWindowSize(LayoutScale(500.0f, 190.0f)); 4831 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 4832 4833 ImGui::PushFont(g_large_font); 4834 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 4835 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, 4836 ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); 4837 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 4838 4839 bool is_open = true; 4840 if (ImGui::BeginPopupModal(tstr, &is_open, 4841 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 4842 { 4843 BeginMenuButtons(); 4844 4845 const float end = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 4846 4847 #if 0 4848 for (u32 i = 0; i < opt.vector_size; i++) 4849 { 4850 static constexpr const char* components[] = { "X", "Y", "Z", "W" }; 4851 if (opt.vector_size == 1) 4852 tstr.Assign("##value"); 4853 else 4854 tstr.Fmt("{}##value{}", components[i], i); 4855 4856 ImGui::SetNextItemWidth(end); 4857 if (ImGui::SliderInt(tstr, &opt.value[i].int_value, opt.min_value[i].int_value, 4858 opt.max_value[i].int_value, "%d", ImGuiSliderFlags_NoInput)) 4859 { 4860 SavePostProcessingChain(); 4861 } 4862 } 4863 #else 4864 bool changed = false; 4865 ImGui::SetNextItemWidth(end); 4866 switch (opt.vector_size) 4867 { 4868 case 1: 4869 { 4870 changed = ImGui::SliderInt("##value", &opt.value[0].int_value, opt.min_value[0].int_value, 4871 opt.max_value[0].int_value); 4872 } 4873 break; 4874 4875 case 2: 4876 { 4877 changed = ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value, 4878 opt.max_value[0].int_value); 4879 } 4880 break; 4881 4882 case 3: 4883 { 4884 changed = ImGui::SliderInt3("##value", &opt.value[0].int_value, opt.min_value[0].int_value, 4885 opt.max_value[0].int_value); 4886 } 4887 break; 4888 4889 case 4: 4890 { 4891 changed = ImGui::SliderInt4("##value", &opt.value[0].int_value, opt.min_value[0].int_value, 4892 opt.max_value[0].int_value); 4893 } 4894 break; 4895 } 4896 4897 if (changed) 4898 { 4899 PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); 4900 SetSettingsChanged(bsi); 4901 } 4902 #endif 4903 4904 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); 4905 if (MenuButtonWithoutSummary(FSUI_CSTR("OK"), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font, 4906 ImVec2(0.5f, 0.0f))) 4907 { 4908 ImGui::CloseCurrentPopup(); 4909 } 4910 EndMenuButtons(); 4911 4912 ImGui::EndPopup(); 4913 } 4914 4915 ImGui::PopStyleVar(3); 4916 ImGui::PopFont(); 4917 } 4918 break; 4919 4920 default: 4921 break; 4922 } 4923 } 4924 4925 ImGui::PopID(); 4926 } 4927 4928 switch (postprocessing_action) 4929 { 4930 case POSTPROCESSING_ACTION_REMOVE: 4931 { 4932 const PostProcessingStageInfo& si = s_postprocessing_stages[postprocessing_action_index]; 4933 ShowToast(std::string(), 4934 fmt::format(FSUI_FSTR("Removed stage {} ({})."), postprocessing_action_index + 1, si.name)); 4935 PostProcessing::Config::RemoveStage(*bsi, section, postprocessing_action_index); 4936 PopulatePostProcessingChain(bsi, section); 4937 SetSettingsChanged(bsi); 4938 } 4939 break; 4940 case POSTPROCESSING_ACTION_MOVE_UP: 4941 { 4942 PostProcessing::Config::MoveStageUp(*bsi, section, postprocessing_action_index); 4943 PopulatePostProcessingChain(bsi, section); 4944 SetSettingsChanged(bsi); 4945 } 4946 break; 4947 case POSTPROCESSING_ACTION_MOVE_DOWN: 4948 { 4949 PostProcessing::Config::MoveStageDown(*bsi, section, postprocessing_action_index); 4950 PopulatePostProcessingChain(bsi, section); 4951 SetSettingsChanged(bsi); 4952 } 4953 break; 4954 default: 4955 break; 4956 } 4957 4958 EndMenuButtons(); 4959 } 4960 4961 void FullscreenUI::DrawAudioSettingsPage() 4962 { 4963 SettingsInterface* bsi = GetEditingSettingsInterface(); 4964 4965 BeginMenuButtons(); 4966 4967 MenuHeading(FSUI_CSTR("Audio Control")); 4968 4969 DrawIntRangeSetting(bsi, FSUI_CSTR("Output Volume"), 4970 FSUI_CSTR("Controls the volume of the audio played on the host."), "Audio", "OutputVolume", 100, 4971 0, 200, "%d%%"); 4972 DrawIntRangeSetting(bsi, FSUI_CSTR("Fast Forward Volume"), 4973 FSUI_CSTR("Controls the volume of the audio played on the host when fast forwarding."), "Audio", 4974 "FastForwardVolume", 200, 0, 100, "%d%%"); 4975 DrawToggleSetting(bsi, FSUI_CSTR("Mute All Sound"), 4976 FSUI_CSTR("Prevents the emulator from producing any audible sound."), "Audio", "OutputMuted", 4977 false); 4978 DrawToggleSetting(bsi, FSUI_CSTR("Mute CD Audio"), 4979 FSUI_CSTR("Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to " 4980 "disable background music in some games."), 4981 "CDROM", "MuteCDAudio", false); 4982 4983 MenuHeading(FSUI_CSTR("Backend Settings")); 4984 4985 DrawEnumSetting( 4986 bsi, FSUI_CSTR("Audio Backend"), 4987 FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "Audio", 4988 "Backend", AudioStream::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName, 4989 &AudioStream::GetBackendDisplayName, AudioBackend::Count); 4990 DrawEnumSetting(bsi, FSUI_CSTR("Expansion Mode"), 4991 FSUI_CSTR("Determines how audio is expanded from stereo to surround for supported games."), "Audio", 4992 "ExpansionMode", AudioStreamParameters::DEFAULT_EXPANSION_MODE, &AudioStream::ParseExpansionMode, 4993 &AudioStream::GetExpansionModeName, &AudioStream::GetExpansionModeDisplayName, 4994 AudioExpansionMode::Count); 4995 DrawEnumSetting(bsi, FSUI_CSTR("Stretch Mode"), 4996 FSUI_CSTR("Determines quality of audio when not running at 100% speed."), "Audio", "StretchMode", 4997 AudioStreamParameters::DEFAULT_STRETCH_MODE, &AudioStream::ParseStretchMode, 4998 &AudioStream::GetStretchModeName, &AudioStream::GetStretchModeDisplayName, AudioStretchMode::Count); 4999 DrawIntRangeSetting(bsi, FSUI_CSTR("Buffer Size"), 5000 FSUI_CSTR("Determines the amount of audio buffered before being pulled by the host API."), 5001 "Audio", "BufferMS", AudioStreamParameters::DEFAULT_BUFFER_MS, 10, 500, FSUI_CSTR("%d ms")); 5002 if (!GetEffectiveBoolSetting(bsi, "Audio", "OutputLatencyMinimal", 5003 AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL)) 5004 { 5005 DrawIntRangeSetting( 5006 bsi, FSUI_CSTR("Output Latency"), 5007 FSUI_CSTR("Determines how much latency there is between the audio being picked up by the host API, and " 5008 "played through speakers."), 5009 "Audio", "OutputLatencyMS", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MS, 1, 500, FSUI_CSTR("%d ms")); 5010 } 5011 DrawToggleSetting(bsi, FSUI_CSTR("Minimal Output Latency"), 5012 FSUI_CSTR("When enabled, the minimum supported output latency will be used for the host API."), 5013 "Audio", "OutputLatencyMinimal", AudioStreamParameters::DEFAULT_OUTPUT_LATENCY_MINIMAL); 5014 5015 EndMenuButtons(); 5016 } 5017 5018 void FullscreenUI::DrawAchievementsSettingsPage() 5019 { 5020 #ifdef ENABLE_RAINTEGRATION 5021 if (Achievements::IsUsingRAIntegration()) 5022 { 5023 BeginMenuButtons(); 5024 ActiveButton( 5025 FSUI_ICONSTR(ICON_FA_BAN, 5026 FSUI_CSTR("RAIntegration is being used instead of the built-in achievements implementation.")), 5027 false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5028 EndMenuButtons(); 5029 return; 5030 } 5031 #endif 5032 5033 SettingsInterface* bsi = GetEditingSettingsInterface(); 5034 5035 BeginMenuButtons(); 5036 5037 MenuHeading(FSUI_CSTR("Settings")); 5038 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TROPHY, "Enable Achievements"), 5039 FSUI_CSTR("When enabled and logged in, DuckStation will scan for achievements on startup."), 5040 "Cheevos", "Enabled", false); 5041 5042 const bool enabled = bsi->GetBoolValue("Cheevos", "Enabled", false); 5043 5044 if (DrawToggleSetting( 5045 bsi, FSUI_ICONSTR(ICON_FA_HARD_HAT, "Hardcore Mode"), 5046 FSUI_CSTR("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, " 5047 "cheats, and slowdown functions."), 5048 "Cheevos", "ChallengeMode", false, enabled)) 5049 { 5050 if (System::IsValid() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false)) 5051 ShowToast(std::string(), FSUI_STR("Hardcore mode will be enabled on next game restart.")); 5052 } 5053 DrawToggleSetting( 5054 bsi, FSUI_ICONSTR(ICON_FA_INBOX, "Achievement Notifications"), 5055 FSUI_CSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Cheevos", 5056 "Notifications", true, enabled); 5057 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"), 5058 FSUI_CSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."), 5059 "Cheevos", "LeaderboardNotifications", true, enabled); 5060 DrawToggleSetting( 5061 bsi, FSUI_ICONSTR(ICON_FA_HEADPHONES, "Sound Effects"), 5062 FSUI_CSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Cheevos", 5063 "SoundEffects", true, enabled); 5064 DrawToggleSetting( 5065 bsi, FSUI_ICONSTR(ICON_FA_MAGIC, "Enable In-Game Overlays"), 5066 FSUI_CSTR("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."), 5067 "Cheevos", "Overlays", true, enabled); 5068 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_USER_FRIENDS, "Encore Mode"), 5069 FSUI_CSTR("When enabled, each session will behave as if no achievements have been unlocked."), 5070 "Cheevos", "EncoreMode", false, enabled); 5071 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_STETHOSCOPE, "Spectator Mode"), 5072 FSUI_CSTR("When enabled, DuckStation will assume all achievements are locked and not send any " 5073 "unlock notifications to the server."), 5074 "Cheevos", "SpectatorMode", false, enabled); 5075 DrawToggleSetting( 5076 bsi, FSUI_ICONSTR(ICON_FA_MEDAL, "Test Unofficial Achievements"), 5077 FSUI_CSTR("When enabled, DuckStation will list achievements from unofficial sets. These achievements are not " 5078 "tracked by RetroAchievements."), 5079 "Cheevos", "UnofficialTestMode", false, enabled); 5080 5081 if (!IsEditingGameSettings(bsi)) 5082 { 5083 MenuHeading(FSUI_CSTR("Account")); 5084 if (bsi->ContainsValue("Cheevos", "Token")) 5085 { 5086 ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]); 5087 ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), 5088 bsi->GetTinyStringValue("Cheevos", "Username")), 5089 false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5090 5091 TinyString ts_string; 5092 ts_string.format( 5093 FSUI_FSTR("{:%Y-%m-%d %H:%M:%S}"), 5094 fmt::localtime( 5095 StringUtil::FromChars<u64>(bsi->GetTinyStringValue("Cheevos", "LoginTimestamp", "0")).value_or(0))); 5096 ActiveButton( 5097 SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_CLOCK, "Login token generated on {}")), ts_string), 5098 false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5099 ImGui::PopStyleColor(); 5100 5101 if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Logout"), FSUI_CSTR("Logs out of RetroAchievements."))) 5102 { 5103 Host::RunOnCPUThread([]() { Achievements::Logout(); }); 5104 } 5105 } 5106 else 5107 { 5108 ActiveButton(FSUI_ICONSTR(ICON_FA_USER, "Not Logged In"), false, false, 5109 ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5110 5111 if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Login"), FSUI_CSTR("Logs in to RetroAchievements."))) 5112 Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason::UserInitiated); 5113 } 5114 5115 MenuHeading(FSUI_CSTR("Current Game")); 5116 if (Achievements::HasActiveGame()) 5117 { 5118 const auto lock = Achievements::GetLock(); 5119 5120 ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]); 5121 ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_BOOKMARK, "Game: {} ({})")), 5122 Achievements::GetGameID(), Achievements::GetGameTitle()), 5123 false, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5124 5125 const std::string& rich_presence_string = Achievements::GetRichPresenceString(); 5126 if (!rich_presence_string.empty()) 5127 { 5128 ActiveButton(SmallString::from_format(ICON_FA_MAP "{}", rich_presence_string), false, false, 5129 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5130 } 5131 else 5132 { 5133 ActiveButton(FSUI_ICONSTR(ICON_FA_MAP, "Rich presence inactive or unsupported."), false, false, 5134 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5135 } 5136 5137 ImGui::PopStyleColor(); 5138 } 5139 else 5140 { 5141 ActiveButton(FSUI_ICONSTR(ICON_FA_BAN, "Game not loaded or no RetroAchievements available."), false, false, 5142 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5143 } 5144 } 5145 5146 EndMenuButtons(); 5147 } 5148 5149 void FullscreenUI::DrawAdvancedSettingsPage() 5150 { 5151 SettingsInterface* bsi = GetEditingSettingsInterface(); 5152 5153 BeginMenuButtons(); 5154 5155 MenuHeading(FSUI_CSTR("Logging Settings")); 5156 DrawEnumSetting(bsi, FSUI_CSTR("Log Level"), 5157 FSUI_CSTR("Sets the verbosity of messages logged. Higher levels will log more messages."), "Logging", 5158 "LogLevel", Settings::DEFAULT_LOG_LEVEL, &Settings::ParseLogLevelName, &Settings::GetLogLevelName, 5159 &Settings::GetLogLevelDisplayName, LOGLEVEL_COUNT); 5160 DrawToggleSetting(bsi, FSUI_CSTR("Log To System Console"), FSUI_CSTR("Logs messages to the console window."), 5161 FSUI_CSTR("Logging"), "LogToConsole", Settings::DEFAULT_LOG_TO_CONSOLE); 5162 DrawToggleSetting(bsi, FSUI_CSTR("Log To Debug Console"), 5163 FSUI_CSTR("Logs messages to the debug console where supported."), "Logging", "LogToDebug", false); 5164 DrawToggleSetting(bsi, FSUI_CSTR("Log To File"), FSUI_CSTR("Logs messages to duckstation.log in the user directory."), 5165 "Logging", "LogToFile", false); 5166 5167 MenuHeading(FSUI_CSTR("Debugging Settings")); 5168 5169 DrawToggleSetting(bsi, FSUI_CSTR("Use Debug GPU Device"), 5170 FSUI_CSTR("Enable debugging when supported by the host's renderer API. Only for developer use."), 5171 "GPU", "UseDebugDevice", false); 5172 5173 #ifdef _WIN32 5174 DrawToggleSetting(bsi, FSUI_CSTR("Increase Timer Resolution"), 5175 FSUI_CSTR("Enables more precise frame pacing at the cost of battery life."), "Main", 5176 "IncreaseTimerResolution", true); 5177 #endif 5178 5179 DrawToggleSetting(bsi, FSUI_CSTR("Allow Booting Without SBI File"), 5180 FSUI_CSTR("Allows loading protected games without subchannel information."), "CDROM", 5181 "AllowBootingWithoutSBIFile", false); 5182 5183 DrawToggleSetting(bsi, FSUI_CSTR("Create Save State Backups"), 5184 FSUI_CSTR("Renames existing save states when saving to a backup file."), "Main", 5185 "CreateSaveStateBackups", false); 5186 DrawToggleSetting( 5187 bsi, FSUI_CSTR("Load Devices From Save States"), 5188 FSUI_CSTR("When enabled, memory cards and controllers will be overwritten when save states are loaded."), "Main", 5189 "LoadDevicesFromSaveStates", false); 5190 DrawEnumSetting(bsi, FSUI_CSTR("Save State Compression"), 5191 FSUI_CSTR("Reduces the size of save states by compressing the data before saving."), "Main", 5192 "SaveStateCompression", Settings::DEFAULT_SAVE_STATE_COMPRESSION_MODE, 5193 &Settings::ParseSaveStateCompressionModeName, &Settings::GetSaveStateCompressionModeName, 5194 &Settings::GetSaveStateCompressionModeDisplayName, SaveStateCompressionMode::Count); 5195 5196 MenuHeading(FSUI_CSTR("Display Settings")); 5197 DrawToggleSetting(bsi, FSUI_CSTR("Show Status Indicators"), 5198 FSUI_CSTR("Shows persistent icons when turbo is active or when paused."), "Display", 5199 "ShowStatusIndicators", true); 5200 DrawToggleSetting(bsi, FSUI_CSTR("Show Enhancement Settings"), 5201 FSUI_CSTR("Shows enhancement settings in the bottom-right corner of the screen."), "Display", 5202 "ShowEnhancements", false); 5203 DrawEnumSetting(bsi, FSUI_CSTR("Wireframe Rendering"), 5204 FSUI_CSTR("Overlays or replaces normal triangle drawing with a wireframe/line view."), "GPU", 5205 "WireframeMode", GPUWireframeMode::Disabled, &Settings::ParseGPUWireframeMode, 5206 &Settings::GetGPUWireframeModeName, &Settings::GetGPUWireframeModeDisplayName, 5207 GPUWireframeMode::Count); 5208 5209 MenuHeading(FSUI_CSTR("CPU Emulation")); 5210 5211 DrawToggleSetting( 5212 bsi, FSUI_CSTR("Enable Recompiler ICache"), 5213 FSUI_CSTR("Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast."), "CPU", 5214 "RecompilerICache", false); 5215 DrawToggleSetting(bsi, FSUI_CSTR("Enable Recompiler Memory Exceptions"), 5216 FSUI_CSTR("Enables alignment and bus exceptions. Not needed for any known games."), "CPU", 5217 "RecompilerMemoryExceptions", false); 5218 DrawToggleSetting( 5219 bsi, FSUI_CSTR("Enable Recompiler Block Linking"), 5220 FSUI_CSTR("Performance enhancement - jumps directly between blocks instead of returning to the dispatcher."), "CPU", 5221 "RecompilerBlockLinking", true); 5222 DrawEnumSetting(bsi, FSUI_CSTR("Recompiler Fast Memory Access"), 5223 FSUI_CSTR("Avoids calls to C++ code, significantly speeding up the recompiler."), "CPU", 5224 "FastmemMode", Settings::DEFAULT_CPU_FASTMEM_MODE, &Settings::ParseCPUFastmemMode, 5225 &Settings::GetCPUFastmemModeName, &Settings::GetCPUFastmemModeDisplayName, CPUFastmemMode::Count); 5226 5227 MenuHeading(FSUI_CSTR("CD-ROM Emulation")); 5228 5229 DrawToggleSetting(bsi, FSUI_CSTR("Enable Region Check"), 5230 FSUI_CSTR("Simulates the region check present in original, unmodified consoles."), "CDROM", 5231 "RegionCheck", false); 5232 5233 EndMenuButtons(); 5234 } 5235 5236 void FullscreenUI::DrawPauseMenu() 5237 { 5238 SmallString buffer; 5239 5240 ImDrawList* dl = ImGui::GetBackgroundDrawList(); 5241 const ImVec2 display_size(ImGui::GetIO().DisplaySize); 5242 const ImU32 text_color = ImGui::GetColorU32(UIBackgroundTextColor) | IM_COL32_A_MASK; 5243 dl->AddRectFilled(ImVec2(0.0f, 0.0f), display_size, 5244 (ImGui::GetColorU32(UIBackgroundColor) & ~IM_COL32_A_MASK) | (200 << IM_COL32_A_SHIFT)); 5245 5246 // title info 5247 { 5248 const std::string& title = System::GetGameTitle(); 5249 const std::string& serial = System::GetGameSerial(); 5250 5251 if (!serial.empty()) 5252 buffer.format("{} - ", serial); 5253 buffer.append(Path::GetFileName(System::GetDiscPath())); 5254 5255 const float image_width = 60.0f; 5256 const float image_height = 60.0f; 5257 5258 const ImVec2 title_size( 5259 g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, title.c_str())); 5260 const ImVec2 subtitle_size( 5261 g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), -1.0f, buffer.c_str())); 5262 5263 ImVec2 title_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - title_size.x, 5264 display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height)); 5265 ImVec2 subtitle_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - subtitle_size.x, 5266 title_pos.y + g_large_font->FontSize + LayoutScale(4.0f)); 5267 5268 float rp_height = 0.0f; 5269 { 5270 const auto lock = Achievements::GetLock(); 5271 const std::string& rp = Achievements::IsActive() ? Achievements::GetRichPresenceString() : std::string(); 5272 5273 if (!rp.empty()) 5274 { 5275 const float wrap_width = LayoutScale(350.0f); 5276 const ImVec2 rp_size = g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), 5277 wrap_width, rp.data(), rp.data() + rp.length()); 5278 5279 // Add a small extra gap if any Rich Presence is displayed 5280 rp_height = rp_size.y - g_medium_font->FontSize + LayoutScale(2.0f); 5281 5282 const ImVec2 rp_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - rp_size.x, 5283 subtitle_pos.y + g_medium_font->FontSize + LayoutScale(4.0f) - rp_height); 5284 5285 title_pos.y -= rp_height; 5286 subtitle_pos.y -= rp_height; 5287 5288 DrawShadowedText(dl, g_medium_font, rp_pos, text_color, rp.data(), rp.data() + rp.length(), wrap_width); 5289 } 5290 } 5291 5292 DrawShadowedText(dl, g_large_font, title_pos, text_color, title.c_str()); 5293 DrawShadowedText(dl, g_medium_font, subtitle_pos, text_color, buffer.c_str()); 5294 5295 GPUTexture* const cover = GetCoverForCurrentGame(); 5296 const ImVec2 image_min(display_size.x - LayoutScale(10.0f + image_width), 5297 display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height) - 5298 rp_height); 5299 const ImVec2 image_max(image_min.x + LayoutScale(image_width), image_min.y + LayoutScale(image_height) + rp_height); 5300 const ImRect image_rect(CenterImage(ImRect(image_min, image_max), ImVec2(static_cast<float>(cover->GetWidth()), 5301 static_cast<float>(cover->GetHeight())))); 5302 dl->AddImage(cover, image_rect.Min, image_rect.Max); 5303 } 5304 5305 // current time / play time 5306 { 5307 buffer.format("{:%X}", fmt::localtime(std::time(nullptr))); 5308 5309 const ImVec2 time_size(g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, 5310 buffer.c_str(), buffer.end_ptr())); 5311 const ImVec2 time_pos(display_size.x - LayoutScale(10.0f) - time_size.x, LayoutScale(10.0f)); 5312 DrawShadowedText(dl, g_large_font, time_pos, text_color, buffer.c_str(), buffer.end_ptr()); 5313 5314 const std::string& serial = System::GetGameSerial(); 5315 if (!serial.empty()) 5316 { 5317 const std::time_t cached_played_time = GameList::GetCachedPlayedTimeForSerial(serial); 5318 const std::time_t session_time = static_cast<std::time_t>(System::GetSessionPlayedTime()); 5319 5320 buffer.format(FSUI_FSTR("Session: {}"), GameList::FormatTimespan(session_time, true)); 5321 const ImVec2 session_size(g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), 5322 -1.0f, buffer.c_str(), buffer.end_ptr())); 5323 const ImVec2 session_pos(display_size.x - LayoutScale(10.0f) - session_size.x, 5324 time_pos.y + g_large_font->FontSize + LayoutScale(4.0f)); 5325 DrawShadowedText(dl, g_medium_font, session_pos, text_color, buffer.c_str(), buffer.end_ptr()); 5326 5327 buffer.format(FSUI_FSTR("All Time: {}"), GameList::FormatTimespan(cached_played_time + session_time, true)); 5328 const ImVec2 total_size(g_medium_font->CalcTextSizeA(g_medium_font->FontSize, std::numeric_limits<float>::max(), 5329 -1.0f, buffer.c_str(), buffer.end_ptr())); 5330 const ImVec2 total_pos(display_size.x - LayoutScale(10.0f) - total_size.x, 5331 session_pos.y + g_medium_font->FontSize + LayoutScale(4.0f)); 5332 DrawShadowedText(dl, g_medium_font, total_pos, text_color, buffer.c_str(), buffer.end_ptr()); 5333 } 5334 } 5335 5336 const ImVec2 window_size(LayoutScale(500.0f, LAYOUT_SCREEN_HEIGHT)); 5337 const ImVec2 window_pos(0.0f, display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - window_size.y); 5338 5339 if (BeginFullscreenWindow(window_pos, window_size, "pause_menu", ImVec4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f, 5340 ImVec2(10.0f, 10.0f), ImGuiWindowFlags_NoBackground)) 5341 { 5342 static constexpr u32 submenu_item_count[] = { 5343 12, // None 5344 4, // Exit 5345 3, // Achievements 5346 }; 5347 5348 ResetFocusHere(); 5349 BeginMenuButtons(submenu_item_count[static_cast<u32>(s_current_pause_submenu)], 1.0f, 5350 ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING, 5351 ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 5352 5353 switch (s_current_pause_submenu) 5354 { 5355 case PauseSubMenu::None: 5356 { 5357 // NOTE: Menu close must come first, because otherwise VM destruction options will race. 5358 const bool has_game = System::IsValid() && !System::GetGameSerial().empty(); 5359 5360 if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false) || WantsToCloseMenu()) 5361 ClosePauseMenu(); 5362 5363 if (ActiveButton(FSUI_ICONSTR(ICON_FA_FAST_FORWARD, "Toggle Fast Forward"), false)) 5364 { 5365 ClosePauseMenu(); 5366 DoToggleFastForward(); 5367 } 5368 5369 if (ActiveButton(FSUI_ICONSTR(ICON_FA_UNDO, "Load State"), false, has_game)) 5370 { 5371 if (OpenSaveStateSelector(true)) 5372 s_current_main_window = MainWindowType::None; 5373 } 5374 5375 if (ActiveButton(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Save State"), false, has_game)) 5376 { 5377 if (OpenSaveStateSelector(false)) 5378 s_current_main_window = MainWindowType::None; 5379 } 5380 5381 if (ActiveButton(FSUI_ICONSTR(ICON_FA_FROWN_OPEN, "Cheat List"), false, 5382 !System::GetGameSerial().empty() && g_settings.enable_cheats)) 5383 { 5384 s_current_main_window = MainWindowType::None; 5385 DoCheatsMenu(); 5386 } 5387 5388 if (ActiveButton(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Toggle Analog"), false)) 5389 { 5390 ClosePauseMenu(); 5391 DoToggleAnalogMode(); 5392 } 5393 5394 if (ActiveButton(FSUI_ICONSTR(ICON_FA_WRENCH, "Game Properties"), false, has_game)) 5395 { 5396 SwitchToGameSettings(); 5397 } 5398 5399 if (ActiveButton(FSUI_ICONSTR(ICON_FA_TROPHY, "Achievements"), false, 5400 Achievements::HasAchievementsOrLeaderboards())) 5401 { 5402 // skip second menu and go straight to cheevos if there's no lbs 5403 if (!Achievements::HasLeaderboards()) 5404 OpenAchievementsWindow(); 5405 else 5406 OpenPauseSubMenu(PauseSubMenu::Achievements); 5407 } 5408 5409 if (ActiveButton(FSUI_ICONSTR(ICON_FA_CAMERA, "Save Screenshot"), false)) 5410 { 5411 System::SaveScreenshot(); 5412 ClosePauseMenu(); 5413 } 5414 5415 if (ActiveButton(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Change Disc"), false)) 5416 { 5417 s_current_main_window = MainWindowType::None; 5418 DoChangeDisc(); 5419 } 5420 5421 if (ActiveButton(FSUI_ICONSTR(ICON_FA_SLIDERS_H, "Settings"), false)) 5422 SwitchToSettings(); 5423 5424 if (ActiveButton(FSUI_ICONSTR(ICON_FA_POWER_OFF, "Close Game"), false)) 5425 { 5426 // skip submenu when we can't save anyway 5427 if (!has_game) 5428 RequestShutdown(false); 5429 else 5430 OpenPauseSubMenu(PauseSubMenu::Exit); 5431 } 5432 } 5433 break; 5434 5435 case PauseSubMenu::Exit: 5436 { 5437 if (ActiveButton(FSUI_ICONSTR(ICON_FA_BACKWARD, "Back To Pause Menu"), false) || WantsToCloseMenu()) 5438 OpenPauseSubMenu(PauseSubMenu::None); 5439 5440 if (ActiveButton(FSUI_ICONSTR(ICON_FA_SYNC, "Reset System"), false)) 5441 { 5442 ClosePauseMenu(); 5443 RequestReset(); 5444 } 5445 5446 if (ActiveButton(FSUI_ICONSTR(ICON_FA_SAVE, "Exit And Save State"), false)) 5447 RequestShutdown(true); 5448 5449 if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_POWER_OFF, "Exit Without Saving"), false)) 5450 RequestShutdown(false); 5451 } 5452 break; 5453 5454 case PauseSubMenu::Achievements: 5455 { 5456 if (ActiveButton(FSUI_ICONSTR(ICON_FA_BACKWARD, "Back To Pause Menu"), false) || WantsToCloseMenu()) 5457 OpenPauseSubMenu(PauseSubMenu::None); 5458 5459 if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_TROPHY, "Achievements"), false)) 5460 OpenAchievementsWindow(); 5461 5462 if (ActiveButton(FSUI_ICONSTR(ICON_FA_STOPWATCH, "Leaderboards"), false)) 5463 OpenLeaderboardsWindow(); 5464 } 5465 break; 5466 } 5467 5468 EndMenuButtons(); 5469 5470 EndFullscreenWindow(); 5471 } 5472 5473 Achievements::DrawPauseMenuOverlays(); 5474 5475 if (IsGamepadInputSource()) 5476 { 5477 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), 5478 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Select")), 5479 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Return To Game"))}); 5480 } 5481 else 5482 { 5483 SetFullscreenFooterText(std::array{ 5484 std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, FSUI_VSTR("Change Selection")), 5485 std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Select")), std::make_pair(ICON_PF_ESC, FSUI_VSTR("Return To Game"))}); 5486 } 5487 } 5488 5489 void FullscreenUI::InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, const std::string& serial, s32 slot, 5490 bool global) 5491 { 5492 li->title = (global || slot > 0) ? fmt::format(global ? FSUI_FSTR("Global Slot {0}##global_slot_{0}") : 5493 FSUI_FSTR("Game Slot {0}##game_slot_{0}"), 5494 slot) : 5495 FSUI_STR("Quick Save"); 5496 li->summary = FSUI_STR("No save present in this slot."); 5497 li->path = {}; 5498 li->timestamp = 0; 5499 li->slot = slot; 5500 li->preview_texture = {}; 5501 li->global = global; 5502 } 5503 5504 bool FullscreenUI::InitializeSaveStateListEntryFromSerial(SaveStateListEntry* li, const std::string& serial, s32 slot, 5505 bool global) 5506 { 5507 const std::string path = 5508 (global ? System::GetGlobalSaveStateFileName(slot) : System::GetGameSaveStateFileName(serial, slot)); 5509 if (!InitializeSaveStateListEntryFromPath(li, path.c_str(), slot, global)) 5510 { 5511 InitializePlaceholderSaveStateListEntry(li, serial, slot, global); 5512 return false; 5513 } 5514 5515 return true; 5516 } 5517 5518 bool FullscreenUI::InitializeSaveStateListEntryFromPath(SaveStateListEntry* li, std::string path, s32 slot, bool global) 5519 { 5520 std::optional<ExtendedSaveStateInfo> ssi(System::GetExtendedSaveStateInfo(path.c_str())); 5521 if (!ssi.has_value()) 5522 return false; 5523 5524 if (global) 5525 { 5526 li->title = fmt::format(FSUI_FSTR("Global Slot {0} - {1}##global_slot_{0}"), slot, ssi->serial); 5527 } 5528 else 5529 { 5530 li->title = (slot > 0) ? fmt::format(FSUI_FSTR("Game Slot {0}##game_slot_{0}"), slot) : FSUI_STR("Game Quick Save"); 5531 } 5532 5533 li->summary = fmt::format(FSUI_FSTR("Saved {:%c}"), fmt::localtime(ssi->timestamp)); 5534 li->timestamp = ssi->timestamp; 5535 li->slot = slot; 5536 li->path = std::move(path); 5537 li->global = global; 5538 if (ssi->screenshot.IsValid()) 5539 li->preview_texture = CreateTextureFromImage(ssi->screenshot); 5540 5541 return true; 5542 } 5543 5544 void FullscreenUI::ClearSaveStateEntryList() 5545 { 5546 for (SaveStateListEntry& entry : s_save_state_selector_slots) 5547 { 5548 if (entry.preview_texture) 5549 g_gpu_device->RecycleTexture(std::move(entry.preview_texture)); 5550 } 5551 s_save_state_selector_slots.clear(); 5552 } 5553 5554 u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const std::string& serial) 5555 { 5556 ClearSaveStateEntryList(); 5557 5558 if (s_save_state_selector_loading) 5559 { 5560 std::optional<ExtendedSaveStateInfo> ssi = System::GetUndoSaveStateInfo(); 5561 if (ssi) 5562 { 5563 SaveStateListEntry li; 5564 li.title = FSUI_STR("Undo Load State"); 5565 li.summary = FSUI_STR("Restores the state of the system prior to the last state loaded."); 5566 if (ssi->screenshot.IsValid()) 5567 li.preview_texture = CreateTextureFromImage(ssi->screenshot); 5568 s_save_state_selector_slots.push_back(std::move(li)); 5569 } 5570 } 5571 5572 if (!serial.empty()) 5573 { 5574 for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++) 5575 { 5576 SaveStateListEntry li; 5577 if (InitializeSaveStateListEntryFromSerial(&li, serial, i, false) || !s_save_state_selector_loading) 5578 s_save_state_selector_slots.push_back(std::move(li)); 5579 } 5580 } 5581 5582 for (s32 i = 1; i <= System::GLOBAL_SAVE_STATE_SLOTS; i++) 5583 { 5584 SaveStateListEntry li; 5585 if (InitializeSaveStateListEntryFromSerial(&li, serial, i, true) || !s_save_state_selector_loading) 5586 s_save_state_selector_slots.push_back(std::move(li)); 5587 } 5588 5589 return static_cast<u32>(s_save_state_selector_slots.size()); 5590 } 5591 5592 bool FullscreenUI::OpenLoadStateSelectorForGame(const std::string& game_path) 5593 { 5594 auto lock = GameList::GetLock(); 5595 const GameList::Entry* entry = GameList::GetEntryForPath(game_path); 5596 if (entry) 5597 { 5598 s_save_state_selector_loading = true; 5599 if (PopulateSaveStateListEntries(entry->title, entry->serial) > 0) 5600 { 5601 s_save_state_selector_open = true; 5602 s_save_state_selector_resuming = false; 5603 s_save_state_selector_game_path = game_path; 5604 return true; 5605 } 5606 } 5607 5608 ShowToast({}, FSUI_STR("No save states found."), 5.0f); 5609 return false; 5610 } 5611 5612 bool FullscreenUI::OpenSaveStateSelector(bool is_loading) 5613 { 5614 s_save_state_selector_game_path = {}; 5615 s_save_state_selector_loading = is_loading; 5616 s_save_state_selector_resuming = false; 5617 if (PopulateSaveStateListEntries(System::GetGameTitle(), System::GetGameSerial()) > 0) 5618 { 5619 s_save_state_selector_open = true; 5620 QueueResetFocus(FocusResetType::PopupOpened); 5621 return true; 5622 } 5623 5624 ShowToast({}, FSUI_STR("No save states found."), 5.0f); 5625 return false; 5626 } 5627 5628 void FullscreenUI::CloseSaveStateSelector() 5629 { 5630 if (s_save_state_selector_open) 5631 QueueResetFocus(FocusResetType::PopupClosed); 5632 5633 ClearSaveStateEntryList(); 5634 s_save_state_selector_open = false; 5635 s_save_state_selector_loading = false; 5636 s_save_state_selector_resuming = false; 5637 s_save_state_selector_game_path = {}; 5638 } 5639 5640 void FullscreenUI::DrawSaveStateSelector(bool is_loading) 5641 { 5642 ImGuiIO& io = ImGui::GetIO(); 5643 5644 ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f)); 5645 ImGui::SetNextWindowSize(io.DisplaySize - LayoutScale(0.0f, LAYOUT_FOOTER_HEIGHT)); 5646 5647 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); 5648 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); 5649 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 5650 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); 5651 ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); 5652 5653 const char* window_title = is_loading ? FSUI_CSTR("Load State") : FSUI_CSTR("Save State"); 5654 ImGui::OpenPopup(window_title); 5655 5656 bool is_open = true; 5657 const bool valid = 5658 ImGui::BeginPopupModal(window_title, &is_open, 5659 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | 5660 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBackground); 5661 if (!valid || !is_open) 5662 { 5663 if (valid) 5664 ImGui::EndPopup(); 5665 5666 ImGui::PopStyleVar(5); 5667 if (!is_open) 5668 { 5669 CloseSaveStateSelector(); 5670 ReturnToPreviousWindow(); 5671 } 5672 return; 5673 } 5674 5675 const ImVec2 heading_size = 5676 ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + 5677 (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); 5678 5679 ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIPrimaryColor, 0.9f)); 5680 5681 bool closed = false; 5682 bool was_close_not_back = false; 5683 if (ImGui::BeginChild("state_titlebar", heading_size, false, ImGuiWindowFlags_NavFlattened)) 5684 { 5685 BeginNavBar(); 5686 if (NavButton(ICON_FA_BACKWARD, true, true)) 5687 closed = true; 5688 5689 NavTitle(is_loading ? FSUI_CSTR("Load State") : FSUI_CSTR("Save State")); 5690 EndNavBar(); 5691 ImGui::EndChild(); 5692 } 5693 5694 ImGui::PopStyleColor(); 5695 ImGui::PushStyleColor(ImGuiCol_ChildBg, ModAlpha(UIBackgroundColor, 0.9f)); 5696 ImGui::SetCursorPos(ImVec2(0.0f, heading_size.y)); 5697 5698 if (IsFocusResetFromWindowChange()) 5699 ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f)); 5700 5701 5702 if (ImGui::BeginChild("state_list", 5703 ImVec2(io.DisplaySize.x, io.DisplaySize.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - heading_size.y), 5704 false, ImGuiWindowFlags_NavFlattened)) 5705 { 5706 ResetFocusHere(); 5707 BeginMenuButtons(); 5708 5709 const ImGuiStyle& style = ImGui::GetStyle(); 5710 5711 const float title_spacing = LayoutScale(10.0f); 5712 const float summary_spacing = LayoutScale(4.0f); 5713 const float item_spacing = LayoutScale(20.0f); 5714 const float item_width_with_spacing = std::floor(LayoutScale(LAYOUT_SCREEN_WIDTH / 4.0f)); 5715 const float item_width = item_width_with_spacing - item_spacing; 5716 const float image_width = item_width - (style.FramePadding.x * 2.0f); 5717 const float image_height = image_width / 1.33f; 5718 const ImVec2 image_size(image_width, image_height); 5719 const float item_height = (style.FramePadding.y * 2.0f) + image_height + title_spacing + g_large_font->FontSize + 5720 summary_spacing + g_medium_font->FontSize; 5721 const ImVec2 item_size(item_width, item_height); 5722 const u32 grid_count_x = static_cast<u32>(std::floor(ImGui::GetWindowWidth() / item_width_with_spacing)); 5723 const float start_x = 5724 (static_cast<float>(ImGui::GetWindowWidth()) - (item_width_with_spacing * static_cast<float>(grid_count_x))) * 5725 0.5f; 5726 5727 u32 grid_x = 0; 5728 ImGui::SetCursorPos(ImVec2(start_x, 0.0f)); 5729 for (u32 i = 0; i < s_save_state_selector_slots.size();) 5730 { 5731 SaveStateListEntry& entry = s_save_state_selector_slots[i]; 5732 if (static_cast<s32>(i) == s_save_state_selector_submenu_index) 5733 { 5734 // can't use a choice dialog here, because we're already in a modal... 5735 ImGuiFullscreen::PushResetLayout(); 5736 ImGui::PushFont(g_large_font); 5737 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 5738 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, 5739 LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); 5740 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); 5741 ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); 5742 ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); 5743 ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); 5744 5745 const float width = LayoutScale(600.0f); 5746 const float title_height = 5747 g_large_font->FontSize + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f; 5748 const float height = 5749 title_height + 5750 LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + (LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f)) * 3.0f; 5751 ImGui::SetNextWindowSize(ImVec2(width, height)); 5752 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 5753 ImGui::OpenPopup(entry.title.c_str()); 5754 5755 bool removed = false; 5756 if (ImGui::BeginPopupModal(entry.title.c_str(), &is_open, 5757 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) 5758 { 5759 ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); 5760 5761 BeginMenuButtons(); 5762 5763 if (ActiveButton(is_loading ? FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Load State") : 5764 FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Save State"), 5765 false, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 5766 { 5767 if (is_loading) 5768 DoLoadState(std::move(entry.path)); 5769 else 5770 DoSaveState(entry.slot, entry.global); 5771 5772 closed = true; 5773 was_close_not_back = true; 5774 } 5775 5776 if (!entry.path.empty() && ActiveButton(FSUI_ICONSTR(ICON_FA_FOLDER_MINUS, "Delete Save"), false, true, 5777 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 5778 { 5779 if (!FileSystem::FileExists(entry.path.c_str())) 5780 { 5781 ShowToast({}, fmt::format(FSUI_FSTR("{} does not exist."), ImGuiFullscreen::RemoveHash(entry.title))); 5782 is_open = true; 5783 } 5784 else if (FileSystem::DeleteFile(entry.path.c_str())) 5785 { 5786 ShowToast({}, fmt::format(FSUI_FSTR("{} deleted."), ImGuiFullscreen::RemoveHash(entry.title))); 5787 s_save_state_selector_slots.erase(s_save_state_selector_slots.begin() + i); 5788 removed = true; 5789 5790 if (s_save_state_selector_slots.empty()) 5791 { 5792 closed = true; 5793 was_close_not_back = true; 5794 } 5795 else 5796 { 5797 is_open = false; 5798 } 5799 } 5800 else 5801 { 5802 ShowToast({}, fmt::format(FSUI_FSTR("Failed to delete {}."), ImGuiFullscreen::RemoveHash(entry.title))); 5803 is_open = false; 5804 } 5805 } 5806 5807 if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false, true, 5808 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 5809 { 5810 is_open = false; 5811 } 5812 5813 EndMenuButtons(); 5814 5815 ImGui::PopStyleColor(); 5816 ImGui::EndPopup(); 5817 } 5818 5819 if (!is_open) 5820 s_save_state_selector_submenu_index = -1; 5821 5822 ImGui::PopStyleColor(3); 5823 ImGui::PopStyleVar(3); 5824 ImGui::PopFont(); 5825 ImGuiFullscreen::PopResetLayout(); 5826 5827 if (removed) 5828 continue; 5829 } 5830 5831 ImGuiWindow* window = ImGui::GetCurrentWindow(); 5832 if (window->SkipItems) 5833 { 5834 i++; 5835 continue; 5836 } 5837 5838 const ImGuiID id = window->GetID(static_cast<int>(i)); 5839 const ImVec2 pos(window->DC.CursorPos); 5840 ImRect bb(pos, pos + item_size); 5841 ImGui::ItemSize(item_size); 5842 if (ImGui::ItemAdd(bb, id)) 5843 { 5844 bool held; 5845 bool hovered; 5846 bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); 5847 if (hovered) 5848 { 5849 const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f); 5850 5851 const float t = std::min(static_cast<float>(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1)), 1.0f); 5852 ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t)); 5853 5854 ImGuiFullscreen::DrawMenuButtonFrame(bb.Min, bb.Max, col, true, 0.0f); 5855 5856 ImGui::PopStyleColor(); 5857 } 5858 5859 bb.Min += style.FramePadding; 5860 bb.Max -= style.FramePadding; 5861 5862 GPUTexture* const screenshot = 5863 entry.preview_texture ? entry.preview_texture.get() : GetCachedTextureAsync("no-save.png"); 5864 const ImRect image_rect( 5865 CenterImage(ImRect(bb.Min, bb.Min + image_size), 5866 ImVec2(static_cast<float>(screenshot->GetWidth()), static_cast<float>(screenshot->GetHeight())))); 5867 5868 ImGui::GetWindowDrawList()->AddImage(screenshot, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), 5869 ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); 5870 5871 const ImVec2 title_pos(bb.Min.x, bb.Min.y + image_height + title_spacing); 5872 const ImRect title_bb(title_pos, ImVec2(bb.Max.x, title_pos.y + g_large_font->FontSize)); 5873 ImGui::PushFont(g_large_font); 5874 ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), 5875 &title_bb); 5876 ImGui::PopFont(); 5877 5878 if (!entry.summary.empty()) 5879 { 5880 const ImVec2 summary_pos(bb.Min.x, title_pos.y + g_large_font->FontSize + summary_spacing); 5881 const ImRect summary_bb(summary_pos, ImVec2(bb.Max.x, summary_pos.y + g_medium_font->FontSize)); 5882 ImGui::PushFont(g_medium_font); 5883 ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, entry.summary.c_str(), nullptr, nullptr, 5884 ImVec2(0.0f, 0.0f), &summary_bb); 5885 ImGui::PopFont(); 5886 } 5887 5888 if (pressed) 5889 { 5890 if (is_loading) 5891 DoLoadState(entry.path); 5892 else 5893 DoSaveState(entry.slot, entry.global); 5894 5895 closed = true; 5896 was_close_not_back = true; 5897 } 5898 else if (hovered && 5899 (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || 5900 ImGui::IsKeyPressed(ImGuiKey_F1, false))) 5901 { 5902 s_save_state_selector_submenu_index = static_cast<s32>(i); 5903 } 5904 } 5905 5906 grid_x++; 5907 if (grid_x == grid_count_x) 5908 { 5909 grid_x = 0; 5910 ImGui::SetCursorPosX(start_x); 5911 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + item_spacing); 5912 } 5913 else 5914 { 5915 ImGui::SameLine(start_x + static_cast<float>(grid_x) * (item_width + item_spacing)); 5916 } 5917 5918 i++; 5919 } 5920 5921 EndMenuButtons(); 5922 ImGui::EndChild(); 5923 } 5924 5925 ImGui::PopStyleColor(); 5926 5927 ImGui::EndPopup(); 5928 ImGui::PopStyleVar(5); 5929 5930 if (IsGamepadInputSource()) 5931 { 5932 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD, FSUI_VSTR("Select State")), 5933 std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Delete State")), 5934 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Load State")), 5935 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Cancel"))}); 5936 } 5937 else 5938 { 5939 SetFullscreenFooterText(std::array{ 5940 std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, 5941 FSUI_VSTR("Select State")), 5942 std::make_pair(ICON_PF_F1, FSUI_VSTR("Delete State")), std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Load State")), 5943 std::make_pair(ICON_PF_ESC, FSUI_VSTR("Cancel"))}); 5944 } 5945 5946 if (WantsToCloseMenu() || closed) 5947 { 5948 CloseSaveStateSelector(); 5949 if (was_close_not_back) 5950 ReturnToMainWindow(); 5951 else if (s_current_main_window != MainWindowType::GameList) 5952 ReturnToPreviousWindow(); 5953 } 5954 } 5955 5956 bool FullscreenUI::OpenLoadStateSelectorForGameResume(const GameList::Entry* entry) 5957 { 5958 SaveStateListEntry slentry; 5959 if (!InitializeSaveStateListEntryFromSerial(&slentry, entry->serial, -1, false)) 5960 return false; 5961 5962 CloseSaveStateSelector(); 5963 s_save_state_selector_slots.push_back(std::move(slentry)); 5964 s_save_state_selector_game_path = entry->path; 5965 s_save_state_selector_loading = true; 5966 s_save_state_selector_open = true; 5967 s_save_state_selector_resuming = true; 5968 QueueResetFocus(FocusResetType::PopupOpened); 5969 return true; 5970 } 5971 5972 void FullscreenUI::DrawResumeStateSelector() 5973 { 5974 ImGui::SetNextWindowSize(LayoutScale(800.0f, 602.0f)); 5975 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 5976 ImGui::OpenPopup(FSUI_CSTR("Load Resume State")); 5977 5978 ImGui::PushFont(g_large_font); 5979 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 5980 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); 5981 5982 bool is_open = true; 5983 if (ImGui::BeginPopupModal(FSUI_CSTR("Load Resume State"), &is_open, 5984 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) 5985 { 5986 SaveStateListEntry& entry = s_save_state_selector_slots.front(); 5987 SmallString time; 5988 TimeToPrintableString(&time, entry.timestamp); 5989 ImGui::TextWrapped( 5990 FSUI_CSTR("A resume save state created at %s was found.\n\nDo you want to load this save and continue?"), 5991 time.c_str()); 5992 5993 const GPUTexture* image = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); 5994 const float image_height = LayoutScale(250.0f); 5995 const float image_width = 5996 image_height * (static_cast<float>(image->GetWidth()) / static_cast<float>(image->GetHeight())); 5997 const ImVec2 pos(ImGui::GetCursorScreenPos() + 5998 ImVec2((ImGui::GetCurrentWindow()->WorkRect.GetWidth() - image_width) * 0.5f, LayoutScale(20.0f))); 5999 const ImRect image_bb(pos, pos + ImVec2(image_width, image_height)); 6000 ImGui::GetWindowDrawList()->AddImage( 6001 static_cast<ImTextureID>(entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get()), 6002 image_bb.Min, image_bb.Max); 6003 6004 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + image_height + LayoutScale(40.0f)); 6005 6006 BeginMenuButtons(); 6007 6008 if (ActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Load State"), false)) 6009 { 6010 DoStartPath(s_save_state_selector_game_path, std::move(entry.path)); 6011 is_open = false; 6012 } 6013 6014 if (ActiveButton(FSUI_ICONSTR(ICON_FA_LIGHTBULB, "Clean Boot"), false)) 6015 { 6016 DoStartPath(s_save_state_selector_game_path); 6017 is_open = false; 6018 } 6019 6020 if (ActiveButton(FSUI_ICONSTR(ICON_FA_FOLDER_MINUS, "Delete State"), false)) 6021 { 6022 if (FileSystem::DeleteFile(entry.path.c_str())) 6023 { 6024 DoStartPath(s_save_state_selector_game_path); 6025 is_open = false; 6026 } 6027 else 6028 { 6029 ShowToast(std::string(), FSUI_STR("Failed to delete save state.")); 6030 } 6031 } 6032 6033 if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Cancel"), false) || WantsToCloseMenu()) 6034 { 6035 ImGui::CloseCurrentPopup(); 6036 is_open = false; 6037 } 6038 EndMenuButtons(); 6039 6040 ImGui::EndPopup(); 6041 } 6042 6043 ImGui::PopStyleVar(2); 6044 ImGui::PopFont(); 6045 6046 if (!is_open) 6047 { 6048 ClearSaveStateEntryList(); 6049 s_save_state_selector_open = false; 6050 s_save_state_selector_loading = false; 6051 s_save_state_selector_resuming = false; 6052 s_save_state_selector_game_path = {}; 6053 } 6054 else 6055 { 6056 SetStandardSelectionFooterText(false); 6057 } 6058 } 6059 6060 void FullscreenUI::DoLoadState(std::string path) 6061 { 6062 Host::RunOnCPUThread([boot_path = s_save_state_selector_game_path, path = std::move(path)]() { 6063 CloseSaveStateSelector(); 6064 6065 if (System::IsValid()) 6066 { 6067 if (path.empty()) 6068 { 6069 // Loading undo state. 6070 if (!System::UndoLoadState()) 6071 ShowToast(std::string(), TRANSLATE_STR("System", "Failed to undo load state.")); 6072 } 6073 else 6074 { 6075 Error error; 6076 if (!System::LoadState(path.c_str(), &error, true)) 6077 { 6078 ShowToast(std::string(), 6079 fmt::format(TRANSLATE_FS("System", "Failed to load state: {}"), error.GetDescription())); 6080 } 6081 } 6082 } 6083 else 6084 { 6085 DoStartPath(std::move(boot_path), std::move(path)); 6086 } 6087 }); 6088 } 6089 6090 void FullscreenUI::DoSaveState(s32 slot, bool global) 6091 { 6092 Host::RunOnCPUThread([slot, global]() { 6093 CloseSaveStateSelector(); 6094 if (!System::IsValid()) 6095 return; 6096 6097 std::string filename(global ? System::GetGlobalSaveStateFileName(slot) : 6098 System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); 6099 Error error; 6100 if (!System::SaveState(filename.c_str(), &error, g_settings.create_save_state_backups)) 6101 { 6102 ShowToast(std::string(), fmt::format(TRANSLATE_FS("System", "Failed to save state: {}"), error.GetDescription())); 6103 } 6104 }); 6105 } 6106 6107 void FullscreenUI::PopulateGameListEntryList() 6108 { 6109 const s32 sort = Host::GetBaseIntSettingValue("Main", "FullscreenUIGameSort", 0); 6110 const bool reverse = Host::GetBaseBoolSettingValue("Main", "FullscreenUIGameSortReverse", false); 6111 const bool merge_disc_sets = Host::GetBaseBoolSettingValue("Main", "FullscreenUIMergeDiscSets", true); 6112 6113 const u32 count = GameList::GetEntryCount(); 6114 s_game_list_sorted_entries.clear(); 6115 s_game_list_sorted_entries.reserve(count); 6116 for (u32 i = 0; i < count; i++) 6117 { 6118 const GameList::Entry* entry = GameList::GetEntryByIndex(i); 6119 if (merge_disc_sets) 6120 { 6121 if (entry->disc_set_member) 6122 continue; 6123 } 6124 else 6125 { 6126 if (entry->IsDiscSet()) 6127 continue; 6128 } 6129 6130 s_game_list_sorted_entries.push_back(entry); 6131 } 6132 6133 std::sort(s_game_list_sorted_entries.begin(), s_game_list_sorted_entries.end(), 6134 [sort, reverse](const GameList::Entry* lhs, const GameList::Entry* rhs) { 6135 switch (sort) 6136 { 6137 case 0: // Type 6138 { 6139 const GameList::EntryType lst = lhs->GetSortType(); 6140 const GameList::EntryType rst = rhs->GetSortType(); 6141 if (lst != rst) 6142 return reverse ? (lst > rst) : (lst < rst); 6143 } 6144 break; 6145 6146 case 1: // Serial 6147 { 6148 if (lhs->serial != rhs->serial) 6149 return reverse ? (lhs->serial > rhs->serial) : (lhs->serial < rhs->serial); 6150 } 6151 break; 6152 6153 case 2: // Title 6154 break; 6155 6156 case 3: // File Title 6157 { 6158 const std::string_view lhs_title(Path::GetFileTitle(lhs->path)); 6159 const std::string_view rhs_title(Path::GetFileTitle(rhs->path)); 6160 const int res = StringUtil::Strncasecmp(lhs_title.data(), rhs_title.data(), 6161 std::min(lhs_title.size(), rhs_title.size())); 6162 if (res != 0) 6163 return reverse ? (res > 0) : (res < 0); 6164 } 6165 break; 6166 6167 case 4: // Time Played 6168 { 6169 if (lhs->total_played_time != rhs->total_played_time) 6170 { 6171 return reverse ? (lhs->total_played_time > rhs->total_played_time) : 6172 (lhs->total_played_time < rhs->total_played_time); 6173 } 6174 } 6175 break; 6176 6177 case 5: // Last Played (reversed by default) 6178 { 6179 if (lhs->last_played_time != rhs->last_played_time) 6180 { 6181 return reverse ? (lhs->last_played_time < rhs->last_played_time) : 6182 (lhs->last_played_time > rhs->last_played_time); 6183 } 6184 } 6185 break; 6186 6187 case 6: // File Size 6188 { 6189 if (lhs->file_size != rhs->file_size) 6190 { 6191 return reverse ? (lhs->file_size > rhs->file_size) : (lhs->file_size < rhs->file_size); 6192 } 6193 } 6194 break; 6195 6196 case 7: // Uncompressed Size 6197 { 6198 if (lhs->uncompressed_size != rhs->uncompressed_size) 6199 { 6200 return reverse ? (lhs->uncompressed_size > rhs->uncompressed_size) : 6201 (lhs->uncompressed_size < rhs->uncompressed_size); 6202 } 6203 } 6204 break; 6205 } 6206 6207 // fallback to title when all else is equal 6208 const int res = StringUtil::Strcasecmp(lhs->title.c_str(), rhs->title.c_str()); 6209 return reverse ? (res > 0) : (res < 0); 6210 }); 6211 } 6212 6213 void FullscreenUI::DrawGameListWindow() 6214 { 6215 auto game_list_lock = GameList::GetLock(); 6216 PopulateGameListEntryList(); 6217 6218 ImGuiIO& io = ImGui::GetIO(); 6219 const ImVec2 heading_size = 6220 ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + 6221 (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); 6222 6223 const float bg_alpha = System::IsValid() ? 0.90f : 1.0f; 6224 6225 if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", MulAlpha(UIPrimaryColor, bg_alpha))) 6226 { 6227 static constexpr float ITEM_WIDTH = 25.0f; 6228 static constexpr const char* icons[] = {ICON_FA_BORDER_ALL, ICON_FA_LIST}; 6229 static constexpr const char* titles[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; 6230 static constexpr u32 count = static_cast<u32>(std::size(titles)); 6231 6232 BeginNavBar(); 6233 6234 if (NavButton(ICON_FA_BACKWARD, true, true)) 6235 ReturnToPreviousWindow(); 6236 6237 NavTitle(Host::TranslateToCString(TR_CONTEXT, titles[static_cast<u32>(s_game_list_view)])); 6238 RightAlignNavButtons(count, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); 6239 6240 for (u32 i = 0; i < count; i++) 6241 { 6242 if (NavButton(icons[i], static_cast<GameListView>(i) == s_game_list_view, true, ITEM_WIDTH, 6243 LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) 6244 { 6245 s_game_list_view = static_cast<GameListView>(i); 6246 } 6247 } 6248 6249 EndNavBar(); 6250 } 6251 6252 EndFullscreenWindow(); 6253 6254 if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false)) 6255 { 6256 s_game_list_view = (s_game_list_view == GameListView::Grid) ? GameListView::List : GameListView::Grid; 6257 } 6258 else if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F2)) 6259 { 6260 s_current_main_window = MainWindowType::GameListSettings; 6261 QueueResetFocus(FocusResetType::ViewChanged); 6262 } 6263 6264 switch (s_game_list_view) 6265 { 6266 case GameListView::Grid: 6267 DrawGameGrid(heading_size); 6268 break; 6269 case GameListView::List: 6270 DrawGameList(heading_size); 6271 break; 6272 default: 6273 break; 6274 } 6275 6276 if (IsGamepadInputSource()) 6277 { 6278 SetFullscreenFooterText(std::array{std::make_pair(ICON_PF_XBOX_DPAD, FSUI_VSTR("Select Game")), 6279 std::make_pair(ICON_PF_BUTTON_X, FSUI_VSTR("Change View")), 6280 std::make_pair(ICON_PF_BURGER_MENU, FSUI_VSTR("Settings")), 6281 std::make_pair(ICON_PF_BUTTON_Y, FSUI_VSTR("Launch Options")), 6282 std::make_pair(ICON_PF_BUTTON_A, FSUI_VSTR("Start Game")), 6283 std::make_pair(ICON_PF_BUTTON_B, FSUI_VSTR("Back"))}); 6284 } 6285 else 6286 { 6287 SetFullscreenFooterText(std::array{ 6288 std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, 6289 FSUI_VSTR("Select Game")), 6290 std::make_pair(ICON_PF_F1, FSUI_VSTR("Change View")), std::make_pair(ICON_PF_F2, FSUI_VSTR("Settings")), 6291 std::make_pair(ICON_PF_F3, FSUI_VSTR("Launch Options")), std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Start Game")), 6292 std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back"))}); 6293 } 6294 } 6295 6296 void FullscreenUI::DrawGameList(const ImVec2& heading_size) 6297 { 6298 if (!BeginFullscreenColumns(nullptr, heading_size.y, true, true)) 6299 { 6300 EndFullscreenColumns(); 6301 return; 6302 } 6303 6304 if (!AreAnyDialogsOpen() && WantsToCloseMenu()) 6305 ReturnToPreviousWindow(); 6306 6307 auto game_list_lock = GameList::GetLock(); 6308 const GameList::Entry* selected_entry = nullptr; 6309 PopulateGameListEntryList(); 6310 6311 if (IsFocusResetFromWindowChange()) 6312 ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f)); 6313 6314 if (BeginFullscreenColumnWindow(0.0f, -530.0f, "game_list_entries")) 6315 { 6316 const ImVec2 image_size(LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT, LAYOUT_MENU_BUTTON_HEIGHT)); 6317 6318 ResetFocusHere(); 6319 6320 BeginMenuButtons(); 6321 6322 SmallString summary; 6323 6324 for (const GameList::Entry* entry : s_game_list_sorted_entries) 6325 { 6326 ImRect bb; 6327 bool visible, hovered; 6328 bool pressed = 6329 MenuButtonFrame(entry->path.c_str(), true, LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max); 6330 if (!visible) 6331 continue; 6332 6333 GPUTexture* cover_texture = GetGameListCover(entry); 6334 6335 if (entry->serial.empty()) 6336 summary.format("{} - ", Settings::GetDiscRegionDisplayName(entry->region)); 6337 else 6338 summary.format("{} - {} - ", entry->serial, Settings::GetDiscRegionDisplayName(entry->region)); 6339 6340 summary.append(Path::GetFileName(entry->path)); 6341 6342 const ImRect image_rect( 6343 CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast<float>(cover_texture->GetWidth()), 6344 static_cast<float>(cover_texture->GetHeight())))); 6345 6346 ImGui::GetWindowDrawList()->AddImage(cover_texture, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), 6347 ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); 6348 6349 const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f); 6350 const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f); 6351 const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint)); 6352 const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max); 6353 6354 ImGui::PushFont(g_large_font); 6355 ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry->title.c_str(), 6356 entry->title.c_str() + entry->title.size(), nullptr, ImVec2(0.0f, 0.0f), &title_bb); 6357 ImGui::PopFont(); 6358 6359 if (!summary.empty()) 6360 { 6361 ImGui::PushFont(g_medium_font); 6362 ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary.c_str(), summary.end_ptr(), nullptr, 6363 ImVec2(0.0f, 0.0f), &summary_bb); 6364 ImGui::PopFont(); 6365 } 6366 6367 if (pressed) 6368 { 6369 HandleGameListActivate(entry); 6370 } 6371 else 6372 { 6373 if (hovered) 6374 selected_entry = entry; 6375 6376 if (selected_entry && 6377 (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || 6378 ImGui::IsKeyPressed(ImGuiKey_F3, false))) 6379 { 6380 HandleGameListOptions(selected_entry); 6381 } 6382 } 6383 } 6384 6385 EndMenuButtons(); 6386 } 6387 EndFullscreenColumnWindow(); 6388 6389 if (BeginFullscreenColumnWindow(-530.0f, 0.0f, "game_list_info", UIPrimaryDarkColor)) 6390 { 6391 const GPUTexture* cover_texture = 6392 selected_entry ? GetGameListCover(selected_entry) : GetTextureForGameListEntryType(GameList::EntryType::Count); 6393 if (cover_texture) 6394 { 6395 const ImRect image_rect( 6396 CenterImage(LayoutScale(ImVec2(350.0f, 350.0f)), ImVec2(static_cast<float>(cover_texture->GetWidth()), 6397 static_cast<float>(cover_texture->GetHeight())))); 6398 6399 ImGui::SetCursorPos(LayoutScale(ImVec2(90.0f, 0.0f)) + image_rect.Min); 6400 ImGui::Image(selected_entry ? GetGameListCover(selected_entry) : 6401 GetTextureForGameListEntryType(GameList::EntryType::Count), 6402 image_rect.GetSize()); 6403 } 6404 6405 const float work_width = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); 6406 constexpr float field_margin_y = 10.0f; 6407 constexpr float start_x = 50.0f; 6408 float text_y = 400.0f; 6409 float text_width; 6410 6411 PushPrimaryColor(); 6412 ImGui::SetCursorPos(LayoutScale(start_x, text_y)); 6413 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, field_margin_y)); 6414 ImGui::BeginGroup(); 6415 6416 if (selected_entry) 6417 { 6418 // title 6419 ImGui::PushFont(g_large_font); 6420 text_width = ImGui::CalcTextSize(selected_entry->title.c_str(), nullptr, false, work_width).x; 6421 ImGui::SetCursorPosX((work_width - text_width) / 2.0f); 6422 ImGui::TextWrapped("%s", selected_entry->title.c_str()); 6423 ImGui::PopFont(); 6424 6425 ImGui::PushFont(g_medium_font); 6426 6427 // developer 6428 if (!selected_entry->developer.empty()) 6429 { 6430 text_width = 6431 ImGui::CalcTextSize(selected_entry->developer.c_str(), 6432 selected_entry->developer.c_str() + selected_entry->developer.length(), false, work_width) 6433 .x; 6434 ImGui::SetCursorPosX((work_width - text_width) / 2.0f); 6435 ImGui::TextWrapped("%s", selected_entry->developer.c_str()); 6436 } 6437 6438 // code 6439 text_width = ImGui::CalcTextSize(selected_entry->serial.c_str(), nullptr, false, work_width).x; 6440 ImGui::SetCursorPosX((work_width - text_width) / 2.0f); 6441 ImGui::TextWrapped("%s", selected_entry->serial.c_str()); 6442 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 15.0f); 6443 6444 // region 6445 { 6446 const TinyString flag_texture = 6447 TinyString::from_format("fullscreenui/{}.png", Settings::GetDiscRegionName(selected_entry->region)); 6448 ImGui::TextUnformatted(FSUI_CSTR("Region: ")); 6449 ImGui::SameLine(); 6450 ImGui::Image(GetCachedTextureAsync(flag_texture.c_str()), LayoutScale(23.0f, 16.0f)); 6451 ImGui::SameLine(); 6452 ImGui::Text(" (%s)", Settings::GetDiscRegionDisplayName(selected_entry->region)); 6453 } 6454 6455 // genre 6456 ImGui::Text(FSUI_CSTR("Genre: %s"), selected_entry->genre.c_str()); 6457 6458 // release date 6459 char release_date_str[64]; 6460 selected_entry->GetReleaseDateString(release_date_str, sizeof(release_date_str)); 6461 ImGui::Text(FSUI_CSTR("Release Date: %s"), release_date_str); 6462 6463 // compatibility 6464 ImGui::TextUnformatted(FSUI_CSTR("Compatibility: ")); 6465 ImGui::SameLine(); 6466 if (selected_entry->compatibility != GameDatabase::CompatibilityRating::Unknown) 6467 { 6468 ImGui::Image(s_game_compatibility_textures[static_cast<u32>(selected_entry->compatibility)].get(), 6469 LayoutScale(64.0f, 16.0f)); 6470 ImGui::SameLine(); 6471 } 6472 ImGui::Text(" (%s)", GameDatabase::GetCompatibilityRatingDisplayName(selected_entry->compatibility)); 6473 6474 // play time 6475 ImGui::Text(FSUI_CSTR("Time Played: %s"), GameList::FormatTimespan(selected_entry->total_played_time).c_str()); 6476 ImGui::Text(FSUI_CSTR("Last Played: %s"), GameList::FormatTimestamp(selected_entry->last_played_time).c_str()); 6477 6478 // size 6479 if (selected_entry->file_size >= 0) 6480 ImGui::Text(FSUI_CSTR("File Size: %.2f MB"), static_cast<float>(selected_entry->file_size) / 1048576.0f); 6481 else 6482 ImGui::TextUnformatted(FSUI_CSTR("Unknown File Size")); 6483 ImGui::Text(FSUI_CSTR("Uncompressed Size: %.2f MB"), 6484 static_cast<float>(selected_entry->uncompressed_size) / 1048576.0f); 6485 6486 ImGui::PopFont(); 6487 } 6488 else 6489 { 6490 // title 6491 const char* title = FSUI_CSTR("No Game Selected"); 6492 ImGui::PushFont(g_large_font); 6493 text_width = ImGui::CalcTextSize(title, nullptr, false, work_width).x; 6494 ImGui::SetCursorPosX((work_width - text_width) / 2.0f); 6495 ImGui::TextWrapped("%s", title); 6496 ImGui::PopFont(); 6497 } 6498 6499 ImGui::EndGroup(); 6500 ImGui::PopStyleVar(); 6501 PopPrimaryColor(); 6502 } 6503 EndFullscreenColumnWindow(); 6504 6505 EndFullscreenColumns(); 6506 } 6507 6508 void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) 6509 { 6510 if (IsFocusResetFromWindowChange()) 6511 ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f)); 6512 6513 ImGuiIO& io = ImGui::GetIO(); 6514 if (!BeginFullscreenWindow( 6515 ImVec2(0.0f, heading_size.y), 6516 ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), "game_grid", 6517 UIBackgroundColor)) 6518 { 6519 EndFullscreenWindow(); 6520 return; 6521 } 6522 6523 if (ImGui::IsWindowFocused() && WantsToCloseMenu()) 6524 ReturnToPreviousWindow(); 6525 6526 ResetFocusHere(); 6527 BeginMenuButtons(); 6528 6529 const ImGuiStyle& style = ImGui::GetStyle(); 6530 6531 const float title_spacing = LayoutScale(10.0f); 6532 const float item_spacing = LayoutScale(20.0f); 6533 const float item_width_with_spacing = std::floor(LayoutScale(LAYOUT_SCREEN_WIDTH / 5.0f)); 6534 const float item_width = item_width_with_spacing - item_spacing; 6535 const float image_width = item_width - (style.FramePadding.x * 2.0f); 6536 const float image_height = image_width; 6537 const ImVec2 image_size(image_width, image_height); 6538 const float item_height = (style.FramePadding.y * 2.0f) + image_height + title_spacing + g_medium_font->FontSize; 6539 const ImVec2 item_size(item_width, item_height); 6540 const u32 grid_count_x = static_cast<u32>(std::floor(ImGui::GetWindowWidth() / item_width_with_spacing)); 6541 const float start_x = 6542 (static_cast<float>(ImGui::GetWindowWidth()) - (item_width_with_spacing * static_cast<float>(grid_count_x))) * 0.5f; 6543 6544 SmallString draw_title; 6545 6546 u32 grid_x = 0; 6547 ImGui::SetCursorPos(ImVec2(start_x, 0.0f)); 6548 for (const GameList::Entry* entry : s_game_list_sorted_entries) 6549 { 6550 ImGuiWindow* window = ImGui::GetCurrentWindow(); 6551 if (window->SkipItems) 6552 continue; 6553 6554 const ImGuiID id = window->GetID(entry->path.c_str(), entry->path.c_str() + entry->path.length()); 6555 const ImVec2 pos(window->DC.CursorPos); 6556 ImRect bb(pos, pos + item_size); 6557 ImGui::ItemSize(item_size); 6558 if (ImGui::ItemAdd(bb, id)) 6559 { 6560 bool held; 6561 bool hovered; 6562 bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); 6563 if (hovered) 6564 { 6565 const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f); 6566 6567 const float t = static_cast<float>(std::min(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1), 1.0)); 6568 ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t)); 6569 6570 ImGuiFullscreen::DrawMenuButtonFrame(bb.Min, bb.Max, col, true, 0.0f); 6571 6572 ImGui::PopStyleColor(); 6573 } 6574 6575 bb.Min += style.FramePadding; 6576 bb.Max -= style.FramePadding; 6577 6578 GPUTexture* const cover_texture = GetGameListCover(entry); 6579 const ImRect image_rect( 6580 CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast<float>(cover_texture->GetWidth()), 6581 static_cast<float>(cover_texture->GetHeight())))); 6582 6583 ImGui::GetWindowDrawList()->AddImage(cover_texture, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), 6584 ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); 6585 6586 const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); 6587 const std::string_view title( 6588 std::string_view(entry->title).substr(0, (entry->title.length() > 31) ? 31 : std::string_view::npos)); 6589 draw_title.format("{}{}", title, (title.length() == entry->title.length()) ? "" : "..."); 6590 ImGui::PushFont(g_medium_font); 6591 ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, draw_title.c_str(), draw_title.end_ptr(), nullptr, 6592 ImVec2(0.5f, 0.0f), &title_bb); 6593 ImGui::PopFont(); 6594 6595 if (pressed) 6596 { 6597 HandleGameListActivate(entry); 6598 } 6599 else if (hovered && 6600 (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || 6601 ImGui::IsKeyPressed(ImGuiKey_F3, false))) 6602 { 6603 HandleGameListOptions(entry); 6604 } 6605 } 6606 6607 grid_x++; 6608 if (grid_x == grid_count_x) 6609 { 6610 grid_x = 0; 6611 ImGui::SetCursorPosX(start_x); 6612 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + item_spacing); 6613 } 6614 else 6615 { 6616 ImGui::SameLine(start_x + static_cast<float>(grid_x) * (item_width + item_spacing)); 6617 } 6618 } 6619 6620 EndMenuButtons(); 6621 EndFullscreenWindow(); 6622 } 6623 6624 void FullscreenUI::HandleGameListActivate(const GameList::Entry* entry) 6625 { 6626 if (entry->IsDiscSet()) 6627 { 6628 HandleSelectDiscForDiscSet(entry->path); 6629 return; 6630 } 6631 6632 // launch game 6633 if (!OpenLoadStateSelectorForGameResume(entry)) 6634 DoStartPath(entry->path); 6635 } 6636 6637 void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) 6638 { 6639 if (!entry->IsDiscSet()) 6640 { 6641 ImGuiFullscreen::ChoiceDialogOptions options = { 6642 {FSUI_ICONSTR(ICON_FA_WRENCH, "Game Properties"), false}, 6643 {FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Open Containing Directory"), false}, 6644 {FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false}, 6645 {FSUI_ICONSTR(ICON_FA_UNDO, "Load State"), false}, 6646 {FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Default Boot"), false}, 6647 {FSUI_ICONSTR(ICON_FA_LIGHTBULB, "Fast Boot"), false}, 6648 {FSUI_ICONSTR(ICON_FA_MAGIC, "Slow Boot"), false}, 6649 {FSUI_ICONSTR(ICON_FA_FOLDER_MINUS, "Reset Play Time"), false}, 6650 {FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false}, 6651 }; 6652 6653 OpenChoiceDialog( 6654 entry->title.c_str(), false, std::move(options), 6655 [entry_path = entry->path, entry_serial = entry->serial](s32 index, const std::string& title, bool checked) { 6656 switch (index) 6657 { 6658 case 0: // Open Game Properties 6659 SwitchToGameSettingsForPath(entry_path); 6660 break; 6661 case 1: // Open Containing Directory 6662 ExitFullscreenAndOpenURL(Path::CreateFileURL(Path::GetDirectory(entry_path))); 6663 break; 6664 case 2: // Resume Game 6665 DoStartPath(entry_path, System::GetGameSaveStateFileName(entry_serial, -1)); 6666 break; 6667 case 3: // Load State 6668 OpenLoadStateSelectorForGame(entry_path); 6669 break; 6670 case 4: // Default Boot 6671 DoStartPath(entry_path); 6672 break; 6673 case 5: // Fast Boot 6674 DoStartPath(entry_path, {}, true); 6675 break; 6676 case 6: // Slow Boot 6677 DoStartPath(entry_path, {}, false); 6678 break; 6679 case 7: // Reset Play Time 6680 GameList::ClearPlayedTimeForSerial(entry_serial); 6681 break; 6682 default: 6683 break; 6684 } 6685 6686 CloseChoiceDialog(); 6687 }); 6688 } 6689 else 6690 { 6691 // shouldn't fail 6692 const GameList::Entry* first_disc_entry = GameList::GetFirstDiscSetMember(entry->path); 6693 if (!first_disc_entry) 6694 return; 6695 6696 ImGuiFullscreen::ChoiceDialogOptions options = { 6697 {FSUI_ICONSTR(ICON_FA_WRENCH, "Game Properties"), false}, 6698 {FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc"), false}, 6699 {FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false}, 6700 }; 6701 6702 OpenChoiceDialog(entry->title.c_str(), false, std::move(options), 6703 [entry_path = first_disc_entry->path, 6704 disc_set_name = entry->path](s32 index, const std::string& title, bool checked) { 6705 switch (index) 6706 { 6707 case 0: // Open Game Properties 6708 SwitchToGameSettingsForPath(entry_path); 6709 break; 6710 case 1: // Select Disc 6711 HandleSelectDiscForDiscSet(disc_set_name); 6712 break; 6713 default: 6714 break; 6715 } 6716 6717 CloseChoiceDialog(); 6718 }); 6719 } 6720 } 6721 6722 void FullscreenUI::HandleSelectDiscForDiscSet(std::string_view disc_set_name) 6723 { 6724 auto lock = GameList::GetLock(); 6725 const std::vector<const GameList::Entry*> entries = GameList::GetDiscSetMembers(disc_set_name, true); 6726 if (entries.empty()) 6727 return; 6728 6729 ImGuiFullscreen::ChoiceDialogOptions options; 6730 std::vector<std::string> paths; 6731 paths.reserve(entries.size()); 6732 6733 for (const GameList::Entry* entry : entries) 6734 { 6735 std::string title = fmt::format(fmt::runtime(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Disc {} | {}")), 6736 entry->disc_set_index + 1, Path::GetFileName(entry->path)); 6737 options.emplace_back(std::move(title), false); 6738 paths.push_back(entry->path); 6739 } 6740 options.emplace_back(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false); 6741 6742 OpenChoiceDialog(SmallString::from_format("Select Disc for {}", disc_set_name), false, std::move(options), 6743 [paths = std::move(paths)](s32 index, const std::string& title, bool checked) { 6744 if (static_cast<u32>(index) < paths.size()) 6745 { 6746 auto lock = GameList::GetLock(); 6747 const GameList::Entry* entry = GameList::GetEntryForPath(paths[index]); 6748 if (entry) 6749 HandleGameListActivate(entry); 6750 } 6751 6752 CloseChoiceDialog(); 6753 }); 6754 } 6755 6756 void FullscreenUI::DrawGameListSettingsWindow() 6757 { 6758 ImGuiIO& io = ImGui::GetIO(); 6759 const ImVec2 heading_size = 6760 ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + 6761 (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); 6762 6763 const float bg_alpha = System::IsValid() ? 0.90f : 1.0f; 6764 6765 if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", MulAlpha(UIPrimaryColor, bg_alpha))) 6766 { 6767 BeginNavBar(); 6768 6769 if (NavButton(ICON_FA_BACKWARD, true, true)) 6770 { 6771 s_current_main_window = MainWindowType::GameList; 6772 QueueResetFocus(FocusResetType::Other); 6773 } 6774 6775 NavTitle(FSUI_CSTR("Game List Settings")); 6776 EndNavBar(); 6777 } 6778 6779 EndFullscreenWindow(); 6780 6781 if (!BeginFullscreenWindow( 6782 ImVec2(0.0f, heading_size.y), 6783 ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)), 6784 "settings_parent", UIBackgroundColor, 0.0f, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f))) 6785 { 6786 EndFullscreenWindow(); 6787 return; 6788 } 6789 6790 if (ImGui::IsWindowFocused() && WantsToCloseMenu()) 6791 { 6792 s_current_main_window = MainWindowType::GameList; 6793 QueueResetFocus(FocusResetType::ViewChanged); 6794 } 6795 6796 auto lock = Host::GetSettingsLock(); 6797 SettingsInterface* bsi = GetEditingSettingsInterface(false); 6798 6799 BeginMenuButtons(); 6800 6801 MenuHeading(FSUI_CSTR("Search Directories")); 6802 if (MenuButton(FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"), 6803 FSUI_CSTR("Adds a new directory to the game search list."))) 6804 { 6805 OpenFileSelector(FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Add Search Directory"), true, [](const std::string& dir) { 6806 if (!dir.empty()) 6807 { 6808 auto lock = Host::GetSettingsLock(); 6809 SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); 6810 6811 bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str()); 6812 bsi->RemoveFromStringList("GameList", "Paths", dir.c_str()); 6813 SetSettingsChanged(bsi); 6814 PopulateGameListDirectoryCache(bsi); 6815 Host::RefreshGameListAsync(false); 6816 } 6817 6818 CloseFileSelector(); 6819 }); 6820 } 6821 6822 for (const auto& it : s_game_list_directories_cache) 6823 { 6824 if (MenuButton(SmallString::from_format(ICON_FA_FOLDER " {}", it.first), 6825 it.second ? FSUI_CSTR("Scanning Subdirectories") : FSUI_CSTR("Not Scanning Subdirectories"))) 6826 { 6827 ImGuiFullscreen::ChoiceDialogOptions options = { 6828 {FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Open in File Browser"), false}, 6829 {it.second ? (FSUI_ICONSTR(ICON_FA_FOLDER_MINUS, "Disable Subdirectory Scanning")) : 6830 (FSUI_ICONSTR(ICON_FA_FOLDER_PLUS, "Enable Subdirectory Scanning")), 6831 false}, 6832 {FSUI_ICONSTR(ICON_FA_TIMES, "Remove From List"), false}, 6833 {FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close Menu"), false}, 6834 }; 6835 6836 OpenChoiceDialog(it.first.c_str(), false, std::move(options), 6837 [dir = it.first, recursive = it.second](s32 index, const std::string& title, bool checked) { 6838 if (index < 0) 6839 return; 6840 6841 if (index == 0) 6842 { 6843 // Open in file browser 6844 ExitFullscreenAndOpenURL(Path::CreateFileURL(dir)); 6845 } 6846 else if (index == 1) 6847 { 6848 // toggle subdirectory scanning 6849 { 6850 auto lock = Host::GetSettingsLock(); 6851 SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); 6852 if (!recursive) 6853 { 6854 bsi->RemoveFromStringList("GameList", "Paths", dir.c_str()); 6855 bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str()); 6856 } 6857 else 6858 { 6859 bsi->RemoveFromStringList("GameList", "RecursivePaths", dir.c_str()); 6860 bsi->AddToStringList("GameList", "Paths", dir.c_str()); 6861 } 6862 6863 SetSettingsChanged(bsi); 6864 PopulateGameListDirectoryCache(bsi); 6865 } 6866 6867 Host::RefreshGameListAsync(false); 6868 } 6869 else if (index == 2) 6870 { 6871 // remove from list 6872 auto lock = Host::GetSettingsLock(); 6873 SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); 6874 bsi->RemoveFromStringList("GameList", "Paths", dir.c_str()); 6875 bsi->RemoveFromStringList("GameList", "RecursivePaths", dir.c_str()); 6876 SetSettingsChanged(bsi); 6877 PopulateGameListDirectoryCache(bsi); 6878 Host::RefreshGameListAsync(false); 6879 } 6880 6881 CloseChoiceDialog(); 6882 }); 6883 } 6884 } 6885 6886 MenuHeading(FSUI_CSTR("List Settings")); 6887 { 6888 static constexpr const char* view_types[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; 6889 static constexpr const char* sort_types[] = { 6890 FSUI_NSTR("Type"), FSUI_NSTR("Serial"), FSUI_NSTR("Title"), FSUI_NSTR("File Title"), 6891 FSUI_NSTR("Time Played"), FSUI_NSTR("Last Played"), FSUI_NSTR("File Size"), FSUI_NSTR("Uncompressed Size")}; 6892 6893 DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BORDER_ALL, "Default View"), 6894 FSUI_CSTR("Selects the view that the game list will open to."), "Main", 6895 "DefaultFullscreenUIGameView", 0, view_types, std::size(view_types), true); 6896 DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_SORT, "Sort By"), 6897 FSUI_CSTR("Determines that field that the game list will be sorted by."), "Main", 6898 "FullscreenUIGameSort", 0, sort_types, std::size(sort_types), true); 6899 DrawToggleSetting( 6900 bsi, FSUI_ICONSTR(ICON_FA_SORT_ALPHA_DOWN, "Sort Reversed"), 6901 FSUI_CSTR("Reverses the game list sort order from the default (usually ascending to descending)."), "Main", 6902 "FullscreenUIGameSortReverse", false); 6903 DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST, "Merge Multi-Disc Games"), 6904 FSUI_CSTR("Merges multi-disc games into one item in the game list."), "Main", 6905 "FullscreenUIMergeDiscSets", true); 6906 } 6907 6908 MenuHeading(FSUI_CSTR("Cover Settings")); 6909 { 6910 DrawFolderSetting(bsi, FSUI_ICONSTR(ICON_FA_FOLDER, "Covers Directory"), "Folders", "Covers", EmuFolders::Covers); 6911 if (MenuButton(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Download Covers"), 6912 FSUI_CSTR("Downloads covers from a user-specified URL template."))) 6913 { 6914 Host::OnCoverDownloaderOpenRequested(); 6915 } 6916 } 6917 6918 MenuHeading(FSUI_CSTR("Operations")); 6919 { 6920 if (MenuButton(FSUI_ICONSTR(ICON_FA_SEARCH, "Scan For New Games"), 6921 FSUI_CSTR("Identifies any new files added to the game directories."))) 6922 { 6923 Host::RefreshGameListAsync(false); 6924 } 6925 if (MenuButton(FSUI_ICONSTR(ICON_FA_SEARCH_PLUS, "Rescan All Games"), 6926 FSUI_CSTR("Forces a full rescan of all games previously identified."))) 6927 { 6928 Host::RefreshGameListAsync(true); 6929 } 6930 } 6931 6932 EndMenuButtons(); 6933 6934 EndFullscreenWindow(); 6935 6936 SetStandardSelectionFooterText(true); 6937 } 6938 6939 void FullscreenUI::SwitchToGameList() 6940 { 6941 s_current_main_window = MainWindowType::GameList; 6942 s_game_list_view = static_cast<GameListView>(Host::GetBaseIntSettingValue("Main", "DefaultFullscreenUIGameView", 0)); 6943 { 6944 auto lock = Host::GetSettingsLock(); 6945 PopulateGameListDirectoryCache(Host::Internal::GetBaseSettingsLayer()); 6946 } 6947 QueueResetFocus(FocusResetType::ViewChanged); 6948 } 6949 6950 GPUTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry) 6951 { 6952 // lookup and grab cover image 6953 auto cover_it = s_cover_image_map.find(entry->path); 6954 if (cover_it == s_cover_image_map.end()) 6955 { 6956 std::string cover_path(GameList::GetCoverImagePathForEntry(entry)); 6957 cover_it = s_cover_image_map.emplace(entry->path, std::move(cover_path)).first; 6958 } 6959 6960 GPUTexture* tex = (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr; 6961 return tex ? tex : GetTextureForGameListEntryType(entry->type); 6962 } 6963 6964 GPUTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type) 6965 { 6966 switch (type) 6967 { 6968 case GameList::EntryType::PSExe: 6969 return s_fallback_exe_texture.get(); 6970 6971 case GameList::EntryType::Playlist: 6972 return s_fallback_playlist_texture.get(); 6973 6974 case GameList::EntryType::PSF: 6975 return s_fallback_psf_texture.get(); 6976 6977 case GameList::EntryType::Disc: 6978 default: 6979 return s_fallback_disc_texture.get(); 6980 } 6981 } 6982 6983 GPUTexture* FullscreenUI::GetCoverForCurrentGame() 6984 { 6985 auto lock = GameList::GetLock(); 6986 6987 const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath()); 6988 if (!entry) 6989 return s_fallback_disc_texture.get(); 6990 6991 return GetGameListCover(entry); 6992 } 6993 6994 ////////////////////////////////////////////////////////////////////////// 6995 // Overlays 6996 ////////////////////////////////////////////////////////////////////////// 6997 6998 void FullscreenUI::OpenAboutWindow() 6999 { 7000 s_about_window_open = true; 7001 } 7002 7003 void FullscreenUI::ExitFullscreenAndOpenURL(std::string_view url) 7004 { 7005 Host::RunOnCPUThread([url = std::string(url)]() { 7006 if (Host::IsFullscreen()) 7007 Host::SetFullscreen(false); 7008 7009 Host::OpenURL(url); 7010 }); 7011 } 7012 7013 void FullscreenUI::CopyTextToClipboard(std::string title, std::string_view text) 7014 { 7015 if (Host::CopyTextToClipboard(text)) 7016 ShowToast(std::string(), std::move(title)); 7017 else 7018 ShowToast(std::string(), FSUI_STR("Failed to copy text to clipboard.")); 7019 } 7020 7021 void FullscreenUI::DrawAboutWindow() 7022 { 7023 ImGui::SetNextWindowSize(LayoutScale(1000.0f, 540.0f)); 7024 ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 7025 ImGui::OpenPopup(FSUI_CSTR("About DuckStation")); 7026 7027 ImGui::PushFont(g_large_font); 7028 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); 7029 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(30.0f, 30.0f)); 7030 7031 if (ImGui::BeginPopupModal(FSUI_CSTR("About DuckStation"), &s_about_window_open, 7032 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) 7033 { 7034 ImGui::TextWrapped("%s", 7035 FSUI_CSTR("DuckStation is a free and open-source simulator/emulator of the Sony PlayStation(TM) " 7036 "console, focusing on playability, speed, and long-term maintainability.")); 7037 ImGui::NewLine(); 7038 ImGui::TextWrapped(FSUI_CSTR("Version: %s"), g_scm_tag_str); 7039 ImGui::NewLine(); 7040 ImGui::TextWrapped( 7041 "%s", FSUI_CSTR("Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title)")); 7042 ImGui::NewLine(); 7043 ImGui::TextWrapped( 7044 "%s", FSUI_CSTR("\"PlayStation\" and \"PSX\" are registered trademarks of Sony Interactive Entertainment Europe " 7045 "Limited. This software is not affiliated in any way with Sony Interactive Entertainment.")); 7046 7047 ImGui::NewLine(); 7048 7049 BeginMenuButtons(); 7050 if (ActiveButton(FSUI_ICONSTR(ICON_FA_GLOBE, "GitHub Repository"), false)) 7051 ExitFullscreenAndOpenURL("https://github.com/stenzek/duckstation/"); 7052 if (ActiveButton(FSUI_ICONSTR(ICON_FA_COMMENT, "Discord Server"), false)) 7053 ExitFullscreenAndOpenURL("https://www.duckstation.org/discord.html"); 7054 if (ActiveButton(FSUI_ICONSTR(ICON_FA_PEOPLE_CARRY, "Contributor List"), false)) 7055 ExitFullscreenAndOpenURL("https://github.com/stenzek/duckstation/blob/master/CONTRIBUTORS.md"); 7056 7057 if (ActiveButton(FSUI_ICONSTR(ICON_FA_WINDOW_CLOSE, "Close"), false) || WantsToCloseMenu()) 7058 { 7059 ImGui::CloseCurrentPopup(); 7060 s_about_window_open = false; 7061 } 7062 else 7063 { 7064 SetStandardSelectionFooterText(true); 7065 } 7066 7067 EndMenuButtons(); 7068 7069 ImGui::EndPopup(); 7070 } 7071 7072 ImGui::PopStyleVar(2); 7073 ImGui::PopFont(); 7074 } 7075 7076 void FullscreenUI::OpenAchievementsWindow() 7077 { 7078 if (!Achievements::IsActive()) 7079 { 7080 Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."), 7081 Host::OSD_INFO_DURATION); 7082 return; 7083 } 7084 7085 if (!System::IsValid() || !Initialize()) 7086 return; 7087 7088 if (!Achievements::HasAchievements() || !Achievements::PrepareAchievementsWindow()) 7089 { 7090 ShowToast(std::string(), FSUI_STR("This game has no achievements.")); 7091 return; 7092 } 7093 7094 if (s_current_main_window != MainWindowType::PauseMenu) 7095 { 7096 PauseForMenuOpen(false); 7097 ForceKeyNavEnabled(); 7098 } 7099 7100 s_current_main_window = MainWindowType::Achievements; 7101 QueueResetFocus(FocusResetType::ViewChanged); 7102 FixStateIfPaused(); 7103 } 7104 7105 bool FullscreenUI::IsAchievementsWindowOpen() 7106 { 7107 return (s_current_main_window == MainWindowType::Achievements); 7108 } 7109 7110 void FullscreenUI::OpenLeaderboardsWindow() 7111 { 7112 if (!Achievements::IsActive()) 7113 { 7114 Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."), 7115 Host::OSD_INFO_DURATION); 7116 return; 7117 } 7118 7119 if (!System::IsValid() || !Initialize()) 7120 return; 7121 7122 if (!Achievements::HasLeaderboards() || !Achievements::PrepareLeaderboardsWindow()) 7123 { 7124 ShowToast(std::string(), FSUI_STR("This game has no leaderboards.")); 7125 return; 7126 } 7127 7128 if (s_current_main_window != MainWindowType::PauseMenu) 7129 { 7130 PauseForMenuOpen(false); 7131 ForceKeyNavEnabled(); 7132 } 7133 7134 s_current_main_window = MainWindowType::Leaderboards; 7135 QueueResetFocus(FocusResetType::ViewChanged); 7136 FixStateIfPaused(); 7137 } 7138 7139 bool FullscreenUI::IsLeaderboardsWindowOpen() 7140 { 7141 return (s_current_main_window == MainWindowType::Leaderboards); 7142 } 7143 7144 #endif // __ANDROID__ 7145 7146 ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 7147 // Translation String Area 7148 // To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top 7149 // of the file, then preprocess and generate a bunch of noops here to define the strings. Sadly that means 7150 // the view in Linguist is gonna suck, but you can search the file for the string for more context. 7151 ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 7152 7153 #if 0 7154 // TRANSLATION-STRING-AREA-BEGIN 7155 TRANSLATE_NOOP("FullscreenUI", "%.2f Seconds"); 7156 TRANSLATE_NOOP("FullscreenUI", "%d Frames"); 7157 TRANSLATE_NOOP("FullscreenUI", "%d ms"); 7158 TRANSLATE_NOOP("FullscreenUI", "%d sectors"); 7159 TRANSLATE_NOOP("FullscreenUI", "-"); 7160 TRANSLATE_NOOP("FullscreenUI", "1 Frame"); 7161 TRANSLATE_NOOP("FullscreenUI", "10 Frames"); 7162 TRANSLATE_NOOP("FullscreenUI", "100% [60 FPS (NTSC) / 50 FPS (PAL)]"); 7163 TRANSLATE_NOOP("FullscreenUI", "1000% [600 FPS (NTSC) / 500 FPS (PAL)]"); 7164 TRANSLATE_NOOP("FullscreenUI", "10x"); 7165 TRANSLATE_NOOP("FullscreenUI", "10x (20x Speed)"); 7166 TRANSLATE_NOOP("FullscreenUI", "11x"); 7167 TRANSLATE_NOOP("FullscreenUI", "125% [75 FPS (NTSC) / 62 FPS (PAL)]"); 7168 TRANSLATE_NOOP("FullscreenUI", "12x"); 7169 TRANSLATE_NOOP("FullscreenUI", "13x"); 7170 TRANSLATE_NOOP("FullscreenUI", "14x"); 7171 TRANSLATE_NOOP("FullscreenUI", "150% [90 FPS (NTSC) / 75 FPS (PAL)]"); 7172 TRANSLATE_NOOP("FullscreenUI", "15x"); 7173 TRANSLATE_NOOP("FullscreenUI", "16x"); 7174 TRANSLATE_NOOP("FullscreenUI", "175% [105 FPS (NTSC) / 87 FPS (PAL)]"); 7175 TRANSLATE_NOOP("FullscreenUI", "1x"); 7176 TRANSLATE_NOOP("FullscreenUI", "2 Frames"); 7177 TRANSLATE_NOOP("FullscreenUI", "20% [12 FPS (NTSC) / 10 FPS (PAL)]"); 7178 TRANSLATE_NOOP("FullscreenUI", "200% [120 FPS (NTSC) / 100 FPS (PAL)]"); 7179 TRANSLATE_NOOP("FullscreenUI", "250% [150 FPS (NTSC) / 125 FPS (PAL)]"); 7180 TRANSLATE_NOOP("FullscreenUI", "2x"); 7181 TRANSLATE_NOOP("FullscreenUI", "2x (Quad Speed)"); 7182 TRANSLATE_NOOP("FullscreenUI", "3 Frames"); 7183 TRANSLATE_NOOP("FullscreenUI", "30% [18 FPS (NTSC) / 15 FPS (PAL)]"); 7184 TRANSLATE_NOOP("FullscreenUI", "300% [180 FPS (NTSC) / 150 FPS (PAL)]"); 7185 TRANSLATE_NOOP("FullscreenUI", "350% [210 FPS (NTSC) / 175 FPS (PAL)]"); 7186 TRANSLATE_NOOP("FullscreenUI", "3x"); 7187 TRANSLATE_NOOP("FullscreenUI", "3x (6x Speed)"); 7188 TRANSLATE_NOOP("FullscreenUI", "3x (for 720p)"); 7189 TRANSLATE_NOOP("FullscreenUI", "4 Frames"); 7190 TRANSLATE_NOOP("FullscreenUI", "40% [24 FPS (NTSC) / 20 FPS (PAL)]"); 7191 TRANSLATE_NOOP("FullscreenUI", "400% [240 FPS (NTSC) / 200 FPS (PAL)]"); 7192 TRANSLATE_NOOP("FullscreenUI", "450% [270 FPS (NTSC) / 225 FPS (PAL)]"); 7193 TRANSLATE_NOOP("FullscreenUI", "4x"); 7194 TRANSLATE_NOOP("FullscreenUI", "4x (8x Speed)"); 7195 TRANSLATE_NOOP("FullscreenUI", "5 Frames"); 7196 TRANSLATE_NOOP("FullscreenUI", "50% [30 FPS (NTSC) / 25 FPS (PAL)]"); 7197 TRANSLATE_NOOP("FullscreenUI", "500% [300 FPS (NTSC) / 250 FPS (PAL)]"); 7198 TRANSLATE_NOOP("FullscreenUI", "5x"); 7199 TRANSLATE_NOOP("FullscreenUI", "5x (10x Speed)"); 7200 TRANSLATE_NOOP("FullscreenUI", "5x (for 1080p)"); 7201 TRANSLATE_NOOP("FullscreenUI", "6 Frames"); 7202 TRANSLATE_NOOP("FullscreenUI", "60% [36 FPS (NTSC) / 30 FPS (PAL)]"); 7203 TRANSLATE_NOOP("FullscreenUI", "600% [360 FPS (NTSC) / 300 FPS (PAL)]"); 7204 TRANSLATE_NOOP("FullscreenUI", "6x"); 7205 TRANSLATE_NOOP("FullscreenUI", "6x (12x Speed)"); 7206 TRANSLATE_NOOP("FullscreenUI", "6x (for 1440p)"); 7207 TRANSLATE_NOOP("FullscreenUI", "7 Frames"); 7208 TRANSLATE_NOOP("FullscreenUI", "70% [42 FPS (NTSC) / 35 FPS (PAL)]"); 7209 TRANSLATE_NOOP("FullscreenUI", "700% [420 FPS (NTSC) / 350 FPS (PAL)]"); 7210 TRANSLATE_NOOP("FullscreenUI", "7x"); 7211 TRANSLATE_NOOP("FullscreenUI", "7x (14x Speed)"); 7212 TRANSLATE_NOOP("FullscreenUI", "8 Frames"); 7213 TRANSLATE_NOOP("FullscreenUI", "80% [48 FPS (NTSC) / 40 FPS (PAL)]"); 7214 TRANSLATE_NOOP("FullscreenUI", "800% [480 FPS (NTSC) / 400 FPS (PAL)]"); 7215 TRANSLATE_NOOP("FullscreenUI", "8x"); 7216 TRANSLATE_NOOP("FullscreenUI", "8x (16x Speed)"); 7217 TRANSLATE_NOOP("FullscreenUI", "9 Frames"); 7218 TRANSLATE_NOOP("FullscreenUI", "90% [54 FPS (NTSC) / 45 FPS (PAL)]"); 7219 TRANSLATE_NOOP("FullscreenUI", "900% [540 FPS (NTSC) / 450 FPS (PAL)]"); 7220 TRANSLATE_NOOP("FullscreenUI", "9x"); 7221 TRANSLATE_NOOP("FullscreenUI", "9x (18x Speed)"); 7222 TRANSLATE_NOOP("FullscreenUI", "9x (for 4K)"); 7223 TRANSLATE_NOOP("FullscreenUI", "A resume save state created at %s was found.\n\nDo you want to load this save and continue?"); 7224 TRANSLATE_NOOP("FullscreenUI", "About"); 7225 TRANSLATE_NOOP("FullscreenUI", "About DuckStation"); 7226 TRANSLATE_NOOP("FullscreenUI", "Account"); 7227 TRANSLATE_NOOP("FullscreenUI", "Accurate Blending"); 7228 TRANSLATE_NOOP("FullscreenUI", "Achievement Notifications"); 7229 TRANSLATE_NOOP("FullscreenUI", "Achievements"); 7230 TRANSLATE_NOOP("FullscreenUI", "Achievements Settings"); 7231 TRANSLATE_NOOP("FullscreenUI", "Achievements are not enabled."); 7232 TRANSLATE_NOOP("FullscreenUI", "Add Search Directory"); 7233 TRANSLATE_NOOP("FullscreenUI", "Add Shader"); 7234 TRANSLATE_NOOP("FullscreenUI", "Adds a new directory to the game search list."); 7235 TRANSLATE_NOOP("FullscreenUI", "Adds a new shader to the chain."); 7236 TRANSLATE_NOOP("FullscreenUI", "Adds additional precision to PGXP data post-projection. May improve visuals in some games."); 7237 TRANSLATE_NOOP("FullscreenUI", "Adjusts the emulation speed so the console's refresh rate matches the host when VSync is enabled."); 7238 TRANSLATE_NOOP("FullscreenUI", "Advanced"); 7239 TRANSLATE_NOOP("FullscreenUI", "Advanced Settings"); 7240 TRANSLATE_NOOP("FullscreenUI", "All Time: {}"); 7241 TRANSLATE_NOOP("FullscreenUI", "Allow Booting Without SBI File"); 7242 TRANSLATE_NOOP("FullscreenUI", "Allows loading protected games without subchannel information."); 7243 TRANSLATE_NOOP("FullscreenUI", "An error occurred while deleting empty game settings:\n{}"); 7244 TRANSLATE_NOOP("FullscreenUI", "An error occurred while saving game settings:\n{}"); 7245 TRANSLATE_NOOP("FullscreenUI", "Applies modern dithering techniques to further smooth out gradients when true color is enabled."); 7246 TRANSLATE_NOOP("FullscreenUI", "Apply Image Patches"); 7247 TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to clear the current post-processing chain? All configuration will be lost."); 7248 TRANSLATE_NOOP("FullscreenUI", "Aspect Ratio"); 7249 TRANSLATE_NOOP("FullscreenUI", "Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization behavior, filling in gaps introduced by upscaling."); 7250 TRANSLATE_NOOP("FullscreenUI", "Attempts to map the selected port to a chosen controller."); 7251 TRANSLATE_NOOP("FullscreenUI", "Audio Backend"); 7252 TRANSLATE_NOOP("FullscreenUI", "Audio Control"); 7253 TRANSLATE_NOOP("FullscreenUI", "Audio Settings"); 7254 TRANSLATE_NOOP("FullscreenUI", "Auto-Detect"); 7255 TRANSLATE_NOOP("FullscreenUI", "Automatic Mapping"); 7256 TRANSLATE_NOOP("FullscreenUI", "Automatic based on window size"); 7257 TRANSLATE_NOOP("FullscreenUI", "Automatic mapping completed for {}."); 7258 TRANSLATE_NOOP("FullscreenUI", "Automatic mapping failed for {}."); 7259 TRANSLATE_NOOP("FullscreenUI", "Automatic mapping failed, no devices are available."); 7260 TRANSLATE_NOOP("FullscreenUI", "Automatically applies patches to disc images when they are present, currently only PPF is supported."); 7261 TRANSLATE_NOOP("FullscreenUI", "Automatically loads and applies cheats on game start. Cheats can break games and saves."); 7262 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."); 7263 TRANSLATE_NOOP("FullscreenUI", "Automatically switches to fullscreen mode when the program is started."); 7264 TRANSLATE_NOOP("FullscreenUI", "Avoids calls to C++ code, significantly speeding up the recompiler."); 7265 TRANSLATE_NOOP("FullscreenUI", "BIOS Directory"); 7266 TRANSLATE_NOOP("FullscreenUI", "BIOS Selection"); 7267 TRANSLATE_NOOP("FullscreenUI", "BIOS Settings"); 7268 TRANSLATE_NOOP("FullscreenUI", "BIOS for {}"); 7269 TRANSLATE_NOOP("FullscreenUI", "BIOS to use when emulating {} consoles."); 7270 TRANSLATE_NOOP("FullscreenUI", "Back"); 7271 TRANSLATE_NOOP("FullscreenUI", "Back To Pause Menu"); 7272 TRANSLATE_NOOP("FullscreenUI", "Backend Settings"); 7273 TRANSLATE_NOOP("FullscreenUI", "Behavior"); 7274 TRANSLATE_NOOP("FullscreenUI", "Borderless Fullscreen"); 7275 TRANSLATE_NOOP("FullscreenUI", "Buffer Size"); 7276 TRANSLATE_NOOP("FullscreenUI", "CD-ROM Emulation"); 7277 TRANSLATE_NOOP("FullscreenUI", "CPU Emulation"); 7278 TRANSLATE_NOOP("FullscreenUI", "CPU Mode"); 7279 TRANSLATE_NOOP("FullscreenUI", "Cancel"); 7280 TRANSLATE_NOOP("FullscreenUI", "Capture"); 7281 TRANSLATE_NOOP("FullscreenUI", "Change Disc"); 7282 TRANSLATE_NOOP("FullscreenUI", "Change Page"); 7283 TRANSLATE_NOOP("FullscreenUI", "Change Selection"); 7284 TRANSLATE_NOOP("FullscreenUI", "Change View"); 7285 TRANSLATE_NOOP("FullscreenUI", "Changes settings for the application."); 7286 TRANSLATE_NOOP("FullscreenUI", "Changes the aspect ratio used to display the console's output to the screen."); 7287 TRANSLATE_NOOP("FullscreenUI", "Cheat List"); 7288 TRANSLATE_NOOP("FullscreenUI", "Chooses the backend to use for rendering the console/game visuals."); 7289 TRANSLATE_NOOP("FullscreenUI", "Chooses the language used for UI elements."); 7290 TRANSLATE_NOOP("FullscreenUI", "Clean Boot"); 7291 TRANSLATE_NOOP("FullscreenUI", "Clear Settings"); 7292 TRANSLATE_NOOP("FullscreenUI", "Clear Shaders"); 7293 TRANSLATE_NOOP("FullscreenUI", "Clears a shader from the chain."); 7294 TRANSLATE_NOOP("FullscreenUI", "Clears all settings set for this game."); 7295 TRANSLATE_NOOP("FullscreenUI", "Clears the mask/transparency bit in VRAM write dumps."); 7296 TRANSLATE_NOOP("FullscreenUI", "Close"); 7297 TRANSLATE_NOOP("FullscreenUI", "Close Game"); 7298 TRANSLATE_NOOP("FullscreenUI", "Close Menu"); 7299 TRANSLATE_NOOP("FullscreenUI", "Compatibility Rating"); 7300 TRANSLATE_NOOP("FullscreenUI", "Compatibility: "); 7301 TRANSLATE_NOOP("FullscreenUI", "Completely exits the application, returning you to your desktop."); 7302 TRANSLATE_NOOP("FullscreenUI", "Configuration"); 7303 TRANSLATE_NOOP("FullscreenUI", "Confirm Power Off"); 7304 TRANSLATE_NOOP("FullscreenUI", "Console Settings"); 7305 TRANSLATE_NOOP("FullscreenUI", "Contributor List"); 7306 TRANSLATE_NOOP("FullscreenUI", "Controller Port {}"); 7307 TRANSLATE_NOOP("FullscreenUI", "Controller Port {} Macros"); 7308 TRANSLATE_NOOP("FullscreenUI", "Controller Port {} Settings"); 7309 TRANSLATE_NOOP("FullscreenUI", "Controller Port {}{}"); 7310 TRANSLATE_NOOP("FullscreenUI", "Controller Port {}{} Macros"); 7311 TRANSLATE_NOOP("FullscreenUI", "Controller Port {}{} Settings"); 7312 TRANSLATE_NOOP("FullscreenUI", "Controller Settings"); 7313 TRANSLATE_NOOP("FullscreenUI", "Controller Type"); 7314 TRANSLATE_NOOP("FullscreenUI", "Controller settings reset to default."); 7315 TRANSLATE_NOOP("FullscreenUI", "Controls"); 7316 TRANSLATE_NOOP("FullscreenUI", "Controls the volume of the audio played on the host when fast forwarding."); 7317 TRANSLATE_NOOP("FullscreenUI", "Controls the volume of the audio played on the host."); 7318 TRANSLATE_NOOP("FullscreenUI", "Copies the current global settings to this game."); 7319 TRANSLATE_NOOP("FullscreenUI", "Copies the global controller configuration to this game."); 7320 TRANSLATE_NOOP("FullscreenUI", "Copy Global Settings"); 7321 TRANSLATE_NOOP("FullscreenUI", "Copy Settings"); 7322 TRANSLATE_NOOP("FullscreenUI", "Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it."); 7323 TRANSLATE_NOOP("FullscreenUI", "Cover Settings"); 7324 TRANSLATE_NOOP("FullscreenUI", "Covers Directory"); 7325 TRANSLATE_NOOP("FullscreenUI", "Create"); 7326 TRANSLATE_NOOP("FullscreenUI", "Create New..."); 7327 TRANSLATE_NOOP("FullscreenUI", "Create Save State Backups"); 7328 TRANSLATE_NOOP("FullscreenUI", "Crop Mode"); 7329 TRANSLATE_NOOP("FullscreenUI", "Culling Correction"); 7330 TRANSLATE_NOOP("FullscreenUI", "Current Game"); 7331 TRANSLATE_NOOP("FullscreenUI", "Debugging Settings"); 7332 TRANSLATE_NOOP("FullscreenUI", "Default"); 7333 TRANSLATE_NOOP("FullscreenUI", "Default Boot"); 7334 TRANSLATE_NOOP("FullscreenUI", "Default View"); 7335 TRANSLATE_NOOP("FullscreenUI", "Default: Disabled"); 7336 TRANSLATE_NOOP("FullscreenUI", "Default: Enabled"); 7337 TRANSLATE_NOOP("FullscreenUI", "Deinterlacing Mode"); 7338 TRANSLATE_NOOP("FullscreenUI", "Delete Save"); 7339 TRANSLATE_NOOP("FullscreenUI", "Delete State"); 7340 TRANSLATE_NOOP("FullscreenUI", "Depth Clear Threshold"); 7341 TRANSLATE_NOOP("FullscreenUI", "Desktop Mode"); 7342 TRANSLATE_NOOP("FullscreenUI", "Details"); 7343 TRANSLATE_NOOP("FullscreenUI", "Details unavailable for game not scanned in game list."); 7344 TRANSLATE_NOOP("FullscreenUI", "Determines how audio is expanded from stereo to surround for supported games."); 7345 TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitor are."); 7346 TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers."); 7347 TRANSLATE_NOOP("FullscreenUI", "Determines how much of the area typically not visible on a consumer TV set to crop/hide."); 7348 TRANSLATE_NOOP("FullscreenUI", "Determines how the emulated CPU executes instructions."); 7349 TRANSLATE_NOOP("FullscreenUI", "Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."); 7350 TRANSLATE_NOOP("FullscreenUI", "Determines quality of audio when not running at 100% speed."); 7351 TRANSLATE_NOOP("FullscreenUI", "Determines that field that the game list will be sorted by."); 7352 TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before being pulled by the host API."); 7353 TRANSLATE_NOOP("FullscreenUI", "Determines the emulated hardware type."); 7354 TRANSLATE_NOOP("FullscreenUI", "Determines the format that screenshots will be saved/compressed with."); 7355 TRANSLATE_NOOP("FullscreenUI", "Determines the position on the screen when black borders must be added."); 7356 TRANSLATE_NOOP("FullscreenUI", "Determines the rotation of the simulated TV screen."); 7357 TRANSLATE_NOOP("FullscreenUI", "Determines the size of screenshots created by DuckStation."); 7358 TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed."); 7359 TRANSLATE_NOOP("FullscreenUI", "Determines which algorithm is used to convert interlaced frames to progressive for display on your system."); 7360 TRANSLATE_NOOP("FullscreenUI", "Device Settings"); 7361 TRANSLATE_NOOP("FullscreenUI", "Disable All Enhancements"); 7362 TRANSLATE_NOOP("FullscreenUI", "Disable Interlacing"); 7363 TRANSLATE_NOOP("FullscreenUI", "Disable Mailbox Presentation"); 7364 TRANSLATE_NOOP("FullscreenUI", "Disable Subdirectory Scanning"); 7365 TRANSLATE_NOOP("FullscreenUI", "Disable on 2D Polygons"); 7366 TRANSLATE_NOOP("FullscreenUI", "Disabled"); 7367 TRANSLATE_NOOP("FullscreenUI", "Disables dithering and uses the full 8 bits per channel of color information."); 7368 TRANSLATE_NOOP("FullscreenUI", "Disables interlaced rendering and display in the GPU. Some games can render in 480p this way, but others will break."); 7369 TRANSLATE_NOOP("FullscreenUI", "Disc {} | {}"); 7370 TRANSLATE_NOOP("FullscreenUI", "Discord Server"); 7371 TRANSLATE_NOOP("FullscreenUI", "Display Settings"); 7372 TRANSLATE_NOOP("FullscreenUI", "Displays popup messages on events such as achievement unlocks and leaderboard submissions."); 7373 TRANSLATE_NOOP("FullscreenUI", "Displays popup messages when starting, submitting, or failing a leaderboard challenge."); 7374 TRANSLATE_NOOP("FullscreenUI", "Double-Click Toggles Fullscreen"); 7375 TRANSLATE_NOOP("FullscreenUI", "Download Covers"); 7376 TRANSLATE_NOOP("FullscreenUI", "Downloads covers from a user-specified URL template."); 7377 TRANSLATE_NOOP("FullscreenUI", "Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games."); 7378 TRANSLATE_NOOP("FullscreenUI", "Downsampling"); 7379 TRANSLATE_NOOP("FullscreenUI", "Downsampling Display Scale"); 7380 TRANSLATE_NOOP("FullscreenUI", "Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title)"); 7381 TRANSLATE_NOOP("FullscreenUI", "DuckStation is a free and open-source simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability."); 7382 TRANSLATE_NOOP("FullscreenUI", "Dump Replaceable VRAM Writes"); 7383 TRANSLATE_NOOP("FullscreenUI", "Emulation Settings"); 7384 TRANSLATE_NOOP("FullscreenUI", "Emulation Speed"); 7385 TRANSLATE_NOOP("FullscreenUI", "Enable 8MB RAM"); 7386 TRANSLATE_NOOP("FullscreenUI", "Enable Achievements"); 7387 TRANSLATE_NOOP("FullscreenUI", "Enable Cheats"); 7388 TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence"); 7389 TRANSLATE_NOOP("FullscreenUI", "Enable Fast Boot"); 7390 TRANSLATE_NOOP("FullscreenUI", "Enable In-Game Overlays"); 7391 TRANSLATE_NOOP("FullscreenUI", "Enable Overclocking"); 7392 TRANSLATE_NOOP("FullscreenUI", "Enable Post Processing"); 7393 TRANSLATE_NOOP("FullscreenUI", "Enable Recompiler Block Linking"); 7394 TRANSLATE_NOOP("FullscreenUI", "Enable Recompiler ICache"); 7395 TRANSLATE_NOOP("FullscreenUI", "Enable Recompiler Memory Exceptions"); 7396 TRANSLATE_NOOP("FullscreenUI", "Enable Region Check"); 7397 TRANSLATE_NOOP("FullscreenUI", "Enable Rewinding"); 7398 TRANSLATE_NOOP("FullscreenUI", "Enable SDL Input Source"); 7399 TRANSLATE_NOOP("FullscreenUI", "Enable Subdirectory Scanning"); 7400 TRANSLATE_NOOP("FullscreenUI", "Enable TTY Logging"); 7401 TRANSLATE_NOOP("FullscreenUI", "Enable VRAM Write Texture Replacement"); 7402 TRANSLATE_NOOP("FullscreenUI", "Enable XInput Input Source"); 7403 TRANSLATE_NOOP("FullscreenUI", "Enable debugging when supported by the host's renderer API. Only for developer use."); 7404 TRANSLATE_NOOP("FullscreenUI", "Enable/Disable the Player LED on DualSense controllers."); 7405 TRANSLATE_NOOP("FullscreenUI", "Enables alignment and bus exceptions. Not needed for any known games."); 7406 TRANSLATE_NOOP("FullscreenUI", "Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles."); 7407 TRANSLATE_NOOP("FullscreenUI", "Enables an additional three controller slots on each port. Not supported in all games."); 7408 TRANSLATE_NOOP("FullscreenUI", "Enables more precise frame pacing at the cost of battery life."); 7409 TRANSLATE_NOOP("FullscreenUI", "Enables the older, less accurate MDEC decoding routines. May be required for old replacement backgrounds to match/load."); 7410 TRANSLATE_NOOP("FullscreenUI", "Enables the replacement of background textures in supported games."); 7411 TRANSLATE_NOOP("FullscreenUI", "Encore Mode"); 7412 TRANSLATE_NOOP("FullscreenUI", "Ensures every frame generated is displayed for optimal pacing. Enable for variable refresh displays, such as GSync/FreeSync. Disable if you are having speed or sound issues."); 7413 TRANSLATE_NOOP("FullscreenUI", "Enter Value"); 7414 TRANSLATE_NOOP("FullscreenUI", "Enter the name of the input profile you wish to create."); 7415 TRANSLATE_NOOP("FullscreenUI", "Error"); 7416 TRANSLATE_NOOP("FullscreenUI", "Execution Mode"); 7417 TRANSLATE_NOOP("FullscreenUI", "Exit"); 7418 TRANSLATE_NOOP("FullscreenUI", "Exit And Save State"); 7419 TRANSLATE_NOOP("FullscreenUI", "Exit DuckStation"); 7420 TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving"); 7421 TRANSLATE_NOOP("FullscreenUI", "Exits Big Picture mode, returning to the desktop interface."); 7422 TRANSLATE_NOOP("FullscreenUI", "Expansion Mode"); 7423 TRANSLATE_NOOP("FullscreenUI", "FMV Chroma Smoothing"); 7424 TRANSLATE_NOOP("FullscreenUI", "Failed to copy text to clipboard."); 7425 TRANSLATE_NOOP("FullscreenUI", "Failed to delete save state."); 7426 TRANSLATE_NOOP("FullscreenUI", "Failed to delete {}."); 7427 TRANSLATE_NOOP("FullscreenUI", "Failed to load '{}'."); 7428 TRANSLATE_NOOP("FullscreenUI", "Failed to load shader {}. It may be invalid.\nError was:"); 7429 TRANSLATE_NOOP("FullscreenUI", "Failed to save input profile '{}'."); 7430 TRANSLATE_NOOP("FullscreenUI", "Fast Boot"); 7431 TRANSLATE_NOOP("FullscreenUI", "Fast Forward Speed"); 7432 TRANSLATE_NOOP("FullscreenUI", "Fast Forward Volume"); 7433 TRANSLATE_NOOP("FullscreenUI", "File Size"); 7434 TRANSLATE_NOOP("FullscreenUI", "File Size: %.2f MB"); 7435 TRANSLATE_NOOP("FullscreenUI", "File Title"); 7436 TRANSLATE_NOOP("FullscreenUI", "Force 4:3 For FMVs"); 7437 TRANSLATE_NOOP("FullscreenUI", "Force NTSC Timings"); 7438 TRANSLATE_NOOP("FullscreenUI", "Forces PAL games to run at NTSC timings, i.e. 60hz. Some PAL games will run at their \"normal\" speeds, while others will break."); 7439 TRANSLATE_NOOP("FullscreenUI", "Forces a full rescan of all games previously identified."); 7440 TRANSLATE_NOOP("FullscreenUI", "Forces blending to be done in the shader at 16-bit precision, when not using true color. Non-trivial performance impact, and unnecessary for most games."); 7441 TRANSLATE_NOOP("FullscreenUI", "Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing."); 7442 TRANSLATE_NOOP("FullscreenUI", "Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games."); 7443 TRANSLATE_NOOP("FullscreenUI", "Frame Time Buffer"); 7444 TRANSLATE_NOOP("FullscreenUI", "From File..."); 7445 TRANSLATE_NOOP("FullscreenUI", "Fullscreen Resolution"); 7446 TRANSLATE_NOOP("FullscreenUI", "GPU Adapter"); 7447 TRANSLATE_NOOP("FullscreenUI", "GPU Renderer"); 7448 TRANSLATE_NOOP("FullscreenUI", "GPU adapter will be applied after restarting."); 7449 TRANSLATE_NOOP("FullscreenUI", "Game Grid"); 7450 TRANSLATE_NOOP("FullscreenUI", "Game List"); 7451 TRANSLATE_NOOP("FullscreenUI", "Game List Settings"); 7452 TRANSLATE_NOOP("FullscreenUI", "Game Properties"); 7453 TRANSLATE_NOOP("FullscreenUI", "Game Quick Save"); 7454 TRANSLATE_NOOP("FullscreenUI", "Game Slot {0}##game_slot_{0}"); 7455 TRANSLATE_NOOP("FullscreenUI", "Game compatibility rating copied to clipboard."); 7456 TRANSLATE_NOOP("FullscreenUI", "Game not loaded or no RetroAchievements available."); 7457 TRANSLATE_NOOP("FullscreenUI", "Game path copied to clipboard."); 7458 TRANSLATE_NOOP("FullscreenUI", "Game region copied to clipboard."); 7459 TRANSLATE_NOOP("FullscreenUI", "Game serial copied to clipboard."); 7460 TRANSLATE_NOOP("FullscreenUI", "Game settings have been cleared for '{}'."); 7461 TRANSLATE_NOOP("FullscreenUI", "Game settings initialized with global settings for '{}'."); 7462 TRANSLATE_NOOP("FullscreenUI", "Game title copied to clipboard."); 7463 TRANSLATE_NOOP("FullscreenUI", "Game type copied to clipboard."); 7464 TRANSLATE_NOOP("FullscreenUI", "Game: {} ({})"); 7465 TRANSLATE_NOOP("FullscreenUI", "Genre: %s"); 7466 TRANSLATE_NOOP("FullscreenUI", "Geometry Tolerance"); 7467 TRANSLATE_NOOP("FullscreenUI", "GitHub Repository"); 7468 TRANSLATE_NOOP("FullscreenUI", "Global Slot {0} - {1}##global_slot_{0}"); 7469 TRANSLATE_NOOP("FullscreenUI", "Global Slot {0}##global_slot_{0}"); 7470 TRANSLATE_NOOP("FullscreenUI", "Graphics Settings"); 7471 TRANSLATE_NOOP("FullscreenUI", "Hardcore Mode"); 7472 TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will be enabled on next game restart."); 7473 TRANSLATE_NOOP("FullscreenUI", "Hide Cursor In Fullscreen"); 7474 TRANSLATE_NOOP("FullscreenUI", "Hides the mouse pointer/cursor when the emulator is in fullscreen mode."); 7475 TRANSLATE_NOOP("FullscreenUI", "Hotkey Settings"); 7476 TRANSLATE_NOOP("FullscreenUI", "How many saves will be kept for rewinding. Higher values have greater memory requirements."); 7477 TRANSLATE_NOOP("FullscreenUI", "How often a rewind state will be created. Higher frequencies have greater system requirements."); 7478 TRANSLATE_NOOP("FullscreenUI", "Identifies any new files added to the game directories."); 7479 TRANSLATE_NOOP("FullscreenUI", "If not enabled, the current post processing chain will be ignored."); 7480 TRANSLATE_NOOP("FullscreenUI", "Increase Timer Resolution"); 7481 TRANSLATE_NOOP("FullscreenUI", "Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."); 7482 TRANSLATE_NOOP("FullscreenUI", "Increases the precision of polygon culling, reducing the number of holes in geometry."); 7483 TRANSLATE_NOOP("FullscreenUI", "Infinite/Instantaneous"); 7484 TRANSLATE_NOOP("FullscreenUI", "Inhibit Screensaver"); 7485 TRANSLATE_NOOP("FullscreenUI", "Input Sources"); 7486 TRANSLATE_NOOP("FullscreenUI", "Input profile '{}' loaded."); 7487 TRANSLATE_NOOP("FullscreenUI", "Input profile '{}' saved."); 7488 TRANSLATE_NOOP("FullscreenUI", "Integration"); 7489 TRANSLATE_NOOP("FullscreenUI", "Interface Settings"); 7490 TRANSLATE_NOOP("FullscreenUI", "Internal Resolution"); 7491 TRANSLATE_NOOP("FullscreenUI", "Last Played"); 7492 TRANSLATE_NOOP("FullscreenUI", "Last Played: %s"); 7493 TRANSLATE_NOOP("FullscreenUI", "Latency Control"); 7494 TRANSLATE_NOOP("FullscreenUI", "Launch Options"); 7495 TRANSLATE_NOOP("FullscreenUI", "Launch a game by selecting a file/disc image."); 7496 TRANSLATE_NOOP("FullscreenUI", "Launch a game from a file, disc, or starts the console without any disc inserted."); 7497 TRANSLATE_NOOP("FullscreenUI", "Launch a game from images scanned from your game directories."); 7498 TRANSLATE_NOOP("FullscreenUI", "Leaderboard Notifications"); 7499 TRANSLATE_NOOP("FullscreenUI", "Leaderboards"); 7500 TRANSLATE_NOOP("FullscreenUI", "Leaderboards are not enabled."); 7501 TRANSLATE_NOOP("FullscreenUI", "Line Detection"); 7502 TRANSLATE_NOOP("FullscreenUI", "List Settings"); 7503 TRANSLATE_NOOP("FullscreenUI", "Load Devices From Save States"); 7504 TRANSLATE_NOOP("FullscreenUI", "Load Global State"); 7505 TRANSLATE_NOOP("FullscreenUI", "Load Profile"); 7506 TRANSLATE_NOOP("FullscreenUI", "Load Resume State"); 7507 TRANSLATE_NOOP("FullscreenUI", "Load State"); 7508 TRANSLATE_NOOP("FullscreenUI", "Loads all replacement texture to RAM, reducing stuttering at runtime."); 7509 TRANSLATE_NOOP("FullscreenUI", "Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay."); 7510 TRANSLATE_NOOP("FullscreenUI", "Log Level"); 7511 TRANSLATE_NOOP("FullscreenUI", "Log To Debug Console"); 7512 TRANSLATE_NOOP("FullscreenUI", "Log To File"); 7513 TRANSLATE_NOOP("FullscreenUI", "Log To System Console"); 7514 TRANSLATE_NOOP("FullscreenUI", "Logging"); 7515 TRANSLATE_NOOP("FullscreenUI", "Logging Settings"); 7516 TRANSLATE_NOOP("FullscreenUI", "Login"); 7517 TRANSLATE_NOOP("FullscreenUI", "Login token generated on {}"); 7518 TRANSLATE_NOOP("FullscreenUI", "Logout"); 7519 TRANSLATE_NOOP("FullscreenUI", "Logs BIOS calls to printf(). Not all games contain debugging messages."); 7520 TRANSLATE_NOOP("FullscreenUI", "Logs in to RetroAchievements."); 7521 TRANSLATE_NOOP("FullscreenUI", "Logs messages to duckstation.log in the user directory."); 7522 TRANSLATE_NOOP("FullscreenUI", "Logs messages to the console window."); 7523 TRANSLATE_NOOP("FullscreenUI", "Logs messages to the debug console where supported."); 7524 TRANSLATE_NOOP("FullscreenUI", "Logs out of RetroAchievements."); 7525 TRANSLATE_NOOP("FullscreenUI", "Macro {} Buttons"); 7526 TRANSLATE_NOOP("FullscreenUI", "Macro {} Frequency"); 7527 TRANSLATE_NOOP("FullscreenUI", "Macro {} Press To Toggle"); 7528 TRANSLATE_NOOP("FullscreenUI", "Macro {} Trigger"); 7529 TRANSLATE_NOOP("FullscreenUI", "Makes games run closer to their console framerate, at a small cost to performance."); 7530 TRANSLATE_NOOP("FullscreenUI", "Memory Card Busy"); 7531 TRANSLATE_NOOP("FullscreenUI", "Memory Card Directory"); 7532 TRANSLATE_NOOP("FullscreenUI", "Memory Card Port {}"); 7533 TRANSLATE_NOOP("FullscreenUI", "Memory Card Settings"); 7534 TRANSLATE_NOOP("FullscreenUI", "Memory Card {} Type"); 7535 TRANSLATE_NOOP("FullscreenUI", "Merge Multi-Disc Games"); 7536 TRANSLATE_NOOP("FullscreenUI", "Merges multi-disc games into one item in the game list."); 7537 TRANSLATE_NOOP("FullscreenUI", "Minimal Output Latency"); 7538 TRANSLATE_NOOP("FullscreenUI", "Move Down"); 7539 TRANSLATE_NOOP("FullscreenUI", "Move Up"); 7540 TRANSLATE_NOOP("FullscreenUI", "Moves this shader higher in the chain, applying it earlier."); 7541 TRANSLATE_NOOP("FullscreenUI", "Moves this shader lower in the chain, applying it later."); 7542 TRANSLATE_NOOP("FullscreenUI", "Multitap"); 7543 TRANSLATE_NOOP("FullscreenUI", "Multitap Mode"); 7544 TRANSLATE_NOOP("FullscreenUI", "Mute All Sound"); 7545 TRANSLATE_NOOP("FullscreenUI", "Mute CD Audio"); 7546 TRANSLATE_NOOP("FullscreenUI", "Navigate"); 7547 TRANSLATE_NOOP("FullscreenUI", "No Binding"); 7548 TRANSLATE_NOOP("FullscreenUI", "No Game Selected"); 7549 TRANSLATE_NOOP("FullscreenUI", "No cheats found for {}."); 7550 TRANSLATE_NOOP("FullscreenUI", "No input profiles available."); 7551 TRANSLATE_NOOP("FullscreenUI", "No resume save state found."); 7552 TRANSLATE_NOOP("FullscreenUI", "No save present in this slot."); 7553 TRANSLATE_NOOP("FullscreenUI", "No save states found."); 7554 TRANSLATE_NOOP("FullscreenUI", "No, resume the game."); 7555 TRANSLATE_NOOP("FullscreenUI", "None (Double Speed)"); 7556 TRANSLATE_NOOP("FullscreenUI", "None (Normal Speed)"); 7557 TRANSLATE_NOOP("FullscreenUI", "Not Logged In"); 7558 TRANSLATE_NOOP("FullscreenUI", "Not Scanning Subdirectories"); 7559 TRANSLATE_NOOP("FullscreenUI", "OK"); 7560 TRANSLATE_NOOP("FullscreenUI", "OSD Scale"); 7561 TRANSLATE_NOOP("FullscreenUI", "On-Screen Display"); 7562 TRANSLATE_NOOP("FullscreenUI", "Open Containing Directory"); 7563 TRANSLATE_NOOP("FullscreenUI", "Open in File Browser"); 7564 TRANSLATE_NOOP("FullscreenUI", "Operations"); 7565 TRANSLATE_NOOP("FullscreenUI", "Optimal Frame Pacing"); 7566 TRANSLATE_NOOP("FullscreenUI", "Options"); 7567 TRANSLATE_NOOP("FullscreenUI", "Output Latency"); 7568 TRANSLATE_NOOP("FullscreenUI", "Output Volume"); 7569 TRANSLATE_NOOP("FullscreenUI", "Overclocking Percentage"); 7570 TRANSLATE_NOOP("FullscreenUI", "Overlays or replaces normal triangle drawing with a wireframe/line view."); 7571 TRANSLATE_NOOP("FullscreenUI", "PGXP (Precision Geometry Transform Pipeline)"); 7572 TRANSLATE_NOOP("FullscreenUI", "PGXP Depth Buffer"); 7573 TRANSLATE_NOOP("FullscreenUI", "PGXP Geometry Correction"); 7574 TRANSLATE_NOOP("FullscreenUI", "Parent Directory"); 7575 TRANSLATE_NOOP("FullscreenUI", "Patches"); 7576 TRANSLATE_NOOP("FullscreenUI", "Patches the BIOS to skip the boot animation. Safe to enable."); 7577 TRANSLATE_NOOP("FullscreenUI", "Path"); 7578 TRANSLATE_NOOP("FullscreenUI", "Pause On Controller Disconnection"); 7579 TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss"); 7580 TRANSLATE_NOOP("FullscreenUI", "Pause On Start"); 7581 TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a controller with bindings is disconnected."); 7582 TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a game is started."); 7583 TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back."); 7584 TRANSLATE_NOOP("FullscreenUI", "Per-Game Configuration"); 7585 TRANSLATE_NOOP("FullscreenUI", "Per-game controller configuration initialized with global settings."); 7586 TRANSLATE_NOOP("FullscreenUI", "Performance enhancement - jumps directly between blocks instead of returning to the dispatcher."); 7587 TRANSLATE_NOOP("FullscreenUI", "Perspective Correct Colors"); 7588 TRANSLATE_NOOP("FullscreenUI", "Perspective Correct Textures"); 7589 TRANSLATE_NOOP("FullscreenUI", "Plays sound effects for events such as achievement unlocks and leaderboard submissions."); 7590 TRANSLATE_NOOP("FullscreenUI", "Port {} Controller Type"); 7591 TRANSLATE_NOOP("FullscreenUI", "Post-Processing Settings"); 7592 TRANSLATE_NOOP("FullscreenUI", "Post-processing chain cleared."); 7593 TRANSLATE_NOOP("FullscreenUI", "Post-processing shaders reloaded."); 7594 TRANSLATE_NOOP("FullscreenUI", "Preload Images to RAM"); 7595 TRANSLATE_NOOP("FullscreenUI", "Preload Replacement Textures"); 7596 TRANSLATE_NOOP("FullscreenUI", "Presents frames on a background thread when fast forwarding or vsync is disabled."); 7597 TRANSLATE_NOOP("FullscreenUI", "Preserve Projection Precision"); 7598 TRANSLATE_NOOP("FullscreenUI", "Prevents the emulator from producing any audible sound."); 7599 TRANSLATE_NOOP("FullscreenUI", "Prevents the screen saver from activating and the host from sleeping while emulation is running."); 7600 TRANSLATE_NOOP("FullscreenUI", "Provides vibration and LED control support over Bluetooth."); 7601 TRANSLATE_NOOP("FullscreenUI", "Push a controller button or axis now."); 7602 TRANSLATE_NOOP("FullscreenUI", "Quick Save"); 7603 TRANSLATE_NOOP("FullscreenUI", "RAIntegration is being used instead of the built-in achievements implementation."); 7604 TRANSLATE_NOOP("FullscreenUI", "Read Speedup"); 7605 TRANSLATE_NOOP("FullscreenUI", "Readahead Sectors"); 7606 TRANSLATE_NOOP("FullscreenUI", "Recompiler Fast Memory Access"); 7607 TRANSLATE_NOOP("FullscreenUI", "Reduce Input Latency"); 7608 TRANSLATE_NOOP("FullscreenUI", "Reduces \"wobbly\" polygons by attempting to preserve the fractional component through memory transfers."); 7609 TRANSLATE_NOOP("FullscreenUI", "Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread."); 7610 TRANSLATE_NOOP("FullscreenUI", "Reduces input latency by delaying the start of frame until closer to the presentation time."); 7611 TRANSLATE_NOOP("FullscreenUI", "Reduces polygon Z-fighting through depth testing. Low compatibility with games."); 7612 TRANSLATE_NOOP("FullscreenUI", "Reduces the size of save states by compressing the data before saving."); 7613 TRANSLATE_NOOP("FullscreenUI", "Region"); 7614 TRANSLATE_NOOP("FullscreenUI", "Region: "); 7615 TRANSLATE_NOOP("FullscreenUI", "Release Date: %s"); 7616 TRANSLATE_NOOP("FullscreenUI", "Reload Shaders"); 7617 TRANSLATE_NOOP("FullscreenUI", "Reloads the shaders from disk, applying any changes."); 7618 TRANSLATE_NOOP("FullscreenUI", "Remove From Chain"); 7619 TRANSLATE_NOOP("FullscreenUI", "Remove From List"); 7620 TRANSLATE_NOOP("FullscreenUI", "Removed stage {} ({})."); 7621 TRANSLATE_NOOP("FullscreenUI", "Removes this shader from the chain."); 7622 TRANSLATE_NOOP("FullscreenUI", "Renames existing save states when saving to a backup file."); 7623 TRANSLATE_NOOP("FullscreenUI", "Rendering"); 7624 TRANSLATE_NOOP("FullscreenUI", "Replaces these settings with a previously saved input profile."); 7625 TRANSLATE_NOOP("FullscreenUI", "Rescan All Games"); 7626 TRANSLATE_NOOP("FullscreenUI", "Reset Memory Card Directory"); 7627 TRANSLATE_NOOP("FullscreenUI", "Reset Play Time"); 7628 TRANSLATE_NOOP("FullscreenUI", "Reset Settings"); 7629 TRANSLATE_NOOP("FullscreenUI", "Reset System"); 7630 TRANSLATE_NOOP("FullscreenUI", "Resets all configuration to defaults (including bindings)."); 7631 TRANSLATE_NOOP("FullscreenUI", "Resets memory card directory to default (user directory)."); 7632 TRANSLATE_NOOP("FullscreenUI", "Resolution change will be applied after restarting."); 7633 TRANSLATE_NOOP("FullscreenUI", "Restores the state of the system prior to the last state loaded."); 7634 TRANSLATE_NOOP("FullscreenUI", "Resume Game"); 7635 TRANSLATE_NOOP("FullscreenUI", "Resume Last Session"); 7636 TRANSLATE_NOOP("FullscreenUI", "Return To Game"); 7637 TRANSLATE_NOOP("FullscreenUI", "Return to desktop mode, or exit the application."); 7638 TRANSLATE_NOOP("FullscreenUI", "Return to the previous menu."); 7639 TRANSLATE_NOOP("FullscreenUI", "Reverses the game list sort order from the default (usually ascending to descending)."); 7640 TRANSLATE_NOOP("FullscreenUI", "Rewind Save Frequency"); 7641 TRANSLATE_NOOP("FullscreenUI", "Rewind Save Slots"); 7642 TRANSLATE_NOOP("FullscreenUI", "Rewind for {0} frames, lasting {1:.2f} seconds will require up to {2} MB of RAM and {3} MB of VRAM."); 7643 TRANSLATE_NOOP("FullscreenUI", "Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements."); 7644 TRANSLATE_NOOP("FullscreenUI", "Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements."); 7645 TRANSLATE_NOOP("FullscreenUI", "Rich presence inactive or unsupported."); 7646 TRANSLATE_NOOP("FullscreenUI", "Round Upscaled Texture Coordinates"); 7647 TRANSLATE_NOOP("FullscreenUI", "Rounds texture coordinates instead of flooring when upscaling. Can fix misaligned textures in some games, but break others, and is incompatible with texture filtering."); 7648 TRANSLATE_NOOP("FullscreenUI", "Runahead"); 7649 TRANSLATE_NOOP("FullscreenUI", "Runahead/Rewind"); 7650 TRANSLATE_NOOP("FullscreenUI", "Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance."); 7651 TRANSLATE_NOOP("FullscreenUI", "SDL DualSense Player LED"); 7652 TRANSLATE_NOOP("FullscreenUI", "SDL DualShock 4 / DualSense Enhanced Mode"); 7653 TRANSLATE_NOOP("FullscreenUI", "Save Profile"); 7654 TRANSLATE_NOOP("FullscreenUI", "Save Screenshot"); 7655 TRANSLATE_NOOP("FullscreenUI", "Save State"); 7656 TRANSLATE_NOOP("FullscreenUI", "Save State Compression"); 7657 TRANSLATE_NOOP("FullscreenUI", "Save State On Exit"); 7658 TRANSLATE_NOOP("FullscreenUI", "Saved {:%c}"); 7659 TRANSLATE_NOOP("FullscreenUI", "Saves state periodically so you can rewind any mistakes while playing."); 7660 TRANSLATE_NOOP("FullscreenUI", "Scaled Dithering"); 7661 TRANSLATE_NOOP("FullscreenUI", "Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution."); 7662 TRANSLATE_NOOP("FullscreenUI", "Scales the dithering pattern with the internal rendering resolution, making it less noticeable. Usually safe to enable."); 7663 TRANSLATE_NOOP("FullscreenUI", "Scaling"); 7664 TRANSLATE_NOOP("FullscreenUI", "Scan For New Games"); 7665 TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories"); 7666 TRANSLATE_NOOP("FullscreenUI", "Screen Position"); 7667 TRANSLATE_NOOP("FullscreenUI", "Screen Rotation"); 7668 TRANSLATE_NOOP("FullscreenUI", "Screenshot Format"); 7669 TRANSLATE_NOOP("FullscreenUI", "Screenshot Quality"); 7670 TRANSLATE_NOOP("FullscreenUI", "Screenshot Size"); 7671 TRANSLATE_NOOP("FullscreenUI", "Search Directories"); 7672 TRANSLATE_NOOP("FullscreenUI", "Seek Speedup"); 7673 TRANSLATE_NOOP("FullscreenUI", "Select"); 7674 TRANSLATE_NOOP("FullscreenUI", "Select Device"); 7675 TRANSLATE_NOOP("FullscreenUI", "Select Disc"); 7676 TRANSLATE_NOOP("FullscreenUI", "Select Disc Drive"); 7677 TRANSLATE_NOOP("FullscreenUI", "Select Disc Image"); 7678 TRANSLATE_NOOP("FullscreenUI", "Select Game"); 7679 TRANSLATE_NOOP("FullscreenUI", "Select Macro {} Binds"); 7680 TRANSLATE_NOOP("FullscreenUI", "Select State"); 7681 TRANSLATE_NOOP("FullscreenUI", "Selects the GPU to use for rendering."); 7682 TRANSLATE_NOOP("FullscreenUI", "Selects the percentage of the normal clock speed the emulated hardware will run at."); 7683 TRANSLATE_NOOP("FullscreenUI", "Selects the quality at which screenshots will be compressed."); 7684 TRANSLATE_NOOP("FullscreenUI", "Selects the resolution scale that will be applied to the final image. 1x will downsample to the original console resolution."); 7685 TRANSLATE_NOOP("FullscreenUI", "Selects the resolution to use in fullscreen modes."); 7686 TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to."); 7687 TRANSLATE_NOOP("FullscreenUI", "Serial"); 7688 TRANSLATE_NOOP("FullscreenUI", "Session: {}"); 7689 TRANSLATE_NOOP("FullscreenUI", "Set Input Binding"); 7690 TRANSLATE_NOOP("FullscreenUI", "Set VRAM Write Dump Alpha Channel"); 7691 TRANSLATE_NOOP("FullscreenUI", "Sets a threshold for discarding precise values when exceeded. May help with glitches in some games."); 7692 TRANSLATE_NOOP("FullscreenUI", "Sets a threshold for discarding the emulated depth buffer. May help in some games."); 7693 TRANSLATE_NOOP("FullscreenUI", "Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems."); 7694 TRANSLATE_NOOP("FullscreenUI", "Sets the target emulation speed. It is not guaranteed that this speed will be reached on all systems."); 7695 TRANSLATE_NOOP("FullscreenUI", "Sets the turbo speed. It is not guaranteed that this speed will be reached on all systems."); 7696 TRANSLATE_NOOP("FullscreenUI", "Sets the verbosity of messages logged. Higher levels will log more messages."); 7697 TRANSLATE_NOOP("FullscreenUI", "Sets which sort of memory card image will be used for slot {}."); 7698 TRANSLATE_NOOP("FullscreenUI", "Setting {} binding {}."); 7699 TRANSLATE_NOOP("FullscreenUI", "Settings"); 7700 TRANSLATE_NOOP("FullscreenUI", "Settings and Operations"); 7701 TRANSLATE_NOOP("FullscreenUI", "Shader {} added as stage {}."); 7702 TRANSLATE_NOOP("FullscreenUI", "Shared Card Name"); 7703 TRANSLATE_NOOP("FullscreenUI", "Show CPU Usage"); 7704 TRANSLATE_NOOP("FullscreenUI", "Show Controller Input"); 7705 TRANSLATE_NOOP("FullscreenUI", "Show Enhancement Settings"); 7706 TRANSLATE_NOOP("FullscreenUI", "Show FPS"); 7707 TRANSLATE_NOOP("FullscreenUI", "Show Frame Times"); 7708 TRANSLATE_NOOP("FullscreenUI", "Show GPU Statistics"); 7709 TRANSLATE_NOOP("FullscreenUI", "Show GPU Usage"); 7710 TRANSLATE_NOOP("FullscreenUI", "Show Latency Statistics"); 7711 TRANSLATE_NOOP("FullscreenUI", "Show OSD Messages"); 7712 TRANSLATE_NOOP("FullscreenUI", "Show Resolution"); 7713 TRANSLATE_NOOP("FullscreenUI", "Show Speed"); 7714 TRANSLATE_NOOP("FullscreenUI", "Show Status Indicators"); 7715 TRANSLATE_NOOP("FullscreenUI", "Shows a visual history of frame times in the upper-left corner of the display."); 7716 TRANSLATE_NOOP("FullscreenUI", "Shows enhancement settings in the bottom-right corner of the screen."); 7717 TRANSLATE_NOOP("FullscreenUI", "Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."); 7718 TRANSLATE_NOOP("FullscreenUI", "Shows information about input and audio latency in the top-right corner of the display."); 7719 TRANSLATE_NOOP("FullscreenUI", "Shows information about the emulated GPU in the top-right corner of the display."); 7720 TRANSLATE_NOOP("FullscreenUI", "Shows on-screen-display messages when events occur."); 7721 TRANSLATE_NOOP("FullscreenUI", "Shows persistent icons when turbo is active or when paused."); 7722 TRANSLATE_NOOP("FullscreenUI", "Shows the current controller state of the system in the bottom-left corner of the display."); 7723 TRANSLATE_NOOP("FullscreenUI", "Shows the current emulation speed of the system in the top-right corner of the display as a percentage."); 7724 TRANSLATE_NOOP("FullscreenUI", "Shows the current rendering resolution of the system in the top-right corner of the display."); 7725 TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part of your profile in Discord."); 7726 TRANSLATE_NOOP("FullscreenUI", "Shows the host's CPU usage based on threads in the top-right corner of the display."); 7727 TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU usage in the top-right corner of the display."); 7728 TRANSLATE_NOOP("FullscreenUI", "Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display."); 7729 TRANSLATE_NOOP("FullscreenUI", "Simulates the CPU's instruction cache in the recompiler. Can help with games running too fast."); 7730 TRANSLATE_NOOP("FullscreenUI", "Simulates the region check present in original, unmodified consoles."); 7731 TRANSLATE_NOOP("FullscreenUI", "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."); 7732 TRANSLATE_NOOP("FullscreenUI", "Skip Duplicate Frame Display"); 7733 TRANSLATE_NOOP("FullscreenUI", "Skips the presentation/display of frames that are not unique. Can result in worse frame pacing."); 7734 TRANSLATE_NOOP("FullscreenUI", "Slow Boot"); 7735 TRANSLATE_NOOP("FullscreenUI", "Smooths out blockyness between colour transitions in 24-bit content, usually FMVs."); 7736 TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 2D objects."); 7737 TRANSLATE_NOOP("FullscreenUI", "Smooths out the blockiness of magnified textures on 3D objects."); 7738 TRANSLATE_NOOP("FullscreenUI", "Sort By"); 7739 TRANSLATE_NOOP("FullscreenUI", "Sort Reversed"); 7740 TRANSLATE_NOOP("FullscreenUI", "Sound Effects"); 7741 TRANSLATE_NOOP("FullscreenUI", "Specifies the amount of buffer time added, which reduces the additional sleep time introduced."); 7742 TRANSLATE_NOOP("FullscreenUI", "Spectator Mode"); 7743 TRANSLATE_NOOP("FullscreenUI", "Speed Control"); 7744 TRANSLATE_NOOP("FullscreenUI", "Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others."); 7745 TRANSLATE_NOOP("FullscreenUI", "Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others."); 7746 TRANSLATE_NOOP("FullscreenUI", "Sprite Texture Filtering"); 7747 TRANSLATE_NOOP("FullscreenUI", "Stage {}: {}"); 7748 TRANSLATE_NOOP("FullscreenUI", "Start BIOS"); 7749 TRANSLATE_NOOP("FullscreenUI", "Start Disc"); 7750 TRANSLATE_NOOP("FullscreenUI", "Start File"); 7751 TRANSLATE_NOOP("FullscreenUI", "Start Fullscreen"); 7752 TRANSLATE_NOOP("FullscreenUI", "Start Game"); 7753 TRANSLATE_NOOP("FullscreenUI", "Start a game from a disc in your PC's DVD drive."); 7754 TRANSLATE_NOOP("FullscreenUI", "Start the console without any disc inserted."); 7755 TRANSLATE_NOOP("FullscreenUI", "Stores the current settings to an input profile."); 7756 TRANSLATE_NOOP("FullscreenUI", "Stretch Display Vertically"); 7757 TRANSLATE_NOOP("FullscreenUI", "Stretch Mode"); 7758 TRANSLATE_NOOP("FullscreenUI", "Stretches the display to match the aspect ratio by multiplying vertically instead of horizontally."); 7759 TRANSLATE_NOOP("FullscreenUI", "Summary"); 7760 TRANSLATE_NOOP("FullscreenUI", "Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."); 7761 TRANSLATE_NOOP("FullscreenUI", "Switches between full screen and windowed when the window is double-clicked."); 7762 TRANSLATE_NOOP("FullscreenUI", "Sync To Host Refresh Rate"); 7763 TRANSLATE_NOOP("FullscreenUI", "Synchronizes presentation of the console's frames to the host. GSync/FreeSync users should enable Optimal Frame Pacing instead."); 7764 TRANSLATE_NOOP("FullscreenUI", "Temporarily disables all enhancements, useful when testing."); 7765 TRANSLATE_NOOP("FullscreenUI", "Test Unofficial Achievements"); 7766 TRANSLATE_NOOP("FullscreenUI", "Texture Filtering"); 7767 TRANSLATE_NOOP("FullscreenUI", "Texture Replacements"); 7768 TRANSLATE_NOOP("FullscreenUI", "The SDL input source supports most controllers."); 7769 TRANSLATE_NOOP("FullscreenUI", "The XInput source provides support for XBox 360/XBox One/XBox Series controllers."); 7770 TRANSLATE_NOOP("FullscreenUI", "The audio backend determines how frames produced by the emulator are submitted to the host."); 7771 TRANSLATE_NOOP("FullscreenUI", "The selected memory card image will be used in shared mode for this slot."); 7772 TRANSLATE_NOOP("FullscreenUI", "This game has no achievements."); 7773 TRANSLATE_NOOP("FullscreenUI", "This game has no leaderboards."); 7774 TRANSLATE_NOOP("FullscreenUI", "Threaded Presentation"); 7775 TRANSLATE_NOOP("FullscreenUI", "Threaded Rendering"); 7776 TRANSLATE_NOOP("FullscreenUI", "Time Played"); 7777 TRANSLATE_NOOP("FullscreenUI", "Time Played: %s"); 7778 TRANSLATE_NOOP("FullscreenUI", "Timing out in {:.0f} seconds..."); 7779 TRANSLATE_NOOP("FullscreenUI", "Title"); 7780 TRANSLATE_NOOP("FullscreenUI", "Toggle Analog"); 7781 TRANSLATE_NOOP("FullscreenUI", "Toggle Fast Forward"); 7782 TRANSLATE_NOOP("FullscreenUI", "Toggle Fullscreen"); 7783 TRANSLATE_NOOP("FullscreenUI", "Toggle every %d frames"); 7784 TRANSLATE_NOOP("FullscreenUI", "True Color Debanding"); 7785 TRANSLATE_NOOP("FullscreenUI", "True Color Rendering"); 7786 TRANSLATE_NOOP("FullscreenUI", "Turbo Speed"); 7787 TRANSLATE_NOOP("FullscreenUI", "Type"); 7788 TRANSLATE_NOOP("FullscreenUI", "UI Language"); 7789 TRANSLATE_NOOP("FullscreenUI", "Uncompressed Size"); 7790 TRANSLATE_NOOP("FullscreenUI", "Uncompressed Size: %.2f MB"); 7791 TRANSLATE_NOOP("FullscreenUI", "Undo Load State"); 7792 TRANSLATE_NOOP("FullscreenUI", "Unknown"); 7793 TRANSLATE_NOOP("FullscreenUI", "Unknown File Size"); 7794 TRANSLATE_NOOP("FullscreenUI", "Unlimited"); 7795 TRANSLATE_NOOP("FullscreenUI", "Use Blit Swap Chain"); 7796 TRANSLATE_NOOP("FullscreenUI", "Use Debug GPU Device"); 7797 TRANSLATE_NOOP("FullscreenUI", "Use Global Setting"); 7798 TRANSLATE_NOOP("FullscreenUI", "Use Light Theme"); 7799 TRANSLATE_NOOP("FullscreenUI", "Use Old MDEC Routines"); 7800 TRANSLATE_NOOP("FullscreenUI", "Use Single Card For Multi-Disc Games"); 7801 TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For Readbacks"); 7802 TRANSLATE_NOOP("FullscreenUI", "Username: {}"); 7803 TRANSLATE_NOOP("FullscreenUI", "Uses PGXP for all instructions, not just memory operations."); 7804 TRANSLATE_NOOP("FullscreenUI", "Uses a blit presentation model instead of flipping. This may be needed on some systems."); 7805 TRANSLATE_NOOP("FullscreenUI", "Uses a light coloured theme instead of the default dark theme."); 7806 TRANSLATE_NOOP("FullscreenUI", "Uses a second thread for drawing graphics. Speed boost, and safe to use."); 7807 TRANSLATE_NOOP("FullscreenUI", "Uses game-specific settings for controllers for this game."); 7808 TRANSLATE_NOOP("FullscreenUI", "Uses native resolution coordinates for 2D polygons, instead of precise coordinates. Can fix misaligned UI in some games, but otherwise should be left disabled."); 7809 TRANSLATE_NOOP("FullscreenUI", "Uses perspective-correct interpolation for colors, which can improve visuals in some games."); 7810 TRANSLATE_NOOP("FullscreenUI", "Uses perspective-correct interpolation for texture coordinates, straightening out warped textures."); 7811 TRANSLATE_NOOP("FullscreenUI", "Uses screen positions to resolve PGXP data. May improve visuals in some games."); 7812 TRANSLATE_NOOP("FullscreenUI", "Value: {} | Default: {} | Minimum: {} | Maximum: {}"); 7813 TRANSLATE_NOOP("FullscreenUI", "Vertex Cache"); 7814 TRANSLATE_NOOP("FullscreenUI", "Vertical Sync (VSync)"); 7815 TRANSLATE_NOOP("FullscreenUI", "WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to finish saving.\n\nDo you want to {0} anyway?"); 7816 TRANSLATE_NOOP("FullscreenUI", "When enabled and logged in, DuckStation will scan for achievements on startup."); 7817 TRANSLATE_NOOP("FullscreenUI", "When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server."); 7818 TRANSLATE_NOOP("FullscreenUI", "When enabled, DuckStation will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements."); 7819 TRANSLATE_NOOP("FullscreenUI", "When enabled, each session will behave as if no achievements have been unlocked."); 7820 TRANSLATE_NOOP("FullscreenUI", "When enabled, memory cards and controllers will be overwritten when save states are loaded."); 7821 TRANSLATE_NOOP("FullscreenUI", "When enabled, the minimum supported output latency will be used for the host API."); 7822 TRANSLATE_NOOP("FullscreenUI", "When playing a multi-disc game and using per-game (title) memory cards, use a single memory card for all discs."); 7823 TRANSLATE_NOOP("FullscreenUI", "When this option is chosen, the clock speed set below will be used."); 7824 TRANSLATE_NOOP("FullscreenUI", "Widescreen Rendering"); 7825 TRANSLATE_NOOP("FullscreenUI", "Wireframe Rendering"); 7826 TRANSLATE_NOOP("FullscreenUI", "Writes textures which can be replaced to the dump directory."); 7827 TRANSLATE_NOOP("FullscreenUI", "Yes, {} now and risk memory card corruption."); 7828 TRANSLATE_NOOP("FullscreenUI", "\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."); 7829 TRANSLATE_NOOP("FullscreenUI", "\"PlayStation\" and \"PSX\" are registered trademarks of Sony Interactive Entertainment Europe Limited. This software is not affiliated in any way with Sony Interactive Entertainment."); 7830 TRANSLATE_NOOP("FullscreenUI", "change disc"); 7831 TRANSLATE_NOOP("FullscreenUI", "reset"); 7832 TRANSLATE_NOOP("FullscreenUI", "shut down"); 7833 TRANSLATE_NOOP("FullscreenUI", "{:%H:%M}"); 7834 TRANSLATE_NOOP("FullscreenUI", "{:%Y-%m-%d %H:%M:%S}"); 7835 TRANSLATE_NOOP("FullscreenUI", "{} Frames"); 7836 TRANSLATE_NOOP("FullscreenUI", "{} deleted."); 7837 TRANSLATE_NOOP("FullscreenUI", "{} does not exist."); 7838 TRANSLATE_NOOP("FullscreenUI", "{} is not a valid disc image."); 7839 TRANSLATE_NOOP("FullscreenUI", "Version: %s"); 7840 // TRANSLATION-STRING-AREA-END 7841 #endif