UI: Massive revamp, new features and improvements
This commit is contained in:
@@ -6,6 +6,7 @@ set(SRCS
|
||||
resources/resources.qrc
|
||||
aboutdialog.cpp
|
||||
aboutdialog.h
|
||||
aboutdialog.ui
|
||||
advancedsettingswidget.cpp
|
||||
advancedsettingswidget.h
|
||||
advancedsettingswidget.ui
|
||||
@@ -18,19 +19,32 @@ set(SRCS
|
||||
biossettingswidget.cpp
|
||||
biossettingswidget.h
|
||||
biossettingswidget.ui
|
||||
cheatmanagerdialog.cpp
|
||||
cheatmanagerdialog.h
|
||||
cheatmanagerdialog.ui
|
||||
cheatcodeeditordialog.cpp
|
||||
cheatcodeeditordialog.h
|
||||
cheatcodeeditordialog.ui
|
||||
cheatmanagerdialog.cpp
|
||||
cheatmanagerdialog.h
|
||||
cheatmanagerdialog.ui
|
||||
collapsiblewidget.cpp
|
||||
collapsiblewidget.h
|
||||
consolesettingswidget.cpp
|
||||
consolesettingswidget.h
|
||||
consolesettingswidget.ui
|
||||
controllersettingswidget.cpp
|
||||
controllersettingswidget.h
|
||||
controllerbindingwidget_analog_controller.ui
|
||||
controllerbindingwidget_analog_joystick.ui
|
||||
controllerbindingwidget_digital_controller.ui
|
||||
controllerbindingwidget_guncon.ui
|
||||
controllerbindingwidget_negcon.ui
|
||||
controllerbindingwidgets.cpp
|
||||
controllerbindingwidgets.h
|
||||
controllerbindingwidget.ui
|
||||
controllerglobalsettingswidget.cpp
|
||||
controllerglobalsettingswidget.h
|
||||
controllerglobalsettingswidget.ui
|
||||
controllersettingsdialog.cpp
|
||||
controllersettingsdialog.h
|
||||
controllersettingsdialog.ui
|
||||
controllersettingwidgetbinder.h
|
||||
debuggermodels.cpp
|
||||
debuggermodels.h
|
||||
debuggerwindow.cpp
|
||||
@@ -39,14 +53,22 @@ set(SRCS
|
||||
displaysettingswidget.cpp
|
||||
displaysettingswidget.h
|
||||
displaysettingswidget.ui
|
||||
displaywidget.cpp
|
||||
displaywidget.h
|
||||
emptygamelistwidget.ui
|
||||
emulationsettingswidget.cpp
|
||||
emulationsettingswidget.h
|
||||
emulationsettingswidget.ui
|
||||
enhancementsettingswidget.cpp
|
||||
enhancementsettingswidget.h
|
||||
enhancementsettingswidget.ui
|
||||
foldersettingswidget.cpp
|
||||
foldersettingswidget.h
|
||||
foldersettingswidget.ui
|
||||
gamelistmodel.cpp
|
||||
gamelistmodel.h
|
||||
gamelistrefreshthread.cpp
|
||||
gamelistrefreshthread.h
|
||||
gamelistsearchdirectoriesmodel.cpp
|
||||
gamelistsearchdirectoriesmodel.h
|
||||
gamelistsettingswidget.cpp
|
||||
@@ -54,9 +76,10 @@ set(SRCS
|
||||
gamelistsettingswidget.ui
|
||||
gamelistwidget.cpp
|
||||
gamelistwidget.h
|
||||
gamepropertiesdialog.cpp
|
||||
gamepropertiesdialog.h
|
||||
gamepropertiesdialog.ui
|
||||
gamelistwidget.ui
|
||||
gamesummarywidget.cpp
|
||||
gamesummarywidget.h
|
||||
gamesummarywidget.ui
|
||||
gdbconnection.cpp
|
||||
gdbconnection.h
|
||||
gdbserver.cpp
|
||||
@@ -69,11 +92,8 @@ set(SRCS
|
||||
inputbindingdialog.cpp
|
||||
inputbindingdialog.h
|
||||
inputbindingdialog.ui
|
||||
inputbindingmonitor.cpp
|
||||
inputbindingmonitor.h
|
||||
inputbindingwidgets.cpp
|
||||
inputbindingwidgets.h
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
mainwindow.h
|
||||
mainwindow.ui
|
||||
@@ -92,17 +112,18 @@ set(SRCS
|
||||
postprocessingsettingswidget.ui
|
||||
postprocessingshaderconfigwidget.cpp
|
||||
postprocessingshaderconfigwidget.h
|
||||
qtdisplaywidget.cpp
|
||||
qtdisplaywidget.h
|
||||
qthostinterface.cpp
|
||||
qthostinterface.h
|
||||
qthost.cpp
|
||||
qthost.h
|
||||
qtkeycodes.cpp
|
||||
qtprogresscallback.cpp
|
||||
qtprogresscallback.h
|
||||
qtutils.cpp
|
||||
qtutils.h
|
||||
resource.h
|
||||
settingsdialog.cpp
|
||||
settingsdialog.h
|
||||
settingsdialog.ui
|
||||
settingwidgetbinder.h
|
||||
)
|
||||
|
||||
if(ENABLE_CHEEVOS)
|
||||
@@ -135,11 +156,11 @@ set(TS_FILES
|
||||
)
|
||||
|
||||
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/translations")
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
qt6_add_translation(QM_FILES ${TS_FILES})
|
||||
|
||||
add_executable(duckstation-qt ${SRCS} ${QM_FILES})
|
||||
target_include_directories(duckstation-qt PRIVATE "${Qt5Gui_PRIVATE_INCLUDE_DIRS}")
|
||||
target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui glad minizip scmversion Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
|
||||
target_include_directories(duckstation-qt PRIVATE "${Qt6Gui_PRIVATE_INCLUDE_DIRS}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui glad minizip scmversion Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network)
|
||||
|
||||
if(WIN32)
|
||||
# We want a Windows subsystem application not console.
|
||||
@@ -148,7 +169,7 @@ if(WIN32)
|
||||
DEBUG_POSTFIX "-debug")
|
||||
|
||||
# Copy in Qt DLLs. Borrowed from Dolphin.
|
||||
get_target_property(MOC_EXECUTABLE_LOCATION Qt5::moc IMPORTED_LOCATION)
|
||||
get_target_property(MOC_EXECUTABLE_LOCATION Qt6::moc IMPORTED_LOCATION)
|
||||
get_filename_component(QT_BINARY_DIRECTORY "${MOC_EXECUTABLE_LOCATION}" DIRECTORY)
|
||||
find_program(WINDEPLOYQT_EXE windeployqt HINTS "${QT_BINARY_DIRECTORY}")
|
||||
add_custom_command(TARGET duckstation-qt POST_BUILD
|
||||
@@ -186,11 +207,11 @@ if(APPLE)
|
||||
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/DuckStation.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# Copy Qt plugins into the bundle
|
||||
get_target_property(qtcocoa_location Qt5::QCocoaIntegrationPlugin LOCATION)
|
||||
get_target_property(qtcocoa_location Qt6::QCocoaIntegrationPlugin LOCATION)
|
||||
target_sources(duckstation-qt PRIVATE "${qtcocoa_location}")
|
||||
set_source_files_properties("${qtcocoa_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/platforms)
|
||||
|
||||
get_target_property(qtmacstyle_location Qt5::QMacStylePlugin LOCATION)
|
||||
get_target_property(qtmacstyle_location Qt6::QMacStylePlugin LOCATION)
|
||||
target_sources(duckstation-qt PRIVATE "${qtmacstyle_location}")
|
||||
set_source_files_properties("${qtmacstyle_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/styles)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "achievementlogindialog.h"
|
||||
#include "core/cheevos.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "frontend-common/achievements.h"
|
||||
#include "qthost.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AchievementLoginDialog::AchievementLoginDialog(QWidget* parent) : QDialog(parent)
|
||||
@@ -24,8 +24,8 @@ void AchievementLoginDialog::loginClicked()
|
||||
m_ui.status->setText(tr("Logging in..."));
|
||||
enableUI(false);
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([this, username, password]() {
|
||||
const bool result = Cheevos::Login(username.toStdString().c_str(), password.toStdString().c_str());
|
||||
Host::RunOnCPUThread([this, username, password]() {
|
||||
const bool result = Achievements::Login(username.toStdString().c_str(), password.toStdString().c_str());
|
||||
QMetaObject::invokeMethod(this, "processLoginResult", Qt::QueuedConnection, Q_ARG(bool, result));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "achievementsettingswidget.h"
|
||||
#include "achievementlogindialog.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/cheevos.h"
|
||||
#include "core/system.h"
|
||||
#include "frontend-common/achievements.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
@@ -10,20 +10,20 @@
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_interface, QWidget* parent,
|
||||
SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.richPresence, "Cheevos", "RichPresence", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.testMode, "Cheevos", "TestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.unofficialTestMode, "Cheevos",
|
||||
"UnofficialTestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.useFirstDiscFromPlaylist, "Cheevos",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Cheevos", "Enabled", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.richPresence, "Cheevos", "RichPresence", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.challengeMode, "Cheevos", "ChallengeMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.testMode, "Cheevos", "TestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialTestMode, "Cheevos", "UnofficialTestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useFirstDiscFromPlaylist, "Cheevos",
|
||||
"UseFirstDiscFromPlaylist", true);
|
||||
m_ui.enable->setChecked(m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false));
|
||||
m_ui.challengeMode->setChecked(m_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
|
||||
tr("When enabled and logged in, DuckStation will scan for achievements on startup."));
|
||||
@@ -45,24 +45,37 @@ AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_inter
|
||||
tr("\"Challenge\" mode for achievements. Disables save state, cheats, and slowdown "
|
||||
"functions, but you receive double the achievement points."));
|
||||
|
||||
connect(m_ui.enable, &QCheckBox::toggled, this, &AchievementSettingsWidget::onEnableToggled);
|
||||
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
|
||||
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
|
||||
connect(m_ui.challengeMode, &QCheckBox::toggled, this, &AchievementSettingsWidget::onChallengeModeToggled);
|
||||
connect(host_interface, &QtHostInterface::achievementsLoaded, this, &AchievementSettingsWidget::onAchievementsLoaded);
|
||||
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
|
||||
if (!m_dialog->isPerGameSettings())
|
||||
{
|
||||
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
|
||||
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
|
||||
connect(g_emu_thread, &EmuThread::achievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed);
|
||||
updateLoginState();
|
||||
|
||||
// force a refresh of game info
|
||||
Host::RunOnCPUThread(Host::OnAchievementsRefreshed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove login and game info, not relevant for per-game
|
||||
m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox);
|
||||
m_ui.gameInfoBox->deleteLater();
|
||||
m_ui.gameInfoBox = nullptr;
|
||||
m_ui.verticalLayout->removeWidget(m_ui.loginBox);
|
||||
m_ui.loginBox->deleteLater();
|
||||
m_ui.loginBox = nullptr;
|
||||
}
|
||||
|
||||
updateEnableState();
|
||||
updateLoginState();
|
||||
|
||||
// force a refresh of game info
|
||||
host_interface->OnAchievementsRefreshed();
|
||||
}
|
||||
|
||||
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
|
||||
|
||||
void AchievementSettingsWidget::updateEnableState()
|
||||
{
|
||||
const bool enabled = m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false);
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false);
|
||||
m_ui.testMode->setEnabled(enabled);
|
||||
m_ui.useFirstDiscFromPlaylist->setEnabled(enabled);
|
||||
m_ui.richPresence->setEnabled(enabled);
|
||||
@@ -71,13 +84,13 @@ void AchievementSettingsWidget::updateEnableState()
|
||||
|
||||
void AchievementSettingsWidget::updateLoginState()
|
||||
{
|
||||
const std::string username(m_host_interface->GetStringSettingValue("Cheevos", "Username"));
|
||||
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
|
||||
const bool logged_in = !username.empty();
|
||||
|
||||
if (logged_in)
|
||||
{
|
||||
const u64 login_unix_timestamp =
|
||||
StringUtil::FromChars<u64>(m_host_interface->GetStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0);
|
||||
StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0);
|
||||
const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp)));
|
||||
m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.")
|
||||
.arg(QString::fromStdString(username))
|
||||
@@ -95,9 +108,9 @@ void AchievementSettingsWidget::updateLoginState()
|
||||
|
||||
void AchievementSettingsWidget::onLoginLogoutPressed()
|
||||
{
|
||||
if (!m_host_interface->GetStringSettingValue("Cheevos", "Username").empty())
|
||||
if (!Host::GetBaseStringSettingValue("Cheevos", "Username").empty())
|
||||
{
|
||||
m_host_interface->executeOnEmulationThread([]() { Cheevos::Logout(); }, true);
|
||||
Host::RunOnCPUThread([]() { Achievements::Logout(); }, true);
|
||||
updateLoginState();
|
||||
return;
|
||||
}
|
||||
@@ -112,7 +125,7 @@ void AchievementSettingsWidget::onLoginLogoutPressed()
|
||||
|
||||
void AchievementSettingsWidget::onViewProfilePressed()
|
||||
{
|
||||
const std::string username(m_host_interface->GetStringSettingValue("Cheevos", "Username"));
|
||||
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
|
||||
if (username.empty())
|
||||
return;
|
||||
|
||||
@@ -122,63 +135,8 @@ void AchievementSettingsWidget::onViewProfilePressed()
|
||||
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onEnableToggled(bool checked)
|
||||
{
|
||||
const bool challenge_mode = m_host_interface->GetBoolSettingValue("Cheevos", "ChallengeMode", false);
|
||||
const bool challenge_mode_active = checked && challenge_mode;
|
||||
if (challenge_mode_active && !confirmChallengeModeEnable())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.challengeMode);
|
||||
m_ui.challengeMode->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("Cheevos", "Enabled", checked);
|
||||
m_host_interface->applySettings(false);
|
||||
|
||||
if (challenge_mode)
|
||||
m_host_interface->getMainWindow()->onAchievementsChallengeModeToggled(challenge_mode_active);
|
||||
|
||||
updateEnableState();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onChallengeModeToggled(bool checked)
|
||||
{
|
||||
if (checked && !confirmChallengeModeEnable())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.challengeMode);
|
||||
m_ui.challengeMode->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("Cheevos", "ChallengeMode", checked);
|
||||
m_host_interface->applySettings(false);
|
||||
m_host_interface->getMainWindow()->onAchievementsChallengeModeToggled(checked);
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total,
|
||||
quint32 points)
|
||||
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total,
|
||||
quint32 points)
|
||||
{
|
||||
m_ui.gameInfo->setText(game_info_string);
|
||||
}
|
||||
|
||||
bool AchievementSettingsWidget::confirmChallengeModeEnable()
|
||||
{
|
||||
if (!System::IsValid())
|
||||
return true;
|
||||
|
||||
QString message = tr("Enabling hardcore mode will shut down your current game.\n\n");
|
||||
|
||||
if (m_host_interface->ShouldSaveResumeState())
|
||||
{
|
||||
message +=
|
||||
tr("The current state will be saved, but you will be unable to load it until you disable hardcore mode.\n\n");
|
||||
}
|
||||
|
||||
message += tr("Do you want to continue?");
|
||||
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Enable Hardcore Mode"), message) != QMessageBox::Yes)
|
||||
return false;
|
||||
|
||||
m_host_interface->synchronousPowerOffSystem();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <QtWidgets/QWidget>
|
||||
#include "ui_achievementsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class AchievementSettingsWidget : public QWidget
|
||||
@@ -10,22 +9,19 @@ class AchievementSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AchievementSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~AchievementSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onEnableToggled(bool checked);
|
||||
void onChallengeModeToggled(bool checked);
|
||||
void updateEnableState();
|
||||
void onLoginLogoutPressed();
|
||||
void onViewProfilePressed();
|
||||
void onAchievementsLoaded(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
|
||||
private:
|
||||
bool confirmChallengeModeEnable();
|
||||
void updateEnableState();
|
||||
void updateLoginState();
|
||||
|
||||
Ui::AchievementSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<widget class="QGroupBox" name="loginBox">
|
||||
<property name="title">
|
||||
<string>Account</string>
|
||||
</property>
|
||||
@@ -112,7 +112,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<widget class="QGroupBox" name="gameInfoBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
@@ -136,7 +136,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="justify">DuckStation uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Quick Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></string>
|
||||
<string><html><head/><body><p align="justify">DuckStation uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
static QCheckBox* addBooleanTweakOption(QtHostInterface* host_interface, QTableWidget* table, QString name,
|
||||
std::string section, std::string key, bool default_value)
|
||||
static QCheckBox* addBooleanTweakOption(SettingsDialog* dialog, QTableWidget* table, QString name, std::string section,
|
||||
std::string key, bool default_value)
|
||||
{
|
||||
const int row = table->rowCount();
|
||||
|
||||
@@ -18,7 +18,10 @@ static QCheckBox* addBooleanTweakOption(QtHostInterface* host_interface, QTableW
|
||||
|
||||
QCheckBox* cb = new QCheckBox(table);
|
||||
if (!section.empty() || !key.empty())
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(host_interface, cb, std::move(section), std::move(key), default_value);
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),
|
||||
default_value);
|
||||
}
|
||||
|
||||
table->setCellWidget(row, 1, cb);
|
||||
return cb;
|
||||
@@ -33,9 +36,8 @@ static QCheckBox* setBooleanTweakOption(QTableWidget* table, int row, bool value
|
||||
return cb;
|
||||
}
|
||||
|
||||
static QSpinBox* addIntRangeTweakOption(QtHostInterface* host_interface, QTableWidget* table, QString name,
|
||||
std::string section, std::string key, int min_value, int max_value,
|
||||
int default_value)
|
||||
static QSpinBox* addIntRangeTweakOption(SettingsDialog* dialog, QTableWidget* table, QString name, std::string section,
|
||||
std::string key, int min_value, int max_value, int default_value)
|
||||
{
|
||||
const int row = table->rowCount();
|
||||
|
||||
@@ -49,7 +51,10 @@ static QSpinBox* addIntRangeTweakOption(QtHostInterface* host_interface, QTableW
|
||||
cb->setMinimum(min_value);
|
||||
cb->setMaximum(max_value);
|
||||
if (!section.empty() || !key.empty())
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(host_interface, cb, std::move(section), std::move(key), default_value);
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),
|
||||
default_value);
|
||||
}
|
||||
|
||||
table->setCellWidget(row, 1, cb);
|
||||
return cb;
|
||||
@@ -64,7 +69,7 @@ static QSpinBox* setIntRangeTweakOption(QTableWidget* table, int row, int value)
|
||||
return cb;
|
||||
}
|
||||
|
||||
static QDoubleSpinBox* addFloatRangeTweakOption(QtHostInterface* host_interface, QTableWidget* table, QString name,
|
||||
static QDoubleSpinBox* addFloatRangeTweakOption(SettingsDialog* dialog, QTableWidget* table, QString name,
|
||||
std::string section, std::string key, float min_value, float max_value,
|
||||
float step_value, float default_value)
|
||||
{
|
||||
@@ -83,8 +88,8 @@ static QDoubleSpinBox* addFloatRangeTweakOption(QtHostInterface* host_interface,
|
||||
|
||||
if (!section.empty() || !key.empty())
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(host_interface, cb, std::move(section), std::move(key),
|
||||
default_value);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(dialog->getSettingsInterface(), cb, std::move(section),
|
||||
std::move(key), default_value);
|
||||
}
|
||||
|
||||
table->setCellWidget(row, 1, cb);
|
||||
@@ -101,9 +106,8 @@ static QDoubleSpinBox* setFloatRangeTweakOption(QTableWidget* table, int row, fl
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static QComboBox* addChoiceTweakOption(QtHostInterface* host_interface, QTableWidget* table, QString name,
|
||||
std::string section, std::string key,
|
||||
std::optional<T> (*parse_callback)(const char*),
|
||||
static QComboBox* addChoiceTweakOption(SettingsDialog* dialog, QTableWidget* table, QString name, std::string section,
|
||||
std::string key, std::optional<T> (*parse_callback)(const char*),
|
||||
const char* (*get_value_callback)(T), const char* (*get_display_callback)(T),
|
||||
const char* tr_context, u32 num_values, T default_value)
|
||||
{
|
||||
@@ -121,8 +125,8 @@ static QComboBox* addChoiceTweakOption(QtHostInterface* host_interface, QTableWi
|
||||
|
||||
if (!section.empty() || !key.empty())
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(host_interface, cb, std::move(section), std::move(key), parse_callback,
|
||||
get_value_callback, default_value);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),
|
||||
parse_callback, get_value_callback, default_value);
|
||||
}
|
||||
|
||||
table->setCellWidget(row, 1, cb);
|
||||
@@ -138,7 +142,7 @@ static void setChoiceTweakOption(QTableWidget* table, int row, T value)
|
||||
cb->setCurrentIndex(static_cast<int>(value));
|
||||
}
|
||||
|
||||
static void addMSAATweakOption(QtHostInterface* host_interface, QTableWidget* table, const QString& name)
|
||||
static void addMSAATweakOption(SettingsDialog* dialog, QTableWidget* table, const QString& name)
|
||||
{
|
||||
const int row = table->rowCount();
|
||||
|
||||
@@ -150,113 +154,111 @@ static void addMSAATweakOption(QtHostInterface* host_interface, QTableWidget* ta
|
||||
|
||||
QComboBox* msaa = new QComboBox(table);
|
||||
QtUtils::FillComboBoxWithMSAAModes(msaa);
|
||||
const QVariant current_msaa_mode(QtUtils::GetMSAAModeValue(
|
||||
static_cast<uint>(QtHostInterface::GetInstance()->GetIntSettingValue("GPU", "Multisamples", 1)),
|
||||
QtHostInterface::GetInstance()->GetBoolSettingValue("GPU", "PerSampleShading", false)));
|
||||
const QVariant current_msaa_mode(
|
||||
QtUtils::GetMSAAModeValue(static_cast<uint>(dialog->getEffectiveIntValue("GPU", "Multisamples", 1)),
|
||||
dialog->getEffectiveBoolValue("GPU", "PerSampleShading", false)));
|
||||
const int current_msaa_index = msaa->findData(current_msaa_mode);
|
||||
if (current_msaa_index >= 0)
|
||||
msaa->setCurrentIndex(current_msaa_index);
|
||||
msaa->connect(msaa, QOverload<int>::of(&QComboBox::currentIndexChanged), [msaa](int index) {
|
||||
msaa->connect(msaa, QOverload<int>::of(&QComboBox::currentIndexChanged), [dialog, msaa](int index) {
|
||||
uint multisamples;
|
||||
bool ssaa;
|
||||
QtUtils::DecodeMSAAModeValue(msaa->itemData(index), &multisamples, &ssaa);
|
||||
QtHostInterface::GetInstance()->SetIntSettingValue("GPU", "Multisamples", static_cast<int>(multisamples));
|
||||
QtHostInterface::GetInstance()->SetBoolSettingValue("GPU", "PerSampleShading", ssaa);
|
||||
QtHostInterface::GetInstance()->applySettings(false);
|
||||
dialog->setIntSettingValue("GPU", "Multisamples", static_cast<int>(multisamples));
|
||||
dialog->setBoolSettingValue("GPU", "PerSampleShading", ssaa);
|
||||
g_emu_thread->applySettings(false);
|
||||
});
|
||||
|
||||
table->setCellWidget(row, 1, msaa);
|
||||
}
|
||||
|
||||
AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(LOGLEVEL_COUNT); i++)
|
||||
m_ui.logLevel->addItem(qApp->translate("LogLevel", Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i))));
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.logLevel, "Logging", "LogLevel",
|
||||
&Settings::ParseLogLevelName, &Settings::GetLogLevelName,
|
||||
Settings::DEFAULT_LOG_LEVEL);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_ui.logFilter, "Logging", "LogFilter");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.logToConsole, "Logging", "LogToConsole");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.logToDebug, "Logging", "LogToDebug");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.logToWindow, "Logging", "LogToWindow");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.logToFile, "Logging", "LogToFile");
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.logLevel, "Logging", "LogLevel", &Settings::ParseLogLevelName,
|
||||
&Settings::GetLogLevelName, Settings::DEFAULT_LOG_LEVEL);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.logFilter, "Logging", "LogFilter");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToConsole, "Logging", "LogToConsole", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToDebug, "Logging", "LogToDebug", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToWindow, "Logging", "LogToWindow", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.logToFile, "Logging", "LogToFile", false);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showDebugMenu, "Main", "ShowDebugMenu");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showDebugMenu, "Main", "ShowDebugMenu", false);
|
||||
|
||||
connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);
|
||||
connect(m_ui.showDebugMenu, &QCheckBox::toggled, m_host_interface->getMainWindow(),
|
||||
&MainWindow::updateDebugMenuVisibility, Qt::QueuedConnection);
|
||||
connect(m_ui.showDebugMenu, &QCheckBox::toggled, g_main_window, &MainWindow::updateDebugMenuVisibility,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_ui.tweakOptionTable->setColumnWidth(0, 380);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Disable All Enhancements"), "Main",
|
||||
"DisableAllEnhancements", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Show Status Indicators"), "Display",
|
||||
"ShowStatusIndicators", true);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Show Enhancement Settings"), "Display",
|
||||
"ShowEnhancements", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Controller Enhanced Mode (PS4/PS5)"), "Main",
|
||||
"ControllerEnhancedMode", false);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Display FPS Limit"), "Display", "MaxFPS", 0, 1000,
|
||||
0);
|
||||
|
||||
addMSAATweakOption(host_interface, m_ui.tweakOptionTable, tr("Multisample Antialiasing"));
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP Vertex Cache"), "GPU", "PGXPVertexCache",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Disable All Enhancements"), "Main", "DisableAllEnhancements",
|
||||
false);
|
||||
addFloatRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP Geometry Tolerance"), "GPU",
|
||||
"PGXPTolerance", -1.0f, 100.0f, 0.25f, -1.0f);
|
||||
addFloatRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP Depth Clear Threshold"), "GPU",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Show Status Indicators"), "Display", "ShowStatusIndicators",
|
||||
true);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Show Enhancement Settings"), "Display", "ShowEnhancements",
|
||||
false);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Controller Enhanced Mode (PS4/PS5)"), "Main",
|
||||
"ControllerEnhancedMode", false);
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("Display FPS Limit"), "Display", "MaxFPS", 0, 1000, 0);
|
||||
|
||||
addMSAATweakOption(dialog, m_ui.tweakOptionTable, tr("Multisample Antialiasing"));
|
||||
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("PGXP Vertex Cache"), "GPU", "PGXPVertexCache", false);
|
||||
addFloatRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("PGXP Geometry Tolerance"), "GPU", "PGXPTolerance", -1.0f,
|
||||
100.0f, 0.25f, -1.0f);
|
||||
addFloatRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("PGXP Depth Clear Threshold"), "GPU",
|
||||
"PGXPDepthClearThreshold", 0.0f, 4096.0f, 1.0f, Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",
|
||||
"RecompilerMemoryExceptions", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable Recompiler Block Linking"), "CPU",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Block Linking"), "CPU",
|
||||
"RecompilerBlockLinking", true);
|
||||
addChoiceTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable Recompiler Fast Memory Access"), "CPU",
|
||||
"FastmemMode", Settings::ParseCPUFastmemMode, Settings::GetCPUFastmemModeName,
|
||||
addChoiceTweakOption(dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Fast Memory Access"), "CPU", "FastmemMode",
|
||||
Settings::ParseCPUFastmemMode, Settings::GetCPUFastmemModeName,
|
||||
Settings::GetCPUFastmemModeDisplayName, "CPUFastmemMode",
|
||||
static_cast<u32>(CPUFastmemMode::Count), Settings::DEFAULT_CPU_FASTMEM_MODE);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable Recompiler ICache"), "CPU",
|
||||
"RecompilerICache", false);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Enable Recompiler ICache"), "CPU", "RecompilerICache",
|
||||
false);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable VRAM Write Texture Replacement"),
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Enable VRAM Write Texture Replacement"),
|
||||
"TextureReplacements", "EnableVRAMWriteReplacements", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Preload Texture Replacements"),
|
||||
"TextureReplacements", "PreloadTextures", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Dump Replaceable VRAM Writes"),
|
||||
"TextureReplacements", "DumpVRAMWrites", false);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Set Dumped VRAM Write Alpha Channel"),
|
||||
"TextureReplacements", "DumpVRAMWriteForceAlphaChannel", true);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Width"),
|
||||
"TextureReplacements", "DumpVRAMWriteWidthThreshold", 1, VRAM_WIDTH,
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Preload Texture Replacements"), "TextureReplacements",
|
||||
"PreloadTextures", false);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Dump Replaceable VRAM Writes"), "TextureReplacements",
|
||||
"DumpVRAMWrites", false);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Set Dumped VRAM Write Alpha Channel"), "TextureReplacements",
|
||||
"DumpVRAMWriteForceAlphaChannel", true);
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Width"), "TextureReplacements",
|
||||
"DumpVRAMWriteWidthThreshold", 1, VRAM_WIDTH,
|
||||
Settings::DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Height"),
|
||||
"TextureReplacements", "DumpVRAMWriteHeightThreshold", 1, VRAM_HEIGHT,
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Height"), "TextureReplacements",
|
||||
"DumpVRAMWriteHeightThreshold", 1, VRAM_HEIGHT,
|
||||
Settings::DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD);
|
||||
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks",
|
||||
"DMAMaxSliceTicks", 100, 10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 100,
|
||||
10000, Settings::DEFAULT_DMA_HALT_TICKS);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks", "DMAMaxSliceTicks", 100,
|
||||
10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS);
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 100, 10000,
|
||||
Settings::DEFAULT_DMA_HALT_TICKS);
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,
|
||||
Settings::DEFAULT_GPU_FIFO_SIZE);
|
||||
addIntRangeTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("GPU Max Run-Ahead"), "Hacks", "GPUMaxRunAhead", 0,
|
||||
1000, Settings::DEFAULT_GPU_MAX_RUN_AHEAD);
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Use Debug Host GPU Device"), "GPU",
|
||||
"UseDebugDevice", false);
|
||||
addIntRangeTweakOption(dialog, m_ui.tweakOptionTable, tr("GPU Max Run-Ahead"), "Hacks", "GPUMaxRunAhead", 0, 1000,
|
||||
Settings::DEFAULT_GPU_MAX_RUN_AHEAD);
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Use Debug Host GPU Device"), "GPU", "UseDebugDevice", false);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Increase Timer Resolution"), "Main",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Increase Timer Resolution"), "Main",
|
||||
"IncreaseTimerResolution", true);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
||||
"AllowBootingWithoutSBIFile", false);
|
||||
|
||||
addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Create Save State Backups"), "General",
|
||||
addBooleanTweakOption(dialog, m_ui.tweakOptionTable, tr("Create Save State Backups"), "General",
|
||||
"CreateSaveStateBackups", false);
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.logLevel, tr("Log Level"), tr("Information"),
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_advancedsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class AdvancedSettingsWidget : public QWidget
|
||||
@@ -12,7 +11,7 @@ class AdvancedSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AdvancedSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit AdvancedSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~AdvancedSettingsWidget();
|
||||
|
||||
private:
|
||||
@@ -45,11 +44,11 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
SettingsDialog* m_dialog;
|
||||
|
||||
Ui::AdvancedSettingsWidget m_ui;
|
||||
|
||||
void onResetToDefaultClicked();
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
QVector<TweakOption> m_tweak_options;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include "audiosettingswidget.h"
|
||||
#include "core/spu.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include "util/audio_stream.h"
|
||||
#include <cmath>
|
||||
|
||||
AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
AudioSettingsWidget::AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(AudioBackend::Count); i++)
|
||||
@@ -15,26 +17,35 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge
|
||||
qApp->translate("AudioBackend", Settings::GetAudioBackendDisplayName(static_cast<AudioBackend>(i))));
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.audioBackend, "Audio", "Backend",
|
||||
&Settings::ParseAudioBackend, &Settings::GetAudioBackendName,
|
||||
Settings::DEFAULT_AUDIO_BACKEND);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.syncToOutput, "Audio", "Sync");
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferSize, "Audio", "BufferSize");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startDumpingOnBoot, "Audio", "DumpOnBoot");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.muteCDAudio, "CDROM", "MuteCDAudio");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.resampling, "Audio", "Resampling", true);
|
||||
|
||||
m_ui.volume->setValue(m_host_interface->GetIntSettingValue("Audio", "OutputVolume", 100));
|
||||
m_ui.fastForwardVolume->setValue(m_host_interface->GetIntSettingValue("Audio", "FastForwardVolume", 100));
|
||||
m_ui.muted->setChecked(m_host_interface->GetBoolSettingValue("Audio", "OutputMuted", false));
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.audioBackend, "Audio", "Backend", &Settings::ParseAudioBackend,
|
||||
&Settings::GetAudioBackendName, Settings::DEFAULT_AUDIO_BACKEND);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.syncToOutput, "Audio", "Sync", true);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.bufferSize, "Audio", "BufferSize",
|
||||
Settings::DEFAULT_AUDIO_BUFFER_SIZE);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.startDumpingOnBoot, "Audio", "DumpOnBoot", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muteCDAudio, "CDROM", "MuteCDAudio", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.resampling, "Audio", "Resampling", true);
|
||||
|
||||
connect(m_ui.bufferSize, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel);
|
||||
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::onOutputVolumeChanged);
|
||||
connect(m_ui.fastForwardVolume, &QSlider::valueChanged, this, &AudioSettingsWidget::onFastForwardVolumeChanged);
|
||||
connect(m_ui.muted, &QCheckBox::stateChanged, this, &AudioSettingsWidget::onOutputMutedChanged);
|
||||
|
||||
updateBufferingLabel();
|
||||
updateVolumeLabel();
|
||||
|
||||
// for per-game, just use the normal path, since it needs to re-read/apply
|
||||
if (!dialog->isPerGameSettings())
|
||||
{
|
||||
m_ui.volume->setValue(m_dialog->getEffectiveIntValue("Audio", "OutputVolume", 100));
|
||||
m_ui.fastForwardVolume->setValue(m_dialog->getEffectiveIntValue("Audio", "FastForwardVolume", 100));
|
||||
m_ui.muted->setChecked(m_dialog->getEffectiveBoolValue("Audio", "OutputMuted", false));
|
||||
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::onOutputVolumeChanged);
|
||||
connect(m_ui.fastForwardVolume, &QSlider::valueChanged, this, &AudioSettingsWidget::onFastForwardVolumeChanged);
|
||||
connect(m_ui.muted, &QCheckBox::stateChanged, this, &AudioSettingsWidget::onOutputMutedChanged);
|
||||
updateVolumeLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.volume, "Audio", "OutputVolume", 100);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.fastForwardVolume, "Audio", "FastForwardVolume", 100);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.muted, "Audio", "OutputMuted", false);
|
||||
}
|
||||
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.audioBackend, tr("Audio Backend"), QStringLiteral("Cubeb"),
|
||||
@@ -82,7 +93,7 @@ void AudioSettingsWidget::updateBufferingLabel()
|
||||
return;
|
||||
}
|
||||
|
||||
const float max_latency = AudioStream::GetMaxLatency(HostInterface::AUDIO_SAMPLE_RATE, actual_buffer_size);
|
||||
const float max_latency = AudioStream::GetMaxLatency(SPU::SAMPLE_RATE, actual_buffer_size);
|
||||
m_ui.bufferingLabel->setText(tr("Maximum Latency: %n frames (%1ms)", "", actual_buffer_size)
|
||||
.arg(static_cast<double>(max_latency) * 1000.0, 0, 'f', 2));
|
||||
}
|
||||
@@ -95,16 +106,16 @@ void AudioSettingsWidget::updateVolumeLabel()
|
||||
|
||||
void AudioSettingsWidget::onOutputVolumeChanged(int new_value)
|
||||
{
|
||||
m_host_interface->SetIntSettingValue("Audio", "OutputVolume", new_value);
|
||||
m_host_interface->setAudioOutputVolume(new_value, m_ui.fastForwardVolume->value());
|
||||
m_dialog->setIntSettingValue("Audio", "OutputVolume", new_value);
|
||||
g_emu_thread->setAudioOutputVolume(new_value, m_ui.fastForwardVolume->value());
|
||||
|
||||
updateVolumeLabel();
|
||||
}
|
||||
|
||||
void AudioSettingsWidget::onFastForwardVolumeChanged(int new_value)
|
||||
{
|
||||
m_host_interface->SetIntSettingValue("Audio", "FastForwardVolume", new_value);
|
||||
m_host_interface->setAudioOutputVolume(m_ui.volume->value(), new_value);
|
||||
m_dialog->setIntSettingValue("Audio", "FastForwardVolume", new_value);
|
||||
g_emu_thread->setAudioOutputVolume(m_ui.volume->value(), new_value);
|
||||
|
||||
updateVolumeLabel();
|
||||
}
|
||||
@@ -112,6 +123,6 @@ void AudioSettingsWidget::onFastForwardVolumeChanged(int new_value)
|
||||
void AudioSettingsWidget::onOutputMutedChanged(int new_state)
|
||||
{
|
||||
const bool muted = (new_state != 0);
|
||||
m_host_interface->SetBoolSettingValue("Audio", "OutputMuted", muted);
|
||||
m_host_interface->setAudioOutputMuted(muted);
|
||||
m_dialog->setBoolSettingValue("Audio", "OutputMuted", muted);
|
||||
g_emu_thread->setAudioOutputMuted(muted);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_audiosettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class AudioSettingsWidget : public QWidget
|
||||
@@ -12,7 +11,7 @@ class AudioSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AudioSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit AudioSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~AudioSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -25,5 +24,5 @@ private Q_SLOTS:
|
||||
private:
|
||||
Ui::AudioSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#include "common/log.h"
|
||||
#include "common/minizip_helpers.h"
|
||||
#include "common/string_util.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include "scmversion/scmversion.h"
|
||||
#include "unzip.h"
|
||||
@@ -45,7 +46,7 @@ static const char* THIS_RELEASE_TAG = SCM_RELEASE_TAG;
|
||||
|
||||
#endif
|
||||
|
||||
AutoUpdaterDialog::AutoUpdaterDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
|
||||
AutoUpdaterDialog::AutoUpdaterDialog(EmuThread* host_interface, QWidget* parent /* = nullptr */)
|
||||
: QDialog(parent), m_host_interface(host_interface)
|
||||
{
|
||||
m_network_access_mgr = new QNetworkAccessManager(this);
|
||||
@@ -375,7 +376,7 @@ void AutoUpdaterDialog::downloadUpdateClicked()
|
||||
progress.setValue(static_cast<int>(received));
|
||||
});
|
||||
|
||||
connect(m_network_access_mgr, &QNetworkAccessManager::finished, [this, &progress](QNetworkReply* reply) {
|
||||
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, [this, &progress](QNetworkReply* reply) {
|
||||
m_network_access_mgr->disconnect();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
@@ -408,7 +409,7 @@ void AutoUpdaterDialog::downloadUpdateClicked()
|
||||
else if (result == 1)
|
||||
{
|
||||
// updater started
|
||||
m_host_interface->requestExit();
|
||||
g_main_window->requestExit();
|
||||
done(0);
|
||||
}
|
||||
|
||||
@@ -417,8 +418,7 @@ void AutoUpdaterDialog::downloadUpdateClicked()
|
||||
|
||||
bool AutoUpdaterDialog::updateNeeded() const
|
||||
{
|
||||
QString last_checked_sha =
|
||||
QString::fromStdString(m_host_interface->GetStringSettingValue("AutoUpdater", "LastVersion"));
|
||||
QString last_checked_sha = QString::fromStdString(Host::GetBaseStringSettingValue("AutoUpdater", "LastVersion"));
|
||||
|
||||
Log_InfoPrintf("Current SHA: %s", g_scm_hash_str);
|
||||
Log_InfoPrintf("Latest SHA: %s", m_latest_sha.toUtf8().constData());
|
||||
@@ -435,7 +435,7 @@ bool AutoUpdaterDialog::updateNeeded() const
|
||||
|
||||
void AutoUpdaterDialog::skipThisUpdateClicked()
|
||||
{
|
||||
m_host_interface->SetStringSettingValue("AutoUpdater", "LastVersion", m_latest_sha.toUtf8().constData());
|
||||
Host::SetBaseStringSettingValue("AutoUpdater", "LastVersion", m_latest_sha.toUtf8().constData());
|
||||
done(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class QtHostInterface;
|
||||
class EmuThread;
|
||||
|
||||
class AutoUpdaterDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AutoUpdaterDialog(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||
explicit AutoUpdaterDialog(EmuThread* host_interface, QWidget* parent = nullptr);
|
||||
~AutoUpdaterDialog();
|
||||
|
||||
static bool isSupported();
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
|
||||
Ui::AutoUpdaterDialog m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
EmuThread* m_host_interface;
|
||||
QNetworkAccessManager* m_network_access_mgr = nullptr;
|
||||
QString m_latest_sha;
|
||||
QString m_download_url;
|
||||
|
||||
@@ -1,19 +1,96 @@
|
||||
#include "biossettingswidget.h"
|
||||
#include "core/bios.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <algorithm>
|
||||
|
||||
static void populateDropDownForRegion(ConsoleRegion region, QComboBox* cb,
|
||||
std::vector<std::pair<std::string, const BIOS::ImageInfo*>>& images)
|
||||
BIOSSettingsWidget::BIOSSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableTTYOutput, "BIOS", "PatchTTYEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fastBoot, "BIOS", "PatchFastBoot", false);
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
|
||||
tr("Patches the BIOS to skip the console's boot animation. Does not work with all games, "
|
||||
"but usually safe to enable."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.enableTTYOutput, tr("Enable TTY Output"), tr("Unchecked"),
|
||||
tr("Patches the BIOS to log calls to printf(). Only use when debugging, can break games."));
|
||||
|
||||
connect(m_ui.imageNTSCJ, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("BIOS", "PathNTSCJ");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dialog->setStringSettingValue("BIOS", "PathNTSCJ",
|
||||
m_ui.imageNTSCJ->itemData(index).toString().toStdString().c_str());
|
||||
}
|
||||
});
|
||||
connect(m_ui.imageNTSCU, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("BIOS", "PathNTSCU");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dialog->setStringSettingValue("BIOS", "PathNTSCU",
|
||||
m_ui.imageNTSCU->itemData(index).toString().toStdString().c_str());
|
||||
}
|
||||
});
|
||||
connect(m_ui.imagePAL, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("BIOS", "PathPAL");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dialog->setStringSettingValue("BIOS", "PathPAL",
|
||||
m_ui.imagePAL->itemData(index).toString().toStdString().c_str());
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
|
||||
|
||||
m_ui.searchDirectory->setText(QString::fromStdString(EmuFolders::Bios));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.searchDirectory, m_ui.browseSearchDirectory,
|
||||
m_ui.openSearchDirectory, nullptr, "BIOS", "SearchDirectory",
|
||||
Path::Combine(EmuFolders::DataRoot, "bios"));
|
||||
connect(m_ui.searchDirectory, &QLineEdit::textChanged, this, &BIOSSettingsWidget::refreshList);
|
||||
refreshList();
|
||||
}
|
||||
|
||||
BIOSSettingsWidget::~BIOSSettingsWidget() = default;
|
||||
|
||||
void BIOSSettingsWidget::refreshList()
|
||||
{
|
||||
auto images = BIOS::FindBIOSImagesInDirectory(m_ui.searchDirectory->text().toUtf8().constData());
|
||||
populateDropDownForRegion(ConsoleRegion::NTSC_J, m_ui.imageNTSCJ, images);
|
||||
populateDropDownForRegion(ConsoleRegion::NTSC_U, m_ui.imageNTSCU, images);
|
||||
populateDropDownForRegion(ConsoleRegion::PAL, m_ui.imagePAL, images);
|
||||
|
||||
setDropDownValue(m_ui.imageNTSCJ, m_dialog->getStringValue("BIOS", "PathNTSCJ", std::nullopt));
|
||||
setDropDownValue(m_ui.imageNTSCU, m_dialog->getStringValue("BIOS", "PathNTSCU", std::nullopt));
|
||||
setDropDownValue(m_ui.imagePAL, m_dialog->getStringValue("BIOS", "PathPAL", std::nullopt));
|
||||
}
|
||||
|
||||
void BIOSSettingsWidget::populateDropDownForRegion(ConsoleRegion region, QComboBox* cb,
|
||||
std::vector<std::pair<std::string, const BIOS::ImageInfo*>>& images)
|
||||
{
|
||||
QSignalBlocker sb(cb);
|
||||
cb->clear();
|
||||
|
||||
cb->addItem(QIcon(QStringLiteral(":/icons/system-search.png")), qApp->translate("BIOSSettingsWidget", "Auto-Detect"));
|
||||
if (m_dialog->isPerGameSettings())
|
||||
cb->addItem(QIcon(QStringLiteral(":/icons/system-search.png")), tr("Use Global Setting"));
|
||||
|
||||
cb->addItem(QIcon(QStringLiteral(":/icons/system-search.png")), tr("Auto-Detect"));
|
||||
|
||||
std::sort(images.begin(), images.end(), [region](const auto& left, const auto& right) {
|
||||
const bool left_region_match = (left.second && left.second->region == region);
|
||||
@@ -28,32 +105,8 @@ static void populateDropDownForRegion(ConsoleRegion region, QComboBox* cb,
|
||||
|
||||
for (const auto& [name, info] : images)
|
||||
{
|
||||
QIcon icon;
|
||||
if (info)
|
||||
{
|
||||
switch (info->region)
|
||||
{
|
||||
case ConsoleRegion::NTSC_J:
|
||||
icon = QIcon(QStringLiteral(":/icons/flag-jp.png"));
|
||||
break;
|
||||
case ConsoleRegion::PAL:
|
||||
icon = QIcon(QStringLiteral(":/icons/flag-eu.png"));
|
||||
break;
|
||||
case ConsoleRegion::NTSC_U:
|
||||
icon = QIcon(QStringLiteral(":/icons/flag-uc.png"));
|
||||
break;
|
||||
default:
|
||||
icon = QIcon(QStringLiteral(":/icons/applications-other.png"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = QIcon(QStringLiteral(":/icons/applications-other.png"));
|
||||
}
|
||||
|
||||
QString name_str(QString::fromStdString(name));
|
||||
cb->addItem(icon,
|
||||
cb->addItem(QtUtils::GetIconForRegion(info ? info->region : ConsoleRegion::Count),
|
||||
QStringLiteral("%1 (%2)")
|
||||
.arg(info ? QString(info->description) : qApp->translate("BIOSSettingsWidget", "Unknown"))
|
||||
.arg(name_str),
|
||||
@@ -61,17 +114,17 @@ static void populateDropDownForRegion(ConsoleRegion region, QComboBox* cb,
|
||||
}
|
||||
}
|
||||
|
||||
static void setDropDownValue(QComboBox* cb, const std::string& name)
|
||||
void BIOSSettingsWidget::setDropDownValue(QComboBox* cb, const std::optional<std::string>& name)
|
||||
{
|
||||
QSignalBlocker sb(cb);
|
||||
|
||||
if (name.empty())
|
||||
if (!name.has_value() || name->empty())
|
||||
{
|
||||
cb->setCurrentIndex(0);
|
||||
cb->setCurrentIndex((m_dialog->isPerGameSettings() && name.has_value()) ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
QString qname(QString::fromStdString(name));
|
||||
QString qname(QString::fromStdString(name.value()));
|
||||
for (int i = 1; i < cb->count(); i++)
|
||||
{
|
||||
if (cb->itemData(i) == qname)
|
||||
@@ -84,84 +137,3 @@ static void setDropDownValue(QComboBox* cb, const std::string& name)
|
||||
cb->addItem(qname, QVariant(qname));
|
||||
cb->setCurrentIndex(cb->count() - 1);
|
||||
}
|
||||
|
||||
BIOSSettingsWidget::BIOSSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableTTYOutput, "BIOS", "PatchTTYEnable");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fastBoot, "BIOS", "PatchFastBoot");
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
|
||||
tr("Patches the BIOS to skip the console's boot animation. Does not work with all games, "
|
||||
"but usually safe to enable."));
|
||||
dialog->registerWidgetHelp(m_ui.enableTTYOutput, tr("Enable TTY Output"), tr("Unchecked"),
|
||||
tr("Patches the BIOS to log calls to printf(). Only use when debugging, can break games."));
|
||||
|
||||
refreshList();
|
||||
|
||||
connect(m_ui.imageNTSCJ, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_host_interface->SetStringSettingValue("BIOS", "PathNTSCJ",
|
||||
m_ui.imageNTSCJ->itemData(index).toString().toStdString().c_str());
|
||||
m_host_interface->applySettings();
|
||||
});
|
||||
connect(m_ui.imageNTSCU, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_host_interface->SetStringSettingValue("BIOS", "PathNTSCU",
|
||||
m_ui.imageNTSCU->itemData(index).toString().toStdString().c_str());
|
||||
m_host_interface->applySettings();
|
||||
});
|
||||
connect(m_ui.imagePAL, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_host_interface->SetStringSettingValue("BIOS", "PathPAL",
|
||||
m_ui.imagePAL->itemData(index).toString().toStdString().c_str());
|
||||
m_host_interface->applySettings();
|
||||
});
|
||||
|
||||
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
|
||||
|
||||
std::string current_search_directory = g_host_interface->GetBIOSDirectory();
|
||||
m_ui.searchDirectory->setText(QString::fromStdString(current_search_directory));
|
||||
connect(m_ui.searchDirectory, &QLineEdit::textChanged, [this](const QString& text) {
|
||||
if (text.isEmpty())
|
||||
{
|
||||
m_host_interface->RemoveSettingValue("BIOS", "SearchDirectory");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_host_interface->SetStringSettingValue("BIOS", "SearchDirectory", text.toStdString().c_str());
|
||||
}
|
||||
refreshList();
|
||||
});
|
||||
connect(m_ui.browseSearchDirectory, &QPushButton::clicked, this, &BIOSSettingsWidget::browseSearchDirectory);
|
||||
connect(m_ui.openSearchDirectory, &QPushButton::clicked, this, &BIOSSettingsWidget::openSearchDirectory);
|
||||
}
|
||||
|
||||
BIOSSettingsWidget::~BIOSSettingsWidget() = default;
|
||||
|
||||
void BIOSSettingsWidget::refreshList()
|
||||
{
|
||||
auto images = m_host_interface->FindBIOSImagesInDirectory(m_host_interface->GetBIOSDirectory().c_str());
|
||||
populateDropDownForRegion(ConsoleRegion::NTSC_J, m_ui.imageNTSCJ, images);
|
||||
populateDropDownForRegion(ConsoleRegion::NTSC_U, m_ui.imageNTSCU, images);
|
||||
populateDropDownForRegion(ConsoleRegion::PAL, m_ui.imagePAL, images);
|
||||
|
||||
setDropDownValue(m_ui.imageNTSCJ, m_host_interface->GetStringSettingValue("BIOS", "PathNTSCJ", ""));
|
||||
setDropDownValue(m_ui.imageNTSCU, m_host_interface->GetStringSettingValue("BIOS", "PathNTSCU", ""));
|
||||
setDropDownValue(m_ui.imagePAL, m_host_interface->GetStringSettingValue("BIOS", "PathPAL", ""));
|
||||
}
|
||||
|
||||
void BIOSSettingsWidget::browseSearchDirectory()
|
||||
{
|
||||
QString directory = QFileDialog::getExistingDirectory(QtUtils::GetRootWidget(this), tr("Select Directory"),
|
||||
m_ui.searchDirectory->text());
|
||||
if (directory.isEmpty())
|
||||
return;
|
||||
|
||||
m_ui.searchDirectory->setText(directory);
|
||||
}
|
||||
|
||||
void BIOSSettingsWidget::openSearchDirectory()
|
||||
{
|
||||
QString dir = QString::fromStdString(m_host_interface->GetBIOSDirectory());
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(dir));
|
||||
}
|
||||
|
||||
@@ -4,24 +4,30 @@
|
||||
|
||||
#include "ui_biossettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
enum class ConsoleRegion;
|
||||
namespace BIOS {
|
||||
struct ImageInfo;
|
||||
}
|
||||
|
||||
class BIOSSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BIOSSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit BIOSSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~BIOSSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void refreshList();
|
||||
void browseSearchDirectory();
|
||||
void openSearchDirectory();
|
||||
|
||||
private:
|
||||
void populateDropDownForRegion(ConsoleRegion region, QComboBox* cb,
|
||||
std::vector<std::pair<std::string, const BIOS::ImageInfo*>>& images);
|
||||
void setDropDownValue(QComboBox* cb, const std::optional<std::string>& name);
|
||||
|
||||
Ui::BIOSSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/bus.h"
|
||||
#include "core/cpu_core.h"
|
||||
#include "core/host.h"
|
||||
#include "core/system.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtGui/QColor>
|
||||
@@ -45,26 +46,33 @@ static QString formatHexAndDecValue(u32 value, u8 size, bool is_signed)
|
||||
return QStringLiteral("0x%1 (%2)").arg(static_cast<u32>(value), size, 16, QChar('0')).arg(static_cast<uint>(value));
|
||||
}
|
||||
|
||||
|
||||
static QString formatCheatCode(u32 address, u32 value, const MemoryAccessSize size)
|
||||
{
|
||||
|
||||
if (size == MemoryAccessSize::Byte && address <= 0x00200000)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x30000000, 8, 16, QChar('0')).toUpper()
|
||||
.arg(static_cast<u16>(value), 4, 16, QChar('0')).toUpper();
|
||||
else if (size == MemoryAccessSize::HalfWord && address <= 0x001FFFFE)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x80000000, 8, 16, QChar('0')).toUpper()
|
||||
.arg(static_cast<u16>(value), 4, 16, QChar('0')).toUpper();
|
||||
else if (size == MemoryAccessSize::Word && address <= 0x001FFFFC)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x90000000, 8, 16, QChar('0')).toUpper()
|
||||
.arg(static_cast<u32>(value), 8, 16, QChar('0')).toUpper();
|
||||
else
|
||||
return QStringLiteral("OUTSIDE RAM RANGE. POKE %1 with %2")
|
||||
.arg(static_cast<u32>(address), 8, 16, QChar('0')).toUpper()
|
||||
.arg(static_cast<u16>(value), 8, 16, QChar('0')).toUpper();
|
||||
if (size == MemoryAccessSize::Byte && address <= 0x00200000)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x30000000, 8, 16, QChar('0'))
|
||||
.toUpper()
|
||||
.arg(static_cast<u16>(value), 4, 16, QChar('0'))
|
||||
.toUpper();
|
||||
else if (size == MemoryAccessSize::HalfWord && address <= 0x001FFFFE)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x80000000, 8, 16, QChar('0'))
|
||||
.toUpper()
|
||||
.arg(static_cast<u16>(value), 4, 16, QChar('0'))
|
||||
.toUpper();
|
||||
else if (size == MemoryAccessSize::Word && address <= 0x001FFFFC)
|
||||
return QStringLiteral("CHEAT CODE: %1 %2")
|
||||
.arg(static_cast<u32>(address) + 0x90000000, 8, 16, QChar('0'))
|
||||
.toUpper()
|
||||
.arg(static_cast<u32>(value), 8, 16, QChar('0'))
|
||||
.toUpper();
|
||||
else
|
||||
return QStringLiteral("OUTSIDE RAM RANGE. POKE %1 with %2")
|
||||
.arg(static_cast<u32>(address), 8, 16, QChar('0'))
|
||||
.toUpper()
|
||||
.arg(static_cast<u16>(value), 8, 16, QChar('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
static QString formatValue(u32 value, bool is_signed)
|
||||
@@ -180,7 +188,8 @@ void CheatManagerDialog::connectUi()
|
||||
connect(m_ui.scanTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::scanItemChanged);
|
||||
connect(m_ui.watchTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::watchItemChanged);
|
||||
|
||||
connect(QtHostInterface::GetInstance(), &QtHostInterface::cheatEnabled, this, &CheatManagerDialog::setCheatCheckState);
|
||||
connect(g_emu_thread, &EmuThread::cheatEnabled, this,
|
||||
&CheatManagerDialog::setCheatCheckState);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::showEvent(QShowEvent* event)
|
||||
@@ -332,17 +341,17 @@ CheatList* CheatManagerDialog::getCheatList() const
|
||||
CheatList* list = System::GetCheatList();
|
||||
if (!list)
|
||||
{
|
||||
QtHostInterface::GetInstance()->LoadCheatListFromGameTitle();
|
||||
System::LoadCheatListFromGameTitle();
|
||||
list = System::GetCheatList();
|
||||
}
|
||||
if (!list)
|
||||
{
|
||||
QtHostInterface::GetInstance()->LoadCheatListFromDatabase();
|
||||
System::LoadCheatListFromDatabase();
|
||||
list = System::GetCheatList();
|
||||
}
|
||||
if (!list)
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[]() { System::SetCheatList(std::make_unique<CheatList>()); }, true);
|
||||
list = System::GetCheatList();
|
||||
}
|
||||
@@ -408,7 +417,7 @@ void CheatManagerDialog::fillItemForCheatCode(QTreeWidgetItem* item, u32 index,
|
||||
|
||||
void CheatManagerDialog::saveCheatList()
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([]() { QtHostInterface::GetInstance()->SaveCheatList(); });
|
||||
Host::RunOnCPUThread([]() { System::SaveCheatList(); });
|
||||
}
|
||||
|
||||
void CheatManagerDialog::cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
|
||||
@@ -470,9 +479,9 @@ void CheatManagerDialog::cheatListItemChanged(QTreeWidgetItem* item, int column)
|
||||
if (cc.enabled == new_enabled)
|
||||
return;
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
|
||||
Host::RunOnCPUThread([index, new_enabled]() {
|
||||
System::GetCheatList()->SetCodeEnabled(static_cast<u32>(index), new_enabled);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -485,16 +494,16 @@ void CheatManagerDialog::activateCheat(u32 index)
|
||||
CheatCode& cc = list->GetCode(index);
|
||||
if (cc.IsManuallyActivated())
|
||||
{
|
||||
QtHostInterface::GetInstance()->applyCheat(index);
|
||||
g_emu_thread->applyCheat(index);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool new_enabled = !cc.enabled;
|
||||
setCheatCheckState(index, new_enabled);
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
|
||||
Host::RunOnCPUThread([index, new_enabled]() {
|
||||
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -542,10 +551,10 @@ void CheatManagerDialog::addCodeClicked()
|
||||
fillItemForCheatCode(item, list->GetCodeCount(), new_code);
|
||||
group_item->setExpanded(true);
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[this, &new_code]() {
|
||||
System::GetCheatList()->AddCode(std::move(new_code));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -588,10 +597,10 @@ void CheatManagerDialog::editCodeClicked()
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[index, &new_code]() {
|
||||
System::GetCheatList()->SetCode(static_cast<u32>(index), std::move(new_code));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -614,10 +623,10 @@ void CheatManagerDialog::deleteCodeClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[index]() {
|
||||
System::GetCheatList()->RemoveCode(static_cast<u32>(index));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
@@ -654,11 +663,11 @@ void CheatManagerDialog::importFromFileTriggered()
|
||||
return;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[&new_cheats]() {
|
||||
DebugAssert(System::HasCheatList());
|
||||
System::GetCheatList()->MergeList(new_cheats);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
@@ -677,11 +686,11 @@ void CheatManagerDialog::importFromTextTriggered()
|
||||
return;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
Host::RunOnCPUThread(
|
||||
[&new_cheats]() {
|
||||
DebugAssert(System::HasCheatList());
|
||||
System::GetCheatList()->MergeList(new_cheats);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
System::SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
@@ -707,8 +716,7 @@ void CheatManagerDialog::clearClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([] { QtHostInterface::GetInstance()->ClearCheatList(true); },
|
||||
true);
|
||||
Host::RunOnCPUThread([] { System::ClearCheatList(true); }, true);
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
@@ -723,8 +731,7 @@ void CheatManagerDialog::resetClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([] { QtHostInterface::GetInstance()->DeleteCheatList(); },
|
||||
true);
|
||||
Host::RunOnCPUThread([] { System::DeleteCheatList(); }, true);
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
@@ -737,11 +744,11 @@ void CheatManagerDialog::addToWatchClicked()
|
||||
|
||||
for (int index = indexFirst; index <= indexLast; index++)
|
||||
{
|
||||
const MemoryScan::Result& res = m_scanner.GetResults()[static_cast<u32>(index)];
|
||||
m_watch.AddEntry(StringUtil::StdStringFromFormat("0x%08x", res.address), res.address, m_scanner.GetSize(), m_scanner.GetValueSigned(), false);
|
||||
updateWatch();
|
||||
}
|
||||
|
||||
const MemoryScan::Result& res = m_scanner.GetResults()[static_cast<u32>(index)];
|
||||
m_watch.AddEntry(StringUtil::StdStringFromFormat("0x%08x", res.address), res.address, m_scanner.GetSize(),
|
||||
m_scanner.GetValueSigned(), false);
|
||||
updateWatch();
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::addManualWatchAddressClicked()
|
||||
@@ -779,9 +786,9 @@ void CheatManagerDialog::removeWatchClicked()
|
||||
|
||||
for (int index = indexLast; index >= indexFirst; index--)
|
||||
{
|
||||
m_watch.RemoveEntry(static_cast<u32>(index));
|
||||
updateWatch();
|
||||
}
|
||||
m_watch.RemoveEntry(static_cast<u32>(index));
|
||||
updateWatch();
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
|
||||
@@ -856,9 +863,9 @@ void CheatManagerDialog::watchItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
uint value;
|
||||
if (item->text()[1] == 'x' || item->text()[1] == 'X')
|
||||
value = item->text().toUInt(&value_ok, 16);
|
||||
value = item->text().toUInt(&value_ok, 16);
|
||||
else
|
||||
value = item->text().toUInt(&value_ok);
|
||||
value = item->text().toUInt(&value_ok);
|
||||
if (value_ok)
|
||||
m_watch.SetEntryValue(index, static_cast<u32>(value));
|
||||
}
|
||||
@@ -930,7 +937,6 @@ void CheatManagerDialog::updateResults()
|
||||
row++;
|
||||
}
|
||||
m_ui.scanResultsCount->setText(QString::number(m_scanner.GetResultCount()));
|
||||
|
||||
}
|
||||
else
|
||||
m_ui.scanResultsCount->setText("0");
|
||||
@@ -1003,12 +1009,12 @@ void CheatManagerDialog::updateWatch()
|
||||
value_item = new QTableWidgetItem(formatHexAndDecValue(res.value, 8, res.is_signed));
|
||||
|
||||
m_ui.watchTable->setItem(row, 3, value_item);
|
||||
|
||||
|
||||
QTableWidgetItem* freeze_item = new QTableWidgetItem();
|
||||
freeze_item->setFlags(freeze_item->flags() | (Qt::ItemIsEditable | Qt::ItemIsUserCheckable));
|
||||
freeze_item->setCheckState(res.freeze ? Qt::Checked : Qt::Unchecked);
|
||||
m_ui.watchTable->setItem(row, 4, freeze_item);
|
||||
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include "util/cd_image.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(ConsoleRegion::Count); i++)
|
||||
@@ -23,12 +25,6 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||
qApp->translate("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i))));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(MultitapMode::Count); i++)
|
||||
{
|
||||
m_ui.multitapMode->addItem(
|
||||
qApp->translate("MultitapMode", Settings::GetMultitapModeDisplayName(static_cast<MultitapMode>(i))));
|
||||
}
|
||||
|
||||
static constexpr float TIME_PER_SECTOR_DOUBLE_SPEED = 1000.0f / 150.0f;
|
||||
m_ui.cdromReadaheadSectors->addItem(tr("Disabled (Synchronous)"));
|
||||
for (u32 i = 1; i <= 32; i++)
|
||||
@@ -40,26 +36,20 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||
.arg(static_cast<float>(i * CDImage::DATA_SECTOR_SIZE) / 1024.0f));
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
|
||||
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
|
||||
Settings::DEFAULT_CONSOLE_REGION);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName,
|
||||
&Settings::GetConsoleRegionName, Settings::DEFAULT_CONSOLE_REGION);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
|
||||
&Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName,
|
||||
Settings::DEFAULT_CPU_EXECUTION_MODE);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableCPUClockSpeedControl, "CPU",
|
||||
"OverclockEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.cdromReadaheadSectors, "CDROM", "ReadaheadSectors",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCPUClockSpeedControl, "CPU", "OverclockEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromReadaheadSectors, "CDROM", "ReadaheadSectors",
|
||||
Settings::DEFAULT_CDROM_READAHEAD_SECTORS);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromRegionCheck, "CDROM", "RegionCheck", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImagePatches, "CDROM",
|
||||
"LoadImagePatches", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.cdromSeekSpeedup, "CDROM", "SeekSpeedup", 1);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
|
||||
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
|
||||
Settings::DEFAULT_MULTITAP_MODE);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromRegionCheck, "CDROM", "RegionCheck", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cdromLoadImagePatches, "CDROM", "LoadImagePatches", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromSeekSpeedup, "CDROM", "SeekSpeedup", 1);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromReadSpeedup, "CDROM", "ReadSpeedup", 1, 1);
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.region, tr("Region"), tr("Auto-Detect"),
|
||||
tr("Determines the emulated hardware type."));
|
||||
@@ -101,21 +91,12 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||
dialog->registerWidgetHelp(m_ui.cdromLoadImagePatches, tr("Apply Image Patches"), tr("Unchecked"),
|
||||
tr("Automatically applies patches to disc images when they are present in the same "
|
||||
"directory. Currently only PPF patches are supported with this option."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.multitapMode, tr("Multitap"), tr("Disabled"),
|
||||
tr("Enables multitap support on specified controller ports. Leave disabled for games that do "
|
||||
"not support multitap input."));
|
||||
|
||||
m_ui.cpuClockSpeed->setEnabled(m_ui.enableCPUClockSpeedControl->checkState() == Qt::Checked);
|
||||
m_ui.cdromReadSpeedup->setCurrentIndex(m_host_interface->GetIntSettingValue("CDROM", "ReadSpeedup", 1) - 1);
|
||||
|
||||
connect(m_ui.enableCPUClockSpeedControl, &QCheckBox::stateChanged, this,
|
||||
&ConsoleSettingsWidget::onEnableCPUClockSpeedControlChecked);
|
||||
connect(m_ui.cpuClockSpeed, &QSlider::valueChanged, this, &ConsoleSettingsWidget::onCPUClockSpeedValueChanged);
|
||||
connect(m_ui.cdromReadSpeedup, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConsoleSettingsWidget::onCDROMReadSpeedupValueChanged);
|
||||
connect(m_ui.multitapMode, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[this](int index) { emit multitapModeChanged(); });
|
||||
|
||||
calculateCPUClockValue();
|
||||
}
|
||||
@@ -124,7 +105,7 @@ ConsoleSettingsWidget::~ConsoleSettingsWidget() = default;
|
||||
|
||||
void ConsoleSettingsWidget::onEnableCPUClockSpeedControlChecked(int state)
|
||||
{
|
||||
if (state == Qt::Checked && !m_host_interface->GetBoolSettingValue("UI", "CPUOverclockingWarningShown", false))
|
||||
if (state == Qt::Checked && !Host::GetBaseBoolSettingValue("UI", "CPUOverclockingWarningShown", false))
|
||||
{
|
||||
const QString message =
|
||||
tr("Enabling CPU overclocking will break games, cause bugs, reduce performance and can significantly increase "
|
||||
@@ -133,16 +114,16 @@ void ConsoleSettingsWidget::onEnableCPUClockSpeedControlChecked(int state)
|
||||
const QString yes_button = tr("Yes, I will confirm bugs without overclocking before reporting.");
|
||||
const QString no_button = tr("No, take me back to safety.");
|
||||
|
||||
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("CPU Overclocking Warning"), message, yes_button,
|
||||
no_button) != 0)
|
||||
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("CPU Overclocking Warning"), message,
|
||||
QMessageBox::Yes | QMessageBox::No) != 0)
|
||||
{
|
||||
QSignalBlocker sb(m_ui.enableCPUClockSpeedControl);
|
||||
m_ui.enableCPUClockSpeedControl->setChecked(Qt::Unchecked);
|
||||
m_host_interface->SetBoolSettingValue("CPU", "OverclockEnable", false);
|
||||
m_dialog->setBoolSettingValue("CPU", "OverclockEnable", false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "CPUOverclockingWarningShown", true);
|
||||
Host::SetBaseBoolSettingValue("UI", "CPUOverclockingWarningShown", true);
|
||||
}
|
||||
|
||||
m_ui.cpuClockSpeed->setEnabled(state == Qt::Checked);
|
||||
@@ -154,10 +135,9 @@ void ConsoleSettingsWidget::onCPUClockSpeedValueChanged(int value)
|
||||
const u32 percent = static_cast<u32>(m_ui.cpuClockSpeed->value());
|
||||
u32 numerator, denominator;
|
||||
Settings::CPUOverclockPercentToFraction(percent, &numerator, &denominator);
|
||||
m_host_interface->SetIntSettingValue("CPU", "OverclockNumerator", static_cast<int>(numerator));
|
||||
m_host_interface->SetIntSettingValue("CPU", "OverclockDenominator", static_cast<int>(denominator));
|
||||
m_dialog->setIntSettingValue("CPU", "OverclockNumerator", static_cast<int>(numerator));
|
||||
m_dialog->setIntSettingValue("CPU", "OverclockDenominator", static_cast<int>(denominator));
|
||||
updateCPUClockSpeedLabel();
|
||||
m_host_interface->applySettings();
|
||||
}
|
||||
|
||||
void ConsoleSettingsWidget::updateCPUClockSpeedLabel()
|
||||
@@ -167,16 +147,10 @@ void ConsoleSettingsWidget::updateCPUClockSpeedLabel()
|
||||
m_ui.cpuClockSpeedLabel->setText(tr("%1% (%2MHz)").arg(percent).arg(frequency / 1000000.0, 0, 'f', 2));
|
||||
}
|
||||
|
||||
void ConsoleSettingsWidget::onCDROMReadSpeedupValueChanged(int value)
|
||||
{
|
||||
m_host_interface->SetIntSettingValue("CDROM", "ReadSpeedup", value + 1);
|
||||
m_host_interface->applySettings();
|
||||
}
|
||||
|
||||
void ConsoleSettingsWidget::calculateCPUClockValue()
|
||||
{
|
||||
const u32 numerator = static_cast<u32>(m_host_interface->GetIntSettingValue("CPU", "OverclockNumerator", 1));
|
||||
const u32 denominator = static_cast<u32>(m_host_interface->GetIntSettingValue("CPU", "OverclockDenominator", 1));
|
||||
const u32 numerator = static_cast<u32>(m_dialog->getEffectiveIntValue("CPU", "OverclockNumerator", 1));
|
||||
const u32 denominator = static_cast<u32>(m_dialog->getEffectiveIntValue("CPU", "OverclockDenominator", 1));
|
||||
const u32 percent = Settings::CPUOverclockFractionToPercent(numerator, denominator);
|
||||
QSignalBlocker sb(m_ui.cpuClockSpeed);
|
||||
m_ui.cpuClockSpeed->setValue(static_cast<int>(percent));
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_consolesettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class ConsoleSettingsWidget : public QWidget
|
||||
@@ -12,22 +11,18 @@ class ConsoleSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConsoleSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit ConsoleSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~ConsoleSettingsWidget();
|
||||
|
||||
Q_SIGNALS:
|
||||
void multitapModeChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onEnableCPUClockSpeedControlChecked(int state);
|
||||
void onCPUClockSpeedValueChanged(int value);
|
||||
void updateCPUClockSpeedLabel();
|
||||
void onCDROMReadSpeedupValueChanged(int value);
|
||||
|
||||
private:
|
||||
void calculateCPUClockValue();
|
||||
|
||||
Ui::ConsoleSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -305,25 +305,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Controller Ports</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Multitap:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="multitapMode"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
||||
110
src/duckstation-qt/controllerbindingwidget.ui
Normal file
110
src/duckstation-qt/controllerbindingwidget.ui
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerBindingWidget</class>
|
||||
<widget class="QWidget" name="ControllerBindingWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>833</width>
|
||||
<height>562</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Controller Type</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="controllerType"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="settings">
|
||||
<property name="text">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="checkbox-multiple-blank-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="macros">
|
||||
<property name="text">
|
||||
<string>Macros</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="flashlight-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>460</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="automaticBinding">
|
||||
<property name="text">
|
||||
<string>Automatic binding</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="gamepad-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearBindings">
|
||||
<property name="text">
|
||||
<string>Clear Bindings</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="file-reduce-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
1271
src/duckstation-qt/controllerbindingwidget_analog_controller.ui
Normal file
1271
src/duckstation-qt/controllerbindingwidget_analog_controller.ui
Normal file
File diff suppressed because it is too large
Load Diff
1186
src/duckstation-qt/controllerbindingwidget_analog_joystick.ui
Normal file
1186
src/duckstation-qt/controllerbindingwidget_analog_joystick.ui
Normal file
File diff suppressed because it is too large
Load Diff
737
src/duckstation-qt/controllerbindingwidget_digital_controller.ui
Normal file
737
src/duckstation-qt/controllerbindingwidget_digital_controller.ui
Normal file
@@ -0,0 +1,737 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerBindingWidget_DigitalController</class>
|
||||
<widget class="QWidget" name="ControllerBindingWidget_DigitalController">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1100</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1100</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_27">
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_22">
|
||||
<property name="title">
|
||||
<string>L1</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_22">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="L1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_21">
|
||||
<property name="title">
|
||||
<string>L2</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_21">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="L2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_23">
|
||||
<property name="title">
|
||||
<string>R2</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_23">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="R2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_24">
|
||||
<property name="title">
|
||||
<string>R1</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_24">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="R1">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_16">
|
||||
<property name="title">
|
||||
<string>Face Buttons</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_16">
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_17">
|
||||
<property name="title">
|
||||
<string>Cross</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_17">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Cross">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_18">
|
||||
<property name="title">
|
||||
<string>Square</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_18">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Square">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_19">
|
||||
<property name="title">
|
||||
<string>Triangle</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_19">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Triangle">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_20">
|
||||
<property name="title">
|
||||
<string>Circle</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_20">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Circle">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>266</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources/resources.qrc">:/controllers/digital_controller.svg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_32">
|
||||
<item row="1" column="0" colspan="4">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>D-Pad</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Down</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Down">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Left</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Left">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Up</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Up">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Right</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Right">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="1">
|
||||
<widget class="QGroupBox" name="groupBox_25">
|
||||
<property name="title">
|
||||
<string>Select</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_25">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Select">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QGroupBox" name="groupBox_26">
|
||||
<property name="title">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_26">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Start">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>InputBindingWidget</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>inputbindingwidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
380
src/duckstation-qt/controllerbindingwidget_guncon.ui
Normal file
380
src/duckstation-qt/controllerbindingwidget_guncon.ui
Normal file
@@ -0,0 +1,380 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerBindingWidget_GunCon</class>
|
||||
<widget class="QWidget" name="ControllerBindingWidget_GunCon">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1100</width>
|
||||
<height>504</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1100</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_35">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="2" rowspan="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_16">
|
||||
<property name="title">
|
||||
<string>Side Buttons</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_16">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_17">
|
||||
<property name="title">
|
||||
<string>B</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_17">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="B">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_19">
|
||||
<property name="title">
|
||||
<string>A</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_19">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="A">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>266</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources/resources.qrc">:/controllers/guncon.svg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_27"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_32">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Trigger</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Fire Offscreen</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="ShootOffscreen">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Fire</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Trigger">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>InputBindingWidget</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>inputbindingwidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
719
src/duckstation-qt/controllerbindingwidget_negcon.ui
Normal file
719
src/duckstation-qt/controllerbindingwidget_negcon.ui
Normal file
@@ -0,0 +1,719 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerBindingWidget_NeGcon</class>
|
||||
<widget class="QWidget" name="ControllerBindingWidget_NeGcon">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1100</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1100</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_35">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0" rowspan="3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>D-Pad</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Down</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Down">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Left</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Left">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Up</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Up">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Right</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Right">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_26">
|
||||
<property name="title">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_26">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="Start">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_27">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_22">
|
||||
<property name="title">
|
||||
<string>L</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_22">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="L">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QGroupBox" name="groupBox_24">
|
||||
<property name="title">
|
||||
<string>R</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_24">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="R">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_16">
|
||||
<property name="title">
|
||||
<string>Face Buttons</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_16">
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_17">
|
||||
<property name="title">
|
||||
<string>I</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_17">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="I">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_18">
|
||||
<property name="title">
|
||||
<string>II</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_18">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="II">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_19">
|
||||
<property name="title">
|
||||
<string>B</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_19">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="B">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_20">
|
||||
<property name="title">
|
||||
<string>A</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_20">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="A">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>266</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources/resources.qrc">:/controllers/negcon.svg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_32">
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="groupBox_11">
|
||||
<property name="title">
|
||||
<string>Steering/Twist</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_15">
|
||||
<property name="title">
|
||||
<string>Right</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_15">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="SteeringRight">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_13">
|
||||
<property name="title">
|
||||
<string>Left</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_13">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="InputBindingWidget" name="SteeringLeft">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>InputBindingWidget</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>inputbindingwidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
825
src/duckstation-qt/controllerbindingwidgets.cpp
Normal file
825
src/duckstation-qt/controllerbindingwidgets.cpp
Normal file
@@ -0,0 +1,825 @@
|
||||
#include "controllerbindingwidgets.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "controllersettingwidgetbinder.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtWidgets/QCheckBox>
|
||||
#include <QtWidgets/QDoubleSpinBox>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QSpinBox>
|
||||
#include <algorithm>
|
||||
|
||||
Log_SetChannel(ControllerBindingWidget);
|
||||
|
||||
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
|
||||
: QWidget(parent), m_dialog(dialog), m_config_section(Controller::GetSettingsSection(port)), m_port_number(port)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
populateControllerTypes();
|
||||
populateBindingWidget();
|
||||
|
||||
connect(m_ui.controllerType, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ControllerBindingWidget::onTypeChanged);
|
||||
connect(m_ui.settings, &QPushButton::clicked, this, &ControllerBindingWidget::onSettingsClicked);
|
||||
connect(m_ui.macros, &QPushButton::clicked, this, &ControllerBindingWidget::onMacrosClicked);
|
||||
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::onAutomaticBindingClicked);
|
||||
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::onClearBindingsClicked);
|
||||
}
|
||||
|
||||
ControllerBindingWidget::~ControllerBindingWidget() = default;
|
||||
|
||||
QIcon ControllerBindingWidget::getIcon() const
|
||||
{
|
||||
return m_current_widget->getIcon();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::populateControllerTypes()
|
||||
{
|
||||
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
|
||||
{
|
||||
const ControllerType ctype = static_cast<ControllerType>(i);
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctype);
|
||||
if (!cinfo)
|
||||
continue;
|
||||
|
||||
m_ui.controllerType->addItem(qApp->translate("ControllerType", cinfo->display_name), QVariant(static_cast<int>(i)));
|
||||
}
|
||||
|
||||
const std::string controller_type_name(
|
||||
m_dialog->getStringValue(m_config_section.c_str(), "Type", Controller::GetDefaultPadType(m_port_number)));
|
||||
m_controller_type = Settings::ParseControllerTypeName(controller_type_name.c_str()).value_or(ControllerType::None);
|
||||
|
||||
const int index = m_ui.controllerType->findData(QVariant(static_cast<int>(m_controller_type)));
|
||||
if (index >= 0 && index != m_ui.controllerType->currentIndex())
|
||||
{
|
||||
QSignalBlocker sb(m_ui.controllerType);
|
||||
m_ui.controllerType->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::populateBindingWidget()
|
||||
{
|
||||
const bool is_initializing = (m_current_widget == nullptr);
|
||||
if (!is_initializing)
|
||||
{
|
||||
m_ui.verticalLayout->removeWidget(m_current_widget);
|
||||
delete m_current_widget;
|
||||
m_current_widget = nullptr;
|
||||
}
|
||||
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(m_controller_type);
|
||||
m_ui.settings->setEnabled(cinfo && cinfo->num_settings > 0);
|
||||
m_ui.macros->setEnabled(cinfo && cinfo->num_bindings > 0);
|
||||
|
||||
switch (m_controller_type)
|
||||
{
|
||||
case ControllerType::AnalogController:
|
||||
m_current_widget = ControllerBindingWidget_AnalogController::createInstance(this);
|
||||
break;
|
||||
case ControllerType::AnalogJoystick:
|
||||
m_current_widget = ControllerBindingWidget_AnalogJoystick::createInstance(this);
|
||||
break;
|
||||
case ControllerType::DigitalController:
|
||||
m_current_widget = ControllerBindingWidget_DigitalController::createInstance(this);
|
||||
break;
|
||||
case ControllerType::GunCon:
|
||||
m_current_widget = ControllerBindingWidget_GunCon::createInstance(this);
|
||||
break;
|
||||
case ControllerType::NeGcon:
|
||||
m_current_widget = ControllerBindingWidget_NeGcon::createInstance(this);
|
||||
break;
|
||||
default:
|
||||
m_current_widget = new ControllerBindingWidget_Base(this);
|
||||
break;
|
||||
}
|
||||
|
||||
m_ui.verticalLayout->addWidget(m_current_widget, 1);
|
||||
|
||||
// no need to do this on first init, only changes
|
||||
if (!is_initializing)
|
||||
m_dialog->updateListDescription(m_port_number, this);
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::onTypeChanged()
|
||||
{
|
||||
bool ok;
|
||||
const int index = m_ui.controllerType->currentData().toInt(&ok);
|
||||
if (!ok || index < 0 || index >= static_cast<int>(ControllerType::Count))
|
||||
return;
|
||||
|
||||
m_controller_type = static_cast<ControllerType>(index);
|
||||
|
||||
SettingsInterface* sif = m_dialog->getProfileSettingsInterface();
|
||||
if (sif)
|
||||
{
|
||||
sif->SetStringValue(m_config_section.c_str(), "Type", Settings::GetControllerTypeName(m_controller_type));
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseStringSettingValue(m_config_section.c_str(), "Type",
|
||||
Settings::GetControllerTypeName(m_controller_type));
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
|
||||
populateBindingWidget();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::onAutomaticBindingClicked()
|
||||
{
|
||||
QMenu menu(this);
|
||||
bool added = false;
|
||||
|
||||
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
|
||||
{
|
||||
// we set it as data, because the device list could get invalidated while the menu is up
|
||||
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
|
||||
action->setData(dev.first);
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (!added)
|
||||
{
|
||||
QAction* action = menu.addAction(tr("No devices available"));
|
||||
action->setEnabled(false);
|
||||
}
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::onClearBindingsClicked()
|
||||
{
|
||||
if (QMessageBox::question(
|
||||
QtUtils::GetRootWidget(this), tr("Clear Bindings"),
|
||||
tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) !=
|
||||
QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_dialog->isEditingGlobalSettings())
|
||||
{
|
||||
auto lock = Host::GetSettingsLock();
|
||||
InputManager::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
|
||||
}
|
||||
else
|
||||
{
|
||||
InputManager::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
|
||||
}
|
||||
|
||||
saveAndRefresh();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::onSettingsClicked()
|
||||
{
|
||||
ControllerCustomSettingsDialog dialog(this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::onMacrosClicked()
|
||||
{
|
||||
ControllerMacroDialog dialog(this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
|
||||
{
|
||||
std::vector<std::pair<GenericInputBinding, std::string>> mapping =
|
||||
InputManager::GetGenericBindingMapping(device.toStdString());
|
||||
if (mapping.empty())
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Automatic Binding"),
|
||||
tr("No generic bindings were generated for device '%1'").arg(device));
|
||||
return;
|
||||
}
|
||||
|
||||
bool result;
|
||||
if (m_dialog->isEditingGlobalSettings())
|
||||
{
|
||||
auto lock = Host::GetSettingsLock();
|
||||
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = InputManager::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
|
||||
m_dialog->getProfileSettingsInterface()->Save();
|
||||
g_emu_thread->reloadInputBindings();
|
||||
}
|
||||
|
||||
// force a refresh after mapping
|
||||
if (result)
|
||||
saveAndRefresh();
|
||||
}
|
||||
|
||||
void ControllerBindingWidget::saveAndRefresh()
|
||||
{
|
||||
onTypeChanged();
|
||||
QtHost::QueueSettingsSave();
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerMacroDialog::ControllerMacroDialog(ControllerBindingWidget* parent) : QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
setWindowTitle(tr("Controller Port %1 Macros").arg(parent->getPortNumber() + 1u));
|
||||
createWidgets(parent);
|
||||
}
|
||||
|
||||
ControllerMacroDialog::~ControllerMacroDialog() = default;
|
||||
|
||||
void ControllerMacroDialog::updateListItem(u32 index)
|
||||
{
|
||||
m_ui.portList->item(static_cast<int>(index))
|
||||
->setText(tr("Macro %1\n%2").arg(index + 1).arg(m_macros[index]->getSummary()));
|
||||
}
|
||||
|
||||
void ControllerMacroDialog::createWidgets(ControllerBindingWidget* parent)
|
||||
{
|
||||
for (u32 i = 0; i < NUM_MACROS; i++)
|
||||
{
|
||||
m_macros[i] = new ControllerMacroEditWidget(this, parent, i);
|
||||
m_ui.container->addWidget(m_macros[i]);
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setIcon(QIcon::fromTheme(QStringLiteral("flashlight-line")));
|
||||
m_ui.portList->addItem(item);
|
||||
updateListItem(i);
|
||||
}
|
||||
|
||||
m_ui.portList->setCurrentItem(0);
|
||||
m_ui.container->setCurrentIndex(0);
|
||||
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsDialog::close);
|
||||
connect(m_ui.portList, &QListWidget::currentRowChanged, m_ui.container, &QStackedWidget::setCurrentIndex);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroDialog* parent, ControllerBindingWidget* bwidget,
|
||||
u32 index)
|
||||
: QWidget(parent), m_parent(parent), m_bwidget(bwidget), m_index(index)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
|
||||
const std::string& section = m_bwidget->getConfigSection();
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(m_bwidget->getControllerType());
|
||||
if (!cinfo)
|
||||
{
|
||||
// Shouldn't ever happen.
|
||||
return;
|
||||
}
|
||||
|
||||
// load binds (single string joined by &)
|
||||
const std::string binds_string(
|
||||
dialog->getStringValue(section.c_str(), fmt::format("Macro{}Binds", index + 1u).c_str(), ""));
|
||||
const std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
|
||||
|
||||
for (const std::string_view& button : buttons_split)
|
||||
{
|
||||
for (u32 i = 0; i < cinfo->num_bindings; i++)
|
||||
{
|
||||
if (button == cinfo->bindings[i].name)
|
||||
{
|
||||
m_binds.push_back(&cinfo->bindings[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// populate list view
|
||||
for (u32 i = 0; i < cinfo->num_bindings; i++)
|
||||
{
|
||||
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setText(QString::fromUtf8(bi.display_name));
|
||||
item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked :
|
||||
Qt::Unchecked);
|
||||
m_ui.bindList->addItem(item);
|
||||
}
|
||||
|
||||
m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0);
|
||||
updateFrequencyText();
|
||||
|
||||
m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), section, fmt::format("Macro{}", index + 1u));
|
||||
|
||||
connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); });
|
||||
connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); });
|
||||
connect(m_ui.setFrequency, &QAbstractButton::clicked, this, &ControllerMacroEditWidget::onSetFrequencyClicked);
|
||||
connect(m_ui.bindList, &QListWidget::itemChanged, this, &ControllerMacroEditWidget::updateBinds);
|
||||
}
|
||||
|
||||
ControllerMacroEditWidget::~ControllerMacroEditWidget() = default;
|
||||
|
||||
QString ControllerMacroEditWidget::getSummary() const
|
||||
{
|
||||
SmallString str;
|
||||
for (const Controller::ControllerBindingInfo* bi : m_binds)
|
||||
{
|
||||
if (!str.IsEmpty())
|
||||
str.AppendCharacter('/');
|
||||
str.AppendString(bi->name);
|
||||
}
|
||||
return str.IsEmpty() ? tr("Not Configured") : QString::fromUtf8(str.GetCharArray(), str.GetLength());
|
||||
}
|
||||
|
||||
void ControllerMacroEditWidget::onSetFrequencyClicked()
|
||||
{
|
||||
bool okay;
|
||||
int new_freq = QInputDialog::getInt(this, tr("Set Frequency"), tr("Frequency: "), static_cast<int>(m_frequency), 0,
|
||||
std::numeric_limits<int>::max(), 1, &okay);
|
||||
if (!okay)
|
||||
return;
|
||||
|
||||
m_frequency = static_cast<u32>(new_freq);
|
||||
updateFrequency();
|
||||
}
|
||||
|
||||
void ControllerMacroEditWidget::modFrequency(s32 delta)
|
||||
{
|
||||
if (delta < 0 && m_frequency == 0)
|
||||
return;
|
||||
|
||||
m_frequency = static_cast<u32>(static_cast<s32>(m_frequency) + delta);
|
||||
updateFrequency();
|
||||
}
|
||||
|
||||
void ControllerMacroEditWidget::updateFrequency()
|
||||
{
|
||||
m_bwidget->getDialog()->setIntValue(m_bwidget->getConfigSection().c_str(),
|
||||
fmt::format("Macro{}Frequency", m_index).c_str(), static_cast<s32>(m_frequency));
|
||||
updateFrequencyText();
|
||||
}
|
||||
|
||||
void ControllerMacroEditWidget::updateFrequencyText()
|
||||
{
|
||||
if (m_frequency == 0)
|
||||
m_ui.frequencyText->setText(tr("Macro will not repeat."));
|
||||
else
|
||||
m_ui.frequencyText->setText(tr("Macro will toggle buttons every %1 frames.").arg(m_frequency));
|
||||
}
|
||||
|
||||
void ControllerMacroEditWidget::updateBinds()
|
||||
{
|
||||
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(m_bwidget->getControllerType());
|
||||
if (!cinfo)
|
||||
return;
|
||||
|
||||
std::vector<const Controller::ControllerBindingInfo*> new_binds;
|
||||
for (u32 i = 0; i < cinfo->num_bindings; i++)
|
||||
{
|
||||
const QListWidgetItem* item = m_ui.bindList->item(static_cast<int>(i));
|
||||
if (!item)
|
||||
{
|
||||
// shouldn't happen
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->checkState() == Qt::Checked)
|
||||
new_binds.push_back(&cinfo->bindings[i]);
|
||||
}
|
||||
if (m_binds == new_binds)
|
||||
return;
|
||||
|
||||
m_binds = std::move(new_binds);
|
||||
|
||||
std::string binds_string;
|
||||
for (const Controller::ControllerBindingInfo* bi : m_binds)
|
||||
{
|
||||
if (!binds_string.empty())
|
||||
binds_string.append(" & ");
|
||||
binds_string.append(bi->name);
|
||||
}
|
||||
|
||||
const std::string& section = m_bwidget->getConfigSection();
|
||||
const std::string key(fmt::format("Macro{}Binds", m_index + 1u));
|
||||
if (binds_string.empty())
|
||||
dialog->clearSettingValue(section.c_str(), key.c_str());
|
||||
else
|
||||
dialog->setStringValue(section.c_str(), key.c_str(), binds_string.c_str());
|
||||
|
||||
m_parent->updateListItem(m_index);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerCustomSettingsDialog::ControllerCustomSettingsDialog(ControllerBindingWidget* parent) : QDialog(parent)
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
|
||||
int row = createSettingWidgets(parent, layout);
|
||||
|
||||
QDialogButtonBox* bbox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::RestoreDefaults, this);
|
||||
connect(bbox, &QDialogButtonBox::rejected, this, &QDialog::accept);
|
||||
connect(bbox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this,
|
||||
&ControllerCustomSettingsDialog::restoreDefaults);
|
||||
layout->addWidget(bbox, row++, 0, 1, 4);
|
||||
}
|
||||
|
||||
ControllerCustomSettingsDialog::~ControllerCustomSettingsDialog() {}
|
||||
|
||||
int ControllerCustomSettingsDialog::createSettingWidgets(ControllerBindingWidget* parent, QGridLayout* layout)
|
||||
{
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(parent->getControllerType());
|
||||
if (!cinfo || cinfo->num_settings == 0)
|
||||
return 0;
|
||||
|
||||
setWindowTitle(tr("%1 Settings").arg(qApp->translate("ControllerType", cinfo->display_name)));
|
||||
|
||||
const std::string& section = parent->getConfigSection();
|
||||
SettingsInterface* sif = parent->getDialog()->getProfileSettingsInterface();
|
||||
int current_row = 0;
|
||||
|
||||
for (u32 i = 0; i < cinfo->num_settings; i++)
|
||||
{
|
||||
const SettingInfo& si = cinfo->settings[i];
|
||||
std::string key_name = si.key;
|
||||
|
||||
switch (si.type)
|
||||
{
|
||||
case SettingInfo::Type::Boolean:
|
||||
{
|
||||
QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.visible_name), this);
|
||||
cb->setObjectName(QString::fromUtf8(si.key));
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, cb, section, std::move(key_name),
|
||||
si.BooleanDefaultValue());
|
||||
layout->addWidget(cb, current_row, 0, 1, 4);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Integer:
|
||||
{
|
||||
QSpinBox* sb = new QSpinBox(this);
|
||||
sb->setObjectName(QString::fromUtf8(si.key));
|
||||
sb->setMinimum(si.IntegerMinValue());
|
||||
sb->setMaximum(si.IntegerMaxValue());
|
||||
sb->setSingleStep(si.IntegerStepValue());
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, section, std::move(key_name), si.IntegerDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
|
||||
layout->addWidget(sb, current_row, 1, 1, 3);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Float:
|
||||
{
|
||||
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
|
||||
sb->setObjectName(QString::fromUtf8(si.key));
|
||||
sb->setMinimum(si.FloatMinValue());
|
||||
sb->setMaximum(si.FloatMaxValue());
|
||||
sb->setSingleStep(si.FloatStepValue());
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, section, std::move(key_name), si.FloatDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
|
||||
layout->addWidget(sb, current_row, 1, 1, 3);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::String:
|
||||
{
|
||||
QLineEdit* le = new QLineEdit(this);
|
||||
le->setObjectName(QString::fromUtf8(si.key));
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
|
||||
layout->addWidget(le, current_row, 1, 1, 3);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Path:
|
||||
{
|
||||
QLineEdit* le = new QLineEdit(this);
|
||||
le->setObjectName(QString::fromUtf8(si.key));
|
||||
QPushButton* browse_button = new QPushButton(tr("Browse..."), this);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
|
||||
connect(browse_button, &QPushButton::clicked, [this, le]() {
|
||||
QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
|
||||
if (!path.isEmpty())
|
||||
le->setText(path);
|
||||
});
|
||||
|
||||
QHBoxLayout* hbox = new QHBoxLayout();
|
||||
hbox->addWidget(le, 1);
|
||||
hbox->addWidget(browse_button);
|
||||
|
||||
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
|
||||
layout->addLayout(hbox, current_row, 1, 1, 3);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QLabel* label = new QLabel(si.description ? qApp->translate(cinfo->name, si.description) : QString(), this);
|
||||
label->setWordWrap(true);
|
||||
layout->addWidget(label, current_row++, 0, 1, 4);
|
||||
|
||||
layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4);
|
||||
}
|
||||
|
||||
resize(600, 100);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
|
||||
|
||||
return current_row;
|
||||
}
|
||||
|
||||
void ControllerCustomSettingsDialog::restoreDefaults()
|
||||
{
|
||||
ControllerBindingWidget* parent = static_cast<ControllerBindingWidget*>(this->parent());
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(parent->getControllerType());
|
||||
if (!cinfo || cinfo->num_settings == 0)
|
||||
return;
|
||||
|
||||
for (u32 i = 0; i < cinfo->num_settings; i++)
|
||||
{
|
||||
const SettingInfo& si = cinfo->settings[i];
|
||||
const QString key(QString::fromStdString(si.key));
|
||||
|
||||
switch (si.type)
|
||||
{
|
||||
case SettingInfo::Type::Boolean:
|
||||
{
|
||||
QCheckBox* widget = findChild<QCheckBox*>(QString::fromStdString(si.key));
|
||||
if (widget)
|
||||
widget->setChecked(si.BooleanDefaultValue());
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Integer:
|
||||
{
|
||||
QSpinBox* widget = findChild<QSpinBox*>(QString::fromStdString(si.key));
|
||||
if (widget)
|
||||
widget->setValue(si.IntegerDefaultValue());
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Float:
|
||||
{
|
||||
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.key));
|
||||
if (widget)
|
||||
widget->setValue(si.FloatDefaultValue());
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::String:
|
||||
{
|
||||
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
|
||||
if (widget)
|
||||
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Path:
|
||||
{
|
||||
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
|
||||
if (widget)
|
||||
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_Base::ControllerBindingWidget_Base(ControllerBindingWidget* parent) : QWidget(parent) {}
|
||||
|
||||
ControllerBindingWidget_Base::~ControllerBindingWidget_Base() {}
|
||||
|
||||
QIcon ControllerBindingWidget_Base::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme("BIOSSettings");
|
||||
}
|
||||
|
||||
void ControllerBindingWidget_Base::initBindingWidgets()
|
||||
{
|
||||
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
|
||||
const ControllerType type = getControllerType();
|
||||
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(type);
|
||||
if (!cinfo)
|
||||
return;
|
||||
|
||||
const std::string& config_section = getConfigSection();
|
||||
for (u32 i = 0; i < cinfo->num_bindings; i++)
|
||||
{
|
||||
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
|
||||
if (bi.type == Controller::ControllerBindingType::Unknown || bi.type == Controller::ControllerBindingType::Motor)
|
||||
continue;
|
||||
|
||||
InputBindingWidget* widget = findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
|
||||
if (!widget)
|
||||
{
|
||||
Log_ErrorPrintf("No widget found for '%s' (%s)", bi.name, cinfo->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
widget->initialize(sif, config_section, bi.name);
|
||||
}
|
||||
|
||||
switch (cinfo->vibration_caps)
|
||||
{
|
||||
case Controller::VibrationCapabilities::LargeSmallMotors:
|
||||
{
|
||||
InputVibrationBindingWidget* widget = findChild<InputVibrationBindingWidget*>(QStringLiteral("LargeMotor"));
|
||||
if (widget)
|
||||
widget->setKey(getDialog(), config_section, "LargeMotor");
|
||||
|
||||
widget = findChild<InputVibrationBindingWidget*>(QStringLiteral("SmallMotor"));
|
||||
if (widget)
|
||||
widget->setKey(getDialog(), config_section, "SmallMotor");
|
||||
}
|
||||
break;
|
||||
|
||||
case Controller::VibrationCapabilities::SingleMotor:
|
||||
{
|
||||
InputVibrationBindingWidget* widget = findChild<InputVibrationBindingWidget*>(QStringLiteral("Motor"));
|
||||
if (widget)
|
||||
widget->setKey(getDialog(), config_section, "Motor");
|
||||
}
|
||||
break;
|
||||
|
||||
case Controller::VibrationCapabilities::NoVibration:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (QSlider* widget = findChild<QSlider*>(QStringLiteral("AnalogDeadzone")); widget)
|
||||
{
|
||||
const float range = static_cast<float>(widget->maximum());
|
||||
QLabel* label = findChild<QLabel*>(QStringLiteral("AnalogDeadzoneLabel"));
|
||||
if (label)
|
||||
{
|
||||
connect(widget, &QSlider::valueChanged, this, [range, label](int value) {
|
||||
label->setText(tr("%1%").arg((static_cast<float>(value) / range) * 100.0f, 0, 'f', 0));
|
||||
});
|
||||
}
|
||||
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(sif, widget, config_section, "AnalogDeadzone",
|
||||
range, Controller::DEFAULT_STICK_DEADZONE);
|
||||
}
|
||||
|
||||
if (QSlider* widget = findChild<QSlider*>(QStringLiteral("AnalogSensitivity")); widget)
|
||||
{
|
||||
// position 1.0f at the halfway point
|
||||
const float range = static_cast<float>(widget->maximum()) * 0.5f;
|
||||
QLabel* label = findChild<QLabel*>(QStringLiteral("AnalogSensitivityLabel"));
|
||||
if (label)
|
||||
{
|
||||
connect(widget, &QSlider::valueChanged, this, [range, label](int value) {
|
||||
label->setText(tr("%1%").arg((static_cast<float>(value) / range) * 100.0f, 0, 'f', 0));
|
||||
});
|
||||
}
|
||||
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(sif, widget, config_section, "AnalogSensitivity",
|
||||
range, Controller::DEFAULT_STICK_SENSITIVITY);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// FIXME
|
||||
if (QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QStringLiteral("SmallMotorScale")); widget)
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, widget, config_section, "SmallMotorScale",
|
||||
Controller::DEFAULT_MOTOR_SCALE);
|
||||
if (QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QStringLiteral("LargeMotorScale")); widget)
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, widget, config_section, "LargeMotorScale",
|
||||
Controller::DEFAULT_MOTOR_SCALE);
|
||||
#endif
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_DigitalController::ControllerBindingWidget_DigitalController(ControllerBindingWidget* parent)
|
||||
: ControllerBindingWidget_Base(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
initBindingWidgets();
|
||||
}
|
||||
|
||||
ControllerBindingWidget_DigitalController::~ControllerBindingWidget_DigitalController() {}
|
||||
|
||||
QIcon ControllerBindingWidget_DigitalController::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme(QStringLiteral("gamepad-line"));
|
||||
}
|
||||
|
||||
ControllerBindingWidget_Base* ControllerBindingWidget_DigitalController::createInstance(ControllerBindingWidget* parent)
|
||||
{
|
||||
return new ControllerBindingWidget_DigitalController(parent);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_AnalogController::ControllerBindingWidget_AnalogController(ControllerBindingWidget* parent)
|
||||
: ControllerBindingWidget_Base(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
initBindingWidgets();
|
||||
}
|
||||
|
||||
ControllerBindingWidget_AnalogController::~ControllerBindingWidget_AnalogController() {}
|
||||
|
||||
QIcon ControllerBindingWidget_AnalogController::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme(QStringLiteral("ControllerSettings"));
|
||||
}
|
||||
|
||||
ControllerBindingWidget_Base* ControllerBindingWidget_AnalogController::createInstance(ControllerBindingWidget* parent)
|
||||
{
|
||||
return new ControllerBindingWidget_AnalogController(parent);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_AnalogJoystick::ControllerBindingWidget_AnalogJoystick(ControllerBindingWidget* parent)
|
||||
: ControllerBindingWidget_Base(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
initBindingWidgets();
|
||||
}
|
||||
|
||||
ControllerBindingWidget_AnalogJoystick::~ControllerBindingWidget_AnalogJoystick() {}
|
||||
|
||||
QIcon ControllerBindingWidget_AnalogJoystick::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme(QStringLiteral("ControllerSettings"));
|
||||
}
|
||||
|
||||
ControllerBindingWidget_Base* ControllerBindingWidget_AnalogJoystick::createInstance(ControllerBindingWidget* parent)
|
||||
{
|
||||
return new ControllerBindingWidget_AnalogJoystick(parent);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_NeGcon::ControllerBindingWidget_NeGcon(ControllerBindingWidget* parent)
|
||||
: ControllerBindingWidget_Base(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
initBindingWidgets();
|
||||
|
||||
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
|
||||
const std::string& config_section = getConfigSection();
|
||||
if (QSlider* widget = findChild<QSlider*>(QStringLiteral("SteeringDeadzone")); widget)
|
||||
{
|
||||
const float range = static_cast<float>(widget->maximum());
|
||||
QLabel* label = findChild<QLabel*>(QStringLiteral("SteeringDeadzoneLabel"));
|
||||
if (label)
|
||||
{
|
||||
connect(widget, &QSlider::valueChanged, this, [range, label](int value) {
|
||||
label->setText(tr("%1%").arg((static_cast<float>(value) / range) * 100.0f, 0, 'f', 0));
|
||||
});
|
||||
}
|
||||
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(sif, widget, config_section, "SteeringDeadzone",
|
||||
range, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ControllerBindingWidget_NeGcon::~ControllerBindingWidget_NeGcon() {}
|
||||
|
||||
QIcon ControllerBindingWidget_NeGcon::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme(QStringLiteral("steering-line"));
|
||||
}
|
||||
|
||||
ControllerBindingWidget_Base* ControllerBindingWidget_NeGcon::createInstance(ControllerBindingWidget* parent)
|
||||
{
|
||||
return new ControllerBindingWidget_NeGcon(parent);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ControllerBindingWidget_GunCon::ControllerBindingWidget_GunCon(ControllerBindingWidget* parent)
|
||||
: ControllerBindingWidget_Base(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
initBindingWidgets();
|
||||
}
|
||||
|
||||
ControllerBindingWidget_GunCon::~ControllerBindingWidget_GunCon() {}
|
||||
|
||||
QIcon ControllerBindingWidget_GunCon::getIcon() const
|
||||
{
|
||||
return QIcon::fromTheme(QStringLiteral("fire-line"));
|
||||
}
|
||||
|
||||
ControllerBindingWidget_Base* ControllerBindingWidget_GunCon::createInstance(ControllerBindingWidget* parent)
|
||||
{
|
||||
return new ControllerBindingWidget_GunCon(parent);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
256
src/duckstation-qt/controllerbindingwidgets.h
Normal file
256
src/duckstation-qt/controllerbindingwidgets.h
Normal file
@@ -0,0 +1,256 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/controller.h"
|
||||
#include "core/settings.h"
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "ui_controllerbindingwidget.h"
|
||||
#include "ui_controllerbindingwidget_analog_controller.h"
|
||||
#include "ui_controllerbindingwidget_analog_joystick.h"
|
||||
#include "ui_controllerbindingwidget_digital_controller.h"
|
||||
#include "ui_controllerbindingwidget_guncon.h"
|
||||
#include "ui_controllerbindingwidget_negcon.h"
|
||||
#include "ui_controllermacrodialog.h"
|
||||
#include "ui_controllermacroeditwidget.h"
|
||||
|
||||
class QVBoxLayout;
|
||||
|
||||
class InputBindingWidget;
|
||||
class ControllerSettingsDialog;
|
||||
class ControllerMacroDialog;
|
||||
class ControllerMacroEditWidget;
|
||||
class ControllerBindingWidget_Base;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port);
|
||||
~ControllerBindingWidget();
|
||||
|
||||
QIcon getIcon() const;
|
||||
|
||||
ALWAYS_INLINE ControllerSettingsDialog* getDialog() const { return m_dialog; }
|
||||
ALWAYS_INLINE const std::string& getConfigSection() const { return m_config_section; }
|
||||
ALWAYS_INLINE ControllerType getControllerType() const { return m_controller_type; }
|
||||
ALWAYS_INLINE u32 getPortNumber() const { return m_port_number; }
|
||||
|
||||
private Q_SLOTS:
|
||||
void onTypeChanged();
|
||||
void onAutomaticBindingClicked();
|
||||
void onClearBindingsClicked();
|
||||
void onSettingsClicked();
|
||||
void onMacrosClicked();
|
||||
|
||||
private:
|
||||
void populateControllerTypes();
|
||||
void populateBindingWidget();
|
||||
void doDeviceAutomaticBinding(const QString& device);
|
||||
void saveAndRefresh();
|
||||
|
||||
Ui::ControllerBindingWidget m_ui;
|
||||
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
|
||||
std::string m_config_section;
|
||||
ControllerType m_controller_type;
|
||||
u32 m_port_number;
|
||||
|
||||
ControllerBindingWidget_Base* m_current_widget = nullptr;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerMacroDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerMacroDialog(ControllerBindingWidget* parent);
|
||||
~ControllerMacroDialog();
|
||||
|
||||
void updateListItem(u32 index);
|
||||
|
||||
private:
|
||||
static constexpr u32 NUM_MACROS = InputManager::NUM_MACRO_BUTTONS_PER_CONTROLLER;
|
||||
|
||||
void createWidgets(ControllerBindingWidget* parent);
|
||||
|
||||
Ui::ControllerMacroDialog m_ui;
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
std::array<ControllerMacroEditWidget*, NUM_MACROS> m_macros;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerMacroEditWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerMacroEditWidget(ControllerMacroDialog* parent, ControllerBindingWidget* bwidget, u32 index);
|
||||
~ControllerMacroEditWidget();
|
||||
|
||||
QString getSummary() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onSetFrequencyClicked();
|
||||
void updateBinds();
|
||||
|
||||
private:
|
||||
void modFrequency(s32 delta);
|
||||
void updateFrequency();
|
||||
void updateFrequencyText();
|
||||
|
||||
Ui::ControllerMacroEditWidget m_ui;
|
||||
|
||||
ControllerMacroDialog* m_parent;
|
||||
ControllerBindingWidget* m_bwidget;
|
||||
u32 m_index;
|
||||
|
||||
std::vector<const Controller::ControllerBindingInfo*> m_binds;
|
||||
u32 m_frequency = 0;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerCustomSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerCustomSettingsDialog(ControllerBindingWidget* parent);
|
||||
~ControllerCustomSettingsDialog();
|
||||
|
||||
int createSettingWidgets(ControllerBindingWidget* parent, QGridLayout* layout);
|
||||
|
||||
private Q_SLOTS:
|
||||
void restoreDefaults();
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_Base : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_Base(ControllerBindingWidget* parent);
|
||||
virtual ~ControllerBindingWidget_Base();
|
||||
|
||||
ALWAYS_INLINE ControllerSettingsDialog* getDialog() const
|
||||
{
|
||||
return static_cast<ControllerBindingWidget*>(parent())->getDialog();
|
||||
}
|
||||
ALWAYS_INLINE const std::string& getConfigSection() const
|
||||
{
|
||||
return static_cast<ControllerBindingWidget*>(parent())->getConfigSection();
|
||||
}
|
||||
ALWAYS_INLINE ControllerType getControllerType() const
|
||||
{
|
||||
return static_cast<ControllerBindingWidget*>(parent())->getControllerType();
|
||||
}
|
||||
ALWAYS_INLINE u32 getPortNumber() const { return static_cast<ControllerBindingWidget*>(parent())->getPortNumber(); }
|
||||
|
||||
virtual QIcon getIcon() const;
|
||||
|
||||
protected:
|
||||
void initBindingWidgets();
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_DigitalController final : public ControllerBindingWidget_Base
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_DigitalController(ControllerBindingWidget* parent);
|
||||
~ControllerBindingWidget_DigitalController();
|
||||
|
||||
QIcon getIcon() const override;
|
||||
|
||||
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
|
||||
|
||||
private:
|
||||
Ui::ControllerBindingWidget_DigitalController m_ui;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_AnalogController final : public ControllerBindingWidget_Base
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_AnalogController(ControllerBindingWidget* parent);
|
||||
~ControllerBindingWidget_AnalogController();
|
||||
|
||||
QIcon getIcon() const override;
|
||||
|
||||
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
|
||||
|
||||
private:
|
||||
Ui::ControllerBindingWidget_AnalogController m_ui;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_AnalogJoystick final : public ControllerBindingWidget_Base
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_AnalogJoystick(ControllerBindingWidget* parent);
|
||||
~ControllerBindingWidget_AnalogJoystick();
|
||||
|
||||
QIcon getIcon() const override;
|
||||
|
||||
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
|
||||
|
||||
private:
|
||||
Ui::ControllerBindingWidget_AnalogJoystick m_ui;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_NeGcon final : public ControllerBindingWidget_Base
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_NeGcon(ControllerBindingWidget* parent);
|
||||
~ControllerBindingWidget_NeGcon();
|
||||
|
||||
QIcon getIcon() const override;
|
||||
|
||||
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
|
||||
|
||||
private:
|
||||
Ui::ControllerBindingWidget_NeGcon m_ui;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ControllerBindingWidget_GunCon final : public ControllerBindingWidget_Base
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerBindingWidget_GunCon(ControllerBindingWidget* parent);
|
||||
~ControllerBindingWidget_GunCon();
|
||||
|
||||
QIcon getIcon() const override;
|
||||
|
||||
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
|
||||
|
||||
private:
|
||||
Ui::ControllerBindingWidget_GunCon m_ui;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
98
src/duckstation-qt/controllerglobalsettingswidget.cpp
Normal file
98
src/duckstation-qt/controllerglobalsettingswidget.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "controllerglobalsettingswidget.h"
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "controllersettingwidgetbinder.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingsInterface* sif = dialog->getProfileSettingsInterface();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources",
|
||||
"SDLControllerEnhancedMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false);
|
||||
#ifdef _WIN32
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDInputSource, "InputSources", "DInput", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableRawInput, "InputSources", "RawInput", false);
|
||||
#else
|
||||
m_ui.enableDInputSource->setEnabled(false);
|
||||
m_ui.enableXInputSource->setEnabled(false);
|
||||
m_ui.enableRawInput->setEnabled(false);
|
||||
#endif
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
|
||||
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
|
||||
Settings::DEFAULT_MULTITAP_MODE);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.pointerXInvert, "ControllerPorts",
|
||||
"PointerXInvert", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.pointerYInvert, "ControllerPorts",
|
||||
"PointerYInvert", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXScale, "ControllerPorts",
|
||||
"PointerXScale", 8.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYScale, "ControllerPorts",
|
||||
"PointerYScale", 8.0f);
|
||||
|
||||
if (dialog->isEditingProfile())
|
||||
{
|
||||
m_ui.useProfileHotkeyBindings->setChecked(
|
||||
m_dialog->getBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false));
|
||||
connect(m_ui.useProfileHotkeyBindings, &QCheckBox::stateChanged, this, [this](int new_state) {
|
||||
m_dialog->setBoolValue("ControllerPorts", "UseProfileHotkeyBindings", (new_state == Qt::Checked));
|
||||
emit bindingSetupChanged();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove profile options from the UI.
|
||||
m_ui.mainLayout->removeWidget(m_ui.profileSettings);
|
||||
m_ui.profileSettings->deleteLater();
|
||||
m_ui.profileSettings = nullptr;
|
||||
}
|
||||
|
||||
connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this,
|
||||
&ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
|
||||
connect(m_ui.multitapMode, &QComboBox::currentIndexChanged, this, [this]() { emit bindingSetupChanged(); });
|
||||
|
||||
connect(m_ui.pointerXScale, &QSlider::valueChanged, this,
|
||||
[this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerYScale, &QSlider::valueChanged, this,
|
||||
[this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerXScale->value()));
|
||||
m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerYScale->value()));
|
||||
|
||||
updateSDLOptionsEnabled();
|
||||
}
|
||||
|
||||
ControllerGlobalSettingsWidget::~ControllerGlobalSettingsWidget() = default;
|
||||
|
||||
void ControllerGlobalSettingsWidget::addDeviceToList(const QString& identifier, const QString& name)
|
||||
{
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
|
||||
item->setData(Qt::UserRole, identifier);
|
||||
m_ui.deviceList->addItem(item);
|
||||
}
|
||||
|
||||
void ControllerGlobalSettingsWidget::removeDeviceFromList(const QString& identifier)
|
||||
{
|
||||
const int count = m_ui.deviceList->count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
QListWidgetItem* item = m_ui.deviceList->item(i);
|
||||
if (item->data(Qt::UserRole) != identifier)
|
||||
continue;
|
||||
|
||||
delete m_ui.deviceList->takeItem(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled()
|
||||
{
|
||||
const bool enabled = m_ui.enableSDLSource->isChecked();
|
||||
m_ui.enableSDLEnhancedMode->setEnabled(enabled);
|
||||
}
|
||||
31
src/duckstation-qt/controllerglobalsettingswidget.h
Normal file
31
src/duckstation-qt/controllerglobalsettingswidget.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include <QtCore/QMap>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "ui_controllerglobalsettingswidget.h"
|
||||
|
||||
class ControllerSettingsDialog;
|
||||
|
||||
class ControllerGlobalSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog);
|
||||
~ControllerGlobalSettingsWidget();
|
||||
|
||||
void addDeviceToList(const QString& identifier, const QString& name);
|
||||
void removeDeviceFromList(const QString& identifier);
|
||||
|
||||
Q_SIGNALS:
|
||||
void bindingSetupChanged();
|
||||
|
||||
private:
|
||||
void updateSDLOptionsEnabled();
|
||||
|
||||
Ui::ControllerGlobalSettingsWidget m_ui;
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
};
|
||||
381
src/duckstation-qt/controllerglobalsettingswidget.ui
Normal file
381
src/duckstation-qt/controllerglobalsettingswidget.ui
Normal file
@@ -0,0 +1,381 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerGlobalSettingsWidget</class>
|
||||
<widget class="QWidget" name="ControllerGlobalSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>902</width>
|
||||
<height>677</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="mainLayout" columnstretch="1,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="4" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Controller Multitap</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Multitap Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="multitapMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enable on Port 1 Only</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enable on Port 2 Only</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enable on Ports 1 and 2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="dinputGroup">
|
||||
<property name="title">
|
||||
<string>DInput Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableDInputSource">
|
||||
<property name="text">
|
||||
<string>Enable DInput Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="sdlGroup">
|
||||
<property name="title">
|
||||
<string>SDL Input Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>The SDL input source supports most controllers, and provides advanced functionality for DualShock 4 / DualSense pads in Bluetooth mode (Vibration / LED Control).</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableSDLSource">
|
||||
<property name="text">
|
||||
<string>Enable SDL Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="enableSDLEnhancedMode">
|
||||
<property name="text">
|
||||
<string>DualShock 4 / DualSense Enhanced Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="7">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Detected Devices</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QListWidget" name="deviceList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Mouse/Pointer Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="pointerXScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="pointerXScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QCheckBox" name="pointerYInvert">
|
||||
<property name="text">
|
||||
<string>Invert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSlider" name="pointerYScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="pointerYScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QCheckBox" name="pointerXInvert">
|
||||
<property name="text">
|
||||
<string>Invert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Vertical Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Horizontal Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="enableMouseMapping">
|
||||
<property name="text">
|
||||
<string>Enable Mouse Mapping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="QCheckBox" name="enableRawInput">
|
||||
<property name="text">
|
||||
<string>Use Raw Input</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="xinputGroup">
|
||||
<property name="title">
|
||||
<string>XInput Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableXInputSource">
|
||||
<property name="text">
|
||||
<string>Enable XInput Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QGroupBox" name="profileSettings">
|
||||
<property name="title">
|
||||
<string>Profile Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="useProfileHotkeyBindings">
|
||||
<property name="text">
|
||||
<string>Use Per-Profile Hotkeys</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
71
src/duckstation-qt/controllermacrodialog.ui
Normal file
71
src/duckstation-qt/controllermacrodialog.ui
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerMacroDialog</class>
|
||||
<widget class="QDialog" name="ControllerMacroDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>799</width>
|
||||
<height>493</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="portList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QStackedWidget" name="container"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
157
src/duckstation-qt/controllermacroeditwidget.ui
Normal file
157
src/duckstation-qt/controllermacroeditwidget.ui
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerMacroEditWidget</class>
|
||||
<widget class="QWidget" name="ControllerMacroEditWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>595</width>
|
||||
<height>473</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Binds/Buttons</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Select the buttons which you want to trigger with this macro. All buttons are activated concurrently.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QListWidget" name="bindList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Trigger</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Select the trigger to activate this macro. This can be a single button, or combination of buttons (chord). Shift-click for multiple triggers.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="InputBindingWidget" name="trigger">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Frequency</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="frequencyText">
|
||||
<property name="text">
|
||||
<string>Macro will toggle every N frames.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setFrequency">
|
||||
<property name="text">
|
||||
<string>Set...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="increaseFrequency">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="arrowType">
|
||||
<enum>Qt::UpArrow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="decreateFrequency">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="arrowType">
|
||||
<enum>Qt::DownArrow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>InputBindingWidget</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>inputbindingwidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
484
src/duckstation-qt/controllersettingsdialog.cpp
Normal file
484
src/duckstation-qt/controllersettingsdialog.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
#include "controllerbindingwidgets.h"
|
||||
#include "controllerglobalsettingswidget.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "hotkeysettingswidget.h"
|
||||
#include "qthost.h"
|
||||
#include "util/ini_settings_interface.h"
|
||||
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QTextEdit>
|
||||
#include <array>
|
||||
|
||||
static constexpr const std::array<char, 4> s_mtap_slot_names = {{'A', 'B', 'C', 'D'}};
|
||||
|
||||
ControllerSettingsDialog::ControllerSettingsDialog(QWidget* parent /* = nullptr */) : QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
refreshProfileList();
|
||||
createWidgets();
|
||||
|
||||
m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this,
|
||||
&ControllerSettingsDialog::onCategoryCurrentRowChanged);
|
||||
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
|
||||
&ControllerSettingsDialog::onCurrentProfileChanged);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsDialog::close);
|
||||
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onNewProfileClicked);
|
||||
connect(m_ui.loadProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onLoadProfileClicked);
|
||||
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onDeleteProfileClicked);
|
||||
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsDialog::onRestoreDefaultsClicked);
|
||||
|
||||
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this,
|
||||
&ControllerSettingsDialog::onInputDevicesEnumerated);
|
||||
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsDialog::onInputDeviceConnected);
|
||||
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this,
|
||||
&ControllerSettingsDialog::onInputDeviceDisconnected);
|
||||
connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this,
|
||||
&ControllerSettingsDialog::onVibrationMotorsEnumerated);
|
||||
|
||||
// trigger a device enumeration to populate the device list
|
||||
g_emu_thread->enumerateInputDevices();
|
||||
g_emu_thread->enumerateVibrationMotors();
|
||||
}
|
||||
|
||||
ControllerSettingsDialog::~ControllerSettingsDialog() = default;
|
||||
|
||||
void ControllerSettingsDialog::setCategory(Category category)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case Category::GlobalSettings:
|
||||
m_ui.settingsCategory->setCurrentRow(0);
|
||||
break;
|
||||
|
||||
// TODO: These will need to take multitap into consideration in the future.
|
||||
case Category::FirstControllerSettings:
|
||||
m_ui.settingsCategory->setCurrentRow(1);
|
||||
break;
|
||||
|
||||
case Category::HotkeySettings:
|
||||
m_ui.settingsCategory->setCurrentRow(3);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onCategoryCurrentRowChanged(int row)
|
||||
{
|
||||
m_ui.settingsContainer->setCurrentIndex(row);
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onCurrentProfileChanged(int index)
|
||||
{
|
||||
switchProfile((index == 0) ? 0 : m_ui.currentProfile->itemText(index));
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onNewProfileClicked()
|
||||
{
|
||||
const QString profile_name(
|
||||
QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:")));
|
||||
if (profile_name.isEmpty())
|
||||
return;
|
||||
|
||||
std::string profile_path(System::GetInputProfilePath(profile_name.toStdString()));
|
||||
if (FileSystem::FileExists(profile_path.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("A profile with the name '%1' already exists.").arg(profile_name));
|
||||
return;
|
||||
}
|
||||
|
||||
const int res = QMessageBox::question(this, tr("Create Input Profile"),
|
||||
tr("Do you want to copy all bindings from the currently-selected profile to "
|
||||
"the new profile? Selecting No will create a completely empty profile."),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
||||
if (res == QMessageBox::Cancel)
|
||||
return;
|
||||
|
||||
INISettingsInterface temp_si(std::move(profile_path));
|
||||
if (res == QMessageBox::Yes)
|
||||
{
|
||||
// copy from global or the current profile
|
||||
if (!m_profile_interface)
|
||||
{
|
||||
// from global
|
||||
auto lock = Host::GetSettingsLock();
|
||||
InputManager::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// from profile
|
||||
const bool copy_hotkey_bindings = m_profile_interface->GetBoolValue("Pad", "UseProfileHotkeyBindings", false);
|
||||
temp_si.SetBoolValue("Pad", "UseProfileHotkeyBindings", copy_hotkey_bindings);
|
||||
InputManager::CopyConfiguration(&temp_si, *m_profile_interface, true, true, copy_hotkey_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
if (!temp_si.Save())
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetFileName())));
|
||||
return;
|
||||
}
|
||||
|
||||
refreshProfileList();
|
||||
switchProfile(profile_name);
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onLoadProfileClicked()
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Load Input Profile"),
|
||||
tr("Are you sure you want to load the input profile named '%1'?\n\n"
|
||||
"All current global bindings will be removed, and the profile bindings loaded.\n\n"
|
||||
"You cannot undo this action.")
|
||||
.arg(m_profile_name)) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = Host::GetSettingsLock();
|
||||
InputManager::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true, false);
|
||||
QtHost::QueueSettingsSave();
|
||||
}
|
||||
g_emu_thread->applySettings();
|
||||
|
||||
// make it visible
|
||||
switchProfile({});
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onDeleteProfileClicked()
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Delete Input Profile"),
|
||||
tr("Are you sure you want to delete the input profile named '%1'?\n\n"
|
||||
"You cannot undo this action.")
|
||||
.arg(m_profile_name)) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string profile_path(System::GetInputProfilePath(m_profile_name.toStdString()));
|
||||
if (!FileSystem::DeleteFile(profile_path.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to delete '%1'.").arg(QString::fromStdString(profile_path)));
|
||||
return;
|
||||
}
|
||||
|
||||
// switch back to global
|
||||
refreshProfileList();
|
||||
switchProfile({});
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onRestoreDefaultsClicked()
|
||||
{
|
||||
if (QMessageBox::question(
|
||||
this, tr("Restore Defaults"),
|
||||
tr("Are you sure you want to restore the default controller configuration?\n\n"
|
||||
"All shared bindings and configuration will be lost, but your input profiles will remain.\n\n"
|
||||
"You cannot undo this action.")) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// actually restore it
|
||||
g_emu_thread->setDefaultSettings(false, true);
|
||||
|
||||
// reload all settings
|
||||
switchProfile({});
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices)
|
||||
{
|
||||
m_device_list = devices;
|
||||
for (const QPair<QString, QString>& device : devices)
|
||||
m_global_settings->addDeviceToList(device.first, device.second);
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onInputDeviceConnected(const QString& identifier, const QString& device_name)
|
||||
{
|
||||
m_device_list.emplace_back(identifier, device_name);
|
||||
m_global_settings->addDeviceToList(identifier, device_name);
|
||||
g_emu_thread->enumerateVibrationMotors();
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onInputDeviceDisconnected(const QString& identifier)
|
||||
{
|
||||
for (auto iter = m_device_list.begin(); iter != m_device_list.end(); ++iter)
|
||||
{
|
||||
if (iter->first == identifier)
|
||||
{
|
||||
m_device_list.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_global_settings->removeDeviceFromList(identifier);
|
||||
g_emu_thread->enumerateVibrationMotors();
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors)
|
||||
{
|
||||
m_vibration_motors.clear();
|
||||
m_vibration_motors.reserve(motors.size());
|
||||
|
||||
for (const InputBindingKey key : motors)
|
||||
{
|
||||
const std::string key_str(InputManager::ConvertInputBindingKeyToString(key));
|
||||
if (!key_str.empty())
|
||||
m_vibration_motors.push_back(QString::fromStdString(key_str));
|
||||
}
|
||||
}
|
||||
|
||||
bool ControllerSettingsDialog::getBoolValue(const char* section, const char* key, bool default_value) const
|
||||
{
|
||||
if (m_profile_interface)
|
||||
return m_profile_interface->GetBoolValue(section, key, default_value);
|
||||
else
|
||||
return Host::GetBaseBoolSettingValue(section, key, default_value);
|
||||
}
|
||||
|
||||
bool ControllerSettingsDialog::getIntValue(const char* section, const char* key, s32 default_value) const
|
||||
{
|
||||
if (m_profile_interface)
|
||||
return m_profile_interface->GetIntValue(section, key, default_value);
|
||||
else
|
||||
return Host::GetBaseIntSettingValue(section, key, default_value);
|
||||
}
|
||||
|
||||
std::string ControllerSettingsDialog::getStringValue(const char* section, const char* key,
|
||||
const char* default_value) const
|
||||
{
|
||||
std::string value;
|
||||
if (m_profile_interface)
|
||||
value = m_profile_interface->GetStringValue(section, key, default_value);
|
||||
else
|
||||
value = Host::GetBaseStringSettingValue(section, key, default_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::setBoolValue(const char* section, const char* key, bool value)
|
||||
{
|
||||
if (m_profile_interface)
|
||||
{
|
||||
m_profile_interface->SetBoolValue(section, key, value);
|
||||
m_profile_interface->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseBoolSettingValue(section, key, value);
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::setIntValue(const char* section, const char* key, s32 value)
|
||||
{
|
||||
if (m_profile_interface)
|
||||
{
|
||||
m_profile_interface->SetIntValue(section, key, value);
|
||||
m_profile_interface->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseIntSettingValue(section, key, value);
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::setStringValue(const char* section, const char* key, const char* value)
|
||||
{
|
||||
if (m_profile_interface)
|
||||
{
|
||||
m_profile_interface->SetStringValue(section, key, value);
|
||||
m_profile_interface->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseStringSettingValue(section, key, value);
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::clearSettingValue(const char* section, const char* key)
|
||||
{
|
||||
if (m_profile_interface)
|
||||
{
|
||||
m_profile_interface->DeleteValue(section, key);
|
||||
m_profile_interface->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::DeleteBaseSettingValue(section, key);
|
||||
g_emu_thread->applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::createWidgets()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.settingsContainer);
|
||||
QSignalBlocker sb2(m_ui.settingsCategory);
|
||||
|
||||
while (m_ui.settingsContainer->count() > 0)
|
||||
{
|
||||
QWidget* widget = m_ui.settingsContainer->widget(m_ui.settingsContainer->count() - 1);
|
||||
m_ui.settingsContainer->removeWidget(widget);
|
||||
widget->deleteLater();
|
||||
}
|
||||
|
||||
m_ui.settingsCategory->clear();
|
||||
|
||||
m_global_settings = nullptr;
|
||||
m_hotkey_settings = nullptr;
|
||||
|
||||
{
|
||||
// global settings
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setText(tr("Global Settings"));
|
||||
item->setIcon(QIcon::fromTheme(QStringLiteral("settings-3-line")));
|
||||
m_ui.settingsCategory->addItem(item);
|
||||
m_ui.settingsCategory->setCurrentRow(0);
|
||||
m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
|
||||
m_ui.settingsContainer->addWidget(m_global_settings);
|
||||
connect(m_global_settings, &ControllerGlobalSettingsWidget::bindingSetupChanged, this,
|
||||
&ControllerSettingsDialog::createWidgets);
|
||||
for (const QPair<QString, QString>& dev : m_device_list)
|
||||
m_global_settings->addDeviceToList(dev.first, dev.second);
|
||||
}
|
||||
|
||||
// load mtap settings
|
||||
const MultitapMode mtap_mode =
|
||||
Settings::ParseMultitapModeName(
|
||||
getStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE))
|
||||
.c_str())
|
||||
.value_or(Settings::DEFAULT_MULTITAP_MODE);
|
||||
const std::array<bool, 2> mtap_enabled = {
|
||||
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
|
||||
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
|
||||
|
||||
// we reorder things a little to make it look less silly for mtap
|
||||
static constexpr const std::array<u32, MAX_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
|
||||
|
||||
// create the ports
|
||||
for (u32 global_slot : mtap_port_order)
|
||||
{
|
||||
const bool is_mtap_port = Controller::PadIsMultitapSlot(global_slot);
|
||||
const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);
|
||||
if (is_mtap_port && !mtap_enabled[port])
|
||||
continue;
|
||||
|
||||
m_port_bindings[global_slot] = new ControllerBindingWidget(m_ui.settingsContainer, this, global_slot);
|
||||
m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
|
||||
|
||||
const Controller::ControllerInfo* ci =
|
||||
Controller::GetControllerInfo(m_port_bindings[global_slot]->getControllerType());
|
||||
const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown"));
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setText(mtap_enabled[port] ?
|
||||
(tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
|
||||
tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
|
||||
item->setIcon(m_port_bindings[global_slot]->getIcon());
|
||||
item->setData(Qt::UserRole, QVariant(global_slot));
|
||||
m_ui.settingsCategory->addItem(item);
|
||||
}
|
||||
|
||||
// only add hotkeys if we're editing global settings
|
||||
if (!m_profile_interface || m_profile_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false))
|
||||
{
|
||||
QListWidgetItem* item = new QListWidgetItem();
|
||||
item->setText(tr("Hotkeys"));
|
||||
item->setIcon(QIcon::fromTheme(QStringLiteral("keyboard-line")));
|
||||
m_ui.settingsCategory->addItem(item);
|
||||
m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
|
||||
m_ui.settingsContainer->addWidget(m_hotkey_settings);
|
||||
}
|
||||
|
||||
m_ui.loadProfile->setEnabled(isEditingProfile());
|
||||
m_ui.deleteProfile->setEnabled(isEditingProfile());
|
||||
m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings());
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::updateListDescription(u32 global_slot, ControllerBindingWidget* widget)
|
||||
{
|
||||
for (int i = 0; i < m_ui.settingsCategory->count(); i++)
|
||||
{
|
||||
QListWidgetItem* item = m_ui.settingsCategory->item(i);
|
||||
const QVariant item_data(item->data(Qt::UserRole));
|
||||
bool is_ok;
|
||||
if (item_data.toUInt(&is_ok) == global_slot && is_ok)
|
||||
{
|
||||
// const bool is_mtap_port = Controller::PadIsMultitapSlot(global_slot);
|
||||
const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);
|
||||
const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false);
|
||||
|
||||
const Controller::ControllerInfo* ci = Controller::GetControllerInfo(widget->getControllerType());
|
||||
const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown"));
|
||||
|
||||
item->setText(mtap_enabled ?
|
||||
(tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
|
||||
tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
|
||||
item->setIcon(widget->getIcon());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ControllerSettingsDialog::refreshProfileList()
|
||||
{
|
||||
const std::vector<std::string> names(InputManager::GetInputProfileNames());
|
||||
|
||||
QSignalBlocker sb(m_ui.currentProfile);
|
||||
m_ui.currentProfile->clear();
|
||||
m_ui.currentProfile->addItem(tr("Shared"));
|
||||
if (isEditingGlobalSettings())
|
||||
m_ui.currentProfile->setCurrentIndex(0);
|
||||
|
||||
for (const std::string& name : names)
|
||||
{
|
||||
const QString qname(QString::fromStdString(name));
|
||||
m_ui.currentProfile->addItem(qname);
|
||||
if (qname == m_profile_name)
|
||||
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsDialog::switchProfile(const QString& name)
|
||||
{
|
||||
QSignalBlocker sb(m_ui.currentProfile);
|
||||
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
std::string path(System::GetInputProfilePath(name.toStdString()));
|
||||
if (!FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name));
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<INISettingsInterface> sif(std::make_unique<INISettingsInterface>(std::move(path)));
|
||||
sif->Load();
|
||||
m_profile_interface = std::move(sif);
|
||||
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_profile_interface.reset();
|
||||
m_ui.currentProfile->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
m_profile_name = name;
|
||||
createWidgets();
|
||||
}
|
||||
96
src/duckstation-qt/controllersettingsdialog.h
Normal file
96
src/duckstation-qt/controllersettingsdialog.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "ui_controllersettingsdialog.h"
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QPair>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
class ControllerGlobalSettingsWidget;
|
||||
class ControllerBindingWidget;
|
||||
class HotkeySettingsWidget;
|
||||
|
||||
class SettingsInterface;
|
||||
|
||||
class ControllerSettingsDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Category
|
||||
{
|
||||
GlobalSettings,
|
||||
FirstControllerSettings,
|
||||
HotkeySettings,
|
||||
Count
|
||||
};
|
||||
|
||||
enum : u32
|
||||
{
|
||||
MAX_PORTS = 8
|
||||
};
|
||||
|
||||
ControllerSettingsDialog(QWidget* parent = nullptr);
|
||||
~ControllerSettingsDialog();
|
||||
|
||||
ALWAYS_INLINE HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; }
|
||||
|
||||
ALWAYS_INLINE const QList<QPair<QString, QString>>& getDeviceList() const { return m_device_list; }
|
||||
ALWAYS_INLINE const QStringList& getVibrationMotors() const { return m_vibration_motors; }
|
||||
|
||||
ALWAYS_INLINE bool isEditingGlobalSettings() const { return m_profile_name.isEmpty(); }
|
||||
ALWAYS_INLINE bool isEditingProfile() const { return !m_profile_name.isEmpty(); }
|
||||
ALWAYS_INLINE SettingsInterface* getProfileSettingsInterface() { return m_profile_interface.get(); }
|
||||
|
||||
void updateListDescription(u32 global_slot, ControllerBindingWidget* widget);
|
||||
|
||||
// Helper functions for updating setting values globally or in the profile.
|
||||
bool getBoolValue(const char* section, const char* key, bool default_value) const;
|
||||
bool getIntValue(const char* section, const char* key, s32 default_value) const;
|
||||
std::string getStringValue(const char* section, const char* key, const char* default_value) const;
|
||||
void setBoolValue(const char* section, const char* key, bool value);
|
||||
void setIntValue(const char* section, const char* key, s32 value);
|
||||
void setStringValue(const char* section, const char* key, const char* value);
|
||||
void clearSettingValue(const char* section, const char* key);
|
||||
|
||||
Q_SIGNALS:
|
||||
void inputProfileSwitched();
|
||||
|
||||
public Q_SLOTS:
|
||||
void setCategory(Category category);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onCategoryCurrentRowChanged(int row);
|
||||
void onCurrentProfileChanged(int index);
|
||||
void onNewProfileClicked();
|
||||
void onLoadProfileClicked();
|
||||
void onDeleteProfileClicked();
|
||||
void onRestoreDefaultsClicked();
|
||||
|
||||
void onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices);
|
||||
void onInputDeviceConnected(const QString& identifier, const QString& device_name);
|
||||
void onInputDeviceDisconnected(const QString& identifier);
|
||||
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
|
||||
|
||||
void createWidgets();
|
||||
|
||||
private:
|
||||
void refreshProfileList();
|
||||
void switchProfile(const QString& name);
|
||||
|
||||
Ui::ControllerSettingsDialog m_ui;
|
||||
|
||||
ControllerGlobalSettingsWidget* m_global_settings = nullptr;
|
||||
std::array<ControllerBindingWidget*, MAX_PORTS> m_port_bindings{};
|
||||
HotkeySettingsWidget* m_hotkey_settings = nullptr;
|
||||
|
||||
QList<QPair<QString, QString>> m_device_list;
|
||||
QStringList m_vibration_motors;
|
||||
|
||||
QString m_profile_name;
|
||||
std::unique_ptr<SettingsInterface> m_profile_interface;
|
||||
};
|
||||
137
src/duckstation-qt/controllersettingsdialog.ui
Normal file
137
src/duckstation-qt/controllersettingsdialog.ui
Normal file
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerSettingsDialog</class>
|
||||
<widget class="QDialog" name="ControllerSettingsDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1276</width>
|
||||
<height>672</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Controller Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="settingsCategory">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QStackedWidget" name="settingsContainer">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1100</width>
|
||||
<height>620</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Profile:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="currentProfile"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="newProfile">
|
||||
<property name="text">
|
||||
<string>New Profile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="file-add-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loadProfile">
|
||||
<property name="text">
|
||||
<string>Load Profile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="folder-open-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteProfile">
|
||||
<property name="text">
|
||||
<string>Delete Profile</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="file-reduce-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreDefaults">
|
||||
<property name="text">
|
||||
<string>Restore Defaults</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="restart-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,597 +0,0 @@
|
||||
#include "controllersettingswidget.h"
|
||||
#include "collapsiblewidget.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/settings.h"
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QCursor>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
static constexpr char INPUT_PROFILE_FILTER[] = "Input Profiles (*.ini)";
|
||||
|
||||
ControllerSettingsWidget::ControllerSettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
{
|
||||
createUi();
|
||||
|
||||
connect(host_interface, &QtHostInterface::inputProfileLoaded, this, &ControllerSettingsWidget::onProfileLoaded);
|
||||
}
|
||||
|
||||
ControllerSettingsWidget::~ControllerSettingsWidget() = default;
|
||||
|
||||
MultitapMode ControllerSettingsWidget::getMultitapMode()
|
||||
{
|
||||
return Settings::ParseMultitapModeName(
|
||||
QtHostInterface::GetInstance()
|
||||
->GetStringSettingValue("ControllerPorts", "MultitapMode",
|
||||
Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE))
|
||||
.c_str())
|
||||
.value_or(Settings::DEFAULT_MULTITAP_MODE);
|
||||
}
|
||||
|
||||
QString ControllerSettingsWidget::getTabTitleForPort(u32 index, MultitapMode mode) const
|
||||
{
|
||||
constexpr u32 NUM_PORTS_PER_MULTITAP = 4;
|
||||
|
||||
u32 port_number, subport_number;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MultitapMode::Port1Only:
|
||||
{
|
||||
if (index == NUM_PORTS_PER_MULTITAP)
|
||||
return tr("Port %1").arg((index / NUM_PORTS_PER_MULTITAP) + 1);
|
||||
else if (index > NUM_PORTS_PER_MULTITAP)
|
||||
return QString();
|
||||
|
||||
port_number = 0;
|
||||
subport_number = index;
|
||||
}
|
||||
break;
|
||||
|
||||
case MultitapMode::Port2Only:
|
||||
{
|
||||
if (index == 0)
|
||||
return tr("Port %1").arg(index + 1);
|
||||
else if (index > NUM_PORTS_PER_MULTITAP)
|
||||
return QString();
|
||||
|
||||
port_number = 1;
|
||||
subport_number = (index - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case MultitapMode::BothPorts:
|
||||
{
|
||||
port_number = index / NUM_PORTS_PER_MULTITAP;
|
||||
subport_number = (index % NUM_PORTS_PER_MULTITAP);
|
||||
}
|
||||
break;
|
||||
|
||||
case MultitapMode::Disabled:
|
||||
default:
|
||||
{
|
||||
if (index >= (NUM_CONTROLLER_AND_CARD_PORTS / NUM_PORTS_PER_MULTITAP))
|
||||
return QString();
|
||||
|
||||
return tr("Port %1").arg(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return tr("Port %1%2").arg(port_number + 1).arg(QChar::fromLatin1('A' + subport_number));
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::createUi()
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
const MultitapMode multitap_mode = getMultitapMode();
|
||||
m_tab_widget = new QTabWidget(this);
|
||||
for (int i = 0; i < static_cast<int>(m_port_ui.size()); i++)
|
||||
createPortSettingsUi(i, &m_port_ui[i], multitap_mode);
|
||||
|
||||
layout->addWidget(m_tab_widget, 0, 0, 1, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::updateMultitapControllerTitles()
|
||||
{
|
||||
m_tab_widget->clear();
|
||||
|
||||
const MultitapMode multitap_mode = getMultitapMode();
|
||||
for (int i = 0; i < static_cast<int>(m_port_ui.size()); i++)
|
||||
createPortSettingsUi(i, &m_port_ui[i], multitap_mode);
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::onProfileLoaded()
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(m_port_ui.size()); i++)
|
||||
{
|
||||
if (!m_port_ui[i].widget)
|
||||
continue;
|
||||
|
||||
ControllerType ctype = Settings::ParseControllerTypeName(
|
||||
m_host_interface
|
||||
->GetStringSettingValue(QStringLiteral("Controller%1").arg(i + 1).toStdString().c_str(),
|
||||
QStringLiteral("Type").toStdString().c_str())
|
||||
.c_str())
|
||||
.value_or(ControllerType::None);
|
||||
|
||||
{
|
||||
QSignalBlocker blocker(m_port_ui[i].controller_type);
|
||||
m_port_ui[i].controller_type->setCurrentIndex(static_cast<int>(ctype));
|
||||
}
|
||||
createPortBindingSettingsUi(i, &m_port_ui[i], ctype);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::reloadBindingButtons()
|
||||
{
|
||||
for (PortSettingsUI& ui : m_port_ui)
|
||||
{
|
||||
InputBindingWidget* widget = ui.first_button;
|
||||
while (widget)
|
||||
{
|
||||
widget->reloadBinding();
|
||||
widget = widget->getNextWidget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui, MultitapMode multitap_mode)
|
||||
{
|
||||
if (ui->widget)
|
||||
{
|
||||
delete ui->widget;
|
||||
*ui = {};
|
||||
}
|
||||
|
||||
const QString tab_title(getTabTitleForPort(index, multitap_mode));
|
||||
if (tab_title.isEmpty())
|
||||
return;
|
||||
|
||||
ui->widget = new QWidget(m_tab_widget);
|
||||
ui->layout = new QVBoxLayout(ui->widget);
|
||||
|
||||
QHBoxLayout* hbox = new QHBoxLayout();
|
||||
hbox->addWidget(new QLabel(tr("Controller Type:"), ui->widget));
|
||||
hbox->addSpacing(8);
|
||||
|
||||
ui->controller_type = new QComboBox(ui->widget);
|
||||
for (int i = 0; i < static_cast<int>(ControllerType::Count); i++)
|
||||
{
|
||||
ui->controller_type->addItem(
|
||||
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
|
||||
}
|
||||
ControllerType ctype =
|
||||
Settings::ParseControllerTypeName(
|
||||
m_host_interface->GetStringSettingValue(TinyString::FromFormat("Controller%d", index + 1), "Type").c_str())
|
||||
.value_or(ControllerType::None);
|
||||
ui->controller_type->setCurrentIndex(static_cast<int>(ctype));
|
||||
connect(ui->controller_type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this, index]() { onControllerTypeChanged(index); });
|
||||
|
||||
hbox->addWidget(ui->controller_type, 1);
|
||||
ui->layout->addLayout(hbox);
|
||||
|
||||
ui->bindings_scroll_area = new QScrollArea(ui->widget);
|
||||
ui->bindings_scroll_area->setWidgetResizable(true);
|
||||
ui->bindings_scroll_area->setFrameShape(QFrame::StyledPanel);
|
||||
ui->bindings_scroll_area->setFrameShadow(QFrame::Plain);
|
||||
|
||||
createPortBindingSettingsUi(index, ui, ctype);
|
||||
|
||||
ui->bindings_scroll_area->setWidget(ui->bindings_container);
|
||||
ui->layout->addWidget(ui->bindings_scroll_area, 1);
|
||||
|
||||
hbox = new QHBoxLayout();
|
||||
QPushButton* load_profile_button = new QPushButton(tr("Load Profile"), ui->widget);
|
||||
connect(load_profile_button, &QPushButton::clicked, this, &ControllerSettingsWidget::onLoadProfileClicked);
|
||||
hbox->addWidget(load_profile_button);
|
||||
|
||||
QPushButton* save_profile_button = new QPushButton(tr("Save Profile"), ui->widget);
|
||||
connect(save_profile_button, &QPushButton::clicked, this, &ControllerSettingsWidget::onSaveProfileClicked);
|
||||
hbox->addWidget(save_profile_button);
|
||||
|
||||
hbox->addStretch(1);
|
||||
|
||||
QPushButton* clear_all_button = new QPushButton(tr("Clear All"), ui->widget);
|
||||
clear_all_button->connect(clear_all_button, &QPushButton::clicked, [this, index]() {
|
||||
if (QMessageBox::question(this, tr("Clear Bindings"),
|
||||
tr("Are you sure you want to clear all bound controls? This can not be reversed.")) !=
|
||||
QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputBindingWidget* widget = m_port_ui[index].first_button;
|
||||
while (widget)
|
||||
{
|
||||
widget->clearBinding();
|
||||
widget = widget->getNextWidget();
|
||||
}
|
||||
});
|
||||
|
||||
QPushButton* rebind_all_button = new QPushButton(tr("Rebind All"), ui->widget);
|
||||
rebind_all_button->connect(rebind_all_button, &QPushButton::clicked, [this, index]() {
|
||||
if (QMessageBox::question(this, tr("Rebind All"),
|
||||
tr("Are you sure you want to rebind all controls? All currently-bound controls will be "
|
||||
"irreversibly cleared. Rebinding will begin after confirmation.")) != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputBindingWidget* widget = m_port_ui[index].first_button;
|
||||
while (widget)
|
||||
{
|
||||
widget->clearBinding();
|
||||
widget = widget->getNextWidget();
|
||||
}
|
||||
|
||||
if (m_port_ui[index].first_button)
|
||||
m_port_ui[index].first_button->beginRebindAll();
|
||||
});
|
||||
|
||||
hbox->addWidget(clear_all_button);
|
||||
hbox->addWidget(rebind_all_button);
|
||||
|
||||
ui->layout->addLayout(hbox);
|
||||
|
||||
ui->widget->setLayout(ui->layout);
|
||||
|
||||
m_tab_widget->addTab(ui->widget, tab_title);
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype)
|
||||
{
|
||||
ui->bindings_container = new QWidget(ui->widget);
|
||||
|
||||
QGridLayout* layout = new QGridLayout(ui->bindings_container);
|
||||
const auto buttons = Controller::GetButtonNames(ctype);
|
||||
const char* cname = Settings::GetControllerTypeName(ctype);
|
||||
|
||||
InputBindingWidget* first_button = nullptr;
|
||||
InputBindingWidget* last_button = nullptr;
|
||||
|
||||
int start_row = 0;
|
||||
if (!buttons.empty())
|
||||
{
|
||||
layout->addWidget(new QLabel(tr("Button Bindings:"), ui->bindings_container), start_row++, 0, 1, 4);
|
||||
|
||||
const int num_rows = (static_cast<int>(buttons.size()) + 1) / 2;
|
||||
int current_row = 0;
|
||||
int current_column = 0;
|
||||
for (const auto& [button_name, button_code] : buttons)
|
||||
{
|
||||
if (current_row == num_rows)
|
||||
{
|
||||
current_row = 0;
|
||||
current_column += 2;
|
||||
}
|
||||
|
||||
std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
|
||||
std::string key_name = StringUtil::StdStringFromFormat("Button%s", button_name.c_str());
|
||||
QLabel* label = new QLabel(qApp->translate(cname, button_name.c_str()), ui->bindings_container);
|
||||
InputButtonBindingWidget* button = new InputButtonBindingWidget(m_host_interface, std::move(section_name),
|
||||
std::move(key_name), ui->bindings_container);
|
||||
layout->addWidget(label, start_row + current_row, current_column);
|
||||
layout->addWidget(button, start_row + current_row, current_column + 1);
|
||||
|
||||
if (!first_button)
|
||||
first_button = button;
|
||||
if (last_button)
|
||||
last_button->setNextWidget(button);
|
||||
last_button = button;
|
||||
|
||||
current_row++;
|
||||
}
|
||||
|
||||
start_row += num_rows;
|
||||
}
|
||||
|
||||
const auto axises = Controller::GetAxisNames(ctype);
|
||||
if (!axises.empty())
|
||||
{
|
||||
layout->addWidget(QtUtils::CreateHorizontalLine(ui->bindings_container), start_row++, 0, 1, 4);
|
||||
layout->addWidget(new QLabel(tr("Axis Bindings:"), ui->bindings_container), start_row++, 0, 1, 4);
|
||||
|
||||
const int num_rows = (static_cast<int>(axises.size()) + 1) / 2;
|
||||
int current_row = 0;
|
||||
int current_column = 0;
|
||||
for (const auto& [axis_name, axis_code, axis_type] : axises)
|
||||
{
|
||||
if (current_row == num_rows)
|
||||
{
|
||||
current_row = 0;
|
||||
current_column += 2;
|
||||
}
|
||||
|
||||
std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
|
||||
std::string key_name = StringUtil::StdStringFromFormat("Axis%s", axis_name.c_str());
|
||||
QLabel* label = new QLabel(qApp->translate(cname, axis_name.c_str()), ui->bindings_container);
|
||||
InputAxisBindingWidget* button = new InputAxisBindingWidget(
|
||||
m_host_interface, std::move(section_name), std::move(key_name), axis_type, ui->bindings_container);
|
||||
layout->addWidget(label, start_row + current_row, current_column);
|
||||
layout->addWidget(button, start_row + current_row, current_column + 1);
|
||||
|
||||
if (!first_button)
|
||||
first_button = button;
|
||||
if (last_button)
|
||||
last_button->setNextWidget(button);
|
||||
last_button = button;
|
||||
|
||||
current_row++;
|
||||
}
|
||||
|
||||
start_row += num_rows;
|
||||
}
|
||||
|
||||
const u32 num_motors = Controller::GetVibrationMotorCount(ctype);
|
||||
if (num_motors > 0)
|
||||
{
|
||||
layout->addWidget(QtUtils::CreateHorizontalLine(ui->widget), start_row++, 0, 1, 4);
|
||||
|
||||
std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
|
||||
QLabel* label = new QLabel(tr("Rumble"), ui->bindings_container);
|
||||
InputRumbleBindingWidget* button =
|
||||
new InputRumbleBindingWidget(m_host_interface, std::move(section_name), "Rumble", ui->bindings_container);
|
||||
|
||||
layout->addWidget(label, start_row, 0);
|
||||
layout->addWidget(button, start_row, 1);
|
||||
|
||||
if (!first_button)
|
||||
first_button = button;
|
||||
if (last_button)
|
||||
last_button->setNextWidget(button);
|
||||
last_button = button;
|
||||
|
||||
start_row++;
|
||||
}
|
||||
|
||||
const Controller::SettingList settings = Controller::GetSettings(ctype);
|
||||
if (!settings.empty())
|
||||
{
|
||||
layout->addWidget(QtUtils::CreateHorizontalLine(ui->widget), start_row++, 0, 1, 4);
|
||||
|
||||
for (const SettingInfo& si : settings)
|
||||
{
|
||||
std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
|
||||
std::string key_name = si.key;
|
||||
const QString setting_tooltip = si.description ? qApp->translate(cname, si.description) : QString();
|
||||
|
||||
switch (si.type)
|
||||
{
|
||||
case SettingInfo::Type::Boolean:
|
||||
{
|
||||
QCheckBox* cb = new QCheckBox(qApp->translate(cname, si.visible_name), ui->bindings_container);
|
||||
cb->setToolTip(setting_tooltip);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, std::move(section_name),
|
||||
std::move(key_name), si.BooleanDefaultValue());
|
||||
layout->addWidget(cb, start_row, 0, 1, 4);
|
||||
start_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Integer:
|
||||
{
|
||||
QSpinBox* sb = new QSpinBox(ui->bindings_container);
|
||||
sb->setToolTip(setting_tooltip);
|
||||
sb->setMinimum(si.IntegerMinValue());
|
||||
sb->setMaximum(si.IntegerMaxValue());
|
||||
sb->setSingleStep(si.IntegerStepValue());
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, sb, std::move(section_name),
|
||||
std::move(key_name), si.IntegerDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0);
|
||||
layout->addWidget(sb, start_row, 1, 1, 3);
|
||||
start_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Float:
|
||||
{
|
||||
QDoubleSpinBox* sb = new QDoubleSpinBox(ui->bindings_container);
|
||||
sb->setToolTip(setting_tooltip);
|
||||
sb->setMinimum(si.FloatMinValue());
|
||||
sb->setMaximum(si.FloatMaxValue());
|
||||
sb->setSingleStep(si.FloatStepValue());
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, sb, std::move(section_name),
|
||||
std::move(key_name), si.FloatDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0);
|
||||
layout->addWidget(sb, start_row, 1, 1, 3);
|
||||
start_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::String:
|
||||
{
|
||||
QLineEdit* le = new QLineEdit(ui->bindings_container);
|
||||
le->setToolTip(setting_tooltip);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, le, std::move(section_name),
|
||||
std::move(key_name), si.StringDefaultValue());
|
||||
layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0);
|
||||
layout->addWidget(le, start_row, 1, 1, 3);
|
||||
start_row++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingInfo::Type::Path:
|
||||
{
|
||||
QLineEdit* le = new QLineEdit(ui->bindings_container);
|
||||
le->setToolTip(setting_tooltip);
|
||||
QPushButton* browse_button = new QPushButton(tr("Browse..."), ui->bindings_container);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, le, std::move(section_name),
|
||||
std::move(key_name), si.StringDefaultValue());
|
||||
connect(browse_button, &QPushButton::clicked, [this, le]() {
|
||||
QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
|
||||
if (!path.isEmpty())
|
||||
le->setText(path);
|
||||
});
|
||||
|
||||
QHBoxLayout* hbox = new QHBoxLayout();
|
||||
hbox->addWidget(le, 1);
|
||||
hbox->addWidget(browse_button);
|
||||
|
||||
layout->addWidget(new QLabel(qApp->translate(cname, si.visible_name), ui->bindings_container), start_row, 0);
|
||||
layout->addLayout(hbox, start_row, 1, 1, 3);
|
||||
start_row++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turbo/autofire
|
||||
if (ctype != ControllerType::None)
|
||||
{
|
||||
layout->addWidget(QtUtils::CreateHorizontalLine(ui->widget), start_row++, 0, 1, 4);
|
||||
|
||||
CollapsibleWidget* collapsible = new CollapsibleWidget(tr("Auto Fire Buttons"), 100, ui->bindings_container);
|
||||
QGridLayout* autofire_layout = new QGridLayout();
|
||||
autofire_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QVector<QPair<QString, QVariant>> option_list;
|
||||
option_list.push_back({});
|
||||
for (const auto& [button_name, button_code] : buttons)
|
||||
option_list.push_back({qApp->translate(cname, button_name.c_str()), QString::fromStdString(button_name)});
|
||||
|
||||
for (u32 autofire_index = 0; autofire_index < QtHostInterface::NUM_CONTROLLER_AUTOFIRE_BUTTONS; autofire_index++)
|
||||
{
|
||||
std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1);
|
||||
autofire_layout->addWidget(new QLabel(tr("Auto Fire %1").arg(autofire_index + 1), collapsible), autofire_index,
|
||||
0);
|
||||
QComboBox* button_cb = new QComboBox(collapsible);
|
||||
for (const auto& it : option_list)
|
||||
button_cb->addItem(it.first, it.second);
|
||||
autofire_layout->addWidget(button_cb, autofire_index, 1);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(
|
||||
m_host_interface, button_cb, section_name,
|
||||
StringUtil::StdStringFromFormat("AutoFire%uButton", autofire_index + 1));
|
||||
|
||||
InputButtonBindingWidget* binding_button = new InputButtonBindingWidget(
|
||||
m_host_interface, section_name, StringUtil::StdStringFromFormat("AutoFire%u", autofire_index + 1), collapsible);
|
||||
autofire_layout->addWidget(binding_button, autofire_index, 2);
|
||||
|
||||
QSpinBox* frequency = new QSpinBox(collapsible);
|
||||
frequency->setMinimum(1);
|
||||
frequency->setMaximum(255);
|
||||
frequency->setSuffix(tr(" Frames"));
|
||||
autofire_layout->addWidget(frequency, autofire_index, 3);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||
m_host_interface, frequency, std::move(section_name),
|
||||
StringUtil::StdStringFromFormat("AutoFire%uFrequency", autofire_index + 1),
|
||||
QtHostInterface::DEFAULT_AUTOFIRE_FREQUENCY);
|
||||
}
|
||||
|
||||
collapsible->getScrollArea()->setFrameStyle(QFrame::NoFrame);
|
||||
collapsible->setContentLayout(autofire_layout);
|
||||
layout->addWidget(collapsible, start_row, 0, 1, 4);
|
||||
|
||||
start_row++;
|
||||
}
|
||||
|
||||
// dummy row to fill remaining space
|
||||
layout->addWidget(new QWidget(ui->bindings_container), start_row, 0, 1, 4);
|
||||
layout->setRowStretch(start_row, 1);
|
||||
|
||||
ui->bindings_scroll_area->setWidget(ui->bindings_container);
|
||||
ui->first_button = first_button;
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::onControllerTypeChanged(int index)
|
||||
{
|
||||
const int type_index = m_port_ui[index].controller_type->currentIndex();
|
||||
if (type_index < 0 || type_index >= static_cast<int>(ControllerType::Count))
|
||||
return;
|
||||
|
||||
m_host_interface->SetStringSettingValue(TinyString::FromFormat("Controller%d", index + 1), "Type",
|
||||
Settings::GetControllerTypeName(static_cast<ControllerType>(type_index)));
|
||||
|
||||
m_host_interface->applySettings();
|
||||
createPortBindingSettingsUi(index, &m_port_ui[index], static_cast<ControllerType>(type_index));
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::onLoadProfileClicked()
|
||||
{
|
||||
const auto profile_names = m_host_interface->getInputProfileList();
|
||||
|
||||
QMenu menu;
|
||||
|
||||
QAction* browse = menu.addAction(tr("Browse..."));
|
||||
connect(browse, &QAction::triggered, [this]() {
|
||||
QString path =
|
||||
QFileDialog::getOpenFileName(this, tr("Select path to input profile ini"), QString(), tr(INPUT_PROFILE_FILTER));
|
||||
if (!path.isEmpty())
|
||||
m_host_interface->applyInputProfile(path);
|
||||
});
|
||||
|
||||
if (!profile_names.empty())
|
||||
menu.addSeparator();
|
||||
|
||||
for (const auto& [name, path] : profile_names)
|
||||
{
|
||||
QAction* action = menu.addAction(QString::fromStdString(name));
|
||||
QString path_qstr = QString::fromStdString(path);
|
||||
connect(action, &QAction::triggered, [this, path_qstr]() { m_host_interface->applyInputProfile(path_qstr); });
|
||||
}
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void ControllerSettingsWidget::onSaveProfileClicked()
|
||||
{
|
||||
const auto profile_names = m_host_interface->getInputProfileList();
|
||||
|
||||
QMenu menu;
|
||||
|
||||
QAction* new_action = menu.addAction(tr("New..."));
|
||||
connect(new_action, &QAction::triggered, [this]() {
|
||||
QString name = QInputDialog::getText(QtUtils::GetRootWidget(this), tr("Enter Input Profile Name"),
|
||||
tr("Enter Input Profile Name"));
|
||||
if (name.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
|
||||
tr("No name entered, input profile was not saved."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->saveInputProfile(m_host_interface->getSavePathForInputProfile(name));
|
||||
});
|
||||
|
||||
QAction* browse = menu.addAction(tr("Browse..."));
|
||||
connect(browse, &QAction::triggered, [this]() {
|
||||
QString path = QFileDialog::getSaveFileName(QtUtils::GetRootWidget(this), tr("Select path to input profile ini"),
|
||||
QString(), tr(INPUT_PROFILE_FILTER));
|
||||
if (path.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
|
||||
tr("No path selected, input profile was not saved."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->saveInputProfile(path);
|
||||
});
|
||||
|
||||
if (!profile_names.empty())
|
||||
menu.addSeparator();
|
||||
|
||||
for (const auto& [name, path] : profile_names)
|
||||
{
|
||||
QAction* action = menu.addAction(QString::fromStdString(name));
|
||||
QString path_qstr = QString::fromStdString(path);
|
||||
connect(action, &QAction::triggered, [this, path_qstr]() { m_host_interface->saveInputProfile(path_qstr); });
|
||||
}
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
#include "core/types.h"
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QScrollArea>
|
||||
#include <QtWidgets/QTabWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class QTimer;
|
||||
|
||||
class QtHostInterface;
|
||||
class InputBindingWidget;
|
||||
|
||||
class ControllerSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerSettingsWidget(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||
~ControllerSettingsWidget();
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateMultitapControllerTitles();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onProfileLoaded();
|
||||
|
||||
private:
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
QTabWidget* m_tab_widget;
|
||||
|
||||
struct PortSettingsUI
|
||||
{
|
||||
QWidget* widget;
|
||||
QVBoxLayout* layout;
|
||||
QComboBox* controller_type;
|
||||
QScrollArea* bindings_scroll_area;
|
||||
QWidget* bindings_container;
|
||||
InputBindingWidget* first_button;
|
||||
};
|
||||
|
||||
static MultitapMode getMultitapMode();
|
||||
|
||||
QString getTabTitleForPort(u32 index, MultitapMode mode) const;
|
||||
|
||||
void createUi();
|
||||
void reloadBindingButtons();
|
||||
void createPortSettingsUi(int index, PortSettingsUI* ui, MultitapMode multitap_mode);
|
||||
void createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype);
|
||||
void onControllerTypeChanged(int index);
|
||||
void onLoadProfileClicked();
|
||||
void onSaveProfileClicked();
|
||||
|
||||
std::array<PortSettingsUI, NUM_CONTROLLER_AND_CARD_PORTS> m_port_ui = {};
|
||||
};
|
||||
161
src/duckstation-qt/controllersettingwidgetbinder.h
Normal file
161
src/duckstation-qt/controllersettingwidgetbinder.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <QtCore/QtCore>
|
||||
#include <QtGui/QAction>
|
||||
#include <QtWidgets/QCheckBox>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QDoubleSpinBox>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QSlider>
|
||||
#include <QtWidgets/QSpinBox>
|
||||
|
||||
#include "core/host_settings.h"
|
||||
#include "qthost.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
/// This nastyness is required because input profiles aren't overlaid settings like the rest of them, it's
|
||||
/// input profile *or* global, not both.
|
||||
namespace ControllerSettingWidgetBinder {
|
||||
/// Interface specific method of BindWidgetToBoolSetting().
|
||||
template<typename WidgetType>
|
||||
static void BindWidgetToInputProfileBool(SettingsInterface* sif, WidgetType* widget, std::string section,
|
||||
std::string key, bool default_value)
|
||||
{
|
||||
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
|
||||
|
||||
if (sif)
|
||||
{
|
||||
const bool value = sif->GetBoolValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setBoolValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
|
||||
const bool new_value = Accessor::getBoolValue(widget);
|
||||
sif->SetBoolValue(section.c_str(), key.c_str(), new_value);
|
||||
sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool value = Host::GetBaseBoolSettingValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setBoolValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
|
||||
const bool new_value = Accessor::getBoolValue(widget);
|
||||
Host::SetBaseBoolSettingValue(section.c_str(), key.c_str(), new_value);
|
||||
g_emu_thread->applySettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface specific method of BindWidgetToFloatSetting().
|
||||
template<typename WidgetType>
|
||||
static void BindWidgetToInputProfileFloat(SettingsInterface* sif, WidgetType* widget, std::string section,
|
||||
std::string key, float default_value)
|
||||
{
|
||||
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
|
||||
|
||||
if (sif)
|
||||
{
|
||||
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setFloatValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
|
||||
const float new_value = Accessor::getFloatValue(widget);
|
||||
sif->SetFloatValue(section.c_str(), key.c_str(), new_value);
|
||||
sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setFloatValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
|
||||
const float new_value = Accessor::getFloatValue(widget);
|
||||
Host::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
|
||||
g_emu_thread->applySettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface specific method of BindWidgetToNormalizedSetting().
|
||||
template<typename WidgetType>
|
||||
static void BindWidgetToInputProfileNormalized(SettingsInterface* sif, WidgetType* widget, std::string section,
|
||||
std::string key, float range, float default_value)
|
||||
{
|
||||
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
|
||||
|
||||
if (sif)
|
||||
{
|
||||
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setIntValue(widget, static_cast<int>(value * range));
|
||||
|
||||
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), range]() {
|
||||
const int new_value = Accessor::getIntValue(widget);
|
||||
sif->SetFloatValue(section.c_str(), key.c_str(), static_cast<float>(new_value) / range);
|
||||
sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setIntValue(widget, static_cast<int>(value * range));
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), range]() {
|
||||
const float new_value = (static_cast<float>(Accessor::getIntValue(widget)) / range);
|
||||
Host::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
|
||||
g_emu_thread->applySettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface specific method of BindWidgetToStringSetting().
|
||||
template<typename WidgetType>
|
||||
static void BindWidgetToInputProfileString(SettingsInterface* sif, WidgetType* widget, std::string section,
|
||||
std::string key, std::string default_value = std::string())
|
||||
{
|
||||
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
|
||||
|
||||
if (sif)
|
||||
{
|
||||
const QString value(
|
||||
QString::fromStdString(sif->GetStringValue(section.c_str(), key.c_str(), default_value.c_str())));
|
||||
|
||||
Accessor::setStringValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, sif, section = std::move(section), key = std::move(key)]() {
|
||||
const QString new_value = Accessor::getStringValue(widget);
|
||||
if (!new_value.isEmpty())
|
||||
sif->SetStringValue(section.c_str(), key.c_str(), new_value.toUtf8().constData());
|
||||
else
|
||||
sif->DeleteValue(section.c_str(), key.c_str());
|
||||
|
||||
sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString value(
|
||||
QString::fromStdString(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())));
|
||||
|
||||
Accessor::setStringValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
|
||||
const QString new_value = Accessor::getStringValue(widget);
|
||||
if (!new_value.isEmpty())
|
||||
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.toUtf8().constData());
|
||||
else
|
||||
Host::DeleteBaseSettingValue(section.c_str(), key.c_str());
|
||||
|
||||
g_emu_thread->applySettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace ControllerSettingWidgetBinder
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "debuggerwindow.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/cpu_core_private.h"
|
||||
#include "debuggermodels.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtGui/QFontDatabase>
|
||||
@@ -21,22 +22,25 @@ DebuggerWindow::DebuggerWindow(QWidget* parent /* = nullptr */)
|
||||
|
||||
DebuggerWindow::~DebuggerWindow() = default;
|
||||
|
||||
void DebuggerWindow::onEmulationPaused(bool paused)
|
||||
void DebuggerWindow::onEmulationPaused()
|
||||
{
|
||||
if (paused)
|
||||
{
|
||||
setUIEnabled(true);
|
||||
refreshAll();
|
||||
refreshBreakpointList();
|
||||
}
|
||||
else
|
||||
{
|
||||
setUIEnabled(false);
|
||||
}
|
||||
setUIEnabled(true);
|
||||
refreshAll();
|
||||
refreshBreakpointList();
|
||||
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionPause);
|
||||
m_ui.actionPause->setChecked(paused);
|
||||
m_ui.actionPause->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow::onEmulationResumed()
|
||||
{
|
||||
setUIEnabled(false);
|
||||
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionPause);
|
||||
m_ui.actionPause->setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +84,7 @@ void DebuggerWindow::onPauseActionToggled(bool paused)
|
||||
setUIEnabled(false);
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->pauseSystem(paused);
|
||||
g_emu_thread->setSystemPaused(paused);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onRunToCursorTriggered()
|
||||
@@ -93,7 +97,7 @@ void DebuggerWindow::onRunToCursorTriggered()
|
||||
}
|
||||
|
||||
CPU::AddBreakpoint(addr.value(), true, true);
|
||||
QtHostInterface::GetInstance()->pauseSystem(false);
|
||||
g_emu_thread->setSystemPaused(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onGoToPCTriggered()
|
||||
@@ -176,7 +180,7 @@ void DebuggerWindow::onStepIntoActionTriggered()
|
||||
{
|
||||
Assert(System::IsPaused());
|
||||
m_registers_model->saveCurrentValues();
|
||||
QtHostInterface::GetInstance()->singleStepCPU();
|
||||
g_emu_thread->singleStepCPU();
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
@@ -191,7 +195,7 @@ void DebuggerWindow::onStepOverActionTriggered()
|
||||
|
||||
// unpause to let it run to the breakpoint
|
||||
m_registers_model->saveCurrentValues();
|
||||
QtHostInterface::GetInstance()->pauseSystem(false);
|
||||
g_emu_thread->setSystemPaused(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onStepOutActionTriggered()
|
||||
@@ -205,7 +209,7 @@ void DebuggerWindow::onStepOutActionTriggered()
|
||||
|
||||
// unpause to let it run to the breakpoint
|
||||
m_registers_model->saveCurrentValues();
|
||||
QtHostInterface::GetInstance()->pauseSystem(false);
|
||||
g_emu_thread->setSystemPaused(false);
|
||||
}
|
||||
|
||||
void DebuggerWindow::onCodeViewItemActivated(QModelIndex index)
|
||||
@@ -351,9 +355,9 @@ void DebuggerWindow::onMemorySearchStringChanged(const QString&)
|
||||
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
QMainWindow::closeEvent(event);
|
||||
QtHostInterface::GetInstance()->pauseSystem(true, true);
|
||||
g_emu_thread->setSystemPaused(true, true);
|
||||
CPU::ClearBreakpoints();
|
||||
QtHostInterface::GetInstance()->pauseSystem(false);
|
||||
g_emu_thread->setSystemPaused(false);
|
||||
emit closed();
|
||||
}
|
||||
|
||||
@@ -379,9 +383,10 @@ void DebuggerWindow::setupAdditionalUi()
|
||||
|
||||
void DebuggerWindow::connectSignals()
|
||||
{
|
||||
QtHostInterface* hi = QtHostInterface::GetInstance();
|
||||
connect(hi, &QtHostInterface::emulationPaused, this, &DebuggerWindow::onEmulationPaused);
|
||||
connect(hi, &QtHostInterface::debuggerMessageReported, this, &DebuggerWindow::onDebuggerMessageReported);
|
||||
EmuThread* hi = g_emu_thread;
|
||||
connect(hi, &EmuThread::systemPaused, this, &DebuggerWindow::onEmulationPaused);
|
||||
connect(hi, &EmuThread::systemResumed, this, &DebuggerWindow::onEmulationResumed);
|
||||
connect(hi, &EmuThread::debuggerMessageReported, this, &DebuggerWindow::onDebuggerMessageReported);
|
||||
|
||||
connect(m_ui.actionPause, &QAction::toggled, this, &DebuggerWindow::onPauseActionToggled);
|
||||
connect(m_ui.actionRunToCursor, &QAction::triggered, this, &DebuggerWindow::onRunToCursorTriggered);
|
||||
@@ -410,7 +415,7 @@ void DebuggerWindow::connectSignals()
|
||||
|
||||
void DebuggerWindow::disconnectSignals()
|
||||
{
|
||||
QtHostInterface* hi = QtHostInterface::GetInstance();
|
||||
EmuThread* hi = g_emu_thread;
|
||||
hi->disconnect(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ Q_SIGNALS:
|
||||
void closed();
|
||||
|
||||
public Q_SLOTS:
|
||||
void onEmulationPaused(bool paused);
|
||||
void onEmulationPaused();
|
||||
void onEmulationResumed();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
@@ -14,51 +14,45 @@
|
||||
#include "frontend-common/d3d12_host_display.h"
|
||||
#endif
|
||||
|
||||
DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
DisplaySettingsWidget::DisplaySettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
setupAdditionalUi();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.renderer, "GPU", "Renderer",
|
||||
&Settings::ParseRendererName, &Settings::GetRendererName,
|
||||
Settings::DEFAULT_GPU_RENDERER);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.displayAspectRatio, "Display", "AspectRatio",
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName,
|
||||
&Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio",
|
||||
&Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName,
|
||||
Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.customAspectRatioNumerator, "Display",
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioNumerator, "Display",
|
||||
"CustomAspectRatioNumerator", 1);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.customAspectRatioDenominator, "Display",
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioDenominator, "Display",
|
||||
"CustomAspectRatioDenominator", 1);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.displayCropMode, "Display", "CropMode",
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayCropMode, "Display", "CropMode",
|
||||
&Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
|
||||
Settings::DEFAULT_DISPLAY_CROP_MODE);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
|
||||
&Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
|
||||
Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayLinearFiltering, "Display",
|
||||
"LinearFiltering", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayIntegerScaling, "Display",
|
||||
"IntegerScaling", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayStretch, "Display", "Stretch", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.internalResolutionScreenshots, "Display",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.displayLinearFiltering, "Display", "LinearFiltering", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.displayIntegerScaling, "Display", "IntegerScaling", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.displayStretch, "Display", "Stretch", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.internalResolutionScreenshots, "Display",
|
||||
"InternalResolutionScreenshots", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display", "VSync");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayAllFrames, "Display", "DisplayAllFrames",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuThread, "GPU", "UseThread", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.threadedPresentation, "GPU",
|
||||
"ThreadedPresentation", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.syncToHostRefreshRate, "Main",
|
||||
"SyncToHostRefreshRate", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showOSDMessages, "Display", "ShowOSDMessages",
|
||||
true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showFPS, "Display", "ShowFPS", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showVPS, "Display", "ShowVPS", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showSpeed, "Display", "ShowSpeed", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showResolution, "Display", "ShowResolution",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.showInput, "Display", "ShowInputs", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.displayAllFrames, "Display", "DisplayAllFrames", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "GPU", "ThreadedPresentation", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.syncToHostRefreshRate, "Main", "SyncToHostRefreshRate", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showOSDMessages, "Display", "ShowOSDMessages", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showResolution, "Display", "ShowResolution", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showCPU, "Display", "ShowCPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showInput, "Display", "ShowInputs", false);
|
||||
|
||||
connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DisplaySettingsWidget::populateGPUAdaptersAndResolutions);
|
||||
@@ -143,11 +137,8 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
|
||||
dialog->registerWidgetHelp(m_ui.showOSDMessages, tr("Show OSD Messages"), tr("Checked"),
|
||||
tr("Shows on-screen-display messages when events occur such as save states being "
|
||||
"created/loaded, screenshots being taken, etc."));
|
||||
dialog->registerWidgetHelp(m_ui.showFPS, tr("Show Game Frame Rate"), tr("Unchecked"),
|
||||
dialog->registerWidgetHelp(m_ui.showFPS, tr("Show FPS"), tr("Unchecked"),
|
||||
tr("Shows the internal frame rate of the game in the top-right corner of the display."));
|
||||
dialog->registerWidgetHelp(m_ui.showVPS, tr("Show Display FPS"), tr("Unchecked"),
|
||||
tr("Shows the number of frames (or v-syncs) displayed per second by the system in the "
|
||||
"top-right corner of the display."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.showSpeed, tr("Show Emulation Speed"), tr("Unchecked"),
|
||||
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
|
||||
@@ -160,7 +151,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
|
||||
#ifdef _WIN32
|
||||
{
|
||||
QCheckBox* cb = new QCheckBox(tr("Use Blit Swap Chain"), m_ui.basicGroupBox);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, "Display", "UseBlitSwapChain", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, cb, "Display", "UseBlitSwapChain", false);
|
||||
m_ui.basicCheckboxGridLayout->addWidget(cb, 2, 1, 1, 1);
|
||||
dialog->registerWidgetHelp(cb, tr("Use Blit Swap Chain"), tr("Unchecked"),
|
||||
tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 "
|
||||
@@ -230,7 +221,7 @@ void DisplaySettingsWidget::populateGPUAdaptersAndResolutions()
|
||||
}
|
||||
|
||||
{
|
||||
const std::string current_adapter(m_host_interface->GetStringSettingValue("GPU", "Adapter"));
|
||||
const std::string current_adapter(m_dialog->getEffectiveStringValue("GPU", "Adapter", ""));
|
||||
QSignalBlocker blocker(m_ui.adapter);
|
||||
|
||||
// add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options
|
||||
@@ -251,7 +242,7 @@ void DisplaySettingsWidget::populateGPUAdaptersAndResolutions()
|
||||
}
|
||||
|
||||
{
|
||||
const std::string current_mode(m_host_interface->GetStringSettingValue("GPU", "FullscreenMode", ""));
|
||||
const std::string current_mode(m_dialog->getEffectiveStringValue("GPU", "FullscreenMode", ""));
|
||||
QSignalBlocker blocker(m_ui.fullscreenMode);
|
||||
|
||||
m_ui.fullscreenMode->clear();
|
||||
@@ -279,11 +270,11 @@ void DisplaySettingsWidget::onGPUAdapterIndexChanged()
|
||||
if (m_ui.adapter->currentIndex() == 0)
|
||||
{
|
||||
// default
|
||||
m_host_interface->RemoveSettingValue("GPU", "Adapter");
|
||||
m_dialog->removeSettingValue("GPU", "Adapter");
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData());
|
||||
m_dialog->setStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData());
|
||||
}
|
||||
|
||||
void DisplaySettingsWidget::onGPUFullscreenModeIndexChanged()
|
||||
@@ -291,12 +282,11 @@ void DisplaySettingsWidget::onGPUFullscreenModeIndexChanged()
|
||||
if (m_ui.fullscreenMode->currentIndex() == 0)
|
||||
{
|
||||
// default
|
||||
m_host_interface->RemoveSettingValue("GPU", "FullscreenMode");
|
||||
m_dialog->removeSettingValue("GPU", "FullscreenMode");
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetStringSettingValue("GPU", "FullscreenMode",
|
||||
m_ui.fullscreenMode->currentText().toUtf8().constData());
|
||||
m_dialog->setStringSettingValue("GPU", "FullscreenMode", m_ui.fullscreenMode->currentText().toUtf8().constData());
|
||||
}
|
||||
|
||||
void DisplaySettingsWidget::onIntegerFilteringChanged()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_displaysettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class PostProcessingChainConfigWidget;
|
||||
class SettingsDialog;
|
||||
|
||||
@@ -13,7 +12,7 @@ class DisplaySettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplaySettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
DisplaySettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~DisplaySettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -28,5 +27,5 @@ private:
|
||||
|
||||
Ui::DisplaySettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -219,13 +219,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="showFPS">
|
||||
<property name="text">
|
||||
<string>Show Game Frame Rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="showSpeed">
|
||||
<property name="text">
|
||||
@@ -234,16 +227,23 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="showVPS">
|
||||
<widget class="QCheckBox" name="showFPS">
|
||||
<property name="text">
|
||||
<string>Show Display FPS</string>
|
||||
<string>Show FPS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="showResolution">
|
||||
<property name="text">
|
||||
<string>Show Resolution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="showResolution">
|
||||
<widget class="QCheckBox" name="showCPU">
|
||||
<property name="text">
|
||||
<string>Show Resolution</string>
|
||||
<string>Show CPU Usage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
440
src/duckstation-qt/displaywidget.cpp
Normal file
440
src/duckstation-qt/displaywidget.cpp
Normal file
@@ -0,0 +1,440 @@
|
||||
#include "displaywidget.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/log.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QWindowStateChangeEvent>
|
||||
#include <cmath>
|
||||
|
||||
#if !defined(_WIN32) && !defined(APPLE)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#endif
|
||||
|
||||
Log_SetChannel(DisplayWidget);
|
||||
|
||||
DisplayWidget::DisplayWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
// We want a native window for both D3D and OpenGL.
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_NativeWindow, true);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||
setAttribute(Qt::WA_KeyCompression, false);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
DisplayWidget::~DisplayWidget()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
ClipCursor(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
qreal DisplayWidget::devicePixelRatioFromScreen() const
|
||||
{
|
||||
const QScreen* screen_for_ratio = screen();
|
||||
if (!screen_for_ratio)
|
||||
screen_for_ratio = QGuiApplication::primaryScreen();
|
||||
|
||||
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowWidth() const
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen())), 1);
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowHeight() const
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen())), 1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle());
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Unknown PNI platform " << platform_name;
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_last_window_width = wi.surface_width = static_cast<u32>(scaledWindowWidth());
|
||||
m_last_window_height = wi.surface_height = static_cast<u32>(scaledWindowHeight());
|
||||
m_last_window_scale = wi.surface_scale = static_cast<float>(devicePixelRatioFromScreen());
|
||||
return wi;
|
||||
}
|
||||
|
||||
void DisplayWidget::updateRelativeMode(bool master_enable)
|
||||
{
|
||||
bool relative_mode = master_enable/* && InputManager::HasPointerAxisBinds()*/;
|
||||
|
||||
#ifdef _WIN32
|
||||
// prefer ClipCursor() over warping movement when we're using raw input
|
||||
bool clip_cursor = relative_mode && InputManager::IsUsingRawInput();
|
||||
if (m_relative_mouse_enabled == relative_mode && m_clip_mouse_enabled == clip_cursor)
|
||||
return;
|
||||
|
||||
Log_InfoPrintf("updateRelativeMode(): relative=%s, clip=%s", relative_mode ? "yes" : "no",
|
||||
clip_cursor ? "yes" : "no");
|
||||
|
||||
if (!clip_cursor && m_clip_mouse_enabled)
|
||||
{
|
||||
m_clip_mouse_enabled = false;
|
||||
ClipCursor(nullptr);
|
||||
}
|
||||
#else
|
||||
if (m_relative_mouse_enabled == relative_mode)
|
||||
return;
|
||||
|
||||
Log_InfoPrintf("updateRelativeMode(): relative=%s", relative_mode ? "yes" : "no");
|
||||
#endif
|
||||
|
||||
if (relative_mode)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_relative_mouse_enabled = !clip_cursor;
|
||||
m_clip_mouse_enabled = clip_cursor;
|
||||
#else
|
||||
m_relative_mouse_enabled = true;
|
||||
#endif
|
||||
m_relative_mouse_start_pos = QCursor::pos();
|
||||
updateCenterPos();
|
||||
grabMouse();
|
||||
}
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
m_relative_mouse_enabled = false;
|
||||
QCursor::setPos(m_relative_mouse_start_pos);
|
||||
releaseMouse();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCursor(bool master_enable)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled || m_clip_mouse_enabled);
|
||||
#else
|
||||
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled);
|
||||
#endif
|
||||
if (m_cursor_hidden == hide)
|
||||
return;
|
||||
|
||||
m_cursor_hidden = hide;
|
||||
if (hide)
|
||||
setCursor(Qt::BlankCursor);
|
||||
else
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCenterPos()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
{
|
||||
RECT rc;
|
||||
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
|
||||
ClipCursor(&rc);
|
||||
}
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
RECT rc;
|
||||
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
|
||||
{
|
||||
m_relative_mouse_center_pos.setX(((rc.right - rc.left) / 2) + rc.left);
|
||||
m_relative_mouse_center_pos.setY(((rc.bottom - rc.top) / 2) + rc.top);
|
||||
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (m_relative_mouse_enabled)
|
||||
{
|
||||
// we do a round trip here because these coordinates are dpi-unscaled
|
||||
m_relative_mouse_center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
|
||||
QCursor::setPos(m_relative_mouse_center_pos);
|
||||
m_relative_mouse_center_pos = QCursor::pos();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QPaintEngine* DisplayWidget::paintEngine() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DisplayWidget::event(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
const QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
|
||||
if (key_event->isAutoRepeat())
|
||||
return true;
|
||||
|
||||
// For some reason, Windows sends "fake" key events.
|
||||
// Scenario: Press shift, press F1, release shift, release F1.
|
||||
// Events: Shift=Pressed, F1=Pressed, Shift=Released, **F1=Pressed**, F1=Released.
|
||||
// To work around this, we keep track of keys pressed with modifiers in a list, and
|
||||
// discard the press event when it's been previously activated. It's pretty gross,
|
||||
// but I can't think of a better way of handling it, and there doesn't appear to be
|
||||
// any window flag which changes this behavior that I can see.
|
||||
|
||||
const u32 key = QtUtils::KeyEventToCode(key_event);
|
||||
const Qt::KeyboardModifiers modifiers = key_event->modifiers();
|
||||
const bool pressed = (key_event->type() == QEvent::KeyPress);
|
||||
const auto it = std::find(m_keys_pressed_with_modifiers.begin(), m_keys_pressed_with_modifiers.end(), key);
|
||||
if (it != m_keys_pressed_with_modifiers.end())
|
||||
{
|
||||
if (pressed)
|
||||
return true;
|
||||
else
|
||||
m_keys_pressed_with_modifiers.erase(it);
|
||||
}
|
||||
else if (modifiers != Qt::NoModifier && modifiers != Qt::KeypadModifier && pressed)
|
||||
{
|
||||
m_keys_pressed_with_modifiers.push_back(key);
|
||||
}
|
||||
|
||||
emit windowKeyEvent(key, pressed);
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseMove:
|
||||
{
|
||||
const QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
|
||||
if (!m_relative_mouse_enabled)
|
||||
{
|
||||
const qreal dpr = devicePixelRatioFromScreen();
|
||||
const QPoint mouse_pos = mouse_event->pos();
|
||||
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * dpr);
|
||||
emit windowMouseMoveEvent(false, scaled_x, scaled_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// On windows, we use winapi here. The reason being that the coordinates in QCursor
|
||||
// are un-dpi-scaled, so we lose precision at higher desktop scalings.
|
||||
float dx = 0.0f, dy = 0.0f;
|
||||
|
||||
#ifndef _WIN32
|
||||
const QPoint mouse_pos = QCursor::pos();
|
||||
if (mouse_pos != m_relative_mouse_center_pos)
|
||||
{
|
||||
dx = static_cast<float>(mouse_pos.x() - m_relative_mouse_center_pos.x());
|
||||
dy = static_cast<float>(mouse_pos.y() - m_relative_mouse_center_pos.y());
|
||||
QCursor::setPos(m_relative_mouse_center_pos);
|
||||
}
|
||||
#else
|
||||
POINT mouse_pos;
|
||||
if (GetCursorPos(&mouse_pos))
|
||||
{
|
||||
dx = static_cast<float>(mouse_pos.x - m_relative_mouse_center_pos.x());
|
||||
dy = static_cast<float>(mouse_pos.y - m_relative_mouse_center_pos.y());
|
||||
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dx != 0.0f || dy != 0.0f)
|
||||
emit windowMouseMoveEvent(true, dx, dy);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
|
||||
emit windowMouseButtonEvent(static_cast<int>(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
|
||||
|
||||
// don't toggle fullscreen when we're bound.. that wouldn't end well.
|
||||
if (event->type() == QEvent::MouseButtonDblClick &&
|
||||
static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton &&
|
||||
!InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) &&
|
||||
Host::GetBoolSettingValue("Main", "DoubleClickTogglesFullscreen", true))
|
||||
{
|
||||
g_emu_thread->toggleFullscreen();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Wheel:
|
||||
{
|
||||
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
|
||||
emit windowMouseWheelEvent(wheel_event->angleDelta());
|
||||
return true;
|
||||
}
|
||||
|
||||
// According to https://bugreports.qt.io/browse/QTBUG-95925 the recommended practice for handling DPI change is
|
||||
// responding to paint events
|
||||
case QEvent::Paint:
|
||||
case QEvent::Resize:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
const float dpr = devicePixelRatioFromScreen();
|
||||
const u32 scaled_width =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height =
|
||||
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
|
||||
{
|
||||
m_last_window_width = scaled_width;
|
||||
m_last_window_height = scaled_height;
|
||||
m_last_window_scale = dpr;
|
||||
emit windowResizedEvent(scaled_width, scaled_height, dpr);
|
||||
}
|
||||
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Move:
|
||||
{
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Close:
|
||||
{
|
||||
// Closing the separate widget will either cancel the close, or trigger shutdown.
|
||||
// In the latter case, it's going to destroy us, so don't let Qt do it first.
|
||||
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true),
|
||||
Q_ARG(bool, false));
|
||||
event->ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit windowRestoredEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return QWidget::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayContainer::DisplayContainer() : QStackedWidget(nullptr) {}
|
||||
|
||||
DisplayContainer::~DisplayContainer() = default;
|
||||
|
||||
bool DisplayContainer::isNeeded(bool fullscreen, bool render_to_main)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
if (!isRunningOnWayland())
|
||||
return false;
|
||||
|
||||
// We only need this on Wayland because of client-side decorations...
|
||||
return (fullscreen || !render_to_main);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DisplayContainer::isRunningOnWayland()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
return (platform_name == QStringLiteral("wayland"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisplayContainer::setDisplayWidget(DisplayWidget* widget)
|
||||
{
|
||||
Assert(!m_display_widget);
|
||||
m_display_widget = widget;
|
||||
addWidget(widget);
|
||||
}
|
||||
|
||||
DisplayWidget* DisplayContainer::removeDisplayWidget()
|
||||
{
|
||||
DisplayWidget* widget = m_display_widget;
|
||||
Assert(widget);
|
||||
m_display_widget = nullptr;
|
||||
removeWidget(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
bool DisplayContainer::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::Close)
|
||||
{
|
||||
// Closing the separate widget will either cancel the close, or trigger shutdown.
|
||||
// In the latter case, it's going to destroy us, so don't let Qt do it first.
|
||||
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true),
|
||||
Q_ARG(bool, false));
|
||||
event->ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool res = QStackedWidget::event(event);
|
||||
if (!m_display_widget)
|
||||
return res;
|
||||
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit m_display_widget->windowRestoredEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
80
src/duckstation-qt/displaywidget.h
Normal file
80
src/duckstation-qt/displaywidget.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "common/window_info.h"
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <optional>
|
||||
|
||||
class DisplayWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DisplayWidget(QWidget* parent);
|
||||
~DisplayWidget();
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
ALWAYS_INLINE void setShouldHideCursor(bool hide) { m_should_hide_cursor = hide; }
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
qreal devicePixelRatioFromScreen() const;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
|
||||
void updateRelativeMode(bool master_enable);
|
||||
void updateCursor(bool master_enable);
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowResizedEvent(int width, int height, float scale);
|
||||
void windowRestoredEvent();
|
||||
void windowKeyEvent(int key_code, bool pressed);
|
||||
void windowMouseMoveEvent(bool relative, float x, float y);
|
||||
void windowMouseButtonEvent(int button, bool pressed);
|
||||
void windowMouseWheelEvent(const QPoint& angle_delta);
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void updateCenterPos();
|
||||
|
||||
QPoint m_relative_mouse_start_pos{};
|
||||
QPoint m_relative_mouse_center_pos{};
|
||||
bool m_relative_mouse_enabled = false;
|
||||
#ifdef _WIN32
|
||||
bool m_clip_mouse_enabled = false;
|
||||
#endif
|
||||
bool m_should_hide_cursor = false;
|
||||
bool m_cursor_hidden = false;
|
||||
|
||||
std::vector<u32> m_keys_pressed_with_modifiers;
|
||||
|
||||
u32 m_last_window_width = 0;
|
||||
u32 m_last_window_height = 0;
|
||||
float m_last_window_scale = 1.0f;
|
||||
};
|
||||
|
||||
class DisplayContainer final : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayContainer();
|
||||
~DisplayContainer();
|
||||
|
||||
// Wayland is broken in lots of ways, so we need to check for it.
|
||||
static bool isRunningOnWayland();
|
||||
|
||||
static bool isNeeded(bool fullscreen, bool render_to_main);
|
||||
|
||||
void setDisplayWidget(DisplayWidget* widget);
|
||||
DisplayWidget* removeDisplayWidget();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
};
|
||||
@@ -13,34 +13,37 @@
|
||||
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
||||
<ClCompile Include="collapsiblewidget.cpp" />
|
||||
<ClCompile Include="consolesettingswidget.cpp" />
|
||||
<ClCompile Include="controllerbindingwidgets.cpp" />
|
||||
<ClCompile Include="controllerglobalsettingswidget.cpp" />
|
||||
<ClCompile Include="controllersettingsdialog.cpp" />
|
||||
<ClCompile Include="emulationsettingswidget.cpp" />
|
||||
<ClCompile Include="debuggermodels.cpp" />
|
||||
<ClCompile Include="debuggerwindow.cpp" />
|
||||
<ClCompile Include="enhancementsettingswidget.cpp" />
|
||||
<ClCompile Include="foldersettingswidget.cpp" />
|
||||
<ClCompile Include="gamelistmodel.cpp" />
|
||||
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
||||
<ClCompile Include="generalsettingswidget.cpp" />
|
||||
<ClCompile Include="displaysettingswidget.cpp" />
|
||||
<ClCompile Include="hotkeysettingswidget.cpp" />
|
||||
<ClCompile Include="inputbindingdialog.cpp" />
|
||||
<ClCompile Include="inputbindingmonitor.cpp" />
|
||||
<ClCompile Include="inputbindingwidgets.cpp" />
|
||||
<ClCompile Include="memoryviewwidget.cpp" />
|
||||
<ClCompile Include="qtdisplaywidget.cpp" />
|
||||
<ClCompile Include="displaywidget.cpp" />
|
||||
<ClCompile Include="gamelistsettingswidget.cpp" />
|
||||
<ClCompile Include="gamelistrefreshthread.cpp" />
|
||||
<ClCompile Include="gamelistwidget.cpp" />
|
||||
<ClCompile Include="gamepropertiesdialog.cpp" />
|
||||
<ClCompile Include="gamesummarywidget.cpp" />
|
||||
<ClCompile Include="gdbconnection.cpp" />
|
||||
<ClCompile Include="gdbserver.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="mainwindow.cpp" />
|
||||
<ClCompile Include="controllersettingswidget.cpp" />
|
||||
<ClCompile Include="memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="memorycardeditordialog.cpp" />
|
||||
<ClCompile Include="postprocessingchainconfigwidget.cpp" />
|
||||
<ClCompile Include="postprocessingshaderconfigwidget.cpp" />
|
||||
<ClCompile Include="postprocessingsettingswidget.cpp" />
|
||||
<ClCompile Include="qthostinterface.cpp" />
|
||||
<ClCompile Include="qthost.cpp" />
|
||||
<ClCompile Include="qtkeycodes.cpp" />
|
||||
<ClCompile Include="qtprogresscallback.cpp" />
|
||||
<ClCompile Include="qtutils.cpp" />
|
||||
<ClCompile Include="settingsdialog.cpp" />
|
||||
@@ -51,11 +54,10 @@
|
||||
<QtMoc Include="biossettingswidget.h" />
|
||||
<QtMoc Include="cheatmanagerdialog.h" />
|
||||
<QtMoc Include="cheatcodeeditordialog.h" />
|
||||
<QtMoc Include="controllersettingswidget.h" />
|
||||
<QtMoc Include="enhancementsettingswidget.h" />
|
||||
<QtMoc Include="memorycardsettingswidget.h" />
|
||||
<QtMoc Include="memorycardeditordialog.h" />
|
||||
<QtMoc Include="qtdisplaywidget.h" />
|
||||
<QtMoc Include="displaywidget.h" />
|
||||
<QtMoc Include="generalsettingswidget.h" />
|
||||
<QtMoc Include="displaysettingswidget.h" />
|
||||
<QtMoc Include="hotkeysettingswidget.h" />
|
||||
@@ -71,22 +73,27 @@
|
||||
<QtMoc Include="achievementsettingswidget.h" />
|
||||
<QtMoc Include="achievementlogindialog.h" />
|
||||
<QtMoc Include="collapsiblewidget.h" />
|
||||
<ClInclude Include="inputbindingmonitor.h" />
|
||||
<QtMoc Include="controllerbindingwidgets.h" />
|
||||
<QtMoc Include="controllerglobalsettingswidget.h" />
|
||||
<QtMoc Include="controllersettingsdialog.h" />
|
||||
<ClInclude Include="controllersettingwidgetbinder.h" />
|
||||
<QtMoc Include="memoryviewwidget.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="settingwidgetbinder.h" />
|
||||
<QtMoc Include="consolesettingswidget.h" />
|
||||
<QtMoc Include="emulationsettingswidget.h" />
|
||||
<QtMoc Include="gamelistsettingswidget.h" />
|
||||
<QtMoc Include="gamelistrefreshthread.h" />
|
||||
<QtMoc Include="gamelistwidget.h" />
|
||||
<QtMoc Include="gamepropertiesdialog.h" />
|
||||
<QtMoc Include="gamesummarywidget.h" />
|
||||
<QtMoc Include="gdbconnection.h" />
|
||||
<QtMoc Include="gdbserver.h" />
|
||||
<QtMoc Include="postprocessingchainconfigwidget.h" />
|
||||
<QtMoc Include="postprocessingshaderconfigwidget.h" />
|
||||
<QtMoc Include="postprocessingsettingswidget.h" />
|
||||
<QtMoc Include="mainwindow.h" />
|
||||
<QtMoc Include="qthostinterface.h" />
|
||||
<QtMoc Include="qthost.h" />
|
||||
<QtMoc Include="foldersettingswidget.h" />
|
||||
<ClInclude Include="qtutils.h" />
|
||||
<QtMoc Include="settingsdialog.h" />
|
||||
</ItemGroup>
|
||||
@@ -127,9 +134,6 @@
|
||||
<QtUi Include="advancedsettingswidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="gamepropertiesdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="postprocessingchainconfigwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
@@ -145,6 +149,63 @@
|
||||
<QtUi Include="cheatcodeeditordialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="inputbindingdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="autoupdaterdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="achievementsettingswidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="achievementlogindialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="debuggerwindow.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget_analog_controller.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget_digital_controller.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget_analog_joystick.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget_negcon.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerbindingwidget_guncon.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllerglobalsettingswidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllersettingsdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllermacrodialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="controllermacroeditwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="emptygamelistwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="gamelistwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="gamesummarywidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="foldersettingswidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtResource Include="resources\resources.qrc">
|
||||
@@ -163,14 +224,19 @@
|
||||
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_collapsiblewidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllerbindingwidgets.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllerglobalsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllersettingsdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_displaywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_emulationsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_foldersettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistrefreshthread.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistsearchdirectoriesmodel.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamepropertiesdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamesummarywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gdbconnection.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gdbserver.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
|
||||
@@ -187,8 +253,7 @@
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qthostinterface.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qthost.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_settingsdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)qrc_resources.cpp" />
|
||||
@@ -202,16 +267,6 @@
|
||||
<ItemGroup>
|
||||
<Image Include="duckstation-qt.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUi Include="inputbindingdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUi Include="autoupdaterdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtTs Include="translations\duckstation-qt_de.ts">
|
||||
<FileType>Document</FileType>
|
||||
@@ -256,13 +311,6 @@
|
||||
<ItemGroup>
|
||||
<None Include="translations\duckstation-qt_es-es.ts" />
|
||||
<None Include="translations\duckstation-qt_tr.ts" />
|
||||
<QtUi Include="achievementsettingswidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="achievementlogindialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<None Include="debuggerwindow.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CommonDataFiles Include="$(SolutionDir)data\**\*.*">
|
||||
@@ -282,7 +330,11 @@
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>$(RootBuildDir)frontend-common\frontend-common.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>$(QtEntryPointLib);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<ClCompile>
|
||||
<DisableSpecificWarnings>4127;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="..\..\dep\msvc\vsprops\Targets.props" />
|
||||
<Import Project="..\..\dep\msvc\vsprops\QtCompile.targets" />
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="mainwindow.cpp" />
|
||||
<ClCompile Include="gamelistwidget.cpp" />
|
||||
<ClCompile Include="settingsdialog.cpp" />
|
||||
<ClCompile Include="consolesettingswidget.cpp" />
|
||||
<ClCompile Include="qthostinterface.cpp" />
|
||||
<ClCompile Include="qthost.cpp" />
|
||||
<ClCompile Include="gamelistsettingswidget.cpp" />
|
||||
<ClCompile Include="qtutils.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qthostinterface.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qthost.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_settingsdialog.cpp" />
|
||||
<ClCompile Include="inputbindingwidgets.cpp" />
|
||||
<ClCompile Include="hotkeysettingswidget.cpp" />
|
||||
@@ -21,25 +20,21 @@
|
||||
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
||||
<ClCompile Include="audiosettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
|
||||
<ClCompile Include="qtdisplaywidget.cpp" />
|
||||
<ClCompile Include="displaywidget.cpp" />
|
||||
<ClCompile Include="qtprogresscallback.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
|
||||
<ClCompile Include="generalsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
|
||||
<ClCompile Include="advancedsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
|
||||
<ClCompile Include="gamepropertiesdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamepropertiesdialog.cpp" />
|
||||
<ClCompile Include="gdbconnection.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gdbconnection.cpp" />
|
||||
<ClCompile Include="gdbserver.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gdbserver.cpp" />
|
||||
<ClCompile Include="controllersettingswidget.cpp" />
|
||||
<ClCompile Include="aboutdialog.cpp" />
|
||||
<ClCompile Include="memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_aboutdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)qrc_resources.cpp" />
|
||||
<ClCompile Include="inputbindingdialog.cpp" />
|
||||
@@ -64,7 +59,6 @@
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
|
||||
<ClCompile Include="inputbindingmonitor.cpp" />
|
||||
<ClCompile Include="cheatmanagerdialog.cpp" />
|
||||
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
|
||||
@@ -83,12 +77,26 @@
|
||||
<ClCompile Include="$(IntDir)moc_achievementlogindialog.cpp" />
|
||||
<ClCompile Include="collapsiblewidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_collapsiblewidget.cpp" />
|
||||
<ClCompile Include="qtkeycodes.cpp" />
|
||||
<ClCompile Include="controllerglobalsettingswidget.cpp" />
|
||||
<ClCompile Include="controllersettingsdialog.cpp" />
|
||||
<ClCompile Include="controllerbindingwidgets.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllerbindingwidgets.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllerglobalsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllersettingsdialog.cpp" />
|
||||
<ClCompile Include="gamelistrefreshthread.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamelistrefreshthread.cpp" />
|
||||
<ClCompile Include="foldersettingswidget.cpp" />
|
||||
<ClCompile Include="gamesummarywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_displaywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_foldersettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_gamesummarywidget.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="qtutils.h" />
|
||||
<ClInclude Include="settingwidgetbinder.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="inputbindingmonitor.h" />
|
||||
<ClInclude Include="controllersettingwidgetbinder.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="resources">
|
||||
@@ -103,19 +111,17 @@
|
||||
<QtMoc Include="gamelistsettingswidget.h" />
|
||||
<QtMoc Include="gamelistwidget.h" />
|
||||
<QtMoc Include="mainwindow.h" />
|
||||
<QtMoc Include="qthostinterface.h" />
|
||||
<QtMoc Include="qthost.h" />
|
||||
<QtMoc Include="settingsdialog.h" />
|
||||
<QtMoc Include="hotkeysettingswidget.h" />
|
||||
<QtMoc Include="inputbindingwidgets.h" />
|
||||
<QtMoc Include="audiosettingswidget.h" />
|
||||
<QtMoc Include="qtdisplaywidget.h" />
|
||||
<QtMoc Include="displaywidget.h" />
|
||||
<QtMoc Include="generalsettingswidget.h" />
|
||||
<QtMoc Include="qtprogresscallback.h" />
|
||||
<QtMoc Include="advancedsettingswidget.h" />
|
||||
<QtMoc Include="gamepropertiesdialog.h" />
|
||||
<QtMoc Include="gdbconnection.h" />
|
||||
<QtMoc Include="gdbserver.h" />
|
||||
<QtMoc Include="controllersettingswidget.h" />
|
||||
<QtMoc Include="aboutdialog.h" />
|
||||
<QtMoc Include="memorycardsettingswidget.h" />
|
||||
<QtMoc Include="inputbindingdialog.h" />
|
||||
@@ -138,6 +144,12 @@
|
||||
<QtMoc Include="achievementsettingswidget.h" />
|
||||
<QtMoc Include="achievementlogindialog.h" />
|
||||
<QtMoc Include="collapsiblewidget.h" />
|
||||
<QtMoc Include="controllerbindingwidgets.h" />
|
||||
<QtMoc Include="controllerglobalsettingswidget.h" />
|
||||
<QtMoc Include="controllersettingsdialog.h" />
|
||||
<QtMoc Include="gamelistrefreshthread.h" />
|
||||
<QtMoc Include="gamesummarywidget.h" />
|
||||
<QtMoc Include="foldersettingswidget.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUi Include="consolesettingswidget.ui" />
|
||||
@@ -147,7 +159,6 @@
|
||||
<QtUi Include="audiosettingswidget.ui" />
|
||||
<QtUi Include="generalsettingswidget.ui" />
|
||||
<QtUi Include="advancedsettingswidget.ui" />
|
||||
<QtUi Include="gamepropertiesdialog.ui" />
|
||||
<QtUi Include="aboutdialog.ui" />
|
||||
<QtUi Include="inputbindingdialog.ui" />
|
||||
<QtUi Include="autoupdaterdialog.ui" />
|
||||
@@ -162,6 +173,19 @@
|
||||
<QtUi Include="emulationsettingswidget.ui" />
|
||||
<QtUi Include="achievementsettingswidget.ui" />
|
||||
<QtUi Include="achievementlogindialog.ui" />
|
||||
<QtUi Include="debuggerwindow.ui" />
|
||||
<QtUi Include="controllerbindingwidget.ui" />
|
||||
<QtUi Include="controllerbindingwidget_analog_controller.ui" />
|
||||
<QtUi Include="controllerglobalsettingswidget.ui" />
|
||||
<QtUi Include="controllersettingsdialog.ui" />
|
||||
<QtUi Include="controllerbindingwidget_digital_controller.ui" />
|
||||
<QtUi Include="emptygamelistwidget.ui" />
|
||||
<QtUi Include="gamelistwidget.ui" />
|
||||
<QtUi Include="gamesummarywidget.ui" />
|
||||
<QtUi Include="foldersettingswidget.ui" />
|
||||
<QtUi Include="controllerbindingwidget_analog_joystick.ui" />
|
||||
<QtUi Include="controllerbindingwidget_negcon.ui" />
|
||||
<QtUi Include="controllerbindingwidget_guncon.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="qt5.natvis" />
|
||||
@@ -178,7 +202,6 @@
|
||||
</QtResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="debuggerwindow.ui" />
|
||||
<None Include="translations\duckstation-qt_tr.ts">
|
||||
<Filter>translations</Filter>
|
||||
</None>
|
||||
|
||||
141
src/duckstation-qt/emptygamelistwidget.ui
Normal file
141
src/duckstation-qt/emptygamelistwidget.ui
Normal file
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>EmptyGameListWidget</class>
|
||||
<widget class="QWidget" name="EmptyGameListWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>687</width>
|
||||
<height>470</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:700;">No games in supported formats were found.</span></p><p>Please add a directory with games to begin.</p><p>Game dumps in the following formats will be scanned and listed:</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="supportedFormats">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addGameDirectory">
|
||||
<property name="text">
|
||||
<string>Add Game Directory...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanForNewGames">
|
||||
<property name="text">
|
||||
<string>Scan For New Games</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "emulationsettingswidget.h"
|
||||
#include "common/make_array.h"
|
||||
#include "core/system.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
@@ -6,37 +7,60 @@
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <limits>
|
||||
|
||||
EmulationSettingsWidget::EmulationSettingsWidget(QtHostInterface* host_interface, QWidget* parent,
|
||||
SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
EmulationSettingsWidget::EmulationSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.rewindEnable, "Main", "RewindEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(m_host_interface, m_ui.rewindSaveFrequency, "Main", "RewindFrequency",
|
||||
10.0f);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.rewindSaveSlots, "Main", "RewindSaveSlots", 10);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.runaheadFrames, "Main", "RunaheadFrameCount", 0);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.rewindEnable, "Main", "RewindEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.rewindSaveFrequency, "Main", "RewindFrequency", 10.0f);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.rewindSaveSlots, "Main", "RewindSaveSlots", 10);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.runaheadFrames, "Main", "RunaheadFrameCount", 0);
|
||||
|
||||
QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.emulationSpeed);
|
||||
const int emulation_speed_index =
|
||||
m_ui.emulationSpeed->findData(QVariant(m_host_interface->GetFloatSettingValue("Main", "EmulationSpeed", 1.0f)));
|
||||
if (emulation_speed_index >= 0)
|
||||
m_ui.emulationSpeed->setCurrentIndex(emulation_speed_index);
|
||||
const float effective_emulation_speed = m_dialog->getEffectiveFloatValue("Main", "EmulationSpeed", 1.0f);
|
||||
fillComboBoxWithEmulationSpeeds(m_ui.emulationSpeed, effective_emulation_speed);
|
||||
if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "EmulationSpeed", std::nullopt).has_value())
|
||||
{
|
||||
m_ui.emulationSpeed->setCurrentIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int emulation_speed_index = m_ui.emulationSpeed->findData(QVariant(effective_emulation_speed));
|
||||
if (emulation_speed_index >= 0)
|
||||
m_ui.emulationSpeed->setCurrentIndex(emulation_speed_index);
|
||||
}
|
||||
connect(m_ui.emulationSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&EmulationSettingsWidget::onEmulationSpeedIndexChanged);
|
||||
QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.fastForwardSpeed);
|
||||
const int fast_forward_speed_index =
|
||||
m_ui.fastForwardSpeed->findData(QVariant(m_host_interface->GetFloatSettingValue("Main", "FastForwardSpeed", 0.0f)));
|
||||
if (fast_forward_speed_index >= 0)
|
||||
m_ui.fastForwardSpeed->setCurrentIndex(fast_forward_speed_index);
|
||||
|
||||
const float effective_fast_forward_speed = m_dialog->getEffectiveFloatValue("Main", "FastForwardSpeed", 0.0f);
|
||||
fillComboBoxWithEmulationSpeeds(m_ui.fastForwardSpeed, effective_fast_forward_speed);
|
||||
if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "FastForwardSpeed", std::nullopt).has_value())
|
||||
{
|
||||
m_ui.emulationSpeed->setCurrentIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int fast_forward_speed_index = m_ui.fastForwardSpeed->findData(QVariant(effective_fast_forward_speed));
|
||||
if (fast_forward_speed_index >= 0)
|
||||
m_ui.fastForwardSpeed->setCurrentIndex(fast_forward_speed_index);
|
||||
}
|
||||
connect(m_ui.fastForwardSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&EmulationSettingsWidget::onFastForwardSpeedIndexChanged);
|
||||
QtUtils::FillComboBoxWithEmulationSpeeds(m_ui.turboSpeed);
|
||||
const int turbo_speed_index =
|
||||
m_ui.turboSpeed->findData(QVariant(m_host_interface->GetFloatSettingValue("Main", "TurboSpeed", 0.0f)));
|
||||
if (turbo_speed_index >= 0)
|
||||
m_ui.turboSpeed->setCurrentIndex(turbo_speed_index);
|
||||
|
||||
const float effective_turbo_speed = m_dialog->getEffectiveFloatValue("Main", "TurboSpeed", 0.0f);
|
||||
fillComboBoxWithEmulationSpeeds(m_ui.turboSpeed, effective_turbo_speed);
|
||||
if (m_dialog->isPerGameSettings() && !m_dialog->getFloatValue("Main", "TurboSpeed", std::nullopt).has_value())
|
||||
{
|
||||
m_ui.emulationSpeed->setCurrentIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int turbo_speed_index = m_ui.turboSpeed->findData(QVariant(effective_turbo_speed));
|
||||
if (turbo_speed_index >= 0)
|
||||
m_ui.turboSpeed->setCurrentIndex(turbo_speed_index);
|
||||
}
|
||||
connect(m_ui.turboSpeed, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&EmulationSettingsWidget::onTurboSpeedIndexChanged);
|
||||
|
||||
@@ -62,46 +86,87 @@ EmulationSettingsWidget::EmulationSettingsWidget(QtHostInterface* host_interface
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.rewindEnable, tr("Rewinding"), tr("Unchecked"),
|
||||
tr("<b>Enable Rewinding:</b> Saves state periodically so you can rewind any mistakes while playing.<br> "
|
||||
"<b>Rewind Save Frequency:</b> How often a rewind state will be created. Higher frequencies have greater system requirements.<br> "
|
||||
"<b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory requirements."));
|
||||
"<b>Rewind Save Frequency:</b> How often a rewind state will be created. Higher frequencies have greater system "
|
||||
"requirements.<br> "
|
||||
"<b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory "
|
||||
"requirements."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.runaheadFrames, tr("Runahead"), tr("Disabled"),
|
||||
tr("Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."));
|
||||
tr(
|
||||
"Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements."));
|
||||
|
||||
updateRewind();
|
||||
}
|
||||
|
||||
EmulationSettingsWidget::~EmulationSettingsWidget() = default;
|
||||
|
||||
void EmulationSettingsWidget::fillComboBoxWithEmulationSpeeds(QComboBox* cb, float global_value)
|
||||
{
|
||||
if (m_dialog->isPerGameSettings())
|
||||
{
|
||||
if (global_value == 0.0f)
|
||||
cb->addItem(tr("Use Global Setting [Unlimited]"));
|
||||
else
|
||||
cb->addItem(tr("Use Global Setting [%1%]").arg(static_cast<u32>(global_value * 100.0f)));
|
||||
}
|
||||
|
||||
cb->addItem(tr("Unlimited"), QVariant(0.0f));
|
||||
|
||||
static constexpr auto speeds = make_array(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 250, 300, 350,
|
||||
400, 450, 500, 600, 700, 800, 900, 1000);
|
||||
for (const int speed : speeds)
|
||||
{
|
||||
cb->addItem(tr("%1% [%2 FPS (NTSC) / %3 FPS (PAL)]").arg(speed).arg((60 * speed) / 100).arg((50 * speed) / 100),
|
||||
QVariant(static_cast<float>(speed) / 100.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSettingsWidget::onEmulationSpeedIndexChanged(int index)
|
||||
{
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("Main", "EmulationSpeed");
|
||||
return;
|
||||
}
|
||||
|
||||
bool okay;
|
||||
const float value = m_ui.emulationSpeed->currentData().toFloat(&okay);
|
||||
m_host_interface->SetFloatSettingValue("Main", "EmulationSpeed", okay ? value : 1.0f);
|
||||
m_host_interface->applySettings();
|
||||
m_dialog->setFloatSettingValue("Main", "EmulationSpeed", okay ? value : 1.0f);
|
||||
}
|
||||
|
||||
void EmulationSettingsWidget::onFastForwardSpeedIndexChanged(int index)
|
||||
{
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("Main", "FastForwardSpeed");
|
||||
return;
|
||||
}
|
||||
|
||||
bool okay;
|
||||
const float value = m_ui.fastForwardSpeed->currentData().toFloat(&okay);
|
||||
m_host_interface->SetFloatSettingValue("Main", "FastForwardSpeed", okay ? value : 0.0f);
|
||||
m_host_interface->applySettings();
|
||||
m_dialog->setFloatSettingValue("Main", "FastForwardSpeed", okay ? value : 0.0f);
|
||||
}
|
||||
|
||||
void EmulationSettingsWidget::onTurboSpeedIndexChanged(int index)
|
||||
{
|
||||
if (m_dialog->isPerGameSettings() && index == 0)
|
||||
{
|
||||
m_dialog->removeSettingValue("Main", "TurboSpeed");
|
||||
return;
|
||||
}
|
||||
|
||||
bool okay;
|
||||
const float value = m_ui.turboSpeed->currentData().toFloat(&okay);
|
||||
m_host_interface->SetFloatSettingValue("Main", "TurboSpeed", okay ? value : 0.0f);
|
||||
m_host_interface->applySettings();
|
||||
m_dialog->setFloatSettingValue("Main", "TurboSpeed", okay ? value : 0.0f);
|
||||
}
|
||||
|
||||
void EmulationSettingsWidget::updateRewind()
|
||||
{
|
||||
m_ui.rewindEnable->setEnabled(!runaheadEnabled());
|
||||
const bool rewind_enabled = m_dialog->getEffectiveBoolValue("Main", "RewindEnable", false);
|
||||
const bool runahead_enabled = m_dialog->getIntValue("Main", "RunaheadFrameCount", 0) > 0;
|
||||
m_ui.rewindEnable->setEnabled(!runahead_enabled);
|
||||
|
||||
if (m_ui.rewindEnable->isEnabled() && m_ui.rewindEnable->isChecked())
|
||||
if (!runahead_enabled && rewind_enabled)
|
||||
{
|
||||
const u32 frames = static_cast<u32>(m_ui.rewindSaveSlots->value());
|
||||
const float frequency = static_cast<float>(m_ui.rewindSaveFrequency->value());
|
||||
@@ -121,7 +186,7 @@ void EmulationSettingsWidget::updateRewind()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_ui.rewindEnable->isEnabled())
|
||||
if (runahead_enabled)
|
||||
{
|
||||
m_ui.rewindSummary->setText(tr(
|
||||
"Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements."));
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_emulationsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class EmulationSettingsWidget : public QWidget
|
||||
@@ -12,7 +11,7 @@ class EmulationSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmulationSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit EmulationSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~EmulationSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -22,9 +21,9 @@ private Q_SLOTS:
|
||||
void updateRewind();
|
||||
|
||||
private:
|
||||
bool runaheadEnabled() { return m_ui.runaheadFrames->currentIndex() > 0; }
|
||||
void fillComboBoxWithEmulationSpeeds(QComboBox* cb, float global_value);
|
||||
|
||||
Ui::EmulationSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -5,39 +5,40 @@
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
EnhancementSettingsWidget::EnhancementSettingsWidget(QtHostInterface* host_interface, QWidget* parent,
|
||||
SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
setupAdditionalUi();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.trueColor, "GPU", "TrueColor", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.scaledDithering, "GPU", "ScaledDithering", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.disableInterlacing, "GPU", "DisableInterlacing",
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.scaledDithering, "GPU", "ScaledDithering", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing",
|
||||
true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.force43For24Bit, "Display", "Force4_3For24Bit",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.force43For24Bit, "Display", "Force4_3For24Bit",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.chromaSmoothingFor24Bit, "GPU",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.chromaSmoothingFor24Bit, "GPU",
|
||||
"ChromaSmoothing24Bit", false);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.textureFiltering, "GPU", "TextureFilter",
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter",
|
||||
&Settings::ParseTextureFilterName, &Settings::GetTextureFilterName,
|
||||
Settings::DEFAULT_GPU_TEXTURE_FILTER);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.widescreenHack, "GPU", "WidescreenHack", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.useSoftwareRendererForReadbacks, "GPU",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.widescreenHack, "GPU", "WidescreenHack", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForReadbacks, "GPU",
|
||||
"UseSoftwareRendererForReadbacks", false);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpEnable, "GPU", "PGXPEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCulling, "GPU", "PGXPCulling", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpTextureCorrection, "GPU",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpEnable, "GPU", "PGXPEnable", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCulling, "GPU", "PGXPCulling", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpTextureCorrection, "GPU",
|
||||
"PGXPTextureCorrection", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpDepthBuffer, "GPU", "PGXPDepthBuffer", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpPreserveProjPrecision, "GPU",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpDepthBuffer, "GPU", "PGXPDepthBuffer", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpPreserveProjPrecision, "GPU",
|
||||
"PGXPPreserveProjFP", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pgxpCPU, "GPU", "PGXPCPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCPU, "GPU", "PGXPCPU", false);
|
||||
|
||||
connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&EnhancementSettingsWidget::updateScaledDitheringEnabled);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_enhancementsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class EnhancementSettingsWidget : public QWidget
|
||||
@@ -12,7 +11,7 @@ class EnhancementSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EnhancementSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
EnhancementSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~EnhancementSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -24,5 +23,5 @@ private:
|
||||
|
||||
Ui::EnhancementSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
24
src/duckstation-qt/foldersettingswidget.cpp
Normal file
24
src/duckstation-qt/foldersettingswidget.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <algorithm>
|
||||
|
||||
#include "foldersettingswidget.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
FolderSettingsWidget::FolderSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cache, m_ui.cacheBrowse, m_ui.cacheOpen, m_ui.cacheReset,
|
||||
"Folders", "Cache", "cache");
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset,
|
||||
"Folders", "Covers", "covers");
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.screenshots, m_ui.screenshotsBrowse, m_ui.screenshotsOpen,
|
||||
m_ui.screenshotsReset, "Folders", "Screenshots", "screenshots");
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen,
|
||||
m_ui.saveStatesReset, "Folders", "SaveStates", "savestates");
|
||||
}
|
||||
|
||||
FolderSettingsWidget::~FolderSettingsWidget() = default;
|
||||
19
src/duckstation-qt/foldersettingswidget.h
Normal file
19
src/duckstation-qt/foldersettingswidget.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include "ui_foldersettingswidget.h"
|
||||
|
||||
class SettingsDialog;
|
||||
|
||||
class FolderSettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FolderSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~FolderSettingsWidget();
|
||||
|
||||
private:
|
||||
Ui::FolderSettingsWidget m_ui;
|
||||
};
|
||||
208
src/duckstation-qt/foldersettingswidget.ui
Normal file
208
src/duckstation-qt/foldersettingswidget.ui
Normal file
@@ -0,0 +1,208 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FolderSettingsWidget</class>
|
||||
<widget class="QWidget" name="FolderSettingsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>648</width>
|
||||
<height>487</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Cache Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="cache"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="cacheBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="cacheOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="cacheReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Used for storing shaders and game list data.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Covers Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="covers"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="coversBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="coversOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="coversReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Used for storing covers in the game grid/Big Picture UIs.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Screenshots Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="screenshots"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="screenshotsBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="screenshotsOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="screenshotsReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Used for screenshots.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Save States Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="saveStates"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="saveStatesBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="saveStatesOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="saveStatesReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Used for storing save states.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>132</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../p2-qt-rebase/pcsx2-qt/resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -3,19 +3,25 @@
|
||||
#include "common/path.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/system.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtCore/QDate>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtCore/QFutureWatcher>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
static constexpr std::array<const char*, GameListModel::Column_Count> s_column_names = {
|
||||
{"Type", "Code", "Title", "File Title", "Developer", "Publisher", "Genre", "Year", "Players", "Size", "Region",
|
||||
{"Type", "Serial", "Title", "File Title", "Developer", "Publisher", "Genre", "Year", "Players", "Size", "Region",
|
||||
"Compatibility", "Cover"}};
|
||||
|
||||
static constexpr int COVER_ART_WIDTH = 512;
|
||||
static constexpr int COVER_ART_HEIGHT = 512;
|
||||
static constexpr int COVER_ART_SPACING = 32;
|
||||
static constexpr int MIN_COVER_CACHE_SIZE = 256;
|
||||
|
||||
static int DPRScale(int size, float dpr)
|
||||
{
|
||||
@@ -62,10 +68,11 @@ static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_hei
|
||||
*pm = padded_image;
|
||||
}
|
||||
|
||||
static QPixmap createPlaceholderImage(int width, int height, float scale, const std::string& title)
|
||||
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
|
||||
const std::string& title)
|
||||
{
|
||||
const float dpr = qApp->devicePixelRatio();
|
||||
QPixmap pm(QStringLiteral(":/icons/cover-placeholder.png"));
|
||||
QPixmap pm(placeholder_pixmap.copy());
|
||||
pm.setDevicePixelRatio(dpr);
|
||||
if (pm.isNull())
|
||||
return QPixmap();
|
||||
@@ -104,8 +111,8 @@ const char* GameListModel::getColumnName(Column col)
|
||||
return s_column_names[static_cast<int>(col)];
|
||||
}
|
||||
|
||||
GameListModel::GameListModel(GameList* game_list, QObject* parent /* = nullptr */)
|
||||
: QAbstractTableModel(parent), m_game_list(game_list)
|
||||
GameListModel::GameListModel(QObject* parent /* = nullptr */)
|
||||
: QAbstractTableModel(parent), m_cover_pixmap_cache(MIN_COVER_CACHE_SIZE)
|
||||
{
|
||||
loadCommonImages();
|
||||
setColumnDisplayNames();
|
||||
@@ -117,16 +124,83 @@ void GameListModel::setCoverScale(float scale)
|
||||
if (m_cover_scale == scale)
|
||||
return;
|
||||
|
||||
m_cover_pixmap_cache.clear();
|
||||
m_cover_pixmap_cache.Clear();
|
||||
m_cover_scale = scale;
|
||||
m_loading_pixmap = QPixmap(getCoverArtWidth(), getCoverArtHeight());
|
||||
m_loading_pixmap.fill(QColor(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
void GameListModel::refreshCovers()
|
||||
{
|
||||
m_cover_pixmap_cache.clear();
|
||||
m_cover_pixmap_cache.Clear();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void GameListModel::updateCacheSize(int width, int height)
|
||||
{
|
||||
// This is a bit conversative, since it doesn't consider padding, but better to be over than under.
|
||||
const int cover_width = getCoverArtWidth();
|
||||
const int cover_height = getCoverArtHeight();
|
||||
const int num_columns = ((width + (cover_width - 1)) / cover_width);
|
||||
const int num_rows = ((height + (cover_height - 1)) / cover_height);
|
||||
m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE)));
|
||||
}
|
||||
|
||||
void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
|
||||
{
|
||||
QFuture<QPixmap> future =
|
||||
QtConcurrent::run([this, path = ge->path, title = ge->title, serial = ge->serial]() -> QPixmap {
|
||||
QPixmap image;
|
||||
const std::string cover_path(GameList::GetCoverImagePath(path, serial, title));
|
||||
if (!cover_path.empty())
|
||||
{
|
||||
const float dpr = qApp->devicePixelRatio();
|
||||
image = QPixmap(QString::fromStdString(cover_path));
|
||||
if (!image.isNull())
|
||||
{
|
||||
image.setDevicePixelRatio(dpr);
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr);
|
||||
}
|
||||
}
|
||||
|
||||
if (image.isNull())
|
||||
image =
|
||||
createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, title);
|
||||
|
||||
return image;
|
||||
});
|
||||
|
||||
// Context must be 'this' so we run on the UI thread.
|
||||
future.then(this, [this, path = ge->path](QPixmap pm) {
|
||||
m_cover_pixmap_cache.Insert(std::move(path), std::move(pm));
|
||||
invalidateCoverForPath(path);
|
||||
});
|
||||
}
|
||||
|
||||
void GameListModel::invalidateCoverForPath(const std::string& path)
|
||||
{
|
||||
// This isn't ideal, but not sure how else we can get the row, when it might change while scanning...
|
||||
auto lock = GameList::GetLock();
|
||||
const u32 count = GameList::GetEntryCount();
|
||||
std::optional<u32> row;
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
if (GameList::GetEntryByIndex(i)->path == path)
|
||||
{
|
||||
row = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!row.has_value())
|
||||
{
|
||||
// Game removed?
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex mi(index(static_cast<int>(row.value()), Column_Cover));
|
||||
emit dataChanged(mi, mi, {Qt::DecorationRole});
|
||||
}
|
||||
|
||||
int GameListModel::getCoverArtWidth() const
|
||||
{
|
||||
return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1);
|
||||
@@ -147,7 +221,7 @@ int GameListModel::rowCount(const QModelIndex& parent) const
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return static_cast<int>(m_game_list->GetEntryCount());
|
||||
return static_cast<int>(GameList::GetEntryCount());
|
||||
}
|
||||
|
||||
int GameListModel::columnCount(const QModelIndex& parent) const
|
||||
@@ -164,10 +238,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
|
||||
const int row = index.row();
|
||||
if (row < 0 || row >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (row < 0 || row >= static_cast<int>(GameList::GetEntryCount()))
|
||||
return {};
|
||||
|
||||
const GameListEntry& ge = m_game_list->GetEntries()[row];
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* ge = GameList::GetEntryByIndex(row);
|
||||
if (!ge)
|
||||
return {};
|
||||
|
||||
switch (role)
|
||||
{
|
||||
@@ -175,33 +252,30 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
switch (index.column())
|
||||
{
|
||||
case Column_Code:
|
||||
return QString::fromStdString(ge.code);
|
||||
case Column_Serial:
|
||||
return QString::fromStdString(ge->serial);
|
||||
|
||||
case Column_Title:
|
||||
return QString::fromStdString(ge.title);
|
||||
return QString::fromStdString(ge->title);
|
||||
|
||||
case Column_FileTitle:
|
||||
{
|
||||
const std::string_view file_title(Path::GetFileTitle(ge.path));
|
||||
return QString::fromUtf8(file_title.data(), static_cast<int>(file_title.length()));
|
||||
}
|
||||
return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path));
|
||||
|
||||
case Column_Developer:
|
||||
return QString::fromStdString(ge.developer);
|
||||
return QString::fromStdString(ge->developer);
|
||||
|
||||
case Column_Publisher:
|
||||
return QString::fromStdString(ge.publisher);
|
||||
return QString::fromStdString(ge->publisher);
|
||||
|
||||
case Column_Genre:
|
||||
return QString::fromStdString(ge.genre);
|
||||
return QString::fromStdString(ge->genre);
|
||||
|
||||
case Column_Year:
|
||||
{
|
||||
if (ge.release_date != 0)
|
||||
if (ge->release_date != 0)
|
||||
{
|
||||
return QStringLiteral("%1").arg(
|
||||
QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge.release_date), Qt::UTC).date().year());
|
||||
QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge->release_date), Qt::UTC).date().year());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -211,19 +285,19 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
|
||||
case Column_Players:
|
||||
{
|
||||
if (ge.min_players == ge.max_players)
|
||||
return QStringLiteral("%1").arg(ge.min_players);
|
||||
if (ge->min_players == ge->max_players)
|
||||
return QStringLiteral("%1").arg(ge->min_players);
|
||||
else
|
||||
return QStringLiteral("%1-%2").arg(ge.min_players).arg(ge.max_players);
|
||||
return QStringLiteral("%1-%2").arg(ge->min_players).arg(ge->max_players);
|
||||
}
|
||||
|
||||
case Column_Size:
|
||||
return QString("%1 MB").arg(static_cast<double>(ge.total_size) / 1048576.0, 0, 'f', 2);
|
||||
return QString("%1 MB").arg(static_cast<double>(ge->total_size) / 1048576.0, 0, 'f', 2);
|
||||
|
||||
case Column_Cover:
|
||||
{
|
||||
if (m_show_titles_for_covers)
|
||||
return QString::fromStdString(ge.title);
|
||||
return QString::fromStdString(ge->title);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
@@ -238,44 +312,41 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
switch (index.column())
|
||||
{
|
||||
case Column_Type:
|
||||
return static_cast<int>(ge.type);
|
||||
return static_cast<int>(ge->type);
|
||||
|
||||
case Column_Code:
|
||||
return QString::fromStdString(ge.code);
|
||||
case Column_Serial:
|
||||
return QString::fromStdString(ge->serial);
|
||||
|
||||
case Column_Title:
|
||||
case Column_Cover:
|
||||
return QString::fromStdString(ge.title);
|
||||
return QString::fromStdString(ge->title);
|
||||
|
||||
case Column_FileTitle:
|
||||
{
|
||||
const std::string_view file_title(Path::GetFileTitle(ge.path));
|
||||
return QString::fromUtf8(file_title.data(), static_cast<int>(file_title.length()));
|
||||
}
|
||||
return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path));
|
||||
|
||||
case Column_Developer:
|
||||
return QString::fromStdString(ge.developer);
|
||||
return QString::fromStdString(ge->developer);
|
||||
|
||||
case Column_Publisher:
|
||||
return QString::fromStdString(ge.publisher);
|
||||
return QString::fromStdString(ge->publisher);
|
||||
|
||||
case Column_Genre:
|
||||
return QString::fromStdString(ge.genre);
|
||||
return QString::fromStdString(ge->genre);
|
||||
|
||||
case Column_Year:
|
||||
return QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge.release_date), Qt::UTC).date().year();
|
||||
return QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ge->release_date), Qt::UTC).date().year();
|
||||
|
||||
case Column_Players:
|
||||
return static_cast<int>(ge.max_players);
|
||||
return static_cast<int>(ge->max_players);
|
||||
|
||||
case Column_Region:
|
||||
return static_cast<int>(ge.region);
|
||||
return static_cast<int>(ge->region);
|
||||
|
||||
case Column_Compatibility:
|
||||
return static_cast<int>(ge.compatibility_rating);
|
||||
return static_cast<int>(ge->compatibility);
|
||||
|
||||
case Column_Size:
|
||||
return static_cast<qulonglong>(ge.total_size);
|
||||
return static_cast<qulonglong>(ge->total_size);
|
||||
|
||||
default:
|
||||
return {};
|
||||
@@ -288,67 +359,30 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
case Column_Type:
|
||||
{
|
||||
switch (ge.type)
|
||||
{
|
||||
case GameListEntryType::Disc:
|
||||
return ((ge.settings.GetUserSettingsCount() > 0) ? m_type_disc_with_settings_pixmap : m_type_disc_pixmap);
|
||||
case GameListEntryType::Playlist:
|
||||
return m_type_playlist_pixmap;
|
||||
case GameListEntryType::PSF:
|
||||
return m_type_psf_pixmap;
|
||||
case GameListEntryType::PSExe:
|
||||
default:
|
||||
return m_type_exe_pixmap;
|
||||
}
|
||||
// TODO: Test for settings
|
||||
return m_type_pixmaps[static_cast<u32>(ge->type)];
|
||||
}
|
||||
|
||||
case Column_Region:
|
||||
{
|
||||
switch (ge.region)
|
||||
{
|
||||
case DiscRegion::NTSC_J:
|
||||
return m_region_jp_pixmap;
|
||||
case DiscRegion::NTSC_U:
|
||||
return m_region_us_pixmap;
|
||||
case DiscRegion::Other:
|
||||
return m_region_other_pixmap;
|
||||
case DiscRegion::PAL:
|
||||
default:
|
||||
return m_region_eu_pixmap;
|
||||
}
|
||||
return m_region_pixmaps[static_cast<u32>(ge->region)];
|
||||
}
|
||||
|
||||
case Column_Compatibility:
|
||||
{
|
||||
return m_compatibiliy_pixmaps[static_cast<int>(
|
||||
(ge.compatibility_rating >= GameListCompatibilityRating::Count) ? GameListCompatibilityRating::Unknown :
|
||||
ge.compatibility_rating)];
|
||||
return m_compatibility_pixmaps[static_cast<u32>(ge->compatibility)];
|
||||
}
|
||||
|
||||
case Column_Cover:
|
||||
{
|
||||
auto it = m_cover_pixmap_cache.find(ge.path);
|
||||
if (it != m_cover_pixmap_cache.end())
|
||||
return it->second;
|
||||
QPixmap* pm = m_cover_pixmap_cache.Lookup(ge->path);
|
||||
if (pm)
|
||||
return *pm;
|
||||
|
||||
QPixmap image;
|
||||
std::string path = m_game_list->GetCoverImagePathForEntry(&ge);
|
||||
if (!path.empty())
|
||||
{
|
||||
const float dpr = qApp->devicePixelRatio();
|
||||
image = QPixmap(QString::fromStdString(path));
|
||||
if (!image.isNull())
|
||||
{
|
||||
image.setDevicePixelRatio(dpr);
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr);
|
||||
}
|
||||
}
|
||||
|
||||
if (image.isNull())
|
||||
image = createPlaceholderImage(getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, ge.title);
|
||||
|
||||
m_cover_pixmap_cache.emplace(ge.path, image);
|
||||
return image;
|
||||
// We insert the placeholder into the cache, so that we don't repeatedly
|
||||
// queue loading jobs for this game.
|
||||
const_cast<GameListModel*>(this)->loadOrGenerateCover(ge);
|
||||
return *m_cover_pixmap_cache.Insert(ge->path, m_loading_pixmap);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -378,15 +412,15 @@ void GameListModel::refresh()
|
||||
|
||||
bool GameListModel::titlesLessThan(int left_row, int right_row) const
|
||||
{
|
||||
if (left_row < 0 || left_row >= static_cast<int>(m_game_list->GetEntryCount()) || right_row < 0 ||
|
||||
right_row >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
|
||||
right_row >= static_cast<int>(GameList::GetEntryCount()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const GameListEntry& left = m_game_list->GetEntries().at(left_row);
|
||||
const GameListEntry& right = m_game_list->GetEntries().at(right_row);
|
||||
return (StringUtil::Strcasecmp(left.title.c_str(), right.title.c_str()) < 0);
|
||||
const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
|
||||
const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
|
||||
return (StringUtil::Strcasecmp(left->title.c_str(), right->title.c_str()) < 0);
|
||||
}
|
||||
|
||||
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const
|
||||
@@ -396,29 +430,33 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
|
||||
const int left_row = left_index.row();
|
||||
const int right_row = right_index.row();
|
||||
if (left_row < 0 || left_row >= static_cast<int>(m_game_list->GetEntryCount()) || right_row < 0 ||
|
||||
right_row >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
|
||||
right_row >= static_cast<int>(GameList::GetEntryCount()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const GameListEntry& left = m_game_list->GetEntries()[left_row];
|
||||
const GameListEntry& right = m_game_list->GetEntries()[right_row];
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
|
||||
const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
|
||||
if (!left || !right)
|
||||
return false;
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case Column_Type:
|
||||
{
|
||||
if (left.type == right.type)
|
||||
if (left->type == right->type)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (static_cast<int>(left.type) < static_cast<int>(right.type));
|
||||
return (static_cast<int>(left->type) < static_cast<int>(right->type));
|
||||
}
|
||||
|
||||
case Column_Code:
|
||||
case Column_Serial:
|
||||
{
|
||||
if (left.code == right.code)
|
||||
if (left->serial == right->serial)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
return (StringUtil::Strcasecmp(left.code.c_str(), right.code.c_str()) < 0);
|
||||
return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0);
|
||||
}
|
||||
|
||||
case Column_Title:
|
||||
@@ -428,8 +466,8 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
|
||||
case Column_FileTitle:
|
||||
{
|
||||
const std::string_view file_title_left(Path::GetFileTitle(left.path));
|
||||
const std::string_view file_title_right(Path::GetFileTitle(right.path));
|
||||
const std::string_view file_title_left(Path::GetFileTitle(left->path));
|
||||
const std::string_view file_title_right(Path::GetFileTitle(right->path));
|
||||
if (file_title_left == file_title_right)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
@@ -439,60 +477,60 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
|
||||
case Column_Region:
|
||||
{
|
||||
if (left.region == right.region)
|
||||
if (left->region == right->region)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
return (static_cast<int>(left.region) < static_cast<int>(right.region));
|
||||
return (static_cast<int>(left->region) < static_cast<int>(right->region));
|
||||
}
|
||||
|
||||
case Column_Compatibility:
|
||||
{
|
||||
if (left.compatibility_rating == right.compatibility_rating)
|
||||
if (left->compatibility == right->compatibility)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (static_cast<int>(left.compatibility_rating) < static_cast<int>(right.compatibility_rating));
|
||||
return (static_cast<int>(left->compatibility) < static_cast<int>(right->compatibility));
|
||||
}
|
||||
|
||||
case Column_Size:
|
||||
{
|
||||
if (left.total_size == right.total_size)
|
||||
if (left->total_size == right->total_size)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (left.total_size < right.total_size);
|
||||
return (left->total_size < right->total_size);
|
||||
}
|
||||
|
||||
case Column_Genre:
|
||||
{
|
||||
if (left.genre == right.genre)
|
||||
if (left->genre == right->genre)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
return (StringUtil::Strcasecmp(left.genre.c_str(), right.genre.c_str()) < 0);
|
||||
return (StringUtil::Strcasecmp(left->genre.c_str(), right->genre.c_str()) < 0);
|
||||
}
|
||||
|
||||
case Column_Developer:
|
||||
{
|
||||
if (left.developer == right.developer)
|
||||
if (left->developer == right->developer)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
return (StringUtil::Strcasecmp(left.developer.c_str(), right.developer.c_str()) < 0);
|
||||
return (StringUtil::Strcasecmp(left->developer.c_str(), right->developer.c_str()) < 0);
|
||||
}
|
||||
|
||||
case Column_Publisher:
|
||||
{
|
||||
if (left.publisher == right.publisher)
|
||||
if (left->publisher == right->publisher)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
return (StringUtil::Strcasecmp(left.publisher.c_str(), right.publisher.c_str()) < 0);
|
||||
return (StringUtil::Strcasecmp(left->publisher.c_str(), right->publisher.c_str()) < 0);
|
||||
}
|
||||
|
||||
case Column_Year:
|
||||
{
|
||||
if (left.release_date == right.release_date)
|
||||
if (left->release_date == right->release_date)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (left.release_date < right.release_date);
|
||||
return (left->release_date < right->release_date);
|
||||
}
|
||||
|
||||
case Column_Players:
|
||||
{
|
||||
u8 left_players = (left.min_players << 4) + left.max_players;
|
||||
u8 right_players = (right.min_players << 4) + right.max_players;
|
||||
u8 left_players = (left->min_players << 4) + left->max_players;
|
||||
u8 right_players = (right->min_players << 4) + right->max_players;
|
||||
if (left_players == right_players)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
@@ -506,25 +544,23 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
|
||||
void GameListModel::loadCommonImages()
|
||||
{
|
||||
// TODO: Use svg instead of png
|
||||
m_type_disc_pixmap = QIcon(QStringLiteral(":/icons/media-optical-24.png")).pixmap(QSize(24, 24));
|
||||
m_type_disc_with_settings_pixmap = QIcon(QStringLiteral(":/icons/media-optical-gear-24.png")).pixmap(QSize(24, 24));
|
||||
m_type_exe_pixmap = QIcon(QStringLiteral(":/icons/applications-system-24.png")).pixmap(QSize(24, 24));
|
||||
m_type_playlist_pixmap = QIcon(QStringLiteral(":/icons/address-book-new-22.png")).pixmap(QSize(22, 22));
|
||||
m_type_psf_pixmap = QIcon(QStringLiteral(":/icons/multimedia-player.png")).pixmap(QSize(22, 22));
|
||||
m_region_eu_pixmap = QIcon(QStringLiteral(":/icons/flag-eu.png")).pixmap(QSize(42, 30));
|
||||
m_region_jp_pixmap = QIcon(QStringLiteral(":/icons/flag-jp.png")).pixmap(QSize(42, 30));
|
||||
m_region_us_pixmap = QIcon(QStringLiteral(":/icons/flag-uc.png")).pixmap(QSize(42, 30));
|
||||
m_region_other_pixmap = QIcon(QStringLiteral(":/icons/flag-other.png")).pixmap(QSize(42, 30));
|
||||
for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::Count); i++)
|
||||
m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(i)).pixmap(QSize(24, 24));
|
||||
|
||||
for (int i = 0; i < static_cast<int>(GameListCompatibilityRating::Count); i++)
|
||||
m_compatibiliy_pixmaps[i].load(QStringLiteral(":/icons/star-%1.png").arg(i));
|
||||
for (u32 i = 0; i < static_cast<u32>(DiscRegion::Count); i++)
|
||||
m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast<DiscRegion>(i)).pixmap(42, 30);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(GameDatabase::CompatibilityRating::Count); i++)
|
||||
m_compatibility_pixmaps[i] = QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)).pixmap(96, 24);
|
||||
|
||||
m_placeholder_pixmap.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath()));
|
||||
setCoverScale(1.0f);
|
||||
}
|
||||
|
||||
void GameListModel::setColumnDisplayNames()
|
||||
{
|
||||
m_column_display_names[Column_Type] = tr("Type");
|
||||
m_column_display_names[Column_Code] = tr("Code");
|
||||
m_column_display_names[Column_Serial] = tr("Code");
|
||||
m_column_display_names[Column_Title] = tr("Title");
|
||||
m_column_display_names[Column_FileTitle] = tr("File Title");
|
||||
m_column_display_names[Column_Developer] = tr("Developer");
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
#include "common/heterogeneous_containers.h"
|
||||
#include "common/lru_cache.h"
|
||||
#include "core/game_database.h"
|
||||
#include "core/types.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include <QtCore/QAbstractTableModel>
|
||||
@@ -6,7 +9,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
class GameListModel final : public QAbstractTableModel
|
||||
{
|
||||
@@ -16,7 +18,7 @@ public:
|
||||
enum Column : int
|
||||
{
|
||||
Column_Type,
|
||||
Column_Code,
|
||||
Column_Serial,
|
||||
Column_Title,
|
||||
Column_FileTitle,
|
||||
Column_Developer,
|
||||
@@ -35,7 +37,7 @@ public:
|
||||
static std::optional<Column> getColumnIdForName(std::string_view name);
|
||||
static const char* getColumnName(Column col);
|
||||
|
||||
GameListModel(GameList* game_list, QObject* parent = nullptr);
|
||||
GameListModel(QObject* parent = nullptr);
|
||||
~GameListModel();
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
@@ -60,28 +62,24 @@ public:
|
||||
int getCoverArtHeight() const;
|
||||
int getCoverArtSpacing() const;
|
||||
void refreshCovers();
|
||||
void updateCacheSize(int width, int height);
|
||||
|
||||
private:
|
||||
void loadCommonImages();
|
||||
void setColumnDisplayNames();
|
||||
void loadOrGenerateCover(const GameList::Entry* ge);
|
||||
void invalidateCoverForPath(const std::string& path);
|
||||
|
||||
GameList* m_game_list;
|
||||
float m_cover_scale = 1.0f;
|
||||
float m_cover_scale = 0.0f;
|
||||
bool m_show_titles_for_covers = false;
|
||||
|
||||
std::array<QString, Column_Count> m_column_display_names;
|
||||
std::array<QPixmap, static_cast<int>(GameList::EntryType::Count)> m_type_pixmaps;
|
||||
std::array<QPixmap, static_cast<int>(DiscRegion::Count)> m_region_pixmaps;
|
||||
std::array<QPixmap, static_cast<int>(GameDatabase::CompatibilityRating::Count)> m_compatibility_pixmaps;
|
||||
|
||||
QPixmap m_type_disc_pixmap;
|
||||
QPixmap m_type_disc_with_settings_pixmap;
|
||||
QPixmap m_type_exe_pixmap;
|
||||
QPixmap m_type_playlist_pixmap;
|
||||
QPixmap m_type_psf_pixmap;
|
||||
QPixmap m_placeholder_pixmap;
|
||||
QPixmap m_loading_pixmap;
|
||||
|
||||
QPixmap m_region_jp_pixmap;
|
||||
QPixmap m_region_eu_pixmap;
|
||||
QPixmap m_region_us_pixmap;
|
||||
QPixmap m_region_other_pixmap;
|
||||
|
||||
std::array<QPixmap, static_cast<int>(GameListCompatibilityRating::Count)> m_compatibiliy_pixmaps;
|
||||
mutable std::unordered_map<std::string, QPixmap> m_cover_pixmap_cache;
|
||||
mutable LRUCache<std::string, QPixmap> m_cover_pixmap_cache;
|
||||
};
|
||||
104
src/duckstation-qt/gamelistrefreshthread.cpp
Normal file
104
src/duckstation-qt/gamelistrefreshthread.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "gamelistrefreshthread.h"
|
||||
#include "common/log.h"
|
||||
#include "common/progress_callback.h"
|
||||
#include "common/timer.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent) : m_parent(parent) {}
|
||||
|
||||
void AsyncRefreshProgressCallback::Cancel()
|
||||
{
|
||||
// Not atomic, but we don't need to cancel immediately.
|
||||
m_cancelled = true;
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::SetStatusText(const char* text)
|
||||
{
|
||||
QString new_text(QString::fromUtf8(text));
|
||||
if (new_text == m_status_text)
|
||||
return;
|
||||
|
||||
m_status_text = new_text;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::SetProgressRange(u32 range)
|
||||
{
|
||||
BaseProgressCallback::SetProgressRange(range);
|
||||
if (static_cast<int>(m_progress_range) == m_last_range)
|
||||
return;
|
||||
|
||||
m_last_range = static_cast<int>(m_progress_range);
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::SetProgressValue(u32 value)
|
||||
{
|
||||
BaseProgressCallback::SetProgressValue(value);
|
||||
if (static_cast<int>(m_progress_value) == m_last_value)
|
||||
return;
|
||||
|
||||
m_last_value = static_cast<int>(m_progress_value);
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::SetTitle(const char* title) {}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayWarning(const char* message)
|
||||
{
|
||||
QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message));
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
|
||||
{
|
||||
Log::Write("AsyncRefreshProgressCallback", "", LOGLEVEL_DEV, message);
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
}
|
||||
|
||||
bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message)
|
||||
{
|
||||
return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes;
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::fireUpdate()
|
||||
{
|
||||
m_parent->refreshProgress(m_status_text, m_last_value, m_last_range);
|
||||
}
|
||||
|
||||
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
|
||||
: QThread(), m_progress(this), m_invalidate_cache(invalidate_cache)
|
||||
{
|
||||
}
|
||||
|
||||
GameListRefreshThread::~GameListRefreshThread() = default;
|
||||
|
||||
void GameListRefreshThread::cancel()
|
||||
{
|
||||
m_progress.Cancel();
|
||||
}
|
||||
|
||||
void GameListRefreshThread::run()
|
||||
{
|
||||
GameList::Refresh(m_invalidate_cache, false, &m_progress);
|
||||
emit refreshComplete();
|
||||
}
|
||||
60
src/duckstation-qt/gamelistrefreshthread.h
Normal file
60
src/duckstation-qt/gamelistrefreshthread.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include "common/progress_callback.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
class GameListRefreshThread;
|
||||
|
||||
class AsyncRefreshProgressCallback : public BaseProgressCallback
|
||||
{
|
||||
public:
|
||||
AsyncRefreshProgressCallback(GameListRefreshThread* parent);
|
||||
|
||||
void Cancel();
|
||||
|
||||
void SetStatusText(const char* text) override;
|
||||
void SetProgressRange(u32 range) override;
|
||||
void SetProgressValue(u32 value) override;
|
||||
void SetTitle(const char* title) override;
|
||||
void DisplayError(const char* message) override;
|
||||
void DisplayWarning(const char* message) override;
|
||||
void DisplayInformation(const char* message) override;
|
||||
void DisplayDebugMessage(const char* message) override;
|
||||
void ModalError(const char* message) override;
|
||||
bool ModalConfirmation(const char* message) override;
|
||||
void ModalInformation(const char* message) override;
|
||||
|
||||
private:
|
||||
void fireUpdate();
|
||||
|
||||
GameListRefreshThread* m_parent;
|
||||
Common::Timer m_last_update_time;
|
||||
QString m_status_text;
|
||||
int m_last_range = 1;
|
||||
int m_last_value = 0;
|
||||
};
|
||||
|
||||
class GameListRefreshThread final : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListRefreshThread(bool invalidate_cache);
|
||||
~GameListRefreshThread();
|
||||
|
||||
void cancel();
|
||||
|
||||
Q_SIGNALS:
|
||||
void refreshProgress(const QString& status, int current, int total);
|
||||
void refreshComplete();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
AsyncRefreshProgressCallback m_progress;
|
||||
bool m_invalidate_cache;
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "gamelistsearchdirectoriesmodel.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
GameListSearchDirectoriesModel::GameListSearchDirectoriesModel(QtHostInterface* host_interface)
|
||||
GameListSearchDirectoriesModel::GameListSearchDirectoriesModel(EmuThread* host_interface)
|
||||
: m_host_interface(host_interface)
|
||||
{
|
||||
loadFromSettings();
|
||||
@@ -80,7 +81,7 @@ bool GameListSearchDirectoriesModel::setData(const QModelIndex& index, const QVa
|
||||
Entry& entry = m_entries[row];
|
||||
entry.recursive = value == Qt::Checked;
|
||||
saveToSettings();
|
||||
m_host_interface->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ void GameListSearchDirectoriesModel::addEntry(const QString& path, bool recursiv
|
||||
}
|
||||
|
||||
saveToSettings();
|
||||
m_host_interface->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false);
|
||||
}
|
||||
|
||||
void GameListSearchDirectoriesModel::removeEntry(int row)
|
||||
@@ -114,7 +115,7 @@ void GameListSearchDirectoriesModel::removeEntry(int row)
|
||||
endRemoveRows();
|
||||
|
||||
saveToSettings();
|
||||
m_host_interface->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false);
|
||||
}
|
||||
|
||||
bool GameListSearchDirectoriesModel::isEntryRecursive(int row) const
|
||||
@@ -131,7 +132,7 @@ void GameListSearchDirectoriesModel::setEntryRecursive(int row, bool recursive)
|
||||
emit dataChanged(index(row, 1), index(row, 1), {Qt::CheckStateRole});
|
||||
|
||||
saveToSettings();
|
||||
m_host_interface->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false);
|
||||
}
|
||||
|
||||
void GameListSearchDirectoriesModel::openEntryInExplorer(QWidget* parent, int row) const
|
||||
@@ -144,11 +145,11 @@ void GameListSearchDirectoriesModel::openEntryInExplorer(QWidget* parent, int ro
|
||||
|
||||
void GameListSearchDirectoriesModel::loadFromSettings()
|
||||
{
|
||||
std::vector<std::string> path_list = m_host_interface->GetSettingStringList("GameList", "Paths");
|
||||
std::vector<std::string> path_list = Host::GetBaseStringListSetting("GameList", "Paths");
|
||||
for (std::string& entry : path_list)
|
||||
m_entries.push_back({QString::fromStdString(entry), false});
|
||||
|
||||
path_list = m_host_interface->GetSettingStringList("GameList", "RecursivePaths");
|
||||
path_list = Host::GetBaseStringListSetting("GameList", "RecursivePaths");
|
||||
for (std::string& entry : path_list)
|
||||
m_entries.push_back({QString::fromStdString(entry), true});
|
||||
}
|
||||
@@ -167,12 +168,12 @@ void GameListSearchDirectoriesModel::saveToSettings()
|
||||
}
|
||||
|
||||
if (paths.empty())
|
||||
m_host_interface->RemoveSettingValue("GameList", "Paths");
|
||||
Host::DeleteBaseSettingValue("GameList", "Paths");
|
||||
else
|
||||
m_host_interface->SetStringListSettingValue("GameList", "Paths", paths);
|
||||
Host::SetBaseStringListSettingValue("GameList", "Paths", paths);
|
||||
|
||||
if (recursive_paths.empty())
|
||||
m_host_interface->RemoveSettingValue("GameList", "RecursivePaths");
|
||||
Host::DeleteBaseSettingValue("GameList", "RecursivePaths");
|
||||
else
|
||||
m_host_interface->SetStringListSettingValue("GameList", "RecursivePaths", recursive_paths);
|
||||
Host::SetBaseStringListSettingValue("GameList", "RecursivePaths", recursive_paths);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
#include <QtCore/QString>
|
||||
#include <vector>
|
||||
|
||||
class QtHostInterface;
|
||||
class EmuThread;
|
||||
|
||||
class GameListSearchDirectoriesModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListSearchDirectoriesModel(QtHostInterface* host_interface);
|
||||
GameListSearchDirectoriesModel(EmuThread* host_interface);
|
||||
~GameListSearchDirectoriesModel();
|
||||
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
@@ -36,6 +36,6 @@ private:
|
||||
bool recursive;
|
||||
};
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
EmuThread* m_host_interface;
|
||||
std::vector<Entry> m_entries;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include "common/string_util.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "gamelistsearchdirectoriesmodel.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QAbstractTableModel>
|
||||
#include <QtCore/QDebug>
|
||||
@@ -16,12 +17,11 @@
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <algorithm>
|
||||
|
||||
GameListSettingsWidget::GameListSettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
GameListSettingsWidget::GameListSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_search_directories_model = new GameListSearchDirectoriesModel(host_interface);
|
||||
m_search_directories_model = new GameListSearchDirectoriesModel(g_emu_thread);
|
||||
m_ui.searchDirectoryList->setModel(m_search_directories_model);
|
||||
m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
@@ -52,11 +52,11 @@ GameListSettingsWidget::~GameListSettingsWidget() = default;
|
||||
|
||||
bool GameListSettingsWidget::addExcludedPath(const std::string& path)
|
||||
{
|
||||
if (!m_host_interface->AddValueToStringList("GameList", "ExcludedPaths", path.c_str()))
|
||||
if (!Host::AddValueToBaseStringListSetting("GameList", "ExcludedPaths", path.c_str()))
|
||||
return false;
|
||||
|
||||
m_ui.excludedPaths->addItem(QString::fromStdString(path));
|
||||
m_host_interface->refreshGameList();
|
||||
g_main_window->refreshGameList(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ void GameListSettingsWidget::refreshExclusionList()
|
||||
{
|
||||
m_ui.excludedPaths->clear();
|
||||
|
||||
const std::vector<std::string> paths(m_host_interface->GetSettingStringList("GameList", "ExcludedPaths"));
|
||||
const std::vector<std::string> paths(Host::GetBaseStringListSetting("GameList", "ExcludedPaths"));
|
||||
for (const std::string& path : paths)
|
||||
m_ui.excludedPaths->addItem(QString::fromStdString(path));
|
||||
}
|
||||
@@ -158,18 +158,18 @@ void GameListSettingsWidget::onRemoveExcludedPathButtonClicked()
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
m_host_interface->RemoveValueFromStringList("GameList", "ExcludedPaths", item->text().toUtf8().constData());
|
||||
Host::RemoveValueFromBaseStringListSetting("GameList", "ExcludedPaths", item->text().toUtf8().constData());
|
||||
delete item;
|
||||
|
||||
m_host_interface->refreshGameList();
|
||||
g_main_window->refreshGameList(false);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onRescanAllGamesClicked()
|
||||
{
|
||||
m_host_interface->refreshGameList(true, false);
|
||||
g_main_window->refreshGameList(true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onScanForNewGamesClicked()
|
||||
{
|
||||
m_host_interface->refreshGameList(false, false);
|
||||
g_main_window->refreshGameList(false);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "ui_gamelistsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
|
||||
class SettingsDialog;
|
||||
class GameListSearchDirectoriesModel;
|
||||
|
||||
class GameListSettingsWidget : public QWidget
|
||||
@@ -13,7 +12,7 @@ class GameListSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListSettingsWidget(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||
GameListSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~GameListSettingsWidget();
|
||||
|
||||
bool addExcludedPath(const std::string& path);
|
||||
@@ -36,8 +35,6 @@ protected:
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
private:
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
Ui::GameListSettingsWidget m_ui;
|
||||
|
||||
GameListSearchDirectoriesModel* m_search_directories_model = nullptr;
|
||||
|
||||
@@ -1,24 +1,67 @@
|
||||
#include "gamelistwidget.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "core/settings.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "gamelistmodel.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "gamelistrefreshthread.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QPixmap>
|
||||
#include <QtGui/QWheelEvent>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
static constexpr float MIN_SCALE = 0.1f;
|
||||
static constexpr float MAX_SCALE = 2.0f;
|
||||
|
||||
static const char* SUPPORTED_FORMATS_STRING =
|
||||
QT_TRANSLATE_NOOP(GameListWidget, ".cue (Cue Sheets)\n"
|
||||
".iso/.img (Single Track Image)\n"
|
||||
".ecm (Error Code Modeling Image)\n"
|
||||
".mds (Media Descriptor Sidecar)\n"
|
||||
".chd (Compressed Hunks of Data)\n"
|
||||
".pbp (PlayStation Portable, Only Decrypted)");
|
||||
|
||||
class GameListSortModel final : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
GameListSortModel(GameListModel* parent) : QSortFilterProxyModel(parent), m_model(parent) {}
|
||||
explicit GameListSortModel(GameListModel* parent) : QSortFilterProxyModel(parent), m_model(parent) {}
|
||||
|
||||
void setFilterType(GameList::EntryType type)
|
||||
{
|
||||
m_filter_type = type;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
void setFilterRegion(DiscRegion region)
|
||||
{
|
||||
m_filter_region = region;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
void setFilterName(const QString& name)
|
||||
{
|
||||
m_filter_name = name;
|
||||
invalidateRowsFilter();
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
|
||||
{
|
||||
// TODO: Search
|
||||
if (m_filter_type != GameList::EntryType::Count || m_filter_region != DiscRegion::Count || !m_filter_name.isEmpty())
|
||||
{
|
||||
const auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryByIndex(source_row);
|
||||
if (m_filter_type != GameList::EntryType::Count && entry->type != m_filter_type)
|
||||
return false;
|
||||
if (m_filter_region != DiscRegion::Count && entry->region != m_filter_region)
|
||||
return false;
|
||||
if (!m_filter_name.isEmpty() &&
|
||||
!QString::fromStdString(entry->title).contains(m_filter_name, Qt::CaseInsensitive))
|
||||
return false;
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
@@ -29,26 +72,55 @@ public:
|
||||
|
||||
private:
|
||||
GameListModel* m_model;
|
||||
GameList::EntryType m_filter_type = GameList::EntryType::Count;
|
||||
DiscRegion m_filter_region = DiscRegion::Count;
|
||||
QString m_filter_name;
|
||||
};
|
||||
|
||||
GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) : QStackedWidget(parent) {}
|
||||
GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) : QWidget(parent) {}
|
||||
|
||||
GameListWidget::~GameListWidget() = default;
|
||||
|
||||
void GameListWidget::initialize(QtHostInterface* host_interface)
|
||||
void GameListWidget::initialize()
|
||||
{
|
||||
m_host_interface = host_interface;
|
||||
m_game_list = host_interface->getGameList();
|
||||
|
||||
connect(m_host_interface, &QtHostInterface::gameListRefreshed, this, &GameListWidget::onGameListRefreshed);
|
||||
|
||||
m_model = new GameListModel(m_game_list, this);
|
||||
m_model->setCoverScale(host_interface->GetFloatSettingValue("UI", "GameListCoverArtScale", 0.45f));
|
||||
m_model->setShowCoverTitles(host_interface->GetBoolSettingValue("UI", "GameListShowCoverTitles", true));
|
||||
m_model = new GameListModel(this);
|
||||
m_model->setCoverScale(Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f));
|
||||
m_model->setShowCoverTitles(Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true));
|
||||
|
||||
m_sort_model = new GameListSortModel(m_model);
|
||||
m_sort_model->setSourceModel(m_model);
|
||||
m_table_view = new QTableView(this);
|
||||
|
||||
m_ui.setupUi(this);
|
||||
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
|
||||
{
|
||||
m_ui.filterType->addItem(
|
||||
QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(type)),
|
||||
qApp->translate("GameList", GameList::GetEntryTypeDisplayName(static_cast<GameList::EntryType>(type))));
|
||||
}
|
||||
for (u32 region = 0; region < static_cast<u32>(DiscRegion::Count); region++)
|
||||
{
|
||||
m_ui.filterRegion->addItem(QtUtils::GetIconForRegion(static_cast<DiscRegion>(region)),
|
||||
QString::fromUtf8(Settings::GetDiscRegionName(static_cast<DiscRegion>(region))));
|
||||
}
|
||||
|
||||
connect(m_ui.viewGameList, &QPushButton::clicked, this, &GameListWidget::showGameList);
|
||||
connect(m_ui.viewGameGrid, &QPushButton::clicked, this, &GameListWidget::showGameGrid);
|
||||
connect(m_ui.gridScale, &QSlider::valueChanged, this, &GameListWidget::gridIntScale);
|
||||
connect(m_ui.viewGridTitles, &QPushButton::toggled, this, &GameListWidget::setShowCoverTitles);
|
||||
connect(m_ui.filterType, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
m_sort_model->setFilterType((index == 0) ? GameList::EntryType::Count :
|
||||
static_cast<GameList::EntryType>(index - 1));
|
||||
});
|
||||
connect(m_ui.filterRegion, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
m_sort_model->setFilterRegion((index == 0) ? DiscRegion::Count : static_cast<DiscRegion>(index - 1));
|
||||
});
|
||||
connect(m_ui.searchText, &QLineEdit::textChanged, this,
|
||||
[this](const QString& text) { m_sort_model->setFilterName(text); });
|
||||
|
||||
// Works around a strange bug where after hiding the game list, the cursor for the whole window changes to a beam..
|
||||
// m_ui.searchText->setCursor(QCursor(Qt::ArrowCursor));
|
||||
|
||||
m_table_view = new QTableView(m_ui.stack);
|
||||
m_table_view->setModel(m_sort_model);
|
||||
m_table_view->setSortingEnabled(true);
|
||||
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
@@ -61,6 +133,7 @@ void GameListWidget::initialize(QtHostInterface* host_interface)
|
||||
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->verticalHeader()->hide();
|
||||
m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
|
||||
|
||||
loadTableViewColumnVisibilitySettings();
|
||||
loadTableViewColumnSortSettings();
|
||||
@@ -75,18 +148,21 @@ void GameListWidget::initialize(QtHostInterface* host_interface)
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
|
||||
&GameListWidget::onTableViewHeaderSortIndicatorChanged);
|
||||
|
||||
insertWidget(0, m_table_view);
|
||||
m_ui.stack->insertWidget(0, m_table_view);
|
||||
|
||||
m_list_view = new GameListGridListView(this);
|
||||
m_list_view = new GameListGridListView(m_ui.stack);
|
||||
m_list_view->setModel(m_sort_model);
|
||||
m_list_view->setModelColumn(GameListModel::Column_Cover);
|
||||
m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_list_view->setViewMode(QListView::IconMode);
|
||||
m_list_view->setResizeMode(QListView::Adjust);
|
||||
m_list_view->setUniformItemSizes(true);
|
||||
m_list_view->setItemAlignment(Qt::AlignHCenter);
|
||||
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_list_view->setFrameStyle(QFrame::NoFrame);
|
||||
m_list_view->setSpacing(m_model->getCoverArtSpacing());
|
||||
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
|
||||
|
||||
updateListFont();
|
||||
|
||||
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
@@ -96,24 +172,32 @@ void GameListWidget::initialize(QtHostInterface* host_interface)
|
||||
connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
|
||||
connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
|
||||
|
||||
insertWidget(1, m_list_view);
|
||||
m_ui.stack->insertWidget(1, m_list_view);
|
||||
|
||||
if (m_host_interface->GetBoolSettingValue("UI", "GameListGridView", false))
|
||||
setCurrentIndex(1);
|
||||
m_empty_widget = new QWidget(m_ui.stack);
|
||||
m_empty_ui.setupUi(m_empty_widget);
|
||||
m_empty_ui.supportedFormats->setText(qApp->translate("GameListWidget", SUPPORTED_FORMATS_STRING));
|
||||
connect(m_empty_ui.addGameDirectory, &QPushButton::clicked, this, [this]() { emit addGameDirectoryRequested(); });
|
||||
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false); });
|
||||
m_ui.stack->insertWidget(2, m_empty_widget);
|
||||
|
||||
if (Host::GetBaseBoolSettingValue("UI", "GameListGridView", false))
|
||||
m_ui.stack->setCurrentIndex(1);
|
||||
else
|
||||
setCurrentIndex(0);
|
||||
m_ui.stack->setCurrentIndex(0);
|
||||
|
||||
updateToolbar();
|
||||
resizeTableViewColumnsToFit();
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameList() const
|
||||
{
|
||||
return currentIndex() == 0;
|
||||
return m_ui.stack->currentIndex() == 0;
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameGrid() const
|
||||
{
|
||||
return currentIndex() == 1;
|
||||
return m_ui.stack->currentIndex() == 1;
|
||||
}
|
||||
|
||||
bool GameListWidget::getShowGridCoverTitles() const
|
||||
@@ -121,60 +205,89 @@ bool GameListWidget::getShowGridCoverTitles() const
|
||||
return m_model->getShowCoverTitles();
|
||||
}
|
||||
|
||||
void GameListWidget::onGameListRefreshed()
|
||||
void GameListWidget::refresh(bool invalidate_cache)
|
||||
{
|
||||
cancelRefresh();
|
||||
|
||||
m_refresh_thread = new GameListRefreshThread(invalidate_cache);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,
|
||||
Qt::QueuedConnection);
|
||||
m_refresh_thread->start();
|
||||
}
|
||||
|
||||
void GameListWidget::cancelRefresh()
|
||||
{
|
||||
if (!m_refresh_thread)
|
||||
return;
|
||||
|
||||
m_refresh_thread->cancel();
|
||||
m_refresh_thread->wait();
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
AssertMsg(!m_refresh_thread, "Game list thread should be unreferenced by now");
|
||||
}
|
||||
|
||||
void GameListWidget::onRefreshProgress(const QString& status, int current, int total)
|
||||
{
|
||||
// switch away from the placeholder while we scan, in case we find anything
|
||||
if (m_ui.stack->currentIndex() == 2)
|
||||
m_ui.stack->setCurrentIndex(Host::GetBaseBoolSettingValue("UI", "GameListGridView", false) ? 1 : 0);
|
||||
|
||||
m_model->refresh();
|
||||
emit refreshProgress(status, current, total);
|
||||
}
|
||||
|
||||
void GameListWidget::onRefreshComplete()
|
||||
{
|
||||
m_model->refresh();
|
||||
emit refreshComplete();
|
||||
|
||||
AssertMsg(m_refresh_thread, "Has a refresh thread");
|
||||
m_refresh_thread->wait();
|
||||
delete m_refresh_thread;
|
||||
m_refresh_thread = nullptr;
|
||||
|
||||
// if we still had no games, switch to the helper widget
|
||||
if (m_model->rowCount() == 0)
|
||||
m_ui.stack->setCurrentIndex(2);
|
||||
}
|
||||
|
||||
void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(current);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
{
|
||||
emit entrySelected(nullptr);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(GameList::GetEntryCount()))
|
||||
return;
|
||||
}
|
||||
|
||||
const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row());
|
||||
emit entrySelected(&entry);
|
||||
emit selectionChanged();
|
||||
}
|
||||
|
||||
void GameListWidget::onTableViewItemActivated(const QModelIndex& index)
|
||||
{
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(index);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(GameList::GetEntryCount()))
|
||||
return;
|
||||
|
||||
const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row());
|
||||
emit entryDoubleClicked(&entry);
|
||||
emit entryActivated();
|
||||
}
|
||||
|
||||
void GameListWidget::onTableViewContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
const GameListEntry* entry = getSelectedEntry();
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
emit entryContextMenuRequested(m_table_view->mapToGlobal(point), entry);
|
||||
emit entryContextMenuRequested(m_table_view->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void GameListWidget::onListViewItemActivated(const QModelIndex& index)
|
||||
{
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(index);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(GameList::GetEntryCount()))
|
||||
return;
|
||||
|
||||
const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row());
|
||||
emit entryDoubleClicked(&entry);
|
||||
emit entryActivated();
|
||||
}
|
||||
|
||||
void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
const GameListEntry* entry = getSelectedEntry();
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
emit entryContextMenuRequested(m_list_view->mapToGlobal(point), entry);
|
||||
emit entryContextMenuRequested(m_list_view->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
|
||||
@@ -206,13 +319,11 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
|
||||
|
||||
void GameListWidget::listZoom(float delta)
|
||||
{
|
||||
static constexpr float MIN_SCALE = 0.1f;
|
||||
static constexpr float MAX_SCALE = 2.0f;
|
||||
|
||||
const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE);
|
||||
m_host_interface->SetFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
|
||||
Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
|
||||
m_model->setCoverScale(new_scale);
|
||||
updateListFont();
|
||||
updateToolbar();
|
||||
|
||||
m_model->refresh();
|
||||
}
|
||||
@@ -227,6 +338,18 @@ void GameListWidget::gridZoomOut()
|
||||
listZoom(-0.05f);
|
||||
}
|
||||
|
||||
void GameListWidget::gridIntScale(int int_scale)
|
||||
{
|
||||
const float new_scale = std::clamp(static_cast<float>(int_scale) / 100.0f, MIN_SCALE, MAX_SCALE);
|
||||
|
||||
Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
|
||||
m_model->setCoverScale(new_scale);
|
||||
updateListFont();
|
||||
updateToolbar();
|
||||
|
||||
m_model->refresh();
|
||||
}
|
||||
|
||||
void GameListWidget::refreshGridCovers()
|
||||
{
|
||||
m_model->refreshCovers();
|
||||
@@ -234,32 +357,47 @@ void GameListWidget::refreshGridCovers()
|
||||
|
||||
void GameListWidget::showGameList()
|
||||
{
|
||||
if (currentIndex() == 0)
|
||||
if (m_ui.stack->currentIndex() == 0 || m_model->rowCount() == 0)
|
||||
{
|
||||
updateToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListGridView", false);
|
||||
setCurrentIndex(0);
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListGridView", false);
|
||||
m_ui.stack->setCurrentIndex(0);
|
||||
resizeTableViewColumnsToFit();
|
||||
updateToolbar();
|
||||
emit layoutChange();
|
||||
}
|
||||
|
||||
void GameListWidget::showGameGrid()
|
||||
{
|
||||
if (currentIndex() == 1)
|
||||
if (m_ui.stack->currentIndex() == 1 || m_model->rowCount() == 0)
|
||||
{
|
||||
updateToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListGridView", true);
|
||||
setCurrentIndex(1);
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListGridView", true);
|
||||
m_ui.stack->setCurrentIndex(1);
|
||||
updateToolbar();
|
||||
emit layoutChange();
|
||||
}
|
||||
|
||||
void GameListWidget::setShowCoverTitles(bool enabled)
|
||||
{
|
||||
if (m_model->getShowCoverTitles() == enabled)
|
||||
{
|
||||
updateToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
|
||||
m_model->setShowCoverTitles(enabled);
|
||||
if (isShowingGameGrid())
|
||||
m_model->refresh();
|
||||
updateToolbar();
|
||||
emit layoutChange();
|
||||
}
|
||||
|
||||
void GameListWidget::updateListFont()
|
||||
@@ -269,9 +407,33 @@ void GameListWidget::updateListFont()
|
||||
m_list_view->setFont(font);
|
||||
}
|
||||
|
||||
void GameListWidget::updateToolbar()
|
||||
{
|
||||
const bool grid_view = isShowingGameGrid();
|
||||
{
|
||||
QSignalBlocker sb(m_ui.viewGameGrid);
|
||||
m_ui.viewGameGrid->setChecked(grid_view);
|
||||
}
|
||||
{
|
||||
QSignalBlocker sb(m_ui.viewGameList);
|
||||
m_ui.viewGameList->setChecked(!grid_view);
|
||||
}
|
||||
{
|
||||
QSignalBlocker sb(m_ui.viewGridTitles);
|
||||
m_ui.viewGridTitles->setChecked(m_model->getShowCoverTitles());
|
||||
}
|
||||
{
|
||||
QSignalBlocker sb(m_ui.gridScale);
|
||||
m_ui.gridScale->setValue(static_cast<int>(m_model->getCoverScale() * 100.0f));
|
||||
}
|
||||
|
||||
m_ui.viewGridTitles->setEnabled(grid_view);
|
||||
m_ui.gridScale->setEnabled(grid_view);
|
||||
}
|
||||
|
||||
void GameListWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QStackedWidget::resizeEvent(event);
|
||||
QWidget::resizeEvent(event);
|
||||
resizeTableViewColumnsToFit();
|
||||
}
|
||||
|
||||
@@ -287,7 +449,7 @@ void GameListWidget::resizeTableViewColumnsToFit()
|
||||
200, // genre
|
||||
50, // year
|
||||
100, // players
|
||||
80, // size
|
||||
80, // size
|
||||
50, // region
|
||||
100 // compatibility
|
||||
});
|
||||
@@ -317,8 +479,8 @@ void GameListWidget::loadTableViewColumnVisibilitySettings()
|
||||
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = m_host_interface->GetBoolSettingValue(
|
||||
"GameListTableView", getColumnVisibilitySettingsKeyName(column), DEFAULT_VISIBILITY[column]);
|
||||
const bool visible = Host::GetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column),
|
||||
DEFAULT_VISIBILITY[column]);
|
||||
m_table_view->setColumnHidden(column, !visible);
|
||||
}
|
||||
}
|
||||
@@ -328,14 +490,14 @@ void GameListWidget::saveTableViewColumnVisibilitySettings()
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
m_host_interface->SetBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
m_host_interface->SetBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
|
||||
}
|
||||
|
||||
void GameListWidget::loadTableViewColumnSortSettings()
|
||||
@@ -344,10 +506,10 @@ void GameListWidget::loadTableViewColumnSortSettings()
|
||||
const bool DEFAULT_SORT_DESCENDING = false;
|
||||
|
||||
const GameListModel::Column sort_column =
|
||||
GameListModel::getColumnIdForName(m_host_interface->GetStringSettingValue("GameListTableView", "SortColumn"))
|
||||
GameListModel::getColumnIdForName(Host::GetBaseStringSettingValue("GameListTableView", "SortColumn"))
|
||||
.value_or(DEFAULT_SORT_COLUMN);
|
||||
const bool sort_descending =
|
||||
m_host_interface->GetBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
|
||||
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
|
||||
m_sort_model->sort(sort_column, sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
@@ -358,16 +520,16 @@ void GameListWidget::saveTableViewColumnSortSettings()
|
||||
|
||||
if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
|
||||
{
|
||||
m_host_interface->SetStringSettingValue(
|
||||
"GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast<GameListModel::Column>(sort_column)));
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "SortColumn",
|
||||
GameListModel::getColumnName(static_cast<GameListModel::Column>(sort_column)));
|
||||
}
|
||||
|
||||
m_host_interface->SetBoolSettingValue("GameListTableView", "SortDescending", sort_descending);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_descending);
|
||||
}
|
||||
|
||||
const GameListEntry* GameListWidget::getSelectedEntry() const
|
||||
const GameList::Entry* GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
if (currentIndex() == 0)
|
||||
if (m_ui.stack->currentIndex() == 0)
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_table_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
@@ -378,10 +540,10 @@ const GameListEntry* GameListWidget::getSelectedEntry() const
|
||||
return nullptr;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
|
||||
return &m_game_list->GetEntries().at(source_index.row());
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -390,10 +552,10 @@ const GameListEntry* GameListWidget::getSelectedEntry() const
|
||||
return nullptr;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex());
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
|
||||
return &m_game_list->GetEntries().at(source_index.row());
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#pragma once
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "ui_emptygamelistwidget.h"
|
||||
#include "ui_gamelistwidget.h"
|
||||
#include <QtWidgets/QListView>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QTableView>
|
||||
|
||||
class GameList;
|
||||
struct GameListEntry;
|
||||
Q_DECLARE_METATYPE(const GameList::Entry*);
|
||||
|
||||
class GameListModel;
|
||||
class GameListSortModel;
|
||||
|
||||
class QtHostInterface;
|
||||
class GameListRefreshThread;
|
||||
|
||||
class GameListGridListView : public QListView
|
||||
{
|
||||
@@ -26,7 +26,7 @@ protected:
|
||||
void wheelEvent(QWheelEvent* e);
|
||||
};
|
||||
|
||||
class GameListWidget : public QStackedWidget
|
||||
class GameListWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -34,22 +34,35 @@ public:
|
||||
GameListWidget(QWidget* parent = nullptr);
|
||||
~GameListWidget();
|
||||
|
||||
void initialize(QtHostInterface* host_interface);
|
||||
ALWAYS_INLINE GameListModel* getModel() const { return m_model; }
|
||||
|
||||
void initialize();
|
||||
void resizeTableViewColumnsToFit();
|
||||
|
||||
void refresh(bool invalidate_cache);
|
||||
void cancelRefresh();
|
||||
|
||||
bool isShowingGameList() const;
|
||||
bool isShowingGameGrid() const;
|
||||
|
||||
bool getShowGridCoverTitles() const;
|
||||
|
||||
const GameListEntry* getSelectedEntry() const;
|
||||
const GameList::Entry* getSelectedEntry() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void entrySelected(const GameListEntry* entry);
|
||||
void entryDoubleClicked(const GameListEntry* entry);
|
||||
void entryContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
||||
void refreshProgress(const QString& status, int current, int total);
|
||||
void refreshComplete();
|
||||
|
||||
void selectionChanged();
|
||||
void entryActivated();
|
||||
void entryContextMenuRequested(const QPoint& point);
|
||||
|
||||
void addGameDirectoryRequested();
|
||||
void layoutChange();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onGameListRefreshed();
|
||||
void onRefreshProgress(const QString& status, int current, int total);
|
||||
void onRefreshComplete();
|
||||
|
||||
void onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void onTableViewItemActivated(const QModelIndex& index);
|
||||
void onTableViewContextMenuRequested(const QPoint& point);
|
||||
@@ -64,13 +77,13 @@ public Q_SLOTS:
|
||||
void setShowCoverTitles(bool enabled);
|
||||
void gridZoomIn();
|
||||
void gridZoomOut();
|
||||
void gridIntScale(int int_scale);
|
||||
void refreshGridCovers();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
private:
|
||||
void resizeTableViewColumnsToFit();
|
||||
void loadTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings(int column);
|
||||
@@ -78,12 +91,17 @@ private:
|
||||
void saveTableViewColumnSortSettings();
|
||||
void listZoom(float delta);
|
||||
void updateListFont();
|
||||
void updateToolbar();
|
||||
|
||||
QtHostInterface* m_host_interface = nullptr;
|
||||
GameList* m_game_list = nullptr;
|
||||
Ui::GameListWidget m_ui;
|
||||
|
||||
GameListModel* m_model = nullptr;
|
||||
GameListSortModel* m_sort_model = nullptr;
|
||||
QTableView* m_table_view = nullptr;
|
||||
GameListGridListView* m_list_view = nullptr;
|
||||
|
||||
QWidget* m_empty_widget = nullptr;
|
||||
Ui::EmptyGameListWidget m_empty_ui;
|
||||
|
||||
GameListRefreshThread* m_refresh_thread = nullptr;
|
||||
};
|
||||
|
||||
220
src/duckstation-qt/gamelistwidget.ui
Normal file
220
src/duckstation-qt/gamelistwidget.ui
Normal file
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameListWidget</class>
|
||||
<widget class="QWidget" name="GameListWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>758</width>
|
||||
<height>619</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="viewGameList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game List</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-check">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="viewGameGrid">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game Grid</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="function-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="viewGridTitles">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Titles</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="price-tag-3-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="gridScale">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="filterType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>All Types</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="filter-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="filterRegion">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>All Regions</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="global-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
#include "frontend-common/game_settings.h"
|
||||
#include "ui_gamepropertiesdialog.h"
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <array>
|
||||
|
||||
struct GameListEntry;
|
||||
struct GameListCompatibilityEntry;
|
||||
|
||||
class QtHostInterface;
|
||||
|
||||
class GamePropertiesDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||
~GamePropertiesDialog();
|
||||
|
||||
static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge, QWidget* parent);
|
||||
|
||||
public Q_SLOTS:
|
||||
void clear();
|
||||
void populate(const GameListEntry* ge);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* ev);
|
||||
void resizeEvent(QResizeEvent* ev);
|
||||
|
||||
private Q_SLOTS:
|
||||
void saveCompatibilityInfo();
|
||||
void saveCompatibilityInfoIfChanged();
|
||||
void setCompatibilityInfoChanged();
|
||||
|
||||
void onSetVersionTestedToCurrentClicked();
|
||||
void onComputeHashClicked();
|
||||
void onExportCompatibilityInfoClicked();
|
||||
void updateCPUClockSpeedLabel();
|
||||
void onEnableCPUClockSpeedControlChecked(int state);
|
||||
|
||||
private:
|
||||
void setupAdditionalUi();
|
||||
void connectUi();
|
||||
void populateCompatibilityInfo(const std::string& game_code);
|
||||
void populateTracksInfo(const std::string& image_path);
|
||||
void populateGameSettings();
|
||||
void populateBooleanUserSetting(QCheckBox* cb, const std::optional<bool>& value);
|
||||
void connectBooleanUserSetting(QCheckBox* cb, std::optional<bool>* value);
|
||||
void saveGameSettings();
|
||||
void fillEntryFromUi(GameListCompatibilityEntry* entry);
|
||||
void computeTrackHashes(std::string& redump_keyword);
|
||||
void onResize();
|
||||
void onUserAspectRatioChanged();
|
||||
|
||||
Ui::GamePropertiesDialog m_ui;
|
||||
std::array<QCheckBox*, static_cast<u32>(GameSettings::Trait::Count)> m_trait_checkboxes{};
|
||||
QPushButton* m_exportCompatibilityInfo;
|
||||
QPushButton* m_computeHashes;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
std::string m_path;
|
||||
std::string m_game_code;
|
||||
std::string m_game_title;
|
||||
std::string m_redump_search_keyword;
|
||||
|
||||
GameSettings::Entry m_game_settings;
|
||||
|
||||
bool m_compatibility_info_changed = false;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
348
src/duckstation-qt/gamesummarywidget.cpp
Normal file
348
src/duckstation-qt/gamesummarywidget.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
#include "gamesummarywidget.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/game_database.h"
|
||||
#include "fmt/format.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "qthost.h"
|
||||
#include "qtprogresscallback.h"
|
||||
#include "settingsdialog.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string& serial, DiscRegion region,
|
||||
const GameDatabase::Entry* entry, SettingsDialog* dialog, QWidget* parent)
|
||||
: m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.revision->setVisible(false);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::Count); i++)
|
||||
{
|
||||
m_ui.entryType->addItem(
|
||||
QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(i)),
|
||||
qApp->translate("GameList", GameList::GetEntryTypeDisplayName(static_cast<GameList::EntryType>(i))));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(DiscRegion::Count); i++)
|
||||
{
|
||||
m_ui.region->addItem(QtUtils::GetIconForRegion(static_cast<DiscRegion>(i)),
|
||||
qApp->translate("DiscRegion", Settings::GetDiscRegionDisplayName(static_cast<DiscRegion>(i))));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameDatabase::CompatibilityRating::Count); i++)
|
||||
{
|
||||
m_ui.compatibility->addItem(QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)),
|
||||
qApp->translate("GameDatabase", GameDatabase::GetCompatibilityRatingDisplayName(
|
||||
static_cast<GameDatabase::CompatibilityRating>(i))));
|
||||
}
|
||||
|
||||
populateUi(path, serial, region, entry);
|
||||
|
||||
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
|
||||
connect(m_ui.computeHashes, &QAbstractButton::clicked, this, &GameSummaryWidget::onComputeHashClicked);
|
||||
}
|
||||
|
||||
GameSummaryWidget::~GameSummaryWidget() = default;
|
||||
|
||||
void GameSummaryWidget::populateUi(const std::string& path, const std::string& serial, DiscRegion region,
|
||||
const GameDatabase::Entry* entry)
|
||||
{
|
||||
m_path = path;
|
||||
|
||||
m_ui.path->setText(QString::fromStdString(path));
|
||||
m_ui.serial->setText(QString::fromStdString(serial));
|
||||
m_ui.region->setCurrentIndex(static_cast<int>(region));
|
||||
|
||||
if (entry)
|
||||
{
|
||||
m_ui.title->setText(QString::fromStdString(entry->title));
|
||||
m_ui.compatibility->setCurrentIndex(static_cast<int>(entry->compatibility));
|
||||
m_ui.genre->setText(entry->genre.empty() ? tr("Unknown") : QString::fromStdString(entry->genre));
|
||||
if (!entry->developer.empty() && !entry->publisher.empty() && entry->developer != entry->publisher)
|
||||
m_ui.developer->setText(tr("%1 (Published by %2)")
|
||||
.arg(QString::fromStdString(entry->developer))
|
||||
.arg(QString::fromStdString(entry->publisher)));
|
||||
else if (!entry->developer.empty())
|
||||
m_ui.developer->setText(QString::fromStdString(entry->developer));
|
||||
else if (!entry->publisher.empty())
|
||||
m_ui.developer->setText(tr("Published by %1").arg(QString::fromStdString(entry->publisher)));
|
||||
else
|
||||
m_ui.developer->setText(tr("Unknown"));
|
||||
|
||||
QString release_info;
|
||||
if (entry->release_date != 0)
|
||||
release_info =
|
||||
tr("Released %1").arg(QDateTime::fromSecsSinceEpoch(entry->release_date, Qt::UTC).date().toString());
|
||||
if (entry->min_players != 0)
|
||||
{
|
||||
if (!release_info.isEmpty())
|
||||
release_info.append(", ");
|
||||
if (entry->min_players != entry->max_players)
|
||||
release_info.append(tr("%1-%2 players").arg(entry->min_players).arg(entry->max_players));
|
||||
else
|
||||
release_info.append(tr("%1 players").arg(entry->min_players));
|
||||
}
|
||||
if (entry->min_blocks != 0)
|
||||
{
|
||||
if (!release_info.isEmpty())
|
||||
release_info.append(", ");
|
||||
if (entry->min_blocks != entry->max_blocks)
|
||||
release_info.append(tr("%1-%2 memory card blocks").arg(entry->min_blocks).arg(entry->max_blocks));
|
||||
else
|
||||
release_info.append(tr("%1 memory card blocks").arg(entry->min_blocks));
|
||||
}
|
||||
if (!release_info.isEmpty())
|
||||
m_ui.releaseInfo->setText(release_info);
|
||||
else
|
||||
m_ui.releaseInfo->setText(tr("Unknown"));
|
||||
|
||||
QString controllers;
|
||||
if (entry->supported_controllers != 0 && entry->supported_controllers != static_cast<u32>(-1))
|
||||
{
|
||||
for (u32 i = 0; i < static_cast<u32>(ControllerType::Count); i++)
|
||||
{
|
||||
if ((entry->supported_controllers & (1u << i)) != 0)
|
||||
{
|
||||
if (!controllers.isEmpty())
|
||||
controllers.append(", ");
|
||||
controllers.append(
|
||||
qApp->translate("ControllerType", Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (controllers.isEmpty())
|
||||
controllers = tr("Unknown");
|
||||
m_ui.controllers->setText(controllers);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.title->setText(tr("Unknown"));
|
||||
m_ui.genre->setText(tr("Unknown"));
|
||||
m_ui.developer->setText(tr("Unknown"));
|
||||
m_ui.releaseInfo->setText(tr("Unknown"));
|
||||
m_ui.controllers->setText(tr("Unknown"));
|
||||
}
|
||||
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* gentry = GameList::GetEntryForPath(path.c_str());
|
||||
if (gentry)
|
||||
m_ui.entryType->setCurrentIndex(static_cast<int>(gentry->type));
|
||||
}
|
||||
|
||||
m_ui.inputProfile->addItem(QIcon::fromTheme(QStringLiteral("gamepad-line")), tr("Use Global Settings"));
|
||||
for (const std::string& name : InputManager::GetInputProfileNames())
|
||||
m_ui.inputProfile->addItem(QString::fromStdString(name));
|
||||
|
||||
std::optional<std::string> profile(m_dialog->getStringValue("ControllerPorts", "InputProfileName", std::nullopt));
|
||||
if (profile.has_value())
|
||||
m_ui.inputProfile->setCurrentIndex(m_ui.inputProfile->findText(QString::fromStdString(profile.value())));
|
||||
else
|
||||
m_ui.inputProfile->setCurrentIndex(0);
|
||||
|
||||
populateTracksInfo();
|
||||
}
|
||||
|
||||
static QString MSFTotString(const CDImage::Position& position)
|
||||
{
|
||||
return QStringLiteral("%1:%2:%3 (LBA %4)")
|
||||
.arg(static_cast<uint>(position.minute), 2, 10, static_cast<QChar>('0'))
|
||||
.arg(static_cast<uint>(position.second), 2, 10, static_cast<QChar>('0'))
|
||||
.arg(static_cast<uint>(position.frame), 2, 10, static_cast<QChar>('0'))
|
||||
.arg(static_cast<ulong>(position.ToLBA()));
|
||||
}
|
||||
|
||||
void GameSummaryWidget::populateTracksInfo()
|
||||
{
|
||||
static constexpr std::array<const char*, 8> track_mode_strings = {
|
||||
{"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}};
|
||||
|
||||
m_ui.tracks->clearContents();
|
||||
QtUtils::ResizeColumnsForTableView(m_ui.tracks, {70, 75, 95, 95, 215, 40});
|
||||
|
||||
std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str(), nullptr);
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
const u32 num_tracks = image->GetTrackCount();
|
||||
for (u32 track = 1; track <= num_tracks; track++)
|
||||
{
|
||||
const CDImage::Position position = image->GetTrackStartMSFPosition(static_cast<u8>(track));
|
||||
const CDImage::Position length = image->GetTrackMSFLength(static_cast<u8>(track));
|
||||
const CDImage::TrackMode mode = image->GetTrackMode(static_cast<u8>(track));
|
||||
const int row = static_cast<int>(track - 1u);
|
||||
|
||||
QTableWidgetItem* num = new QTableWidgetItem(tr("Track %1").arg(track));
|
||||
num->setIcon(QIcon::fromTheme((mode == CDImage::TrackMode::Audio) ? QStringLiteral("file-music-line") :
|
||||
QStringLiteral("dvd-line")));
|
||||
m_ui.tracks->insertRow(row);
|
||||
m_ui.tracks->setItem(row, 0, num);
|
||||
m_ui.tracks->setItem(row, 1, new QTableWidgetItem(track_mode_strings[static_cast<u32>(mode)]));
|
||||
m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position)));
|
||||
m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length)));
|
||||
m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<not computed>")));
|
||||
|
||||
QTableWidgetItem* status = new QTableWidgetItem(QString());
|
||||
status->setTextAlignment(Qt::AlignCenter);
|
||||
m_ui.tracks->setItem(row, 5, status);
|
||||
}
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onInputProfileChanged(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
m_dialog->setStringSettingValue("ControllerPorts", "InputProfileName", std::nullopt);
|
||||
else
|
||||
m_dialog->setStringSettingValue("ControllerPorts", "InputProfileName", m_ui.inputProfile->itemText(index).toUtf8());
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onComputeHashClicked()
|
||||
{
|
||||
// Search redump when it's already computed.
|
||||
if (!m_redump_search_keyword.empty())
|
||||
{
|
||||
QtUtils::OpenURL(this, fmt::format("http://redump.org/discs/quicksearch/{}", m_redump_search_keyword).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str(), nullptr);
|
||||
if (!image)
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), tr("Failed to open CD image for hashing."));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef _DEBUGFAST
|
||||
// Kick off hash preparation asynchronously, as building the map of results may take a while
|
||||
// This breaks for DebugFast because of the iterator debug level mismatch.
|
||||
QFuture<const GameDatabase::TrackHashesMap*> result =
|
||||
QtConcurrent::run([]() { return &GameDatabase::GetTrackHashesMap(); });
|
||||
#endif
|
||||
|
||||
QtProgressCallback progress_callback(this);
|
||||
progress_callback.SetProgressRange(image->GetTrackCount());
|
||||
|
||||
std::vector<CDImageHasher::Hash> track_hashes;
|
||||
track_hashes.reserve(image->GetTrackCount());
|
||||
|
||||
// Calculate hashes
|
||||
bool calculate_hash_success = true;
|
||||
for (u8 track = 1; track <= image->GetTrackCount(); track++)
|
||||
{
|
||||
progress_callback.SetProgressValue(track - 1);
|
||||
progress_callback.PushState();
|
||||
|
||||
CDImageHasher::Hash hash;
|
||||
if (!CDImageHasher::GetTrackHash(image.get(), track, &hash, &progress_callback))
|
||||
{
|
||||
progress_callback.PopState();
|
||||
calculate_hash_success = false;
|
||||
break;
|
||||
}
|
||||
track_hashes.emplace_back(hash);
|
||||
|
||||
QTableWidgetItem* item = m_ui.tracks->item(track - 1, 4);
|
||||
item->setText(QString::fromStdString(CDImageHasher::HashToString(hash)));
|
||||
|
||||
progress_callback.PopState();
|
||||
}
|
||||
|
||||
// Verify hashes against gamedb
|
||||
std::vector<bool> verification_results(image->GetTrackCount(), false);
|
||||
if (calculate_hash_success)
|
||||
{
|
||||
std::string found_revision;
|
||||
m_redump_search_keyword = CDImageHasher::HashToString(track_hashes.front());
|
||||
|
||||
progress_callback.SetStatusText("Verifying hashes...");
|
||||
progress_callback.SetProgressValue(image->GetTrackCount());
|
||||
|
||||
// Verification strategy used:
|
||||
// 1. First, find all matches for the data track
|
||||
// If none are found, fail verification for all tracks
|
||||
// 2. For each data track match, try to match all audio tracks
|
||||
// If all match, assume this revision. Else, try other revisions,
|
||||
// and accept the one with the most matches.
|
||||
#ifndef _DEBUGFAST
|
||||
const GameDatabase::TrackHashesMap& hashes_map = *result.result();
|
||||
#else
|
||||
const GameDatabase::TrackHashesMap& hashes_map = GameDatabase::GetTrackHashesMap();
|
||||
#endif
|
||||
|
||||
auto data_track_matches = hashes_map.equal_range(track_hashes[0]);
|
||||
if (data_track_matches.first != data_track_matches.second)
|
||||
{
|
||||
auto best_data_match = data_track_matches.second;
|
||||
for (auto iter = data_track_matches.first; iter != data_track_matches.second; ++iter)
|
||||
{
|
||||
std::vector<bool> current_verification_results(image->GetTrackCount(), false);
|
||||
const auto& data_track_attribs = iter->second;
|
||||
current_verification_results[0] = true; // Data track already matched
|
||||
|
||||
for (auto audio_tracks_iter = std::next(track_hashes.begin()); audio_tracks_iter != track_hashes.end();
|
||||
++audio_tracks_iter)
|
||||
{
|
||||
auto audio_track_matches = hashes_map.equal_range(*audio_tracks_iter);
|
||||
for (auto audio_iter = audio_track_matches.first; audio_iter != audio_track_matches.second; ++audio_iter)
|
||||
{
|
||||
// If audio track comes from the same revision and code as the data track, "pass" it
|
||||
if (audio_iter->second == data_track_attribs)
|
||||
{
|
||||
current_verification_results[std::distance(track_hashes.begin(), audio_tracks_iter)] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto old_matches_count = std::count(verification_results.begin(), verification_results.end(), true);
|
||||
const auto new_matches_count =
|
||||
std::count(current_verification_results.begin(), current_verification_results.end(), true);
|
||||
|
||||
if (new_matches_count > old_matches_count)
|
||||
{
|
||||
best_data_match = iter;
|
||||
verification_results = current_verification_results;
|
||||
// If all elements got matched, early out
|
||||
if (new_matches_count >= static_cast<ptrdiff_t>(verification_results.size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found_revision = best_data_match->second.revisionString;
|
||||
}
|
||||
|
||||
if (!found_revision.empty())
|
||||
{
|
||||
m_ui.revision->setText(
|
||||
tr("Revision: %1").arg(found_revision.empty() ? tr("N/A") : QString::fromStdString(found_revision)));
|
||||
m_ui.revision->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
for (u8 track = 0; track < image->GetTrackCount(); track++)
|
||||
{
|
||||
QTableWidgetItem* hash_text = m_ui.tracks->item(track, 4);
|
||||
QTableWidgetItem* status_text = m_ui.tracks->item(track, 5);
|
||||
QBrush brush;
|
||||
if (verification_results[track])
|
||||
{
|
||||
brush = QColor(0, 200, 0);
|
||||
status_text->setText(QString::fromUtf8(u8"\u2713"));
|
||||
}
|
||||
else
|
||||
{
|
||||
brush = QColor(200, 0, 0);
|
||||
status_text->setText(QString::fromUtf8(u8"\u2715"));
|
||||
}
|
||||
status_text->setForeground(brush);
|
||||
hash_text->setForeground(brush);
|
||||
}
|
||||
|
||||
if (!m_redump_search_keyword.empty())
|
||||
m_ui.computeHashes->setText(tr("Search on Redump.org"));
|
||||
else
|
||||
m_ui.computeHashes->setEnabled(false);
|
||||
}
|
||||
38
src/duckstation-qt/gamesummarywidget.h
Normal file
38
src/duckstation-qt/gamesummarywidget.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include "ui_gamesummarywidget.h"
|
||||
|
||||
enum class DiscRegion : u8;
|
||||
|
||||
namespace GameDatabase {
|
||||
struct Entry;
|
||||
}
|
||||
|
||||
class SettingsDialog;
|
||||
|
||||
class GameSummaryWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameSummaryWidget(const std::string& path, const std::string& serial, DiscRegion region,
|
||||
const GameDatabase::Entry* entry, SettingsDialog* dialog, QWidget* parent);
|
||||
~GameSummaryWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onInputProfileChanged(int index);
|
||||
void onComputeHashClicked();
|
||||
|
||||
private:
|
||||
void populateUi(const std::string& path, const std::string& serial, DiscRegion region,
|
||||
const GameDatabase::Entry* entry);
|
||||
void populateTracksInfo();
|
||||
|
||||
Ui::GameSummaryWidget m_ui;
|
||||
SettingsDialog* m_dialog;
|
||||
|
||||
std::string m_path;
|
||||
std::string m_redump_search_keyword;
|
||||
};
|
||||
285
src/duckstation-qt/gamesummarywidget.ui
Normal file
285
src/duckstation-qt/gamesummarywidget.ui
Normal file
@@ -0,0 +1,285 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameSummaryWidget</class>
|
||||
<widget class="QWidget" name="GameSummaryWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>692</width>
|
||||
<height>562</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="genre">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="region">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Image Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="path">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Serial:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="0" colspan="2">
|
||||
<widget class="QTableWidget" name="tracks">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>#</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Hash</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Status</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Region:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Developer:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Controllers:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Tracks:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QLineEdit" name="controllers">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="entryType">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Release Info:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Input Profile:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="serial">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Genre:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="inputProfile"/>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="revision">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="computeHashes">
|
||||
<property name="text">
|
||||
<string>Compute Hashes...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="developer">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QLineEdit" name="releaseInfo">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Compatibility:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QComboBox" name="compatibility">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editCompatibility">
|
||||
<property name="text">
|
||||
<string>Edit...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,22 +1,24 @@
|
||||
#include "gdbconnection.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "common/log.h"
|
||||
#include "core/gdb_protocol.h"
|
||||
#include "qthost.h"
|
||||
Log_SetChannel(GDBConnection);
|
||||
|
||||
GDBConnection::GDBConnection(QObject *parent, int descriptor)
|
||||
: QThread(parent), m_descriptor(descriptor)
|
||||
GDBConnection::GDBConnection(QObject* parent, int descriptor) : QThread(parent), m_descriptor(descriptor)
|
||||
{
|
||||
Log_InfoPrintf("(%u) Accepted new connection on GDB server", m_descriptor);
|
||||
|
||||
connect(&m_socket, &QTcpSocket::readyRead, this, &GDBConnection::receivedData);
|
||||
connect(&m_socket, &QTcpSocket::disconnected, this, &GDBConnection::gotDisconnected);
|
||||
|
||||
if (m_socket.setSocketDescriptor(m_descriptor)) {
|
||||
QtHostInterface::GetInstance()->pauseSystem(true, true);
|
||||
if (m_socket.setSocketDescriptor(m_descriptor))
|
||||
{
|
||||
g_emu_thread->setSystemPaused(true, true);
|
||||
}
|
||||
else {
|
||||
Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor,
|
||||
m_socket.errorString().toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,52 +33,60 @@ void GDBConnection::receivedData()
|
||||
qint64 bytesRead;
|
||||
char buffer[256];
|
||||
|
||||
while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0) {
|
||||
for (char c : std::string_view(buffer, bytesRead)) {
|
||||
while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0)
|
||||
{
|
||||
for (char c : std::string_view(buffer, bytesRead))
|
||||
{
|
||||
m_readBuffer.push_back(c);
|
||||
|
||||
if (GDBProtocol::IsPacketInterrupt(m_readBuffer)) {
|
||||
if (GDBProtocol::IsPacketInterrupt(m_readBuffer))
|
||||
{
|
||||
Log_DebugPrintf("(%u) > Interrupt request", m_descriptor);
|
||||
QtHostInterface::GetInstance()->pauseSystem(true, true);
|
||||
g_emu_thread->setSystemPaused(true, true);
|
||||
m_readBuffer.erase();
|
||||
}
|
||||
else if (GDBProtocol::IsPacketContinue(m_readBuffer)) {
|
||||
else if (GDBProtocol::IsPacketContinue(m_readBuffer))
|
||||
{
|
||||
Log_DebugPrintf("(%u) > Continue request", m_descriptor);
|
||||
QtHostInterface::GetInstance()->pauseSystem(false, false);
|
||||
g_emu_thread->setSystemPaused(false, false);
|
||||
m_readBuffer.erase();
|
||||
}
|
||||
else if (GDBProtocol::IsPacketComplete(m_readBuffer)) {
|
||||
else if (GDBProtocol::IsPacketComplete(m_readBuffer))
|
||||
{
|
||||
Log_DebugPrintf("(%u) > %s", m_descriptor, m_readBuffer.c_str());
|
||||
writePacket(GDBProtocol::ProcessPacket(m_readBuffer));
|
||||
m_readBuffer.erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytesRead == -1) {
|
||||
if (bytesRead == -1)
|
||||
{
|
||||
Log_ErrorPrintf("(%u) Failed to read from socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
void GDBConnection::onEmulationPaused(bool paused)
|
||||
void GDBConnection::onEmulationPaused()
|
||||
{
|
||||
if (paused) {
|
||||
if (m_seen_resume) {
|
||||
m_seen_resume = false;
|
||||
// Generate a stop reply packet, insert '?' command to generate it.
|
||||
writePacket(GDBProtocol::ProcessPacket("$?#3f"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_seen_resume = true;
|
||||
// Send ack, in case GDB sent a continue request.
|
||||
writePacket("+");
|
||||
if (m_seen_resume)
|
||||
{
|
||||
m_seen_resume = false;
|
||||
// Generate a stop reply packet, insert '?' command to generate it.
|
||||
writePacket(GDBProtocol::ProcessPacket("$?#3f"));
|
||||
}
|
||||
}
|
||||
|
||||
void GDBConnection::onEmulationResumed()
|
||||
{
|
||||
m_seen_resume = true;
|
||||
// Send ack, in case GDB sent a continue request.
|
||||
writePacket("+");
|
||||
}
|
||||
|
||||
void GDBConnection::writePacket(std::string_view packet)
|
||||
{
|
||||
Log_DebugPrintf("(%u) < %*s", m_descriptor, packet.length(), packet.data());
|
||||
if (m_socket.write(packet.data(), packet.length()) == -1) {
|
||||
if (m_socket.write(packet.data(), packet.length()) == -1)
|
||||
{
|
||||
Log_ErrorPrintf("(%u) Failed to write to socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ public:
|
||||
public Q_SLOTS:
|
||||
void gotDisconnected();
|
||||
void receivedData();
|
||||
void onEmulationPaused(bool paused);
|
||||
void onEmulationPaused();
|
||||
void onEmulationResumed();
|
||||
|
||||
private:
|
||||
void writePacket(std::string_view data);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "gdbserver.h"
|
||||
#include "gdbconnection.h"
|
||||
#include "common/log.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
Log_SetChannel(GDBServer);
|
||||
|
||||
GDBServer::GDBServer(QObject *parent, u16 port)
|
||||
@@ -29,7 +29,8 @@ void GDBServer::incomingConnection(qintptr descriptor)
|
||||
{
|
||||
Log_InfoPrint("Accepted connection on GDB server");
|
||||
GDBConnection *thread = new GDBConnection(this, descriptor);
|
||||
connect(QtHostInterface::GetInstance(), &QtHostInterface::emulationPaused, thread, &GDBConnection::onEmulationPaused);
|
||||
connect(g_emu_thread, &EmuThread::systemPaused, thread, &GDBConnection::onEmulationPaused);
|
||||
connect(g_emu_thread, &EmuThread::systemResumed, thread, &GDBConnection::onEmulationResumed);
|
||||
thread->start();
|
||||
m_connections.push_back(thread);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,42 @@
|
||||
#include "generalsettingswidget.h"
|
||||
#include "autoupdaterdialog.h"
|
||||
#include "frontend-common/controller_interface.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qtutils.h"
|
||||
#include "scmversion/scmversion.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
|
||||
GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
GeneralSettingsWidget::GeneralSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(ControllerInterface::Backend::Count); i++)
|
||||
{
|
||||
m_ui.controllerBackend->addItem(qApp->translate(
|
||||
"ControllerInterface", ControllerInterface::GetBackendName(static_cast<ControllerInterface::Backend>(i))));
|
||||
}
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.inhibitScreensaver, "Main", "InhibitScreensaver", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "Main", "PauseOnFocusLoss", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnStart, "Main", "StartPaused", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.saveStateOnExit, "Main", "SaveStateOnExit", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.loadDevicesFromSaveStates, "Main", "LoadDevicesFromSaveStates",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.applyGameSettings, "Main", "ApplyGameSettings", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoLoadCheats, "Main", "AutoLoadCheats", true);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "Main", "StartPaused", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnFocusLoss, "Main", "PauseOnFocusLoss",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.startFullscreen, "Main", "StartFullscreen", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.doubleClickTogglesFullscreen, "Main",
|
||||
"DoubleClickTogglesFullscreen", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.renderToSeparateWindow, "Main", "RenderToSeparateWindow",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startFullscreen, "Main", "StartFullscreen",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.hideCursorInFullscreen, "Main",
|
||||
"HideCursorInFullscreen", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.inhibitScreensaver, "Main", "InhibitScreensaver",
|
||||
true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.renderToMain, "Main", "RenderToMainWindow", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "Main", "SaveStateOnExit", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main", "ConfirmPowerOff", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.loadDevicesFromSaveStates, "Main",
|
||||
"LoadDevicesFromSaveStates", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.applyGameSettings, "Main", "ApplyGameSettings",
|
||||
true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.autoLoadCheats, "Main", "AutoLoadCheats", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableFullscreenUI, "Main", "EnableFullscreenUI",
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hideMainWindow, "Main", "HideMainWindowWhenRunning", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableWindowResizing, "Main", "DisableWindowResize", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hideMouseCursor, "Main", "HideCursorInFullscreen", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.createSaveStateBackups, "Main", "CreateSaveStateBackups",
|
||||
false);
|
||||
connect(m_ui.renderToSeparateWindow, &QCheckBox::stateChanged, this,
|
||||
&GeneralSettingsWidget::onRenderToSeparateWindowChanged);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(
|
||||
m_host_interface, m_ui.controllerBackend, "Main", "ControllerBackend", &ControllerInterface::ParseBackendName,
|
||||
&ControllerInterface::GetBackendName, ControllerInterface::GetDefaultBackend());
|
||||
onRenderToSeparateWindowChanged();
|
||||
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"),
|
||||
@@ -51,15 +47,15 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
|
||||
"resume directly from where you left off next time."));
|
||||
dialog->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"),
|
||||
tr("Automatically switches to fullscreen mode when a game is started."));
|
||||
dialog->registerWidgetHelp(m_ui.hideCursorInFullscreen, tr("Hide Cursor In Fullscreen"), tr("Checked"),
|
||||
dialog->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Checked"),
|
||||
tr("Hides the mouse pointer/cursor when the emulator is in fullscreen mode."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.inhibitScreensaver, tr("Inhibit Screensaver"), tr("Checked"),
|
||||
tr("Prevents the screen saver from activating and the host from sleeping while emulation is running."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.renderToMain, tr("Render To Main Window"), tr("Checked"),
|
||||
m_ui.renderToSeparateWindow, tr("Render To Separate Window"), tr("Checked"),
|
||||
tr("Renders the display of the simulated console to the main window of the application, over "
|
||||
"the game list. If unchecked, the display will render in a separate window."));
|
||||
"the game list. If checked, the display will render in a separate window."));
|
||||
dialog->registerWidgetHelp(m_ui.pauseOnStart, tr("Pause On Start"), tr("Unchecked"),
|
||||
tr("Pauses the emulator when a game is started."));
|
||||
dialog->registerWidgetHelp(m_ui.pauseOnFocusLoss, tr("Pause On Focus Loss"), tr("Unchecked"),
|
||||
@@ -76,49 +72,32 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
|
||||
"leave this option enabled except when testing enhancements with incompatible games."));
|
||||
dialog->registerWidgetHelp(m_ui.autoLoadCheats, tr("Automatically Load Cheats"), tr("Unchecked"),
|
||||
tr("Automatically loads and applies cheats on game start."));
|
||||
dialog->registerWidgetHelp(m_ui.controllerBackend, tr("Controller Backend"),
|
||||
qApp->translate("ControllerInterface", ControllerInterface::GetBackendName(
|
||||
ControllerInterface::GetDefaultBackend())),
|
||||
tr("Determines the backend which is used for controller input. Windows users may prefer "
|
||||
"to use XInput over SDL2 for compatibility."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.enableFullscreenUI, tr("Enable Fullscreen UI"), tr("Unchecked"),
|
||||
tr("Enables the fullscreen UI mode, suitable for controller operation which is used in the NoGUI frontend."));
|
||||
|
||||
// Since this one is compile-time selected, we don't put it in the .ui file.
|
||||
int current_col = 0;
|
||||
int current_row = m_ui.formLayout_4->rowCount() - current_col;
|
||||
#ifdef WITH_DISCORD_PRESENCE
|
||||
{
|
||||
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main",
|
||||
"EnableDiscordPresence");
|
||||
m_ui.formLayout_4->addWidget(enableDiscordPresence, current_row, current_col);
|
||||
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDiscordPresence, "Main", "EnableDiscordPresence",
|
||||
false);
|
||||
dialog->registerWidgetHelp(m_ui.enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
|
||||
tr("Shows the game you are currently playing as part of your profile in Discord."));
|
||||
current_col++;
|
||||
current_row += (current_col / 2);
|
||||
current_col %= 2;
|
||||
}
|
||||
#else
|
||||
{
|
||||
m_ui.enableDiscordPresence->setEnabled(false);
|
||||
}
|
||||
#endif
|
||||
if (AutoUpdaterDialog::isSupported())
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.autoUpdateEnabled, "AutoUpdater",
|
||||
"CheckAtStartup", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup", true);
|
||||
dialog->registerWidgetHelp(m_ui.autoUpdateEnabled, tr("Enable Automatic Update Check"), tr("Checked"),
|
||||
tr("Automatically checks for updates to the program on startup. Updates can be deferred "
|
||||
"until later or skipped entirely."));
|
||||
|
||||
m_ui.autoUpdateTag->addItems(AutoUpdaterDialog::getTagList());
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_ui.autoUpdateTag, "AutoUpdater", "UpdateTag",
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.autoUpdateTag, "AutoUpdater", "UpdateTag",
|
||||
AutoUpdaterDialog::getDefaultTag());
|
||||
|
||||
m_ui.autoUpdateCurrentVersion->setText(tr("%1 (%2)").arg(g_scm_tag_str).arg(g_scm_date_str));
|
||||
connect(m_ui.checkForUpdates, &QPushButton::clicked,
|
||||
[this]() { m_host_interface->getMainWindow()->checkForUpdates(true); });
|
||||
current_col++;
|
||||
current_row += (current_col / 2);
|
||||
current_col %= 2;
|
||||
connect(m_ui.checkForUpdates, &QPushButton::clicked, [this]() { g_main_window->checkForUpdates(true); });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -128,3 +107,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
|
||||
}
|
||||
|
||||
GeneralSettingsWidget::~GeneralSettingsWidget() = default;
|
||||
|
||||
void GeneralSettingsWidget::onRenderToSeparateWindowChanged()
|
||||
{
|
||||
m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ui_generalsettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class GeneralSettingsWidget : public QWidget
|
||||
@@ -12,11 +11,14 @@ class GeneralSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GeneralSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
explicit GeneralSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~GeneralSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onRenderToSeparateWindowChanged();
|
||||
|
||||
private:
|
||||
Ui::GeneralSettingsWidget m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
@@ -32,52 +32,17 @@
|
||||
<string>Behaviour</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="formLayout_4">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="confirmPowerOff">
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="autoLoadCheats">
|
||||
<property name="text">
|
||||
<string>Confirm Power Off</string>
|
||||
<string>Automatically Load Cheats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="inhibitScreensaver">
|
||||
<property name="text">
|
||||
<string>Inhibit Screensaver</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="renderToMain">
|
||||
<property name="text">
|
||||
<string>Render To Main Window</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="pauseOnStart">
|
||||
<property name="text">
|
||||
<string>Pause On Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="pauseOnFocusLoss">
|
||||
<property name="text">
|
||||
<string>Pause On Focus Loss</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="startFullscreen">
|
||||
<property name="text">
|
||||
<string>Start Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="saveStateOnExit">
|
||||
<property name="text">
|
||||
<string>Save State On Exit</string>
|
||||
<string>Save State On Shutdown</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -88,10 +53,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="autoLoadCheats">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="inhibitScreensaver">
|
||||
<property name="text">
|
||||
<string>Automatically Load Cheats</string>
|
||||
<string>Inhibit Screensaver</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -102,17 +67,38 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="hideCursorInFullscreen">
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="confirmPowerOff">
|
||||
<property name="text">
|
||||
<string>Hide Cursor In Fullscreen</string>
|
||||
<string>Confirm Power Off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="pauseOnFocusLoss">
|
||||
<property name="text">
|
||||
<string>Pause On Focus Loss</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="enableFullscreenUI">
|
||||
<widget class="QCheckBox" name="pauseOnStart">
|
||||
<property name="text">
|
||||
<string>Enable Fullscreen UI</string>
|
||||
<string>Pause On Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="createSaveStateBackups">
|
||||
<property name="text">
|
||||
<string>Create Save State Backups</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="enableDiscordPresence">
|
||||
<property name="text">
|
||||
<string>Enable Discord Presence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -120,24 +106,52 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Miscellaneous</string>
|
||||
<string>Game Display</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Controller Backend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="controllerBackend"/>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QCheckBox" name="startFullscreen">
|
||||
<property name="text">
|
||||
<string>Start Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="doubleClickTogglesFullscreen">
|
||||
<property name="text">
|
||||
<string>Double-Click Toggles Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="renderToSeparateWindow">
|
||||
<property name="text">
|
||||
<string>Render To Separate Window</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="hideMainWindow">
|
||||
<property name="text">
|
||||
<string>Hide Main Window When Running</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="disableWindowResizing">
|
||||
<property name="text">
|
||||
<string>Disable Window Resizing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="hideMouseCursor">
|
||||
<property name="text">
|
||||
<string>Hide Cursor In Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
#include "hotkeysettingswidget.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/settings.h"
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QScrollArea>
|
||||
|
||||
HotkeySettingsWidget::HotkeySettingsWidget(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
HotkeySettingsWidget::HotkeySettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
createUi();
|
||||
}
|
||||
@@ -24,49 +22,57 @@ void HotkeySettingsWidget::createUi()
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_tab_widget = new QTabWidget(this);
|
||||
m_scroll_area = new QScrollArea(this);
|
||||
m_container = new QWidget(m_scroll_area);
|
||||
m_layout = new QVBoxLayout(m_container);
|
||||
m_scroll_area->setWidget(m_container);
|
||||
m_scroll_area->setWidgetResizable(true);
|
||||
m_scroll_area->setBackgroundRole(QPalette::Base);
|
||||
|
||||
createButtons();
|
||||
|
||||
layout->addWidget(m_tab_widget, 0, 0, 1, 1);
|
||||
m_layout->addStretch(1);
|
||||
layout->addWidget(m_scroll_area, 0, 0, 1, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void HotkeySettingsWidget::createButtons()
|
||||
{
|
||||
const auto& hotkeys = m_host_interface->getHotkeyInfoList();
|
||||
for (const auto& hi : hotkeys)
|
||||
const std::vector<const HotkeyInfo*> hotkeys(InputManager::GetHotkeyList());
|
||||
for (const HotkeyInfo* hotkey : hotkeys)
|
||||
{
|
||||
const auto category = qApp->translate("Hotkeys", hi.category);
|
||||
const QString category(qApp->translate("Hotkeys", hotkey->category));
|
||||
|
||||
auto iter = m_categories.find(category);
|
||||
if (iter == m_categories.end())
|
||||
{
|
||||
QScrollArea* scroll = new QScrollArea(m_tab_widget);
|
||||
QWidget* container = new QWidget(scroll);
|
||||
QVBoxLayout* vlayout = new QVBoxLayout(container);
|
||||
QLabel* label = new QLabel(category, m_container);
|
||||
QFont label_font(label->font());
|
||||
label_font.setPointSizeF(14.0f);
|
||||
label->setFont(label_font);
|
||||
m_layout->addWidget(label);
|
||||
|
||||
QLabel* line = new QLabel(m_container);
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFixedHeight(4);
|
||||
m_layout->addWidget(line);
|
||||
|
||||
QGridLayout* layout = new QGridLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
vlayout->addLayout(layout);
|
||||
vlayout->addStretch(1);
|
||||
iter = m_categories.insert(category, Category{container, layout});
|
||||
scroll->setWidget(container);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setBackgroundRole(QPalette::Base);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
m_tab_widget->addTab(scroll, category);
|
||||
m_layout->addLayout(layout);
|
||||
iter = m_categories.insert(category, layout);
|
||||
}
|
||||
|
||||
QWidget* container = iter->container;
|
||||
QGridLayout* layout = iter->layout;
|
||||
QGridLayout* layout = *iter;
|
||||
const int target_row = layout->count() / 2;
|
||||
|
||||
std::string section_name("Hotkeys");
|
||||
std::string key_name(hi.name.GetCharArray());
|
||||
layout->addWidget(new QLabel(qApp->translate("Hotkeys", hi.display_name), container), target_row, 0);
|
||||
layout->addWidget(
|
||||
new InputButtonBindingWidget(m_host_interface, std::move(section_name), std::move(key_name), container),
|
||||
target_row, 1);
|
||||
QLabel* label = new QLabel(qApp->translate("Hotkeys", hotkey->display_name), m_container);
|
||||
layout->addWidget(label, target_row, 0);
|
||||
|
||||
InputBindingWidget* bind =
|
||||
new InputBindingWidget(m_container, m_dialog->getProfileSettingsInterface(), "Hotkeys", hotkey->name);
|
||||
bind->setMinimumWidth(300);
|
||||
layout->addWidget(bind, target_row, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
#pragma once
|
||||
#include "core/types.h"
|
||||
#include <QtWidgets/QTabWidget>
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class QtHostInterface;
|
||||
class QScrollArea;
|
||||
class QGridLayout;
|
||||
class QVBoxLayout;
|
||||
|
||||
class ControllerSettingsDialog;
|
||||
|
||||
class HotkeySettingsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HotkeySettingsWidget(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||
HotkeySettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog);
|
||||
~HotkeySettingsWidget();
|
||||
|
||||
private:
|
||||
void createUi();
|
||||
void createButtons();
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
QScrollArea* m_scroll_area = nullptr;
|
||||
QWidget* m_container = nullptr;
|
||||
QVBoxLayout* m_layout = nullptr;
|
||||
|
||||
QTabWidget* m_tab_widget;
|
||||
|
||||
struct Category
|
||||
{
|
||||
QWidget* container;
|
||||
QGridLayout* layout;
|
||||
};
|
||||
QMap<QString, Category> m_categories;
|
||||
QMap<QString, QGridLayout*> m_categories;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
#include "inputbindingdialog.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "frontend-common/controller_interface.h"
|
||||
#include "inputbindingmonitor.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <QtGui/QWheelEvent>
|
||||
|
||||
InputBindingDialog::InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
InputBindingDialog::InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name,
|
||||
std::vector<std::string> bindings, QWidget* parent)
|
||||
: QDialog(parent), m_host_interface(host_interface), m_section_name(std::move(section_name)),
|
||||
m_key_name(std::move(key_name)), m_bindings(std::move(bindings))
|
||||
: QDialog(parent), m_sif(sif), m_section_name(std::move(section_name)), m_key_name(std::move(key_name)),
|
||||
m_bindings(std::move(bindings))
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.title->setText(
|
||||
@@ -39,34 +35,81 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
|
||||
const QEvent::Type event_type = event->type();
|
||||
|
||||
// if the key is being released, set the input
|
||||
if (event_type == QEvent::KeyRelease)
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonRelease)
|
||||
{
|
||||
addNewBinding(std::move(m_new_binding_value));
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::KeyPress)
|
||||
{
|
||||
const QKeyEvent* key_event = static_cast<const QKeyEvent*>(event);
|
||||
const QString binding(QtUtils::KeyEventToString(key_event->key(), key_event->modifiers()));
|
||||
if (!binding.isEmpty())
|
||||
m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString();
|
||||
m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(QtUtils::KeyEventToCode(key_event)));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
// double clicks get triggered if we click bind, then click again quickly.
|
||||
unsigned button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
|
||||
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::Wheel)
|
||||
{
|
||||
const QPoint delta_angle(static_cast<QWheelEvent*>(event)->angleDelta());
|
||||
const float dx = std::clamp(static_cast<float>(delta_angle.x()) / QtUtils::MOUSE_WHEEL_DELTA, -1.0f, 1.0f);
|
||||
if (dx != 0.0f)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX));
|
||||
key.negative = (dx < 0.0f);
|
||||
m_new_bindings.push_back(key);
|
||||
}
|
||||
|
||||
const float dy = std::clamp(static_cast<float>(delta_angle.y()) / QtUtils::MOUSE_WHEEL_DELTA, -1.0f, 1.0f);
|
||||
if (dy != 0.0f)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY));
|
||||
key.negative = (dy < 0.0f);
|
||||
m_new_bindings.push_back(key);
|
||||
}
|
||||
|
||||
if (dx != 0.0f || dy != 0.0f)
|
||||
{
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonRelease)
|
||||
else if (event_type == QEvent::MouseMove && m_mouse_mapping_enabled)
|
||||
{
|
||||
const u32 button_mask = static_cast<u32>(static_cast<const QMouseEvent*>(event)->button());
|
||||
const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask);
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1);
|
||||
addNewBinding(std::move(m_new_binding_value));
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
// if we've moved more than a decent distance from the center of the widget, bind it.
|
||||
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
|
||||
static constexpr const s32 THRESHOLD = 50;
|
||||
const QPointF diff(static_cast<QMouseEvent*>(event)->globalPosition() - m_input_listen_start_position);
|
||||
bool has_one = false;
|
||||
|
||||
if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
return true;
|
||||
if (std::abs(diff.x()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
|
||||
key.negative = (diff.x() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
if (std::abs(diff.y()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
|
||||
key.negative = (diff.y() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
|
||||
if (has_one)
|
||||
{
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -86,6 +129,9 @@ void InputBindingDialog::onInputListenTimerTimeout()
|
||||
|
||||
void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
m_new_bindings.clear();
|
||||
m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled();
|
||||
m_input_listen_start_position = QCursor::pos();
|
||||
m_input_listen_timer = new QTimer(this);
|
||||
m_input_listen_timer->setSingleShot(false);
|
||||
m_input_listen_timer->start(1000);
|
||||
@@ -102,6 +148,8 @@ void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
||||
installEventFilter(this);
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
setMouseTracking(true);
|
||||
hookInputManager();
|
||||
}
|
||||
|
||||
void InputBindingDialog::stopListeningForInput()
|
||||
@@ -115,50 +163,29 @@ void InputBindingDialog::stopListeningForInput()
|
||||
delete m_input_listen_timer;
|
||||
m_input_listen_timer = nullptr;
|
||||
|
||||
unhookInputManager();
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
setMouseTracking(false);
|
||||
removeEventFilter(this);
|
||||
}
|
||||
|
||||
void InputBindingDialog::addNewBinding(std::string new_binding)
|
||||
void InputBindingDialog::addNewBinding()
|
||||
{
|
||||
if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end())
|
||||
if (m_new_bindings.empty())
|
||||
return;
|
||||
|
||||
m_ui.bindingList->addItem(QString::fromStdString(new_binding));
|
||||
m_bindings.push_back(std::move(new_binding));
|
||||
saveListToSettings();
|
||||
}
|
||||
|
||||
void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive)
|
||||
{
|
||||
const char* invert_char = inverted ? "-" : "";
|
||||
const char* sign_char = "";
|
||||
if (half_axis_positive)
|
||||
const std::string new_binding(
|
||||
InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size()));
|
||||
if (!new_binding.empty())
|
||||
{
|
||||
sign_char = *half_axis_positive ? "+" : "-";
|
||||
if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end())
|
||||
return;
|
||||
|
||||
m_ui.bindingList->addItem(QString::fromStdString(new_binding));
|
||||
m_bindings.push_back(std::move(new_binding));
|
||||
saveListToSettings();
|
||||
}
|
||||
|
||||
std::string binding =
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d%s", controller_index, sign_char, axis_index, invert_char);
|
||||
addNewBinding(std::move(binding));
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingDialog::bindToControllerButton(int controller_index, int button_index)
|
||||
{
|
||||
std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index);
|
||||
addNewBinding(std::move(binding));
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingDialog::bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction)
|
||||
{
|
||||
std::string binding = StringUtil::StdStringFromFormat("Controller%d/Hat%d %s", controller_index, hat_index,
|
||||
hat_direction.toLatin1().constData());
|
||||
addNewBinding(std::move(binding));
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingDialog::onAddBindingButtonClicked()
|
||||
@@ -196,112 +223,65 @@ void InputBindingDialog::updateList()
|
||||
|
||||
void InputBindingDialog::saveListToSettings()
|
||||
{
|
||||
if (!m_bindings.empty())
|
||||
m_host_interface->SetStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
|
||||
else
|
||||
m_host_interface->RemoveSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
|
||||
m_host_interface->updateInputMap();
|
||||
}
|
||||
|
||||
InputButtonBindingDialog::InputButtonBindingDialog(QtHostInterface* host_interface, std::string section_name,
|
||||
std::string key_name, std::vector<std::string> bindings,
|
||||
QWidget* parent)
|
||||
: InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent)
|
||||
{
|
||||
}
|
||||
|
||||
InputButtonBindingDialog::~InputButtonBindingDialog()
|
||||
{
|
||||
if (isListeningForInput())
|
||||
InputButtonBindingDialog::stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingDialog::hookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->SetHook(InputButtonBindingMonitor(this));
|
||||
}
|
||||
|
||||
void InputButtonBindingDialog::unhookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->ClearHook();
|
||||
}
|
||||
|
||||
void InputButtonBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
InputBindingDialog::startListeningForInput(timeout_in_seconds);
|
||||
hookControllerInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingDialog::stopListeningForInput()
|
||||
{
|
||||
unhookControllerInput();
|
||||
InputBindingDialog::stopListeningForInput();
|
||||
}
|
||||
|
||||
InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name,
|
||||
std::string key_name, std::vector<std::string> bindings,
|
||||
Controller::AxisType axis_type, QWidget* parent)
|
||||
: InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent),
|
||||
m_axis_type(axis_type)
|
||||
{
|
||||
}
|
||||
|
||||
InputAxisBindingDialog::~InputAxisBindingDialog()
|
||||
{
|
||||
if (isListeningForInput())
|
||||
InputAxisBindingDialog::stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputAxisBindingDialog::hookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->SetHook(InputAxisBindingMonitor(this, m_axis_type));
|
||||
}
|
||||
|
||||
void InputAxisBindingDialog::unhookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->ClearHook();
|
||||
}
|
||||
|
||||
bool InputAxisBindingDialog::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (m_axis_type != Controller::AxisType::Half)
|
||||
if (m_sif)
|
||||
{
|
||||
const QEvent::Type event_type = event->type();
|
||||
if (!m_bindings.empty())
|
||||
m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
|
||||
else
|
||||
m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
m_sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_bindings.empty())
|
||||
Host::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
|
||||
else
|
||||
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
g_emu_thread->reloadInputBindings();
|
||||
}
|
||||
}
|
||||
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease)
|
||||
void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value)
|
||||
{
|
||||
const float abs_value = std::abs(value);
|
||||
|
||||
for (InputBindingKey other_key : m_new_bindings)
|
||||
{
|
||||
if (other_key.MaskDirection() == key.MaskDirection())
|
||||
{
|
||||
return true;
|
||||
if (abs_value < 0.5f)
|
||||
{
|
||||
// if this key is in our new binding list, it's a "release", and we're done
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, keep waiting
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return InputBindingDialog::eventFilter(watched, event);
|
||||
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
|
||||
if (abs_value >= 0.5f)
|
||||
{
|
||||
InputBindingKey key_to_add = key;
|
||||
key_to_add.negative = (value < 0.0f);
|
||||
m_new_bindings.push_back(key_to_add);
|
||||
}
|
||||
}
|
||||
|
||||
void InputAxisBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
||||
void InputBindingDialog::hookInputManager()
|
||||
{
|
||||
InputBindingDialog::startListeningForInput(timeout_in_seconds);
|
||||
hookControllerInput();
|
||||
InputManager::SetHook([this](InputBindingKey key, float value) {
|
||||
QMetaObject::invokeMethod(this, "inputManagerHookCallback", Qt::QueuedConnection, Q_ARG(InputBindingKey, key),
|
||||
Q_ARG(float, value));
|
||||
return InputInterceptHook::CallbackResult::StopProcessingEvent;
|
||||
});
|
||||
}
|
||||
|
||||
void InputAxisBindingDialog::stopListeningForInput()
|
||||
void InputBindingDialog::unhookInputManager()
|
||||
{
|
||||
unhookControllerInput();
|
||||
InputBindingDialog::stopListeningForInput();
|
||||
InputManager::RemoveHook();
|
||||
}
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "core/controller.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "ui_inputbindingdialog.h"
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsInterface;
|
||||
|
||||
class InputBindingDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name,
|
||||
std::vector<std::string> bindings, QWidget* parent);
|
||||
~InputBindingDialog();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive);
|
||||
void bindToControllerButton(int controller_index, int button_index);
|
||||
void bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction);
|
||||
void onAddBindingButtonClicked();
|
||||
void onRemoveBindingButtonClicked();
|
||||
void onClearBindingsButtonClicked();
|
||||
void onInputListenTimerTimeout();
|
||||
void inputManagerHookCallback(InputBindingKey key, float value);
|
||||
|
||||
protected:
|
||||
enum : u32
|
||||
@@ -40,56 +37,24 @@ protected:
|
||||
virtual void stopListeningForInput();
|
||||
|
||||
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
|
||||
void addNewBinding(std::string new_binding);
|
||||
void addNewBinding();
|
||||
|
||||
void updateList();
|
||||
void saveListToSettings();
|
||||
|
||||
void hookInputManager();
|
||||
void unhookInputManager();
|
||||
|
||||
Ui::InputBindingDialog m_ui;
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
SettingsInterface* m_sif;
|
||||
std::string m_section_name;
|
||||
std::string m_key_name;
|
||||
std::vector<std::string> m_bindings;
|
||||
std::string m_new_binding_value;
|
||||
std::vector<InputBindingKey> m_new_bindings;
|
||||
|
||||
QTimer* m_input_listen_timer = nullptr;
|
||||
u32 m_input_listen_remaining_seconds = 0;
|
||||
};
|
||||
|
||||
class InputButtonBindingDialog final : public InputBindingDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputButtonBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
std::vector<std::string> bindings, QWidget* parent);
|
||||
~InputButtonBindingDialog();
|
||||
|
||||
protected:
|
||||
void startListeningForInput(u32 timeout_in_seconds) override;
|
||||
void stopListeningForInput() override;
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
};
|
||||
|
||||
class InputAxisBindingDialog final : public InputBindingDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
std::vector<std::string> bindings, Controller::AxisType axis_type, QWidget* parent);
|
||||
~InputAxisBindingDialog();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||
void startListeningForInput(u32 timeout_in_seconds) override;
|
||||
void stopListeningForInput() override;
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
|
||||
private:
|
||||
Controller::AxisType m_axis_type;
|
||||
QPointF m_input_listen_start_position{};
|
||||
bool m_mouse_mapping_enabled = false;
|
||||
};
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
#include "inputbindingmonitor.h"
|
||||
#include <cmath>
|
||||
|
||||
ControllerInterface::Hook::CallbackResult
|
||||
InputButtonBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
{
|
||||
if (ei.type == ControllerInterface::Hook::Type::Axis)
|
||||
{
|
||||
// wait until it's at least half pushed so we don't get confused between axises with small movement
|
||||
if (std::abs(std::get<float>(ei.value)) < 0.5f)
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
|
||||
// TODO: this probably should consider the "last value"
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, false),
|
||||
Q_ARG(std::optional<bool>, std::get<float>(ei.value) > 0));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Button && std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Hat)
|
||||
{
|
||||
const std::string_view hat_position = std::get<std::string_view>(ei.value);
|
||||
if (!hat_position.empty())
|
||||
{
|
||||
QString str = QString::fromLatin1(hat_position.data(), static_cast<int>(hat_position.size()));
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerHat", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(QString, std::move(str)));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
}
|
||||
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult InputAxisBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
{
|
||||
if (ei.type == ControllerInterface::Hook::Type::Axis)
|
||||
{
|
||||
std::optional<bool> half_axis_positive, inverted;
|
||||
if (!ProcessAxisInput(ei, half_axis_positive, inverted))
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, inverted.value_or(false)),
|
||||
Q_ARG(std::optional<bool>, half_axis_positive));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half &&
|
||||
std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index),
|
||||
Q_ARG(int, ei.button_or_axis_number));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
}
|
||||
|
||||
bool InputAxisBindingMonitor::ProcessAxisInput(const ControllerInterface::Hook& ei,
|
||||
std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const
|
||||
{
|
||||
const float value = std::get<float>(ei.value);
|
||||
|
||||
if (!ei.track_history) // Keyboard, mouse, game controller
|
||||
{
|
||||
// wait until it's at least half pushed so we don't get confused between axises with small movement
|
||||
if (std::abs(value) < 0.5f)
|
||||
return false;
|
||||
|
||||
if (m_axis_type == Controller::AxisType::Half)
|
||||
half_axis_positive = (value > 0.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
else // Joystick
|
||||
{
|
||||
auto& history = m_context->m_inputs_history;
|
||||
// Reject inputs coming from multiple sources
|
||||
if (!history.empty())
|
||||
{
|
||||
const auto& item = history.front();
|
||||
if (ei.controller_index != item.controller_index || ei.button_or_axis_number != item.axis_number)
|
||||
return false;
|
||||
}
|
||||
history.push_back({ei.controller_index, ei.button_or_axis_number, value});
|
||||
return AnalyzeInputHistory(half_axis_positive, inverted);
|
||||
}
|
||||
}
|
||||
|
||||
bool InputAxisBindingMonitor::AnalyzeInputHistory(std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const
|
||||
{
|
||||
const auto& history = m_context->m_inputs_history;
|
||||
const auto [min, max] = std::minmax_element(
|
||||
history.begin(), history.end(), [](const auto& left, const auto& right) { return left.value < right.value; });
|
||||
|
||||
// Ignore small input magnitudes
|
||||
if (std::abs(max->value - min->value) < 0.5f)
|
||||
return false;
|
||||
|
||||
// Used heuristics:
|
||||
// * If history contains inputs with both - and + sign (ignoring 0), bind a full axis
|
||||
// * If history contains only 0 and inputs of the same sign AND maxes out at 1.0/-1.0, bind a half axis
|
||||
// * Use the direction of input changes to determine whether the axis is inverted or not
|
||||
if (std::signbit(min->value) != std::signbit(max->value))
|
||||
{
|
||||
if (min->value != 0.0f && max->value != 0.0f)
|
||||
{
|
||||
// If max value comes before the min value, invert the half axis
|
||||
if (m_axis_type == Controller::AxisType::Half)
|
||||
{
|
||||
inverted = std::distance(min, max) < 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((std::abs(min->value) > 0.99f || std::abs(max->value) > 0.99f) &&
|
||||
(std::abs(min->value) < 0.01f || std::abs(max->value) < 0.01f))
|
||||
{
|
||||
|
||||
if (m_axis_type == Controller::AxisType::Half)
|
||||
{
|
||||
half_axis_positive = max->value > 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult
|
||||
InputRumbleBindingMonitor::operator()(const ControllerInterface::Hook& ei) const
|
||||
{
|
||||
if (ei.type == ControllerInterface::Hook::Type::Button && std::get<float>(ei.value) > 0.0f)
|
||||
{
|
||||
QMetaObject::invokeMethod(m_parent, "bindToControllerRumble", Q_ARG(int, ei.controller_index));
|
||||
return ControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||
}
|
||||
|
||||
return ControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "frontend-common/controller_interface.h"
|
||||
#include <QtCore/QObject>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// NOTE: Those Monitor classes must be copyable to meet the requirements of std::function, but at the same time we want
|
||||
// copies to be opaque to the caling code and share context. Therefore, all mutable context of the monitor (if required)
|
||||
// must be enclosed in a std::shared_ptr. m_parent/m_axis_type don't mutate so they don't need to be stored as such.
|
||||
|
||||
class InputButtonBindingMonitor
|
||||
{
|
||||
public:
|
||||
explicit InputButtonBindingMonitor(QObject* parent) : m_parent(parent) {}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult operator()(const ControllerInterface::Hook& ei) const;
|
||||
|
||||
private:
|
||||
QObject* m_parent;
|
||||
};
|
||||
|
||||
class InputAxisBindingMonitor
|
||||
{
|
||||
public:
|
||||
explicit InputAxisBindingMonitor(QObject* parent, Controller::AxisType axis_type)
|
||||
: m_parent(parent), m_axis_type(axis_type)
|
||||
{
|
||||
}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult operator()(const ControllerInterface::Hook& ei) const;
|
||||
|
||||
private:
|
||||
bool ProcessAxisInput(const ControllerInterface::Hook& ei, std::optional<bool>& half_axis_positive,
|
||||
std::optional<bool>& inverted) const;
|
||||
bool AnalyzeInputHistory(std::optional<bool>& half_axis_positive, std::optional<bool>& inverted) const;
|
||||
|
||||
struct Context
|
||||
{
|
||||
struct History
|
||||
{
|
||||
int controller_index;
|
||||
int axis_number;
|
||||
float value;
|
||||
};
|
||||
|
||||
std::vector<History> m_inputs_history;
|
||||
};
|
||||
|
||||
QObject* m_parent;
|
||||
Controller::AxisType m_axis_type;
|
||||
std::shared_ptr<Context> m_context = std::make_shared<Context>();
|
||||
};
|
||||
|
||||
class InputRumbleBindingMonitor
|
||||
{
|
||||
public:
|
||||
explicit InputRumbleBindingMonitor(QObject* parent) : m_parent(parent) {}
|
||||
|
||||
ControllerInterface::Hook::CallbackResult operator()(const ControllerInterface::Hook& ei) const;
|
||||
|
||||
private:
|
||||
QObject* m_parent;
|
||||
};
|
||||
@@ -1,29 +1,34 @@
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "frontend-common/controller_interface.h"
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "inputbindingdialog.h"
|
||||
#include "inputbindingmonitor.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QMouseEvent>
|
||||
#include <QtGui/QWheelEvent>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
InputBindingWidget::InputBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
QWidget* parent)
|
||||
: QPushButton(parent), m_host_interface(host_interface), m_section_name(std::move(section_name)),
|
||||
m_key_name(std::move(key_name))
|
||||
InputBindingWidget::InputBindingWidget(QWidget* parent) : QPushButton(parent)
|
||||
{
|
||||
m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
|
||||
updateText();
|
||||
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
|
||||
}
|
||||
|
||||
setMinimumWidth(150);
|
||||
setMaximumWidth(150);
|
||||
InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name,
|
||||
std::string key_name)
|
||||
: QPushButton(parent)
|
||||
{
|
||||
setMinimumWidth(225);
|
||||
setMaximumWidth(225);
|
||||
|
||||
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
|
||||
|
||||
initialize(sif, std::move(section_name), std::move(key_name));
|
||||
}
|
||||
|
||||
InputBindingWidget::~InputBindingWidget()
|
||||
@@ -31,54 +36,54 @@ InputBindingWidget::~InputBindingWidget()
|
||||
Q_ASSERT(!isListeningForInput());
|
||||
}
|
||||
|
||||
bool InputBindingWidget::isMouseMappingEnabled()
|
||||
{
|
||||
return Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
|
||||
}
|
||||
|
||||
void InputBindingWidget::initialize(SettingsInterface* sif, std::string section_name, std::string key_name)
|
||||
{
|
||||
m_sif = sif;
|
||||
m_section_name = std::move(section_name);
|
||||
m_key_name = std::move(key_name);
|
||||
reloadBinding();
|
||||
}
|
||||
|
||||
void InputBindingWidget::updateText()
|
||||
{
|
||||
if (m_bindings.empty())
|
||||
setText(QString());
|
||||
else if (m_bindings.size() > 1)
|
||||
setText(tr("%n bindings", "", static_cast<int>(m_bindings.size())));
|
||||
else
|
||||
setText(QString::fromStdString(m_bindings[0]));
|
||||
}
|
||||
|
||||
void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive)
|
||||
{
|
||||
const char* invert_char = inverted ? "-" : "";
|
||||
const char* sign_char = "";
|
||||
if (half_axis_positive)
|
||||
{
|
||||
sign_char = *half_axis_positive ? "+" : "-";
|
||||
setText(QString());
|
||||
}
|
||||
else if (m_bindings.size() > 1)
|
||||
{
|
||||
setText(tr("%n bindings", "", static_cast<int>(m_bindings.size())));
|
||||
|
||||
m_new_binding_value =
|
||||
StringUtil::StdStringFromFormat("Controller%d/%sAxis%d%s", controller_index, sign_char, axis_index, invert_char);
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
// keep the full thing for the tooltip
|
||||
std::stringstream ss;
|
||||
bool first = true;
|
||||
for (const std::string& binding : m_bindings)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ss << "\n";
|
||||
ss << binding;
|
||||
}
|
||||
setToolTip(QString::fromStdString(ss.str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
QString binding_text(QString::fromStdString(m_bindings[0]));
|
||||
setToolTip(binding_text);
|
||||
|
||||
void InputBindingWidget::bindToControllerButton(int controller_index, int button_index)
|
||||
{
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index);
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingWidget::bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction)
|
||||
{
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Hat%d %s", controller_index, hat_index,
|
||||
hat_direction.toLatin1().constData());
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputBindingWidget::beginRebindAll()
|
||||
{
|
||||
m_is_binding_all = true;
|
||||
if (isListeningForInput())
|
||||
stopListeningForInput();
|
||||
|
||||
startListeningForInput(TIMEOUT_FOR_ALL_BINDING);
|
||||
// fix up accelerators, and if it's too long, ellipsise it
|
||||
if (binding_text.contains('&'))
|
||||
binding_text = binding_text.replace(QStringLiteral("&"), QStringLiteral("&&"));
|
||||
if (binding_text.length() > 35)
|
||||
binding_text = binding_text.left(35).append(QStringLiteral("..."));
|
||||
setText(binding_text);
|
||||
}
|
||||
}
|
||||
|
||||
bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
@@ -86,7 +91,7 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
const QEvent::Type event_type = event->type();
|
||||
|
||||
// if the key is being released, set the input
|
||||
if (event_type == QEvent::KeyRelease)
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonRelease)
|
||||
{
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
@@ -95,25 +100,72 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
else if (event_type == QEvent::KeyPress)
|
||||
{
|
||||
const QKeyEvent* key_event = static_cast<const QKeyEvent*>(event);
|
||||
const QString binding(QtUtils::KeyEventToString(key_event->key(), key_event->modifiers()));
|
||||
if (!binding.isEmpty())
|
||||
m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString();
|
||||
m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(QtUtils::KeyEventToCode(key_event)));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
// double clicks get triggered if we click bind, then click again quickly.
|
||||
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
|
||||
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::Wheel)
|
||||
{
|
||||
const QPoint delta_angle(static_cast<QWheelEvent*>(event)->angleDelta());
|
||||
const float dx = std::clamp(static_cast<float>(delta_angle.x()) / QtUtils::MOUSE_WHEEL_DELTA, -1.0f, 1.0f);
|
||||
if (dx != 0.0f)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelX));
|
||||
key.negative = (dx < 0.0f);
|
||||
m_new_bindings.push_back(key);
|
||||
}
|
||||
|
||||
const float dy = std::clamp(static_cast<float>(delta_angle.y()) / QtUtils::MOUSE_WHEEL_DELTA, -1.0f, 1.0f);
|
||||
if (dy != 0.0f)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::WheelY));
|
||||
key.negative = (dy < 0.0f);
|
||||
m_new_bindings.push_back(key);
|
||||
}
|
||||
|
||||
if (dx != 0.0f || dy != 0.0f)
|
||||
{
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonRelease)
|
||||
else if (event_type == QEvent::MouseMove && m_mouse_mapping_enabled)
|
||||
{
|
||||
const u32 button_mask = static_cast<u32>(static_cast<const QMouseEvent*>(event)->button());
|
||||
const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask);
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1);
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
// if we've moved more than a decent distance from the center of the widget, bind it.
|
||||
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
|
||||
static constexpr const s32 THRESHOLD = 50;
|
||||
const QPointF diff(static_cast<QMouseEvent*>(event)->globalPosition() - m_input_listen_start_position);
|
||||
bool has_one = false;
|
||||
|
||||
if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
return true;
|
||||
if (std::abs(diff.x()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
|
||||
key.negative = (diff.x() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
if (std::abs(diff.y()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
|
||||
key.negative = (diff.y() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
|
||||
if (has_one)
|
||||
{
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -147,27 +199,51 @@ void InputBindingWidget::mouseReleaseEvent(QMouseEvent* e)
|
||||
|
||||
void InputBindingWidget::setNewBinding()
|
||||
{
|
||||
if (m_new_binding_value.empty())
|
||||
if (m_new_bindings.empty())
|
||||
return;
|
||||
|
||||
m_host_interface->SetStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_new_binding_value.c_str());
|
||||
m_host_interface->updateInputMap();
|
||||
const std::string new_binding(
|
||||
InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size()));
|
||||
if (!new_binding.empty())
|
||||
{
|
||||
if (m_sif)
|
||||
{
|
||||
m_sif->SetStringValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
|
||||
m_sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
|
||||
g_emu_thread->reloadInputBindings();
|
||||
}
|
||||
}
|
||||
|
||||
m_bindings.clear();
|
||||
m_bindings.push_back(std::move(m_new_binding_value));
|
||||
m_bindings.push_back(std::move(new_binding));
|
||||
}
|
||||
|
||||
void InputBindingWidget::clearBinding()
|
||||
{
|
||||
m_bindings.clear();
|
||||
m_host_interface->RemoveSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
m_host_interface->updateInputMap();
|
||||
updateText();
|
||||
if (m_sif)
|
||||
{
|
||||
m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
m_sif->Save();
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
g_emu_thread->reloadInputBindings();
|
||||
}
|
||||
reloadBinding();
|
||||
}
|
||||
|
||||
void InputBindingWidget::reloadBinding()
|
||||
{
|
||||
m_bindings = m_host_interface->GetSettingStringList(m_section_name.c_str(), m_key_name.c_str());
|
||||
m_bindings = m_sif ? m_sif->GetStringList(m_section_name.c_str(), m_key_name.c_str()) :
|
||||
Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str());
|
||||
updateText();
|
||||
}
|
||||
|
||||
@@ -199,6 +275,9 @@ void InputBindingWidget::onInputListenTimerTimeout()
|
||||
|
||||
void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
m_new_bindings.clear();
|
||||
m_mouse_mapping_enabled = isMouseMappingEnabled();
|
||||
m_input_listen_start_position = QCursor::pos();
|
||||
m_input_listen_timer = new QTimer(this);
|
||||
m_input_listen_timer->setSingleShot(false);
|
||||
m_input_listen_timer->start(1000);
|
||||
@@ -211,185 +290,154 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
installEventFilter(this);
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
setMouseTracking(true);
|
||||
hookInputManager();
|
||||
}
|
||||
|
||||
void InputBindingWidget::stopListeningForInput()
|
||||
{
|
||||
updateText();
|
||||
reloadBinding();
|
||||
delete m_input_listen_timer;
|
||||
m_input_listen_timer = nullptr;
|
||||
std::vector<InputBindingKey>().swap(m_new_bindings);
|
||||
|
||||
unhookInputManager();
|
||||
setMouseTracking(false);
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
removeEventFilter(this);
|
||||
|
||||
if (m_is_binding_all && m_next_widget)
|
||||
m_next_widget->beginRebindAll();
|
||||
m_is_binding_all = false;
|
||||
}
|
||||
|
||||
void InputBindingWidget::openDialog() {}
|
||||
|
||||
InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name,
|
||||
std::string key_name, QWidget* parent)
|
||||
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
|
||||
void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float value)
|
||||
{
|
||||
}
|
||||
const float abs_value = std::abs(value);
|
||||
|
||||
InputButtonBindingWidget::~InputButtonBindingWidget()
|
||||
{
|
||||
if (isListeningForInput())
|
||||
InputButtonBindingWidget::stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::hookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->SetHook(InputButtonBindingMonitor(this));
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::unhookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->ClearHook();
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
InputBindingWidget::startListeningForInput(timeout_in_seconds);
|
||||
hookControllerInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::stopListeningForInput()
|
||||
{
|
||||
unhookControllerInput();
|
||||
InputBindingWidget::stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::openDialog()
|
||||
{
|
||||
InputButtonBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings,
|
||||
QtUtils::GetRootWidget(this));
|
||||
binding_dialog.exec();
|
||||
reloadBinding();
|
||||
}
|
||||
|
||||
InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name,
|
||||
std::string key_name, Controller::AxisType axis_type, QWidget* parent)
|
||||
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent), m_axis_type(axis_type)
|
||||
{
|
||||
}
|
||||
|
||||
InputAxisBindingWidget::~InputAxisBindingWidget()
|
||||
{
|
||||
if (isListeningForInput())
|
||||
InputAxisBindingWidget::stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputAxisBindingWidget::hookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->SetHook(InputAxisBindingMonitor(this, m_axis_type));
|
||||
}
|
||||
|
||||
void InputAxisBindingWidget::unhookControllerInput()
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
return;
|
||||
|
||||
controller_interface->ClearHook();
|
||||
}
|
||||
|
||||
bool InputAxisBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (m_axis_type != Controller::AxisType::Half)
|
||||
for (InputBindingKey other_key : m_new_bindings)
|
||||
{
|
||||
const QEvent::Type event_type = event->type();
|
||||
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease)
|
||||
if (other_key.MaskDirection() == key.MaskDirection())
|
||||
{
|
||||
return true;
|
||||
if (abs_value < 0.5f)
|
||||
{
|
||||
// if this key is in our new binding list, it's a "release", and we're done
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, keep waiting
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return InputBindingWidget::eventFilter(watched, event);
|
||||
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
|
||||
if (abs_value >= 0.5f)
|
||||
{
|
||||
InputBindingKey key_to_add = key;
|
||||
key_to_add.negative = (value < 0.0f);
|
||||
m_new_bindings.push_back(key_to_add);
|
||||
}
|
||||
}
|
||||
|
||||
void InputAxisBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
void InputBindingWidget::hookInputManager()
|
||||
{
|
||||
InputBindingWidget::startListeningForInput(timeout_in_seconds);
|
||||
hookControllerInput();
|
||||
InputManager::SetHook([this](InputBindingKey key, float value) {
|
||||
QMetaObject::invokeMethod(this, "inputManagerHookCallback", Qt::QueuedConnection, Q_ARG(InputBindingKey, key),
|
||||
Q_ARG(float, value));
|
||||
return InputInterceptHook::CallbackResult::StopProcessingEvent;
|
||||
});
|
||||
}
|
||||
|
||||
void InputAxisBindingWidget::stopListeningForInput()
|
||||
void InputBindingWidget::unhookInputManager()
|
||||
{
|
||||
unhookControllerInput();
|
||||
InputBindingWidget::stopListeningForInput();
|
||||
InputManager::RemoveHook();
|
||||
}
|
||||
|
||||
void InputAxisBindingWidget::openDialog()
|
||||
void InputBindingWidget::openDialog()
|
||||
{
|
||||
InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings, m_axis_type,
|
||||
QtUtils::GetRootWidget(this));
|
||||
InputBindingDialog binding_dialog(m_sif, m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this));
|
||||
binding_dialog.exec();
|
||||
reloadBinding();
|
||||
}
|
||||
|
||||
InputRumbleBindingWidget::InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name,
|
||||
std::string key_name, QWidget* parent)
|
||||
: InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent)
|
||||
InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent)
|
||||
{
|
||||
connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked);
|
||||
}
|
||||
|
||||
InputRumbleBindingWidget::~InputRumbleBindingWidget()
|
||||
InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog,
|
||||
std::string section_name, std::string key_name)
|
||||
{
|
||||
if (isListeningForInput())
|
||||
InputRumbleBindingWidget::stopListeningForInput();
|
||||
setMinimumWidth(225);
|
||||
setMaximumWidth(225);
|
||||
|
||||
connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked);
|
||||
|
||||
setKey(dialog, std::move(section_name), std::move(key_name));
|
||||
}
|
||||
|
||||
void InputRumbleBindingWidget::hookControllerInput()
|
||||
InputVibrationBindingWidget::~InputVibrationBindingWidget() {}
|
||||
|
||||
void InputVibrationBindingWidget::setKey(ControllerSettingsDialog* dialog, std::string section_name,
|
||||
std::string key_name)
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
m_dialog = dialog;
|
||||
m_section_name = std::move(section_name);
|
||||
m_key_name = std::move(key_name);
|
||||
m_binding = Host::GetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
setText(QString::fromStdString(m_binding));
|
||||
}
|
||||
|
||||
void InputVibrationBindingWidget::clearBinding()
|
||||
{
|
||||
m_binding = {};
|
||||
Host::DeleteBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
|
||||
g_emu_thread->reloadInputBindings();
|
||||
setText(QString());
|
||||
}
|
||||
|
||||
void InputVibrationBindingWidget::onClicked()
|
||||
{
|
||||
QInputDialog dialog(QtUtils::GetRootWidget(this));
|
||||
|
||||
const QString full_key(
|
||||
QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
|
||||
const QString current(QString::fromStdString(m_binding));
|
||||
QStringList input_options(m_dialog->getVibrationMotors());
|
||||
if (!current.isEmpty() && input_options.indexOf(current) < 0)
|
||||
{
|
||||
input_options.append(current);
|
||||
}
|
||||
else if (input_options.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
|
||||
tr("No devices with vibration motors were detected."));
|
||||
return;
|
||||
}
|
||||
|
||||
QInputDialog input_dialog(this);
|
||||
input_dialog.setWindowTitle(full_key);
|
||||
input_dialog.setLabelText(tr("Select vibration motor for %1.").arg(full_key));
|
||||
input_dialog.setInputMode(QInputDialog::TextInput);
|
||||
input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems);
|
||||
input_dialog.setComboBoxEditable(false);
|
||||
input_dialog.setComboBoxItems(std::move(input_options));
|
||||
input_dialog.setTextValue(current);
|
||||
if (input_dialog.exec() == 0)
|
||||
return;
|
||||
|
||||
controller_interface->SetHook(InputRumbleBindingMonitor(this));
|
||||
const QString new_value(input_dialog.textValue());
|
||||
m_binding = new_value.toStdString();
|
||||
Host::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_binding.c_str());
|
||||
setText(new_value);
|
||||
}
|
||||
|
||||
void InputRumbleBindingWidget::unhookControllerInput()
|
||||
void InputVibrationBindingWidget::mouseReleaseEvent(QMouseEvent* e)
|
||||
{
|
||||
ControllerInterface* controller_interface = m_host_interface->getControllerInterface();
|
||||
if (!controller_interface)
|
||||
if (e->button() == Qt::RightButton)
|
||||
{
|
||||
clearBinding();
|
||||
return;
|
||||
}
|
||||
|
||||
controller_interface->ClearHook();
|
||||
}
|
||||
|
||||
void InputRumbleBindingWidget::bindToControllerRumble(int controller_index)
|
||||
{
|
||||
m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d", controller_index);
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
}
|
||||
|
||||
void InputRumbleBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
InputBindingWidget::startListeningForInput(timeout_in_seconds);
|
||||
hookControllerInput();
|
||||
}
|
||||
|
||||
void InputRumbleBindingWidget::stopListeningForInput()
|
||||
{
|
||||
unhookControllerInput();
|
||||
InputBindingWidget::stopListeningForInput();
|
||||
QPushButton::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
#pragma once
|
||||
#include "core/controller.h"
|
||||
#include "core/types.h"
|
||||
#include "common/types.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <optional>
|
||||
|
||||
class QTimer;
|
||||
|
||||
class QtHostInterface;
|
||||
class ControllerSettingsDialog;
|
||||
class SettingsInterface;
|
||||
|
||||
class InputBindingWidget : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, QWidget* parent);
|
||||
InputBindingWidget(QWidget* parent);
|
||||
InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, std::string key_name);
|
||||
~InputBindingWidget();
|
||||
|
||||
ALWAYS_INLINE InputBindingWidget* getNextWidget() const { return m_next_widget; }
|
||||
ALWAYS_INLINE void setNextWidget(InputBindingWidget* widget) { m_next_widget = widget; }
|
||||
static bool isMouseMappingEnabled();
|
||||
|
||||
void initialize(SettingsInterface* sif, std::string section_name, std::string key_name);
|
||||
|
||||
public Q_SLOTS:
|
||||
void bindToControllerAxis(int controller_index, int axis_index, bool inverted,
|
||||
std::optional<bool> half_axis_positive);
|
||||
void bindToControllerButton(int controller_index, int button_index);
|
||||
void bindToControllerHat(int controller_index, int hat_index, const QString& hat_direction);
|
||||
void beginRebindAll();
|
||||
void clearBinding();
|
||||
void reloadBinding();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onClicked();
|
||||
void onInputListenTimerTimeout();
|
||||
void inputManagerHookCallback(InputBindingKey key, float value);
|
||||
|
||||
protected:
|
||||
enum : u32
|
||||
@@ -51,71 +50,45 @@ protected:
|
||||
void setNewBinding();
|
||||
void updateText();
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
void hookInputManager();
|
||||
void unhookInputManager();
|
||||
|
||||
SettingsInterface* m_sif = nullptr;
|
||||
std::string m_section_name;
|
||||
std::string m_key_name;
|
||||
std::vector<std::string> m_bindings;
|
||||
std::string m_new_binding_value;
|
||||
std::vector<InputBindingKey> m_new_bindings;
|
||||
QTimer* m_input_listen_timer = nullptr;
|
||||
u32 m_input_listen_remaining_seconds = 0;
|
||||
|
||||
InputBindingWidget* m_next_widget = nullptr;
|
||||
bool m_is_binding_all = false;
|
||||
QPointF m_input_listen_start_position{};
|
||||
bool m_mouse_mapping_enabled = false;
|
||||
};
|
||||
|
||||
class InputButtonBindingWidget : public InputBindingWidget
|
||||
class InputVibrationBindingWidget : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputButtonBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
QWidget* parent);
|
||||
~InputButtonBindingWidget();
|
||||
InputVibrationBindingWidget(QWidget* parent);
|
||||
InputVibrationBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, std::string section_name,
|
||||
std::string key_name);
|
||||
~InputVibrationBindingWidget();
|
||||
|
||||
void setKey(ControllerSettingsDialog* dialog, std::string section_name, std::string key_name);
|
||||
|
||||
public Q_SLOTS:
|
||||
void clearBinding();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onClicked();
|
||||
|
||||
protected:
|
||||
void startListeningForInput(u32 timeout_in_seconds) override;
|
||||
void stopListeningForInput() override;
|
||||
void openDialog() override;
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
};
|
||||
|
||||
class InputAxisBindingWidget : public InputBindingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
Controller::AxisType axis_type, QWidget* parent);
|
||||
~InputAxisBindingWidget();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||
void startListeningForInput(u32 timeout_in_seconds) override;
|
||||
void stopListeningForInput() override;
|
||||
void openDialog() override;
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
virtual void mouseReleaseEvent(QMouseEvent* e) override;
|
||||
|
||||
private:
|
||||
Controller::AxisType m_axis_type;
|
||||
std::string m_section_name;
|
||||
std::string m_key_name;
|
||||
std::string m_binding;
|
||||
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
class InputRumbleBindingWidget : public InputBindingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InputRumbleBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name,
|
||||
QWidget* parent);
|
||||
~InputRumbleBindingWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
void bindToControllerRumble(int controller_index);
|
||||
|
||||
protected:
|
||||
void startListeningForInput(u32 timeout_in_seconds) override;
|
||||
void stopListeningForInput() override;
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
};
|
||||
@@ -1,159 +0,0 @@
|
||||
#include "common/crash_handler.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
static bool ParseCommandLineParameters(QApplication& app, QtHostInterface* host_interface,
|
||||
std::unique_ptr<SystemBootParameters>* boot_params)
|
||||
{
|
||||
const QStringList args(app.arguments());
|
||||
std::vector<std::string> converted_args;
|
||||
std::vector<char*> converted_argv;
|
||||
converted_args.reserve(args.size());
|
||||
converted_argv.reserve(args.size());
|
||||
|
||||
for (const QString& arg : args)
|
||||
converted_args.push_back(arg.toStdString());
|
||||
|
||||
for (std::string& arg : converted_args)
|
||||
converted_argv.push_back(arg.data());
|
||||
|
||||
return host_interface->ParseCommandLineParameters(args.size(), converted_argv.data(), boot_params);
|
||||
}
|
||||
|
||||
static void SignalHandler(int signal)
|
||||
{
|
||||
// First try the normal (graceful) shutdown/exit.
|
||||
static bool graceful_shutdown_attempted = false;
|
||||
if (!graceful_shutdown_attempted)
|
||||
{
|
||||
std::fprintf(stderr, "Received CTRL+C, attempting graceful shutdown. Press CTRL+C again to force.\n");
|
||||
graceful_shutdown_attempted = true;
|
||||
QtHostInterface::GetInstance()->requestExit();
|
||||
return;
|
||||
}
|
||||
|
||||
std::signal(signal, SIG_DFL);
|
||||
|
||||
// MacOS is missing std::quick_exit() despite it being C++11...
|
||||
#ifndef __APPLE__
|
||||
std::quick_exit(1);
|
||||
#else
|
||||
_Exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void HookSignals()
|
||||
{
|
||||
std::signal(SIGINT, SignalHandler);
|
||||
std::signal(SIGTERM, SignalHandler);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
CrashHandler::Install();
|
||||
|
||||
// Register any standard types we need elsewhere
|
||||
qRegisterMetaType<std::optional<bool>>();
|
||||
qRegisterMetaType<std::function<void()>>();
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Use Segoe UI on Windows rather than MS Shell Dlg 2, courtesy of Dolphin.
|
||||
// Can be removed once switched to Qt 6.
|
||||
QApplication::setFont(QApplication::font("QMenu"));
|
||||
#endif
|
||||
|
||||
std::unique_ptr<QtHostInterface> host_interface = std::make_unique<QtHostInterface>();
|
||||
std::unique_ptr<SystemBootParameters> boot_params;
|
||||
if (!ParseCommandLineParameters(app, host_interface.get(), &boot_params))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
MainWindow* window = new MainWindow(host_interface.get());
|
||||
|
||||
if (!host_interface->Initialize())
|
||||
{
|
||||
host_interface->Shutdown();
|
||||
QMessageBox::critical(nullptr, QObject::tr("DuckStation Error"),
|
||||
QObject::tr("Failed to initialize host interface. Cannot continue."), QMessageBox::Ok);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
window->initializeAndShow();
|
||||
HookSignals();
|
||||
|
||||
// if we're in batch mode, don't bother refreshing the game list as it won't be used
|
||||
if (!host_interface->inBatchMode())
|
||||
host_interface->refreshGameList();
|
||||
|
||||
if (boot_params)
|
||||
{
|
||||
host_interface->bootSystem(std::move(boot_params));
|
||||
}
|
||||
else
|
||||
{
|
||||
window->startupUpdateCheck();
|
||||
}
|
||||
|
||||
int result = app.exec();
|
||||
|
||||
host_interface->Shutdown();
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Apparently Qt6 got rid of this?
|
||||
#include "common/windows_headers.h"
|
||||
#include <shellapi.h>
|
||||
|
||||
/*
|
||||
WinMain() - Initializes Windows and calls user's startup function main().
|
||||
NOTE: WinMain() won't be called if the application was linked as a "console"
|
||||
application.
|
||||
*/
|
||||
|
||||
// Convert a wchar_t to char string, equivalent to QString::toLocal8Bit()
|
||||
// when passed CP_ACP.
|
||||
static inline char* wideToMulti(unsigned int codePage, const wchar_t* aw)
|
||||
{
|
||||
const int required = WideCharToMultiByte(codePage, 0, aw, -1, nullptr, 0, nullptr, nullptr);
|
||||
char* result = new char[required];
|
||||
WideCharToMultiByte(codePage, 0, aw, -1, result, required, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR /*cmdParamarg*/, int /* cmdShow */)
|
||||
{
|
||||
int argc = 0;
|
||||
wchar_t** argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argvW == nullptr)
|
||||
return -1;
|
||||
char** argv = new char* [argc + 1];
|
||||
for (int i = 0; i != argc; ++i)
|
||||
argv[i] = wideToMulti(CP_ACP, argvW[i]);
|
||||
argv[argc] = nullptr;
|
||||
LocalFree(argvW);
|
||||
const int exitCode = main(argc, argv);
|
||||
for (int i = 0; (i != argc) && (argv[i] != nullptr); ++i)
|
||||
delete[] argv[i];
|
||||
delete[] argv;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,23 +5,28 @@
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <memory>
|
||||
|
||||
#include "controllersettingsdialog.h"
|
||||
#include "core/types.h"
|
||||
#include "qtdisplaywidget.h"
|
||||
#include "displaywidget.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
class QLabel;
|
||||
class QThread;
|
||||
class QProgressBar;
|
||||
|
||||
class GameListWidget;
|
||||
class QtHostInterface;
|
||||
class EmuThread;
|
||||
class AutoUpdaterDialog;
|
||||
class MemoryCardEditorDialog;
|
||||
class CheatManagerDialog;
|
||||
class DebuggerWindow;
|
||||
class MainWindow;
|
||||
|
||||
class HostDisplay;
|
||||
struct GameListEntry;
|
||||
namespace GameList {
|
||||
struct Entry;
|
||||
}
|
||||
|
||||
class GDBServer;
|
||||
|
||||
@@ -30,11 +35,38 @@ class MainWindow final : public QMainWindow
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QtHostInterface* host_interface);
|
||||
/// This class is a scoped lock on the system, which prevents it from running while
|
||||
/// the object exists. Its purpose is to be used for blocking/modal popup boxes,
|
||||
/// where the VM needs to exit fullscreen temporarily.
|
||||
class SystemLock
|
||||
{
|
||||
public:
|
||||
SystemLock(SystemLock&& lock);
|
||||
SystemLock(const SystemLock&) = delete;
|
||||
~SystemLock();
|
||||
|
||||
/// Returns the parent widget, which can be used for any popup dialogs.
|
||||
ALWAYS_INLINE QWidget* getDialogParent() const { return m_dialog_parent; }
|
||||
|
||||
/// Cancels any pending unpause/fullscreen transition.
|
||||
/// Call when you're going to destroy the system anyway.
|
||||
void cancelResume();
|
||||
|
||||
private:
|
||||
SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen);
|
||||
friend MainWindow;
|
||||
|
||||
QWidget* m_dialog_parent;
|
||||
bool m_was_paused;
|
||||
bool m_was_fullscreen;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit MainWindow();
|
||||
~MainWindow();
|
||||
|
||||
/// Initializes the window. Call once at startup.
|
||||
void initializeAndShow();
|
||||
void initialize();
|
||||
|
||||
/// Performs update check if enabled in settings.
|
||||
void startupUpdateCheck();
|
||||
@@ -42,36 +74,49 @@ public:
|
||||
/// Opens memory card editor with the specified paths.
|
||||
void openMemoryCardEditor(const QString& card_a_path, const QString& card_b_path);
|
||||
|
||||
/// Updates the state of the controls which should be disabled by achievements challenge mode.
|
||||
void onAchievementsChallengeModeToggled(bool enabled);
|
||||
/// Locks the system by pausing it, while a popup dialog is displayed.
|
||||
SystemLock pauseAndLockSystem();
|
||||
|
||||
/// Accessors for the status bar widgets, updated by the emulation thread.
|
||||
ALWAYS_INLINE QLabel* getStatusRendererWidget() const { return m_status_renderer_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
|
||||
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
|
||||
|
||||
public Q_SLOTS:
|
||||
/// Updates debug menu visibility (hides if disabled).
|
||||
void updateDebugMenuVisibility();
|
||||
|
||||
void refreshGameList(bool invalidate_cache);
|
||||
void cancelGameListRefresh();
|
||||
|
||||
void runOnUIThread(const std::function<void()>& func);
|
||||
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool block_until_done = false);
|
||||
void requestExit(bool allow_save_to_state = true);
|
||||
void checkForSettingChanges();
|
||||
|
||||
void checkForUpdates(bool display_message);
|
||||
|
||||
void* getNativeWindowId();
|
||||
|
||||
private Q_SLOTS:
|
||||
void reportError(const QString& message);
|
||||
void reportMessage(const QString& message);
|
||||
bool confirmMessage(const QString& message);
|
||||
QtDisplayWidget* createDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main);
|
||||
QtDisplayWidget* updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main);
|
||||
void reportError(const QString& title, const QString& message);
|
||||
bool confirmMessage(const QString& title, const QString& message);
|
||||
bool createDisplay(bool fullscreen, bool render_to_main);
|
||||
bool updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
void displaySizeRequested(qint32 width, qint32 height);
|
||||
void destroyDisplay();
|
||||
void focusDisplayWidget();
|
||||
void onMouseModeRequested(bool relative_mode, bool hide_cursor);
|
||||
void updateMouseMode(bool paused);
|
||||
|
||||
void onSettingsResetToDefault();
|
||||
void onEmulationStarting();
|
||||
void onEmulationStarted();
|
||||
void onEmulationStopped();
|
||||
void onEmulationPaused(bool paused);
|
||||
void onSystemPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
|
||||
float worst_frame_time, GPURenderer renderer, quint32 render_width,
|
||||
quint32 render_height, bool render_interlaced);
|
||||
void onSystemStarting();
|
||||
void onSystemStarted();
|
||||
void onSystemDestroyed();
|
||||
void onSystemPaused();
|
||||
void onSystemResumed();
|
||||
void onRunningGameChanged(const QString& filename, const QString& game_code, const QString& game_title);
|
||||
void onAchievementsChallengeModeChanged();
|
||||
void onApplicationStateChanged(Qt::ApplicationState state);
|
||||
|
||||
void onStartFileActionTriggered();
|
||||
@@ -102,10 +147,11 @@ private Q_SLOTS:
|
||||
void onToolsCheatManagerTriggered();
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
|
||||
void onGameListEntrySelected(const GameListEntry* entry);
|
||||
void onGameListEntryDoubleClicked(const GameListEntry* entry);
|
||||
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
||||
void onGameListSetCoverImageRequested(const GameListEntry* entry);
|
||||
void onGameListRefreshComplete();
|
||||
void onGameListRefreshProgress(const QString& status, int current, int total);
|
||||
void onGameListSelectionChanged();
|
||||
void onGameListEntryActivated();
|
||||
void onGameListEntryContextMenuRequested(const QPoint& point);
|
||||
|
||||
void onUpdateCheckComplete();
|
||||
|
||||
@@ -113,76 +159,116 @@ private Q_SLOTS:
|
||||
void onCPUDebuggerClosed();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void changeEvent(QEvent* event) override;
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE QWidget* getDisplayContainer() const
|
||||
{
|
||||
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
|
||||
}
|
||||
|
||||
void setTheme(const QString& theme);
|
||||
void setStyleFromSettings();
|
||||
void setIconThemeFromSettings();
|
||||
void setupAdditionalUi();
|
||||
void connectSignals();
|
||||
void addThemeToMenu(const QString& name, const QString& key);
|
||||
|
||||
void updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode);
|
||||
void updateStatusBarWidgetVisibility();
|
||||
void updateWindowTitle();
|
||||
void updateWindowState(bool force_visible = false);
|
||||
|
||||
void setProgressBar(int current, int total);
|
||||
void clearProgressBar();
|
||||
|
||||
QWidget* getContentParent();
|
||||
QWidget* getDisplayContainer() const;
|
||||
bool isShowingGameList() const;
|
||||
bool isRenderingFullscreen() const;
|
||||
bool isRenderingToMain() const;
|
||||
bool shouldHideMouseCursor() const;
|
||||
bool shouldHideMainWindow() const;
|
||||
|
||||
void switchToGameListView();
|
||||
void switchToEmulationView();
|
||||
void startGameOrChangeDiscs(const std::string& path);
|
||||
void saveStateToConfig();
|
||||
void restoreStateFromConfig();
|
||||
void saveDisplayWindowGeometryToConfig();
|
||||
void restoreDisplayWindowGeometryFromConfig();
|
||||
void destroyDisplayWidget();
|
||||
void createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen);
|
||||
void destroyDisplayWidget(bool show_game_list);
|
||||
void setDisplayFullscreen(const std::string& fullscreen_mode);
|
||||
bool shouldHideCursorInFullscreen() const;
|
||||
|
||||
SettingsDialog* getSettingsDialog();
|
||||
void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
|
||||
void doSettings(const char* category = nullptr);
|
||||
|
||||
ControllerSettingsDialog* getControllerSettingsDialog();
|
||||
void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count);
|
||||
|
||||
void updateDebugMenuCPUExecutionMode();
|
||||
void updateDebugMenuGPURenderer();
|
||||
void updateDebugMenuCropMode();
|
||||
void updateMenuSelectedTheme();
|
||||
void ensureGameListLoaded();
|
||||
std::string getDeviceDiscPath(const QString& title);
|
||||
void setGameListEntryCoverImage(const GameList::Entry* entry);
|
||||
void recreate();
|
||||
|
||||
/// Fills menu with save state info and handlers.
|
||||
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
||||
|
||||
void populateLoadStateMenu(const char* game_code, QMenu* menu);
|
||||
void populateSaveStateMenu(const char* game_code, QMenu* menu);
|
||||
|
||||
/// Fills menu with the current playlist entries. The disc index is marked as checked.
|
||||
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
||||
|
||||
/// Fills menu with the current cheat options.
|
||||
void populateCheatsMenu(QMenu* menu);
|
||||
|
||||
std::optional<bool> promptForResumeState(const std::string& save_state_path);
|
||||
void startFile(std::string path, std::optional<std::string> save_path, std::optional<bool> fast_boot);
|
||||
void startFileOrChangeDisc(const QString& path);
|
||||
void promptForDiscChange(const QString& path);
|
||||
|
||||
Ui::MainWindow m_ui;
|
||||
|
||||
QString m_unthemed_style_name;
|
||||
|
||||
QtHostInterface* m_host_interface = nullptr;
|
||||
|
||||
GameListWidget* m_game_list_widget = nullptr;
|
||||
|
||||
HostDisplay* m_host_display = nullptr;
|
||||
QtDisplayWidget* m_display_widget = nullptr;
|
||||
QtDisplayContainer* m_display_container = nullptr;
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
DisplayContainer* m_display_container = nullptr;
|
||||
|
||||
QLabel* m_status_speed_widget = nullptr;
|
||||
QLabel* m_status_fps_widget = nullptr;
|
||||
QLabel* m_status_frame_time_widget = nullptr;
|
||||
QProgressBar* m_status_progress_widget = nullptr;
|
||||
QLabel* m_status_renderer_widget = nullptr;
|
||||
QLabel* m_status_fps_widget = nullptr;
|
||||
QLabel* m_status_vps_widget = nullptr;
|
||||
QLabel* m_status_resolution_widget = nullptr;
|
||||
|
||||
SettingsDialog* m_settings_dialog = nullptr;
|
||||
ControllerSettingsDialog* m_controller_settings_dialog = nullptr;
|
||||
|
||||
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||
DebuggerWindow* m_debugger_window = nullptr;
|
||||
|
||||
std::string m_running_game_code;
|
||||
std::string m_current_game_title;
|
||||
std::string m_current_game_code;
|
||||
|
||||
bool m_emulation_running = false;
|
||||
bool m_was_paused_by_focus_loss = false;
|
||||
bool m_open_debugger_on_start = false;
|
||||
bool m_relative_mouse_mode = false;
|
||||
bool m_mouse_cursor_hidden = false;
|
||||
|
||||
bool m_display_created = false;
|
||||
bool m_save_states_invalidated = false;
|
||||
bool m_was_paused_on_surface_loss = false;
|
||||
bool m_was_disc_change_request = false;
|
||||
bool m_is_closing = false;
|
||||
|
||||
GDBServer* m_gdb_server = nullptr;
|
||||
};
|
||||
|
||||
extern MainWindow* g_main_window;
|
||||
|
||||
@@ -20,19 +20,13 @@
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<widget class="QStackedWidget" name="mainContainer">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page"/>
|
||||
<widget class="QWidget" name="page_2"/>
|
||||
</widget>
|
||||
<widget class="QStackedWidget" name="mainContainer" />
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>754</width>
|
||||
<width>800</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -45,7 +39,8 @@
|
||||
<string>Change Disc</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="ChangeDisc"/>
|
||||
<iconset theme="dvd-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<actiongroup name="actionGroupChangeDiscSubImages"/>
|
||||
<addaction name="actionChangeDiscFromFile"/>
|
||||
@@ -59,7 +54,8 @@
|
||||
<string>Cheats</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="Cheats"/>
|
||||
<iconset theme="flask-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuLoadState">
|
||||
@@ -67,7 +63,8 @@
|
||||
<string>Load State</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="LoadState"/>
|
||||
<iconset theme="folder-open-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSaveState">
|
||||
@@ -75,12 +72,14 @@
|
||||
<string>Save State</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="SaveState"/>
|
||||
<iconset theme="save-3-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="actionStartFile"/>
|
||||
<addaction name="actionStartDisc"/>
|
||||
<addaction name="actionStartBios"/>
|
||||
<addaction name="actionStartFullscreenUI"/>
|
||||
<addaction name="actionResumeLastState"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionPowerOff"/>
|
||||
@@ -105,28 +104,36 @@
|
||||
<property name="title">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="paint-brush-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSettingsLanguage">
|
||||
<property name="title">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="global-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionGeneralSettings"/>
|
||||
<addaction name="actionGameListSettings"/>
|
||||
<addaction name="actionBIOSSettings"/>
|
||||
<addaction name="actionConsoleSettings"/>
|
||||
<addaction name="actionEmulationSettings"/>
|
||||
<addaction name="actionGameListSettings"/>
|
||||
<addaction name="actionHotkeySettings"/>
|
||||
<addaction name="actionControllerSettings"/>
|
||||
<addaction name="actionMemoryCardSettings"/>
|
||||
<addaction name="actionDisplaySettings"/>
|
||||
<addaction name="actionEnhancementSettings"/>
|
||||
<addaction name="actionPostProcessingSettings"/>
|
||||
<addaction name="actionAudioSettings"/>
|
||||
<addaction name="actionAchievementSettings"/>
|
||||
<addaction name="actionFolderSettings"/>
|
||||
<addaction name="actionAdvancedSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionControllerSettings"/>
|
||||
<addaction name="actionHotkeySettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAddGameDirectory"/>
|
||||
<addaction name="actionScanForNewGames"/>
|
||||
<addaction name="actionRescanAllGames"/>
|
||||
@@ -254,6 +261,7 @@
|
||||
</attribute>
|
||||
<addaction name="actionStartFile"/>
|
||||
<addaction name="actionStartBios"/>
|
||||
<addaction name="actionStartFullscreenUI2"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionResumeLastState"/>
|
||||
<addaction name="actionReset"/>
|
||||
@@ -267,11 +275,13 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionFullscreen"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionControllerSettings"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="actionStartFile">
|
||||
<property name="icon">
|
||||
<iconset theme="StartfileSettings"/>
|
||||
<iconset theme="file-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start &File...</string>
|
||||
@@ -279,7 +289,8 @@
|
||||
</action>
|
||||
<action name="actionStartDisc">
|
||||
<property name="icon">
|
||||
<iconset theme="StartdiscSettings"/>
|
||||
<iconset theme="disc-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start &Disc...</string>
|
||||
@@ -287,7 +298,8 @@
|
||||
</action>
|
||||
<action name="actionStartBios">
|
||||
<property name="icon">
|
||||
<iconset theme="BIOSSettings"/>
|
||||
<iconset theme="hard-drive-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start &BIOS</string>
|
||||
@@ -295,7 +307,8 @@
|
||||
</action>
|
||||
<action name="actionScanForNewGames">
|
||||
<property name="icon">
|
||||
<iconset theme="ScanForGames"/>
|
||||
<iconset theme="file-search-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Scan For New Games</string>
|
||||
@@ -303,7 +316,8 @@
|
||||
</action>
|
||||
<action name="actionRescanAllGames">
|
||||
<property name="icon">
|
||||
<iconset theme="RescanAllGames"/>
|
||||
<iconset theme="refresh-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Rescan All Games</string>
|
||||
@@ -311,7 +325,8 @@
|
||||
</action>
|
||||
<action name="actionPowerOff">
|
||||
<property name="icon">
|
||||
<iconset theme="PowerOff"/>
|
||||
<iconset theme="shut-down-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Power &Off</string>
|
||||
@@ -319,7 +334,8 @@
|
||||
</action>
|
||||
<action name="actionReset">
|
||||
<property name="icon">
|
||||
<iconset theme="Reset"/>
|
||||
<iconset theme="restart-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Reset</string>
|
||||
@@ -330,7 +346,8 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="Pause"/>
|
||||
<iconset theme="pause-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Pause</string>
|
||||
@@ -338,7 +355,8 @@
|
||||
</action>
|
||||
<action name="actionLoadState">
|
||||
<property name="icon">
|
||||
<iconset theme="LoadState"/>
|
||||
<iconset theme="folder-open-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Load State</string>
|
||||
@@ -346,7 +364,8 @@
|
||||
</action>
|
||||
<action name="actionSaveState">
|
||||
<property name="icon">
|
||||
<iconset theme="SaveState"/>
|
||||
<iconset theme="save-3-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Save State</string>
|
||||
@@ -354,7 +373,8 @@
|
||||
</action>
|
||||
<action name="actionExit">
|
||||
<property name="icon">
|
||||
<iconset theme="Exit"/>
|
||||
<iconset theme="door-open-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>E&xit</string>
|
||||
@@ -362,71 +382,80 @@
|
||||
</action>
|
||||
<action name="actionBIOSSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="BIOSSettings"/>
|
||||
<iconset theme="hard-drive-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>B&IOS Settings...</string>
|
||||
<string>B&IOS</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionConsoleSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="ConsoleSettings"/>
|
||||
<iconset theme="artboard-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>C&onsole Settings...</string>
|
||||
<string>C&onsole</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEmulationSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="EmulationSettings"/>
|
||||
<iconset theme="dashboard-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>E&mulation Settings...</string>
|
||||
<string>E&mulation</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionControllerSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="ControllerSettings"/>
|
||||
<iconset theme="gamepad-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Controller Settings...</string>
|
||||
<string>&Controllers</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionHotkeySettings">
|
||||
<property name="icon">
|
||||
<iconset theme="HotkeySettings"/>
|
||||
<iconset theme="keyboard-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Hotkey Settings...</string>
|
||||
<string>&Hotkeys</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDisplaySettings">
|
||||
<property name="icon">
|
||||
<iconset theme="DisplaySettings"/>
|
||||
<iconset theme="brush-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Display Settings...</string>
|
||||
<string>&Display</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEnhancementSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="EnhancementSettings"/>
|
||||
<iconset theme="paint-fill">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Enhancement Settings...</string>
|
||||
<string>&Enhancements</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPostProcessingSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="PostprocessingSettings"/>
|
||||
<iconset theme="pantone-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Post-Processing Settings...</string>
|
||||
<string>&Post-Processing</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFullscreen">
|
||||
<property name="icon">
|
||||
<iconset theme="Fullscreen"/>
|
||||
<iconset theme="fullscreen-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fullscreen</string>
|
||||
@@ -466,8 +495,8 @@
|
||||
</action>
|
||||
<action name="actionCheckForUpdates">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/update.png</normaloff>:/icons/update.png</iconset>
|
||||
<iconset theme="download-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Check for &Updates...</string>
|
||||
@@ -493,7 +522,8 @@
|
||||
</action>
|
||||
<action name="actionChangeDisc">
|
||||
<property name="icon">
|
||||
<iconset theme="ChangeDisc"/>
|
||||
<iconset theme="dvd-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Change Disc...</string>
|
||||
@@ -501,7 +531,8 @@
|
||||
</action>
|
||||
<action name="actionCheats">
|
||||
<property name="icon">
|
||||
<iconset theme="Cheats"/>
|
||||
<iconset theme="flask-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cheats...</string>
|
||||
@@ -509,47 +540,62 @@
|
||||
</action>
|
||||
<action name="actionAudioSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="AudioSettings"/>
|
||||
<iconset theme="volume-up-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Audio Settings...</string>
|
||||
<string>Audio</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAchievementSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="AchievementsSettings"/>
|
||||
<iconset theme="trophy-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Achievement Settings...</string>
|
||||
<string>Achievements</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFolderSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="folder-open-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Folders</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGameListSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="GamelistSettings"/>
|
||||
<iconset theme="folder-settings-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game List Settings...</string>
|
||||
<string>Game List</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGeneralSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="GeneralSettings"/>
|
||||
<iconset theme="settings-3-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>General Settings...</string>
|
||||
<string>General</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdvancedSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="AdvancedSettings"/>
|
||||
<iconset theme="tools-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Advanced Settings...</string>
|
||||
<string>Advanced</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddGameDirectory">
|
||||
<property name="icon">
|
||||
<iconset theme="AddGameDirectory"/>
|
||||
<iconset theme="folder-add-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Game Directory...</string>
|
||||
@@ -557,7 +603,8 @@
|
||||
</action>
|
||||
<action name="actionSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="GeneralSettings"/>
|
||||
<iconset theme="settings-3-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Settings...</string>
|
||||
@@ -714,7 +761,8 @@
|
||||
</action>
|
||||
<action name="actionScreenshot">
|
||||
<property name="icon">
|
||||
<iconset theme="Screenshot"/>
|
||||
<iconset theme="screenshot-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Screenshot</string>
|
||||
@@ -722,15 +770,17 @@
|
||||
</action>
|
||||
<action name="actionMemoryCardSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="MemorycardSettings"/>
|
||||
<iconset theme="sd-card-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Memory Card Settings...</string>
|
||||
<string>&Memory Cards</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionResumeLastState">
|
||||
<property name="icon">
|
||||
<iconset theme="Resume"/>
|
||||
<iconset theme="play-circle-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Resume</string>
|
||||
@@ -774,7 +824,8 @@
|
||||
</action>
|
||||
<action name="actionViewGameList">
|
||||
<property name="icon">
|
||||
<iconset theme="GameList"/>
|
||||
<iconset theme="list-check">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game &List</string>
|
||||
@@ -813,7 +864,8 @@
|
||||
</action>
|
||||
<action name="actionViewGameGrid">
|
||||
<property name="icon">
|
||||
<iconset theme="GameGrid"/>
|
||||
<iconset theme="function-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Game &Grid</string>
|
||||
@@ -863,12 +915,31 @@
|
||||
</action>
|
||||
<action name="actionPowerOffWithoutSaving">
|
||||
<property name="icon">
|
||||
<iconset theme="PoweroffWsaving"/>
|
||||
<iconset theme="close-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Power Off &Without Saving</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartFullscreenUI">
|
||||
<property name="icon">
|
||||
<iconset theme="tv-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start Big Picture Mode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartFullscreenUI2">
|
||||
<property name="icon">
|
||||
<iconset theme="tv-2-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Big Picture</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "memorycardeditordialog.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/path.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/host_interface.h"
|
||||
#include "core/host.h"
|
||||
#include "core/settings.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
@@ -150,13 +152,12 @@ void MemoryCardEditorDialog::populateComboBox(QComboBox* cb)
|
||||
|
||||
cb->addItem(QString());
|
||||
|
||||
const std::string base_path(g_host_interface->GetUserDirectoryRelativePath("memcards"));
|
||||
FileSystem::FindResultsArray results;
|
||||
FileSystem::FindFiles(base_path.c_str(), "*.mcd", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
|
||||
FileSystem::FindFiles(EmuFolders::MemoryCards.c_str(), "*.mcd",
|
||||
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
|
||||
for (FILESYSTEM_FIND_DATA& fd : results)
|
||||
{
|
||||
std::string real_filename(
|
||||
StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s", base_path.c_str(), fd.FileName.c_str()));
|
||||
std::string real_filename(Path::Combine(EmuFolders::MemoryCards, fd.FileName));
|
||||
std::string::size_type pos = fd.FileName.rfind('.');
|
||||
if (pos != std::string::npos)
|
||||
fd.FileName.erase(pos);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "core/settings.h"
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
@@ -15,9 +15,8 @@
|
||||
static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
|
||||
QT_TRANSLATE_NOOP("MemoryCardSettingsWidget", "All Memory Card Types (*.mcd *.mcr *.mc)");
|
||||
|
||||
MemoryCardSettingsWidget::MemoryCardSettingsWidget(QtHostInterface* host_interface, QWidget* parent,
|
||||
SettingsDialog* dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
createUi(dialog);
|
||||
}
|
||||
@@ -38,6 +37,9 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog)
|
||||
{
|
||||
QGroupBox* box = new QGroupBox(tr("Shared Settings"), this);
|
||||
QVBoxLayout* box_layout = new QVBoxLayout(box);
|
||||
QPushButton* browse = new QPushButton(tr("Browse..."), box);
|
||||
QPushButton* reset = new QPushButton(tr("Reset"), box);
|
||||
QPushButton* open_memcards = new QPushButton(tr("Open Directory..."), box);
|
||||
|
||||
{
|
||||
QLabel* label = new QLabel(tr("Memory Card Directory:"), box);
|
||||
@@ -45,29 +47,17 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog)
|
||||
|
||||
QHBoxLayout* hbox = new QHBoxLayout();
|
||||
m_memory_card_directory = new QLineEdit(box);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_memory_card_directory, "MemoryCards",
|
||||
"Directory");
|
||||
if (m_memory_card_directory->text().isEmpty())
|
||||
{
|
||||
QSignalBlocker sb(m_memory_card_directory);
|
||||
m_memory_card_directory->setText(QString::fromStdString(m_host_interface->GetMemoryCardDirectory()));
|
||||
}
|
||||
|
||||
hbox->addWidget(m_memory_card_directory);
|
||||
|
||||
QPushButton* browse = new QPushButton(tr("Browse..."), box);
|
||||
connect(browse, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onBrowseMemCardsDirectoryClicked);
|
||||
hbox->addWidget(browse);
|
||||
|
||||
QPushButton* reset = new QPushButton(tr("Reset"), box);
|
||||
connect(reset, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onResetMemCardsDirectoryClicked);
|
||||
hbox->addWidget(reset);
|
||||
|
||||
box_layout->addLayout(hbox);
|
||||
}
|
||||
|
||||
QCheckBox* playlist_title_as_game_title = new QCheckBox(tr("Use Single Card For Sub-Images"), box);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, playlist_title_as_game_title, "MemoryCards",
|
||||
"UsePlaylistTitle", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_dialog->getSettingsInterface(), playlist_title_as_game_title,
|
||||
"MemoryCards", "UsePlaylistTitle", true);
|
||||
box_layout->addWidget(playlist_title_as_game_title);
|
||||
dialog->registerWidgetHelp(
|
||||
playlist_title_as_game_title, tr("Use Single Card For Sub-Images"), tr("Checked"),
|
||||
@@ -86,8 +76,6 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog)
|
||||
note_label->setWordWrap(true);
|
||||
note_layout->addWidget(note_label, 1);
|
||||
|
||||
QPushButton* open_memcards = new QPushButton(tr("Open Directory..."), box);
|
||||
connect(open_memcards, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onOpenMemCardsDirectoryClicked);
|
||||
note_layout->addWidget(open_memcards);
|
||||
box_layout->addLayout(note_layout);
|
||||
}
|
||||
@@ -101,13 +89,16 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog)
|
||||
hbox->addWidget(label, 1);
|
||||
|
||||
QPushButton* button = new QPushButton(tr("Memory Card Editor..."), box);
|
||||
connect(button, &QPushButton::clicked,
|
||||
[]() { QtHostInterface::GetInstance()->getMainWindow()->openMemoryCardEditor(QString(), QString()); });
|
||||
connect(button, &QPushButton::clicked, []() { g_main_window->openMemoryCardEditor(QString(), QString()); });
|
||||
hbox->addWidget(button);
|
||||
box_layout->addLayout(hbox);
|
||||
}
|
||||
|
||||
layout->addWidget(box);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(m_dialog->getSettingsInterface(), m_memory_card_directory, browse,
|
||||
open_memcards, reset, "MemoryCards", "Directory",
|
||||
Path::Combine(EmuFolders::DataRoot, "memcards"));
|
||||
}
|
||||
|
||||
layout->addStretch(1);
|
||||
@@ -128,30 +119,32 @@ void MemoryCardSettingsWidget::createPortSettingsUi(SettingsDialog* dialog, int
|
||||
}
|
||||
|
||||
const MemoryCardType default_value = (index == 0) ? MemoryCardType::PerGameTitle : MemoryCardType::None;
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(
|
||||
m_host_interface, ui->memory_card_type, "MemoryCards", StringUtil::StdStringFromFormat("Card%dType", index + 1),
|
||||
&Settings::ParseMemoryCardTypeName, &Settings::GetMemoryCardTypeName, default_value);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_dialog->getSettingsInterface(), ui->memory_card_type, "MemoryCards",
|
||||
StringUtil::StdStringFromFormat("Card%dType", index + 1),
|
||||
&Settings::ParseMemoryCardTypeName, &Settings::GetMemoryCardTypeName,
|
||||
default_value);
|
||||
ui->layout->addWidget(new QLabel(tr("Memory Card Type:"), ui->container));
|
||||
ui->layout->addWidget(ui->memory_card_type);
|
||||
|
||||
QHBoxLayout* memory_card_layout = new QHBoxLayout();
|
||||
ui->memory_card_path = new QLineEdit(ui->container);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, ui->memory_card_path, "MemoryCards",
|
||||
StringUtil::StdStringFromFormat("Card%dPath", index + 1));
|
||||
updateMemoryCardPath(index);
|
||||
connect(ui->memory_card_path, &QLineEdit::textChanged, this, [this, index]() { onMemoryCardPathChanged(index); });
|
||||
if (ui->memory_card_path->text().isEmpty())
|
||||
{
|
||||
QSignalBlocker sb(ui->memory_card_path);
|
||||
ui->memory_card_path->setText(
|
||||
QString::fromStdString(m_host_interface->GetSharedMemoryCardPath(static_cast<u32>(index))));
|
||||
ui->memory_card_path->setText(QString::fromStdString(g_settings.GetSharedMemoryCardPath(static_cast<u32>(index))));
|
||||
}
|
||||
memory_card_layout->addWidget(ui->memory_card_path);
|
||||
|
||||
QPushButton* memory_card_path_browse = new QPushButton(tr("Browse..."), ui->container);
|
||||
connect(memory_card_path_browse, &QPushButton::clicked, [this, index]() { onBrowseMemoryCardPathClicked(index); });
|
||||
connect(memory_card_path_browse, &QPushButton::clicked, this,
|
||||
[this, index]() { onBrowseMemoryCardPathClicked(index); });
|
||||
memory_card_layout->addWidget(memory_card_path_browse);
|
||||
|
||||
QPushButton* memory_card_path_reset = new QPushButton(tr("Reset"), ui->container);
|
||||
connect(memory_card_path_reset, &QPushButton::clicked, [this, index]() { onResetMemoryCardPathClicked(index); });
|
||||
connect(memory_card_path_reset, &QPushButton::clicked, this,
|
||||
[this, index]() { onResetMemoryCardPathClicked(index); });
|
||||
memory_card_layout->addWidget(memory_card_path_reset);
|
||||
|
||||
ui->layout->addWidget(new QLabel(tr("Shared Memory Card Path:"), ui->container));
|
||||
@@ -170,38 +163,33 @@ void MemoryCardSettingsWidget::onBrowseMemoryCardPathClicked(int index)
|
||||
m_port_ui[index].memory_card_path->setText(path);
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::onMemoryCardPathChanged(int index)
|
||||
{
|
||||
const auto key = TinyString::FromFormat("Card%dPath", index + 1);
|
||||
std::string relative_path(
|
||||
Path::MakeRelative(m_port_ui[index].memory_card_path->text().toStdString(), EmuFolders::MemoryCards));
|
||||
m_dialog->setStringSettingValue("MemoryCards", key, relative_path.c_str());
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::onResetMemoryCardPathClicked(int index)
|
||||
{
|
||||
m_host_interface->RemoveSettingValue("MemoryCards", TinyString::FromFormat("Card%dPath", index + 1));
|
||||
m_host_interface->applySettings();
|
||||
const auto key = TinyString::FromFormat("Card%dPath", index + 1);
|
||||
if (m_dialog->isPerGameSettings())
|
||||
m_dialog->removeSettingValue("MemoryCards", key);
|
||||
else
|
||||
m_dialog->setStringSettingValue("MemoryCards", key, Settings::GetDefaultSharedMemoryCardName(index).c_str());
|
||||
|
||||
updateMemoryCardPath(index);
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::updateMemoryCardPath(int index)
|
||||
{
|
||||
const auto key = TinyString::FromFormat("Card%dPath", index + 1);
|
||||
std::string path(
|
||||
m_dialog->getEffectiveStringValue("MemoryCards", key, Settings::GetDefaultSharedMemoryCardName(index).c_str()));
|
||||
if (!Path::IsAbsolute(path))
|
||||
path = Path::Combine(EmuFolders::MemoryCards, path);
|
||||
|
||||
QSignalBlocker db(m_port_ui[index].memory_card_path);
|
||||
m_port_ui[index].memory_card_path->setText(QString::fromStdString(m_host_interface->GetSharedMemoryCardPath(index)));
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::onOpenMemCardsDirectoryClicked()
|
||||
{
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_memory_card_directory->text()));
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::onBrowseMemCardsDirectoryClicked()
|
||||
{
|
||||
QString path =
|
||||
QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Select path to memory card directory")));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
m_memory_card_directory->setText(path);
|
||||
m_host_interface->applySettings();
|
||||
}
|
||||
|
||||
void MemoryCardSettingsWidget::onResetMemCardsDirectoryClicked()
|
||||
{
|
||||
m_host_interface->RemoveSettingValue("MemoryCards", "Directory");
|
||||
m_host_interface->applySettings();
|
||||
|
||||
// This sucks.. settings are applied asynchronously, so we have to manually build the path here.
|
||||
QString memory_card_directory(m_host_interface->getUserDirectoryRelativePath(QStringLiteral("memcards")));
|
||||
QSignalBlocker db(m_memory_card_directory);
|
||||
m_memory_card_directory->setText(memory_card_directory);
|
||||
m_port_ui[index].memory_card_path->setText(QString::fromStdString(path));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class MemoryCardSettingsWidget : public QWidget
|
||||
@@ -16,11 +15,11 @@ class MemoryCardSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MemoryCardSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* dialog);
|
||||
MemoryCardSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~MemoryCardSettingsWidget();
|
||||
|
||||
private:
|
||||
QtHostInterface* m_host_interface;
|
||||
SettingsDialog* m_dialog;
|
||||
|
||||
struct PortSettingsUI
|
||||
{
|
||||
@@ -34,9 +33,8 @@ private:
|
||||
void createPortSettingsUi(SettingsDialog* dialog, int index, PortSettingsUI* ui);
|
||||
void onBrowseMemoryCardPathClicked(int index);
|
||||
void onResetMemoryCardPathClicked(int index);
|
||||
void onOpenMemCardsDirectoryClicked();
|
||||
void onBrowseMemCardsDirectoryClicked();
|
||||
void onResetMemCardsDirectoryClicked();
|
||||
void onMemoryCardPathChanged(int index);
|
||||
void updateMemoryCardPath(int index);
|
||||
|
||||
std::array<PortSettingsUI, 2> m_port_ui = {};
|
||||
QLineEdit* m_memory_card_directory = nullptr;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "postprocessingchainconfigwidget.h"
|
||||
#include "frontend-common/postprocessing_chain.h"
|
||||
#include "postprocessingshaderconfigwidget.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include <QtGui/QCursor>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
@@ -203,7 +203,7 @@ void PostProcessingChainConfigWidget::onShaderConfigButtonClicked()
|
||||
|
||||
void PostProcessingChainConfigWidget::onReloadButtonClicked()
|
||||
{
|
||||
QtHostInterface::GetInstance()->reloadPostProcessingShaders();
|
||||
g_emu_thread->reloadPostProcessingShaders();
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onSelectedShaderChanged()
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include "postprocessingsettingswidget.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qthost.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
PostProcessingSettingsWidget::PostProcessingSettingsWidget(QtHostInterface* host_interface, QWidget* parent,
|
||||
SettingsDialog* settings_dialog)
|
||||
: QWidget(parent), m_host_interface(host_interface)
|
||||
PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), m_dialog(dialog)
|
||||
{
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
m_ui.widget->setOptionsButtonVisible(false);
|
||||
m_ui.reload->setEnabled(false);
|
||||
updateShaderConfigPanel(-1);
|
||||
connectUi();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(host_interface, m_ui.enablePostProcessing, "Display", "PostProcessing",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, "Display", "PostProcessing", false);
|
||||
|
||||
std::string post_chain = m_host_interface->GetStringSettingValue("Display", "PostProcessChain");
|
||||
std::string post_chain = m_dialog->getStringValue("Display", "PostProcessChain", "").value_or(std::string());
|
||||
if (!post_chain.empty())
|
||||
{
|
||||
if (!m_ui.widget->setConfigString(post_chain))
|
||||
@@ -83,19 +83,17 @@ void PostProcessingSettingsWidget::onConfigChanged(const std::string& new_config
|
||||
{
|
||||
if (new_config.empty())
|
||||
{
|
||||
m_host_interface->RemoveSettingValue("Display", "PostProcessChain");
|
||||
m_dialog->removeSettingValue("Display", "PostProcessChain");
|
||||
m_ui.reload->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_host_interface->SetStringSettingValue("Display", "PostProcessChain", new_config.c_str());
|
||||
m_dialog->setStringSettingValue("Display", "PostProcessChain", new_config.c_str());
|
||||
m_ui.reload->setEnabled(true);
|
||||
}
|
||||
|
||||
m_host_interface->applySettings();
|
||||
}
|
||||
|
||||
void PostProcessingSettingsWidget::onReloadClicked()
|
||||
{
|
||||
m_host_interface->reloadPostProcessingShaders();
|
||||
g_emu_thread->reloadPostProcessingShaders();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "ui_postprocessingsettingswidget.h"
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class QtHostInterface;
|
||||
class SettingsDialog;
|
||||
|
||||
class PostProcessingSettingsWidget : public QWidget
|
||||
@@ -12,7 +11,7 @@ class PostProcessingSettingsWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PostProcessingSettingsWidget(QtHostInterface* host_interface, QWidget* parent, SettingsDialog* settings_dialog);
|
||||
PostProcessingSettingsWidget(SettingsDialog* dialog, QWidget* parent);
|
||||
~PostProcessingSettingsWidget();
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -24,8 +23,8 @@ private Q_SLOTS:
|
||||
private:
|
||||
void connectUi();
|
||||
void updateShaderConfigPanel(s32 index);
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
|
||||
SettingsDialog* m_dialog;
|
||||
|
||||
Ui::PostProcessingSettingsWidget m_ui;
|
||||
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
#include "qtdisplaywidget.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QWindowStateChangeEvent>
|
||||
#include <cmath>
|
||||
|
||||
#if !defined(_WIN32) && !defined(APPLE)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
QtDisplayWidget::QtDisplayWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
// We want a native window for both D3D and OpenGL.
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_NativeWindow, true);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
QtDisplayWidget::~QtDisplayWidget() = default;
|
||||
|
||||
qreal QtDisplayWidget::devicePixelRatioFromScreen() const
|
||||
{
|
||||
QScreen* screen_for_ratio;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
screen_for_ratio = windowHandle()->screen();
|
||||
#else
|
||||
screen_for_ratio = screen();
|
||||
#endif
|
||||
if (!screen_for_ratio)
|
||||
screen_for_ratio = QGuiApplication::primaryScreen();
|
||||
|
||||
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
|
||||
}
|
||||
|
||||
int QtDisplayWidget::scaledWindowWidth() const
|
||||
{
|
||||
return static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen()));
|
||||
}
|
||||
|
||||
int QtDisplayWidget::scaledWindowHeight() const
|
||||
{
|
||||
return static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen()));
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> QtDisplayWidget::getWindowInfo() const
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
|
||||
wi.window_handle = reinterpret_cast<void*>(winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle());
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Unknown PNI platform " << platform_name;
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
wi.surface_width = scaledWindowWidth();
|
||||
wi.surface_height = scaledWindowHeight();
|
||||
wi.surface_scale = devicePixelRatioFromScreen();
|
||||
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
void QtDisplayWidget::setRelativeMode(bool enabled)
|
||||
{
|
||||
if (m_relative_mouse_enabled == enabled)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
m_relative_mouse_start_position = QCursor::pos();
|
||||
|
||||
const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2));
|
||||
QCursor::setPos(center_pos);
|
||||
m_relative_mouse_last_position = center_pos;
|
||||
grabMouse();
|
||||
}
|
||||
else
|
||||
{
|
||||
QCursor::setPos(m_relative_mouse_start_position);
|
||||
releaseMouse();
|
||||
}
|
||||
|
||||
m_relative_mouse_enabled = enabled;
|
||||
}
|
||||
|
||||
QPaintEngine* QtDisplayWidget::paintEngine() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool QtDisplayWidget::event(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
const QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
|
||||
if (!key_event->isAutoRepeat())
|
||||
{
|
||||
emit windowKeyEvent(key_event->key(), static_cast<int>(key_event->modifiers()),
|
||||
event->type() == QEvent::KeyPress);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseMove:
|
||||
{
|
||||
const QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
|
||||
if (!m_relative_mouse_enabled)
|
||||
{
|
||||
const qreal dpr = devicePixelRatioFromScreen();
|
||||
const QPoint mouse_pos = mouse_event->pos();
|
||||
const int scaled_x = static_cast<int>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
const int scaled_y = static_cast<int>(static_cast<qreal>(mouse_pos.y()) * dpr);
|
||||
|
||||
windowMouseMoveEvent(scaled_x, scaled_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
|
||||
const QPoint mouse_pos = mapToGlobal(mouse_event->pos());
|
||||
|
||||
const int dx = mouse_pos.x() - center_pos.x();
|
||||
const int dy = mouse_pos.y() - center_pos.y();
|
||||
m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx);
|
||||
m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy);
|
||||
windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y());
|
||||
QCursor::setPos(center_pos);
|
||||
|
||||
#if 0
|
||||
qCritical() << "center" << center_pos.x() << "," << center_pos.y();
|
||||
qCritical() << "mouse" << mouse_pos.x() << "," << mouse_pos.y();
|
||||
qCritical() << "dxdy" << dx << "," << dy;
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
|
||||
emit windowMouseButtonEvent(static_cast<int>(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Wheel:
|
||||
{
|
||||
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
|
||||
emit windowMouseWheelEvent(wheel_event->angleDelta());
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Resize:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
emit windowResizedEvent(scaledWindowWidth(), scaledWindowHeight());
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Close:
|
||||
{
|
||||
emit windowClosedEvent();
|
||||
QWidget::event(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit windowRestoredEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::FocusIn:
|
||||
{
|
||||
QWidget::event(event);
|
||||
emit windowFocusEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::ActivationChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
if (isActiveWindow())
|
||||
emit windowFocusEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return QWidget::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
QtDisplayContainer::QtDisplayContainer() : QStackedWidget(nullptr) {}
|
||||
|
||||
QtDisplayContainer::~QtDisplayContainer() = default;
|
||||
|
||||
bool QtDisplayContainer::IsNeeded(bool fullscreen, bool render_to_main)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
if (fullscreen || render_to_main)
|
||||
return false;
|
||||
|
||||
// We only need this on Wayland because of client-side decorations...
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
return (platform_name == QStringLiteral("wayland"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void QtDisplayContainer::setDisplayWidget(QtDisplayWidget* widget)
|
||||
{
|
||||
Assert(!m_display_widget);
|
||||
m_display_widget = widget;
|
||||
addWidget(widget);
|
||||
}
|
||||
|
||||
QtDisplayWidget* QtDisplayContainer::removeDisplayWidget()
|
||||
{
|
||||
QtDisplayWidget* widget = m_display_widget;
|
||||
Assert(widget);
|
||||
m_display_widget = nullptr;
|
||||
removeWidget(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
bool QtDisplayContainer::event(QEvent* event)
|
||||
{
|
||||
const bool res = QStackedWidget::event(event);
|
||||
if (!m_display_widget)
|
||||
return res;
|
||||
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::Close:
|
||||
{
|
||||
emit m_display_widget->windowClosedEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit m_display_widget->windowRestoredEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::FocusIn:
|
||||
{
|
||||
emit m_display_widget->windowFocusEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::ActivationChange:
|
||||
{
|
||||
if (isActiveWindow())
|
||||
emit m_display_widget->windowFocusEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "common/window_info.h"
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <optional>
|
||||
|
||||
class QtDisplayWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtDisplayWidget(QWidget* parent);
|
||||
~QtDisplayWidget();
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
qreal devicePixelRatioFromScreen() const;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo() const;
|
||||
|
||||
void setRelativeMode(bool enabled);
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowFocusEvent();
|
||||
void windowResizedEvent(int width, int height);
|
||||
void windowRestoredEvent();
|
||||
void windowClosedEvent();
|
||||
void windowKeyEvent(int key_code, int mods, bool pressed);
|
||||
void windowMouseMoveEvent(int x, int y);
|
||||
void windowMouseButtonEvent(int button, bool pressed);
|
||||
void windowMouseWheelEvent(const QPoint& angle_delta);
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
QPoint m_relative_mouse_start_position{};
|
||||
QPoint m_relative_mouse_last_position{};
|
||||
bool m_relative_mouse_enabled = false;
|
||||
};
|
||||
|
||||
class QtDisplayContainer final : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtDisplayContainer();
|
||||
~QtDisplayContainer();
|
||||
|
||||
static bool IsNeeded(bool fullscreen, bool render_to_main);
|
||||
|
||||
void setDisplayWidget(QtDisplayWidget* widget);
|
||||
QtDisplayWidget* removeDisplayWidget();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
QtDisplayWidget* m_display_widget = nullptr;
|
||||
};
|
||||
2282
src/duckstation-qt/qthost.cpp
Normal file
2282
src/duckstation-qt/qthost.cpp
Normal file
File diff suppressed because it is too large
Load Diff
264
src/duckstation-qt/qthost.h
Normal file
264
src/duckstation-qt/qthost.h
Normal file
@@ -0,0 +1,264 @@
|
||||
#pragma once
|
||||
#include "core/host.h"
|
||||
#include "core/host_display.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "core/system.h"
|
||||
#include "core/types.h"
|
||||
#include "frontend-common/common_host.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "frontend-common/input_manager.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QThread>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class ByteStream;
|
||||
|
||||
class QActionGroup;
|
||||
class QEventLoop;
|
||||
class QMenu;
|
||||
class QWidget;
|
||||
class QTimer;
|
||||
class QTranslator;
|
||||
|
||||
class INISettingsInterface;
|
||||
|
||||
class HostDisplay;
|
||||
|
||||
class MainWindow;
|
||||
class DisplayWidget;
|
||||
|
||||
Q_DECLARE_METATYPE(std::optional<bool>);
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<SystemBootParameters>);
|
||||
|
||||
// These cause errors when compiling with gcc, implicitly defined?
|
||||
// Q_DECLARE_METATYPE(std::function<void()>);
|
||||
// Q_DECLARE_METATYPE(GPURenderer);
|
||||
// Q_DECLARE_METATYPE(InputBindingKey);
|
||||
|
||||
class EmuThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// This class is a scoped lock on the system, which prevents it from running while
|
||||
/// the object exists. Its purpose is to be used for blocking/modal popup boxes,
|
||||
/// where the VM needs to exit fullscreen temporarily.
|
||||
class SystemLock
|
||||
{
|
||||
public:
|
||||
SystemLock(SystemLock&& lock);
|
||||
SystemLock(const SystemLock&) = delete;
|
||||
~SystemLock();
|
||||
|
||||
/// Cancels any pending unpause/fullscreen transition.
|
||||
/// Call when you're going to destroy the system anyway.
|
||||
void cancelResume();
|
||||
|
||||
private:
|
||||
SystemLock(bool was_paused, bool was_fullscreen);
|
||||
friend EmuThread;
|
||||
|
||||
bool m_was_paused;
|
||||
bool m_was_fullscreen;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EmuThread(QThread* ui_thread);
|
||||
~EmuThread();
|
||||
|
||||
static void start();
|
||||
static void stop();
|
||||
|
||||
ALWAYS_INLINE bool isOnThread() const { return QThread::currentThread() == this; }
|
||||
|
||||
ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; }
|
||||
|
||||
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
|
||||
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
||||
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
|
||||
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
|
||||
|
||||
bool acquireHostDisplay(HostDisplay::RenderAPI api);
|
||||
void connectDisplaySignals(DisplayWidget* widget);
|
||||
void releaseHostDisplay();
|
||||
void renderDisplay();
|
||||
|
||||
void startBackgroundControllerPollTimer();
|
||||
void stopBackgroundControllerPollTimer();
|
||||
void wakeThread();
|
||||
|
||||
bool shouldRenderToMain() const;
|
||||
void loadSettings(SettingsInterface& si);
|
||||
void setInitialState();
|
||||
void checkForSettingsChanges(const Settings& old_settings);
|
||||
|
||||
void bootOrLoadState(std::string path);
|
||||
|
||||
void updatePerformanceCounters();
|
||||
void resetPerformanceCounters();
|
||||
|
||||
/// Locks the system by pausing it, while a popup dialog is displayed.
|
||||
/// This version is **only** for the system thread. UI thread should use the MainWindow variant.
|
||||
SystemLock pauseAndLockSystem();
|
||||
|
||||
Q_SIGNALS:
|
||||
void errorReported(const QString& title, const QString& message);
|
||||
bool messageConfirmed(const QString& title, const QString& message);
|
||||
void debuggerMessageReported(const QString& message);
|
||||
void settingsResetToDefault();
|
||||
void onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices);
|
||||
void onInputDeviceConnected(const QString& identifier, const QString& device_name);
|
||||
void onInputDeviceDisconnected(const QString& identifier);
|
||||
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
|
||||
void systemStarting();
|
||||
void systemStarted();
|
||||
void systemDestroyed();
|
||||
void systemPaused();
|
||||
void systemResumed();
|
||||
void gameListRefreshed();
|
||||
bool createDisplayRequested(bool fullscreen, bool render_to_main);
|
||||
bool updateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
void displaySizeRequested(qint32 width, qint32 height);
|
||||
void focusDisplayWidgetRequested();
|
||||
void destroyDisplayRequested();
|
||||
void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title);
|
||||
void inputProfileLoaded();
|
||||
void mouseModeRequested(bool relative, bool hide_cursor);
|
||||
void achievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void achievementsChallengeModeChanged();
|
||||
void cheatEnabled(quint32 index, bool enabled);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setDefaultSettings(bool system = true, bool controller = true);
|
||||
void applySettings(bool display_osd_messages = false);
|
||||
void reloadGameSettings(bool display_osd_messages = false);
|
||||
void reloadInputSources();
|
||||
void reloadInputBindings();
|
||||
void enumerateInputDevices();
|
||||
void enumerateVibrationMotors();
|
||||
void startFullscreenUI();
|
||||
void stopFullscreenUI();
|
||||
void bootSystem(std::shared_ptr<SystemBootParameters> params);
|
||||
void resumeSystemFromMostRecentState();
|
||||
void shutdownSystem(bool save_state = true);
|
||||
void resetSystem();
|
||||
void setSystemPaused(bool paused, bool wait_until_paused = false);
|
||||
void changeDisc(const QString& new_disc_filename);
|
||||
void changeDiscFromPlaylist(quint32 index);
|
||||
void loadState(const QString& filename);
|
||||
void loadState(bool global, qint32 slot);
|
||||
void saveState(const QString& filename, bool block_until_done = false);
|
||||
void saveState(bool global, qint32 slot, bool block_until_done = false);
|
||||
void undoLoadState();
|
||||
void setAudioOutputVolume(int volume, int fast_forward_volume);
|
||||
void setAudioOutputMuted(bool muted);
|
||||
void startDumpingAudio();
|
||||
void stopDumpingAudio();
|
||||
void singleStepCPU();
|
||||
void dumpRAM(const QString& filename);
|
||||
void dumpVRAM(const QString& filename);
|
||||
void dumpSPURAM(const QString& filename);
|
||||
void saveScreenshot();
|
||||
void redrawDisplayWindow();
|
||||
void toggleFullscreen();
|
||||
void setFullscreen(bool fullscreen);
|
||||
void setSurfaceless(bool surfaceless);
|
||||
void requestDisplaySize(float scale);
|
||||
void loadCheatList(const QString& filename);
|
||||
void setCheatEnabled(quint32 index, bool enabled);
|
||||
void applyCheat(quint32 index);
|
||||
void reloadPostProcessingShaders();
|
||||
|
||||
private Q_SLOTS:
|
||||
void stopInThread();
|
||||
void onDisplayWindowMouseMoveEvent(bool relative, float x, float y);
|
||||
void onDisplayWindowMouseButtonEvent(int button, bool pressed);
|
||||
void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
|
||||
void onDisplayWindowResized(int width, int height);
|
||||
void onDisplayWindowKeyEvent(int key, bool pressed);
|
||||
void doBackgroundControllerPoll();
|
||||
void runOnEmuThread(std::function<void()> callback);
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
using InputButtonHandler = std::function<void(bool)>;
|
||||
using InputAxisHandler = std::function<void(float)>;
|
||||
|
||||
void createBackgroundControllerPollTimer();
|
||||
void destroyBackgroundControllerPollTimer();
|
||||
void updateDisplayState();
|
||||
|
||||
QThread* m_ui_thread;
|
||||
QSemaphore m_started_semaphore;
|
||||
QEventLoop* m_event_loop = nullptr;
|
||||
QTimer* m_background_controller_polling_timer = nullptr;
|
||||
|
||||
std::atomic_bool m_shutdown_flag{false};
|
||||
|
||||
bool m_run_fullscreen_ui = false;
|
||||
bool m_is_rendering_to_main = false;
|
||||
bool m_is_fullscreen = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_lost_exclusive_fullscreen = false;
|
||||
bool m_is_surfaceless = false;
|
||||
bool m_save_state_on_shutdown = false;
|
||||
|
||||
bool m_was_paused_by_focus_loss = false;
|
||||
|
||||
float m_last_speed = std::numeric_limits<float>::infinity();
|
||||
float m_last_game_fps = std::numeric_limits<float>::infinity();
|
||||
float m_last_video_fps = std::numeric_limits<float>::infinity();
|
||||
u32 m_last_render_width = std::numeric_limits<u32>::max();
|
||||
u32 m_last_render_height = std::numeric_limits<u32>::max();
|
||||
GPURenderer m_last_renderer = GPURenderer::Count;
|
||||
};
|
||||
|
||||
extern EmuThread* g_emu_thread;
|
||||
|
||||
namespace QtHost {
|
||||
/// Sets batch mode (exit after game shutdown).
|
||||
bool InBatchMode();
|
||||
|
||||
/// Sets NoGUI mode (implys batch mode, does not display main window, exits on shutdown).
|
||||
bool InNoGUIMode();
|
||||
|
||||
/// Executes a function on the UI thread.
|
||||
void RunOnUIThread(const std::function<void()>& func, bool block = false);
|
||||
|
||||
/// Returns a list of supported languages and codes (suffixes for translation files).
|
||||
std::vector<std::pair<QString, QString>> GetAvailableLanguageList();
|
||||
|
||||
/// Call when the language changes.
|
||||
void ReinstallTranslator();
|
||||
|
||||
/// Returns the application name and version, optionally including debug/devel config indicator.
|
||||
QString GetAppNameAndVersion();
|
||||
|
||||
/// Returns the debug/devel config indicator.
|
||||
QString GetAppConfigSuffix();
|
||||
|
||||
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
|
||||
QString GetResourcesBasePath();
|
||||
|
||||
/// Thread-safe settings access.
|
||||
void QueueSettingsSave();
|
||||
|
||||
/// VM state, safe to access on UI thread.
|
||||
bool IsSystemValid();
|
||||
bool IsSystemPaused();
|
||||
} // namespace QtHost
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,297 +0,0 @@
|
||||
#pragma once
|
||||
#include "common/event.h"
|
||||
#include "core/host_interface.h"
|
||||
#include "core/system.h"
|
||||
#include "frontend-common/common_host_interface.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QThread>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class ByteStream;
|
||||
|
||||
class QActionGroup;
|
||||
class QEventLoop;
|
||||
class QMenu;
|
||||
class QWidget;
|
||||
class QTimer;
|
||||
class QTranslator;
|
||||
|
||||
class INISettingsInterface;
|
||||
|
||||
class MainWindow;
|
||||
class QtDisplayWidget;
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<SystemBootParameters>);
|
||||
Q_DECLARE_METATYPE(const GameListEntry*);
|
||||
Q_DECLARE_METATYPE(GPURenderer);
|
||||
|
||||
class QtHostInterface final : public QObject, public CommonHostInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHostInterface(QObject* parent = nullptr);
|
||||
~QtHostInterface();
|
||||
|
||||
ALWAYS_INLINE static QtHostInterface* GetInstance() { return static_cast<QtHostInterface*>(g_host_interface); }
|
||||
|
||||
const char* GetFrontendName() const override;
|
||||
|
||||
bool Initialize() override;
|
||||
void Shutdown() override;
|
||||
|
||||
void RunLater(std::function<void()> func) override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void ReportError(const char* message) override;
|
||||
void ReportMessage(const char* message) override;
|
||||
void ReportDebuggerMessage(const char* message) override;
|
||||
bool ConfirmMessage(const char* message) override;
|
||||
|
||||
public:
|
||||
/// Thread-safe settings access.
|
||||
void SetBoolSettingValue(const char* section, const char* key, bool value);
|
||||
void SetIntSettingValue(const char* section, const char* key, int value);
|
||||
void SetFloatSettingValue(const char* section, const char* key, float value);
|
||||
void SetStringSettingValue(const char* section, const char* key, const char* value);
|
||||
void SetStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values);
|
||||
bool AddValueToStringList(const char* section, const char* key, const char* value);
|
||||
bool RemoveValueFromStringList(const char* section, const char* key, const char* value);
|
||||
void RemoveSettingValue(const char* section, const char* key);
|
||||
|
||||
TinyString TranslateString(const char* context, const char* str, const char* disambiguation = nullptr,
|
||||
int n = -1) const override;
|
||||
std::string TranslateStdString(const char* context, const char* str, const char* disambiguation = nullptr,
|
||||
int n = -1) const override;
|
||||
|
||||
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
||||
void* GetTopLevelWindowHandle() const override;
|
||||
|
||||
ALWAYS_INLINE const GameList* getGameList() const { return m_game_list.get(); }
|
||||
ALWAYS_INLINE GameList* getGameList() { return m_game_list.get(); }
|
||||
void refreshGameList(bool invalidate_cache = false, bool invalidate_database = false);
|
||||
|
||||
ALWAYS_INLINE const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); }
|
||||
ALWAYS_INLINE ControllerInterface* getControllerInterface() const { return GetControllerInterface(); }
|
||||
ALWAYS_INLINE bool inBatchMode() const { return InBatchMode(); }
|
||||
ALWAYS_INLINE void requestExit() { RequestExit(); }
|
||||
|
||||
ALWAYS_INLINE bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
||||
|
||||
ALWAYS_INLINE MainWindow* getMainWindow() const { return m_main_window; }
|
||||
void setMainWindow(MainWindow* window);
|
||||
HostDisplay* createHostDisplay();
|
||||
void connectDisplaySignals(QtDisplayWidget* widget);
|
||||
void reinstallTranslator();
|
||||
|
||||
void populateLoadStateMenu(const char* game_code, QMenu* menu);
|
||||
void populateSaveStateMenu(const char* game_code, QMenu* menu);
|
||||
|
||||
/// Fills menu with save state info and handlers.
|
||||
void populateGameListContextMenu(const GameListEntry* entry, QWidget* parent_window, QMenu* menu);
|
||||
|
||||
/// Fills menu with the current playlist entries. The disc index is marked as checked.
|
||||
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
||||
|
||||
/// Fills menu with the current cheat options.
|
||||
void populateCheatsMenu(QMenu* menu);
|
||||
|
||||
ALWAYS_INLINE QString getSavePathForInputProfile(const QString& name) const
|
||||
{
|
||||
return QString::fromStdString(GetSavePathForInputProfile(name.toUtf8().constData()));
|
||||
}
|
||||
ALWAYS_INLINE InputProfileList getInputProfileList() const { return GetInputProfileList(); }
|
||||
void saveInputProfile(const QString& profile_path);
|
||||
|
||||
/// Returns a path relative to the user directory.
|
||||
QString getUserDirectoryRelativePath(const QString& arg) const;
|
||||
|
||||
/// Returns a path relative to the application directory (for system files).
|
||||
QString getProgramDirectoryRelativePath(const QString& arg) const;
|
||||
|
||||
/// Returns a list of supported languages and codes (suffixes for translation files).
|
||||
static std::vector<std::pair<QString, QString>> getAvailableLanguageList();
|
||||
|
||||
/// Returns program directory as a QString.
|
||||
QString getProgramDirectory() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void errorReported(const QString& message);
|
||||
void messageReported(const QString& message);
|
||||
void debuggerMessageReported(const QString& message);
|
||||
bool messageConfirmed(const QString& message);
|
||||
void settingsResetToDefault();
|
||||
void emulationStarting();
|
||||
void emulationStarted();
|
||||
void emulationStopped();
|
||||
void emulationPaused(bool paused);
|
||||
void gameListRefreshed();
|
||||
QtDisplayWidget* createDisplayRequested(QThread* worker_thread, bool fullscreen, bool render_to_main);
|
||||
QtDisplayWidget* updateDisplayRequested(QThread* worker_thread, bool fullscreen, bool render_to_main);
|
||||
void displaySizeRequested(qint32 width, qint32 height);
|
||||
void focusDisplayWidgetRequested();
|
||||
void destroyDisplayRequested();
|
||||
void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time,
|
||||
GPURenderer renderer, quint32 render_width, quint32 render_height,
|
||||
bool render_interlaced);
|
||||
void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title);
|
||||
void exitRequested();
|
||||
void inputProfileLoaded();
|
||||
void mouseModeRequested(bool relative, bool hide_cursor);
|
||||
void achievementsLoaded(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void cheatEnabled(quint32 index, bool enabled);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setDefaultSettings();
|
||||
void applySettings(bool display_osd_messages = false);
|
||||
void updateInputMap();
|
||||
void applyInputProfile(const QString& profile_path);
|
||||
void bootSystem(std::shared_ptr<SystemBootParameters> params);
|
||||
void resumeSystemFromState(const QString& filename, bool boot_on_failure);
|
||||
void resumeSystemFromMostRecentState();
|
||||
void powerOffSystem();
|
||||
void powerOffSystemWithoutSaving();
|
||||
void synchronousPowerOffSystem();
|
||||
void resetSystem();
|
||||
void pauseSystem(bool paused, bool wait_until_paused = false);
|
||||
void changeDisc(const QString& new_disc_filename);
|
||||
void changeDiscFromPlaylist(quint32 index);
|
||||
void loadState(const QString& filename);
|
||||
void loadState(bool global, qint32 slot);
|
||||
void saveState(const QString& filename, bool block_until_done = false);
|
||||
void saveState(bool global, qint32 slot, bool block_until_done = false);
|
||||
void undoLoadState();
|
||||
void setAudioOutputVolume(int volume, int fast_forward_volume);
|
||||
void setAudioOutputMuted(bool muted);
|
||||
void startDumpingAudio();
|
||||
void stopDumpingAudio();
|
||||
void singleStepCPU();
|
||||
void dumpRAM(const QString& filename);
|
||||
void dumpVRAM(const QString& filename);
|
||||
void dumpSPURAM(const QString& filename);
|
||||
void saveScreenshot();
|
||||
void redrawDisplayWindow();
|
||||
void toggleFullscreen();
|
||||
void loadCheatList(const QString& filename);
|
||||
void setCheatEnabled(quint32 index, bool enabled);
|
||||
void applyCheat(quint32 index);
|
||||
void reloadPostProcessingShaders();
|
||||
void requestRenderWindowScale(qreal scale);
|
||||
void executeOnEmulationThread(std::function<void()> callback, bool wait = false);
|
||||
void OnAchievementsRefreshed() override;
|
||||
void OnDisplayInvalidated() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void doStopThread();
|
||||
void onDisplayWindowMouseMoveEvent(int x, int y);
|
||||
void onDisplayWindowMouseButtonEvent(int button, bool pressed);
|
||||
void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
|
||||
void onDisplayWindowResized(int width, int height);
|
||||
void onDisplayWindowFocused();
|
||||
void onDisplayWindowKeyEvent(int key, int mods, bool pressed);
|
||||
void doBackgroundControllerPoll();
|
||||
void doSaveSettings();
|
||||
|
||||
protected:
|
||||
bool AcquireHostDisplay() override;
|
||||
void ReleaseHostDisplay() override;
|
||||
bool IsFullscreen() const override;
|
||||
bool SetFullscreen(bool enabled) override;
|
||||
|
||||
void RequestExit() override;
|
||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
||||
|
||||
void OnSystemCreated() override;
|
||||
void OnSystemPaused(bool paused) override;
|
||||
void OnSystemDestroyed() override;
|
||||
void OnSystemPerformanceCountersUpdated() override;
|
||||
void OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code,
|
||||
const std::string& game_title) override;
|
||||
|
||||
void SetDefaultSettings(SettingsInterface& si) override;
|
||||
void SetDefaultSettings() override;
|
||||
void ApplySettings(bool display_osd_messages) override;
|
||||
|
||||
void SetMouseMode(bool relative, bool hide_cursor) override;
|
||||
|
||||
private:
|
||||
enum : u32
|
||||
{
|
||||
BACKGROUND_CONTROLLER_POLLING_INTERVAL =
|
||||
100, /// Interval at which the controllers are polled when the system is not active.
|
||||
|
||||
SETTINGS_SAVE_DELAY = 1000,
|
||||
|
||||
/// Crappy solution to the Qt indices being massive.
|
||||
IMGUI_KEY_MASK = 511,
|
||||
};
|
||||
|
||||
using InputButtonHandler = std::function<void(bool)>;
|
||||
using InputAxisHandler = std::function<void(float)>;
|
||||
|
||||
class Thread : public QThread
|
||||
{
|
||||
public:
|
||||
Thread(QtHostInterface* parent);
|
||||
~Thread();
|
||||
|
||||
void setInitResult(bool result);
|
||||
bool waitForInit();
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
QtHostInterface* m_parent;
|
||||
std::atomic_bool m_init_result{false};
|
||||
Common::Event m_init_event;
|
||||
};
|
||||
|
||||
void createBackgroundControllerPollTimer();
|
||||
void destroyBackgroundControllerPollTimer();
|
||||
void startBackgroundControllerPollTimer();
|
||||
void stopBackgroundControllerPollTimer();
|
||||
|
||||
void setImGuiFont();
|
||||
void setImGuiKeyMap();
|
||||
|
||||
void createThread();
|
||||
void stopThread();
|
||||
void threadEntryPoint();
|
||||
bool initializeOnThread();
|
||||
void shutdownOnThread();
|
||||
void installTranslator();
|
||||
void renderDisplay();
|
||||
void checkRenderToMainState();
|
||||
void updateDisplayState();
|
||||
void queueSettingsSave();
|
||||
void wakeThread();
|
||||
|
||||
MainWindow* m_main_window = nullptr;
|
||||
QThread* m_original_thread = nullptr;
|
||||
Thread* m_worker_thread = nullptr;
|
||||
QEventLoop* m_worker_thread_event_loop = nullptr;
|
||||
Common::Event m_worker_thread_sync_execute_done;
|
||||
|
||||
std::atomic_bool m_shutdown_flag{false};
|
||||
|
||||
QTimer* m_background_controller_polling_timer = nullptr;
|
||||
std::unique_ptr<QTimer> m_settings_save_timer;
|
||||
std::vector<QTranslator*> m_translators;
|
||||
|
||||
bool m_is_rendering_to_main = false;
|
||||
bool m_is_fullscreen = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_lost_exclusive_fullscreen = false;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user