Favorites in the game list (#2649)

Changed how favorites are saved to match PR #2984. Adjusted the favorite
icon size. Fixed bug where favorites were inconsistent when changing to
list mode. Instantly sort list when adding or removing a favorite.

Co-authored-by: David Antunes <david.f.antunes@tecnico.ulisboa.pt>
This commit is contained in:
David Antunes 2025-06-05 23:43:02 +01:00 committed by Joao Ribeiro
parent 226058d2e9
commit 327144c874
10 changed files with 180 additions and 20 deletions

View File

@ -29,6 +29,7 @@ path = [
"src/images/discord.png",
"src/images/dump_icon.png",
"src/images/exit_icon.png",
"src/images/favorite_icon.png",
"src/images/file_icon.png",
"src/images/trophy_icon.png",
"src/images/flag_china.png",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -34,7 +34,8 @@ GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
&GameGridFrame::RefreshGridBackgroundImage);
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false);
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, false);
PopulateGameGrid(m_game_info->m_games, false);
});
}
@ -88,6 +89,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
this->crtColumn = -1;
QVector<GameInfo> m_games_;
this->clearContents();
SortByFavorite();
if (fromSearch)
m_games_ = m_games_search;
else
@ -110,14 +112,21 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
for (int i = 0; i < m_games_.size(); i++) {
QWidget* widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
QLabel* image_label = new QLabel();
QWidget* image_container = new QWidget();
image_container->setFixedSize(icon_size, icon_size);
QLabel* image_label = new QLabel(image_container);
QImage icon = m_games_[gameCounter].icon.scaled(
QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image_label->setFixedSize(icon.width(), icon.height());
image_label->setPixmap(QPixmap::fromImage(icon));
image_label->move(0, 0);
SetFavoriteIcon(image_container, m_games_, gameCounter);
QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial));
name_label->setAlignment(Qt::AlignHCenter);
layout->addWidget(image_label);
layout->addWidget(image_container);
layout->addWidget(name_label);
// Resizing of font-size.
@ -225,3 +234,41 @@ void GameGridFrame::resizeEvent(QResizeEvent* event) {
bool GameGridFrame::IsValidCellSelected() {
return validCellSelected;
}
void GameGridFrame::SetFavoriteIcon(QWidget* parentWidget, QVector<GameInfo> m_games_,
int gameCounter) {
QString serialStr = QString::fromStdString(m_games_[gameCounter].serial);
bool isFavorite = m_gui_settings->GetValue(gui::favorites, serialStr, false).toBool();
QLabel* label = new QLabel(parentWidget);
label->setPixmap(
QPixmap(":images/favorite_icon.png")
.scaled(icon_size / 3.8, icon_size / 3.8, Qt::KeepAspectRatio, Qt::SmoothTransformation));
label->move(icon_size - icon_size / 4, 2);
label->raise();
label->setVisible(isFavorite);
label->setObjectName("favoriteIcon");
}
void GameGridFrame::SortByFavorite() {
std::sort(
m_game_info->m_games.begin(), m_game_info->m_games.end(),
[this](const GameInfo& a, const GameInfo& b) { return this->CompareWithFavorite(a, b); });
}
bool GameGridFrame::CompareWithFavorite(GameInfo a, GameInfo b) {
std::string serial_a = a.serial;
std::string serial_b = b.serial;
QString serialStr_a = QString::fromStdString(a.serial);
QString serialStr_b = QString::fromStdString(b.serial);
bool isFavorite_a = m_gui_settings->GetValue(gui::favorites, serialStr_a, false).toBool();
bool isFavorite_b = m_gui_settings->GetValue(gui::favorites, serialStr_b, false).toBool();
if (isFavorite_a != isFavorite_b) {
return isFavorite_a;
} else {
std::string name_a = a.name, name_b = b.name;
std::transform(name_a.begin(), name_a.end(), name_a.begin(), ::tolower);
std::transform(name_b.begin(), name_b.end(), name_b.begin(), ::tolower);
return name_a < name_b;
}
}

View File

@ -39,6 +39,8 @@ private:
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes
std::shared_ptr<gui_settings> m_gui_settings;
void SetFavoriteIcon(QWidget* parentWidget, QVector<GameInfo> m_games_, int gameCounter);
bool CompareWithFavorite(GameInfo a, GameInfo b);
public:
explicit GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
@ -47,6 +49,7 @@ public:
QWidget* parent = nullptr);
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
bool IsValidCellSelected();
void SortByFavorite();
bool cellClicked = false;
int icon_size;

View File

