From b824d55093a961efbdcdfd4f330d96306e835070 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 17 Jun 2023 11:47:56 -0400 Subject: [PATCH] Add Leaderboards tab to Achievement dialog A new tab is added to the Achievements dialog to chart out the leaderboards in a table. Each row of the table contains the leaderboard information and up to four relevant entries, varying based on how many entries are in the leaderboard, whether or not the player has a submitted score, and where in the leaderboard the player's score is. --- .../AchievementLeaderboardWidget.cpp | 127 ++++++++++++++++++ .../AchievementLeaderboardWidget.h | 24 ++++ .../Achievements/AchievementsWindow.cpp | 7 + .../Achievements/AchievementsWindow.h | 2 + Source/Core/DolphinQt/CMakeLists.txt | 2 + Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + 6 files changed, 164 insertions(+) create mode 100644 Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp create mode 100644 Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp new file mode 100644 index 0000000000..243bc4453c --- /dev/null +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp @@ -0,0 +1,127 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef USE_RETRO_ACHIEVEMENTS +#include "DolphinQt/Achievements/AchievementLeaderboardWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/AchievementManager.h" +#include "Core/Config/AchievementSettings.h" +#include "Core/Config/MainSettings.h" +#include "Core/Core.h" + +#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h" +#include "DolphinQt/QtUtils/ClearLayoutRecursively.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/NonDefaultQPushButton.h" +#include "DolphinQt/QtUtils/SignalBlocking.h" +#include "DolphinQt/Settings.h" + +AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QWidget(parent) +{ + m_common_box = new QGroupBox(); + m_common_layout = new QGridLayout(); + + { + std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()}; + UpdateData(); + } + + m_common_box->setLayout(m_common_layout); + + auto* layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->setAlignment(Qt::AlignTop); + layout->addWidget(m_common_box); + setLayout(layout); +} + +void AchievementLeaderboardWidget::UpdateData() +{ + ClearLayoutRecursively(m_common_layout); + + if (!AchievementManager::GetInstance()->IsGameLoaded()) + return; + const auto& leaderboards = AchievementManager::GetInstance()->GetLeaderboardsInfo(); + int row = 0; + for (const auto& board_row : leaderboards) + { + const AchievementManager::LeaderboardStatus& board = board_row.second; + QLabel* a_title = new QLabel(QString::fromStdString(board.name)); + QLabel* a_description = new QLabel(QString::fromStdString(board.description)); + QVBoxLayout* a_col_left = new QVBoxLayout(); + a_col_left->addWidget(a_title); + a_col_left->addWidget(a_description); + if (row > 0) + { + QFrame* a_divider = new QFrame(); + a_divider->setFrameShape(QFrame::HLine); + m_common_layout->addWidget(a_divider, row - 1, 0); + } + m_common_layout->addLayout(a_col_left, row, 0); + // Each leaderboard entry is displayed with four values. These are *generally* intended to be, + // in order, the first place entry, the entry one above the player, the player's entry, and + // the entry one below the player. + // Edge cases: + // * If there are fewer than four entries in the leaderboard, all entries will be shown in + // order and the remainder of the list will be padded with empty values. + // * If the player does not currently have a score in the leaderboard, or is in the top 3, + // the four slots will be the top four players in order. + // * If the player is last place, the player will be in the fourth slot, and the second and + // third slots will be the two players above them. The first slot will always be first place. + std::array to_display{1, 2, 3, 4}; + if (board.player_index > to_display.size() - 1) + { + // If the rank one below than the player is found, offset = 1. + u32 offset = static_cast(board.entries.count(board.player_index + 1)); + // Example: player is 10th place but not last + // to_display = {1, 10-3+1+1, 10-3+1+2, 10-3+1+3} = {1, 9, 10, 11} + // Example: player is 15th place and is last + // to_display = {1, 15-3+0+1, 15-3+0+2, 15-3+0+3} = {1, 13, 14, 15} + for (size_t i = 1; i < to_display.size(); ++i) + to_display[i] = board.player_index - 3 + offset + static_cast(i); + } + for (size_t i = 0; i < to_display.size(); ++i) + { + u32 index = to_display[i]; + QLabel* a_rank = new QLabel(QStringLiteral("---")); + QLabel* a_username = new QLabel(QStringLiteral("---")); + QLabel* a_score = new QLabel(QStringLiteral("---")); + const auto it = board.entries.find(index); + if (it != board.entries.end()) + { + a_rank->setText(tr("Rank %1").arg(it->second.rank)); + a_username->setText(QString::fromStdString(it->second.username)); + a_score->setText(QString::fromUtf8(it->second.score.data())); + } + QVBoxLayout* a_col = new QVBoxLayout(); + a_col->addWidget(a_rank); + a_col->addWidget(a_username); + a_col->addWidget(a_score); + if (row > 0) + { + QFrame* a_divider = new QFrame(); + a_divider->setFrameShape(QFrame::HLine); + m_common_layout->addWidget(a_divider, row - 1, static_cast(i) + 1); + } + m_common_layout->addLayout(a_col, row, static_cast(i) + 1); + } + row += 2; + } +} + +#endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h new file mode 100644 index 0000000000..055ea6ab3f --- /dev/null +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h @@ -0,0 +1,24 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef USE_RETRO_ACHIEVEMENTS +#include + +class QGroupBox; +class QGridLayout; + +class AchievementLeaderboardWidget final : public QWidget +{ + Q_OBJECT +public: + explicit AchievementLeaderboardWidget(QWidget* parent); + void UpdateData(); + +private: + QGroupBox* m_common_box; + QGridLayout* m_common_layout; +}; + +#endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index d376da6109..8d01663c87 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -11,6 +11,7 @@ #include #include "DolphinQt/Achievements/AchievementHeaderWidget.h" +#include "DolphinQt/Achievements/AchievementLeaderboardWidget.h" #include "DolphinQt/Achievements/AchievementProgressWidget.h" #include "DolphinQt/Achievements/AchievementSettingsWidget.h" #include "DolphinQt/QtUtils/QueueOnObject.h" @@ -42,10 +43,14 @@ void AchievementsWindow::CreateMainLayout() m_header_widget = new AchievementHeaderWidget(this); m_tab_widget = new QTabWidget(); m_progress_widget = new AchievementProgressWidget(m_tab_widget); + m_leaderboard_widget = new AchievementLeaderboardWidget(m_tab_widget); m_tab_widget->addTab( GetWrappedWidget(new AchievementSettingsWidget(m_tab_widget, this), this, 125, 100), tr("Settings")); m_tab_widget->addTab(GetWrappedWidget(m_progress_widget, this, 125, 100), tr("Progress")); + m_tab_widget->setTabVisible(1, AchievementManager::GetInstance()->IsGameLoaded()); + m_tab_widget->addTab(GetWrappedWidget(m_leaderboard_widget, this, 125, 100), tr("Leaderboards")); + m_tab_widget->setTabVisible(2, AchievementManager::GetInstance()->IsGameLoaded()); m_button_box = new QDialogButtonBox(QDialogButtonBox::Close); @@ -70,6 +75,8 @@ void AchievementsWindow::UpdateData() // Settings tab handles its own updates ... indeed, that calls this m_progress_widget->UpdateData(); m_tab_widget->setTabVisible(1, AchievementManager::GetInstance()->IsGameLoaded()); + m_leaderboard_widget->UpdateData(); + m_tab_widget->setTabVisible(2, AchievementManager::GetInstance()->IsGameLoaded()); } update(); } diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.h b/Source/Core/DolphinQt/Achievements/AchievementsWindow.h index d8407a3a17..6fd7165e1f 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.h +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.h @@ -10,6 +10,7 @@ #include "DolphinQt/QtUtils/QueueOnObject.h" class AchievementHeaderWidget; +class AchievementLeaderboardWidget; class AchievementProgressWidget; class QDialogButtonBox; class QTabWidget; @@ -30,6 +31,7 @@ private: AchievementHeaderWidget* m_header_widget; QTabWidget* m_tab_widget; AchievementProgressWidget* m_progress_widget; + AchievementLeaderboardWidget* m_leaderboard_widget; QDialogButtonBox* m_button_box; }; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 87fc14c766..b271fab7fb 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -30,6 +30,8 @@ add_executable(dolphin-emu CheatsManager.h Achievements/AchievementHeaderWidget.cpp Achievements/AchievementHeaderWidget.h + Achievements/AchievementLeaderboardWidget.cpp + Achievements/AchievementLeaderboardWidget.h Achievements/AchievementProgressWidget.cpp Achievements/AchievementProgressWidget.h Achievements/AchievementSettingsWidget.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index f73ea5c1cc..35f1110e4c 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -51,6 +51,7 @@ + @@ -261,6 +262,7 @@ +