diff --git a/src/core/fullscreenui.cpp b/src/core/fullscreenui.cpp index 999b1de67..61d30f20f 100644 --- a/src/core/fullscreenui.cpp +++ b/src/core/fullscreenui.cpp @@ -1080,6 +1080,10 @@ void FullscreenUI::BeginChangeDiscOnCoreThread(bool return_to_game) if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && entry->disc_set) { + // Don't hold both settings and game list lock. + const bool localized_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES); + // Another load request cannot start because we have the lock held. auto lock = GameList::GetLock(); if (!GameList::IsGameListLoaded()) @@ -1108,7 +1112,7 @@ void FullscreenUI::BeginChangeDiscOnCoreThread(bool return_to_game) // This is still needed because a scan-in-progress will report loaded. GameList::EnsureLoaded(lock); - auto matches = GameList::GetEntriesInDiscSet(entry->disc_set, GameList::ShouldShowLocalizedTitles()); + auto matches = GameList::GetEntriesInDiscSet(entry->disc_set, localized_titles); if (matches.size() > 1) { ChoiceDialogOptions options; diff --git a/src/core/fullscreenui_game_list.cpp b/src/core/fullscreenui_game_list.cpp index 6c4b84543..6699125d2 100644 --- a/src/core/fullscreenui_game_list.cpp +++ b/src/core/fullscreenui_game_list.cpp @@ -1,16 +1,18 @@ -// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "achievements.h" #include "core.h" #include "fullscreenui_private.h" #include "game_list.h" +#include "host.h" #include "system.h" #include "video_thread.h" #include "util/gpu_texture.h" #include "util/imgui_manager.h" +#include "common/bitutils.h" #include "common/error.h" #include "common/file_system.h" #include "common/path.h" @@ -22,13 +24,6 @@ namespace FullscreenUI { -enum class GameListView : u8 -{ - Grid, - List, - Count -}; - static void DrawGameList(const ImVec2& heading_size); static void DrawGameGrid(const ImVec2& heading_size); static void HandleGameListActivate(const GameList::Entry* entry); @@ -53,7 +48,14 @@ struct GameListLocals std::unordered_map cover_image_map; std::unordered_map icon_image_map; std::vector game_list_sorted_entries; - GameListView game_list_view = GameListView::Grid; + GameList::Column sort_column = GameList::DEFAULT_SORT_COLUMN; + bool sort_reversed = GameList::DEFAULT_SORT_REVERSED; + bool localized_titles = GameList::DEFAULT_LOCALIZED_TITLES; + bool merge_disc_sets = GameList::DEFAULT_MERGE_DISC_SETS; + bool grid_view = GameList::DEFAULT_FULLSCREENUI_GRID_VIEW; + bool cover_titles = GameList::DEFAULT_COVER_TITLES; + bool compact_mode = true; + bool show_trophy_icons = true; float game_list_current_selection_timeout = 0.0f; std::string game_list_current_selection_path; char game_list_search_string[256]; @@ -148,16 +150,13 @@ void FullscreenUI::DoSetCoverImage(std::string source_path, std::string existing void FullscreenUI::PopulateGameListEntryList() { - const s32 sort = Core::GetBaseIntSettingValue("Main", "FullscreenUIGameSort", 0); - const bool reverse = Core::GetBaseBoolSettingValue("Main", "FullscreenUIGameSortReverse", false); - const bool merge_disc_sets = Core::GetBaseBoolSettingValue("Main", "FullscreenUIMergeDiscSets", true); const std::string_view string_filter = s_game_list_locals.game_list_search_string; s_game_list_locals.game_list_sorted_entries.clear(); s_game_list_locals.game_list_sorted_entries.reserve(GameList::GetEntryCount()); for (const GameList::Entry& entry : GameList::GetEntries()) { - if (merge_disc_sets) + if (s_game_list_locals.merge_disc_sets) { if (entry.disc_set_member) continue; @@ -182,110 +181,11 @@ void FullscreenUI::PopulateGameListEntryList() s_game_list_locals.game_list_sorted_entries.push_back(&entry); } - std::sort(s_game_list_locals.game_list_sorted_entries.begin(), s_game_list_locals.game_list_sorted_entries.end(), - [sort, reverse](const GameList::Entry* lhs, const GameList::Entry* rhs) { - switch (sort) - { - case 0: // Type - { - const GameList::EntryType lst = lhs->GetSortType(); - const GameList::EntryType rst = rhs->GetSortType(); - if (lst != rst) - return reverse ? (lst > rst) : (lst < rst); - } - break; - - case 1: // Serial - { - if (lhs->serial != rhs->serial) - return reverse ? (lhs->serial > rhs->serial) : (lhs->serial < rhs->serial); - } - break; - - case 2: // Title - break; - - case 3: // File Title - { - const std::string_view lhs_title(Path::GetFileTitle(lhs->path)); - const std::string_view rhs_title(Path::GetFileTitle(rhs->path)); - const int res = StringUtil::Strncasecmp(lhs_title.data(), rhs_title.data(), - std::min(lhs_title.size(), rhs_title.size())); - if (res != 0) - return reverse ? (res > 0) : (res < 0); - } - break; - - case 4: // Time Played - { - if (lhs->total_played_time != rhs->total_played_time) - { - return reverse ? (lhs->total_played_time > rhs->total_played_time) : - (lhs->total_played_time < rhs->total_played_time); - } - } - break; - - case 5: // Last Played (reversed by default) - { - if (lhs->last_played_time != rhs->last_played_time) - { - return reverse ? (lhs->last_played_time < rhs->last_played_time) : - (lhs->last_played_time > rhs->last_played_time); - } - } - break; - - case 6: // File Size - { - if (lhs->file_size != rhs->file_size) - { - return reverse ? (lhs->file_size > rhs->file_size) : (lhs->file_size < rhs->file_size); - } - } - break; - - case 7: // Uncompressed Size - { - if (lhs->uncompressed_size != rhs->uncompressed_size) - { - return reverse ? (lhs->uncompressed_size > rhs->uncompressed_size) : - (lhs->uncompressed_size < rhs->uncompressed_size); - } - } - break; - - case 8: // Achievements - { - // sort by unlock percentage - const float unlock_lhs = - (lhs->num_achievements > 0) ? - (static_cast(std::max(lhs->unlocked_achievements, lhs->unlocked_achievements_hc)) / - static_cast(lhs->num_achievements)) : - 0; - const float unlock_rhs = - (rhs->num_achievements > 0) ? - (static_cast(std::max(rhs->unlocked_achievements, rhs->unlocked_achievements_hc)) / - static_cast(rhs->num_achievements)) : - 0; - if (std::abs(unlock_lhs - unlock_rhs) >= 0.0001f) - return reverse ? (unlock_lhs > unlock_rhs) : (unlock_lhs < unlock_rhs); - - // order by achievement count - if (lhs->num_achievements != rhs->num_achievements) - return reverse ? (rhs->num_achievements < lhs->num_achievements) : - (lhs->num_achievements < rhs->num_achievements); - } - } - - // fallback to title when all else is equal - const int res = StringUtil::CompareNoCase(lhs->GetSortTitle(), rhs->GetSortTitle()); - if (res != 0) - return reverse ? (res > 0) : (res < 0); - - // fallback to path when all else is equal - return reverse ? (lhs->path > rhs->path) : (lhs->path < rhs->path); - }); + std::ranges::sort(s_game_list_locals.game_list_sorted_entries, [](const GameList::Entry* lhs, + const GameList::Entry* rhs) { + return GameList::CompareEntryLessThan(s_game_list_locals.sort_reversed ? rhs : lhs, + s_game_list_locals.sort_reversed ? lhs : rhs, s_game_list_locals.sort_column); + }); } void FullscreenUI::DrawGameListWindow() @@ -300,10 +200,6 @@ void FullscreenUI::DrawGameListWindow() if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", MulAlpha(UIStyle.PrimaryColor, GetBackgroundAlpha()))) { - static constexpr const char* icons[] = {ICON_FA_TABLE_CELLS_LARGE, ICON_FA_LIST}; - static constexpr const char* titles[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; - static constexpr u32 count = static_cast(std::size(titles)); - BeginNavBar(); if (NavButton(ICON_PF_NAVIGATION_BACK, true, true)) @@ -312,7 +208,7 @@ void FullscreenUI::DrawGameListWindow() []() { SwitchToMainWindow(MainWindowType::Landing); }); } - NavTitle(Host::TranslateToStringView(FSUI_TR_CONTEXT, titles[static_cast(s_game_list_locals.game_list_view)])); + NavTitle(s_game_list_locals.grid_view ? FSUI_VSTR("Game Grid") : FSUI_VSTR("Game List")); static constexpr const float& search_font_size = UIStyle.MediumFontSize; static constexpr const float& search_font_weight = UIStyle.NormalFontWeight; @@ -348,14 +244,18 @@ void FullscreenUI::DrawGameListWindow() ImGui::PopStyleColor(2); ImGui::SetCursorPos(prev_cursor_pos); - RightAlignNavButtons(count); + RightAlignNavButtons(2u); - for (u32 i = 0; i < count; i++) + for (u32 grid = 0; grid < 2; grid++) { - if (NavButton(icons[i], static_cast(i) == s_game_list_locals.game_list_view, true)) + if (NavButton(grid ? ICON_FA_TABLE_CELLS_LARGE : ICON_FA_LIST, grid == BoolToUInt32(s_game_list_locals.grid_view), + true)) { - BeginTransition([i]() { - s_game_list_locals.game_list_view = static_cast(i); + BeginTransition([grid]() { + s_game_list_locals.grid_view = ConvertToBoolUnchecked(grid); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW, + s_game_list_locals.grid_view); + Host::CommitBaseSettingChanges(); QueueResetFocus(FocusResetType::ViewChanged); }); } @@ -366,17 +266,10 @@ void FullscreenUI::DrawGameListWindow() EndFullscreenWindow(); - switch (s_game_list_locals.game_list_view) - { - case GameListView::Grid: - DrawGameGrid(heading_size); - break; - case GameListView::List: - DrawGameList(heading_size); - break; - default: - break; - } + if (s_game_list_locals.grid_view) + DrawGameGrid(heading_size); + else + DrawGameList(heading_size); // note: has to come afterwards if (!AreAnyDialogsOpen()) @@ -386,8 +279,10 @@ void FullscreenUI::DrawGameListWindow() { EnqueueSoundEffect(SFX_NAV_MOVE); BeginTransition([]() { - s_game_list_locals.game_list_view = - (s_game_list_locals.game_list_view == GameListView::Grid) ? GameListView::List : GameListView::Grid; + s_game_list_locals.grid_view = !s_game_list_locals.grid_view; + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW, + s_game_list_locals.grid_view); + Host::CommitBaseSettingChanges(); QueueResetFocus(FocusResetType::ViewChanged); }); } @@ -450,8 +345,6 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) []() { SwitchToMainWindow(MainWindowType::Landing); }); } - const bool compact_mode = Core::GetBaseBoolSettingValue("Main", "FullscreenUIGameListCompactMode", true); - const bool show_localized_titles = GameList::ShouldShowLocalizedTitles(); auto game_list_lock = GameList::GetLock(); const GameList::Entry* selected_entry = nullptr; PopulateGameListEntryList(); @@ -462,8 +355,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (BeginFullscreenColumnWindow(0.0f, -530.0f, "game_list_entries", GetTransparentBackgroundColor(), ImVec2(LAYOUT_MENU_WINDOW_X_PADDING, LAYOUT_MENU_WINDOW_Y_PADDING))) { - const float image_size = compact_mode ? UIStyle.LargeFontSize : LayoutScale(50.0f); - const float row_image_padding = LayoutScale(compact_mode ? 15.0f : 15.0f); + const float image_size = s_game_list_locals.compact_mode ? UIStyle.LargeFontSize : LayoutScale(50.0f); + const float row_image_padding = LayoutScale(s_game_list_locals.compact_mode ? 15.0f : 15.0f); const float row_left_margin = image_size + row_image_padding; ResetFocusHere(); @@ -476,7 +369,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) for (const GameList::Entry* entry : s_game_list_locals.game_list_sorted_entries) { - if (!compact_mode) + if (!s_game_list_locals.compact_mode) { if (entry->serial.empty()) { @@ -489,20 +382,22 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) } } - const MenuButtonBounds mbb(entry->GetDisplayTitle(show_localized_titles), {}, summary, row_left_margin); + const MenuButtonBounds mbb(entry->GetDisplayTitle(s_game_list_locals.localized_titles), {}, summary, + row_left_margin); bool visible, hovered; bool pressed = MenuButtonFrame(GetKeyForGameListEntry(entry), true, mbb.frame_bb, &visible, &hovered); if (!visible) continue; - DrawGameListCover(entry, false, true, false, show_localized_titles, ImGui::GetWindowDrawList(), + DrawGameListCover(entry, false, true, false, s_game_list_locals.localized_titles, ImGui::GetWindowDrawList(), ImRect(ImVec2(mbb.title_bb.Min.x - row_left_margin, mbb.title_bb.Min.y), ImVec2(mbb.title_bb.Min.x - row_image_padding, mbb.title_bb.Min.y + image_size))); RenderShadowedTextClipped(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight, mbb.title_bb.Min, - mbb.title_bb.Max, text_color, entry->GetDisplayTitle(show_localized_titles), - &mbb.title_size, ImVec2(0.0f, 0.0f), mbb.title_size.x, &mbb.title_bb); + mbb.title_bb.Max, text_color, + entry->GetDisplayTitle(s_game_list_locals.localized_titles), &mbb.title_size, + ImVec2(0.0f, 0.0f), mbb.title_size.x, &mbb.title_bb); if (!summary.empty()) { @@ -605,7 +500,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (selected_entry) { const ImVec4 subtitle_text_color = DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]); - const std::string_view title = selected_entry->GetDisplayTitle(show_localized_titles); + const std::string_view title = selected_entry->GetDisplayTitle(s_game_list_locals.localized_titles); // title ImGui::PushFont(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight); @@ -813,9 +708,6 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) BeginMenuButtons(0, 0.0f, 15.0f, 15.0f, 20.0f, 20.0f); const ImGuiStyle& style = ImGui::GetStyle(); - const bool show_trophy_icons = Core::GetBaseBoolSettingValue("Main", "FullscreenUIShowTrophyIcons", true); - const bool show_titles = Core::GetBaseBoolSettingValue("Main", "FullscreenUIShowGridTitles", true); - const bool show_localized_titles = GameList::ShouldShowLocalizedTitles(); const float title_font_size = UIStyle.MediumLargeFontSize; const float title_font_weight = UIStyle.NormalFontWeight; @@ -856,7 +748,7 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) // tends to break when scrolling vertically - it goes left/right. Precompute the maximum item height for the row // first, and make all items the same size to work around this. const GameList::Entry* entry = s_game_list_locals.game_list_sorted_entries[entry_index]; - if (grid_x == 0 && show_titles) + if (grid_x == 0 && s_game_list_locals.cover_titles) { row_item_height = 0.0f; @@ -865,7 +757,7 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) for (size_t row_entry_index = entry_index; row_entry_index < row_entry_index_end; row_entry_index++) { const GameList::Entry* row_entry = s_game_list_locals.game_list_sorted_entries[row_entry_index]; - const std::string_view row_title = row_entry->GetDisplayTitle(show_localized_titles); + const std::string_view row_title = row_entry->GetDisplayTitle(s_game_list_locals.localized_titles); const ImVec2 this_title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, image_width, IMSTR_START_END(row_title)); row_item_height = std::max(row_item_height, this_title_size.y); @@ -875,9 +767,9 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) } ImVec2 title_size; - if (show_titles) + if (s_game_list_locals.cover_titles) { - const std::string_view title = entry->GetDisplayTitle(show_localized_titles); + const std::string_view title = entry->GetDisplayTitle(s_game_list_locals.localized_titles); title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, image_width, IMSTR_START_END(title)); } @@ -899,9 +791,10 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) bb.Min += style.FramePadding; bb.Max -= style.FramePadding; - DrawGameListCover(entry, false, false, true, show_localized_titles, dl, ImRect(bb.Min, bb.Min + image_size)); + DrawGameListCover(entry, false, false, true, s_game_list_locals.localized_titles, dl, + ImRect(bb.Min, bb.Min + image_size)); - if (show_trophy_icons) + if (s_game_list_locals.show_trophy_icons) { GPUTexture* const cover_trophy = GetGameListCoverTrophy(entry, image_size); if (cover_trophy) @@ -913,11 +806,12 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) } } - if (show_titles) + if (s_game_list_locals.cover_titles) { const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); RenderMultiLineShadowedTextClipped(dl, UIStyle.Font, title_font_size, title_font_weight, title_bb.Min, - title_bb.Max, text_color, entry->GetDisplayTitle(show_localized_titles), + title_bb.Max, text_color, + entry->GetDisplayTitle(s_game_list_locals.localized_titles), LAYOUT_CENTER_ALIGN_TEXT, image_width, &title_bb); } @@ -984,7 +878,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) }; const GameDatabase::DiscSetEntry* dsentry = entry->dbentry->disc_set; - OpenChoiceDialog(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()), false, std::move(options), + OpenChoiceDialog(entry->GetDisplayTitle(s_game_list_locals.localized_titles), false, std::move(options), [dsentry](s32 index, const std::string& title, bool checked) mutable { switch (index) { @@ -1023,7 +917,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) {FSUI_ICONSTR(ICON_FA_DELETE_LEFT, "Reset Play Time"), false}, }; - OpenChoiceDialog(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()), false, std::move(options), + OpenChoiceDialog(entry->GetDisplayTitle(s_game_list_locals.localized_titles), false, std::move(options), [entry_path = entry->path, entry_serial = entry->serial](s32 index, const std::string& title, bool checked) mutable { switch (index) @@ -1072,7 +966,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) {FSUI_ICONSTR(ICON_FA_HOURGLASS, "Slow Boot"), false}, }; - OpenChoiceDialog(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()), false, std::move(options), + OpenChoiceDialog(entry->GetDisplayTitle(s_game_list_locals.localized_titles), false, std::move(options), [entry_path = entry->path](s32 index, const std::string& title, bool checked) mutable { switch (index) { @@ -1120,9 +1014,9 @@ void FullscreenUI::HandleSelectDiscForDiscSet(const GameDatabase::DiscSetEntry* options.emplace_back(FSUI_ICONVSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false); const GameList::Entry* dsgentry = GameList::GetEntryForPath(dsentry->GetSaveTitle()); - const bool localized_titles = GameList::ShouldShowLocalizedTitles(); - OpenChoiceDialog(fmt::format(FSUI_FSTR("Select Disc for {}"), dsgentry ? dsgentry->GetDisplayTitle(localized_titles) : - dsentry->GetDisplayTitle(localized_titles)), + OpenChoiceDialog(fmt::format(FSUI_FSTR("Select Disc for {}"), + dsgentry ? dsgentry->GetDisplayTitle(s_game_list_locals.localized_titles) : + dsentry->GetDisplayTitle(s_game_list_locals.localized_titles)), false, std::move(options), [paths = std::move(paths)](s32 index, const std::string& title, bool checked) { if (static_cast(index) >= paths.size()) @@ -1137,9 +1031,8 @@ void FullscreenUI::HandleSelectDiscForDiscSet(const GameDatabase::DiscSetEntry* void FullscreenUI::SwitchToGameList() { - s_game_list_locals.game_list_view = - static_cast(std::min(Core::GetBaseUIntSettingValue("Main", "DefaultFullscreenUIGameView", 0), - static_cast(GameListView::Count) - 1)); + ReloadGameListSettings(); + s_game_list_locals.game_list_current_selection_path = {}; s_game_list_locals.game_list_current_selection_timeout = 0.0f; s_game_list_locals.game_list_search_string[0] = '\0'; @@ -1155,6 +1048,29 @@ void FullscreenUI::SwitchToGameList() SwitchToMainWindow(MainWindowType::GameList); } +void FullscreenUI::ReloadGameListSettings() +{ + s_game_list_locals.sort_column = + GameList::ParseColumnName( + Core::GetBaseStringSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_COLUMN)) + .value_or(GameList::DEFAULT_SORT_COLUMN); + s_game_list_locals.sort_reversed = + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_REVERSED, false); + s_game_list_locals.localized_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES); + s_game_list_locals.merge_disc_sets = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_MERGE_DISC_SETS, GameList::DEFAULT_MERGE_DISC_SETS); + s_game_list_locals.grid_view = + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW, + GameList::DEFAULT_FULLSCREENUI_GRID_VIEW); + s_game_list_locals.cover_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_COVER_TITLES, GameList::DEFAULT_COVER_TITLES); + s_game_list_locals.compact_mode = + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListCompactMode", true); + s_game_list_locals.show_trophy_icons = + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListGridTrophyIcons", true); +} + GPUTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry, bool fallback_to_achievements_icon, bool fallback_to_icon, bool return_default_image) { diff --git a/src/core/fullscreenui_private.h b/src/core/fullscreenui_private.h index b7414d0db..f0591c136 100644 --- a/src/core/fullscreenui_private.h +++ b/src/core/fullscreenui_private.h @@ -99,6 +99,7 @@ FileSelectorFilters GetImageFilters(); void ClearGameListState(); void SwitchToGameList(); +void ReloadGameListSettings(); void DrawGameListWindow(); void DoStartPath(std::string path, std::string state = std::string(), std::optional fast_boot = std::nullopt); diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index bf82ca4e0..84bbef479 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -99,7 +99,7 @@ static void StartImageVerification(); static void ProcessImageVerificationResults(std::string path, GameDatabase::TrackVerificationResult verification, bool result, bool cancelled, Error error); -static void DrawSummarySettingsPage(bool show_localized_titles); +static void DrawSummarySettingsPage(); static void DrawSummarySettingsTrackList(); static void OpenGameTitleActions(const std::string_view& path, const std::string_view& title, bool has_custom_title, bool is_disc_set_title); @@ -196,7 +196,7 @@ static void DrawEnumSetting(SettingsInterface* bsi, std::string_view title, std: std::optional (*from_string_function)(std::string_view str), const char* (*to_string_function)(DataType value), const char* (*to_display_string_function)(DataType value), SizeType option_count, - bool enabled = true, bool use_dropdown = true); + bool enabled = true, bool use_dropdown = true, void (*changed_callback)() = nullptr); static void DrawSpeedSelectorSetting(SettingsInterface* bsi, std::string_view title, std::string_view summary, const char* section, const char* key, float default_value, bool enabled = true); static void DrawFolderSetting(SettingsInterface* bsi, std::string_view title, const char* section, const char* key, @@ -1528,7 +1528,8 @@ void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, std::string_view titl std::optional (*from_string_function)(std::string_view str), const char* (*to_string_function)(DataType value), const char* (*to_display_string_function)(DataType value), SizeType option_count, - bool enabled /* = true */, bool use_dropdown /* = true */) + bool enabled /* = true */, bool use_dropdown /* = true */, + void (*changed_callback)() /* = nullptr*/) { const bool game_settings = IsEditingGameSettings(bsi); @@ -1577,17 +1578,21 @@ void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, std::string_view titl if (use_dropdown) { OpenDropdownDialog(title, std::move(cd_options), - [section = TinyString(section), key = TinyString(key), to_string_function, + [section = TinyString(section), key = TinyString(key), to_string_function, changed_callback, game_settings](s32 index, const std::string& title) { apply_option(section, key, to_string_function, game_settings, index); + if (changed_callback) + changed_callback(); }); } else { OpenChoiceDialog(title, false, std::move(cd_options), - [section = TinyString(section), key = TinyString(key), to_string_function, + [section = TinyString(section), key = TinyString(key), to_string_function, changed_callback, game_settings](s32 index, const std::string& title, bool checked) { apply_option(section, key, to_string_function, game_settings, index); + if (changed_callback) + changed_callback(); }); } } @@ -1918,7 +1923,6 @@ void FullscreenUI::DrawSettingsWindow() const float bg_alpha = GetBackgroundAlpha(); const bool blur_background = CanBlurBackground() && s_settings_locals.settings_page != SettingsPage::PostProcessing; - const bool show_localized_titles = GameList::ShouldShowLocalizedTitles(); static constexpr const SettingsPage global_pages[] = { SettingsPage::Interface, SettingsPage::GameList, SettingsPage::BIOS, SettingsPage::Console, @@ -1968,9 +1972,11 @@ void FullscreenUI::DrawSettingsWindow() if (NavButton(ICON_PF_NAVIGATION_BACK, true, true)) ReturnToPreviousWindow(TransitionEffect::ZoomOut); - NavTitle(s_settings_locals.game_settings_entry ? - s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles) : - Host::TranslateToStringView(FSUI_TR_CONTEXT, titles[static_cast(pages[index])].first)); + NavTitle( + s_settings_locals.game_settings_entry ? + s_settings_locals.game_settings_entry->GetDisplayTitle(Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES)) : + Host::TranslateToStringView(FSUI_TR_CONTEXT, titles[static_cast(pages[index])].first)); RightAlignNavButtons(count); @@ -2029,7 +2035,7 @@ void FullscreenUI::DrawSettingsWindow() switch (s_settings_locals.settings_page) { case SettingsPage::Summary: - DrawSummarySettingsPage(show_localized_titles); + DrawSummarySettingsPage(); break; case SettingsPage::Interface: @@ -2188,7 +2194,7 @@ void FullscreenUI::DrawSettingsWindow() } } -void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles) +void FullscreenUI::DrawSummarySettingsPage() { BeginMenuButtons(); ResetFocusHere(); @@ -2206,6 +2212,8 @@ void FullscreenUI::DrawSummarySettingsPage(bool show_localized_titles) { const float title_width = LayoutScale(200.0f); const bool allow_customization = !s_settings_locals.game_settings_entry->is_runtime_populated; + const bool show_localized_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES); const std::string_view title = s_settings_locals.game_settings_entry->GetDisplayTitle(show_localized_titles); if (MenuButtonWithInlineValue(s_settings_locals.game_settings_entry->has_custom_title ? FSUI_ICONVSTR(ICON_FA_WINDOW_MAXIMIZE, "Title (*)") : @@ -2891,48 +2899,48 @@ void FullscreenUI::DrawGameListSettingsPage() SettingsInterface* bsi = GetEditingSettingsInterface(false); BeginMenuButtons(); - ResetFocusHere(); MenuHeading(FSUI_VSTR("List Settings")); { - static constexpr const char* view_types[] = {FSUI_NSTR("Game Grid"), FSUI_NSTR("Game List")}; - static constexpr const char* sort_types[] = { - FSUI_NSTR("Type"), - FSUI_NSTR("Serial"), - FSUI_NSTR("Title"), - FSUI_NSTR("File Title"), - FSUI_NSTR("Time Played"), - FSUI_NSTR("Last Played"), - FSUI_NSTR("File Size"), - FSUI_NSTR("Uncompressed Size"), - FSUI_NSTR("Achievement Unlock/Count"), - }; + ResetFocusHere(); - DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_TABLE_CELLS_LARGE, "Default View"), - FSUI_VSTR("Selects the view that the game list will open to."), "Main", - "DefaultFullscreenUIGameView", 0, view_types); - DrawIntListSetting(bsi, FSUI_ICONVSTR(ICON_FA_SORT, "Sort By"), - FSUI_VSTR("Determines that field that the game list will be sorted by."), "Main", - "FullscreenUIGameSort", 0, sort_types); - DrawToggleSetting( + bool settings_changed = DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_TABLE_CELLS_LARGE, "Grid View"), + FSUI_VSTR("Shows the game list as a grid instead of a vertical list."), GameList::UI_SETTING_SECTION, + GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW, GameList::DEFAULT_FULLSCREENUI_GRID_VIEW); + DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_SORT, "Sort By"), + FSUI_VSTR("Determines that field that the game list will be sorted by."), + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_COLUMN, GameList::DEFAULT_SORT_COLUMN, + &GameList::ParseColumnName, &GameList::GetColumnName, &GameList::GetColumnDisplayName, + GameList::Column::MaxCount, false, true, &FullscreenUI::ReloadGameListSettings); + settings_changed |= DrawToggleSetting( bsi, FSUI_ICONVSTR(ICON_FA_ARROW_DOWN_Z_A, "Sort Reversed"), - FSUI_VSTR("Reverses the game list sort order from the default (usually ascending to descending)."), "Main", - "FullscreenUIGameSortReverse", false); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_RECTANGLE_LIST, "Merge Multi-Disc Games"), - FSUI_VSTR("Merges multi-disc games into one item in the game list."), "Main", - "FullscreenUIMergeDiscSets", true); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Show Localized Titles"), - FSUI_VSTR("Uses localized (native language) titles in the game list."), "UI", - "GameListShowLocalizedTitles", true); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TROPHY, "Show Achievement Trophy Icons"), - FSUI_VSTR("Shows trophy icons in game grid when games have achievements or have been mastered."), - "Main", "FullscreenUIShowTrophyIcons", true); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TAGS, "Show Grid View Titles"), - FSUI_VSTR("Shows titles underneath the images in the game grid view."), "Main", - "FullscreenUIShowGridTitles", true); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TEXT_SLASH, "List Compact Mode"), - FSUI_VSTR("Displays only the game title in the list, instead of the title and serial/file name."), - "Main", "FullscreenUIGameListCompactMode", true); + FSUI_VSTR("Reverses the game list sort order from the default (usually ascending to descending)."), + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_REVERSED, GameList::DEFAULT_SORT_REVERSED); + settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_RECTANGLE_LIST, "Merge Multi-Disc Games"), + FSUI_VSTR("Merges multi-disc games into one item in the game list."), + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_MERGE_DISC_SETS, + GameList::DEFAULT_MERGE_DISC_SETS); + settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LANGUAGE, "Show Localized Titles"), + FSUI_VSTR("Uses localized (native language) titles in the game list."), + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, + GameList::DEFAULT_LOCALIZED_TITLES); + settings_changed |= DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_TROPHY, "Show Achievement Trophy Icons"), + FSUI_VSTR("Shows trophy icons in game grid when games have achievements or have been mastered."), + GameList::UI_SETTING_SECTION, "GameListGridTrophyIcons", true); + settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_TAGS, "Show Grid View Titles"), + FSUI_VSTR("Shows titles underneath the images in the game grid view."), + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_COVER_TITLES, + GameList::DEFAULT_COVER_TITLES); + settings_changed |= DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_TEXT_SLASH, "List Compact Mode"), + FSUI_VSTR("Displays only the game title in the list, instead of the title and serial/file name."), + GameList::UI_SETTING_SECTION, "GameListCompactMode", true); + + // Need to queue because we have the settings lock... + if (settings_changed) + Host::RunOnCoreThread([]() { VideoThread::RunOnThread(&FullscreenUI::ReloadGameListSettings); }); } MenuHeading(FSUI_VSTR("Search Directories")); diff --git a/src/core/fullscreenui_strings.h b/src/core/fullscreenui_strings.h index 4b627b630..f6193e382 100644 --- a/src/core/fullscreenui_strings.h +++ b/src/core/fullscreenui_strings.h @@ -71,7 +71,6 @@ TRANSLATE_NOOP("FullscreenUI", "A resume save state was found for this game, but TRANSLATE_NOOP("FullscreenUI", "AMOLED"); TRANSLATE_NOOP("FullscreenUI", "About"); TRANSLATE_NOOP("FullscreenUI", "Achievement Notifications"); -TRANSLATE_NOOP("FullscreenUI", "Achievement Unlock/Count"); TRANSLATE_NOOP("FullscreenUI", "Achievement Unlocks Unconfirmed"); TRANSLATE_NOOP("FullscreenUI", "Achievements"); TRANSLATE_NOOP("FullscreenUI", "Achievements Settings"); @@ -228,7 +227,6 @@ TRANSLATE_NOOP("FullscreenUI", "Debugging Settings"); TRANSLATE_NOOP("FullscreenUI", "Default"); TRANSLATE_NOOP("FullscreenUI", "Default Boot"); TRANSLATE_NOOP("FullscreenUI", "Default Value"); -TRANSLATE_NOOP("FullscreenUI", "Default View"); TRANSLATE_NOOP("FullscreenUI", "Default: Disabled"); TRANSLATE_NOOP("FullscreenUI", "Default: Enabled"); TRANSLATE_NOOP("FullscreenUI", "Default: {}"); @@ -387,8 +385,6 @@ TRANSLATE_NOOP("FullscreenUI", "Fast Forward Speed"); TRANSLATE_NOOP("FullscreenUI", "Fast Forward Volume"); TRANSLATE_NOOP("FullscreenUI", "Fast forwards through memory card access, both loading and saving. Can reduce waiting times in games that frequently access memory cards."); TRANSLATE_NOOP("FullscreenUI", "Fast forwards through the early loading process when fast booting, saving time. Results may vary between games."); -TRANSLATE_NOOP("FullscreenUI", "File Size"); -TRANSLATE_NOOP("FullscreenUI", "File Title"); TRANSLATE_NOOP("FullscreenUI", "Filter Framebuffer Uploads"); TRANSLATE_NOOP("FullscreenUI", "Fine Crop Amount"); TRANSLATE_NOOP("FullscreenUI", "Fine Crop Mode"); @@ -436,6 +432,7 @@ TRANSLATE_NOOP("FullscreenUI", "Global Slot {0}##global_slot_{0}"); TRANSLATE_NOOP("FullscreenUI", "Graphics Settings"); TRANSLATE_NOOP("FullscreenUI", "Green Giant"); TRANSLATE_NOOP("FullscreenUI", "Grey Matter"); +TRANSLATE_NOOP("FullscreenUI", "Grid View"); TRANSLATE_NOOP("FullscreenUI", "Hardcore Mode"); TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will not be enabled until the game is restarted. Do you want to restart the game now?"); TRANSLATE_NOOP("FullscreenUI", "Hash Calculation Failed"); @@ -467,7 +464,6 @@ TRANSLATE_NOOP("FullscreenUI", "Interface Settings"); TRANSLATE_NOOP("FullscreenUI", "Internal Resolution"); TRANSLATE_NOOP("FullscreenUI", "Language"); TRANSLATE_NOOP("FullscreenUI", "Language: "); -TRANSLATE_NOOP("FullscreenUI", "Last Played"); TRANSLATE_NOOP("FullscreenUI", "Last Played: "); TRANSLATE_NOOP("FullscreenUI", "Latency Control"); TRANSLATE_NOOP("FullscreenUI", "Launch Options"); @@ -750,7 +746,6 @@ TRANSLATE_NOOP("FullscreenUI", "Selects the resolution to use in fullscreen mode TRANSLATE_NOOP("FullscreenUI", "Selects the screen location for achievement and leaderboard notifications."); TRANSLATE_NOOP("FullscreenUI", "Selects the screen location for challenge/progress indicators, and leaderboard trackers."); TRANSLATE_NOOP("FullscreenUI", "Selects the type of emulated controller for this port."); -TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to."); TRANSLATE_NOOP("FullscreenUI", "Selects which location on the screen messages are displayed."); TRANSLATE_NOOP("FullscreenUI", "Serial"); TRANSLATE_NOOP("FullscreenUI", "Serial (*)"); @@ -804,6 +799,7 @@ TRANSLATE_NOOP("FullscreenUI", "Shows persistent icons when turbo is active or w TRANSLATE_NOOP("FullscreenUI", "Shows the current controller state of the system in the bottom-left corner of the display."); TRANSLATE_NOOP("FullscreenUI", "Shows the current emulation speed of the system in the top-right corner of the display as a percentage."); TRANSLATE_NOOP("FullscreenUI", "Shows the current rendering resolution of the system in the top-right corner of the display."); +TRANSLATE_NOOP("FullscreenUI", "Shows the game list as a grid instead of a vertical list."); TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part of your profile in Discord."); TRANSLATE_NOOP("FullscreenUI", "Shows the host's CPU usage of each system thread in the top-right corner of the display."); TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU usage in the top-right corner of the display."); @@ -867,7 +863,6 @@ TRANSLATE_NOOP("FullscreenUI", "The unlock state for '{}' will be removed. This TRANSLATE_NOOP("FullscreenUI", "Theme"); TRANSLATE_NOOP("FullscreenUI", "This game was not scanned by DuckStation. Some functionality is not available."); TRANSLATE_NOOP("FullscreenUI", "Threaded Rendering"); -TRANSLATE_NOOP("FullscreenUI", "Time Played"); TRANSLATE_NOOP("FullscreenUI", "Time Played: "); TRANSLATE_NOOP("FullscreenUI", "Timing out in {:.0f} seconds..."); TRANSLATE_NOOP("FullscreenUI", "Title"); @@ -888,7 +883,6 @@ TRANSLATE_NOOP("FullscreenUI", "Tries to detect FMVs and disable read speedup du TRANSLATE_NOOP("FullscreenUI", "Trigger"); TRANSLATE_NOOP("FullscreenUI", "Turbo Speed"); TRANSLATE_NOOP("FullscreenUI", "Type"); -TRANSLATE_NOOP("FullscreenUI", "Uncompressed Size"); TRANSLATE_NOOP("FullscreenUI", "Undo Load State"); TRANSLATE_NOOP("FullscreenUI", "Ungrouped"); TRANSLATE_NOOP("FullscreenUI", "Unknown"); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 4ae5eac6e..9f5b8fc29 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -165,6 +165,87 @@ ALIGN_TO_CACHE_LINE static State s_state; } // namespace GameList +const char* const GameList::UI_SETTING_SECTION = "UI"; +constexpr const char* const GameList::SETTING_KEY_LOCALIZED_TITLES = "GameListShowLocalizedTitles"; +constexpr const char* const GameList::SETTING_KEY_SORT_COLUMN = "GameListSortColumn"; +constexpr const char* const GameList::SETTING_KEY_SORT_REVERSED = "GameListSortReversed"; +constexpr const char* const GameList::SETTING_KEY_PREFER_ACHIEVEMENT_CONTENT_ICONS = + "GameListPreferAchievementContentIcons"; +constexpr const char* const GameList::SETTING_KEY_MERGE_DISC_SETS = "GameListMergeDiscSets"; +constexpr const char* const GameList::SETTING_KEY_GRID_VIEW = "GameListGridView"; +constexpr const char* const GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW = "GameListFullscreenUIGridView"; +constexpr const char* const GameList::SETTING_KEY_COVER_TITLES = "GameListCoverTitles"; + +GameList::Entry::Entry() = default; + +GameList::Entry::~Entry() = default; + +std::string_view GameList::Entry::GetDisplayTitle(bool localized) const +{ + // if custom title is present, use that for display too + return !title.empty() ? std::string_view(title) : + (IsDiscSet() ? dbentry->disc_set->GetDisplayTitle(localized) : + (dbentry ? dbentry->GetDisplayTitle(localized) : std::string_view())); +} + +std::string_view GameList::Entry::GetSortTitle() const +{ + // if custom title is present, use that for sorting too + return !title.empty() ? + std::string_view(title) : + (IsDiscSet() ? dbentry->disc_set->GetSortTitle() : (dbentry ? dbentry->GetSortTitle() : std::string_view())); +} + +std::string_view GameList::Entry::GetSaveTitle() const +{ + // if custom title is present, use that for save folder too + return !title.empty() ? + std::string_view(title) : + (IsDiscSet() ? dbentry->disc_set->GetSaveTitle() : (dbentry ? dbentry->GetSaveTitle() : std::string_view())); +} + +std::string_view GameList::Entry::GetLanguageIcon() const +{ + std::string_view ret; + if (custom_language != GameDatabase::Language::MaxCount) + ret = GameDatabase::GetLanguageName(custom_language); + else if (dbentry) + ret = dbentry->GetLanguageFlagName(region); + else + ret = Settings::GetDiscRegionName(region); + + return ret; +} + +TinyString GameList::Entry::GetLanguageIconName() const +{ + return GameDatabase::GetLanguageFlagResourceName(GetLanguageIcon()); +} + +TinyString GameList::Entry::GetCompatibilityIconFileName() const +{ + return TinyString::from_format( + "images/star-{}.svg", + static_cast(dbentry ? dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown)); +} + +std::string GameList::Entry::GetReleaseDateString() const +{ + std::string ret; + + if (!dbentry || dbentry->release_date == 0) + ret = TRANSLATE_STR("GameList", "Unknown"); + else + ret = Host::FormatDate(static_cast(dbentry->release_date), false); + + return ret; +} + +bool GameList::Entry::IsGame() const +{ + return (type != EntryType::PSF && type != EntryType::AudioCD && !serial.empty()); +} + const char* GameList::GetEntryTypeName(EntryType type) { static std::array(EntryType::MaxCount)> names = {{ @@ -191,22 +272,223 @@ const char* GameList::GetEntryTypeDisplayName(EntryType type) return Host::TranslateToCString("GameList", names[static_cast(type)], "EntryType"); } +static constexpr std::array(GameList::Column::MaxCount)> s_column_names = {{ + "Type", + "Serial", + "Title", + "FileTitle", + "Developer", + "Publisher", + "Genre", + "Year", + "Players", + "TimePlayed", + "LastPlayed", + "Size", + "DataSize", + "Region", + "Achievements", + "Compatibility", +}}; + +static constexpr std::array(GameList::Column::MaxCount)> s_column_display_names = {{ + TRANSLATE_DISAMBIG_NOOP("GameList", "Type", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Serial", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Title", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "File Title", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Developer", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Publisher", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Genre", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Year", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Players", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Time Played", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Last Played", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Size", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Data Size", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Region", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Achievements", "Column"), + TRANSLATE_DISAMBIG_NOOP("GameList", "Compatibility", "Column"), +}}; + +const char* GameList::GetColumnName(Column column) +{ + return (static_cast(column) < s_column_names.size()) ? s_column_names[static_cast(column)] : ""; +} + +const char* GameList::GetColumnDisplayName(Column column) +{ + return (static_cast(column) < s_column_display_names.size()) ? + Host::TranslateToCString("GameList", s_column_display_names[static_cast(column)], "Column") : + ""; +} + +std::optional GameList::ParseColumnName(std::string_view name) +{ + for (size_t i = 0; i < s_column_names.size(); i++) + { + if (name == s_column_names[i]) + return static_cast(i); + } + + return std::nullopt; +} + +bool GameList::CompareEntryLessThan(const Entry* left, const Entry* right, Column column) +{ + switch (column) + { + case Column::Type: + { + const GameList::EntryType lst = left->GetSortType(); + const GameList::EntryType rst = right->GetSortType(); + if (lst == rst) + return CompareEntryTitlesLessThan(left, right); + + return (static_cast(lst) < static_cast(rst)); + } + + case Column::Serial: + { + const int res = StringUtil::CompareNoCase(left->serial, right->serial); + return (res == 0) ? CompareEntryTitlesLessThan(left, right) : (res < 0); + } + + case Column::Title: + { + return CompareEntryTitlesLessThan(left, right); + } + + case Column::FileTitle: + { + const std::string_view file_title_left = Path::GetFileTitle(left->path); + const std::string_view file_title_right = Path::GetFileTitle(right->path); + const int res = StringUtil::CompareNoCase(file_title_left, file_title_right); + return (res == 0) ? CompareEntryTitlesLessThan(left, right) : (res < 0); + } + + case Column::Developer: + { + const int res = StringUtil::CompareNoCase(left->dbentry ? left->dbentry->developer : std::string_view(), + right->dbentry ? right->dbentry->developer : std::string_view()); + return (res == 0) ? CompareEntryTitlesLessThan(left, right) : (res < 0); + } + + case Column::Publisher: + { + const int res = StringUtil::CompareNoCase(left->dbentry ? left->dbentry->publisher : std::string_view(), + right->dbentry ? right->dbentry->publisher : std::string_view()); + return (res == 0) ? CompareEntryTitlesLessThan(left, right) : (res < 0); + } + + case Column::Genre: + { + const int res = StringUtil::CompareNoCase(left->dbentry ? left->dbentry->genre : std::string_view(), + right->dbentry ? right->dbentry->genre : std::string_view()); + return (res == 0) ? CompareEntryTitlesLessThan(left, right) : (res < 0); + } + + case Column::Year: + { + const u64 ldate = left->dbentry ? left->dbentry->release_date : 0; + const u64 rdate = right->dbentry ? right->dbentry->release_date : 0; + return (ldate == rdate) ? CompareEntryTitlesLessThan(left, right) : (ldate < rdate); + } + + case Column::Players: + { + const u8 left_players = left->dbentry ? ((left->dbentry->min_players << 4) + left->dbentry->max_players) : 0; + const u8 right_players = right->dbentry ? ((right->dbentry->min_players << 4) + right->dbentry->max_players) : 0; + return (left_players == right_players) ? CompareEntryTitlesLessThan(left, right) : (left_players < right_players); + } + + case Column::TimePlayed: + { + return (left->total_played_time == right->total_played_time) ? + CompareEntryTitlesLessThan(left, right) : + (left->total_played_time < right->total_played_time); + } + + case Column::LastPlayed: + { + return (left->last_played_time == right->last_played_time) ? CompareEntryTitlesLessThan(left, right) : + (left->last_played_time < right->last_played_time); + } + + case Column::FileSize: + { + return (left->file_size == right->file_size) ? CompareEntryTitlesLessThan(left, right) : + (left->file_size < right->file_size); + } + + case Column::DataSize: + { + return (left->uncompressed_size == right->uncompressed_size) ? + CompareEntryTitlesLessThan(left, right) : + (left->uncompressed_size < right->uncompressed_size); + } + + case Column::Region: + { + return (left->region == right->region) ? CompareEntryTitlesLessThan(left, right) : + (static_cast(left->region) < static_cast(right->region)); + } + + case Column::Achievements: + { + // sort by unlock percentage + const float unlock_left = + (left->num_achievements > 0) ? + (static_cast(std::max(left->unlocked_achievements, left->unlocked_achievements_hc)) / + static_cast(left->num_achievements)) : + 0; + const float unlock_right = + (right->num_achievements > 0) ? + (static_cast(std::max(right->unlocked_achievements, right->unlocked_achievements_hc)) / + static_cast(right->num_achievements)) : + 0; + if (std::abs(unlock_left - unlock_right) < 0.0001f) + { + // order by achievement count + if (left->num_achievements == right->num_achievements) + return CompareEntryTitlesLessThan(left, right); + + return (left->num_achievements < right->num_achievements); + } + + return (unlock_left < unlock_right); + } + + case Column::Compatibilty: + { + const GameDatabase::CompatibilityRating left_compatibility = + left->dbentry ? left->dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown; + const GameDatabase::CompatibilityRating right_compatibility = + right->dbentry ? right->dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown; + return (left_compatibility == right_compatibility) ? CompareEntryTitlesLessThan(left, right) : + (left_compatibility < right_compatibility); + } + + default: + return false; + } +} + +bool GameList::CompareEntryTitlesLessThan(const Entry* left, const Entry* right) +{ + const s32 res = StringUtil::CompareNoCase(left->GetSortTitle(), right->GetSortTitle()); + if (res != 0) + return (res < 0); + + // Fallback to path compare if titles are the same. + return (left->path < right->path); +} + bool GameList::IsGameListLoaded() { const std::unique_lock lock(s_state.mutex); return (s_state.game_list_loaded != ListState::Unloaded); } -bool GameList::ShouldShowLocalizedTitles() -{ - return Core::GetBaseBoolSettingValue("UI", "GameListShowLocalizedTitles", true); -} - -bool GameList::PreferAchievementGameBadgesForIcons() -{ - return Core::GetBaseBoolSettingValue("UI", "GameListPreferAchievementGameBadgesForIcons", false); -} - bool GameList::IsScannableFilename(std::string_view path) { // we don't scan bin files because they'll duplicate with cue, same with img for ccd @@ -1424,72 +1706,6 @@ std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const cha return Path::Combine(EmuFolders::Covers, Path::SanitizeFileName(filename)); } -std::string_view GameList::Entry::GetDisplayTitle(bool localized) const -{ - // if custom title is present, use that for display too - return !title.empty() ? std::string_view(title) : - (IsDiscSet() ? dbentry->disc_set->GetDisplayTitle(localized) : - (dbentry ? dbentry->GetDisplayTitle(localized) : std::string_view())); -} - -std::string_view GameList::Entry::GetSortTitle() const -{ - // if custom title is present, use that for sorting too - return !title.empty() ? - std::string_view(title) : - (IsDiscSet() ? dbentry->disc_set->GetSortTitle() : (dbentry ? dbentry->GetSortTitle() : std::string_view())); -} - -std::string_view GameList::Entry::GetSaveTitle() const -{ - // if custom title is present, use that for save folder too - return !title.empty() ? - std::string_view(title) : - (IsDiscSet() ? dbentry->disc_set->GetSaveTitle() : (dbentry ? dbentry->GetSaveTitle() : std::string_view())); -} - -std::string_view GameList::Entry::GetLanguageIcon() const -{ - std::string_view ret; - if (custom_language != GameDatabase::Language::MaxCount) - ret = GameDatabase::GetLanguageName(custom_language); - else if (dbentry) - ret = dbentry->GetLanguageFlagName(region); - else - ret = Settings::GetDiscRegionName(region); - - return ret; -} - -TinyString GameList::Entry::GetLanguageIconName() const -{ - return GameDatabase::GetLanguageFlagResourceName(GetLanguageIcon()); -} - -TinyString GameList::Entry::GetCompatibilityIconFileName() const -{ - return TinyString::from_format( - "images/star-{}.svg", - static_cast(dbentry ? dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown)); -} - -std::string GameList::Entry::GetReleaseDateString() const -{ - std::string ret; - - if (!dbentry || dbentry->release_date == 0) - ret = TRANSLATE_STR("GameList", "Unknown"); - else - ret = Host::FormatDate(static_cast(dbentry->release_date), false); - - return ret; -} - -bool GameList::Entry::IsGame() const -{ - return (type != EntryType::PSF && type != EntryType::AudioCD && !serial.empty()); -} - std::string GameList::GetPlayedTimePath() { return Path::Combine(EmuFolders::DataRoot, "playtime.dat"); @@ -2140,8 +2356,12 @@ std::string GameList::GetGameIconPath(std::string_view custom_title, std::string if (achievements_game_id != 0) { fallback_path = Achievements::GetGameBadgeURL(achievements_game_id); - if (!fallback_path.empty() && PreferAchievementGameBadgesForIcons()) + if (!fallback_path.empty() && + Core::GetBaseBoolSettingValue(UI_SETTING_SECTION, SETTING_KEY_PREFER_ACHIEVEMENT_CONTENT_ICONS, + DEFAULT_PREFER_ACHIEVEMENT_CONTENT_ICONS)) + { return (ret = std::move(fallback_path)); + } } if (serial.empty()) diff --git a/src/core/game_list.h b/src/core/game_list.h index 1f0b81139..63b8749bc 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -31,8 +31,33 @@ enum class EntryType : u8 MaxCount }; +enum class Column : u8 +{ + Type, + Serial, + Title, + FileTitle, + Developer, + Publisher, + Genre, + Year, + Players, + TimePlayed, + LastPlayed, + FileSize, + DataSize, + Region, + Achievements, + Compatibilty, + + MaxCount, +}; + struct Entry { + Entry(); + ~Entry(); + EntryType type = EntryType::MaxCount; DiscRegion region = DiscRegion::Other; @@ -102,6 +127,12 @@ using EntryList = std::vector; const char* GetEntryTypeName(EntryType type); const char* GetEntryTypeDisplayName(EntryType type); +const char* GetColumnName(Column column); +const char* GetColumnDisplayName(Column column); +std::optional ParseColumnName(std::string_view name); +bool CompareEntryLessThan(const Entry* left, const Entry* right, Column column); +bool CompareEntryTitlesLessThan(const Entry* left, const Entry* right); + bool IsScannableFilename(std::string_view path); /// Populates a game list entry struct with information from the specified path. @@ -119,7 +150,6 @@ const Entry* GetFirstDiscSetMember(const GameDatabase::DiscSetEntry* dsentry); size_t GetEntryCount(); bool IsGameListLoaded(); -bool ShouldShowLocalizedTitles(); /// Returns true if the specified path should not have game properties saved. bool CanEditGameSettingsForPath(const std::string_view path, const std::string_view serial); @@ -193,8 +223,24 @@ void UpdateAchievementData(std::span hash, u32 game_id, u32 num_ac u32 num_unlocked_hardcore); void UpdateAllAchievementData(); -/// Accesses achievement game badges. Assumes the lock is held. -bool PreferAchievementGameBadgesForIcons(); +// Setting keys +extern const char* const UI_SETTING_SECTION; +extern const char* const SETTING_KEY_LOCALIZED_TITLES; +extern const char* const SETTING_KEY_SORT_COLUMN; +extern const char* const SETTING_KEY_SORT_REVERSED; +extern const char* const SETTING_KEY_PREFER_ACHIEVEMENT_CONTENT_ICONS; +extern const char* const SETTING_KEY_MERGE_DISC_SETS; +extern const char* const SETTING_KEY_GRID_VIEW; +extern const char* const SETTING_KEY_FULLSCREENUI_GRID_VIEW; +extern const char* const SETTING_KEY_COVER_TITLES; +inline constexpr bool DEFAULT_LOCALIZED_TITLES = true; +inline constexpr Column DEFAULT_SORT_COLUMN = Column::Title; +inline constexpr bool DEFAULT_SORT_REVERSED = false; +inline constexpr bool DEFAULT_PREFER_ACHIEVEMENT_CONTENT_ICONS = false; +inline constexpr bool DEFAULT_MERGE_DISC_SETS = true; +inline constexpr bool DEFAULT_GRID_VIEW = false; +inline constexpr bool DEFAULT_FULLSCREENUI_GRID_VIEW = true; +inline constexpr bool DEFAULT_COVER_TITLES = true; } // namespace GameList diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 8760d1495..44c0f653a 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -162,7 +162,6 @@ std::span Settings::GetSectionSaveOrder() "Cheats", "Main", "UI", - "GameListTableView", "AutoUpdater", "Folders", "GameList", diff --git a/src/core/system.cpp b/src/core/system.cpp index 8c87aa806..72a8dee52 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4175,8 +4175,9 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo s_state.running_game_entry = GameDatabase::GetEntryForSerial(s_state.running_game_serial); if (s_state.running_game_entry && s_state.running_game_title.empty()) { - s_state.running_game_title = - s_state.running_game_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()); + s_state.running_game_title = s_state.running_game_entry->GetDisplayTitle( + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, + GameList::DEFAULT_LOCALIZED_TITLES)); } else if (s_state.running_game_title.empty()) { @@ -4204,8 +4205,9 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo if (s_state.running_game_title.empty()) { - s_state.running_game_title = - s_state.running_game_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()); + s_state.running_game_title = s_state.running_game_entry->GetDisplayTitle( + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, + GameList::DEFAULT_LOCALIZED_TITLES)); } } else diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 579ddbb5c..6c2728fa3 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -83,25 +83,21 @@ static const char* SUPPORTED_FORMATS_STRING = ".chd (Compressed Hunks of Data)\n" ".pbp (PlayStation Portable, Only Decrypted)"); -static constexpr std::array s_column_names = {{ - QT_TRANSLATE_NOOP("GameListModel", "Icon"), - QT_TRANSLATE_NOOP("GameListModel", "Serial"), - QT_TRANSLATE_NOOP("GameListModel", "Title"), - QT_TRANSLATE_NOOP("GameListModel", "File Title"), - QT_TRANSLATE_NOOP("GameListModel", "Developer"), - QT_TRANSLATE_NOOP("GameListModel", "Publisher"), - QT_TRANSLATE_NOOP("GameListModel", "Genre"), - QT_TRANSLATE_NOOP("GameListModel", "Year"), - QT_TRANSLATE_NOOP("GameListModel", "Players"), - QT_TRANSLATE_NOOP("GameListModel", "Time Played"), - QT_TRANSLATE_NOOP("GameListModel", "Last Played"), - QT_TRANSLATE_NOOP("GameListModel", "Size"), - QT_TRANSLATE_NOOP("GameListModel", "Data Size"), - QT_TRANSLATE_NOOP("GameListModel", "Region"), - QT_TRANSLATE_NOOP("GameListModel", "Achievements"), - QT_TRANSLATE_NOOP("GameListModel", "Compatibility"), - "Cover", // Do not translate. -}}; +static_assert(static_cast(GameList::Column::Type) == GameListModel::Column_Icon); +static_assert(static_cast(GameList::Column::Serial) == GameListModel::Column_Serial); +static_assert(static_cast(GameList::Column::Title) == GameListModel::Column_Title); +static_assert(static_cast(GameList::Column::FileTitle) == GameListModel::Column_FileTitle); +static_assert(static_cast(GameList::Column::Developer) == GameListModel::Column_Developer); +static_assert(static_cast(GameList::Column::Publisher) == GameListModel::Column_Publisher); +static_assert(static_cast(GameList::Column::Genre) == GameListModel::Column_Genre); +static_assert(static_cast(GameList::Column::Year) == GameListModel::Column_Year); +static_assert(static_cast(GameList::Column::Players) == GameListModel::Column_Players); +static_assert(static_cast(GameList::Column::TimePlayed) == GameListModel::Column_TimePlayed); +static_assert(static_cast(GameList::Column::LastPlayed) == GameListModel::Column_LastPlayed); +static_assert(static_cast(GameList::Column::FileSize) == GameListModel::Column_FileSize); +static_assert(static_cast(GameList::Column::DataSize) == GameListModel::Column_DataSize); +static_assert(static_cast(GameList::Column::Region) == GameListModel::Column_Region); +static_assert(static_cast(GameList::Column::Achievements) == GameListModel::Column_Achievements); static void resizeImage(QImage* image, const QSize& expected_size) { @@ -222,31 +218,11 @@ static QString sizeToString(s64 size) QCoreApplication::translate("GameList", "Unknown"); } -std::optional GameListModel::getColumnIdForName(std::string_view name) -{ - for (int column = 0; column < Column_Count; column++) - { - if (name == s_column_names[column]) - return static_cast(column); - } - - return std::nullopt; -} - -const char* GameListModel::getColumnName(Column col) -{ - return s_column_names[static_cast(col)]; -} - GameListModel::GameListModel(GameListWidget* parent) : QAbstractTableModel(parent), m_device_pixel_ratio(parent->devicePixelRatio()), m_icon_pixmap_cache(MIN_COVER_CACHE_SIZE) { - m_cover_scale = Core::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", DEFAULT_COVER_SCALE); - m_icon_size = Core::GetBaseIntSettingValue("UI", "GameListIconSize", GAME_ICON_DEFAULT_SIZE); - m_show_localized_titles = GameList::ShouldShowLocalizedTitles(); - m_show_titles_for_covers = Core::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true); - m_show_game_icons = Core::GetBaseBoolSettingValue("UI", "GameListShowGameIcons", true); + reloadSettings(); loadCommonImages(); loadCoverScaleDependentPixmaps(); @@ -259,6 +235,30 @@ GameListModel::GameListModel(GameListWidget* parent) GameListModel::~GameListModel() = default; +void GameListModel::loadSettings() +{ + m_cover_scale = + Core::GetBaseFloatSettingValue(GameList::UI_SETTING_SECTION, "GameListCoverArtScale", DEFAULT_COVER_SCALE); + m_icon_size = Core::GetBaseIntSettingValue(GameList::UI_SETTING_SECTION, "GameListIconSize", GAME_ICON_DEFAULT_SIZE); + m_show_localized_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES); + m_show_titles_for_covers = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_COVER_TITLES, GameList::DEFAULT_COVER_TITLES); + m_show_game_icons = Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListShowGameIcons", true); +} + +void GameListModel::reloadSettings() +{ + setCoverScale( + Core::GetBaseFloatSettingValue(GameList::UI_SETTING_SECTION, "GameListCoverArtScale", DEFAULT_COVER_SCALE)); + setIconSize(Core::GetBaseIntSettingValue(GameList::UI_SETTING_SECTION, "GameListIconSize", GAME_ICON_DEFAULT_SIZE)); + setShowLocalizedTitles(Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES)); + setShowCoverTitles(Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_COVER_TITLES, + GameList::DEFAULT_COVER_TITLES)); + setShowGameIcons(Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListShowGameIcons", true)); +} + bool GameListModel::getShowLocalizedTitles() const { return m_show_localized_titles; @@ -303,9 +303,6 @@ void GameListModel::setIconSize(int size) m_icon_size = size; - Core::SetBaseIntSettingValue("UI", "GameListIconSize", size); - Host::CommitBaseSettingChanges(); - emit iconSizeChanged(m_icon_size); // Might look odd, but this is needed to force the section sizes to invalidate @@ -333,9 +330,10 @@ void GameListModel::setCoverScale(float scale) m_cover_pixmap_cache.Apply( [](const std::string&, CoverPixmapCacheEntry& entry) { entry.scale = entry.is_loading ? 0.0f : entry.scale; }); - Core::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", scale); - Host::CommitBaseSettingChanges(); - updateCoverScale(); + loadCoverScaleDependentPixmaps(); + emit coverScaleChanged(m_cover_scale); + emit dataChanged(index(0, Column_Cover), index(rowCount() - 1, Column_Cover), + {Qt::DecorationRole, Qt::FontRole, Qt::SizeHintRole}); } void GameListModel::loadCoverScaleDependentPixmaps() @@ -368,14 +366,6 @@ void GameListModel::loadCoverScaleDependentPixmaps() } } -void GameListModel::updateCoverScale() -{ - loadCoverScaleDependentPixmaps(); - emit coverScaleChanged(m_cover_scale); - emit dataChanged(index(0, Column_Cover), index(rowCount() - 1, Column_Cover), - {Qt::DecorationRole, Qt::FontRole, Qt::SizeHintRole}); -} - void GameListModel::updateCacheSize(int num_rows, int num_columns, QSortFilterProxyModel* const sort_model, int top_left_row) { @@ -1108,11 +1098,10 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const { - QVariant ret; - if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < Column_Count) - ret = QCoreApplication::translate("GameListModel", s_column_names[static_cast(section)]); + if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section <= Column_LastVisible) + return QString::fromUtf8(GameList::GetColumnDisplayName(static_cast(section))); - return ret; + return {}; } const QPixmap& GameListModel::getNoAchievementsPixmap() const @@ -1162,16 +1151,6 @@ void GameListModel::refresh() endResetModel(); } -bool GameListModel::titlesLessThan(const GameList::Entry* left, const GameList::Entry* right) const -{ - const s32 res = StringUtil::CompareNoCase(left->GetSortTitle(), right->GetSortTitle()); - if (res != 0) - return (res < 0); - - // Fallback to path compare if titles are the same. - return (left->path < right->path); -} - bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const { if (!left_index.isValid() || !right_index.isValid()) @@ -1188,7 +1167,7 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r if (!left || !right) return false; - return lessThan(left, right, column); + return GameList::CompareEntryLessThan(left, right, static_cast(column)); } else { @@ -1198,169 +1177,7 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r if (!left || !right) return false; - return lessThan(left, right, column); - } -} - -bool GameListModel::lessThan(const GameList::Entry* left, const GameList::Entry* right, int column) const -{ - switch (column) - { - case Column_Icon: - { - const GameList::EntryType lst = left->GetSortType(); - const GameList::EntryType rst = right->GetSortType(); - if (lst == rst) - return titlesLessThan(left, right); - - return (static_cast(lst) < static_cast(rst)); - } - - case Column_Serial: - { - if (left->serial == right->serial) - return titlesLessThan(left, right); - return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0); - } - - case Column_Title: - { - return titlesLessThan(left, right); - } - - case Column_FileTitle: - { - const std::string_view file_title_left = Path::GetFileTitle(left->path); - const std::string_view file_title_right = Path::GetFileTitle(right->path); - if (file_title_left == file_title_right) - return titlesLessThan(left, right); - - const std::size_t smallest = std::min(file_title_left.size(), file_title_right.size()); - return (StringUtil::Strncasecmp(file_title_left.data(), file_title_right.data(), smallest) < 0); - } - - case Column_Region: - { - if (left->region == right->region) - return titlesLessThan(left, right); - return (static_cast(left->region) < static_cast(right->region)); - } - - case Column_Compatibility: - { - const GameDatabase::CompatibilityRating left_compatibility = - left->dbentry ? left->dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown; - const GameDatabase::CompatibilityRating right_compatibility = - right->dbentry ? right->dbentry->compatibility : GameDatabase::CompatibilityRating::Unknown; - if (left_compatibility == right_compatibility) - return titlesLessThan(left, right); - - return (static_cast(left_compatibility) < static_cast(right_compatibility)); - } - - case Column_FileSize: - { - if (left->file_size == right->file_size) - return titlesLessThan(left, right); - - return (left->file_size < right->file_size); - } - - case Column_DataSize: - { - if (left->uncompressed_size == right->uncompressed_size) - return titlesLessThan(left, right); - - return (left->uncompressed_size < right->uncompressed_size); - } - - case Column_Genre: - { - const int compres = - StringUtil::CompareNoCase(left->dbentry ? std::string_view(left->dbentry->genre) : std::string_view(), - right->dbentry ? std::string_view(right->dbentry->genre) : std::string_view()); - return (compres == 0) ? titlesLessThan(left, right) : (compres < 0); - } - - case Column_Developer: - { - const int compres = - StringUtil::CompareNoCase(left->dbentry ? std::string_view(left->dbentry->developer) : std::string_view(), - right->dbentry ? std::string_view(right->dbentry->developer) : std::string_view()); - return (compres == 0) ? titlesLessThan(left, right) : (compres < 0); - } - - case Column_Publisher: - { - const int compres = - StringUtil::CompareNoCase(left->dbentry ? std::string_view(left->dbentry->publisher) : std::string_view(), - right->dbentry ? std::string_view(right->dbentry->publisher) : std::string_view()); - return (compres == 0) ? titlesLessThan(left, right) : (compres < 0); - } - - case Column_Year: - { - const u64 ldate = left->dbentry ? left->dbentry->release_date : 0; - const u64 rdate = right->dbentry ? right->dbentry->release_date : 0; - if (ldate == rdate) - return titlesLessThan(left, right); - - return (ldate < rdate); - } - - case Column_TimePlayed: - { - if (left->total_played_time == right->total_played_time) - return titlesLessThan(left, right); - - return (left->total_played_time < right->total_played_time); - } - - case Column_LastPlayed: - { - if (left->last_played_time == right->last_played_time) - return titlesLessThan(left, right); - - return (left->last_played_time < right->last_played_time); - } - - case Column_Players: - { - const u8 left_players = left->dbentry ? ((left->dbentry->min_players << 4) + left->dbentry->max_players) : 0; - const u8 right_players = right->dbentry ? ((right->dbentry->min_players << 4) + right->dbentry->max_players) : 0; - if (left_players == right_players) - return titlesLessThan(left, right); - - return (left_players < right_players); - } - - case Column_Achievements: - { - // sort by unlock percentage - const float unlock_left = - (left->num_achievements > 0) ? - (static_cast(std::max(left->unlocked_achievements, left->unlocked_achievements_hc)) / - static_cast(left->num_achievements)) : - 0; - const float unlock_right = - (right->num_achievements > 0) ? - (static_cast(std::max(right->unlocked_achievements, right->unlocked_achievements_hc)) / - static_cast(right->num_achievements)) : - 0; - if (std::abs(unlock_left - unlock_right) < 0.0001f) - { - // order by achievement count - if (left->num_achievements == right->num_achievements) - return titlesLessThan(left, right); - - return (left->num_achievements < right->num_achievements); - } - - return (unlock_left < unlock_right); - } - - default: - return false; + return GameList::CompareEntryLessThan(left, right, static_cast(column)); } } @@ -1397,11 +1214,17 @@ class GameListSortModel final : public QSortFilterProxyModel public: explicit GameListSortModel(GameListModel* parent) : QSortFilterProxyModel(parent), m_model(parent) { - m_merge_disc_sets = Core::GetBaseBoolSettingValue("UI", "GameListMergeDiscSets", true); + reloadSettings(); } bool isMergingDiscSets() const { return m_merge_disc_sets; } + void reloadSettings() + { + setMergeDiscSets(Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_MERGE_DISC_SETS, + GameList::DEFAULT_MERGE_DISC_SETS)); + } + void setMergeDiscSets(bool enabled) { beginFilterChange(); @@ -1856,11 +1679,11 @@ GameListWidget::GameListWidget(QWidget* parent, QAction* action_view_list, QActi QString::fromUtf8(Settings::GetDiscRegionDisplayName(static_cast(region)))); } - m_list_view = new GameListListView(m_model, m_sort_model, m_ui.stack); + m_list_view = new GameListListView(this, m_model, m_sort_model, m_ui.stack); m_list_view->setAnimateGameIcons(Core::GetBaseBoolSettingValue("UI", "GameListAnimateGameIcons", false)); m_ui.stack->insertWidget(0, m_list_view); - m_grid_view = new GameListGridView(m_model, m_sort_model, m_ui.stack); + m_grid_view = new GameListGridView(this, m_model, m_sort_model, m_ui.stack); m_grid_view->setItemDelegate(new GameListCoverDelegate(m_grid_view, m_model, m_sort_model)); m_ui.stack->insertWidget(1, m_grid_view); @@ -1911,7 +1734,9 @@ GameListWidget::GameListWidget(QWidget* parent, QAction* action_view_list, QActi action_show_localized_titles->setChecked(m_model->getShowLocalizedTitles()); action_show_list_icons->setChecked(m_model->getShowGameIcons()); action_animate_list_icons->setChecked(m_list_view->isAnimatingGameIcons()); - action_prefer_achievement_game_icons->setChecked(GameList::PreferAchievementGameBadgesForIcons()); + action_prefer_achievement_game_icons->setChecked( + Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_PREFER_ACHIEVEMENT_CONTENT_ICONS, + GameList::DEFAULT_PREFER_ACHIEVEMENT_CONTENT_ICONS)); action_show_grid_titles->setChecked(m_model->getShowCoverTitles()); onIconSizeChanged(m_model->getIconSize()); @@ -1926,6 +1751,18 @@ QString GameListWidget::getSupportedFormatsString() return tr(SUPPORTED_FORMATS_STRING); } +void GameListWidget::reloadSettings() +{ + m_model->reloadSettings(); + m_sort_model->reloadSettings(); + m_list_view->loadColumnSortSettings(); + m_ui.mergeDiscSets->defaultAction()->setChecked(m_sort_model->isMergingDiscSets()); + m_ui.showLocalizedTitles->defaultAction()->setChecked(m_model->getShowLocalizedTitles()); + m_ui.showGridTitles->defaultAction()->setChecked(m_model->getShowCoverTitles()); + m_grid_view->updateLayout(true); + reloadViewModeFromSettings(); +} + bool GameListWidget::isShowingGameList() const { return (m_ui.stack->currentIndex() == VIEW_MODE_LIST); @@ -2185,7 +2022,7 @@ void GameListWidget::showGameList() if (isShowingGameList() || m_model->rowCount() == 0) return; - Core::SetBaseBoolSettingValue("UI", "GameListGridView", false); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListGridView", false); Host::CommitBaseSettingChanges(); setViewMode(VIEW_MODE_LIST); @@ -2197,7 +2034,7 @@ void GameListWidget::showGameGrid() if (isShowingGameGrid() || m_model->rowCount() == 0) return; - Core::SetBaseBoolSettingValue("UI", "GameListGridView", true); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListGridView", true); Host::CommitBaseSettingChanges(); setViewMode(VIEW_MODE_GRID); @@ -2205,7 +2042,8 @@ void GameListWidget::showGameGrid() void GameListWidget::reloadViewModeFromSettings() { - const bool grid_view = Core::GetBaseBoolSettingValue("UI", "GameListGridView", false); + const bool grid_view = Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_GRID_VIEW, + GameList::DEFAULT_GRID_VIEW); m_ui.viewGameList->defaultAction()->setChecked(!grid_view); m_ui.viewGameGrid->defaultAction()->setChecked(grid_view); @@ -2218,7 +2056,7 @@ void GameListWidget::setMergeDiscSets(bool enabled) if (m_sort_model->isMergingDiscSets() == enabled) return; - Core::SetBaseBoolSettingValue("UI", "GameListMergeDiscSets", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_MERGE_DISC_SETS, enabled); Host::CommitBaseSettingChanges(); m_sort_model->setMergeDiscSets(enabled); } @@ -2228,7 +2066,7 @@ void GameListWidget::setShowLocalizedTitles(bool enabled) if (m_model->getShowLocalizedTitles() == enabled) return; - Core::SetBaseBoolSettingValue("UI", "GameListShowLocalizedTitles", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, enabled); Host::CommitBaseSettingChanges(); m_model->setShowLocalizedTitles(enabled); } @@ -2238,7 +2076,7 @@ void GameListWidget::setShowGameIcons(bool enabled) if (m_model->getShowGameIcons() == enabled) return; - Core::SetBaseBoolSettingValue("UI", "GameListShowGameIcons", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListShowGameIcons", enabled); Host::CommitBaseSettingChanges(); m_model->setShowGameIcons(enabled); if (isShowingGameList() && m_list_view->isAnimatingGameIcons()) @@ -2255,7 +2093,7 @@ void GameListWidget::setAnimateGameIcons(bool enabled) if (m_list_view->isAnimatingGameIcons() == enabled) return; - Core::SetBaseBoolSettingValue("UI", "GameListAnimateGameIcons", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "GameListAnimateGameIcons", enabled); Host::CommitBaseSettingChanges(); m_list_view->setAnimateGameIcons(enabled); if (isShowingGameList()) @@ -2264,7 +2102,8 @@ void GameListWidget::setAnimateGameIcons(bool enabled) void GameListWidget::setPreferAchievementGameIcons(bool enabled) { - Core::SetBaseBoolSettingValue("UI", "GameListPreferAchievementGameBadgesForIcons", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_PREFER_ACHIEVEMENT_CONTENT_ICONS, + enabled); Host::CommitBaseSettingChanges(); if (!enabled) @@ -2278,12 +2117,36 @@ void GameListWidget::setShowCoverTitles(bool enabled) if (m_model->getShowCoverTitles() == enabled) return; - Core::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_COVER_TITLES, enabled); Host::CommitBaseSettingChanges(); m_model->setShowCoverTitles(enabled); m_grid_view->updateLayout(false); } +void GameListWidget::setCoverScale(float scale) +{ + if (m_model->getCoverScale() == scale) + return; + + Core::SetBaseFloatSettingValue(GameList::UI_SETTING_SECTION, "GameListCoverArtScale", scale); + Host::CommitBaseSettingChanges(); + + m_model->setCoverScale(scale); +} + +void GameListWidget::setIconSize(int size) +{ + if (m_model->getIconSize() == size) + return; + + Core::SetBaseIntSettingValue(GameList::UI_SETTING_SECTION, "GameListIconSize", size); + Host::CommitBaseSettingChanges(); + + m_model->setIconSize(size); + + m_list_view->updateAnimatedGameIconDelegate(); +} + void GameListWidget::setViewMode(int stack_index) { if (stack_index == VIEW_MODE_GRID && m_grid_view->isLayoutUpdatedDeferred()) @@ -2343,14 +2206,9 @@ void GameListWidget::showScaleToolTip() void GameListWidget::onScaleSliderChanged(int value) { if (isShowingGameGrid()) - { - m_model->setCoverScale(static_cast(value) / 100.0f); - } + setCoverScale(static_cast(value) / 100.0f); else if (isShowingGameList()) - { - m_model->setIconSize(value * GAME_ICON_SIZE_STEP); - m_list_view->updateAnimatedGameIconDelegate(); - } + setIconSize(value * GAME_ICON_SIZE_STEP); if (m_ui.scale->isSliderDown()) showScaleToolTip(); @@ -2419,8 +2277,9 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const } } -GameListListView::GameListListView(GameListModel* model, GameListSortModel* sort_model, QWidget* parent) - : QTableView(parent), m_model(model), m_sort_model(sort_model) +GameListListView::GameListListView(GameListWidget* widget, GameListModel* model, GameListSortModel* sort_model, + QWidget* parent) + : QTableView(parent), m_widget(widget), m_model(model), m_sort_model(sort_model) { setModel(sort_model); setSortingEnabled(true); @@ -2537,12 +2396,12 @@ void GameListListView::updateFixedColumnWidths() static TinyString getColumnVisibilitySettingsKeyName(int column) { - return TinyString::from_format("Show{}", GameListModel::getColumnName(static_cast(column))); + return TinyString::from_format("Show{}", GameList::GetColumnName(static_cast(column))); } void GameListListView::loadColumnVisibilitySettings() { - static constexpr std::array DEFAULT_VISIBILITY = {{ + static constexpr std::array DEFAULT_VISIBILITY = { true, // type true, // serial true, // title @@ -2558,31 +2417,36 @@ void GameListListView::loadColumnVisibilitySettings() false, // size true, // region false, // achievements - false // compatibility - }}; + false, // compatibility + false, // cover, unused + }; + static_assert(std::size(DEFAULT_VISIBILITY) == GameListModel::Column_Count); - for (int column = 0; column < GameListModel::Column_Count; column++) + for (int column = 0; column <= GameListModel::Column_LastVisible; column++) { - const bool visible = Core::GetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), - DEFAULT_VISIBILITY[column]); + const bool visible = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, getColumnVisibilitySettingsKeyName(column), DEFAULT_VISIBILITY[column]); setColumnHidden(column, !visible); } + setColumnHidden(GameListModel::Column_Cover, true); } void GameListListView::loadColumnSortSettings() { - const GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Icon; - const bool DEFAULT_SORT_DESCENDING = false; - - const GameListModel::Column sort_column = - GameListModel::getColumnIdForName(Core::GetBaseStringSettingValue("GameListTableView", "SortColumn")) - .value_or(DEFAULT_SORT_COLUMN); - const bool sort_descending = - Core::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING); + const GameList::Column sort_column = + GameList::ParseColumnName( + Core::GetBaseStringSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_COLUMN)) + .value_or(GameList::DEFAULT_SORT_COLUMN); + const bool sort_descending = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_REVERSED, GameList::DEFAULT_SORT_REVERSED); + const int sort_column_index = static_cast(sort_column); const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder; - m_sort_model->sort(sort_column, sort_order); + m_sort_model->sort(sort_column_index, sort_order); if (QHeaderView* hv = horizontalHeader()) - hv->setSortIndicator(sort_column, sort_order); + { + const QSignalBlocker sb(hv); + hv->setSortIndicator(sort_column_index, sort_order); + } } void GameListListView::saveColumnSortSettings() @@ -2590,13 +2454,13 @@ void GameListListView::saveColumnSortSettings() const int sort_column = horizontalHeader()->sortIndicatorSection(); const bool sort_descending = (horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder); - if (sort_column >= 0 && sort_column < GameListModel::Column_Count) + if (sort_column >= 0 && sort_column <= GameListModel::Column_LastVisible) { - Core::SetBaseStringSettingValue("GameListTableView", "SortColumn", - GameListModel::getColumnName(static_cast(sort_column))); + Core::SetBaseStringSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_COLUMN, + GameList::GetColumnName(static_cast(sort_column))); } - Core::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_descending); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_SORT_REVERSED, sort_descending); Host::CommitBaseSettingChanges(); } @@ -2607,7 +2471,7 @@ void GameListListView::setAndSaveColumnHidden(int column, bool hidden) return; setColumnHidden(column, hidden); - Core::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), !hidden); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, getColumnVisibilitySettingsKeyName(column), !hidden); Host::CommitBaseSettingChanges(); } @@ -2632,8 +2496,7 @@ void GameListListView::onHeaderContextMenuRequested(const QPoint& point) void GameListListView::adjustIconSize(int delta) { const int new_size = std::clamp(m_model->getIconSize() + delta, GAME_ICON_MIN_SIZE, GAME_ICON_MAX_SIZE); - m_model->setIconSize(new_size); - updateAnimatedGameIconDelegate(); + m_widget->setIconSize(new_size); } bool GameListListView::isAnimatingGameIcons() const @@ -2704,8 +2567,9 @@ void GameListListView::clearAnimatedGameIconDelegate() m_animated_icon_row = -1; } -GameListGridView::GameListGridView(GameListModel* model, GameListSortModel* sort_model, QWidget* parent) - : QListView(parent), m_model(model), m_sort_model(sort_model) +GameListGridView::GameListGridView(GameListWidget* widget, GameListModel* model, GameListSortModel* sort_model, + QWidget* parent) + : QListView(parent), m_widget(widget), m_model(model), m_sort_model(sort_model) { setModel(sort_model); setModelColumn(GameListModel::Column_Cover); @@ -2752,7 +2616,7 @@ void GameListGridView::resizeEvent(QResizeEvent* e) void GameListGridView::adjustZoom(float delta) { const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_COVER_SCALE, MAX_COVER_SCALE); - m_model->setCoverScale(new_scale); + m_widget->setCoverScale(new_scale); } void GameListGridView::updateLayout(bool disable_defer) diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index 9c6e0d997..145b943f6 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -64,9 +64,6 @@ public: Column_LastVisible = Column_Compatibility, }; - static std::optional getColumnIdForName(std::string_view name); - static const char* getColumnName(Column col); - explicit GameListModel(GameListWidget* parent); ~GameListModel(); @@ -89,10 +86,10 @@ public: void refresh(); void reloadThemeSpecificImages(); - bool titlesLessThan(const GameList::Entry* left, const GameList::Entry* right) const; - bool lessThan(const GameList::Entry* left, const GameList::Entry* right, int column) const; bool lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const; + void reloadSettings(); + bool getShowLocalizedTitles() const; void setShowLocalizedTitles(bool enabled); @@ -142,9 +139,9 @@ private: void rowsChanged(const QList& rows); + void loadSettings(); void loadCommonImages(); void loadSizeDependentPixmaps(); - void updateCoverScale(); void loadCoverScaleDependentPixmaps(); void loadOrGenerateCover(const GameList::Entry* ge); void coverLoaded(const std::string& path, const QImage& image, float scale); @@ -192,13 +189,14 @@ class GameListListView final : public QTableView Q_OBJECT public: - GameListListView(GameListModel* model, GameListSortModel* sort_model, QWidget* parent); + GameListListView(GameListWidget* widget, GameListModel* model, GameListSortModel* sort_model, QWidget* parent); ~GameListListView() override; QFontMetrics fontMetricsForHorizontalHeader() const; void setFixedColumnWidth(const QFontMetrics& fm, int column, int str_width); void setAndSaveColumnHidden(int column, bool hidden); void updateFixedColumnWidths(); + void loadColumnSortSettings(); void adjustIconSize(int delta); @@ -212,11 +210,11 @@ protected: private: void loadColumnVisibilitySettings(); - void loadColumnSortSettings(); void onHeaderContextMenuRequested(const QPoint& point); void saveColumnSortSettings(); + GameListWidget* m_widget = nullptr; GameListModel* m_model = nullptr; GameListSortModel* m_sort_model = nullptr; @@ -229,7 +227,7 @@ class GameListGridView final : public QListView Q_OBJECT public: - GameListGridView(GameListModel* model, GameListSortModel* sort_model, QWidget* parent); + GameListGridView(GameListWidget* widget, GameListModel* model, GameListSortModel* sort_model, QWidget* parent); ~GameListGridView() override; ALWAYS_INLINE bool isLayoutUpdatedDeferred() const { return m_layout_update_deferred; } @@ -246,6 +244,7 @@ protected: void resizeEvent(QResizeEvent* e) override; private: + GameListWidget* m_widget = nullptr; GameListModel* m_model = nullptr; GameListSortModel* m_sort_model = nullptr; int m_horizontal_offset = 0; @@ -268,6 +267,7 @@ public: ALWAYS_INLINE GameListListView* getListView() const { return m_list_view; } ALWAYS_INLINE GameListGridView* getGridView() const { return m_grid_view; } + void reloadSettings(); void refresh(bool invalidate_cache); void cancelRefresh(); void setBackgroundPath(const std::string_view path); @@ -290,6 +290,8 @@ public: void setAnimateGameIcons(bool enabled); void setPreferAchievementGameIcons(bool enabled); void setShowCoverTitles(bool enabled); + void setCoverScale(float scale); + void setIconSize(int size); void focusSearchWidget(); // Returns translated supported formats string. Formats are separated by newlines. diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index bb58285f9..d2d488c7a 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -36,7 +36,8 @@ using namespace Qt::StringLiterals; -GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindow* dialog, QWidget* parent) +GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, bool localized_titles, SettingsWindow* dialog, + QWidget* parent) : m_dialog(dialog) { m_ui.setupUi(this); @@ -70,7 +71,7 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo m_ui.customLanguage->addItem(QtUtils::GetIconForLanguage(language_name), QString::fromUtf8(language_name)); } - populateUi(entry); + populateUi(entry, localized_titles); connect(m_ui.compatibilityComments, &QAbstractButton::clicked, this, &GameSummaryWidget::onCompatibilityCommentsClicked); @@ -134,7 +135,7 @@ void GameSummaryWidget::reloadGameSettings() m_ui.editInputProfile->setEnabled(m_ui.inputProfile->currentIndex() >= 1); } -void GameSummaryWidget::populateUi(const GameList::Entry* entry) +void GameSummaryWidget::populateUi(const GameList::Entry* entry, bool localized_titles) { m_path = entry->path; @@ -151,7 +152,7 @@ void GameSummaryWidget::populateUi(const GameList::Entry* entry) { m_ui.hashes->setText(tr("N/A")); } - m_ui.title->setText(QtUtils::StringViewToQString(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()))); + m_ui.title->setText(QtUtils::StringViewToQString(entry->GetDisplayTitle(localized_titles))); m_ui.region->setCurrentIndex(static_cast(entry->region)); m_ui.entryType->setCurrentIndex(static_cast(entry->type)); @@ -169,8 +170,7 @@ void GameSummaryWidget::populateUi(const GameList::Entry* entry) entry->disc_set_member ? GameList::GetEntryForPath(entry->GetDiscSetEntry()->GetSaveTitle()) : nullptr; if (disc_set_entry) { - m_ui.discSetTitle->setText( - QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()))); + m_ui.discSetTitle->setText(QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(localized_titles))); m_ui.restoreDiscSetTitle->setEnabled(disc_set_entry->has_custom_title); } else @@ -410,7 +410,8 @@ void GameSummaryWidget::setCustomTitle(const std::string& text) const GameList::Entry* entry = GameList::GetEntryForPath(m_path); if (entry) { - const std::string_view title = entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()); + const std::string_view title = entry->GetDisplayTitle(Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES)); m_dialog->setGameTitle(title); { @@ -440,8 +441,8 @@ void GameSummaryWidget::setCustomDiscSetTitle(const std::string& text) GameList::SaveCustomTitleForPath(disc_set_entry->path, text); const QSignalBlocker sb(m_ui.discSetTitle); - m_ui.discSetTitle->setText( - QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()))); + m_ui.discSetTitle->setText(QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES)))); m_ui.restoreDiscSetTitle->setEnabled(disc_set_entry->has_custom_title); diff --git a/src/duckstation-qt/gamesummarywidget.h b/src/duckstation-qt/gamesummarywidget.h index 5aef92120..729311f61 100644 --- a/src/duckstation-qt/gamesummarywidget.h +++ b/src/duckstation-qt/gamesummarywidget.h @@ -29,13 +29,13 @@ class GameSummaryWidget : public QWidget Q_OBJECT public: - GameSummaryWidget(const GameList::Entry* entry, SettingsWindow* dialog, QWidget* parent); + GameSummaryWidget(const GameList::Entry* entry, bool localized_titles, SettingsWindow* dialog, QWidget* parent); ~GameSummaryWidget(); void reloadGameSettings(); private: - void populateUi(const GameList::Entry* entry); + void populateUi(const GameList::Entry* entry, bool localized_titles); void setCustomTitle(const std::string& text); void setCustomDiscSetTitle(const std::string& text); void setCustomRegion(int region); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 628cf43d7..fb6933c48 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1462,6 +1462,10 @@ void MainWindow::onFullscreenUIStartedOrStopped(bool running) s_locals.fullscreen_ui_started = running; m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture Mode")); m_ui.actionToolbarStartFullscreenUI->setText(running ? tr("Exit Big Picture") : tr("Big Picture")); + + // If big picture was exited, then we should refresh the game list settings because they may have changed. + if (!running && !m_is_closing && !QtHost::InBatchMode()) + m_game_list_widget->reloadSettings(); } void MainWindow::onCloseGameActionTriggered() @@ -3344,9 +3348,9 @@ void MainWindow::onAchievementsLoginSuccess(const QString& username, quint32 poi // Automatically show the achievements column after first login. If the user has manually hidden it, // it will not be automatically shown again. - if (!Core::GetBaseBoolSettingValue("GameListTableView", "TriedShowingAchievementsColumn", false)) + if (!Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "TriedShowingAchievementsColumn", false)) { - Core::SetBaseBoolSettingValue("GameListTableView", "TriedShowingAchievementsColumn", true); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, "TriedShowingAchievementsColumn", true); m_game_list_widget->getListView()->setAndSaveColumnHidden(GameListModel::Column_Achievements, false); } } diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index 100939bbd..bdd7105f7 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -63,11 +63,13 @@ SettingsWindow::SettingsWindow(const GameList::Entry* entry, std::unique_ptrgetIconForGame(QString::fromStdString(entry->path)); !icon.isNull()) setWindowIcon(icon); - setGameTitle(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles())); + const bool show_localized_titles = Core::GetBaseBoolSettingValue( + GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_LOCALIZED_TITLES, GameList::DEFAULT_LOCALIZED_TITLES); + setGameTitle(entry->GetDisplayTitle(show_localized_titles)); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - addWidget(m_game_summary = new GameSummaryWidget(entry, this, m_ui.settingsContainer), tr("Summary"), - u":/icons/monochrome/svg/file-list-line.svg"_s, + addWidget(m_game_summary = new GameSummaryWidget(entry, show_localized_titles, this, m_ui.settingsContainer), + tr("Summary"), u":/icons/monochrome/svg/file-list-line.svg"_s, tr("Summary
This page shows information about the selected game, and allows you to " "validate your disc was dumped correctly.")); addPages(); diff --git a/src/duckstation-qt/setupwizarddialog.cpp b/src/duckstation-qt/setupwizarddialog.cpp index 76347356d..50ed64ad8 100644 --- a/src/duckstation-qt/setupwizarddialog.cpp +++ b/src/duckstation-qt/setupwizarddialog.cpp @@ -695,7 +695,8 @@ void SetupWizardDialog::onAchievementsViewProfilePressed() void SetupWizardDialog::setupGameListViewPage() { - const bool use_grid = Core::GetBaseBoolSettingValue("UI", "GameListGridView", false); + const bool use_grid = Core::GetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_GRID_VIEW, + GameList::DEFAULT_GRID_VIEW); m_ui.listView->setChecked(!use_grid); m_ui.gridView->setChecked(use_grid); @@ -717,8 +718,9 @@ void SetupWizardDialog::onGridViewChanged(bool checked) return; // NOTE: No settings apply here, we explicitly change the layout. - Core::SetBaseBoolSettingValue("UI", "GameListGridView", setting_value); - Core::SetBaseUIntSettingValue("Main", "DefaultFullscreenUIGameView", setting_value ? 0 : 1); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_GRID_VIEW, setting_value); + Core::SetBaseBoolSettingValue(GameList::UI_SETTING_SECTION, GameList::SETTING_KEY_FULLSCREENUI_GRID_VIEW, + setting_value); Host::CommitBaseSettingChanges(); g_main_window->getGameListWidget()->reloadViewModeFromSettings(); }