Qt: Separate controller settings to global and profiles

This commit is contained in:
Stenzek 2025-01-11 20:04:19 +10:00
parent 2d63b34d48
commit e4c11aa905
No known key found for this signature in database
9 changed files with 167 additions and 106 deletions

View File

@ -5,6 +5,7 @@
#include "controllerbindingwidgets.h" #include "controllerbindingwidgets.h"
#include "controllerglobalsettingswidget.h" #include "controllerglobalsettingswidget.h"
#include "hotkeysettingswidget.h" #include "hotkeysettingswidget.h"
#include "mainwindow.h"
#include "qthost.h" #include "qthost.h"
#include "core/controller.h" #include "core/controller.h"
@ -22,8 +23,8 @@
#include <array> #include <array>
ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /* = nullptr */, ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /* = nullptr */,
QWidget* parent /* = nullptr */) bool edit_profiles /* = false */, QWidget* parent /* = nullptr */)
: QWidget(parent), m_editing_settings_interface(game_sif) : QWidget(parent), m_editing_settings_interface(game_sif), m_editing_input_profiles(edit_profiles)
{ {
m_ui.setupUi(this); m_ui.setupUi(this);
@ -35,23 +36,9 @@ ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /
&ControllerSettingsWindow::onCategoryCurrentRowChanged); &ControllerSettingsWindow::onCategoryCurrentRowChanged);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close);
if (!game_sif) if (!game_sif && !edit_profiles)
{
refreshProfileList();
m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings);
delete m_ui.copyGlobalSettings;
m_ui.copyGlobalSettings = nullptr;
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
&ControllerSettingsWindow::onCurrentProfileChanged);
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
}
else
{ {
// editing global settings
m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel); m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
delete m_ui.editProfileLabel; delete m_ui.editProfileLabel;
m_ui.editProfileLabel = nullptr; m_ui.editProfileLabel = nullptr;
@ -67,11 +54,51 @@ ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /
m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile); m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
delete m_ui.deleteProfile; delete m_ui.deleteProfile;
m_ui.deleteProfile = nullptr; m_ui.deleteProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings);
delete m_ui.copyGlobalSettings;
m_ui.copyGlobalSettings = nullptr;
if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults))
connect(button, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
}
else
{
if (QPushButton* button = m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults))
m_ui.buttonBox->removeButton(button);
connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this, connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this,
&ControllerSettingsWindow::onCopyGlobalSettingsClicked); &ControllerSettingsWindow::onCopyGlobalSettingsClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this,
&ControllerSettingsWindow::onRestoreDefaultsForGameClicked); if (edit_profiles)
{
setWindowTitle(tr("DuckStation Controller Profiles"));
refreshProfileList();
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
&ControllerSettingsWindow::onCurrentProfileChanged);
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
}
else
{
// editing game settings
m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
delete m_ui.editProfileLabel;
m_ui.editProfileLabel = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.currentProfile);
delete m_ui.currentProfile;
m_ui.currentProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.newProfile);
delete m_ui.newProfile;
m_ui.newProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.applyProfile);
delete m_ui.applyProfile;
m_ui.applyProfile = nullptr;
m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
delete m_ui.deleteProfile;
m_ui.deleteProfile = nullptr;
}
} }
createWidgets(); createWidgets();
@ -81,7 +108,7 @@ ControllerSettingsWindow::~ControllerSettingsWindow() = default;
void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif) void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif)
{ {
ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, parent); ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, false, parent);
dlg->setWindowFlag(Qt::Window); dlg->setWindowFlag(Qt::Window);
dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setWindowModality(Qt::WindowModality::WindowModal); dlg->setWindowModality(Qt::WindowModality::WindowModal);
@ -207,9 +234,8 @@ void ControllerSettingsWindow::onNewProfileClicked()
if (!temp_si.Save()) if (!temp_si.Save())
{ {
QMessageBox::critical( QMessageBox::critical(this, tr("Error"),
this, tr("Error"), tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetPath())));
tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetPath())));
return; return;
} }
@ -238,14 +264,14 @@ void ControllerSettingsWindow::onApplyProfileClicked()
} }
g_emu_thread->applySettings(); g_emu_thread->applySettings();
// make it visible // Recreate global widget on profile apply
switchProfile({}); g_main_window->getControllerSettingsWindow()->createWidgets();
} }
void ControllerSettingsWindow::onDeleteProfileClicked() void ControllerSettingsWindow::onDeleteProfileClicked()
{ {
if (QMessageBox::question(this, tr("Delete Input Profile"), if (QMessageBox::question(this, tr("Delete Input Preset"),
tr("Are you sure you want to delete the input profile named '%1'?\n\n" tr("Are you sure you want to delete the input preset named '%1'?\n\n"
"You cannot undo this action.") "You cannot undo this action.")
.arg(m_profile_name)) != QMessageBox::Yes) .arg(m_profile_name)) != QMessageBox::Yes)
{ {
@ -266,11 +292,10 @@ void ControllerSettingsWindow::onDeleteProfileClicked()
void ControllerSettingsWindow::onRestoreDefaultsClicked() void ControllerSettingsWindow::onRestoreDefaultsClicked()
{ {
if (QMessageBox::question( if (QMessageBox::question(this, tr("Restore Defaults"),
this, tr("Restore Defaults"), tr("Are you sure you want to restore the default controller configuration?\n\n"
tr("Are you sure you want to restore the default controller configuration?\n\n" "All bindings and configuration will be lost. You cannot undo this action.")) !=
"All shared bindings and configuration will be lost, but your input profiles will remain.\n\n" QMessageBox::Yes)
"You cannot undo this action.")) != QMessageBox::Yes)
{ {
return; return;
} }
@ -279,12 +304,12 @@ void ControllerSettingsWindow::onRestoreDefaultsClicked()
g_emu_thread->setDefaultSettings(false, true); g_emu_thread->setDefaultSettings(false, true);
// reload all settings // reload all settings
switchProfile({}); createWidgets();
} }
void ControllerSettingsWindow::onCopyGlobalSettingsClicked() void ControllerSettingsWindow::onCopyGlobalSettingsClicked()
{ {
DebugAssert(isEditingGameSettings()); DebugAssert(!isEditingGlobalSettings());
{ {
const auto lock = Host::GetSettingsLock(); const auto lock = Host::GetSettingsLock();
@ -297,19 +322,8 @@ void ControllerSettingsWindow::onCopyGlobalSettingsClicked()
createWidgets(); createWidgets();
QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"), QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
tr("Per-game controller configuration reset to global settings.")); isEditingGameSettings() ? tr("Per-game controller configuration reset to global settings.") :
} tr("Controller profile reset to global settings."));
void ControllerSettingsWindow::onRestoreDefaultsForGameClicked()
{
DebugAssert(isEditingGameSettings());
Settings::SetDefaultControllerConfig(*m_editing_settings_interface);
m_editing_settings_interface->Save();
g_emu_thread->reloadGameSettings();
createWidgets();
QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
tr("Per-game controller configuration reset to default settings."));
} }
bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const
@ -472,11 +486,12 @@ void ControllerSettingsWindow::createWidgets()
m_ui.settingsContainer->addWidget(m_hotkey_settings); m_ui.settingsContainer->addWidget(m_hotkey_settings);
} }
if (!isEditingGameSettings()) if (isEditingProfile())
{ {
m_ui.applyProfile->setEnabled(isEditingProfile()); const bool enable_buttons = static_cast<bool>(m_profile_settings_interface);
m_ui.deleteProfile->setEnabled(isEditingProfile()); m_ui.applyProfile->setEnabled(enable_buttons);
m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings()); m_ui.deleteProfile->setEnabled(enable_buttons);
m_ui.copyGlobalSettings->setEnabled(enable_buttons);
} }
} }
@ -519,55 +534,64 @@ std::array<bool, 2> ControllerSettingsWindow::getEnabledMultitaps() const
return {{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts), return {{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}}; (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
} }
void ControllerSettingsWindow::refreshProfileList() void ControllerSettingsWindow::refreshProfileList()
{ {
const std::vector<std::string> names(InputManager::GetInputProfileNames()); const std::vector<std::string> names(InputManager::GetInputProfileNames());
QSignalBlocker sb(m_ui.currentProfile); QSignalBlocker sb(m_ui.currentProfile);
m_ui.currentProfile->clear(); m_ui.currentProfile->clear();
m_ui.currentProfile->addItem(tr("Shared"));
if (isEditingGlobalSettings())
m_ui.currentProfile->setCurrentIndex(0);
bool current_profile_found = false;
for (const std::string& name : names) for (const std::string& name : names)
{ {
const QString qname(QString::fromStdString(name)); const QString qname(QString::fromStdString(name));
m_ui.currentProfile->addItem(qname); m_ui.currentProfile->addItem(qname);
if (qname == m_profile_name) if (qname == m_profile_name)
{
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1); m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1);
current_profile_found = true;
}
} }
if (!current_profile_found)
switchProfile(names.empty() ? std::string_view() : std::string_view(names.front()));
} }
void ControllerSettingsWindow::switchProfile(const std::string_view name) void ControllerSettingsWindow::switchProfile(const std::string_view name)
{ {
QSignalBlocker sb(m_ui.currentProfile); const QString name_qstr = QtUtils::StringViewToQString(name);
if (!name.empty())
{ {
const QString name_qstr = QtUtils::StringViewToQString(name); QSignalBlocker sb(m_ui.currentProfile);
std::string path = System::GetInputProfilePath(name);
if (!FileSystem::FileExists(path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr));
return;
}
std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(path));
sif->Load();
m_profile_settings_interface = std::move(sif);
m_editing_settings_interface = m_profile_settings_interface.get();
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr)); m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr));
m_profile_name = name_qstr;
} }
else m_profile_name = name_qstr;
m_profile_settings_interface.reset();
m_editing_settings_interface = nullptr;
// disable UI if there is no selection
const bool disable_ui = name.empty();
m_ui.settingsCategory->setDisabled(disable_ui);
m_ui.settingsContainer->setDisabled(disable_ui);
if (name_qstr.isEmpty())
{ {
m_profile_settings_interface.reset(); createWidgets();
m_editing_settings_interface = nullptr; return;
m_ui.currentProfile->setCurrentIndex(0);
m_profile_name = QString();
} }
std::string path = System::GetInputProfilePath(name);
if (!FileSystem::FileExists(path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr));
return;
}
std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(path));
sif->Load();
m_profile_settings_interface = std::move(sif);
m_editing_settings_interface = m_profile_settings_interface.get();
createWidgets(); createWidgets();
} }

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once #pragma once
@ -14,6 +14,7 @@
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/QStringList> #include <QtCore/QStringList>
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <QtCore/QAbstractListModel>
#include <array> #include <array>
#include <string> #include <string>
@ -41,7 +42,8 @@ public:
Count Count
}; };
ControllerSettingsWindow(SettingsInterface* game_sif = nullptr, QWidget* parent = nullptr); ControllerSettingsWindow(SettingsInterface* game_sif = nullptr, bool edit_profiles = false,
QWidget* parent = nullptr);
~ControllerSettingsWindow(); ~ControllerSettingsWindow();
static void editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif); static void editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif);
@ -50,13 +52,13 @@ public:
ALWAYS_INLINE bool isEditingGlobalSettings() const ALWAYS_INLINE bool isEditingGlobalSettings() const
{ {
return (m_profile_name.isEmpty() && !m_editing_settings_interface); return (!m_editing_input_profiles && !m_editing_settings_interface);
} }
ALWAYS_INLINE bool isEditingGameSettings() const ALWAYS_INLINE bool isEditingGameSettings() const
{ {
return (m_profile_name.isEmpty() && m_editing_settings_interface); return (!m_editing_input_profiles && m_editing_settings_interface);
} }
ALWAYS_INLINE bool isEditingProfile() const { return !m_profile_name.isEmpty(); } ALWAYS_INLINE bool isEditingProfile() const { return m_editing_input_profiles; }
ALWAYS_INLINE SettingsInterface* getEditingSettingsInterface() { return m_editing_settings_interface; } ALWAYS_INLINE SettingsInterface* getEditingSettingsInterface() { return m_editing_settings_interface; }
Category getCurrentCategory() const; Category getCurrentCategory() const;
@ -90,7 +92,6 @@ private Q_SLOTS:
void onDeleteProfileClicked(); void onDeleteProfileClicked();
void onRestoreDefaultsClicked(); void onRestoreDefaultsClicked();
void onCopyGlobalSettingsClicked(); void onCopyGlobalSettingsClicked();
void onRestoreDefaultsForGameClicked();
void createWidgets(); void createWidgets();
@ -113,4 +114,5 @@ private:
QString m_profile_name; QString m_profile_name;
std::unique_ptr<SettingsInterface> m_profile_settings_interface; std::unique_ptr<SettingsInterface> m_profile_settings_interface;
bool m_editing_input_profiles = false;
}; };

