Merge pull request #6198 from Kewlan/favorite-games

game_list: Mark games as favorite to make them appear at the top.
This commit is contained in:
bunnei 2021-04-25 23:33:44 -07:00 committed by GitHub
commit 7412f314e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 152 additions and 5 deletions

3
dist/license.md vendored
View file

@ -12,6 +12,7 @@ qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
@ -20,6 +21,7 @@ qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/qdarkstyle/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
@ -28,5 +30,6 @@ qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
qt_themes/colorful/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
<!-- TODO: Add the license of the yuzu icon -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -7,6 +7,7 @@
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="colorful">

View file

@ -10,6 +10,7 @@
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

View file

@ -8,6 +8,7 @@
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="qss_icons">

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

View file

@ -8,6 +8,7 @@
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
</qresource>
<qresource prefix="qss_icons">

View file

@ -358,6 +358,7 @@ chip.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
sd_card.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
star.png | CC BY-ND 3.0 | https://icons8.com
Note:
Some icons are different in different themes, and they are separately listed

View file

@ -937,6 +937,13 @@ void Config::ReadUIGamelistValues() {
UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
UISettings::values.cache_game_list =
ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
for (int i = 0; i < favorites_size; i++) {
qt_config->setArrayIndex(i);
UISettings::values.favorited_ids.append(
ReadSetting(QStringLiteral("program_id")).toULongLong());
}
qt_config->endArray();
qt_config->endGroup();
}
@ -1480,6 +1487,13 @@ void Config::SaveUIGamelistValues() {
WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
qt_config->beginWriteArray(QStringLiteral("favorites"));
for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
qt_config->setArrayIndex(i);
WriteSetting(QStringLiteral("program_id"),
QVariant::fromValue(UISettings::values.favorited_ids[i]));
}
qt_config->endArray();
qt_config->endGroup();
}

View file

