From dafa96b39ca00b635d18601ce87c2743bc5a02d6 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Wed, 7 Feb 2024 00:37:09 +0100 Subject: [PATCH] citra_qt: Games can be marked as favorites to make them appear at the top of the list Co-Authored-By: Colin E. <5352197+Kewlan@users.noreply.github.com> --- dist/license.md | 3 + dist/qt_themes/colorful/icons/48x48/star.png | Bin 0 -> 1108 bytes dist/qt_themes/colorful/style.qrc | 1 + dist/qt_themes/colorful_dark/style.qrc | 1 + dist/qt_themes/default/default.qrc | 1 + dist/qt_themes/default/icons/48x48/star.png | Bin 0 -> 1029 bytes .../qt_themes/qdarkstyle/icons/48x48/star.png | Bin 0 -> 1055 bytes dist/qt_themes/qdarkstyle/style.qrc | 1 + .../icons/48x48/star.png | Bin 0 -> 725 bytes .../qdarkstyle_midnight_blue/style.qrc | 1 + license.txt | 1 + src/citra_qt/configuration/config.cpp | 16 +++ src/citra_qt/game_list.cpp | 106 +++++++++++++++++- src/citra_qt/game_list.h | 5 + src/citra_qt/game_list_p.h | 26 ++++- src/citra_qt/uisettings.h | 2 + 16 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 dist/qt_themes/colorful/icons/48x48/star.png create mode 100644 dist/qt_themes/default/icons/48x48/star.png create mode 100644 dist/qt_themes/qdarkstyle/icons/48x48/star.png create mode 100644 dist/qt_themes/qdarkstyle_midnight_blue/icons/48x48/star.png diff --git a/dist/license.md b/dist/license.md index b3e8d05a4..207fc638e 100644 --- a/dist/license.md +++ b/dist/license.md @@ -15,6 +15,7 @@ qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/48x48/no_avatar.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/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com @@ -26,6 +27,7 @@ qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/48x48/no_avatar.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/connected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com @@ -36,6 +38,7 @@ 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 qt_themes/colorful_dark/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com 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 0000000000000000000000000000000000000000..19d55a0a8065cf4168752568bc67d4ebc3b8ec9f GIT binary patch literal 1108 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?FU|>mi z^mSxl*x1kgCy|wbfq}EYBeIx*fx8KW8Tb8qw3LB?`K70eV@QVc+nML{eF8;}pRWvV z=~^PaM(4%*Raswry?@BJEK@NvtMfjx?qhVvgb5xI92*6Mjvia|kze5q&qw81=eBex zBsprIY029AxcdH`DJvyEzuV!zdxn4Uee36E=Kg+X`TXCkaIN5FJ{2Bo>UJKH3ePkZ zk*VcpUv~IO#qTX*Tx;H6oA{eo_Q?s`_{7MQd9T*#N2~4Ou_$ryc9DB~aZ;(%$%Z@Y zR%b5ocp`aZ0prJx zovnB4zRs;bbc3ij+$M?aWqq>)+O7{lCR+GXTeoH zB}pglTv@iV`GoGNn&`9I_v)^S&Hq#U;NYgrC#mb4VrFmkvDzykJGZVueM0%o$J^Q$ zf6>~>cS$sNmdg(-%cm9SwipeXi zyI0-b`ttvQqKUVHX3yOC@^9-RYjr7sqG0QX$0T!q$;WV9s&oxx7uhGAdouRK@yuK< z{%6Hc)V8y{+@mysnd>Qwd+=^W*{jP8EIL15G_y}&G_>Q^REfLZygq5_rp_4WhHKfX z293rOnhRXKza=bRvTw?>hQqHC(#0oEx@98gWE5fB+@^g=Kzox|c2DB=xv?3CwOT6K z+U8WnL23j7~4vwF~xL-eQ;`Dnh$5+m8DxSKQ zLGJ7fyY}dLXNFfe$!`njxgN@xNAdK>!o literal 0 HcmV?d00001 diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc index 2866c69bd..bd7898eb1 100644 --- a/dist/qt_themes/colorful/style.qrc +++ b/dist/qt_themes/colorful/style.qrc @@ -10,6 +10,7 @@ icons/48x48/folder.png icons/48x48/plus.png icons/48x48/sd_card.png + icons/48x48/star.png icons/256x256/plus_folder.png diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc index 9c531fe1b..ec328117d 100644 --- a/dist/qt_themes/colorful_dark/style.qrc +++ b/dist/qt_themes/colorful_dark/style.qrc @@ -11,6 +11,7 @@ ../qdarkstyle/icons/48x48/no_avatar.png ../colorful/icons/48x48/plus.png ../colorful/icons/48x48/sd_card.png + ../colorful/icons/48x48/star.png ../colorful/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index 6da475316..9c8e8f13e 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -14,6 +14,7 @@ icons/48x48/plus.png icons/48x48/sd_card.png icons/256x256/citra.png + icons/48x48/star.png icons/256x256/plus_folder.png 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 0000000000000000000000000000000000000000..c2b78f0c3e543913b18d4f95956109c7d2646ba8 GIT binary patch literal 1029 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F5ttU4+a=O{YDuVTLWssh)}5C#t}bF{ zab#)aE>0A1nQ+l*(eXu#Jd~oYtYQ)uoVIZ9gpQg?sSk1ui$Bi!UwN;9U*e4WUC(3*A6hnTLX6Pe0~40o^?0fOIhN9r)Blatfp>$B%6Z4Y{cnYexo;${ zG-tZ+^ogJ0e1NpG!ZybFAOFo_n`-aS$(`T%?(~%J!W&Fa{1HkpY4Q--@#t{9snU0j zfLG>?#)7Q#7JaMJ7g;dPNu>93nA1Dj?kjJ5T`NY**!G|ta?st5;V}Hi_#x9AoULhI2FO!8LBu#!a@J(qx z-ngvd&#enB#;pD`^EteOSoahwdM^6*S?T>k$;Avi9xlAepW`>(Kz_$NM+=4=!vw2V ztvM?zR^3r~#BAMkO6#s&#M!_O(^-Y}?~iTSCOCyzKh|a0hUM%plf=)(s;%65s`glF zOU#0^t^v<}y<8yuoH}EZ@4T zrl^`ccK5x^Ub4Qs;YNTy%kP?`?#8*hM1CLn%6YCz+0XWZ>XE3}q)h>u>%ZPMWSeuT zR%mV0=F>Z}dw*K+TYs8WpsDru+o`AV45yFI=v7MNInkZWRNZnx{f+q6(gWr4pH~|7 z)b93K5G0)K6DuS9LPY89E?fQ=9}KKlbVtXtU-sV>`=xZ2+5F~RY8$(*Fl0B%@i+4a zKEEFHcYCx?|Fac>Z=F*<_Sc{JEdo9B~WS zp7U~1V1Maj8|l02ggGCmWJ&uh3c7FY5Xo}R=z-p4&0huuO*87-}w}%J(MUAhvZ(FaqWciWYWFPkcgJUIKC+1GE6J5A}wUC64lCP4{ zI+uB0SJj*pEDm&;)^cj?f#pI!Z@WI3XSmGTR49UF&*Gv#Y;Kkn?l-r2eH4qBkygj^ zLAROXz)6;W!IC@oGcBGhbYphl1HFLh2kb?5Ob<-XJALNNmY-8!)%fPoPr@BGeA(zuBj`2#=Te+t%9fFp# z-O<05oN&tC#Qtbj)*mLdrKNG{%%-~<&i+5}Cv8#mhXa3Z1$jPW(3eqR5a+B-wUv_R zn$vwThxyrb?+Erey^1;PVihL#?JvS?4z&6(v*j>x@jbeKK#c#{_6K$ExehE_^QX~+ z`NIXpOUyc+=^amF-Abk&%raeKJViZ>Tc(WT)!sVGIooBKi)PqN&t2qx#9VU6mwBtV zgsQc8ZM)1I^3$1BhVkC=g5`@Qv0PhGbTs(HMf3ZM)#mZPI1=qV3Yx7?^o zSXui@_)(nZxoI_b#E+iS-ss#nLn?v4zTTSaK;D95g7qGS^Uk0Ct~wth8qC1BF+MdHg zEcx5w+87J59+QHQt#d!-)-haI=rXI}<$=i(&E^dkzwHzh{(Pj!WaTfu1#j}5f|#my z{^rP0-L=H&BU3hey}y3)>n~5mrX_1%j6Zez?@ESl(aJktPZTGVF`dtme%$md=i5vN z+Yica76&rDesBcZq|fnt_=?TT^27A0NB&7{Tyz{MXO+gicons/48x48/no_avatar.png icons/48x48/plus.png icons/48x48/sd_card.png + icons/48x48/star.png icons/256x256/plus_folder.png 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 0000000000000000000000000000000000000000..90d423a1d4c1e05ccec0a01fa34abca9fe99676d GIT binary patch literal 725 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F*yi}}0p)tauU5tllaBo)Q>MX1|2Fs}G8UF*X1?d#4kh)10I zxu-Oht>X5f1JwtftX->o;H20i={YB&jE_!G6Pxqy#|=flX;IuD>Yt z%zS;vW0%y9G>Hb?12Lywn6F@58c^zd?trLgC>WzJ@ zrk=r*v?ke|Hk$rtp3$+IENR{zOP%IDJ2JEL>N@N_qBtxT8P4$8%RPs&?uqRg-5{&DZ>&f9`V?jK*A{>|1; z-1hOM8Ro|xNIl7Zup&K1%Dtv(&T<|NuUS*CJPtN!jh}Q)I{8^lQ%%}}H6I?St_;7V zcwIrBIWAaMyZpLC!XqgLDcv-&Rg?6*cBy1qC7;oAl2^=W*eq$bWc~r!gt?;OEN(03 zzqT&4TGr@i`&l@l-0RQ1C-#{=$4ovf^1WF2V2bm+#|Ms^?VNW^D1Sw{yP5F;(*u?9 z3{{hF+!V7sT<`9&Z2v)<0`Ba&`pWs76%0BNKii!$^7OU{Rygn7cvD#Mf8wjT_gM_@ hyTtDLlm3TM;M?I@r(Pae&%nUI;OXk;vd$@?2>^orLy!Of literal 0 HcmV?d00001 diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc index b55a72fd1..3b08654f8 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc @@ -11,6 +11,7 @@ icons/48x48/no_avatar.png icons/48x48/plus.png icons/48x48/sd_card.png + icons/48x48/star.png icons/256x256/plus_folder.png diff --git a/license.txt b/license.txt index 09f9ad2d5..f94c73f1a 100644 --- a/license.txt +++ b/license.txt @@ -356,3 +356,4 @@ folder.png | CC BY-ND 3.0 | https://icons8.com plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com sd_card.png | CC BY-ND 3.0 | https://icons8.com +star.png | CC BY-ND 3.0 | https://icons8.com diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 21271946b..3a52ee6cf 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -814,6 +814,14 @@ void Config::ReadUIGameListValues() { ReadBasicSetting(UISettings::values.show_type_column); ReadBasicSetting(UISettings::values.show_size_column); + 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(); } @@ -1304,6 +1312,14 @@ void Config::SaveUIGameListValues() { WriteBasicSetting(UISettings::values.show_type_column); WriteBasicSetting(UISettings::values.show_size_column); + 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(); } diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index d3d4e20d1..9f520739c 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,10 @@ void GameListSearchField::setFilterResult(int visible, int total) { QStringLiteral("%1 %2 %3 %4").arg(visible).arg(result_of_text).arg(total).arg(result_text)); } +bool GameListSearchField::IsEmpty() const { + return edit_filter->text().isEmpty(); +} + QString GameList::GetLastFilterResultItem() const { QString file_path; const int folderCount = item_model->rowCount(); @@ -206,7 +211,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(); @@ -217,8 +224,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(); @@ -281,6 +289,13 @@ void GameList::OnUpdateThemedIcons() { child->setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size), 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; } @@ -422,6 +437,15 @@ void GameList::DonePopulating(const QStringList& watch_list) { item_model->invisibleRootItem()->appendRow(new GameListAddDir()); + // Add favorites row + 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(); if (!watch_dirs.isEmpty()) { @@ -440,7 +464,7 @@ void GameList::DonePopulating(const QStringList& watch_list) { tree_view->setEnabled(true); const int folderCount = tree_view->model()->rowCount(); int children_total = 0; - for (int i = 0; i < folderCount; ++i) { + for (int i = 1; i < folderCount; ++i) { children_total += item_model->item(i, 0)->rowCount(); } search_field->setFilterResult(children_total, children_total); @@ -477,6 +501,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { case GameListItemType::SystemDir: AddPermDirPopup(context_menu, selected); break; + case GameListItemType::Favorites: + AddFavoritesPopup(context_menu); + break; default: break; } @@ -533,6 +560,8 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) { void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, u64 program_id, u64 extdata_id, Service::FS::MediaType media_type) { + QAction* favorite = context_menu.addAction(tr("Favorite")); + context_menu.addSeparator(); QMenu* open_menu = context_menu.addMenu(tr("Open")); QAction* open_application_location = open_menu->addAction(tr("Application Location")); open_menu->addSeparator(); @@ -577,6 +606,10 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr program_id, [&opengl_cache_exists](QFile& file) { opengl_cache_exists |= file.exists(); }); #endif + favorite->setVisible(program_id != 0); + favorite->setCheckable(true); + favorite->setChecked(UISettings::values.favorited_ids.contains(program_id)); + std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); open_save_location->setEnabled( is_application && FileUtil::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor( @@ -624,6 +657,7 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end()); + connect(favorite, &QAction::triggered, [this, program_id]() { ToggleFavorite(program_id); }); connect(open_save_location, &QAction::triggered, this, [this, program_id] { emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA); }); @@ -774,7 +808,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, [this, selected, row, game_dir_index] { @@ -812,6 +846,18 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { }); } +void GameList::AddFavoritesPopup(QMenu& context_menu) { + QAction* clear = context_menu.addAction(tr("Clear")); + + connect(clear, &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")}; @@ -944,6 +990,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 list; + for (int k = 0; k < COLUMN_COUNT; 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; + } + } +} + QString GameList::FindGameByProgramID(u64 program_id, int role) { return FindGameByProgramID(item_model->invisibleRootItem(), program_id, role); } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 9b9ccb05a..f7969850a 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -80,6 +80,10 @@ public: void RefreshGameDirectory(); + void ToggleFavorite(u64 program_id); + void AddFavorite(u64 program_id); + void RemoveFavorite(u64 program_id); + static const QStringList supported_file_extensions; signals: @@ -113,6 +117,7 @@ private: u64 extdata_id, Service::FS::MediaType media_type); void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); + void AddFavoritesPopup(QMenu& context_menu); void UpdateColumnVisibility(); QString FindGameByProgramID(QStandardItem* current_item, u64 program_id, int role); diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 338d8cee7..16a7b5a8f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -34,7 +34,8 @@ enum class GameListItemType { CustomDir = QStandardItem::UserType + 2, InstalledDir = QStandardItem::UserType + 3, SystemDir = QStandardItem::UserType + 4, - AddDir = QStandardItem::UserType + 5 + AddDir = QStandardItem::UserType + 5, + Favorites = QStandardItem::UserType + 6, }; Q_DECLARE_METATYPE(GameListItemType); @@ -430,6 +431,28 @@ public: } }; +class GameListFavorites : public GameListItem { +public: + explicit GameListFavorites() { + setData(type(), TypeRole); + + const int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue()); + 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(GameListItemType::Favorites); + } + + bool operator<(const QStandardItem& other) const override { + return false; + } +}; + class GameList; class QHBoxLayout; class QTreeView; @@ -444,6 +467,7 @@ public: explicit GameListSearchField(GameList* parent = nullptr); void setFilterResult(int visible, int total); + bool IsEmpty() const; void clear(); void setFocus(); diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 5ac61e204..78e3c9b55 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -117,6 +117,8 @@ struct Values { QVector game_dirs; QStringList recent_files; QString last_artic_base_addr; + QVector favorited_ids; + QString language; QString theme;