summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dist/license.md3
-rw-r--r--dist/qt_themes/colorful/icons/48x48/star.pngbin0 -> 1248 bytes
-rw-r--r--dist/qt_themes/colorful/style.qrc1
-rw-r--r--dist/qt_themes/default/default.qrc1
-rw-r--r--dist/qt_themes/default/icons/48x48/star.pngbin0 -> 686 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/star.pngbin0 -> 725 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc1
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/star.pngbin0 -> 725 bytes
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qrc1
-rw-r--r--license.txt1
-rw-r--r--src/yuzu/configuration/config.cpp14
-rw-r--r--src/yuzu/game_list.cpp103
-rw-r--r--src/yuzu/game_list.h5
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/uisettings.h1
15 files changed, 152 insertions, 5 deletions
diff --git a/dist/license.md b/dist/license.md
index e9bc87656..7bdebfec1 100644
--- a/dist/license.md
+++ b/dist/license.md
@@ -12,6 +12,7 @@ qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
12qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com 12qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
13qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team 13qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
14qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com 14qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
15qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
15qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com 16qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
16qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io 17qt_themes/qdarkstyle/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
17qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com 18qt_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
20qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com 21qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
21qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team 22qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
22qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com 23qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
24qt_themes/qdarkstyle/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
23qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com 25qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
24qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io 26qt_themes/colorful/icons/16x16/view-refresh.png | Apache 2.0 | https://material.io
25qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com 27qt_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
28qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com 30qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
29qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com 31qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com
30qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com 32qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
33qt_themes/colorful/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
31 34
32<!-- TODO: Add the license of the yuzu icon --> \ No newline at end of file 35<!-- TODO: Add the license of the yuzu icon --> \ No newline at end of file
diff --git a/dist/qt_themes/colorful/icons/48x48/star.png b/dist/qt_themes/colorful/icons/48x48/star.png
new file mode 100644
index 000000000..43b5d52ed
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/star.png
Binary files differ
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc
index 36735519a..18b10869e 100644
--- a/dist/qt_themes/colorful/style.qrc
+++ b/dist/qt_themes/colorful/style.qrc
@@ -7,6 +7,7 @@
7 <file alias="48x48/folder.png">icons/48x48/folder.png</file> 7 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">icons/48x48/plus.png</file> 8 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> 9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
10 <file alias="48x48/star.png">icons/48x48/star.png</file>
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 11 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
11 </qresource> 12 </qresource>
12 <qresource prefix="colorful"> 13 <qresource prefix="colorful">
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index 2182f33f3..b195747a3 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -10,6 +10,7 @@
10 <file alias="48x48/folder.png">icons/48x48/folder.png</file> 10 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
11 <file alias="48x48/plus.png">icons/48x48/plus.png</file> 11 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
12 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> 12 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
13 <file alias="48x48/star.png">icons/48x48/star.png</file>
13 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> 14 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
14 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 15 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
15 </qresource> 16 </qresource>
diff --git a/dist/qt_themes/default/icons/48x48/star.png b/dist/qt_themes/default/icons/48x48/star.png
new file mode 100644
index 000000000..740f7f3e7
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/star.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/star.png b/dist/qt_themes/qdarkstyle/icons/48x48/star.png
new file mode 100644
index 000000000..90d423a1d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/star.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index 2b91204f3..34e872d25 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -8,6 +8,7 @@
8 <file alias="48x48/folder.png">icons/48x48/folder.png</file> 8 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
9 <file alias="48x48/plus.png">icons/48x48/plus.png</file> 9 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
10 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> 10 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
11 <file alias="48x48/star.png">icons/48x48/star.png</file>
11 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 12 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
12 </qresource> 13 </qresource>
13 <qresource prefix="qss_icons"> 14 <qresource prefix="qss_icons">
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/star.png b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/star.png
new file mode 100644
index 000000000..90d423a1d
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/star.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
index 579e73ece..142dd3288 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
@@ -8,6 +8,7 @@
8 <file alias="48x48/folder.png">icons/48x48/folder.png</file> 8 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
9 <file alias="48x48/plus.png">icons/48x48/plus.png</file> 9 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
10 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> 10 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
11 <file alias="48x48/star.png">icons/48x48/star.png</file>
11 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 12 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
12 </qresource> 13 </qresource>
13 <qresource prefix="qss_icons"> 14 <qresource prefix="qss_icons">
diff --git a/license.txt b/license.txt
index 86e7b3c1b..495f3e676 100644
--- a/license.txt
+++ b/license.txt
@@ -358,6 +358,7 @@ chip.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
358folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com 358folder.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
359plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com 359plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
360sd_card.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com 360sd_card.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
361star.png | CC BY-ND 3.0 | https://icons8.com
361 362
362Note: 363Note:
363Some icons are different in different themes, and they are separately listed 364Some icons are different in different themes, and they are separately listed
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 50ea15e2a..ff6a6e961 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -934,6 +934,13 @@ void Config::ReadUIGamelistValues() {
934 UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt(); 934 UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
935 UISettings::values.cache_game_list = 935 UISettings::values.cache_game_list =
936 ReadSetting(QStringLiteral("cache_game_list"), true).toBool(); 936 ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
937 const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
938 for (int i = 0; i < favorites_size; i++) {
939 qt_config->setArrayIndex(i);
940 UISettings::values.favorited_ids.append(
941 ReadSetting(QStringLiteral("program_id")).toULongLong());
942 }
943 qt_config->endArray();
937 944
938 qt_config->endGroup(); 945 qt_config->endGroup();
939} 946}
@@ -1476,6 +1483,13 @@ void Config::SaveUIGamelistValues() {
1476 WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3); 1483 WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
1477 WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2); 1484 WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
1478 WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true); 1485 WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
1486 qt_config->beginWriteArray(QStringLiteral("favorites"));
1487 for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
1488 qt_config->setArrayIndex(i);
1489 WriteSetting(QStringLiteral("program_id"),
1490 QVariant::fromValue(UISettings::values.favorited_ids[i]));
1491 }
1492 qt_config->endArray();
1479 1493
1480 qt_config->endGroup(); 1494 qt_config->endGroup();
1481} 1495}
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 9afd5b45f..48b78d65f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -11,6 +11,7 @@
11#include <QJsonDocument> 11#include <QJsonDocument>
12#include <QJsonObject> 12#include <QJsonObject>
13#include <QKeyEvent> 13#include <QKeyEvent>
14#include <QList>
14#include <QMenu> 15#include <QMenu>
15#include <QThreadPool> 16#include <QThreadPool>
16#include <fmt/format.h> 17#include <fmt/format.h>
@@ -84,6 +85,10 @@ void GameListSearchField::setFilterResult(int visible, int total) {
84 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); 85 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
85} 86}
86 87
88bool GameListSearchField::isEmpty() const {
89 return edit_filter->text().isEmpty();
90}
91
87QString GameList::GetLastFilterResultItem() const { 92QString GameList::GetLastFilterResultItem() const {
88 QString file_path; 93 QString file_path;
89 const int folder_count = item_model->rowCount(); 94 const int folder_count = item_model->rowCount();
@@ -187,7 +192,9 @@ void GameList::OnTextChanged(const QString& new_text) {
187 // If the searchfield is empty every item is visible 192 // If the searchfield is empty every item is visible
188 // Otherwise the filter gets applied 193 // Otherwise the filter gets applied
189 if (edit_filter_text.isEmpty()) { 194 if (edit_filter_text.isEmpty()) {
190 for (int i = 0; i < folder_count; ++i) { 195 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
196 UISettings::values.favorited_ids.size() == 0);
197 for (int i = 1; i < folder_count; ++i) {
191 folder = item_model->item(i, 0); 198 folder = item_model->item(i, 0);
192 const QModelIndex folder_index = folder->index(); 199 const QModelIndex folder_index = folder->index();
193 const int children_count = folder->rowCount(); 200 const int children_count = folder->rowCount();
@@ -198,8 +205,9 @@ void GameList::OnTextChanged(const QString& new_text) {
198 } 205 }
199 search_field->setFilterResult(children_total, children_total); 206 search_field->setFilterResult(children_total, children_total);
200 } else { 207 } else {
208 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
201 int result_count = 0; 209 int result_count = 0;
202 for (int i = 0; i < folder_count; ++i) { 210 for (int i = 1; i < folder_count; ++i) {
203 folder = item_model->item(i, 0); 211 folder = item_model->item(i, 0);
204 const QModelIndex folder_index = folder->index(); 212 const QModelIndex folder_index = folder->index();
205 const int children_count = folder->rowCount(); 213 const int children_count = folder->rowCount();
@@ -280,6 +288,13 @@ void GameList::OnUpdateThemedIcons() {
280 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 288 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
281 Qt::DecorationRole); 289 Qt::DecorationRole);
282 break; 290 break;
291 case GameListItemType::Favorites:
292 child->setData(
293 QIcon::fromTheme(QStringLiteral("star"))
294 .pixmap(icon_size)
295 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
296 Qt::DecorationRole);
297 break;
283 default: 298 default:
284 break; 299 break;
285 } 300 }
@@ -427,6 +442,13 @@ void GameList::DonePopulating(const QStringList& watch_list) {
427 emit ShowList(!IsEmpty()); 442 emit ShowList(!IsEmpty());
428 443
429 item_model->invisibleRootItem()->appendRow(new GameListAddDir()); 444 item_model->invisibleRootItem()->appendRow(new GameListAddDir());
445 item_model->invisibleRootItem()->insertRow(0, new GameListFavorites());
446 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
447 UISettings::values.favorited_ids.size() == 0);
448 tree_view->expand(item_model->invisibleRootItem()->child(0)->index());
449 for (const auto id : UISettings::values.favorited_ids) {
450 AddFavorite(id);
451 }
430 452
431 // Clear out the old directories to watch for changes and add the new ones 453 // Clear out the old directories to watch for changes and add the new ones
432 auto watch_dirs = watcher->directories(); 454 auto watch_dirs = watcher->directories();
@@ -446,7 +468,7 @@ void GameList::DonePopulating(const QStringList& watch_list) {
446 tree_view->setEnabled(true); 468 tree_view->setEnabled(true);
447 const int folder_count = tree_view->model()->rowCount(); 469 const int folder_count = tree_view->model()->rowCount();
448 int children_total = 0; 470 int children_total = 0;
449 for (int i = 0; i < folder_count; ++i) { 471 for (int i = 1; i < folder_count; ++i) {
450 children_total += item_model->item(i, 0)->rowCount(); 472 children_total += item_model->item(i, 0)->rowCount();
451 } 473 }
452 search_field->setFilterResult(children_total, children_total); 474 search_field->setFilterResult(children_total, children_total);
@@ -478,6 +500,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
478 case GameListItemType::SysNandDir: 500 case GameListItemType::SysNandDir:
479 AddPermDirPopup(context_menu, selected); 501 AddPermDirPopup(context_menu, selected);
480 break; 502 break;
503 case GameListItemType::Favorites:
504 AddFavoritesPopup(context_menu);
505 break;
481 default: 506 default:
482 break; 507 break;
483 } 508 }
@@ -485,6 +510,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
485} 510}
486 511
487void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) { 512void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
513 QAction* favorite = context_menu.addAction(tr("Favorite"));
514 context_menu.addSeparator();
488 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 515 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
489 QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); 516 QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
490 QAction* open_transferable_shader_cache = 517 QAction* open_transferable_shader_cache =
@@ -503,6 +530,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
503 context_menu.addSeparator(); 530 context_menu.addSeparator();
504 QAction* properties = context_menu.addAction(tr("Properties")); 531 QAction* properties = context_menu.addAction(tr("Properties"));
505 532
533 favorite->setVisible(program_id != 0);
534 favorite->setCheckable(true);
535 favorite->setChecked(UISettings::values.favorited_ids.contains(program_id));
506 open_save_location->setVisible(program_id != 0); 536 open_save_location->setVisible(program_id != 0);
507 open_mod_location->setVisible(program_id != 0); 537 open_mod_location->setVisible(program_id != 0);
508 open_transferable_shader_cache->setVisible(program_id != 0); 538 open_transferable_shader_cache->setVisible(program_id != 0);
@@ -513,6 +543,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
513 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 543 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
514 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); 544 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
515 545
546 connect(favorite, &QAction::triggered, [this, program_id]() { ToggleFavorite(program_id); });
516 connect(open_save_location, &QAction::triggered, [this, program_id, path]() { 547 connect(open_save_location, &QAction::triggered, [this, program_id, path]() {
517 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); 548 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
518 }); 549 });
@@ -576,7 +607,7 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
576 607
577 const int row = selected.row(); 608 const int row = selected.row();
578 609
579 move_up->setEnabled(row > 0); 610 move_up->setEnabled(row > 1);
580 move_down->setEnabled(row < item_model->rowCount() - 2); 611 move_down->setEnabled(row < item_model->rowCount() - 2);
581 612
582 connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] { 613 connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] {
@@ -614,6 +645,18 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
614 }); 645 });
615} 646}
616 647
648void GameList::AddFavoritesPopup(QMenu& context_menu) {
649 QAction* clear_all = context_menu.addAction(tr("Clear"));
650
651 connect(clear_all, &QAction::triggered, [this] {
652 for (const auto id : UISettings::values.favorited_ids) {
653 RemoveFavorite(id);
654 }
655 UISettings::values.favorited_ids.clear();
656 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
657 });
658}
659
617void GameList::LoadCompatibilityList() { 660void GameList::LoadCompatibilityList() {
618 QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")}; 661 QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
619 662
@@ -728,6 +771,58 @@ void GameList::RefreshGameDirectory() {
728 } 771 }
729} 772}
730 773
774void GameList::ToggleFavorite(u64 program_id) {
775 if (!UISettings::values.favorited_ids.contains(program_id)) {
776 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
777 !search_field->isEmpty());
778 UISettings::values.favorited_ids.append(program_id);
779 AddFavorite(program_id);
780 item_model->sort(tree_view->header()->sortIndicatorSection(),
781 tree_view->header()->sortIndicatorOrder());
782 } else {
783 UISettings::values.favorited_ids.removeOne(program_id);
784 RemoveFavorite(program_id);
785 if (UISettings::values.favorited_ids.size() == 0) {
786 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
787 }
788 }
789}
790
791void GameList::AddFavorite(u64 program_id) {
792 auto* favorites_row = item_model->item(0);
793
794 for (int i = 1; i < item_model->rowCount() - 1; i++) {
795 const auto* folder = item_model->item(i);
796 for (int j = 0; j < folder->rowCount(); j++) {
797 if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() ==
798 program_id) {
799 QList<QStandardItem*> list;
800 for (int k = 0; k < item_model->columnCount(); k++) {
801 list.append(folder->child(j, k)->clone());
802 }
803 list[0]->setData(folder->child(j)->data(GameListItem::SortRole),
804 GameListItem::SortRole);
805 list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString());
806
807 favorites_row->appendRow(list);
808 return;
809 }
810 }
811 }
812}
813
814void GameList::RemoveFavorite(u64 program_id) {
815 auto* favorites_row = item_model->item(0);
816
817 for (int i = 0; i < favorites_row->rowCount(); i++) {
818 const auto* game = favorites_row->child(i);
819 if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) {
820 favorites_row->removeRow(i);
821 return;
822 }
823 }
824}
825
731GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { 826GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
732 connect(parent, &GMainWindow::UpdateThemedIcons, this, 827 connect(parent, &GMainWindow::UpdateThemedIcons, this,
733 &GameListPlaceholder::onUpdateThemedIcons); 828 &GameListPlaceholder::onUpdateThemedIcons);
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 58059a3c4..9c0a1a482 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -112,10 +112,15 @@ private:
112 112
113 void RefreshGameDirectory(); 113 void RefreshGameDirectory();
114 114
115 void ToggleFavorite(u64 program_id);
116 void AddFavorite(u64 program_id);
117 void RemoveFavorite(u64 program_id);
118
115 void PopupContextMenu(const QPoint& menu_location); 119 void PopupContextMenu(const QPoint& menu_location);
116 void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path); 120 void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
117 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); 121 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
118 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); 122 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
123 void AddFavoritesPopup(QMenu& context_menu);
119 124
120 std::shared_ptr<FileSys::VfsFilesystem> vfs; 125 std::shared_ptr<FileSys::VfsFilesystem> vfs;
121 FileSys::ManualContentProvider* provider; 126 FileSys::ManualContentProvider* provider;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index f25445f18..7ca8ece23 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -29,7 +29,8 @@ enum class GameListItemType {
29 SdmcDir = QStandardItem::UserType + 3, 29 SdmcDir = QStandardItem::UserType + 3,
30 UserNandDir = QStandardItem::UserType + 4, 30 UserNandDir = QStandardItem::UserType + 4,
31 SysNandDir = QStandardItem::UserType + 5, 31 SysNandDir = QStandardItem::UserType + 5,
32 AddDir = QStandardItem::UserType + 6 32 AddDir = QStandardItem::UserType + 6,
33 Favorites = QStandardItem::UserType + 7,
33}; 34};
34 35
35Q_DECLARE_METATYPE(GameListItemType); 36Q_DECLARE_METATYPE(GameListItemType);
@@ -310,6 +311,28 @@ public:
310 } 311 }
311}; 312};
312 313
314class GameListFavorites : public GameListItem {
315public:
316 explicit GameListFavorites() {
317 setData(type(), TypeRole);
318
319 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
320 setData(QIcon::fromTheme(QStringLiteral("star"))
321 .pixmap(icon_size)
322 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
323 Qt::DecorationRole);
324 setData(QObject::tr("Favorites"), Qt::DisplayRole);
325 }
326
327 int type() const override {
328 return static_cast<int>(GameListItemType::Favorites);
329 }
330
331 bool operator<(const QStandardItem& other) const override {
332 return false;
333 }
334};
335
313class GameList; 336class GameList;
314class QHBoxLayout; 337class QHBoxLayout;
315class QTreeView; 338class QTreeView;
@@ -324,6 +347,7 @@ public:
324 explicit GameListSearchField(GameList* parent = nullptr); 347 explicit GameListSearchField(GameList* parent = nullptr);
325 348
326 void setFilterResult(int visible, int total); 349 void setFilterResult(int visible, int total);
350 bool isEmpty() const;
327 351
328 void clear(); 352 void clear();
329 void setFocus(); 353 void setFocus();
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index ce3945485..5ba00b8c8 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -74,6 +74,7 @@ struct Values {
74 QString game_dir_deprecated; 74 QString game_dir_deprecated;
75 bool game_dir_deprecated_deepscan; 75 bool game_dir_deprecated_deepscan;
76 QVector<UISettings::GameDir> game_dirs; 76 QVector<UISettings::GameDir> game_dirs;
77 QVector<u64> favorited_ids;
77 QStringList recent_files; 78 QStringList recent_files;
78 QString language; 79 QString language;
79 80