diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 585cf62a3e..8d33993fc1 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -129,6 +129,8 @@ add_executable(dolphin-emu Config/Graphics/PostProcessingConfigWindow.h Config/Graphics/SoftwareRendererWidget.cpp Config/Graphics/SoftwareRendererWidget.h + Config/GraphicsModListWidget.cpp + Config/GraphicsModListWidget.h Config/InfoWidget.cpp Config/InfoWidget.h Config/LogConfigWidget.cpp diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp new file mode 100644 index 0000000000..8782bbc97d --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp @@ -0,0 +1,268 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/GraphicsModListWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "DolphinQt/Settings.h" +#include "UICommon/GameFile.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/VideoConfig.h" + +GraphicsModListWidget::GraphicsModListWidget(const UICommon::GameFile& game) + : m_game_id(game.GetGameID()), m_mod_group(m_game_id) +{ + CalculateGameRunning(Core::GetState()); + if (m_loaded_game_is_running && g_Config.graphics_mod_config) + { + m_mod_group.SetChangeCount(g_Config.graphics_mod_config->GetChangeCount()); + } + CreateWidgets(); + ConnectWidgets(); + + RefreshModList(); + OnModChanged(std::nullopt); +} + +GraphicsModListWidget::~GraphicsModListWidget() +{ + if (m_needs_save) + { + m_mod_group.Save(); + } +} + +void GraphicsModListWidget::CreateWidgets() +{ + auto* main_layout = new QHBoxLayout(this); + + auto* left_v_layout = new QVBoxLayout; + + m_mod_list = new QListWidget; + m_mod_list->setSortingEnabled(false); + m_mod_list->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems); + m_mod_list->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_mod_list->setSelectionRectVisible(true); + m_mod_list->setDragDropMode(QAbstractItemView::InternalMove); + + m_refresh = new QPushButton(tr("&Refresh List")); + QHBoxLayout* hlayout = new QHBoxLayout; + hlayout->addStretch(); + hlayout->addWidget(m_refresh); + + left_v_layout->addWidget(m_mod_list); + left_v_layout->addLayout(hlayout); + + auto* right_v_layout = new QVBoxLayout; + + m_selected_mod_name = new QLabel(); + right_v_layout->addWidget(m_selected_mod_name); + + m_mod_meta_layout = new QVBoxLayout; + right_v_layout->addLayout(m_mod_meta_layout); + + main_layout->addLayout(left_v_layout); + main_layout->addLayout(right_v_layout, 1); + + setLayout(main_layout); +} + +void GraphicsModListWidget::ConnectWidgets() +{ + connect(m_mod_list, &QListWidget::itemSelectionChanged, this, + &GraphicsModListWidget::ModSelectionChanged); + + connect(m_mod_list, &QListWidget::itemChanged, this, &GraphicsModListWidget::ModItemChanged); + + connect(m_mod_list->model(), &QAbstractItemModel::rowsMoved, this, + &GraphicsModListWidget::SaveModList); + + connect(m_refresh, &QPushButton::clicked, this, &GraphicsModListWidget::RefreshModList); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &GraphicsModListWidget::CalculateGameRunning); +} + +void GraphicsModListWidget::RefreshModList() +{ + m_mod_list->setCurrentItem(nullptr); + m_mod_list->clear(); + + m_mod_group = GraphicsModGroupConfig(m_game_id); + m_mod_group.Load(); + + std::set groups; + + for (const auto& mod : m_mod_group.GetMods()) + { + if (mod.m_groups.empty()) + continue; + + for (const auto& group : mod.m_groups) + { + groups.insert(group.m_name); + } + } + + for (const auto& mod : m_mod_group.GetMods()) + { + // Group only mods shouldn't be shown + if (mod.m_features.empty()) + continue; + + // If the group doesn't exist in the available mod's features, skip + if (std::none_of(mod.m_features.begin(), mod.m_features.end(), + [&groups](const GraphicsModFeatureConfig& feature) { + return groups.count(feature.m_group) == 1; + })) + { + continue; + } + + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(mod.m_title)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setData(Qt::UserRole, QString::fromStdString(mod.GetAbsolutePath())); + item->setCheckState(mod.m_enabled ? Qt::Checked : Qt::Unchecked); + + m_mod_list->addItem(item); + } +} + +void GraphicsModListWidget::ModSelectionChanged() +{ + if (m_mod_list->currentItem() == nullptr) + return; + if (m_mod_list->count() == 0) + return; + const auto absolute_path = m_mod_list->currentItem()->data(Qt::UserRole).toString().toStdString(); + OnModChanged(absolute_path); +} + +void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item) +{ + const auto absolute_path = item->data(Qt::UserRole).toString(); + GraphicsModConfig* mod = m_mod_group.GetMod(absolute_path.toStdString()); + if (!mod) + return; + + const bool was_enabled = mod->m_enabled; + const bool should_enable = item->checkState() == Qt::Checked; + mod->m_enabled = should_enable; + if (was_enabled == should_enable) + return; + + m_mod_group.SetChangeCount(m_mod_group.GetChangeCount() + 1); + if (m_loaded_game_is_running) + { + g_Config.graphics_mod_config = m_mod_group; + } + m_needs_save = true; +} + +void GraphicsModListWidget::OnModChanged(std::optional absolute_path) +{ + ClearLayoutRecursively(m_mod_meta_layout); + + adjustSize(); + + if (!absolute_path) + { + m_selected_mod_name->setText(QStringLiteral("No graphics mod selected")); + m_selected_mod_name->setAlignment(Qt::AlignCenter); + return; + } + + GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path); + if (!mod) + return; + + m_selected_mod_name->setText(QString::fromStdString(mod->m_title)); + m_selected_mod_name->setAlignment(Qt::AlignLeft); + QFont font = m_selected_mod_name->font(); + font.setWeight(QFont::Bold); + m_selected_mod_name->setFont(font); + + if (!mod->m_author.empty()) + { + auto* author_label = new QLabel(tr("By: ") + QString::fromStdString(mod->m_author)); + m_mod_meta_layout->addWidget(author_label); + } + + if (!mod->m_description.empty()) + { + auto* description_label = + new QLabel(tr("Description: ") + QString::fromStdString(mod->m_description)); + m_mod_meta_layout->addWidget(description_label); + } +} + +void GraphicsModListWidget::SaveModList() +{ + for (int i = 0; i < m_mod_list->count(); i++) + { + const auto absolute_path = m_mod_list->model() + ->data(m_mod_list->model()->index(i, 0), Qt::UserRole) + .toString() + .toStdString(); + m_mod_group.GetMod(absolute_path)->m_weight = i; + } + + if (m_loaded_game_is_running) + { + g_Config.graphics_mod_config = m_mod_group; + } + m_needs_save = true; +} + +void GraphicsModListWidget::ClearLayoutRecursively(QLayout* layout) +{ + while (QLayoutItem* child = layout->takeAt(0)) + { + if (child == nullptr) + continue; + + if (child->widget()) + { + layout->removeWidget(child->widget()); + delete child->widget(); + } + else if (child->layout()) + { + ClearLayoutRecursively(child->layout()); + layout->removeItem(child); + } + else + { + layout->removeItem(child); + } + delete child; + } +} + +void GraphicsModListWidget::SaveToDisk() +{ + m_needs_save = false; + m_mod_group.Save(); +} + +const GraphicsModGroupConfig& GraphicsModListWidget::GetGraphicsModConfig() const +{ + return m_mod_group; +} + +void GraphicsModListWidget::CalculateGameRunning(Core::State state) +{ + m_loaded_game_is_running = + state == Core::State::Running ? m_game_id == SConfig::GetInstance().GetGameID() : false; +} diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.h b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h new file mode 100644 index 0000000000..b8b1f5969c --- /dev/null +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h @@ -0,0 +1,69 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" + +class QHBoxLayout; +class QLabel; +class QListWidget; +class QListWidgetItem; +class QModelIndex; +class QPushButton; +class QVBoxLayout; + +namespace Core +{ +enum class State; +} + +namespace UICommon +{ +class GameFile; +} + +class GraphicsModListWidget : public QWidget +{ +public: + explicit GraphicsModListWidget(const UICommon::GameFile& game); + ~GraphicsModListWidget(); + + void SaveToDisk(); + + const GraphicsModGroupConfig& GetGraphicsModConfig() const; + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void RefreshModList(); + void ModSelectionChanged(); + void ModItemChanged(QListWidgetItem* item); + + void OnModChanged(std::optional absolute_path); + + void SaveModList(); + + void ClearLayoutRecursively(QLayout* layout); + + void CalculateGameRunning(Core::State state); + bool m_loaded_game_is_running = false; + bool m_needs_save = false; + + QListWidget* m_mod_list; + + QLabel* m_selected_mod_name; + QVBoxLayout* m_mod_meta_layout; + + QPushButton* m_refresh; + + std::string m_game_id; + GraphicsModGroupConfig m_mod_group; +}; diff --git a/Source/Core/DolphinQt/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt/Config/PropertiesDialog.cpp index a7589a4fc1..5ecb9d87bf 100644 --- a/Source/Core/DolphinQt/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt/Config/PropertiesDialog.cpp @@ -17,12 +17,14 @@ #include "DolphinQt/Config/FilesystemWidget.h" #include "DolphinQt/Config/GameConfigWidget.h" #include "DolphinQt/Config/GeckoCodeWidget.h" +#include "DolphinQt/Config/GraphicsModListWidget.h" #include "DolphinQt/Config/InfoWidget.h" #include "DolphinQt/Config/PatchesWidget.h" #include "DolphinQt/Config/VerifyWidget.h" #include "DolphinQt/QtUtils/WrapInScrollArea.h" #include "UICommon/GameFile.h" +#include "VideoCommon/VideoConfig.h" PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game) : QDialog(parent) @@ -43,6 +45,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga new GeckoCodeWidget(game.GetGameID(), game.GetGameTDBID(), game.GetRevision()); PatchesWidget* patches = new PatchesWidget(game); GameConfigWidget* game_config = new GameConfigWidget(game); + GraphicsModListWidget* graphics_mod_list = new GraphicsModListWidget(game); connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings); @@ -57,6 +60,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga tab_widget->addTab(GetWrappedWidget(ar, this, padding_width, padding_height), tr("AR Codes")); tab_widget->addTab(GetWrappedWidget(gecko, this, padding_width, padding_height), tr("Gecko Codes")); + tab_widget->addTab(GetWrappedWidget(graphics_mod_list, this, padding_width, padding_height), + tr("Graphics Mods")); tab_widget->addTab(GetWrappedWidget(info, this, padding_width, padding_height), tr("Info")); if (game.GetPlatform() != DiscIO::Platform::ELFOrDOL) @@ -82,6 +87,8 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga QDialogButtonBox* close_box = new QDialogButtonBox(QDialogButtonBox::Close); connect(close_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(close_box, &QDialogButtonBox::rejected, graphics_mod_list, + &GraphicsModListWidget::SaveToDisk); layout->addWidget(close_box); diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e6df0c3122..4808009b73 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -76,6 +76,7 @@ + @@ -270,6 +271,7 @@ +