mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 04:16:57 +02:00
trophies: add viewer inspector and wrap toast titles
This commit is contained in:
@@ -195,6 +195,37 @@ std::mutex g_trophy_notification_mutex;
|
|||||||
std::deque<TrophyNotification> g_trophy_notifications;
|
std::deque<TrophyNotification> g_trophy_notifications;
|
||||||
std::atomic<bool> g_trophy_notification_active {false};
|
std::atomic<bool> g_trophy_notification_active {false};
|
||||||
|
|
||||||
|
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
|
||||||
|
std::vector<std::string_view> lines;
|
||||||
|
auto* const font = ImGui::GetFont();
|
||||||
|
for (;;) {
|
||||||
|
const auto newline = text.find('\n');
|
||||||
|
const auto paragraph = text.substr(0, newline);
|
||||||
|
auto line = paragraph.substr(0, 0);
|
||||||
|
for (auto start = paragraph.find_first_not_of(" \t\r"); start != std::string_view::npos;) {
|
||||||
|
const auto end = std::min(paragraph.find_first_of(" \t\r", start), paragraph.size());
|
||||||
|
const auto word = paragraph.substr(start, end - start);
|
||||||
|
const std::string_view candidate(line.empty() ? word.begin() : line.begin(),
|
||||||
|
word.end());
|
||||||
|
// Keep an oversized word intact; the toast's clip rectangle handles overflow.
|
||||||
|
if (!line.empty() && font->CalcTextSizeA(font_size, std::numeric_limits<float>::max(),
|
||||||
|
0.0f, candidate.data(),
|
||||||
|
candidate.data() + candidate.size()).x > width) {
|
||||||
|
lines.push_back(line);
|
||||||
|
line = word;
|
||||||
|
} else {
|
||||||
|
line = candidate;
|
||||||
|
}
|
||||||
|
start = paragraph.find_first_not_of(" \t\r", end);
|
||||||
|
}
|
||||||
|
lines.push_back(line);
|
||||||
|
if (newline == std::string_view::npos) {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
text.remove_prefix(newline + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ClearInputEvents() {
|
void ClearInputEvents() {
|
||||||
std::scoped_lock lock(g_input_mutex);
|
std::scoped_lock lock(g_input_mutex);
|
||||||
g_input_events.clear();
|
g_input_events.clear();
|
||||||
@@ -1101,7 +1132,26 @@ struct SystemOverlay::Impl {
|
|||||||
0.65f, 1.25f);
|
0.65f, 1.25f);
|
||||||
const float width = std::max(
|
const float width = std::max(
|
||||||
0.0f, std::min(520.0f * screen_scale, static_cast<float>(frame_extent.width) - 32.0f));
|
0.0f, std::min(520.0f * screen_scale, static_cast<float>(frame_extent.width) - 32.0f));
|
||||||
const float height = 104.0f * screen_scale;
|
constexpr std::array GRADE_COLORS {
|
||||||
|
IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255), IM_COL32(211, 220, 232, 255),
|
||||||
|
IM_COL32(211, 142, 91, 255)};
|
||||||
|
const int grade = trophy_notification->grade;
|
||||||
|
const bool has_grade = grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size());
|
||||||
|
const float pad = 10.0f * screen_scale;
|
||||||
|
const float base_height = 104.0f * screen_scale;
|
||||||
|
const float icon_size = base_height - 2.0f * pad;
|
||||||
|
const float text_offset = trophy_notification_image != nullptr
|
||||||
|
? pad + icon_size + 18.0f * screen_scale
|
||||||
|
: 20.0f * screen_scale;
|
||||||
|
const float title_offset = text_offset + (has_grade ? 30.0f * screen_scale : 0.0f);
|
||||||
|
const float title_size = 26.0f * screen_scale;
|
||||||
|
const float line_height = 30.0f * screen_scale;
|
||||||
|
const float text_padding = 16.0f * screen_scale;
|
||||||
|
const auto title_lines = WrapTrophyTitle(trophy_notification->name, title_size,
|
||||||
|
width - title_offset - text_padding);
|
||||||
|
const float block_height =
|
||||||
|
static_cast<float>(title_lines.size()) * line_height + 28.0f * screen_scale;
|
||||||
|
const float height = std::max(base_height, block_height + 2.0f * text_padding);
|
||||||
const float rounding = 14.0f * screen_scale;
|
const float rounding = 14.0f * screen_scale;
|
||||||
const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f);
|
const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f);
|
||||||
const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f);
|
const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f);
|
||||||
@@ -1121,26 +1171,19 @@ struct SystemOverlay::Impl {
|
|||||||
draw->AddRect(top_left, bottom_right, fade(255, 255, 255, 28), rounding,
|
draw->AddRect(top_left, bottom_right, fade(255, 255, 255, 28), rounding,
|
||||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||||
|
|
||||||
const float pad = 10.0f * screen_scale;
|
const float text_left = top_left.x + text_offset;
|
||||||
const float icon_size = height - 2.0f * pad;
|
|
||||||
float text_left = top_left.x + 20.0f * screen_scale;
|
|
||||||
if (trophy_notification_image != nullptr) {
|
if (trophy_notification_image != nullptr) {
|
||||||
const ImVec2 icon_min {top_left.x + pad, top_left.y + pad};
|
const ImVec2 icon_min {top_left.x + pad, top_left.y + (height - icon_size) * 0.5f};
|
||||||
const ImVec2 icon_max {icon_min.x + icon_size, icon_min.y + icon_size};
|
const ImVec2 icon_max {icon_min.x + icon_size, icon_min.y + icon_size};
|
||||||
draw->AddImageRounded(trophy_notification_image->GetTexRef(), icon_min, icon_max,
|
draw->AddImageRounded(trophy_notification_image->GetTexRef(), icon_min, icon_max,
|
||||||
{0, 0}, {1, 1}, fade(255, 255, 255, 255), 8.0f * screen_scale);
|
{0, 0}, {1, 1}, fade(255, 255, 255, 255), 8.0f * screen_scale);
|
||||||
draw->AddRect(icon_min, icon_max, fade(255, 255, 255, 40), 8.0f * screen_scale,
|
draw->AddRect(icon_min, icon_max, fade(255, 255, 255, 40), 8.0f * screen_scale,
|
||||||
ImDrawFlags_RoundCornersAll, 1.0f);
|
ImDrawFlags_RoundCornersAll, 1.0f);
|
||||||
text_left = icon_max.x + 18.0f * screen_scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::array GRADE_COLORS {IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255),
|
const float title_y = top_left.y + (height - block_height) * 0.5f;
|
||||||
IM_COL32(211, 220, 232, 255),
|
const float title_x = top_left.x + title_offset;
|
||||||
IM_COL32(211, 142, 91, 255)};
|
if (has_grade) {
|
||||||
const int grade = trophy_notification->grade;
|
|
||||||
const float title_y = top_left.y + height * 0.5f - 25.0f * screen_scale;
|
|
||||||
float title_x = text_left;
|
|
||||||
if (grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size())) {
|
|
||||||
// Small cup glyph tinted with the trophy grade.
|
// Small cup glyph tinted with the trophy grade.
|
||||||
const ImU32 cup = ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha);
|
const ImU32 cup = ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha);
|
||||||
const float u = screen_scale;
|
const float u = screen_scale;
|
||||||
@@ -1155,12 +1198,15 @@ struct SystemOverlay::Impl {
|
|||||||
draw->AddRectFilled({cx - 1.5f * u, cy + 2.0f * u}, {cx + 1.5f * u, cy + 7.0f * u}, cup);
|
draw->AddRectFilled({cx - 1.5f * u, cy + 2.0f * u}, {cx + 1.5f * u, cy + 7.0f * u}, cup);
|
||||||
draw->AddRectFilled({cx - 5.0f * u, cy + 7.0f * u}, {cx + 5.0f * u, cy + 10.0f * u}, cup,
|
draw->AddRectFilled({cx - 5.0f * u, cy + 7.0f * u}, {cx + 5.0f * u, cy + 10.0f * u}, cup,
|
||||||
1.5f * u);
|
1.5f * u);
|
||||||
title_x += 30.0f * u;
|
|
||||||
}
|
}
|
||||||
draw->PushClipRect(top_left, {bottom_right.x - 16.0f * screen_scale, bottom_right.y}, true);
|
draw->PushClipRect(top_left, {bottom_right.x - text_padding, bottom_right.y}, true);
|
||||||
draw->AddText(nullptr, 26.0f * screen_scale, {title_x, title_y},
|
float line_y = title_y;
|
||||||
fade(255, 255, 255, 255), trophy_notification->name.c_str());
|
for (const auto line : title_lines) {
|
||||||
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, title_y + 38.0f * screen_scale},
|
draw->AddText(nullptr, title_size, {title_x, line_y}, fade(255, 255, 255, 255),
|
||||||
|
line.data(), line.data() + line.size());
|
||||||
|
line_y += line_height;
|
||||||
|
}
|
||||||
|
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, line_y + 8.0f * screen_scale},
|
||||||
fade(176, 180, 192, 255), "Trophy earned!");
|
fade(176, 180, 192, 255), "Trophy earned!");
|
||||||
draw->PopClipRect();
|
draw->PopClipRect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QGridLayout>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -25,6 +27,8 @@
|
|||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$"));
|
static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$"));
|
||||||
@@ -54,17 +58,115 @@ static QString GradeToText(int grade) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString TrophyTooltip(const Common::Trophies::Trophy& trophy) {
|
QString TrophyName(int id, const Common::Trophies::Trophy& trophy, bool masked) {
|
||||||
QStringList lines {QString::fromStdString(trophy.name),
|
return masked ? QObject::tr("Hidden trophy")
|
||||||
QString::fromStdString(trophy.description),
|
: trophy.name.empty() ? QObject::tr("Trophy %1").arg(id)
|
||||||
QObject::tr("Grade: %1").arg(GradeToText(trophy.grade))};
|
: QString::fromStdString(trophy.name);
|
||||||
if (trophy.hidden) {
|
}
|
||||||
lines.append(QObject::tr("Hidden trophy"));
|
|
||||||
|
QPixmap TrophyIcon(const Common::Trophies::Trophy& trophy, bool unlocked, bool masked, int size) {
|
||||||
|
QPixmap pixmap;
|
||||||
|
if (masked) {
|
||||||
|
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
||||||
|
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
||||||
|
static_cast<uint>(trophy.icon_png.size())) &&
|
||||||
|
!unlocked) {
|
||||||
|
auto image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||||
|
for (int y = 0; y < image.height(); ++y) {
|
||||||
|
auto* line = reinterpret_cast<QRgb*>(image.scanLine(y));
|
||||||
|
for (int x = 0; x < image.width(); ++x) {
|
||||||
|
const int gray = qGray(line[x]);
|
||||||
|
line[x] = qRgba(gray, gray, gray, qAlpha(line[x]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixmap = QPixmap::fromImage(image);
|
||||||
}
|
}
|
||||||
if (trophy.has_reward && !trophy.reward.empty()) {
|
return pixmap.isNull()
|
||||||
lines.append(QObject::tr("Reward: %1").arg(QString::fromStdString(trophy.reward)));
|
? pixmap
|
||||||
|
: pixmap.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TrophyEarnedDate(int id, const Common::Trophies::UnlockData& unlocks) {
|
||||||
|
const auto found = unlocks.timestamps.find(id);
|
||||||
|
if (found == unlocks.timestamps.end()) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
return lines.join(QLatin1Char('\n'));
|
return QDateTime::fromMSecsSinceEpoch(
|
||||||
|
static_cast<qint64>((found->second - Common::Trophies::UnixEpochTick) / 1000))
|
||||||
|
.toLocalTime()
|
||||||
|
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowTrophyInspector(QWidget* parent, int id, const Common::Trophies::Trophy& trophy,
|
||||||
|
bool unlocked, const QString& earned_date) {
|
||||||
|
QDialog dialog(parent);
|
||||||
|
dialog.setWindowTitle(QObject::tr("Trophy Details"));
|
||||||
|
dialog.resize(640, 380);
|
||||||
|
auto* layout = new QVBoxLayout(&dialog);
|
||||||
|
auto* top = new QHBoxLayout;
|
||||||
|
auto* icon = new QLabel(&dialog);
|
||||||
|
icon->setFixedSize(148, 148);
|
||||||
|
icon->setAlignment(Qt::AlignCenter);
|
||||||
|
auto* text_box = new QVBoxLayout;
|
||||||
|
auto* name = new QLabel(&dialog);
|
||||||
|
name->setTextFormat(Qt::PlainText);
|
||||||
|
name->setStyleSheet(QStringLiteral("font-size: 26px;"));
|
||||||
|
name->setWordWrap(true);
|
||||||
|
auto* subtitle = new QLabel(QObject::tr("Hidden trophy"), &dialog);
|
||||||
|
subtitle->setStyleSheet(QStringLiteral("font-size: 16px;"));
|
||||||
|
auto* reveal = new QPushButton(QObject::tr("Show Hidden Trophy"), &dialog);
|
||||||
|
text_box->addWidget(name);
|
||||||
|
text_box->addWidget(subtitle);
|
||||||
|
text_box->addWidget(reveal, 0, Qt::AlignLeft);
|
||||||
|
text_box->addStretch(1);
|
||||||
|
top->addWidget(icon);
|
||||||
|
top->addSpacing(16);
|
||||||
|
top->addLayout(text_box, 1);
|
||||||
|
layout->addLayout(top);
|
||||||
|
|
||||||
|
auto* grid = new QGridLayout;
|
||||||
|
grid->setColumnStretch(1, 1);
|
||||||
|
const auto make_row = [&](const QString& label, const QString& text = {}) {
|
||||||
|
auto* key = new QLabel(label, &dialog);
|
||||||
|
auto* value = new QLabel(text, &dialog);
|
||||||
|
key->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||||
|
value->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||||
|
value->setTextFormat(Qt::PlainText);
|
||||||
|
value->setWordWrap(true);
|
||||||
|
const int row = grid->rowCount();
|
||||||
|
grid->addWidget(key, row, 0);
|
||||||
|
grid->addWidget(value, row, 1);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
auto* grade = make_row(QObject::tr("Grade"));
|
||||||
|
make_row(QObject::tr("Status"), unlocked ? QObject::tr("Earned") : QObject::tr("Not earned"));
|
||||||
|
make_row(QObject::tr("Earned date"),
|
||||||
|
unlocked && !earned_date.isEmpty() ? earned_date : QStringLiteral("-"));
|
||||||
|
auto* details = make_row(QObject::tr("Details"));
|
||||||
|
auto* reward =
|
||||||
|
trophy.has_reward && !trophy.reward.empty() ? make_row(QObject::tr("Reward")) : nullptr;
|
||||||
|
layout->addLayout(grid);
|
||||||
|
layout->addStretch(1);
|
||||||
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
|
||||||
|
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
|
||||||
|
const auto refresh = [&](bool masked) {
|
||||||
|
icon->setPixmap(TrophyIcon(trophy, unlocked, masked, 148));
|
||||||
|
name->setText(TrophyName(id, trophy, masked));
|
||||||
|
subtitle->setVisible(trophy.hidden && !masked);
|
||||||
|
reveal->setVisible(masked);
|
||||||
|
grade->setText(masked ? QObject::tr("Hidden trophy") : GradeToText(trophy.grade));
|
||||||
|
details->setText(masked || trophy.description.empty()
|
||||||
|
? QStringLiteral("-")
|
||||||
|
: QString::fromStdString(trophy.description));
|
||||||
|
if (reward != nullptr) {
|
||||||
|
reward->setText(masked ? QStringLiteral("-") : QString::fromStdString(trophy.reward));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
QObject::connect(reveal, &QPushButton::clicked, &dialog, [&] { refresh(false); });
|
||||||
|
refresh(trophy.hidden && !unlocked);
|
||||||
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -337,13 +439,13 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
|||||||
}
|
}
|
||||||
QStringList errors;
|
QStringList errors;
|
||||||
for (const auto& file: trophy_files) {
|
for (const auto& file: trophy_files) {
|
||||||
const auto package =
|
auto package =
|
||||||
Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language);
|
Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language);
|
||||||
if (package.trophies.empty()) {
|
if (package.trophies.empty()) {
|
||||||
errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName()));
|
errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
auto unlocks = LoadUnlocks(info, runtime_directory, file);
|
||||||
const auto counts = Common::Trophies::GetProgress(package, unlocks);
|
const auto counts = Common::Trophies::GetProgress(package, unlocks);
|
||||||
auto* page = new QWidget(m_tabs);
|
auto* page = new QWidget(m_tabs);
|
||||||
auto* page_layout = new QVBoxLayout(page);
|
auto* page_layout = new QVBoxLayout(page);
|
||||||
@@ -395,8 +497,10 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
|||||||
const auto hidden_locked = trophy.hidden && !trophy_unlocked;
|
const auto hidden_locked = trophy.hidden && !trophy_unlocked;
|
||||||
auto* item = new QListWidgetItem(list);
|
auto* item = new QListWidgetItem(list);
|
||||||
item->setSizeHint(QSize(900, 96));
|
item->setSizeHint(QSize(900, 96));
|
||||||
|
item->setData(Qt::UserRole, id);
|
||||||
|
|
||||||
auto* card = new QWidget(list);
|
auto* card = new QWidget(list);
|
||||||
|
card->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
card->setStyleSheet(
|
card->setStyleSheet(
|
||||||
QStringLiteral("QWidget#card { background: palette(base); border-radius: 12px; }"));
|
QStringLiteral("QWidget#card { background: palette(base); border-radius: 12px; }"));
|
||||||
card->setObjectName(QStringLiteral("card"));
|
card->setObjectName(QStringLiteral("card"));
|
||||||
@@ -404,26 +508,12 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
|||||||
auto* icon = new QLabel(card);
|
auto* icon = new QLabel(card);
|
||||||
icon->setFixedSize(76, 76);
|
icon->setFixedSize(76, 76);
|
||||||
icon->setAlignment(Qt::AlignCenter);
|
icon->setAlignment(Qt::AlignCenter);
|
||||||
QPixmap pixmap;
|
icon->setPixmap(TrophyIcon(trophy, trophy_unlocked, hidden_locked, 76));
|
||||||
if (hidden_locked) {
|
|
||||||
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
|
|
||||||
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
|
|
||||||
static_cast<uint>(trophy.icon_png.size())) &&
|
|
||||||
!trophy_unlocked) {
|
|
||||||
pixmap =
|
|
||||||
QPixmap::fromImage(pixmap.toImage().convertToFormat(QImage::Format_Grayscale8));
|
|
||||||
}
|
|
||||||
if (!pixmap.isNull()) {
|
|
||||||
icon->setPixmap(
|
|
||||||
pixmap.scaled(76, 76, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
||||||
}
|
|
||||||
row->addWidget(icon);
|
row->addWidget(icon);
|
||||||
|
|
||||||
auto* left = new QVBoxLayout;
|
auto* left = new QVBoxLayout;
|
||||||
const auto name_text = hidden_locked ? tr("Hidden trophy")
|
auto* name = new QLabel(TrophyName(id, trophy, hidden_locked), card);
|
||||||
: trophy.name.empty() ? tr("Trophy %1").arg(id)
|
name->setTextFormat(Qt::PlainText);
|
||||||
: QString::fromStdString(trophy.name);
|
|
||||||
auto* name = new QLabel(name_text, card);
|
|
||||||
name->setStyleSheet(QStringLiteral("font-size: 20px; font-weight: bold;"));
|
name->setStyleSheet(QStringLiteral("font-size: 20px; font-weight: bold;"));
|
||||||
left->addWidget(name);
|
left->addWidget(name);
|
||||||
if (!hidden_locked) {
|
if (!hidden_locked) {
|
||||||
@@ -440,15 +530,9 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
|||||||
|
|
||||||
row->addLayout(left, 2);
|
row->addLayout(left, 2);
|
||||||
|
|
||||||
auto* right = new QVBoxLayout;
|
auto* right = new QVBoxLayout;
|
||||||
QString date_text;
|
auto date_text = TrophyEarnedDate(id, unlocks);
|
||||||
if (const auto found = unlocks.timestamps.find(id); found != unlocks.timestamps.end()) {
|
if (date_text.isEmpty() && trophy_unlocked) {
|
||||||
date_text = QDateTime::fromMSecsSinceEpoch(
|
|
||||||
static_cast<qint64>(
|
|
||||||
(found->second - Common::Trophies::UnixEpochTick) / 1000))
|
|
||||||
.toLocalTime()
|
|
||||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
|
||||||
} else if (trophy_unlocked) {
|
|
||||||
date_text = tr("Earned");
|
date_text = tr("Earned");
|
||||||
}
|
}
|
||||||
auto* date = new QLabel(date_text, card);
|
auto* date = new QLabel(date_text, card);
|
||||||
@@ -456,15 +540,22 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
|
|||||||
date->setStyleSheet(QStringLiteral("font-size: 15px;"));
|
date->setStyleSheet(QStringLiteral("font-size: 15px;"));
|
||||||
auto* detail = new QLabel(
|
auto* detail = new QLabel(
|
||||||
hidden_locked ? QString {} : QString::fromStdString(trophy.description), card);
|
hidden_locked ? QString {} : QString::fromStdString(trophy.description), card);
|
||||||
|
detail->setTextFormat(Qt::PlainText);
|
||||||
detail->setWordWrap(true);
|
detail->setWordWrap(true);
|
||||||
detail->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
detail->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||||
detail->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
detail->setStyleSheet(QStringLiteral("font-size: 17px;"));
|
||||||
right->addWidget(date);
|
right->addWidget(date);
|
||||||
right->addWidget(detail, 1);
|
right->addWidget(detail, 1);
|
||||||
row->addLayout(right, 3);
|
row->addLayout(right, 3);
|
||||||
card->setToolTip(hidden_locked ? name_text : TrophyTooltip(trophy));
|
|
||||||
list->setItemWidget(item, card);
|
list->setItemWidget(item, card);
|
||||||
}
|
}
|
||||||
|
connect(list, &QListWidget::itemClicked, this,
|
||||||
|
[this, trophies = std::move(package.trophies),
|
||||||
|
unlocks = std::move(unlocks)](QListWidgetItem* item) {
|
||||||
|
const int id = item->data(Qt::UserRole).toInt();
|
||||||
|
ShowTrophyInspector(this, id, trophies.at(id), unlocks.unlocked.contains(id),
|
||||||
|
TrophyEarnedDate(id, unlocks));
|
||||||
|
});
|
||||||
m_tabs->addTab(page, QString::fromStdString(package.title));
|
m_tabs->addTab(page, QString::fromStdString(package.title));
|
||||||
}
|
}
|
||||||
m_tabs->tabBar()->setVisible(m_tabs->count() > 1);
|
m_tabs->tabBar()->setVisible(m_tabs->count() > 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user