From ca9f2d43beeb03ca2127a21ef34ead9f497bcb03 Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 7 Feb 2026 22:48:39 +0100 Subject: [PATCH] [desktop] Add icon-only mode to grid and improve design (#3485) - Move Game Icon Size to the main toolbar. It's cleaner that way - Add a "Show Game Name" toggle that does as it says. Disabling it basically creates an "icons-only" mode. Useful for controller-only nav with big icons (TODO: maybe make a 192 size?) - Fixed a crash with controller nav. Oops - Rounded corners of the game icon in grid mode - Fixed the scroll bar creating extra clamping range on the grid icons - Item can be deselected if user clicks on the blank space outside of the view As a bonus fixed a crash on mod manager Future TODOs for design: - [ ] Row 1 type. Not sure what to do here tbh. - [ ] Move around game list settings in configure_ui to make it clear that nothing there affects the grid view. - [ ] 192x192 size? 256 feels too big on my 1440p screen whereas 128 feels too small. - Set text space as a function of fontMetrics. Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3485 Reviewed-by: DraVee Reviewed-by: Maufeat --- src/frontend_common/mod_manager.cpp | 8 +- src/qt_common/config/uisettings.h | 1 + src/qt_common/util/mod.cpp | 2 + .../configure_per_game_addons.cpp | 8 +- .../configuration/configure_per_game_addons.h | 2 +- src/yuzu/configuration/configure_ui.cpp | 29 +--- src/yuzu/configuration/configure_ui.ui | 20 +-- src/yuzu/game/game_card.cpp | 61 +++++--- src/yuzu/game/game_card.h | 3 +- src/yuzu/game/game_list.cpp | 143 ++++++++++++------ src/yuzu/main.ui | 25 +++ src/yuzu/main_window.cpp | 91 ++++++++++- src/yuzu/main_window.h | 6 + 13 files changed, 271 insertions(+), 128 deletions(-) diff --git a/src/frontend_common/mod_manager.cpp b/src/frontend_common/mod_manager.cpp index 6ac79d22e2..cbbb3b3400 100644 --- a/src/frontend_common/mod_manager.cpp +++ b/src/frontend_common/mod_manager.cpp @@ -18,10 +18,10 @@ std::vector GetModFolder(const std::string& root) { auto callback = [&paths](const std::filesystem::directory_entry& entry) -> bool { const auto name = entry.path().filename().string(); - static constexpr const std::array valid_names = {"exefs", - "romfs" - "romfs_ext", - "cheats", "romfslite"}; + static const std::array valid_names = {"exefs", + "romfs" + "romfs_ext", + "cheats", "romfslite"}; if (std::ranges::find(valid_names, name) != valid_names.end()) { paths.emplace_back(entry.path().parent_path()); diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 89e3833508..3c75268377 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -211,6 +211,7 @@ struct Values { Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; Setting game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList}; + Setting show_game_name{linkage, true, "show_game_name", Category::UiGameList}; std::atomic_bool is_game_list_reload_pending{false}; Setting cache_game_list{linkage, true, "cache_game_list", Category::UiGameList}; diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index 1168bde2f6..f32076fada 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -43,6 +43,8 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { QString name = QtCommon::Frontend::GetTextInput( tr("Mod Name"), tr("What should this mod be called?"), default_name); + if (name.isEmpty()) return {}; + // if std_path is empty, frontend_common could not determine mod type and/or name. // so we have to prompt the user and set up the structure ourselves if (paths.empty()) { diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index ecafa6826a..da84d23876 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -161,14 +161,14 @@ void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { } } -void ConfigurePerGameAddons::InstallModPath(const QString& path) { - const auto mods = QtCommon::Mod::GetModFolders(path, {}); +void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString &fallbackName) { + const auto mods = QtCommon::Mod::GetModFolders(path, fallbackName); if (mods.size() > 1) { ModSelectDialog* dialog = new ModSelectDialog(mods, this); connect(dialog, &ModSelectDialog::modsSelected, this, &ConfigurePerGameAddons::InstallMods); dialog->show(); - } else { + } else if (!mods.empty()) { InstallMods(mods); } } @@ -194,7 +194,7 @@ void ConfigurePerGameAddons::InstallModZip() { const QString extracted = QtCommon::Mod::ExtractMod(path); if (!extracted.isEmpty()) - InstallModPath(extracted); + InstallModPath(extracted, QFileInfo(path).baseName()); } void ConfigurePerGameAddons::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 3c7c0b0ff0..d2f361139b 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -44,7 +44,7 @@ public: public slots: void InstallMods(const QStringList &mods); - void InstallModPath(const QString& path); + void InstallModPath(const QString& path, const QString& fallbackName = {}); void InstallModFolder(); void InstallModZip(); diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index b88f41b756..0e91a1a9fd 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "yuzu/configuration/configure_ui.h" @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -21,7 +20,6 @@ #include "common/common_types.h" #include "common/fs/path_util.h" -#include "common/logging/log.h" #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" @@ -32,13 +30,6 @@ #include "qt_common/config/uisettings.h" namespace { -constexpr std::array default_game_icon_sizes{ - std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), - std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), - std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), - std::make_pair(128, QT_TRANSLATE_NOOP("ConfigureUI", "Large (128x128)")), - std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), -}; constexpr std::array default_folder_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), @@ -57,10 +48,6 @@ constexpr std::array row_text_names{ }; // clang-format on -QString GetTranslatedGameIconSize(size_t index) { - return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second); -} - QString GetTranslatedFolderIconSize(size_t index) { return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second); } @@ -127,8 +114,6 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) connect(ui->show_types, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_play_time, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); - connect(ui->game_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, - &ConfigureUi::RequestGameListUpdate); connect(ui->folder_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_1_text_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -172,7 +157,6 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.show_size = ui->show_size->isChecked(); UISettings::values.show_types = ui->show_types->isChecked(); UISettings::values.show_play_time = ui->show_play_time->isChecked(); - UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); @@ -202,8 +186,6 @@ void ConfigureUi::SetConfiguration() { ui->show_size->setChecked(UISettings::values.show_size.GetValue()); ui->show_types->setChecked(UISettings::values.show_types.GetValue()); ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); - ui->game_icon_size_combobox->setCurrentIndex( - ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); ui->folder_icon_size_combobox->setCurrentIndex( ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue())); @@ -231,11 +213,6 @@ void ConfigureUi::changeEvent(QEvent* event) { void ConfigureUi::RetranslateUI() { ui->retranslateUi(this); - for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) { - ui->game_icon_size_combobox->setItemText(i, - GetTranslatedGameIconSize(static_cast(i))); - } - for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) { ui->folder_icon_size_combobox->setItemText( i, GetTranslatedFolderIconSize(static_cast(i))); @@ -270,10 +247,6 @@ void ConfigureUi::InitializeLanguageComboBox() { } void ConfigureUi::InitializeIconSizeComboBox() { - for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { - const auto size = default_game_icon_sizes[i].first; - ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size); - } for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) { const auto size = default_folder_icon_sizes[i].first; ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index b8e6483814..123068c9e2 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -7,7 +7,7 @@ 0 0 363 - 603 + 613 @@ -111,20 +111,6 @@ - - - - - - Game Icon Size: - - - - - - - - @@ -221,7 +207,7 @@ TextLabel - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -251,7 +237,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/src/yuzu/game/game_card.cpp b/src/yuzu/game/game_card.cpp index d172d3e535..26ab99faaf 100644 --- a/src/yuzu/game/game_card.cpp +++ b/src/yuzu/game/game_card.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include "game_card.h" #include "qt_common/config/uisettings.h" @@ -18,7 +19,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->setRenderHint(QPainter::Antialiasing); // padding - QRect cardRect = option.rect.adjusted(4, 4, -4, -4); + QRect cardRect = option.rect.adjusted(4 + m_padding / 2, 4, -4 - m_padding / 2, -4); // colors QPalette palette = option.palette; @@ -32,7 +33,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, borderColor = palette.highlight().color().lighter(150); textColor = palette.highlightedText().color(); } else if (option.state & QStyle::State_MouseOver) { - backgroundColor = backgroundColor.lighter(110); + backgroundColor = backgroundColor.lighter(120); } // bg @@ -40,7 +41,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->setPen(QPen(borderColor, 1)); painter->drawRoundedRect(cardRect, 10, 10); - static constexpr const int padding = 10; + static constexpr const int padding = 8; // icon int _iconsize = UISettings::values.game_icon_size.GetValue(); @@ -58,7 +59,18 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, iconRect = QRect(x, y, scaledSize.width(), scaledSize.height()); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); + + // Put this in a separate thing on the painter stack to prevent clipping the text. + painter->save(); + + // round image edges + QPainterPath path; + path.addRoundedRect(iconRect, 10, 10); + painter->setClipPath(path); + painter->drawPixmap(iconRect, iconPixmap); + + painter->restore(); } else { // if there is no icon just draw a blank rect iconRect = QRect(cardRect.left() + padding, @@ -66,31 +78,33 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, _iconsize, _iconsize); } - // if "none" is selected, pretend there's a - _iconsize = _iconsize ? _iconsize : 96; + if (UISettings::values.show_game_name.GetValue()) { + // if "none" is selected, pretend there's a + _iconsize = _iconsize ? _iconsize : 96; - // padding + text - QRect textRect = cardRect; - textRect.setTop(iconRect.bottom() + 8); - textRect.adjust(padding, 0, -padding, -padding); + // padding + text + QRect textRect = cardRect; + textRect.setTop(iconRect.bottom() + 8); + textRect.adjust(padding, 0, -padding, -padding); - // We are already crammed on space, ignore the row 2 - QString title = index.data(Qt::DisplayRole).toString(); - title = title.split(QLatin1Char('\n')).first(); + // We are already crammed on space, ignore the row 2 + QString title = index.data(Qt::DisplayRole).toString(); + title = title.split(QLatin1Char('\n')).first(); - // now draw text - painter->setPen(textColor); - QFont font = option.font; - font.setBold(true); + // now draw text + painter->setPen(textColor); + QFont font = option.font; + font.setBold(true); - // TODO(crueter): fix this abysmal scaling - // If "none" is selected, then default to 8.5 point font. - font.setPointSize(1 + std::max(7.0, _iconsize ? std::sqrt(_iconsize * 0.6) : 7.5)); + // TODO(crueter): fix this abysmal scaling + // If "none" is selected, then default to 8.5 point font. + font.setPointSize(1 + std::max(7.0, _iconsize ? std::sqrt(_iconsize * 0.6) : 7.5)); - // TODO(crueter): elide mode - painter->setFont(font); + // TODO(crueter): elide mode + painter->setFont(font); - painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, title); + painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, title); + } painter->restore(); } @@ -99,6 +113,7 @@ QSize GameCard::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& return m_size; } -void GameCard::setSize(const QSize& newSize) { +void GameCard::setSize(const QSize& newSize, const int padding) { m_size = newSize; + m_padding = padding; } diff --git a/src/yuzu/game/game_card.h b/src/yuzu/game/game_card.h index 3c695c9047..9fd0ec081a 100644 --- a/src/yuzu/game/game_card.h +++ b/src/yuzu/game/game_card.h @@ -20,8 +20,9 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void setSize(const QSize& newSize); + void setSize(const QSize& newSize, const int padding); private: QSize m_size; + int m_padding; }; diff --git a/src/yuzu/game/game_list.cpp b/src/yuzu/game/game_list.cpp index 515fed1a8d..80995a0e79 100644 --- a/src/yuzu/game/game_list.cpp +++ b/src/yuzu/game/game_list.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include @@ -13,13 +14,12 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include +#include #include "common/common_types.h" #include "common/logging/log.h" #include "common/settings.h" @@ -28,6 +28,7 @@ #include "core/file_sys/registered_cache.h" #include "game/game_card.h" #include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" #include "qt_common/util/game.h" #include "yuzu/compatibility_list.h" #include "yuzu/game/game_list.h" @@ -35,7 +36,6 @@ #include "yuzu/game/game_list_worker.h" #include "yuzu/main_window.h" #include "yuzu/util/controller_navigation.h" -#include "qt_common/qt_common.h" GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) : QObject(parent), gamelist{gamelist_} {} @@ -393,16 +393,21 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); // list view setup - list_view->setViewMode(QListView::IconMode); - list_view->setResizeMode(QListView::Adjust); - list_view->setUniformItemSizes(false); + list_view->setViewMode(QListView::ListMode); + list_view->setResizeMode(QListView::Fixed); + list_view->setUniformItemSizes(true); list_view->setSelectionMode(QAbstractItemView::SingleSelection); list_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); list_view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + // Forcefully disable scroll bar, prevents thing where game list items + // will start clamping prematurely. + list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + list_view->setEditTriggers(QAbstractItemView::NoEditTriggers); list_view->setContextMenuPolicy(Qt::CustomContextMenu); list_view->setGridSize(QSize(140, 160)); - m_gameCard->setSize(list_view->gridSize()); + m_gameCard->setSize(list_view->gridSize(), 0); list_view->setSpacing(10); list_view->setWordWrap(true); @@ -438,8 +443,8 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid return; } QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); - QCoreApplication::postEvent(tree_view, event); - QCoreApplication::postEvent(list_view, event); + + QCoreApplication::postEvent(m_currentView, event); }); // We must register all custom types with the Qt Automoc system so that we are able to use @@ -488,27 +493,26 @@ void GameList::ResetViewMode() { break; } - if (m_isTreeMode != newTreeMode) { - m_isTreeMode = newTreeMode; - - auto view = m_currentView->viewport(); + auto view = m_currentView->viewport(); + view->installEventFilter(this); - view->installEventFilter(this); + // touch gestures + view->grabGesture(Qt::SwipeGesture); + view->grabGesture(Qt::PanGesture); - // touch gestures - view->grabGesture(Qt::SwipeGesture); - view->grabGesture(Qt::PanGesture); + // TODO: touch? + QScroller::grabGesture(view, QScroller::LeftMouseButtonGesture); - // TODO: touch? - QScroller::grabGesture(view, QScroller::LeftMouseButtonGesture); + auto scroller = QScroller::scroller(view); + QScrollerProperties props; + props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, + QScrollerProperties::OvershootAlwaysOff); + props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, + QScrollerProperties::OvershootAlwaysOff); + scroller->setScrollerProperties(props); - auto scroller = QScroller::scroller(view); - QScrollerProperties props; - props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, - QScrollerProperties::OvershootAlwaysOff); - props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, - QScrollerProperties::OvershootAlwaysOff); - scroller->setScrollerProperties(props); + if (m_isTreeMode != newTreeMode) { + m_isTreeMode = newTreeMode; RefreshGameDirectory(); } @@ -1007,30 +1011,55 @@ void GameList::UpdateIconSize() { // Update sizes and stuff for the list view const u32 icon_size = UISettings::values.game_icon_size.GetValue(); - // the scaling on the card is kinda abysmal. - // TODO(crueter): refactor int heightMargin = 0; int widthMargin = 80; - switch (icon_size) { - case 128: - heightMargin = 70; - break; - case 0: - widthMargin = 120; - heightMargin = 120; - break; - case 64: - heightMargin = 80; - break; - case 32: - case 256: - heightMargin = 81; - break; + + if (UISettings::values.show_game_name) { + // the scaling on the card is kinda abysmal. + // TODO(crueter): refactor + switch (icon_size) { + case 128: + heightMargin = 65; + break; + case 0: + widthMargin = 120; + heightMargin = 120; + break; + case 64: + heightMargin = 77; + break; + case 32: + case 256: + heightMargin = 81; + break; + } + } else { + widthMargin = 24; + heightMargin = 24; } - // TODO(crueter): Auto size - list_view->setGridSize(QSize(icon_size + widthMargin, icon_size + heightMargin)); - m_gameCard->setSize(list_view->gridSize()); + // "auto" resize // + const int view_width = list_view->viewport()->width(); + + // Tiny space padding to prevent the list view from forcing its own resize operation. + const double spacing = 0.01; + const int min_item_width = icon_size + widthMargin; + + // And now stretch it a bit to fill out remaining space. + // Not perfect but works well enough for now + int columns = std::max(1, view_width / min_item_width); + int stretched_width = (view_width - (spacing * (columns - 1))) / columns; + + // only updates things if grid size is changed + QSize grid_size(stretched_width, icon_size + heightMargin); + if (list_view->gridSize() != grid_size) { + list_view->setUpdatesEnabled(false); + + list_view->setGridSize(grid_size); + m_gameCard->setSize(grid_size, stretched_width - min_item_width); + + list_view->setUpdatesEnabled(true); + } } void GameList::PopulateAsync(QVector& game_dirs) { @@ -1043,7 +1072,8 @@ void GameList::PopulateAsync(QVector& game_dirs) { tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - UpdateIconSize(); + if (!m_isTreeMode) + UpdateIconSize(); // Cancel any existing worker. current_worker.reset(); @@ -1266,6 +1296,23 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { return true; } + + if (obj == m_currentView->viewport() && event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + + // if the user clicks outside of the list, deselect the current item. + QModelIndex index = m_currentView->indexAt(mouseEvent->pos()); + if (!index.isValid()) { + m_currentView->selectionModel()->clearSelection(); + m_currentView->setCurrentIndex(QModelIndex()); + } + } + + if (obj == list_view->viewport() && event->type() == QEvent::Resize) { + UpdateIconSize(); + return true; + } + return QWidget::eventFilter(obj, event); } diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 54fc778f80..327a7dffd2 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -112,6 +112,11 @@ + + + Game &Icon Size + + Reset Window Size to &720p @@ -145,6 +150,8 @@ + + @@ -586,6 +593,24 @@ &Grid View + + + Game Icon Size + + + + + None + + + + + true + + + Show Game &Name + + diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index def24ef5d3..2e5548c775 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -73,6 +73,7 @@ #include #include #include +#include // Qt Common // #include "qt_common/config/uisettings.h" @@ -377,6 +378,22 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } +namespace { + +constexpr std::array, 5> default_game_icon_sizes{ + std::make_pair(0, QT_TRANSLATE_NOOP("MainWindow", "None")), + std::make_pair(32, QT_TRANSLATE_NOOP("MainWindow", "Small (32x32)")), + std::make_pair(64, QT_TRANSLATE_NOOP("MainWindow", "Standard (64x64)")), + std::make_pair(128, QT_TRANSLATE_NOOP("MainWindow", "Large (128x128)")), + std::make_pair(256, QT_TRANSLATE_NOOP("MainWindow", "Full Size (256x256)")), +}; + +QString GetTranslatedGameIconSize(size_t index) { + return QCoreApplication::translate("MainWindow", default_game_icon_sizes[index].second); +} + +} + #ifndef _WIN32 // TODO(crueter): carboxyl does this, is it needed in qml? inline static bool isDarkMode() { @@ -1607,6 +1624,33 @@ void MainWindow::ConnectMenuEvents() { connect_menu(ui->action_Grid_View, &MainWindow::SetGridView); connect_menu(ui->action_Tree_View, &MainWindow::SetTreeView); + game_size_actions = new QActionGroup(this); + game_size_actions->setExclusive(true); + + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto current_size = UISettings::values.game_icon_size.GetValue(); + const auto size = default_game_icon_sizes[i].first; + QAction *action = ui->menuGame_Icon_Size->addAction(GetTranslatedGameIconSize(i)); + action->setCheckable(true); + + if (current_size == size) action->setChecked(true); + + game_size_actions->addAction(action); + + connect(action, &QAction::triggered, this, [this, size](bool checked) { + if (checked) { + UISettings::values.game_icon_size.SetValue(size); + CheckIconSize(); + game_list->RefreshGameDirectory(); + } + }); + } + + CheckIconSize(); + + ui->action_Show_Game_Name->setChecked(UISettings::values.show_game_name.GetValue()); + connect(ui->action_Show_Game_Name, &QAction::triggered, this, &MainWindow::ToggleShowGameName); + // Multiplayer connect(ui->action_View_Lobby, &QAction::triggered, multiplayer_state, &MultiplayerState::OnViewLobby); @@ -3385,6 +3429,9 @@ void MainWindow::SetGameListMode(Settings::GameListMode mode) { ui->action_Tree_View->setChecked(mode == Settings::GameListMode::TreeView); UISettings::values.game_list_mode = mode; + ui->action_Show_Game_Name->setEnabled(mode == Settings::GameListMode::GridView); + + CheckIconSize(); game_list->ResetViewMode(); } @@ -3396,6 +3443,43 @@ void MainWindow::SetTreeView() { SetGameListMode(Settings::GameListMode::TreeView); } +void MainWindow::CheckIconSize() { + // When in grid view mode, with text off + // there is no point in having icons turned off.. + auto actions = game_size_actions->actions(); + if (UISettings::values.game_list_mode.GetValue() == Settings::GameListMode::GridView && + !UISettings::values.show_game_name.GetValue()) { + u32 newSize = UISettings::values.game_icon_size.GetValue(); + if (newSize == 0) { + newSize = 64; + UISettings::values.game_icon_size.SetValue(newSize); + } + + // Then disable the "none" action and update that menu. + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto current_size = newSize; + const auto size = default_game_icon_sizes[i].first; + if (current_size == size) actions.at(i)->setChecked(true); + } + + // Update this if you add anything before None. + actions.at(0)->setEnabled(false); + } else { + actions.at(0)->setEnabled(true); + } +} + +void MainWindow::ToggleShowGameName() { + auto &setting = UISettings::values.show_game_name; + const bool newValue = !setting.GetValue(); + ui->action_Show_Game_Name->setChecked(newValue); + setting.SetValue(newValue); + + CheckIconSize(); + + game_list->RefreshGameDirectory(); +} + void MainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); @@ -3916,7 +4000,6 @@ void MainWindow::OnDataDialog() { // refresh stuff in case it was cleared OnGameListRefresh(); - } void MainWindow::OnToggleFilterBar() { @@ -3939,7 +4022,6 @@ void MainWindow::OnGameListRefresh() { SetFirmwareVersion(); } - void MainWindow::LaunchFirmwareApplet(u64 raw_program_id, std::optional cabinet_mode) { auto const program_id = Service::AM::AppletProgramId(raw_program_id); auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); @@ -4658,6 +4740,11 @@ void MainWindow::OnLanguageChanged(const QString& locale) { qApp->removeTranslator(&translator); } + QList actions = game_size_actions->actions(); + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + actions.at(i)->setText(GetTranslatedGameIconSize(i)); + } + UISettings::values.language = locale.toStdString(); LoadTranslation(); ui->retranslateUi(this); diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index 3261ccc9a1..ddabe21ae0 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "common/common_types.h" #include "common/settings_enums.h" @@ -407,6 +408,9 @@ private slots: void SetGridView(); void SetTreeView(); + void CheckIconSize(); + void ToggleShowGameName(); + void LaunchFirmwareApplet(u64 program_id, std::optional mode); void OnCreateHomeMenuDesktopShortcut(); void OnCreateHomeMenuApplicationMenuShortcut(); @@ -532,6 +536,8 @@ private: QString startup_icon_theme; + QActionGroup *game_size_actions; + // Debugger panes ControllerDialog* controller_dialog = nullptr;