From 3b111c9a30508035b7568d000e854222412e154f Mon Sep 17 00:00:00 2001 From: SternXD Date: Mon, 4 May 2026 22:40:17 -0400 Subject: [PATCH] Achievements: Group leaderboards by subset and tracking state --- pcsx2/Achievements.cpp | 420 +++++++++++++++++++++++++++-------------- 1 file changed, 282 insertions(+), 138 deletions(-) diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index ff672d2341bd7..8dcc1161479eb 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -183,6 +183,8 @@ namespace Achievements static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, bool is_self, float rank_column_width, float name_column_width, float time_column_width, float column_spacing); static void OpenSubset(const rc_client_subset_t* subset); + static bool DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only); + static void DrawSubsetSidebarFooter(bool sidebar_has_focus); static void OpenLeaderboard(const rc_client_leaderboard_t* lboard); static void LeaderboardFetchNearbyCallback( int result, const char* error_message, rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata); @@ -237,6 +239,9 @@ namespace Achievements static float s_leaderboard_list_scroll = 0.0f; static bool s_restore_leaderboard_scroll = false; + static std::map, bool> s_achievement_buckets_collapsed; + static std::map, bool> s_leaderboard_buckets_collapsed; + static std::vector s_active_leaderboard_trackers; static std::vector s_active_challenge_indicators; static std::optional s_active_progress_indicator; @@ -2032,6 +2037,9 @@ void Achievements::ClearUIState() rc_client_destroy_achievement_list(s_achievement_list); s_achievement_list = nullptr; } + + s_achievement_buckets_collapsed.clear(); + s_leaderboard_buckets_collapsed.clear(); } template @@ -2419,6 +2427,9 @@ bool Achievements::PrepareAchievementsWindow() CloseSubset(); s_subset_list = rc_client_create_subset_list(s_client); + if (!s_subset_list) + Console.Warning("Achievements: rc_client_create_subset_list() returned null"); + s_achievement_buckets_collapsed.clear(); if (s_achievement_list) rc_client_destroy_achievement_list(s_achievement_list); @@ -2433,6 +2444,131 @@ bool Achievements::PrepareAchievementsWindow() return true; } +bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only) +{ + if (!s_subset_list) + return false; + + using ImGuiFullscreen::g_medium_font; + using ImGuiFullscreen::LayoutScale; + + static constexpr float alpha = 0.8f; + const ImVec4 sidebar_bg(0.10f, 0.10f, 0.10f, alpha); + ImGui::PushStyleColor(ImGuiCol_ChildBg, sidebar_bg); + ImGui::PushStyleColor(ImGuiCol_NavCursor, IM_COL32(0, 0, 0, 0)); + + bool subset_selected = false; + + if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), 0, 0)) + { + sidebar_has_focus = ImGui::IsWindowFocused(); + ImGuiFullscreen::BeginMenuButtons(); + + const float padding = LayoutScale(10.0f); + const float spacing = LayoutScale(8.0f); + const float icon_size = LayoutScale(28.0f); + + for (u32 i = 0; i < s_subset_list->num_subsets; i++) + { + const rc_client_subset_t* subset = s_subset_list->subsets[i]; + if (leaderboards_only && subset->num_leaderboards == 0) + continue; + + TinyString id_str; + id_str.format("sidebar_subset_{}", subset->id); + + ImRect bb; + bool visible, hovered; + ImGuiFullscreen::MenuButtonFrame(id_str, true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, + &visible, &hovered, &bb.Min, &bb.Max, 0, 1.0f); + + if (s_open_subset && s_open_subset->id == subset->id) + { + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x + LayoutScale(4.0f), bb.Max.y), IM_COL32(52, 152, 219, 255)); + } + + const ImVec2 icon_min(bb.Min.x + padding, bb.Min.y + (bb.Max.y - bb.Min.y - icon_size) * 0.5f); + const ImVec2 icon_max(icon_min.x + icon_size, icon_min.y + icon_size); + + std::string badge_path = GetSubsetBadgePath(subset); + if (!badge_path.empty()) + { + GSTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(badge_path); + if (badge) + { + ImGui::GetWindowDrawList()->AddImage(reinterpret_cast(badge->GetNativeHandle()), + icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + } + } + + const ImRect text_bb(ImVec2(icon_max.x + spacing, bb.Min.y), ImVec2(bb.Max.x - padding, bb.Max.y)); + ImGui::PushFont(g_medium_font.first, g_medium_font.second); + ImGuiFullscreen::RenderTextClippedWithShadow( + text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb); + ImGui::PopFont(); + + if (ImGui::IsItemClicked() || ImGui::IsItemActivated()) + { + OpenSubset(subset); + subset_selected = true; + break; + } + } + + ImGuiFullscreen::EndMenuButtons(); + } + ImGui::EndChild(); + + ImGui::PopStyleColor(2); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + ImGui::SameLine(); + ImGui::PopStyleVar(); + return subset_selected; +} + +void Achievements::DrawSubsetSidebarFooter(bool sidebar_has_focus) +{ + if (ImGuiFullscreen::IsGamepadInputSource()) + { + const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs(); + if (sidebar_has_focus) + { + ImGuiFullscreen::SetFullscreenFooterText(std::array{ + std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Navigate Subsets")), + std::make_pair(glyphs.confirm(circleOK), TRANSLATE_SV("Achievements", "Select")), + std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")), + }); + } + else + { + ImGuiFullscreen::SetFullscreenFooterText(std::array{ + std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")), + std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Subsets")), + }); + } + } + else + { + if (sidebar_has_focus) + { + ImGuiFullscreen::SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Navigate Subsets")), + std::make_pair(ICON_PF_ENTER, TRANSLATE_SV("Achievements", "Select")), + std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")), + }); + } + else + { + ImGuiFullscreen::SetFullscreenFooterText(std::array{ + std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Change Selection")), + std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Subsets")), + }); + } + } +} + void Achievements::DrawAchievementsWindow() { using ImGuiFullscreen::g_large_font; @@ -2449,7 +2585,10 @@ void Achievements::DrawAchievementsWindow() Achievements::IdleUpdate(); const bool has_multiple_subsets = (s_subset_list && s_subset_list->num_subsets > 1); - bool s_sidebar_has_focus = false; + static bool sidebar_has_focus = false; + static bool focus_sidebar = false; + if (!has_multiple_subsets) + sidebar_has_focus = false; static constexpr float alpha = 0.8f; static constexpr float heading_alpha = 0.95f; @@ -2493,11 +2632,18 @@ void Achievements::DrawAchievementsWindow() SmallString text; ImVec2 text_size; + const bool wants_close = ImGuiFullscreen::WantsToCloseMenu(); if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) || - ImGuiFullscreen::WantsToCloseMenu()) + (wants_close && (!has_multiple_subsets || sidebar_has_focus))) { + sidebar_has_focus = false; FullscreenUI::ReturnToPreviousWindow(); } + else if (has_multiple_subsets && !sidebar_has_focus && wants_close) + { + sidebar_has_focus = true; + focus_sidebar = true; + } const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + GetLineHeight(g_large_font))); text.assign(s_open_subset ? s_open_subset->title : s_game_title); @@ -2589,75 +2735,13 @@ void Achievements::DrawAchievementsWindow() { if (has_multiple_subsets) { - const ImVec4 sidebar_bg(0.10f, 0.10f, 0.10f, alpha); - ImGui::PushStyleColor(ImGuiCol_ChildBg, sidebar_bg); - - if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), ImGuiChildFlags_NavFlattened, 0)) + if (focus_sidebar) { - ImGuiFullscreen::BeginMenuButtons(); - - for (u32 i = 0; i < s_subset_list->num_subsets; i++) - { - const rc_client_subset_t* subset = s_subset_list->subsets[i]; - TinyString id_str; - id_str.format("sidebar_subset_{}", subset->id); - - ImRect bb; - bool visible, hovered; - ImGuiFullscreen::MenuButtonFrame(id_str, true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, - &visible, &hovered, &bb.Min, &bb.Max, 0, 1.0f); - - if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) - s_sidebar_has_focus = true; - - const float padding = LayoutScale(10.0f); - const float spacing = LayoutScale(8.0f); - const float icon_size = LayoutScale(28.0f); - - if (s_open_subset && s_open_subset->id == subset->id) - { - const float line_width = LayoutScale(4.0f); - const ImVec2 line_min(bb.Min.x, bb.Min.y); - const ImVec2 line_max(bb.Min.x + line_width, bb.Max.y); - ImGui::GetWindowDrawList()->AddRectFilled(line_min, line_max, IM_COL32(52, 152, 219, 255)); - } - - const ImVec2 icon_min(bb.Min.x + padding, bb.Min.y + (bb.Max.y - bb.Min.y - icon_size) * 0.5f); - const ImVec2 icon_max(icon_min.x + icon_size, icon_min.y + icon_size); - - std::string badge_path = GetSubsetBadgePath(subset); - if (!badge_path.empty()) - { - GSTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(badge_path); - if (badge) - { - ImGui::GetWindowDrawList()->AddImage(reinterpret_cast(badge->GetNativeHandle()), - icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); - } - } - - const float text_x = icon_max.x + spacing; - const ImRect text_bb(ImVec2(text_x, bb.Min.y), ImVec2(bb.Max.x - padding, bb.Max.y)); - - ImGui::PushFont(g_medium_font.first, g_medium_font.second); - ImGuiFullscreen::RenderTextClippedWithShadow( - text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb); - ImGui::PopFont(); - - if (ImGui::IsItemClicked() || ImGui::IsItemActivated()) - OpenSubset(subset); - } - - ImGuiFullscreen::EndMenuButtons(); + ImGui::SetNextWindowFocus(); + focus_sidebar = false; } - ImGui::EndChild(); - - if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) - s_sidebar_has_focus = true; - - ImGui::PopStyleColor(); - - ImGui::SameLine(); + if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, false)) + ImGui::SetNextWindowFocus(); } const float content_width = has_multiple_subsets ? (display_size.x - sidebar_width) : display_size.x; @@ -2667,12 +2751,9 @@ void Achievements::DrawAchievementsWindow() ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor)); - if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), ImGuiChildFlags_NavFlattened, 0)) + if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), 0, 0)) { - if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused()) - s_sidebar_has_focus = false; - static std::map, bool> buckets_collapsed; static const char* bucket_names[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = { TRANSLATE_NOOP("Achievements", "Unknown"), TRANSLATE_NOOP("Achievements", "Locked"), @@ -2701,7 +2782,7 @@ void Achievements::DrawAchievementsWindow() pxAssert(bucket.bucket_type < NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS); - bool& bucket_collapsed = buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)]; + bool& bucket_collapsed = s_achievement_buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)]; const char* translated_bucket_name = Host::TranslateToCString("Achievements", bucket_names[bucket.bucket_type]); bucket_collapsed ^= @@ -2726,54 +2807,9 @@ void Achievements::DrawAchievementsWindow() ImGuiFullscreen::EndFullscreenWindow(); if (has_multiple_subsets) - { - if (ImGuiFullscreen::IsGamepadInputSource()) - { - const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; - const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs(); - if (s_sidebar_has_focus) - { - ImGuiFullscreen::SetFullscreenFooterText(std::array{ - std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Navigate Subsets")), - std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Back to List")), - std::make_pair(glyphs.confirm(circleOK), TRANSLATE_SV("Achievements", "Select")), - std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")), - }); - } - else - { - ImGuiFullscreen::SetFullscreenFooterText(std::array{ - std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Navigate Subsets")), - std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")), - std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")), - }); - } - } - else - { - if (s_sidebar_has_focus) - { - ImGuiFullscreen::SetFullscreenFooterText(std::array{ - std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Navigate Subsets")), - std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, TRANSLATE_SV("Achievements", "Back to List")), - std::make_pair(ICON_PF_ENTER, TRANSLATE_SV("Achievements", "Select")), - std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")), - }); - } - else - { - ImGuiFullscreen::SetFullscreenFooterText(std::array{ - std::make_pair(ICON_PF_ARROW_LEFT ICON_PF_ARROW_RIGHT, TRANSLATE_SV("Achievements", "Navigate Subsets")), - std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Change Selection")), - std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")), - }); - } - } - } + DrawSubsetSidebarFooter(sidebar_has_focus); else - { FullscreenUI::SetStandardSelectionFooterText(true); - } } void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo) @@ -2989,9 +3025,21 @@ bool Achievements::PrepareLeaderboardsWindow() s_achievement_badge_paths = {}; CloseLeaderboard(); + + if (s_subset_list) + { + rc_client_destroy_subset_list(s_subset_list); + s_subset_list = nullptr; + } + CloseSubset(); + s_subset_list = rc_client_create_subset_list(client); + if (!s_subset_list) + Console.Warning("Achievements: rc_client_create_subset_list() returned null"); + s_leaderboard_buckets_collapsed.clear(); + if (s_leaderboard_list) rc_client_destroy_leaderboard_list(s_leaderboard_list); - s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); + s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING); if (!s_leaderboard_list) { Console.Error("Achievements: rc_client_create_leaderboard_list() returned null"); @@ -3004,6 +3052,18 @@ bool Achievements::PrepareLeaderboardsWindow() s_restore_leaderboard_scroll = false; s_leaderboard_list_scroll = 0.0f; + if (s_subset_list) + { + for (u32 i = 0; i < s_subset_list->num_subsets; i++) + { + if (s_subset_list->subsets[i]->num_leaderboards > 0) + { + OpenSubset(s_subset_list->subsets[i]); + break; + } + } + } + return true; } @@ -3027,6 +3087,14 @@ void Achievements::DrawLeaderboardsWindow() const bool is_leaderboard_open = (s_open_leaderboard != nullptr); bool close_leaderboard_on_exit = false; + const bool has_multiple_subsets = (s_subset_list && std::count_if(s_subset_list->subsets, + s_subset_list->subsets + s_subset_list->num_subsets, + [](const rc_client_subset_t* subset) { return subset->num_leaderboards > 0; }) > 1); + static bool sidebar_has_focus = false; + static bool focus_sidebar = false; + if (!has_multiple_subsets || is_leaderboard_open) + sidebar_has_focus = false; + ImRect bb; const ImVec4 background(0.13f, 0.13f, 0.13f, alpha); @@ -3089,11 +3157,18 @@ void Achievements::DrawLeaderboardsWindow() if (!is_leaderboard_open) { + const bool wants_close = ImGuiFullscreen::WantsToCloseMenu(); if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) || - ImGuiFullscreen::WantsToCloseMenu()) + (wants_close && (!has_multiple_subsets || sidebar_has_focus))) { + sidebar_has_focus = false; FullscreenUI::ReturnToPreviousWindow(); } + else if (has_multiple_subsets && !sidebar_has_focus && wants_close) + { + sidebar_has_focus = true; + focus_sidebar = true; + } } else { @@ -3250,36 +3325,105 @@ void Achievements::DrawLeaderboardsWindow() if (!is_leaderboard_open) { + const float sidebar_width = has_multiple_subsets ? LayoutScale(200.0f) : 0.0f; + if (ImGuiFullscreen::BeginFullscreenWindow( ImVec2(0.0f, heading_height), ImVec2(display_size.x, display_size.y - heading_height - LayoutScale(ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT)), - "leaderboards", background, 0.0f, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f), 0)) + "leaderboards", background, 0.0f, ImVec2(0.0f, 0.0f), 0)) { - if (s_restore_leaderboard_scroll) + if (has_multiple_subsets) { - ImGui::SetScrollY(s_leaderboard_list_scroll); - s_restore_leaderboard_scroll = false; + if (focus_sidebar) + { + ImGui::SetNextWindowFocus(); + focus_sidebar = false; + } + if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, true)) + ImGui::SetNextWindowFocus(); } - const bool had_pending_focus_request = s_restore_leaderboard_focus; - - ImGuiFullscreen::BeginMenuButtons(); + const float content_width = has_multiple_subsets ? (display_size.x - sidebar_width) : display_size.x; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, background); + ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor)); - for (u32 bucket_index = 0; bucket_index < s_leaderboard_list->num_buckets; bucket_index++) + if (ImGui::BeginChild("leaderboards_content", ImVec2(content_width, 0.0f), 0, 0)) { - const rc_client_leaderboard_bucket_t& bucket = s_leaderboard_list->buckets[bucket_index]; - for (u32 i = 0; i < bucket.num_leaderboards; i++) - DrawLeaderboardListEntry(bucket.leaderboards[i]); - } - ImGuiFullscreen::EndMenuButtons(); + if (s_restore_leaderboard_scroll) + { + ImGui::SetScrollY(s_leaderboard_list_scroll); + s_restore_leaderboard_scroll = false; + } + + const bool had_pending_focus_request = s_restore_leaderboard_focus; + + ImGuiFullscreen::BeginMenuButtons(); + + static const char* lboard_bucket_names[NUM_RC_CLIENT_LEADERBOARD_BUCKETS] = { + TRANSLATE_NOOP("Achievements", "Unknown"), + TRANSLATE_NOOP("Achievements", "Inactive"), + TRANSLATE_NOOP("Achievements", "Active"), + TRANSLATE_NOOP("Achievements", "Unsupported"), + TRANSLATE_NOOP("Achievements", "All"), + }; + const bool show_bucket_headers = (s_leaderboard_list->num_buckets > 1); + + for (u32 bucket_index = 0; bucket_index < s_leaderboard_list->num_buckets; bucket_index++) + { + const rc_client_leaderboard_bucket_t& bucket = s_leaderboard_list->buckets[bucket_index]; + if (s_open_subset && bucket.subset_id != 0 && bucket.subset_id != s_open_subset->id) + continue; + if (show_bucket_headers) + { + pxAssert(bucket.bucket_type < NUM_RC_CLIENT_LEADERBOARD_BUCKETS); + const char* type_name = Host::TranslateToCString("Achievements", lboard_bucket_names[bucket.bucket_type]); + + TinyString bucket_label; + if (bucket.subset_id != 0 && !s_open_subset && s_subset_list) + { + auto subset_iter = std::find_if(s_subset_list->subsets, s_subset_list->subsets + s_subset_list->num_subsets, + [&](const rc_client_subset_t* s) { return s->id == bucket.subset_id; }); + if (subset_iter != s_subset_list->subsets + s_subset_list->num_subsets) + bucket_label.format("{} - {}", (*subset_iter)->title, type_name); + else + bucket_label.assign(type_name); + } + else + { + bucket_label.assign(type_name); + } - if (had_pending_focus_request && s_restore_leaderboard_focus) - s_restore_leaderboard_focus = false; + bool& bucket_collapsed = s_leaderboard_buckets_collapsed[std::make_pair(bucket.subset_id, bucket.bucket_type)]; + if (ImGuiFullscreen::MenuHeadingButton( + bucket_label, bucket_collapsed ? ICON_FA_CHEVRON_DOWN : ICON_FA_CHEVRON_UP)) + bucket_collapsed = !bucket_collapsed; + if (bucket_collapsed) + continue; + } + for (u32 i = 0; i < bucket.num_leaderboards; i++) + DrawLeaderboardListEntry(bucket.leaderboards[i]); + } + + ImGuiFullscreen::EndMenuButtons(); + + if (had_pending_focus_request && s_restore_leaderboard_focus) + s_restore_leaderboard_focus = false; + } + ImGui::EndChild(); + + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(); } ImGuiFullscreen::EndFullscreenWindow(); - FullscreenUI::SetStandardSelectionFooterText(true); + if (has_multiple_subsets) + DrawSubsetSidebarFooter(sidebar_has_focus); + else + FullscreenUI::SetStandardSelectionFooterText(true); } else {