diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 409136809..3a61f9aa0 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -43,6 +43,8 @@ add_executable(citra-qt configuration/configure_motion_touch.h configuration/configure_system.cpp configuration/configure_system.h + configuration/configure_ui.cpp + configuration/configure_ui.h configuration/configure_web.cpp configuration/configure_web.h debugger/console.h @@ -119,6 +121,7 @@ set(UIS configuration/configure_input.ui configuration/configure_motion_touch.ui configuration/configure_system.ui + configuration/configure_ui.ui configuration/configure_web.ui debugger/registers.ui multiplayer/direct_connect.ui diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index fb3268d16..a7c284471 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -219,6 +219,28 @@ void Config::ReadValues() { ReadSetting("microProfileDialogVisible", false).toBool(); qt_config->endGroup(); + qt_config->beginGroup("GameList"); + int icon_size = ReadSetting("iconSize", 2).toInt(); + if (icon_size < 0 || icon_size > 2) { + icon_size = 2; + } + UISettings::values.game_list_icon_size = UISettings::GameListIconSize{icon_size}; + + int row_1 = ReadSetting("row1", 2).toInt(); + if (row_1 < 0 || row_1 > 3) { + row_1 = 2; + } + UISettings::values.game_list_row_1 = UISettings::GameListText{row_1}; + + int row_2 = ReadSetting("row2", 0).toInt(); + if (row_2 < -1 || row_2 > 3) { + row_2 = 0; + } + UISettings::values.game_list_row_2 = UISettings::GameListText{row_2}; + + UISettings::values.game_list_hide_no_icon = ReadSetting("hideNoIcon", false).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("Paths"); UISettings::values.roms_path = ReadSetting("romsPath").toString(); UISettings::values.symbols_path = ReadSetting("symbolsPath").toString(); @@ -448,6 +470,13 @@ void Config::SaveValues() { WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false); qt_config->endGroup(); + qt_config->beginGroup("GameList"); + WriteSetting("iconSize", static_cast(UISettings::values.game_list_icon_size), 2); + WriteSetting("row1", static_cast(UISettings::values.game_list_row_1), 2); + WriteSetting("row2", static_cast(UISettings::values.game_list_row_2), 0); + WriteSetting("hideNoIcon", UISettings::values.game_list_hide_no_icon, false); + qt_config->endGroup(); + qt_config->beginGroup("Paths"); WriteSetting("romsPath", UISettings::values.roms_path); WriteSetting("symbolsPath", UISettings::values.symbols_path); diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui index 0f24016b2..70796a035 100644 --- a/src/citra_qt/configuration/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -63,6 +63,11 @@ Web + + + UI + + @@ -125,6 +130,12 @@
configuration/configure_web.h
1 + + ConfigureUi + QWidget +
configuration/configure_ui.h
+ 1 +
diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index ec52aaf28..ba2eda32d 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -16,8 +16,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry ui->generalTab->PopulateHotkeyList(registry); this->setConfiguration(); this->PopulateSelectionList(); - connect(ui->generalTab, &ConfigureGeneral::languageChanged, this, - &ConfigureDialog::onLanguageChanged); + connect(ui->uiTab, &ConfigureUi::languageChanged, this, &ConfigureDialog::onLanguageChanged); connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, &ConfigureDialog::UpdateVisibleTabs); @@ -39,6 +38,7 @@ void ConfigureDialog::applyConfiguration() { ui->cameraTab->applyConfiguration(); ui->debugTab->applyConfiguration(); ui->webTab->applyConfiguration(); + ui->uiTab->applyConfiguration(); Settings::Apply(); Settings::LogSettings(); } @@ -46,7 +46,7 @@ void ConfigureDialog::applyConfiguration() { void ConfigureDialog::PopulateSelectionList() { const std::array, 4> items{ - {{tr("General"), {tr("General"), tr("Web"), tr("Debug")}}, + {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("UI")}}, {tr("System"), {tr("System"), tr("Audio"), tr("Camera")}}, {tr("Graphics"), {tr("Graphics")}}, {tr("Controls"), {tr("Input")}}}}; @@ -70,6 +70,7 @@ void ConfigureDialog::onLanguageChanged(const QString& locale) { ui->cameraTab->retranslateUi(); ui->debugTab->retranslateUi(); ui->webTab->retranslateUi(); + ui->uiTab->retranslateUi(); } void ConfigureDialog::UpdateVisibleTabs() { @@ -77,11 +78,15 @@ void ConfigureDialog::UpdateVisibleTabs() { if (items.isEmpty()) return; - const QHash widgets = { - {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab}, - {tr("Input"), ui->inputTab}, {tr("Graphics"), ui->graphicsTab}, - {tr("Audio"), ui->audioTab}, {tr("Camera"), ui->cameraTab}, - {tr("Debug"), ui->debugTab}, {tr("Web"), ui->webTab}}; + const QHash widgets = {{tr("General"), ui->generalTab}, + {tr("System"), ui->systemTab}, + {tr("Input"), ui->inputTab}, + {tr("Graphics"), ui->graphicsTab}, + {tr("Audio"), ui->audioTab}, + {tr("Camera"), ui->cameraTab}, + {tr("Debug"), ui->debugTab}, + {tr("Web"), ui->webTab}, + {tr("UI"), ui->uiTab}}; ui->tabWidget->clear(); diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index 33befa7b7..3153ff8c8 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include "citra_qt/configuration/configure_general.h" #include "citra_qt/ui_settings.h" #include "core/core.h" @@ -13,28 +12,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGeneral) { ui->setupUi(this); - ui->language_combobox->addItem(tr(""), QString("")); - ui->language_combobox->addItem(tr("English"), QString("en")); - QDirIterator it(":/languages", QDirIterator::NoIteratorFlags); - while (it.hasNext()) { - QString locale = it.next(); - locale.truncate(locale.lastIndexOf('.')); - locale.remove(0, locale.lastIndexOf('/') + 1); - QString lang = QLocale::languageToString(QLocale(locale).language()); - ui->language_combobox->addItem(lang, locale); - } - - // Unlike other configuration changes, interface language changes need to be reflected on the - // interface immediately. This is done by passing a signal to the main window, and then - // retranslating when passing back. - connect(ui->language_combobox, - static_cast(&QComboBox::currentIndexChanged), this, - &ConfigureGeneral::onLanguageChanged); - - for (const auto& theme : UISettings::themes) { - ui->theme_combobox->addItem(theme.first, theme.second); - } - this->setConfiguration(); ui->updateBox->setVisible(UISettings::values.updater_found); @@ -50,10 +27,6 @@ void ConfigureGeneral::setConfiguration() { // The first item is "auto-select" with actual value -1, so plus one here will do the trick ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1); - - ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); - ui->language_combobox->setCurrentIndex( - ui->language_combobox->findData(UISettings::values.language)); } void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { @@ -62,8 +35,6 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { void ConfigureGeneral::applyConfiguration() { UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - UISettings::values.theme = - ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked(); UISettings::values.update_on_close = ui->toggle_auto_update->isChecked(); @@ -71,13 +42,6 @@ void ConfigureGeneral::applyConfiguration() { Settings::values.region_value = ui->region_combobox->currentIndex() - 1; } -void ConfigureGeneral::onLanguageChanged(int index) { - if (index == -1) - return; - - emit languageChanged(ui->language_combobox->itemData(index).toString()); -} - void ConfigureGeneral::retranslateUi() { ui->retranslateUi(this); ui->hotkeysDialog->retranslateUi(); diff --git a/src/citra_qt/configuration/configure_general.h b/src/citra_qt/configuration/configure_general.h index 2742ec159..9e67589bf 100644 --- a/src/citra_qt/configuration/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h @@ -24,12 +24,6 @@ public: void applyConfiguration(); void retranslateUi(); -private slots: - void onLanguageChanged(int index); - -signals: - void languageChanged(const QString& locale); - private: void setConfiguration(); diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index c014dc4d7..103ac54d3 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -31,20 +31,6 @@ - - - - - - Interface language - - - - - - - - @@ -145,33 +131,6 @@ - - - - Theme - - - - - - - - - - Theme: - - - - - - - - - - - - - diff --git a/src/citra_qt/configuration/configure_ui.cpp b/src/citra_qt/configuration/configure_ui.cpp new file mode 100644 index 000000000..7972970ad --- /dev/null +++ b/src/citra_qt/configuration/configure_ui.cpp @@ -0,0 +1,72 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "citra_qt/configuration/configure_ui.h" +#include "citra_qt/ui_settings.h" +#include "ui_configure_ui.h" + +ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { + ui->setupUi(this); + ui->language_combobox->addItem(tr(""), QString("")); + ui->language_combobox->addItem(tr("English"), QString("en")); + QDirIterator it(":/languages", QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + QString locale = it.next(); + locale.truncate(locale.lastIndexOf('.')); + locale.remove(0, locale.lastIndexOf('/') + 1); + QString lang = QLocale::languageToString(QLocale(locale).language()); + ui->language_combobox->addItem(lang, locale); + } + + // Unlike other configuration changes, interface language changes need to be reflected on the + // interface immediately. This is done by passing a signal to the main window, and then + // retranslating when passing back. + connect(ui->language_combobox, + static_cast(&QComboBox::currentIndexChanged), this, + &ConfigureUi::onLanguageChanged); + + for (const auto& theme : UISettings::themes) { + ui->theme_combobox->addItem(theme.first, theme.second); + } + + this->setConfiguration(); +} + +ConfigureUi::~ConfigureUi() = default; + +void ConfigureUi::setConfiguration() { + ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->language_combobox->setCurrentIndex( + ui->language_combobox->findData(UISettings::values.language)); + ui->icon_size_combobox->setCurrentIndex( + static_cast(UISettings::values.game_list_icon_size)); + ui->row_1_text_combobox->setCurrentIndex(static_cast(UISettings::values.game_list_row_1)); + ui->row_2_text_combobox->setCurrentIndex(static_cast(UISettings::values.game_list_row_2) + + 1); + ui->toggle_hide_no_icon->setChecked(UISettings::values.game_list_hide_no_icon); +} + +void ConfigureUi::applyConfiguration() { + UISettings::values.theme = + ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + UISettings::values.game_list_icon_size = + static_cast(ui->icon_size_combobox->currentIndex()); + UISettings::values.game_list_row_1 = + static_cast(ui->row_1_text_combobox->currentIndex()); + UISettings::values.game_list_row_2 = + static_cast(ui->row_2_text_combobox->currentIndex() - 1); + UISettings::values.game_list_hide_no_icon = ui->toggle_hide_no_icon->isChecked(); +} + +void ConfigureUi::onLanguageChanged(int index) { + if (index == -1) + return; + + emit languageChanged(ui->language_combobox->itemData(index).toString()); +} + +void ConfigureUi::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_ui.h b/src/citra_qt/configuration/configure_ui.h new file mode 100644 index 000000000..cb1b9790f --- /dev/null +++ b/src/citra_qt/configuration/configure_ui.h @@ -0,0 +1,34 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Ui { +class ConfigureUi; +} + +class ConfigureUi : public QWidget { + Q_OBJECT + +public: + explicit ConfigureUi(QWidget* parent = nullptr); + ~ConfigureUi(); + + void applyConfiguration(); + void retranslateUi(); + +private slots: + void onLanguageChanged(int index); + +signals: + void languageChanged(const QString& locale); + +private: + void setConfiguration(); + + std::unique_ptr ui; +}; diff --git a/src/citra_qt/configuration/configure_ui.ui b/src/citra_qt/configuration/configure_ui.ui new file mode 100644 index 000000000..87eeb3e15 --- /dev/null +++ b/src/citra_qt/configuration/configure_ui.ui @@ -0,0 +1,194 @@ + + + ConfigureUi + + + Form + + + + 0 + 0 + 290 + 280 + + + + + + + General + + + + + + + + + + Interface language: + + + + + + + + + + + + + + Theme: + + + + + + + + + + + + + + + + + Game List + + + + + + + + + + Icon Size: + + + + + + + + None + + + + + Small (24x24) + + + + + Large (48x48) + + + + + + + + + + + + Row 1 Text: + + + + + + + + File Name + + + + + Full Path + + + + + Title Name + + + + + Title ID + + + + + + + + + + + + Row 2 Text: + + + + + + + + None + + + + + File Name + + + + + Full Path + + + + + Title Name + + + + + Title ID + + + + + + + + + + Hide Titles without Icon + + + + + + + + + + + + Qt::Vertical + + + + + + + + diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index e21edb11d..f736c21df 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -723,6 +723,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign return update_smdh; }(); + if (!Loader::IsValidSMDH(smdh) && UISettings::values.game_list_hide_no_icon) { + // Skip this invalid entry + return true; + } + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); // The game list uses this as compatibility number for untested games diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index b3e2911b6..c355ecc1e 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -62,6 +62,8 @@ public: QString FindGameByProgramID(u64 program_id); + void RefreshGameDirectory(); + static const QStringList supported_file_extensions; signals: @@ -87,8 +89,6 @@ private: void ValidateEntry(const QModelIndex& item); void DonePopulating(QStringList watch_list); - void RefreshGameDirectory(); - void PopupContextMenu(const QPoint& menu_location); void AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id, u64 extdata_id); void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 93aaedac2..cc8b92851 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -124,6 +124,13 @@ public: } }; +/// Game list icon sizes (in px) +static const std::unordered_map IconSizes{ + {UISettings::GameListIconSize::NoIcon, 0}, + {UISettings::GameListIconSize::SmallIcon, 24}, + {UISettings::GameListIconSize::LargeIcon, 48}, +}; + /** * A specialization of GameListItem for path values. * This class ensures that for every full path value it holds, a correct string representation @@ -145,9 +152,18 @@ public: setData(qulonglong(program_id), ProgramIdRole); setData(qulonglong(extdata_id), ExtdataIdRole); + if (UISettings::values.game_list_icon_size == UISettings::GameListIconSize::NoIcon) { + // Do not display icons + setData(QPixmap(), Qt::DecorationRole); + } + + bool large = + UISettings::values.game_list_icon_size == UISettings::GameListIconSize::LargeIcon; + if (!Loader::IsValidSMDH(smdh_data)) { // SMDH is not valid, set a default icon - setData(GetDefaultIcon(true), Qt::DecorationRole); + if (UISettings::values.game_list_icon_size != UISettings::GameListIconSize::NoIcon) + setData(GetDefaultIcon(large), Qt::DecorationRole); return; } @@ -155,7 +171,8 @@ public: memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); // Get icon from SMDH - setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole); + if (UISettings::values.game_list_icon_size != UISettings::GameListIconSize::NoIcon) + setData(GetQPixmapFromSMDH(smdh, large), Qt::DecorationRole); // Get title from SMDH setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), @@ -171,29 +188,23 @@ public: std::string path, filename, extension; Common::SplitPath(data(FullPathRole).toString().toStdString(), &path, &filename, &extension); - QString title = data(TitleRole).toString(); - QString second_name = QString::fromStdString(filename + extension); - static QRegExp installed_pattern( - QString::fromStdString( - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + - "Nintendo " - "3DS/00000000000000000000000000000000/00000000000000000000000000000000/" - "title/0004000(0|e)/[0-9a-f]{8}/content/") - .replace("\\", "\\\\")); - static QRegExp system_pattern( - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "00000000000000000000000000000000/" - "title/00040010/[0-9a-f]{8}/content/") - .replace("\\", "\\\\")); - if (installed_pattern.exactMatch(QString::fromStdString(path)) || - system_pattern.exactMatch(QString::fromStdString(path))) { - // Use a different mechanism for system / installed titles showing program ID - second_name = QString("%1-%2") - .arg(data(ProgramIdRole).toULongLong(), 16, 16, QChar('0')) - .toUpper() - .arg(QString::fromStdString(filename)); + + const std::unordered_map display_texts{ + {UISettings::GameListText::FileName, QString::fromStdString(filename + extension)}, + {UISettings::GameListText::FullPath, data(FullPathRole).toString()}, + {UISettings::GameListText::TitleName, data(TitleRole).toString()}, + {UISettings::GameListText::TitleID, + QString::fromStdString(fmt::format("{:016X}", data(ProgramIdRole).toULongLong()))}, + }; + + const QString& row1 = display_texts.at(UISettings::values.game_list_row_1); + + QString row2; + auto row_2_id = UISettings::values.game_list_row_2; + if (row_2_id != UISettings::GameListText::NoText) { + row2 = (row1.isEmpty() ? "" : "\n ") + display_texts.at(row_2_id); } - return title + (title.isEmpty() ? "" : "\n ") + second_name; + return row1 + row2; } else { return GameListItem::data(role); } @@ -320,18 +331,20 @@ public: UISettings::GameDir* game_dir = &directory; setData(QVariant::fromValue(game_dir), GameDirRole); + + int icon_size = IconSizes.at(UISettings::values.game_list_icon_size); switch (dir_type) { case GameListItemType::InstalledDir: - setData(QIcon::fromTheme("sd_card").pixmap(48), Qt::DecorationRole); + setData(QIcon::fromTheme("sd_card").pixmap(icon_size), Qt::DecorationRole); setData("Installed Titles", Qt::DisplayRole); break; case GameListItemType::SystemDir: - setData(QIcon::fromTheme("chip").pixmap(48), Qt::DecorationRole); + setData(QIcon::fromTheme("chip").pixmap(icon_size), Qt::DecorationRole); setData("System Titles", Qt::DisplayRole); break; case GameListItemType::CustomDir: QString icon_name = QFileInfo::exists(game_dir->path) ? "folder" : "bad_folder"; - setData(QIcon::fromTheme(icon_name).pixmap(48), Qt::DecorationRole); + setData(QIcon::fromTheme(icon_name).pixmap(icon_size), Qt::DecorationRole); setData(game_dir->path, Qt::DisplayRole); break; }; @@ -349,7 +362,9 @@ class GameListAddDir : public GameListItem { public: explicit GameListAddDir() { setData(type(), TypeRole); - setData(QIcon::fromTheme("plus").pixmap(48), Qt::DecorationRole); + + int icon_size = IconSizes.at(UISettings::values.game_list_icon_size); + setData(QIcon::fromTheme("plus").pixmap(icon_size), Qt::DecorationRole); setData("Add New Game Directory", Qt::DisplayRole); } diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index ef7c97d12..9bdcc06a8 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -1286,6 +1286,7 @@ void GMainWindow::OnConfigure() { SetDiscordEnabled(UISettings::values.enable_discord_presence); emit UpdateThemedIcons(); SyncMenuUISettings(); + game_list->RefreshGameDirectory(); config->Save(); } } diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index b36da3341..caa6a353f 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -31,6 +31,20 @@ struct GameDir { }; }; +enum class GameListIconSize { + NoIcon, ///< Do not display icons + SmallIcon, ///< Display a small (24x24) icon + LargeIcon, ///< Display a large (48x48) icon +}; + +enum class GameListText { + NoText = -1, ///< No text + FileName, ///< Display the file name of the entry + FullPath, ///< Display the full path of the entry + TitleName, ///< Display the name of the title + TitleID, ///< Display the title ID +}; + struct Values { QByteArray geometry; QByteArray state; @@ -58,6 +72,12 @@ struct Values { // Discord RPC bool enable_discord_presence; + // Game List + GameListIconSize game_list_icon_size; + GameListText game_list_row_1; + GameListText game_list_row_2; + bool game_list_hide_no_icon; + QString roms_path; QString symbols_path; QString movie_record_path;