@ -30,9 +30,8 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
this->horizontalHeader()->setHighlightSections(false);
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setStretchLastSection(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
this->setColumnCount(10);
this->setColumnCount(11);
this->setColumnWidth(1, 300); // Name
this->setColumnWidth(2, 140); // Compatibility
this->setColumnWidth(3, 120); // Serial
@ -41,16 +40,24 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
this->setColumnWidth(6, 90); // Size
this->setColumnWidth(7, 90); // Version
this->setColumnWidth(8, 120); // Play Time
this->setColumnWidth(10, 90); // Favorite
QStringList headers;
headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region")
<< tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path");
<< tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path")
<< tr("Favorite");
this->setHorizontalHeaderLabels(headers);
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed);
this->horizontalHeader()->setSectionResizeMode(9, QHeaderView::Stretch);
this->horizontalHeader()->setSectionResizeMode(10, QHeaderView::Fixed);
PopulateGameList();
connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) {
ToggleFavorite(row, column);
PopulateGameList(false);
});
connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged);
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this,
&GameListFrame::RefreshListBackgroundImage);
@ -65,17 +72,20 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
SortNameDescending(columnIndex);
this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder);
ListSortedAsc = false;
sortColumn = columnIndex;
} else {
SortNameAscending(columnIndex);
this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder);
ListSortedAsc = true;
sortColumn = columnIndex;
}
this->clearContents();
PopulateGameList(false);
});
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, true);
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, true);
PopulateGameList(false);
});
connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) {
@ -116,11 +126,8 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) {
this->setRowCount(m_game_info->m_games.size());
ResizeIcons(icon_size);
if (isInitialPopulation) {
SortNameAscending(1); // Column 1 = Name
ResizeIcons(icon_size);
}
ApplyLastSorting(isInitialPopulation);
for (int i = 0; i < m_game_info->m_games.size(); i++) {
SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name));
@ -129,6 +136,7 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) {
SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw));
SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size));
SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version));
SetFavoriteIcon(i, 10);
m_game_info->m_games[i].compatibility =
m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial);
@ -226,20 +234,49 @@ void GameListFrame::resizeEvent(QResizeEvent* event) {
RefreshListBackgroundImage();
}
bool GameListFrame::CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending) {
std::string serial_a = a.serial;
std::string serial_b = b.serial;
QString serialStr_a = QString::fromStdString(a.serial);
QString serialStr_b = QString::fromStdString(b.serial);
bool isFavorite_a = m_gui_settings->GetValue(gui::favorites, serialStr_a, false).toBool();
bool isFavorite_b = m_gui_settings->GetValue(gui::favorites, serialStr_b, false).toBool();
if (isFavorite_a != isFavorite_b) {
return isFavorite_a;
} else if (ascending) {
return CompareStringsAscending(a, b, columnIndex);
} else {
return CompareStringsDescending(a, b, columnIndex);
}
}
void GameListFrame::SortNameAscending(int columnIndex) {
std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(),
[columnIndex](const GameInfo& a, const GameInfo& b) {
return CompareStringsAscending(a, b, columnIndex);
[this, columnIndex](const GameInfo& a, const GameInfo& b) {
return this->CompareWithFavorite(a, b, columnIndex, true);
});
}
void GameListFrame::SortNameDescending(int columnIndex) {
std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(),
[columnIndex](const GameInfo& a, const GameInfo& b) {
return CompareStringsDescending(a, b, columnIndex);
[this, columnIndex](const GameInfo& a, const GameInfo& b) {
return this->CompareWithFavorite(a, b, columnIndex, false);
});
}
void GameListFrame::ApplyLastSorting(bool isInitialPopulation) {
if (isInitialPopulation) {
SortNameAscending(1); // Column 1 = Name
ResizeIcons(icon_size);
} else if (ListSortedAsc) {
SortNameAscending(sortColumn);
ResizeIcons(icon_size);
} else {
SortNameDescending(sortColumn);
ResizeIcons(icon_size);
}
}
void GameListFrame::ResizeIcons(int iconSize) {
for (int index = 0; auto& game : m_game_info->m_games) {
QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio,
@ -390,6 +427,54 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) {
this->setCellWidget(row, column, widget);
}
void GameListFrame::SetFavoriteIcon(int row, int column) {
QString serialStr = QString::fromStdString(m_game_info->m_games[row].serial);
bool isFavorite = m_gui_settings->GetValue(gui::favorites, serialStr, false).toBool();
QTableWidgetItem* item = new QTableWidgetItem();
QImage scaledPixmap = QImage(":images/favorite_icon.png");
scaledPixmap = scaledPixmap.scaledToHeight(this->columnWidth(column) / 2.5);
scaledPixmap = scaledPixmap.scaledToWidth(this->columnWidth(column) / 2.5);
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(widget);
label->setPixmap(QPixmap::fromImage(scaledPixmap));
label->setObjectName("favoriteIcon");
label->setVisible(isFavorite);
layout->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
widget->setLayout(layout);
this->setItem(row, column, item);
this->setCellWidget(row, column, widget);
if (column > 0) {
this->horizontalHeader()->setSectionResizeMode(column - 1, QHeaderView::Stretch);
}
}
void GameListFrame::ToggleFavorite(int row, int column) {
if (column != 10) {
return;
}
QWidget* cellWidget = this->cellWidget(row, column);
if (!cellWidget) {
return;
}
QLabel* label = cellWidget->findChild<QLabel*>("favoriteIcon");
if (!label) {
return;
}
QString serialStr = QString::fromStdString(m_game_info->m_games[row].serial);
bool isFavorite = m_gui_settings->GetValue(gui::favorites, serialStr, false).toBool();
m_gui_settings->SetValue(gui::favorites, serialStr, !isFavorite, true);
label->setVisible(!isFavorite);
}
QString GameListFrame::GetPlayTime(const std::string& serial) {
QString playTime;
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);

View File

@ -38,15 +38,18 @@ public Q_SLOTS:
void PlayBackgroundMusic(QTableWidgetItem* item);
void onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
int previousColumn);
void ToggleFavorite(int row, int column);
private:
void SetTableItem(int row, int column, QString itemStr);
void SetRegionFlag(int row, int column, QString itemStr);
void SetFavoriteIcon(int row, int column);
void SetCompatibilityItem(int row, int column, CompatibilityEntry entry);
QString GetPlayTime(const std::string& serial);
QList<QAction*> m_columnActs;
GameInfoClass* game_inf_get = nullptr;
bool ListSortedAsc = true;
int sortColumn = 1;
QTableWidgetItem* m_current_item = nullptr;
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes
@ -55,6 +58,7 @@ private:
public:
void PopulateGameList(bool isInitialPopulation = true);
void ResizeIcons(int iconSize);
void ApplyLastSorting(bool isInitialPopulation);
QTableWidgetItem* GetCurrentItem();
QImage backgroundImage;
GameListUtils m_game_list_utils;
@ -130,4 +134,6 @@ public:
return false;
}
}
bool CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending);
};

