From 29e92705c4b74fc203e413c72b646b4d2ed7394b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 26 Jul 2025 23:11:06 +1000 Subject: [PATCH] FullscreenUI: Add option to hide grid view labels Also handle titles that span multiple lines. --- src/core/fullscreen_ui.cpp | 79 ++++++++++++++++++++++++----------- src/util/imgui_fullscreen.cpp | 32 ++++++++++++++ src/util/imgui_fullscreen.h | 4 ++ 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 2092827cd..8aa5c806d 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -571,6 +571,7 @@ struct ALIGN_TO_CACHE_LINE UIState std::string game_list_current_selection_path; float game_list_current_selection_timeout = 0.0f; bool game_list_show_trophy_icons = true; + bool game_grid_show_titles = true; }; } // namespace @@ -8303,6 +8304,8 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) const ImGuiStyle& style = ImGui::GetStyle(); + const float title_font_size = UIStyle.MediumFontSize; + const float title_font_weight = UIStyle.BoldFontWeight; const float avail_width = ImGui::GetContentRegionAvail().x; const float title_spacing = LayoutScale(10.0f); const float item_width_with_spacing = std::floor(avail_width / 5.0f); @@ -8310,27 +8313,56 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) const float image_width = item_width - (style.FramePadding.x * 2.0f); const float image_height = image_width; const ImVec2 image_size(image_width, image_height); - const float item_height = (style.FramePadding.y * 2.0f) + image_height + title_spacing + UIStyle.MediumFontSize; - const ImVec2 item_size(item_width, item_height); + const float base_item_height = (style.FramePadding.y * 2.0f) + image_height; const u32 grid_count_x = static_cast(std::floor(avail_width / item_width_with_spacing)); // calculate padding to center it, the last item in the row doesn't need spacing const float x_padding = std::floor( (avail_width - ((item_width_with_spacing * static_cast(grid_count_x)) - style.ItemSpacing.x)) * 0.5f); + ImGuiWindow* const window = ImGui::GetCurrentWindow(); + ImDrawList* const dl = ImGui::GetWindowDrawList(); SmallString draw_title; const u32 text_color = ImGui::GetColorU32(ImGuiCol_Text); u32 grid_x = 0; + float row_item_height = base_item_height; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + x_padding); - for (const GameList::Entry* entry : s_state.game_list_sorted_entries) + for (size_t entry_index = 0; entry_index < s_state.game_list_sorted_entries.size(); entry_index++) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) continue; + // This is pretty annoying. If we don't use an equal sized item for each grid item, keyboard/gamepad navigation + // 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_state.game_list_sorted_entries[entry_index]; + if (grid_x == 0 && s_state.game_grid_show_titles) + { + row_item_height = 0.0f; + + const size_t row_entry_index_end = std::min(entry_index + grid_count_x, s_state.game_list_sorted_entries.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_state.game_list_sorted_entries[row_entry_index]; + const ImVec2 this_title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, + image_width, IMSTR_START_END(row_entry->title)); + row_item_height = std::max(row_item_height, this_title_size.y); + } + + row_item_height += title_spacing + base_item_height; + } + + ImVec2 title_size; + if (s_state.game_grid_show_titles) + { + title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, image_width, + IMSTR_START_END(entry->title)); + } + const ImGuiID id = window->GetID(entry->path.c_str(), entry->path.c_str() + entry->path.length()); const ImVec2 pos(window->DC.CursorPos); + const ImVec2 item_size(item_width, row_item_height); ImRect bb(pos, pos + item_size); ImGui::ItemSize(item_size); if (ImGui::ItemAdd(bb, id)) @@ -8358,36 +8390,24 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); - ImGui::GetWindowDrawList()->AddImage(cover_texture, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), - ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + dl->AddImage(cover_texture, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), + IM_COL32(255, 255, 255, 255)); GPUTexture* const cover_trophy = GetGameListCoverTrophy(entry, image_size); if (cover_trophy) { const ImVec2 trophy_size = ImVec2(static_cast(cover_trophy->GetWidth()), static_cast(cover_trophy->GetHeight())); - ImGui::GetWindowDrawList()->AddImage(cover_trophy, bb.Min + image_size - trophy_size, bb.Min + image_size, - ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + dl->AddImage(cover_trophy, bb.Min + image_size - trophy_size, bb.Min + image_size, ImVec2(0.0f, 0.0f), + ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); } - const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); - const char* remaining_text; - UIStyle.Font->CalcTextSizeA(UIStyle.MediumFontSize, UIStyle.NormalFontWeight, bb.GetWidth(), 0.0f, - IMSTR_START_END(entry->title), &remaining_text); - const u32 unclipped_size = static_cast(remaining_text - entry->title.data()); - if (unclipped_size > 0 && unclipped_size != entry->title.size()) + if (draw_title) { - // ellipise title, remove one character to make room - draw_title.format("{}...", std::string_view(entry->title).substr(0, unclipped_size - 1)); - RenderShadowedTextClipped(UIStyle.Font, UIStyle.MediumFontSize, UIStyle.NormalFontWeight, title_bb.Min, - title_bb.Max, text_color, draw_title, nullptr, LAYOUT_CENTER_ALIGN_TEXT, 0.0f, - &title_bb); - } - else - { - RenderShadowedTextClipped(UIStyle.Font, UIStyle.MediumFontSize, UIStyle.NormalFontWeight, title_bb.Min, - title_bb.Max, text_color, entry->title, nullptr, LAYOUT_CENTER_ALIGN_TEXT, 0.0f, - &title_bb); + const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); + ImGuiFullscreen::RenderMultiLineShadowedTextClipped(dl, UIStyle.Font, title_font_size, title_font_weight, + title_bb.Min, title_bb.Max, text_color, entry->title, + LAYOUT_CENTER_ALIGN_TEXT, image_width, &title_bb); } if (pressed) @@ -8603,6 +8623,12 @@ void FullscreenUI::DrawGameListSettingsPage() { s_state.game_list_show_trophy_icons = bsi->GetBoolValue("Main", "FullscreenUIShowTrophyIcons", true); } + if (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)) + { + s_state.game_grid_show_titles = bsi->GetBoolValue("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."), @@ -8807,6 +8833,7 @@ void FullscreenUI::SwitchToGameList() s_state.game_list_view = static_cast(Host::GetBaseIntSettingValue("Main", "DefaultFullscreenUIGameView", 0)); s_state.game_list_show_trophy_icons = Host::GetBaseBoolSettingValue("Main", "FullscreenUIShowTrophyIcons", true); + s_state.game_grid_show_titles = Host::GetBaseBoolSettingValue("Main", "FullscreenUIShowGridTitles", true); s_state.game_list_current_selection_path = {}; s_state.game_list_current_selection_timeout = 0.0f; @@ -9936,6 +9963,7 @@ TRANSLATE_NOOP("FullscreenUI", "Show FPS"); TRANSLATE_NOOP("FullscreenUI", "Show Frame Times"); TRANSLATE_NOOP("FullscreenUI", "Show GPU Statistics"); TRANSLATE_NOOP("FullscreenUI", "Show GPU Usage"); +TRANSLATE_NOOP("FullscreenUI", "Show Grid View Titles"); TRANSLATE_NOOP("FullscreenUI", "Show Latency Statistics"); TRANSLATE_NOOP("FullscreenUI", "Show OSD Messages"); TRANSLATE_NOOP("FullscreenUI", "Show Resolution"); @@ -9956,6 +9984,7 @@ TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part 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."); TRANSLATE_NOOP("FullscreenUI", "Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display."); +TRANSLATE_NOOP("FullscreenUI", "Shows titles underneath the images in the game grid view."); TRANSLATE_NOOP("FullscreenUI", "Shows trophy icons in game grid when games have achievements or have been mastered."); TRANSLATE_NOOP("FullscreenUI", "Simulates the region check present in original, unmodified consoles."); TRANSLATE_NOOP("FullscreenUI", "Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."); diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index ac88b9d5f..32654f301 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -1722,6 +1722,38 @@ void ImGuiFullscreen::RenderShadowedTextClipped(ImFont* font, float font_size, f text_size_if_known, align, wrap_width, clip_rect); } +void ImGuiFullscreen::RenderMultiLineShadowedTextClipped(ImDrawList* draw_list, ImFont* font, float font_size, + float font_weight, const ImVec2& pos_min, + const ImVec2& pos_max, u32 color, std::string_view text, + const ImVec2& align, float wrap_width, + const ImRect* clip_rect /* = nullptr */, + float shadow_offset /* = LayoutScale(LAYOUT_SHADOW_OFFSET) */) +{ + if (text.empty()) + return; + + const char* text_display_end = ImGui::FindRenderedTextEnd(IMSTR_START_END(text)); + const size_t text_len = (text_display_end - text.data()); + if (text_len == 0) + return; + + text = text.substr(0, text_len); + + const char* text_ptr = text.data(); + const char* text_end = text.data() + text_len; + ImVec2 current_pos = pos_min; + while (text_ptr < text_end) + { + const char* line_end = font->CalcWordWrapPosition(font_size, font_weight, text_ptr, text_end, wrap_width); + const ImVec2 line_size = font->CalcTextSizeA(font_size, font_weight, FLT_MAX, 0.0f, text_ptr, line_end); + RenderShadowedTextClipped(draw_list, font, font_size, font_weight, current_pos, pos_max, color, + std::string_view(text_ptr, line_end), &line_size, align, 0.0f, clip_rect, shadow_offset); + + current_pos.y += line_size.y; + text_ptr = line_end; + } +} + void ImGuiFullscreen::RenderAutoLabelText(ImDrawList* draw_list, ImFont* font, float font_size, float font_weight, float label_weight, const ImVec2& pos_min, const ImVec2& pos_max, u32 color, std::string_view text, char separator, float shadow_offset) diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index 11ca33d4c..b6b6d2a81 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -323,6 +323,10 @@ void RenderShadowedTextClipped(ImDrawList* draw_list, ImFont* font, float font_s const ImVec2& pos_min, const ImVec2& pos_max, u32 color, std::string_view text, const ImVec2* text_size_if_known, const ImVec2& align, float wrap_width, const ImRect* clip_rect, float shadow_offset); +void RenderMultiLineShadowedTextClipped(ImDrawList* draw_list, ImFont* font, float font_size, float font_weight, + const ImVec2& pos_min, const ImVec2& pos_max, u32 color, std::string_view text, + const ImVec2& align, float wrap_width, const ImRect* clip_rect = nullptr, + float shadow_offset = LayoutScale(LAYOUT_SHADOW_OFFSET)); void RenderAutoLabelText(ImDrawList* draw_list, ImFont* font, float font_size, float font_weight, float label_weight, const ImVec2& pos_min, const ImVec2& pos_max, u32 color, std::string_view text, char separator = ':', float shadow_offset = LayoutScale(LAYOUT_SHADOW_OFFSET));