@ -11,6 +11,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QKeyEvent>
#include <QList>
#include <QMenu>
#include <QThreadPool>
#include <fmt/format.h>
@ -84,6 +85,10 @@ void GameListSearchField::setFilterResult(int visible, int total) {
label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
}
bool GameListSearchField::isEmpty() const {
return edit_filter->text().isEmpty();
}
QString GameList::GetLastFilterResultItem() const {
QString file_path;
const int folder_count = item_model->rowCount();
@ -187,7 +192,9 @@ void GameList::OnTextChanged(const QString& new_text) {
// If the searchfield is empty every item is visible
// Otherwise the filter gets applied
if (edit_filter_text.isEmpty()) {
for (int i = 0; i < folder_count; ++i) {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
UISettings::values.favorited_ids.size() == 0);
for (int i = 1; i < folder_count; ++i) {
folder = item_model->item(i, 0);
const QModelIndex folder_index = folder->index();
const int children_count = folder->rowCount();
@ -198,8 +205,9 @@ void GameList::OnTextChanged(const QString& new_text) {
}
search_field->setFilterResult(children_total, children_total);
} else {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
int result_count = 0;
for (int i = 0; i < folder_count; ++i) {
for (int i = 1; i < folder_count; ++i) {
folder = item_model->item(i, 0);
const QModelIndex folder_index = folder->index();
const int children_count = folder->rowCount();
@ -280,6 +288,13 @@ void GameList::OnUpdateThemedIcons() {
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
break;
case GameListItemType::Favorites:
child->setData(
QIcon::fromTheme(QStringLiteral("star"))
.pixmap(icon_size)
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
break;
default:
break;
}
@ -427,6 +442,13 @@ void GameList::DonePopulating(const QStringList& watch_list) {
emit ShowList(!IsEmpty());
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
item_model->invisibleRootItem()->insertRow(0, new GameListFavorites());
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
UISettings::values.favorited_ids.size() == 0);
tree_view->expand(item_model->invisibleRootItem()->child(0)->index());
for (const auto id : UISettings::values.favorited_ids) {
AddFavorite(id);
}
// Clear out the old directories to watch for changes and add the new ones
auto watch_dirs = watcher->directories();
@ -446,7 +468,7 @@ void GameList::DonePopulating(const QStringList& watch_list) {
tree_view->setEnabled(true);
const int folder_count = tree_view->model()->rowCount();
int children_total = 0;
for (int i = 0; i < folder_count; ++i) {
for (int i = 1; i < folder_count; ++i) {
children_total += item_model->item(i, 0)->rowCount();
}
search_field->setFilterResult(children_total, children_total);
@ -478,6 +500,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
case GameListItemType::SysNandDir:
AddPermDirPopup(context_menu, selected);
break;
case GameListItemType::Favorites:
AddFavoritesPopup(context_menu);
break;
default:
break;
}
@ -485,6 +510,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
}
void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
QAction* favorite = context_menu.addAction(tr("Favorite"));
context_menu.addSeparator();
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
QAction* open_transferable_shader_cache =
@ -503,6 +530,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
favorite->setVisible(program_id != 0);
favorite->setCheckable(true);
favorite->setChecked(UISettings::values.favorited_ids.contains(program_id));
open_save_location->setVisible(program_id != 0);
open_mod_location->setVisible(program_id != 0);
open_transferable_shader_cache->setVisible(program_id != 0);
@ -513,6 +543,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
connect(favorite, &QAction::triggered, [this, program_id]() { ToggleFavorite(program_id); });
connect(open_save_location, &QAction::triggered, [this, program_id, path]() {
emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
});
@ -576,7 +607,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
const int row = selected.row();
move_up->setEnabled(row > 0);
move_up->setEnabled(row > 1);
move_down->setEnabled(row < item_model->rowCount() - 2);
connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] {
@ -614,6 +645,18 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
});
}
void GameList::AddFavoritesPopup(QMenu& context_menu) {
QAction* clear_all = context_menu.addAction(tr("Clear"));
connect(clear_all, &QAction::triggered, [this] {
for (const auto id : UISettings::values.favorited_ids) {
RemoveFavorite(id);
}
UISettings::values.favorited_ids.clear();
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
});
}
void GameList::LoadCompatibilityList() {
QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
@ -728,6 +771,58 @@ void GameList::RefreshGameDirectory() {
}
}
void GameList::ToggleFavorite(u64 program_id) {
if (!UISettings::values.favorited_ids.contains(program_id)) {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
!search_field->isEmpty());
UISettings::values.favorited_ids.append(program_id);
AddFavorite(program_id);
item_model->sort(tree_view->header()->sortIndicatorSection(),
tree_view->header()->sortIndicatorOrder());
} else {
UISettings::values.favorited_ids.removeOne(program_id);
RemoveFavorite(program_id);
if (UISettings::values.favorited_ids.size() == 0) {
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
}
}
}
void GameList::AddFavorite(u64 program_id) {
auto* favorites_row = item_model->item(0);
for (int i = 1; i < item_model->rowCount() - 1; i++) {
const auto* folder = item_model->item(i);
for (int j = 0; j < folder->rowCount(); j++) {
if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() ==
program_id) {
QList<QStandardItem*> list;
for (int k = 0; k < item_model->columnCount(); k++) {
list.append(folder->child(j, k)->clone());
}
list[0]->setData(folder->child(j)->data(GameListItem::SortRole),
GameListItem::SortRole);
list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString());
favorites_row->appendRow(list);
return;
}
}
}
}
void GameList::RemoveFavorite(u64 program_id) {
auto* favorites_row = item_model->item(0);
for (int i = 0; i < favorites_row->rowCount(); i++) {
const auto* game = favorites_row->child(i);
if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) {
favorites_row->removeRow(i);
return;
}
}
}
GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
connect(parent, &GMainWindow::UpdateThemedIcons, this,
&GameListPlaceholder::onUpdateThemedIcons);

View file

@ -112,10 +112,15 @@ private:
void RefreshGameDirectory();
void ToggleFavorite(u64 program_id);
void AddFavorite(u64 program_id);
void RemoveFavorite(u64 program_id);
void PopupContextMenu(const QPoint& menu_location);
void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
void AddFavoritesPopup(QMenu& context_menu);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;

View file

@ -29,7 +29,8 @@ enum class GameListItemType {
SdmcDir = QStandardItem::UserType + 3,
UserNandDir = QStandardItem::UserType + 4,
SysNandDir = QStandardItem::UserType + 5,
AddDir = QStandardItem::UserType + 6
AddDir = QStandardItem::UserType + 6,
Favorites = QStandardItem::UserType + 7,
};
Q_DECLARE_METATYPE(GameListItemType);
@ -310,6 +311,28 @@ public:
}
};
class GameListFavorites : public GameListItem {
public:
explicit GameListFavorites() {
setData(type(), TypeRole);
const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
setData(QIcon::fromTheme(QStringLiteral("star"))
.pixmap(icon_size)
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
setData(QObject::tr("Favorites"), Qt::DisplayRole);
}
int type() const override {
return static_cast<int>(GameListItemType::Favorites);
}
bool operator<(const QStandardItem& other) const override {
return false;
}
};
class GameList;
class QHBoxLayout;
class QTreeView;
@ -324,6 +347,7 @@ public:
explicit GameListSearchField(GameList* parent = nullptr);
void setFilterResult(int visible, int total);
bool isEmpty() const;
void clear();
void setFocus();

View file

@ -74,6 +74,7 @@ struct Values {
QString game_dir_deprecated;
bool game_dir_deprecated_deepscan;
QVector<UISettings::GameDir> game_dirs;
QVector<u64> favorited_ids;
QStringList recent_files;
QString language;