View File

@ -16,6 +16,7 @@
#include "common/scm_rev.h"
#include "compatibility_info.h"
#include "game_info.h"
#include "gui_settings.h"
#include "trophy_viewer.h"
#ifdef Q_OS_WIN
@ -32,8 +33,10 @@ class GuiContextMenus : public QObject {
public:
void RequestGameMenu(const QPoint& pos, QVector<GameInfo>& m_games,
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
std::shared_ptr<gui_settings> settings,
QTableWidget* widget, bool isList) {
QPoint global_pos = widget->viewport()->mapToGlobal(pos);
std::shared_ptr<gui_settings> m_gui_settings = std::move(settings);
int itemID = 0;
if (isList) {
itemID = widget->currentRow();
@ -63,11 +66,13 @@ public:
menu.addMenu(openFolderMenu);
QAction addToFavorites(tr("Add/Remove Favorite"), widget);
QAction createShortcut(tr("Create Shortcut"), widget);
QAction openCheats(tr("Cheats / Patches"), widget);
QAction openSfoViewer(tr("SFO Viewer"), widget);
QAction openTrophyViewer(tr("Trophy Viewer"), widget);
menu.addAction(&addToFavorites);
menu.addAction(&createShortcut);
menu.addAction(&openCheats);
menu.addAction(&openSfoViewer);
@ -301,6 +306,12 @@ public:
}
}
if (selected == &addToFavorites) {
QString serialStr = QString::fromStdString(m_games[itemID].serial);
bool isFavorite = m_gui_settings->GetValue(gui::favorites, serialStr, false).toBool();
m_gui_settings->SetValue(gui::favorites, serialStr, !isFavorite, true);
}
if (selected == &openCheats) {
QString gameName = QString::fromStdString(m_games[itemID].name);
QString gameSerial = QString::fromStdString(m_games[itemID].serial);

View File

@ -12,6 +12,7 @@ const QString general_settings = "general_settings";
const QString main_window = "main_window";
const QString game_list = "game_list";
const QString game_grid = "game_grid";
const QString favorites = "favorites";
// general
const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false);

View File

@ -563,10 +563,8 @@ void MainWindow::CreateConnects() {
m_game_grid_frame->hide();
m_elf_viewer->hide();
m_game_list_frame->show();
if (m_game_list_frame->item(0, 0) == nullptr) {
m_game_list_frame->clearContents();
m_game_list_frame->PopulateGameList();
}
m_game_list_frame->clearContents();
m_game_list_frame->PopulateGameList();
isTableList = true;
m_gui_settings->SetValue(gui::gl_mode, 0);
int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt();
@ -843,6 +841,13 @@ void MainWindow::CreateConnects() {
}
void MainWindow::StartGame() {
// Ignore favorite column
if (m_game_list_frame->currentItem()->column() == 10) {
m_game_list_frame->ToggleFavorite(m_game_list_frame->currentItem()->row(),
m_game_list_frame->currentItem()->column());
return;
}
BackgroundMusicPlayer::getInstance().stopMusic();
QString gamePath = "";
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();

View File

@ -36,6 +36,7 @@
<file>images/KBM.png</file>
<file>images/fullscreen_icon.png</file>
<file>images/refreshlist_icon.png</file>
<file>images/favorite_icon.png</file>
<file>images/trophy_icon.png</file>
</qresource>
</RCC>