View File

@ -126,22 +126,12 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Close</set> <set>QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::RestoreDefaults</set>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -797,6 +797,7 @@ void MainWindow::destroySubWindows()
QtUtils::CloseAndDeleteWindow(m_debugger_window); QtUtils::CloseAndDeleteWindow(m_debugger_window);
QtUtils::CloseAndDeleteWindow(m_memory_card_editor_window); QtUtils::CloseAndDeleteWindow(m_memory_card_editor_window);
QtUtils::CloseAndDeleteWindow(m_controller_settings_window); QtUtils::CloseAndDeleteWindow(m_controller_settings_window);
QtUtils::CloseAndDeleteWindow(m_input_profile_editor_window);
QtUtils::CloseAndDeleteWindow(m_settings_window); QtUtils::CloseAndDeleteWindow(m_settings_window);
SettingsWindow::closeGamePropertiesDialogs(); SettingsWindow::closeGamePropertiesDialogs();
@ -2021,6 +2022,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); }); connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); });
connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); }); connect(m_ui.actionFolderSettings, &QAction::triggered, [this]() { doSettings("Folders"); });
connect(m_ui.actionAdvancedSettings, &QAction::triggered, [this]() { doSettings("Advanced"); }); connect(m_ui.actionAdvancedSettings, &QAction::triggered, [this]() { doSettings("Advanced"); });
connect(m_ui.actionControllerProfiles, &QAction::triggered, this, &MainWindow::onSettingsControllerProfilesTriggered);
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled); connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled); connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled);
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled); connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
@ -2336,11 +2338,29 @@ void MainWindow::doControllerSettings(
dlg->setCategory(category); dlg->setCategory(category);
} }
void MainWindow::onSettingsTriggeredFromToolbar()
{
if (s_system_valid)
m_settings_toolbar_menu->exec(QCursor::pos());
else
doSettings();
}
void MainWindow::onSettingsControllerProfilesTriggered()
{
if (!m_input_profile_editor_window)
m_input_profile_editor_window = new ControllerSettingsWindow(nullptr, true);
QtUtils::ShowOrRaiseWindow(m_input_profile_editor_window);
}
void MainWindow::openInputProfileEditor(const std::string_view name) void MainWindow::openInputProfileEditor(const std::string_view name)
{ {
ControllerSettingsWindow* dlg = getControllerSettingsWindow(); if (!m_input_profile_editor_window)
QtUtils::ShowOrRaiseWindow(dlg); m_input_profile_editor_window = new ControllerSettingsWindow(nullptr, true);
dlg->switchProfile(name);
QtUtils::ShowOrRaiseWindow(m_input_profile_editor_window);
m_input_profile_editor_window->switchProfile(name);
} }
void MainWindow::showEvent(QShowEvent* event) void MainWindow::showEvent(QShowEvent* event)
@ -2794,14 +2814,6 @@ void MainWindow::onToolsOpenTextureDirectoryTriggered()
QtUtils::OpenURL(this, QUrl::fromLocalFile(dir)); QtUtils::OpenURL(this, QUrl::fromLocalFile(dir));
} }
void MainWindow::onSettingsTriggeredFromToolbar()
{
if (s_system_valid)
m_settings_toolbar_menu->exec(QCursor::pos());
else
doSettings();
}
void MainWindow::checkForUpdates(bool display_message) void MainWindow::checkForUpdates(bool display_message)
{ {
if (!AutoUpdaterDialog::isSupported()) if (!AutoUpdaterDialog::isSupported())

View File

@ -192,6 +192,7 @@ private Q_SLOTS:
void onToolsOpenDataDirectoryTriggered(); void onToolsOpenDataDirectoryTriggered();
void onToolsOpenTextureDirectoryTriggered(); void onToolsOpenTextureDirectoryTriggered();
void onSettingsTriggeredFromToolbar(); void onSettingsTriggeredFromToolbar();
void onSettingsControllerProfilesTriggered();
void onGameListRefreshComplete(); void onGameListRefreshComplete();
void onGameListRefreshProgress(const QString& status, int current, int total); void onGameListRefreshProgress(const QString& status, int current, int total);
@ -313,6 +314,7 @@ private:
SettingsWindow* m_settings_window = nullptr; SettingsWindow* m_settings_window = nullptr;
ControllerSettingsWindow* m_controller_settings_window = nullptr; ControllerSettingsWindow* m_controller_settings_window = nullptr;
ControllerSettingsWindow* m_input_profile_editor_window = nullptr;
AutoUpdaterDialog* m_auto_updater_dialog = nullptr; AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
MemoryCardEditorWindow* m_memory_card_editor_window = nullptr; MemoryCardEditorWindow* m_memory_card_editor_window = nullptr;

View File

@ -115,6 +115,7 @@
<addaction name="actionAdvancedSettings"/> <addaction name="actionAdvancedSettings"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionControllerSettings"/> <addaction name="actionControllerSettings"/>
<addaction name="actionControllerProfiles"/>
<addaction name="actionHotkeySettings"/> <addaction name="actionHotkeySettings"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionAddGameDirectory"/> <addaction name="actionAddGameDirectory"/>
@ -985,6 +986,14 @@
<string>Controller Test</string> <string>Controller Test</string>
</property> </property>
</action> </action>
<action name="actionControllerProfiles">
<property name="icon">
<iconset theme="controllers-line"/>
</property>
<property name="text">
<string>Controller Profiles</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="resources/duckstation-qt.qrc"/> <include location="resources/duckstation-qt.qrc"/>

View File

@ -37,6 +37,7 @@
<file>icons/black/svg/chip-2-line.svg</file> <file>icons/black/svg/chip-2-line.svg</file>
<file>icons/black/svg/chip-line.svg</file> <file>icons/black/svg/chip-line.svg</file>
<file>icons/black/svg/close-line.svg</file> <file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/controllers-line.svg</file>
<file>icons/black/svg/controller-digital-line.svg</file> <file>icons/black/svg/controller-digital-line.svg</file>
<file>icons/black/svg/controller-line.svg</file> <file>icons/black/svg/controller-line.svg</file>
<file>icons/black/svg/controller-strike-line.svg</file> <file>icons/black/svg/controller-strike-line.svg</file>
@ -254,6 +255,7 @@
<file>icons/white/svg/chip-2-line.svg</file> <file>icons/white/svg/chip-2-line.svg</file>
<file>icons/white/svg/chip-line.svg</file> <file>icons/white/svg/chip-line.svg</file>
<file>icons/white/svg/close-line.svg</file> <file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/controllers-line.svg</file>
<file>icons/white/svg/controller-digital-line.svg</file> <file>icons/white/svg/controller-digital-line.svg</file>
<file>icons/white/svg/controller-line.svg</file> <file>icons/white/svg/controller-line.svg</file>
<file>icons/white/svg/controller-strike-line.svg</file> <file>icons/white/svg/controller-strike-line.svg</file>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="controller_-_Pcsx2" viewBox="0 0 150 150" version="1.1" sodipodi:docname="controllers-line.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview id="namedview331" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="4.2567828" inkscape:cx="37.704531" inkscape:cy="80.459825" inkscape:window-width="2191" inkscape:window-height="1129" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="controller_-_Pcsx2"/>
<defs id="defs326">
<style id="style324">.cls-1{opacity:.85;}.cls-1,.cls-2{fill:none;}.cls-2{stroke:#000;stroke-width:12px;}</style>
</defs>
<path id="tela" class="cls-1" d="M0,0H150V150H0V0Z"/>
<path id="By_Maxihplay_and_Kam" class="cls-2" d="M 106.88086 25.220703 C 103.74146 25.215063 100.07226 26.068752 96.041016 28.5625 L 93.650391 36.662109 L 50.78125 36.662109 L 48.070312 28.142578 C 48.070312 28.142578 37.421629 21.522194 25.681641 28.492188 L 21.960938 37.873047 C 21.960938 37.873047 12.850782 43.331891 14.050781 58.921875 L 6.640625 93.232422 C 6.640625 93.232422 8.0532413 102.07667 15.007812 105.49805 L 12.695312 116.20117 C 12.695312 116.20117 15.816426 135.89062 35.816406 127.89062 L 50.136719 110.51172 C 50.136719 110.51172 65.296805 121.26162 73.466797 103.93164 L 82.736328 103.93164 C 82.736328 103.93164 89.935876 119.83061 106.25586 109.64062 L 122.42578 128.92188 C 122.42578 128.92188 141.61563 135.18029 144.76562 114.57031 L 136.30664 79.560547 C 136.30664 79.560547 136.36343 70.015863 132.05664 64.060547 L 130.25195 56.591797 C 130.25195 56.591797 130.38202 40.702809 121.08203 37.132812 L 118.13086 29.402344 C 118.13086 29.402344 113.78754 25.23311 106.88086 25.220703 z "/>
<path id="By_Maxihplay_and_Kam-3" class="cls-2" d="m 20.120399,81.876162 -7.41,34.309998 c 0,0 3.12,19.69 23.12,11.69 l 14.32001,-17.38 c 0,0 15.16,10.75 23.33,-6.58 h 9.27 c 0,0 7.2,15.9 23.519991,5.71 l 16.17,19.28 c 0,0 19.19,6.26 22.34,-14.35 l -8.46,-35.009998 c 0,0 0.13,-15.89 -9.17,-19.46 l -2.95,-7.73 c 0,0 -9.19,-8.82 -22.09,-0.84 l -2.389994,8.1 H 56.850409 l -2.71,-8.52 c 0,0 -10.65001,-6.62 -22.39001,0.35 l -3.72,9.38 c 0,0 -9.11,5.46 -7.91,21.05 z" style="fill:none;stroke:#000000;stroke-width:12px"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 150 150" version="1.1" sodipodi:docname="controllers-line.svg" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview id="namedview331" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:zoom="4.2567828" inkscape:cx="37.704531" inkscape:cy="80.459825" inkscape:window-width="2191" inkscape:window-height="1129" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="controller_-_Pcsx2"/>
<defs id="defs326">
<style id="style324">.cls-1{opacity:.85;}.cls-1,.cls-2{fill:none;}.cls-2{stroke:#fff;stroke-width:12px;}</style>
</defs>
<path id="tela" class="cls-1" d="M0,0H150V150H0V0Z"/>
<path id="By_Maxihplay_and_Kam" class="cls-2" d="M 106.88086 25.220703 C 103.74146 25.215063 100.07226 26.068752 96.041016 28.5625 L 93.650391 36.662109 L 50.78125 36.662109 L 48.070312 28.142578 C 48.070312 28.142578 37.421629 21.522194 25.681641 28.492188 L 21.960938 37.873047 C 21.960938 37.873047 12.850782 43.331891 14.050781 58.921875 L 6.640625 93.232422 C 6.640625 93.232422 8.0532413 102.07667 15.007812 105.49805 L 12.695312 116.20117 C 12.695312 116.20117 15.816426 135.89062 35.816406 127.89062 L 50.136719 110.51172 C 50.136719 110.51172 65.296805 121.26162 73.466797 103.93164 L 82.736328 103.93164 C 82.736328 103.93164 89.935876 119.83061 106.25586 109.64062 L 122.42578 128.92188 C 122.42578 128.92188 141.61563 135.18029 144.76562 114.57031 L 136.30664 79.560547 C 136.30664 79.560547 136.36343 70.015863 132.05664 64.060547 L 130.25195 56.591797 C 130.25195 56.591797 130.38202 40.702809 121.08203 37.132812 L 118.13086 29.402344 C 118.13086 29.402344 113.78754 25.23311 106.88086 25.220703 z "/>
<path id="By_Maxihplay_and_Kam-3" class="cls-2" d="m 20.120399,81.876162 -7.41,34.309998 c 0,0 3.12,19.69 23.12,11.69 l 14.32001,-17.38 c 0,0 15.16,10.75 23.33,-6.58 h 9.27 c 0,0 7.2,15.9 23.519991,5.71 l 16.17,19.28 c 0,0 19.19,6.26 22.34,-14.35 l -8.46,-35.009998 c 0,0 0.13,-15.89 -9.17,-19.46 l -2.95,-7.73 c 0,0 -9.19,-8.82 -22.09,-0.84 l -2.389994,8.1 H 56.850409 l -2.71,-8.52 c 0,0 -10.65001,-6.62 -22.39001,0.35 l -3.72,9.38 c 0,0 -9.11,5.46 -7.91,21.05 z" style="fill:none;stroke:#ffffff;stroke-width:12px"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB