diff --git a/src/common/config.cpp b/src/common/config.cpp index 2059da0b3..d9dfb861f 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -95,6 +95,8 @@ std::vector m_pkg_viewer; std::vector m_elf_viewer; std::vector m_recent_files; std::string emulator_language = "en"; +static int backgroundImageOpacity = 50; +static bool showBackgroundImage = true; // Language u32 m_language = 1; // english @@ -611,6 +613,22 @@ u32 GetLanguage() { return m_language; } +int getBackgroundImageOpacity() { + return backgroundImageOpacity; +} + +void setBackgroundImageOpacity(int opacity) { + backgroundImageOpacity = std::clamp(opacity, 0, 100); +} + +bool getShowBackgroundImage() { + return showBackgroundImage; +} + +void setShowBackgroundImage(bool show) { + showBackgroundImage = show; +} + void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -731,6 +749,8 @@ void load(const std::filesystem::path& path) { m_recent_files = toml::find_or>(gui, "recentFiles", {}); m_table_mode = toml::find_or(gui, "gameTableMode", 0); emulator_language = toml::find_or(gui, "emulatorLanguage", "en"); + backgroundImageOpacity = toml::find_or(gui, "backgroundImageOpacity", 50); + showBackgroundImage = toml::find_or(gui, "showBackgroundImage", true); } if (data.contains("Settings")) { @@ -821,6 +841,8 @@ void save(const std::filesystem::path& path) { data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["GUI"]["emulatorLanguage"] = emulator_language; + data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity; + data["GUI"]["showBackgroundImage"] = showBackgroundImage; data["Settings"]["consoleLanguage"] = m_language; std::ofstream file(path, std::ios::binary); @@ -914,6 +936,8 @@ void setDefaultValues() { separateupdatefolder = false; compatibilityData = false; checkCompatibilityOnStartup = false; + backgroundImageOpacity = 50; + showBackgroundImage = true; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 77ed69ece..69e497527 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -30,6 +30,8 @@ bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); +int getBackgroundImageOpacity(); +bool getShowBackgroundImage(); std::string getLogFilter(); std::string getLogType(); @@ -88,6 +90,8 @@ void setGameInstallDirs(const std::vector& settings_insta void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); +void setBackgroundImageOpacity(int opacity); +void setShowBackgroundImage(bool show); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index d719ac878..2db4b7e4e 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -38,17 +38,34 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) { - crtRow = currentRow; - crtColumn = currentColumn; - columnCnt = this->columnCount(); - - auto itemID = (crtRow * columnCnt) + currentColumn; - if (itemID > m_game_info->m_games.count() - 1) { + // Early exit for invalid indices + if (currentRow < 0 || currentColumn < 0) { cellClicked = false; validCellSelected = false; BackgroundMusicPlayer::getInstance().stopMusic(); return; } + + crtRow = currentRow; + crtColumn = currentColumn; + columnCnt = this->columnCount(); + + // Prevent integer overflow + if (columnCnt <= 0 || crtRow > (std::numeric_limits::max() / columnCnt)) { + cellClicked = false; + validCellSelected = false; + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + + auto itemID = (crtRow * columnCnt) + currentColumn; + if (itemID < 0 || itemID > m_game_info->m_games.count() - 1) { + cellClicked = false; + validCellSelected = false; + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + cellClicked = true; validCellSelected = true; SetGridBackgroundImage(crtRow, crtColumn); @@ -65,6 +82,8 @@ void GameGridFrame::PlayBackgroundMusic(QString path) { } void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool fromSearch) { + this->crtRow = -1; + this->crtColumn = -1; QVector m_games_; this->clearContents(); if (fromSearch) @@ -136,43 +155,48 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from } void GameGridFrame::SetGridBackgroundImage(int row, int column) { - int itemID = (row * this->columnCount()) + column; QWidget* item = this->cellWidget(row, column); - if (item) { - QString pic1Path; - Common::FS::PathToQString(pic1Path, (*m_games_shared)[itemID].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); - - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); - - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - (*m_games_shared)[itemID].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; - } - } - RefreshGridBackgroundImage(); + if (!item) { + // handle case where no item was clicked + return; } + + // If background images are hidden, clear the background image + if (!Config::getShowBackgroundImage()) { + backgroundImage = QImage(); + m_last_opacity = -1; // Reset opacity tracking when disabled + m_current_game_path.clear(); // Reset current game path + RefreshGridBackgroundImage(); + return; + } + + const auto& game = (*m_games_shared)[itemID]; + const int opacity = Config::getBackgroundImageOpacity(); + + // Recompute if opacity changed or we switched to a different game + if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + m_last_opacity = opacity; + m_current_game_path = game.pic_path; + } + } + + RefreshGridBackgroundImage(); } void GameGridFrame::RefreshGridBackgroundImage() { - if (!backgroundImage.isNull()) { - QPalette palette; + QPalette palette; + if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { palette.setBrush(QPalette::Base, QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); } + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); } bool GameGridFrame::IsValidCellSelected() { diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 4825d6daf..370b71dcb 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -33,6 +33,8 @@ private: std::shared_ptr m_compat_info; std::shared_ptr> m_games_shared; bool validCellSelected = false; + int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation + std::filesystem::path m_current_game_path; // Track current game path to detect changes public: explicit GameGridFrame(std::shared_ptr game_info_get, diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index f2d08f578..64c0f17ba 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -89,6 +89,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int if (!item) { return; } + m_current_item = item; // Store current item SetListBackgroundImage(item); PlayBackgroundMusic(item); } @@ -104,6 +105,7 @@ void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { } void GameListFrame::PopulateGameList(bool isInitialPopulation) { + this->m_current_item = nullptr; // Do not show status column if it is not enabled this->setColumnHidden(2, !Config::getCompatibilityEnabled()); this->setColumnHidden(6, !Config::GetLoadGameSizeEnabled()); @@ -167,38 +169,41 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { return; } - QString pic1Path; - Common::FS::PathToQString(pic1Path, m_game_info->m_games[item->row()].pic_path); - const auto blurredPic1Path = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial / "pic1.png"; - QString blurredPic1PathQt; - Common::FS::PathToQString(blurredPic1PathQt, blurredPic1Path); + // If background images are hidden, clear the background image + if (!Config::getShowBackgroundImage()) { + backgroundImage = QImage(); + m_last_opacity = -1; // Reset opacity tracking when disabled + m_current_game_path.clear(); // Reset current game path + RefreshListBackgroundImage(); + return; + } - backgroundImage = QImage(blurredPic1PathQt); - if (backgroundImage.isNull()) { - QImage image(pic1Path); - backgroundImage = m_game_list_utils.BlurImage(image, image.rect(), 16); + const auto& game = m_game_info->m_games[item->row()]; + const int opacity = Config::getBackgroundImageOpacity(); - std::filesystem::path img_path = - Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / - m_game_info->m_games[item->row()].serial; - std::filesystem::create_directories(img_path); - if (!backgroundImage.save(blurredPic1PathQt, "PNG")) { - // qDebug() << "Error: Unable to save image."; + // Recompute if opacity changed or we switched to a different game + if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { + QImage original_image(QString::fromStdString(game.pic_path.string())); + if (!original_image.isNull()) { + backgroundImage = m_game_list_utils.ChangeImageOpacity( + original_image, original_image.rect(), opacity / 100.0f); + m_last_opacity = opacity; + m_current_game_path = game.pic_path; } } + RefreshListBackgroundImage(); } void GameListFrame::RefreshListBackgroundImage() { - if (!backgroundImage.isNull()) { - QPalette palette; + QPalette palette; + if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { palette.setBrush(QPalette::Base, QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); } + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); } void GameListFrame::SortNameAscending(int columnIndex) { @@ -392,3 +397,7 @@ QString GameListFrame::GetPlayTime(const std::string& serial) { file.close(); return playTime; } + +QTableWidgetItem* GameListFrame::GetCurrentItem() { + return m_current_item; +} \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 7e37c4ea7..b2e5f1e2f 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -44,11 +44,14 @@ private: QList m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; + QTableWidgetItem* m_current_item = nullptr; + int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation + std::filesystem::path m_current_game_path; // Track current game path to detect changes public: void PopulateGameList(bool isInitialPopulation = true); void ResizeIcons(int iconSize); - + QTableWidgetItem* GetCurrentItem(); QImage backgroundImage; GameListUtils m_game_list_utils; GuiContextMenus m_gui_context_menus; diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 581a8a55f..c6b69e70e 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -201,4 +201,30 @@ public: return result; } + + // Opacity is a float between 0 and 1 + static QImage ChangeImageOpacity(const QImage& image, const QRect& rect, float opacity) { + // Convert to ARGB32 format to ensure alpha channel support + QImage result = image.convertToFormat(QImage::Format_ARGB32); + + // Ensure opacity is between 0 and 1 + opacity = std::clamp(opacity, 0.0f, 1.0f); + + // Convert opacity to integer alpha value (0-255) + int alpha = static_cast(opacity * 255); + + // Process only the specified rectangle area + for (int y = rect.top(); y <= rect.bottom(); ++y) { + QRgb* line = reinterpret_cast(result.scanLine(y)); + for (int x = rect.left(); x <= rect.right(); ++x) { + // Get current pixel + QRgb pixel = line[x]; + // Keep RGB values, but modify alpha while preserving relative transparency + int newAlpha = (qAlpha(pixel) * alpha) / 255; + line[x] = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), newAlpha); + } + } + + return result; + } }; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 0ab9d3a42..67615a1b6 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -297,6 +297,23 @@ void MainWindow::CreateConnects() { connect(settingsDialog, &SettingsDialog::CompatibilityChanged, this, &MainWindow::RefreshGameTable); + connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this, + [this](int opacity) { + Config::setBackgroundImageOpacity(opacity); + if (m_game_list_frame) { + QTableWidgetItem* current = m_game_list_frame->GetCurrentItem(); + if (current) { + m_game_list_frame->SetListBackgroundImage(current); + } + } + if (m_game_grid_frame) { + if (m_game_grid_frame->IsValidCellSelected()) { + m_game_grid_frame->SetGridBackgroundImage(m_game_grid_frame->crtRow, + m_game_grid_frame->crtColumn); + } + } + }); + settingsDialog->exec(); }); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 7505db106..8f4b22c6d 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -173,6 +173,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, { connect(ui->chooseHomeTabComboBox, &QComboBox::currentTextChanged, this, [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); + + connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, + [](int state) { Config::setShowBackgroundImage(state == Qt::Checked); }); } // Input TAB { @@ -251,6 +254,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, #ifdef ENABLE_UPDATER ui->updaterGroupBox->installEventFilter(this); #endif + ui->GUIBackgroundImageGroupBox->installEventFilter(this); ui->GUIMusicGroupBox->installEventFilter(this); ui->disableTrophycheckBox->installEventFilter(this); ui->enableCompatibilityCheckBox->installEventFilter(this); @@ -410,6 +414,8 @@ void SettingsDialog::LoadValuesFromConfig() { ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ResetInstallFolders(); + ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity()); + ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage()); } void SettingsDialog::InitializeEmulatorLanguages() { @@ -504,6 +510,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { } else if (elementName == "updaterGroupBox") { text = tr("updaterGroupBox"); #endif + } else if (elementName == "GUIBackgroundImageGroupBox") { + text = tr("GUIBackgroundImageGroupBox"); } else if (elementName == "GUIMusicGroupBox") { text = tr("GUIMusicGroupBox"); } else if (elementName == "disableTrophycheckBox") { @@ -638,6 +646,9 @@ void SettingsDialog::UpdateSettings() { Config::setChooseHomeTab(ui->chooseHomeTabComboBox->currentText().toStdString()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); + Config::setBackgroundImageOpacity(ui->backgroundImageOpacitySlider->value()); + emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); + Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked()); #ifdef ENABLE_DISCORD_RPC auto* rpc = Common::Singleton::Instance(); diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 892e67671..c440351f6 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -33,6 +33,7 @@ public: signals: void LanguageChanged(const std::string& locale); void CompatibilityChanged(); + void BackgroundOpacityChanged(int opacity); private: void LoadValuesFromConfig(); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index d15f49efe..80f7a117e 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -583,6 +583,76 @@ + + + + Background Image + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Show Background Image + + + + + + + 9 + + + + + + 0 + 0 + + + + Opacity + + + + + + + + 0 + 0 + + + + 0 + + + 100 + + + 50 + + + Qt::Orientation::Horizontal + + + + + + + + diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index afaa17520..d0540d7cd 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -757,6 +757,18 @@ Disable Trophy Pop-ups Disable Trophy Pop-ups + + Background Image + Background Image + + + Show Background Image + Show Background Image + + + Opacity + Opacity + Play title music Play title music @@ -853,6 +865,10 @@ updaterGroupBox Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + + GUIBackgroundImageGroupBox + Background Image:\nControl the opacity of the game background image. + GUIMusicGroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index d732e67ea..772980994 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -748,6 +748,18 @@ Disable Trophy Pop-ups Disable Trophy Pop-ups + + Background Image + Imagen de fondo + + + Show Background Image + Mostrar Imagen de Fondo + + + Opacity + Opacidad + Play title music Reproducir la música de apertura @@ -844,6 +856,10 @@ updaterGroupBox Actualización:\nRelease: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nNightly: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables. + + GUIBackgroundImageGroupBox + Imagen de fondo:\nControle la opacidad de la imagen de fondo del juego. + GUIMusicGroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica.