dolphin/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp
LillyJadeKatrin 4214c301ef Refactor AchievementsWindow::UpdateData to take a partial update parameter
UpdateData in AchievementsWindow now only updates the components being requested, massively improving the window's performance. The parameter is UpdatedItems in AchievementManager, which tracks which portions of the system have been updated for every update callback.
2024-05-01 07:04:32 -04:00

160 lines
5.7 KiB
C++

// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef USE_RETRO_ACHIEVEMENTS
#include "DolphinQt/Achievements/AchievementLeaderboardWidget.h"
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QString>
#include <QVBoxLayout>
#include "Common/CommonTypes.h"
#include "Core/AchievementManager.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "DolphinQt/QtUtils/ClearLayoutRecursively.h"
#include "DolphinQt/Settings.h"
AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QWidget(parent)
{
m_common_box = new QGroupBox();
m_common_layout = new QGridLayout();
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(bool clean_all)
{
if (clean_all)
{
ClearLayoutRecursively(m_common_layout);
auto& instance = AchievementManager::GetInstance();
if (!instance.IsGameLoaded())
return;
auto* client = instance.GetClient();
auto* leaderboard_list =
rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
u32 row = 0;
for (u32 bucket = 0; bucket < leaderboard_list->num_buckets; bucket++)
{
const auto& leaderboard_bucket = leaderboard_list->buckets[bucket];
for (u32 board = 0; board < leaderboard_bucket.num_leaderboards; board++)
{
const auto* leaderboard = leaderboard_bucket.leaderboards[board];
m_leaderboard_order[leaderboard->id] = row;
QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title));
QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->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);
for (size_t ix = 0; ix < 4; ix++)
{
QVBoxLayout* a_col = new QVBoxLayout();
for (size_t jx = 0; jx < 3; jx++)
a_col->addWidget(new QLabel(QStringLiteral("---")));
if (row > 0)
{
QFrame* a_divider = new QFrame();
a_divider->setFrameShape(QFrame::HLine);
m_common_layout->addWidget(a_divider, row - 1, static_cast<int>(ix) + 1);
}
m_common_layout->addLayout(a_col, row, static_cast<int>(ix) + 1);
}
row += 2;
}
}
rc_client_destroy_leaderboard_list(leaderboard_list);
}
for (auto row : m_leaderboard_order)
{
UpdateRow(row.second);
}
}
void AchievementLeaderboardWidget::UpdateData(
const std::set<AchievementManager::AchievementId>& update_ids)
{
for (auto row : m_leaderboard_order)
{
if (update_ids.contains(row.first))
{
UpdateRow(row.second);
}
}
}
void AchievementLeaderboardWidget::UpdateRow(AchievementManager::AchievementId leaderboard_id)
{
const auto leaderboard_itr = m_leaderboard_order.find(leaderboard_id);
if (leaderboard_itr == m_leaderboard_order.end())
return;
const int row = leaderboard_itr->second;
const AchievementManager::LeaderboardStatus* board;
{
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
board = AchievementManager::GetInstance().GetLeaderboardInfo(leaderboard_id);
}
if (!board)
return;
// 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<u32, 4> 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<u32>(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 ix = 1; ix < to_display.size(); ++ix)
to_display[ix] = board->player_index - 3 + offset + static_cast<u32>(ix);
}
for (size_t ix = 0; ix < to_display.size(); ++ix)
{
const auto it = board->entries.find(to_display[ix]);
if (it != board->entries.end())
{
QVBoxLayout* a_col = new QVBoxLayout();
a_col->addWidget(new QLabel(tr("Rank %1").arg(it->second.rank)));
a_col->addWidget(new QLabel(QString::fromStdString(it->second.username)));
a_col->addWidget(new QLabel(QString::fromUtf8(it->second.score.data())));
auto old_item = m_common_layout->itemAtPosition(row, static_cast<int>(ix) + 1);
m_common_layout->removeItem(old_item);
ClearLayoutRecursively(static_cast<QLayout*>(old_item));
m_common_layout->addLayout(a_col, row, static_cast<int>(ix) + 1);
}
}
}
#endif // USE_RETRO_ACHIEVEMENTS