dolphin/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp
LillyJadeKatrin c57be0efca Refactor Leaderboard widget to allow partial updates
Similarly to the Progress widget (though without the separate object for each box, because each Leaderboard unit is just three text fields stacked vertically), AchievementLeaderboardWidget.UpdateData will now accept three options: destroy all and rebuild, update all, or update a set of rows.

As part of this, AchievementManager::GetLeaderboardsInfo has been refactored to GetLeaderboardInfo to return a single leaderboard for the ID passed in.
2024-05-01 06:43:20 -04:00

157 lines
5.5 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();
UpdateData(true);
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;
rc_api_fetch_game_data_response_t* game_data;
{
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
game_data = instance.GetGameData();
}
for (u32 row = 0; row < game_data->num_leaderboards; row += 2)
{
const auto* leaderboard = game_data->leaderboards + (row / 2);
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);
}
}
}
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