diff options
Diffstat (limited to 'src/yuzu/configuration')
31 files changed, 2009 insertions, 786 deletions
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1ce62e4a6..6fa842cd5 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,6 +5,7 @@ #include <array> #include <QKeySequence> #include <QSettings> +#include "common/common_paths.h" #include "common/file_util.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" @@ -14,14 +15,10 @@ namespace FS = Common::FS; -Config::Config(const std::string& config_file, bool is_global) { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file; - FS::CreateFullPath(qt_config_loc); - qt_config = - std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); - global = is_global; - Reload(); +Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { + global = config_type == ConfigType::GlobalConfig; + + Initialize(config_name); } Config::~Config() { @@ -242,84 +239,152 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ }}; // clang-format on -void Config::ReadPlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - auto& player = Settings::values.players[p]; +void Config::Initialize(const std::string& config_name) { + switch (type) { + case ConfigType::GlobalConfig: + qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), + config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + Reload(); + break; + case ConfigType::PerGameConfig: + qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + Reload(); + break; + case ConfigType::InputProfile: + qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), config_name); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + break; + } +} + +void Config::ReadPlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); + + auto& player = Settings::values.players.GetValue()[player_index]; + + if (player_prefix.isEmpty()) { + const auto controller = static_cast<Settings::ControllerType>( + qt_config + ->value(QStringLiteral("%1type").arg(player_prefix), + static_cast<u8>(Settings::ControllerType::ProController)) + .toUInt()); + if (controller == Settings::ControllerType::LeftJoycon || + controller == Settings::ControllerType::RightJoycon) { + player.controller_type = controller; + } + } else { player.connected = - ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); + ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0) + .toBool(); player.controller_type = static_cast<Settings::ControllerType>( qt_config - ->value(QStringLiteral("player_%1_type").arg(p), + ->value(QStringLiteral("%1type").arg(player_prefix), static_cast<u8>(Settings::ControllerType::ProController)) .toUInt()); + player.vibration_enabled = + qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true) + .toBool(); + + player.vibration_strength = + qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100) + .toInt(); + player.body_color_left = qt_config - ->value(QStringLiteral("player_%1_body_color_left").arg(p), + ->value(QStringLiteral("%1body_color_left").arg(player_prefix), Settings::JOYCON_BODY_NEON_BLUE) .toUInt(); - player.body_color_right = qt_config - ->value(QStringLiteral("player_%1_body_color_right").arg(p), - Settings::JOYCON_BODY_NEON_RED) - .toUInt(); - player.button_color_left = qt_config - ->value(QStringLiteral("player_%1_button_color_left").arg(p), - Settings::JOYCON_BUTTONS_NEON_BLUE) - .toUInt(); + player.body_color_right = + qt_config + ->value(QStringLiteral("%1body_color_right").arg(player_prefix), + Settings::JOYCON_BODY_NEON_RED) + .toUInt(); + player.button_color_left = + qt_config + ->value(QStringLiteral("%1button_color_left").arg(player_prefix), + Settings::JOYCON_BUTTONS_NEON_BLUE) + .toUInt(); player.button_color_right = qt_config - ->value(QStringLiteral("player_%1_button_color_right").arg(p), + ->value(QStringLiteral("%1button_color_right").arg(player_prefix), Settings::JOYCON_BUTTONS_NEON_RED) .toUInt(); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - auto& player_buttons = player.buttons[i]; - - player_buttons = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeButton::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_buttons.empty()) { - player_buttons = default_param; - } + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; + + player_buttons = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeButton::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_buttons.empty()) { + player_buttons = default_param; } + } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_motions[i]); - auto& player_motions = player.motions[i]; - - player_motions = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeMotion::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_motions.empty()) { - player_motions = default_param; - } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; + + player_analogs = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_analogs.empty()) { + player_analogs = default_param; } + } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - auto& player_analogs = player.analogs[i]; - - player_analogs = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_analogs.empty()) { - player_analogs = default_param; - } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + auto& player_vibrations = player.vibrations[i]; + + player_vibrations = + qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeVibration::mapping[i]), + QString{}) + .toString() + .toStdString(); + } + + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; } } } @@ -436,18 +501,21 @@ void Config::ReadAudioValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - ReadPlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + ReadPlayerValue(p); + } ReadDebugValues(); ReadKeyboardValues(); ReadMouseValues(); ReadTouchscreenValues(); ReadMotionTouchValues(); - Settings::values.vibration_enabled = - ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); - Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool(); - Settings::values.use_docked_mode = - ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); + ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false); + ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), + true); + ReadSettingGlobal(Settings::values.enable_accurate_vibrations, + QStringLiteral("enable_accurate_vibrations"), false); + ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true); qt_config->endGroup(); } @@ -920,49 +988,64 @@ void Config::ReadValues() { ReadSystemValues(); } -void Config::SavePlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - const auto& player = Settings::values.players[p]; +void Config::SavePlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); - WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); - WriteSetting(QStringLiteral("player_%1_type").arg(p), - static_cast<u8>(player.controller_type), - static_cast<u8>(Settings::ControllerType::ProController)); + const auto& player = Settings::values.players.GetValue()[player_index]; - WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, + WriteSetting(QStringLiteral("%1type").arg(player_prefix), + static_cast<u8>(player.controller_type), + static_cast<u8>(Settings::ControllerType::ProController)); + + if (!player_prefix.isEmpty()) { + WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false); + WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix), + player.vibration_enabled, true); + WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix), + player.vibration_strength, 100); + WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left, Settings::JOYCON_BODY_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right, - Settings::JOYCON_BODY_NEON_RED); - WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left, - Settings::JOYCON_BUTTONS_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), + WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix), + player.body_color_right, Settings::JOYCON_BODY_NEON_RED); + WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix), + player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE); + WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix), player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(player.buttons[i]), - QString::fromStdString(default_param)); - } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeMotion::mapping[i]), - QString::fromStdString(player.motions[i]), - QString::fromStdString(default_param)); - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(player.analogs[i]), - QString::fromStdString(default_param)); - } + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(player.buttons[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(player.analogs[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) { + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeVibration::mapping[i]), + QString::fromStdString(player.vibrations[i]), QString{}); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); } } @@ -1087,14 +1170,20 @@ void Config::SaveAudioValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - SavePlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { + SavePlayerValue(p); + } SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); SaveMotionTouchValues(); - WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); - WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); + WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); + WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, + true); + WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"), + Settings::values.enable_accurate_vibrations, false); + WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true); WriteSetting(QStringLiteral("motion_device"), QString::fromStdString(Settings::values.motion_device), QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); @@ -1102,7 +1191,6 @@ void Config::SaveControlValues() { QString::fromStdString(Settings::values.touch_device), QStringLiteral("engine:emu_window")); WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); - WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); qt_config->endGroup(); } @@ -1515,3 +1603,19 @@ void Config::Save() { Settings::Sanitize(); SaveValues(); } + +void Config::ReadControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + ReadPlayerValue(player_index); + qt_config->endGroup(); +} + +void Config::SaveControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + SavePlayerValue(player_index); + qt_config->endGroup(); +} + +const std::string& Config::GetConfigFilePath() const { + return qt_config_loc; +} diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 5d8e45d78..8a600e19d 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -16,12 +16,24 @@ class QSettings; class Config { public: - explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true); + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_name = "qt-config", + ConfigType config_type = ConfigType::GlobalConfig); ~Config(); void Reload(); void Save(); + void ReadControlPlayerValue(std::size_t player_index); + void SaveControlPlayerValue(std::size_t player_index); + + const std::string& GetConfigFilePath() const; + static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; @@ -33,8 +45,10 @@ public: static const std::array<UISettings::Shortcut, 16> default_hotkeys; private: + void Initialize(const std::string& config_name); + void ReadValues(); - void ReadPlayerValues(); + void ReadPlayerValue(std::size_t player_index); void ReadDebugValues(); void ReadKeyboardValues(); void ReadMouseValues(); @@ -62,7 +76,7 @@ private: void ReadWebServiceValues(); void SaveValues(); - void SavePlayerValues(); + void SavePlayerValue(std::size_t player_index); void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); @@ -111,9 +125,9 @@ private: void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, const QVariant& default_value); + ConfigType type; std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; - bool global; }; diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index fcf42cdcb..f92c3aff3 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -275,32 +275,12 @@ <signal>accepted()</signal> <receiver>ConfigureDialog</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>220</x> - <y>380</y> - </hint> - <hint type="destinationlabel"> - <x>220</x> - <y>200</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureDialog</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>220</x> - <y>380</y> - </hint> - <hint type="destinationlabel"> - <x>220</x> - <y>200</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp index 0097c9a29..a878ef9c6 100644 --- a/src/yuzu/configuration/configure_debug_controller.cpp +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -4,11 +4,14 @@ #include "ui_configure_debug_controller.h" #include "yuzu/configuration/configure_debug_controller.h" +#include "yuzu/configuration/configure_input_player.h" ConfigureDebugController::ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem) + InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles) : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), - debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) { + debug_controller( + new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) { ui->setupUi(this); ui->controllerLayout->addWidget(debug_controller); diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h index 34dcf705f..b4f53fad5 100644 --- a/src/yuzu/configuration/configure_debug_controller.h +++ b/src/yuzu/configuration/configure_debug_controller.h @@ -6,10 +6,13 @@ #include <memory> #include <QDialog> -#include "yuzu/configuration/configure_input_player.h" class QPushButton; +class ConfigureInputPlayer; + +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -22,8 +25,8 @@ class ConfigureDebugController : public QDialog { Q_OBJECT public: - explicit ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem); + explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles); ~ConfigureDebugController() override; void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui index a95ed50ff..7b7e6582c 100644 --- a/src/yuzu/configuration/configure_debug_controller.ui +++ b/src/yuzu/configuration/configure_debug_controller.ui @@ -66,32 +66,12 @@ <signal>accepted()</signal> <receiver>ConfigureDebugController</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureDebugController</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 2725fcb2b..d9009091b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -23,6 +23,8 @@ #include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" namespace { template <typename Dialog, typename... Args> @@ -64,7 +66,8 @@ void OnDockedModeChanged(bool last_state, bool new_state) { } ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), + profiles(std::make_unique<InputProfiles>()) { ui->setupUi(this); } @@ -73,14 +76,22 @@ ConfigureInput::~ConfigureInput() = default; void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { player_controllers = { - new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem), + new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, + profiles.get()), }; player_tabs = { @@ -113,8 +124,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, } } }); - connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, - [this] { UpdateAllInputDevices(); }); + connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, + &ConfigureInput::UpdateAllInputDevices); + connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, + &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); @@ -134,7 +147,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); ui->tabAdvanced->layout()->addWidget(advanced); connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { - CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem); + CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get()); }); connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem); @@ -146,6 +159,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); }); + connect(ui->vibrationButton, &QPushButton::clicked, + [this] { CallConfigureDialog<ConfigureVibration>(*this); }); + connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] { CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); }); @@ -171,12 +187,12 @@ void ConfigureInput::ApplyConfiguration() { advanced->ApplyConfiguration(); - const bool pre_docked_mode = Settings::values.use_docked_mode; - Settings::values.use_docked_mode = ui->radioDocked->isChecked(); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); + Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue()); - Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); - Settings::values.motion_enabled = ui->motionGroup->isChecked(); + Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); + Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); } void ConfigureInput::changeEvent(QEvent* event) { @@ -193,16 +209,16 @@ void ConfigureInput::RetranslateUI() { void ConfigureInput::LoadConfiguration() { LoadPlayerControllerIndices(); - UpdateDockedState(Settings::values.players[8].connected); + UpdateDockedState(Settings::values.players.GetValue()[8].connected); - ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); - ui->motionGroup->setChecked(Settings::values.motion_enabled); + ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue()); + ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue()); } void ConfigureInput::LoadPlayerControllerIndices() { for (std::size_t i = 0; i < player_connected.size(); ++i) { - const auto connected = Settings::values.players[i].connected || - (i == 0 && Settings::values.players[8].connected); + const auto connected = Settings::values.players.GetValue()[i].connected || + (i == 0 && Settings::values.players.GetValue()[8].connected); player_connected[i]->setChecked(connected); } } @@ -231,8 +247,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) { ui->radioDocked->setEnabled(!is_handheld); ui->radioUndocked->setEnabled(!is_handheld); - ui->radioDocked->setChecked(Settings::values.use_docked_mode); - ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); + ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); + ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); // Also force into undocked mode if the controller type is handheld. if (is_handheld) { @@ -242,6 +258,16 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) { void ConfigureInput::UpdateAllInputDevices() { for (const auto& player : player_controllers) { - player->UpdateInputDevices(); + player->UpdateInputDeviceCombobox(); + } +} + +void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) { + for (std::size_t i = 0; i < player_controllers.size(); ++i) { + if (i == player_index) { + continue; + } + + player_controllers[i]->UpdateInputProfiles(); } } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 0e8b2fd4e..f4eb0d78b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -8,17 +8,18 @@ #include <memory> #include <QKeyEvent> +#include <QList> #include <QWidget> -#include "yuzu/configuration/configure_input_advanced.h" -#include "yuzu/configuration/configure_input_player.h" - -#include "ui_configure_input.h" - class QCheckBox; class QString; class QTimer; +class ConfigureInputAdvanced; +class ConfigureInputPlayer; + +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -51,6 +52,7 @@ private: void UpdateDockedState(bool is_handheld); void UpdateAllInputDevices(); + void UpdateAllInputProfiles(std::size_t player_index); /// Load configuration settings. void LoadConfiguration(); @@ -61,6 +63,8 @@ private: std::unique_ptr<Ui::ConfigureInput> ui; + std::unique_ptr<InputProfiles> profiles; + std::array<ConfigureInputPlayer*, 8> player_controllers; std::array<QWidget*, 8> player_tabs; std::array<QCheckBox*, 8> player_connected; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 136955224..2707025e7 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>700</width> + <width>680</width> <height>540</height> </rect> </property> @@ -142,7 +142,7 @@ <number>6</number> </property> <property name="leftMargin"> - <number>3</number> + <number>8</number> </property> <property name="topMargin"> <number>6</number> @@ -195,30 +195,24 @@ <number>3</number> </property> <item> - <widget class="QSpinBox" name="vibrationSpin"> + <widget class="QPushButton" name="vibrationButton"> <property name="minimumSize"> <size> - <width>65</width> - <height>21</height> + <width>68</width> + <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>65</width> + <width>68</width> <height>16777215</height> </size> </property> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>200</number> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> </property> - <property name="value"> - <number>100</number> + <property name="text"> + <string>Configure</string> </property> </widget> </item> @@ -250,18 +244,18 @@ <widget class="QPushButton" name="motionButton"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Configure</string> @@ -272,7 +266,7 @@ </widget> </item> <item alignment="Qt::AlignVCenter"> - <widget class="QWidget" name="widget" native="true"> + <widget class="QWidget" name="connectedControllers" native="true"> <layout class="QGridLayout" name="gridLayout_2"> <property name="leftMargin"> <number>5</number> @@ -468,13 +462,13 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -494,7 +488,7 @@ <enum>Qt::LeftToRight</enum> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Defaults</string> @@ -511,13 +505,13 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -537,7 +531,7 @@ <enum>Qt::LeftToRight</enum> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Clear</string> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 81f9dc16c..abaf03630 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -68,8 +68,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) { connect(color_buttons[button_idx], &QPushButton::clicked, this, [this, player_idx, button_idx] { - OnControllerButtonClick(static_cast<int>(player_idx), - static_cast<int>(button_idx)); + OnControllerButtonClick(player_idx, button_idx); }); } } @@ -94,20 +93,21 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) ConfigureInputAdvanced::~ConfigureInputAdvanced() = default; -void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) { +void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx, + std::size_t button_idx) { const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]); if (!new_bg_color.isValid()) { return; } controllers_colors[player_idx][button_idx] = new_bg_color; controllers_color_buttons[player_idx][button_idx]->setStyleSheet( - QStringLiteral("background-color: %1; min-width: 55px;") + QStringLiteral("background-color: %1; min-width: 60px;") .arg(controllers_colors[player_idx][button_idx].name())); } void ConfigureInputAdvanced::ApplyConfiguration() { for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { - auto& player = Settings::values.players[player_idx]; + auto& player = Settings::values.players.GetValue()[player_idx]; std::array<u32, 4> colors{}; std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(), colors.begin(), [](QColor color) { return color.rgb(); }); @@ -126,7 +126,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { void ConfigureInputAdvanced::LoadConfiguration() { for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) { - auto& player = Settings::values.players[player_idx]; + auto& player = Settings::values.players.GetValue()[player_idx]; std::array<u32, 4> colors = { player.body_color_left, player.button_color_left, @@ -139,7 +139,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) { controllers_color_buttons[player_idx][button_idx]->setStyleSheet( - QStringLiteral("background-color: %1; min-width: 55px;") + QStringLiteral("background-color: %1; min-width: 60px;") .arg(controllers_colors[player_idx][button_idx].name())); } } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 50bb87768..3083d55c1 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -35,7 +35,7 @@ private: void RetranslateUI(); void UpdateUIEnabled(); - void OnControllerButtonClick(int player_idx, int button_idx); + void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx); void LoadConfiguration(); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 5958435fc..a880a7c68 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -192,18 +192,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -247,18 +247,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -323,18 +323,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -378,18 +378,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -478,18 +478,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -533,18 +533,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -609,18 +609,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -664,18 +664,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -782,18 +782,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -837,18 +837,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -913,18 +913,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -968,18 +968,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1068,18 +1068,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1123,18 +1123,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1199,18 +1199,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1254,18 +1254,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1393,18 +1393,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1448,18 +1448,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1524,18 +1524,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1579,18 +1579,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1679,18 +1679,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1734,18 +1734,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1810,18 +1810,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1865,18 +1865,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -1983,18 +1983,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2038,18 +2038,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2114,18 +2114,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2169,18 +2169,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2269,18 +2269,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2324,18 +2324,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2400,18 +2400,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> @@ -2455,18 +2455,18 @@ </property> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string/> diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp deleted file mode 100644 index 1866003c2..000000000 --- a/src/yuzu/configuration/configure_input_dialog.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "ui_configure_input_dialog.h" -#include "yuzu/configuration/configure_input_dialog.h" - -ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players, - InputCommon::InputSubsystem* input_subsystem) - : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()), - input_widget(new ConfigureInput(this)) { - ui->setupUi(this); - - input_widget->Initialize(input_subsystem, max_players); - - ui->inputLayout->addWidget(input_widget); - - RetranslateUI(); -} - -ConfigureInputDialog::~ConfigureInputDialog() = default; - -void ConfigureInputDialog::ApplyConfiguration() { - input_widget->ApplyConfiguration(); -} - -void ConfigureInputDialog::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) { - RetranslateUI(); - } - - QDialog::changeEvent(event); -} - -void ConfigureInputDialog::RetranslateUI() { - ui->retranslateUi(this); -} diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h deleted file mode 100644 index d1bd865f9..000000000 --- a/src/yuzu/configuration/configure_input_dialog.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <QDialog> -#include "yuzu/configuration/configure_input.h" - -class QPushButton; - -namespace InputCommon { -class InputSubsystem; -} - -namespace Ui { -class ConfigureInputDialog; -} - -class ConfigureInputDialog : public QDialog { - Q_OBJECT - -public: - explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players, - InputCommon::InputSubsystem* input_subsystem); - ~ConfigureInputDialog() override; - - void ApplyConfiguration(); - -private: - void changeEvent(QEvent* event) override; - void RetranslateUI(); - - std::unique_ptr<Ui::ConfigureInputDialog> ui; - - ConfigureInput* input_widget; -}; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index f58ca29d7..72640f5e7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <memory> +#include <thread> #include <utility> #include <QGridLayout> #include <QInputDialog> @@ -22,8 +23,9 @@ #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" - -constexpr std::size_t HANDHELD_INDEX = 8; +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h" const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> ConfigureInputPlayer::analog_sub_buttons{{ @@ -35,6 +37,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> namespace { +constexpr std::size_t HANDHELD_INDEX = 8; + void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { Core::System& system{Core::System::GetInstance()}; @@ -240,10 +244,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug) + InputProfiles* profiles_, bool debug) : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), - debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()), - poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) { + debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()), + bottom_row(bottom_row) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -366,6 +371,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } connect(analog_button, &QPushButton::clicked, [=, this] { + if (!map_analog_stick_accepted) { + map_analog_stick_accepted = + QMessageBox::information( + this, tr("Map Analog Stick"), + tr("After pressing OK, first move your joystick horizontally, and then " + "vertically.\nTo invert the axes, first move your joystick " + "vertically, and then horizontally."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok; + if (!map_analog_stick_accepted) { + return; + } + } HandleClick( analog_map_buttons[analog_id][sub_button_id], [=, this](const Common::ParamPackage& params) { @@ -455,11 +472,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } + if (debug || player_index == 9) { + ui->groupConnectedController->setCheckable(false); + } + // The Debug Controller can only choose the Pro Controller. if (debug) { ui->buttonScreenshot->setEnabled(false); ui->buttonHome->setEnabled(false); - ui->groupConnectedController->setCheckable(false); QStringList debug_controller_types = { tr("Pro Controller"), }; @@ -477,11 +497,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i UpdateMotionButtons(); }); - connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, + connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this, &ConfigureInputPlayer::UpdateMappingWithDefaults); + ui->comboDevices->setCurrentIndex(-1); + ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); - UpdateInputDevices(); connect(ui->buttonRefreshDevices, &QPushButton::clicked, [this] { emit RefreshInputDevices(); }); @@ -492,14 +513,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i Common::ParamPackage params; if (input_subsystem->GetGCButtons()->IsPolling()) { params = input_subsystem->GetGCButtons()->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } if (input_subsystem->GetGCAnalogs()->IsPolling()) { params = input_subsystem->GetGCAnalogs()->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } @@ -513,13 +534,24 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } for (auto& poller : device_pollers) { params = poller->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } }); + UpdateInputProfiles(); + + connect(ui->buttonProfilesNew, &QPushButton::clicked, this, + &ConfigureInputPlayer::CreateProfile); + connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, + &ConfigureInputPlayer::DeleteProfile); + connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this, + &ConfigureInputPlayer::LoadProfile); + connect(ui->buttonProfilesSave, &QPushButton::clicked, this, + &ConfigureInputPlayer::SaveProfile); + LoadConfiguration(); // TODO(wwylele): enable this when we actually emulate it @@ -529,7 +561,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i ConfigureInputPlayer::~ConfigureInputPlayer() = default; void ConfigureInputPlayer::ApplyConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs; @@ -543,33 +575,58 @@ void ConfigureInputPlayer::ApplyConfiguration() { } auto& motions = player.motions; + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); - player.controller_type = - static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); - player.connected = ui->groupConnectedController->isChecked(); + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; - // Player 2-8 - if (player_index != 0) { - UpdateController(player.controller_type, player_index, player.connected); + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); return; } - // Player 1 and Handheld - auto& handheld = Settings::values.players[HANDHELD_INDEX]; - // If Handheld is selected, copy all the settings from Player 1 to Handheld. - if (player.controller_type == Settings::ControllerType::Handheld) { - handheld = player; - handheld.connected = ui->groupConnectedController->isChecked(); - player.connected = false; // Disconnect Player 1 - } else { - player.connected = ui->groupConnectedController->isChecked(); - handheld.connected = false; // Disconnect Handheld + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); + + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + // Handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + if (controller_type == Settings::ControllerType::Handheld) { + handheld = player; + } + handheld.connected = ui->groupConnectedController->isChecked() && + controller_type == Settings::ControllerType::Handheld; + UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); + } + + if (!player.connected) { + return; } - UpdateController(player.controller_type, player_index, player.connected); - UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); + // This emulates a delay between disconnecting and reconnecting controllers as some games + // do not respond to a change in controller type if it was instantaneous. + using namespace std::chrono_literals; + std::this_thread::sleep_for(20ms); + + UpdateController(controller_type, player_index, player_connected); +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { + if (bottom_row == nullptr) { + return; + } + QWidget::showEvent(event); + ui->main->addWidget(bottom_row); } void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -586,7 +643,7 @@ void ConfigureInputPlayer::RetranslateUI() { } void ConfigureInputPlayer::LoadConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; if (debug) { std::transform(Settings::values.debug_pad_buttons.begin(), Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -604,6 +661,7 @@ void ConfigureInputPlayer::LoadConfiguration() { } UpdateUI(); + UpdateInputDeviceCombobox(); if (debug) { return; @@ -612,44 +670,75 @@ void ConfigureInputPlayer::LoadConfiguration() { ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); ui->groupConnectedController->setChecked( player.connected || - (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected)); + (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected)); } -void ConfigureInputPlayer::UpdateInputDevices() { - input_devices = input_subsystem->GetInputDevices(); - ui->comboDevices->clear(); - for (auto device : input_devices) { - ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); - } +void ConfigureInputPlayer::ConnectPlayer(bool connected) { + ui->groupConnectedController->setChecked(connected); } -void ConfigureInputPlayer::RestoreDefaults() { - // Reset Buttons - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +void ConfigureInputPlayer::UpdateInputDeviceCombobox() { + // Skip input device persistence if "Input Devices" is set to "Any". + if (ui->comboDevices->currentIndex() == 0) { + UpdateInputDevices(); + return; } - // Reset Analogs and Modifier Buttons - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); - } + // Find the first button that isn't empty. + const auto button_param = + std::find_if(buttons_param.begin(), buttons_param.end(), + [](const Common::ParamPackage param) { return param.Has("engine"); }); + const bool buttons_empty = button_param == buttons_param.end(); - analogs_param[analog_id].Set( - "modifier", InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id])); + const auto current_engine = button_param->Get("engine", ""); + const auto current_guid = button_param->Get("guid", ""); + const auto current_port = button_param->Get("port", ""); + + const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse"; + + UpdateInputDevices(); + + if (buttons_empty) { + return; } - for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { - motions_param[motion_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + const bool all_one_device = + std::all_of(buttons_param.begin(), buttons_param.end(), + [current_engine, current_guid, current_port, + is_keyboard_mouse](const Common::ParamPackage param) { + if (is_keyboard_mouse) { + return !param.Has("engine") || param.Get("engine", "") == "keyboard" || + param.Get("engine", "") == "mouse"; + } + return !param.Has("engine") || (param.Get("engine", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port); + }); + + if (all_one_device) { + if (is_keyboard_mouse) { + ui->comboDevices->setCurrentIndex(1); + return; + } + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [current_engine, current_guid, current_port](const Common::ParamPackage param) { + return param.Get("class", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast<int>(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + } else { + ui->comboDevices->setCurrentIndex(0); } +} - UpdateUI(); - UpdateInputDevices(); - ui->comboControllerType->setCurrentIndex(0); +void ConfigureInputPlayer::RestoreDefaults() { + UpdateMappingWithDefaults(); } void ConfigureInputPlayer::ClearAll() { @@ -752,10 +841,167 @@ void ConfigureInputPlayer::UpdateUI() { } } +void ConfigureInputPlayer::UpdateInputDevices() { + input_devices = input_subsystem->GetInputDevices(); + ui->comboDevices->clear(); + for (auto device : input_devices) { + ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); + } +} + +void ConfigureInputPlayer::UpdateControllerIcon() { + // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its + // "nonstandard" to use an image through the icon support) + const QString stylesheet = [this] { + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + return QStringLiteral("image: url(:/controller/pro_controller%0)"); + case Settings::ControllerType::DualJoyconDetached: + return QStringLiteral("image: url(:/controller/dual_joycon%0)"); + case Settings::ControllerType::LeftJoycon: + return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)"); + case Settings::ControllerType::RightJoycon: + return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)"); + case Settings::ControllerType::Handheld: + return QStringLiteral("image: url(:/controller/handheld%0)"); + default: + return QString{}; + } + }(); + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark"))) { + return QStringLiteral("_dark"); + } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_midnight"); + } else { + return QString{}; + } + }(); + + ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); +} + +void ConfigureInputPlayer::UpdateControllerAvailableButtons() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + // List of all the widgets that will be hidden by any of the following layouts that need + // "unhidden" after the controller type changes + const std::array<QWidget*, 9> layout_show = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget, + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonShoulderButtonsLeft, + ui->buttonMiscButtonsMinusScreenshot, + ui->bottomLeft, + ui->buttonShoulderButtonsRight, + ui->buttonMiscButtonsPlusHome, + ui->bottomRight, + }; + + for (auto* widget : layout_show) { + widget->show(); + } + + std::vector<QWidget*> layout_hidden; + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + layout_hidden = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget2, + }; + break; + case Settings::ControllerType::LeftJoycon: + layout_hidden = { + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonShoulderButtonsRight, + ui->buttonMiscButtonsPlusHome, + ui->bottomRight, + }; + break; + case Settings::ControllerType::RightJoycon: + layout_hidden = { + ui->horizontalSpacerShoulderButtonsWidget, + ui->buttonShoulderButtonsLeft, + ui->buttonMiscButtonsMinusScreenshot, + ui->bottomLeft, + }; + break; + } + + for (auto* widget : layout_hidden) { + widget->hide(); + } +} + +void ConfigureInputPlayer::UpdateMotionButtons() { + if (debug) { + // Motion isn't used with the debug controller, hide both groupboxes. + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); + return; + } + + // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::Handheld: + // Show "Motion 1" and hide "Motion 2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::RightJoycon: + // Show "Motion 2" and hide "Motion 1". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->show(); + break; + case Settings::ControllerType::DualJoyconDetached: + default: + // Show both "Motion 1/2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->show(); + break; + } +} + void ConfigureInputPlayer::UpdateMappingWithDefaults() { - if (ui->comboDevices->currentIndex() < 2) { + if (ui->comboDevices->currentIndex() == 0) { return; } + + if (ui->comboDevices->currentIndex() == 1) { + // Reset keyboard bindings + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + + analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); + } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motions_param[motion_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + } + + UpdateUI(); + return; + } + + // Reset controller bindings const auto& device = input_devices[ui->comboDevices->currentIndex()]; auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); @@ -828,9 +1074,27 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, } UpdateUI(); + UpdateInputDeviceCombobox(); + input_setter = std::nullopt; } +bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const { + if (ui->comboDevices->currentIndex() == 0) { + return true; + } + + // Keyboard/Mouse + if (ui->comboDevices->currentIndex() == 1) { + return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse"; + } + + const auto current_input_device = input_devices[ui->comboDevices->currentIndex()]; + return params.Get("engine", "") == current_input_device.Get("class", "") && + params.Get("guid", "") == current_input_device.Get("guid", "") && + params.Get("port", "") == current_input_device.Get("port", ""); +} + void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { if (!input_setter || !event) { return; @@ -865,135 +1129,101 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { SetPollingResult({}, true); } -void ConfigureInputPlayer::UpdateControllerIcon() { - // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its - // "nonstandard" to use an image through the icon support) - const QString stylesheet = [this] { - switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { - case Settings::ControllerType::ProController: - return QStringLiteral("image: url(:/controller/pro_controller%0)"); - case Settings::ControllerType::DualJoyconDetached: - return QStringLiteral("image: url(:/controller/dual_joycon%0)"); - case Settings::ControllerType::LeftJoycon: - return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)"); - case Settings::ControllerType::RightJoycon: - return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)"); - case Settings::ControllerType::Handheld: - return QStringLiteral("image: url(:/controller/handheld%0)"); - default: - return QString{}; - } - }(); - - const QString theme = [this] { - if (QIcon::themeName().contains(QStringLiteral("dark"))) { - return QStringLiteral("_dark"); - } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { - return QStringLiteral("_midnight"); - } else { - return QString{}; - } - }(); +void ConfigureInputPlayer::CreateProfile() { + const auto profile_name = + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); - ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); -} + if (profile_name.isEmpty()) { + return; + } -void ConfigureInputPlayer::UpdateControllerAvailableButtons() { - auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); - if (debug) { - layout = Settings::ControllerType::ProController; + if (!profiles->IsProfileNameValid(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("The given profile name is not valid!")); + return; } - // List of all the widgets that will be hidden by any of the following layouts that need - // "unhidden" after the controller type changes - const std::array<QWidget*, 9> layout_show = { - ui->buttonShoulderButtonsSLSR, - ui->horizontalSpacerShoulderButtonsWidget, - ui->horizontalSpacerShoulderButtonsWidget2, - ui->buttonShoulderButtonsLeft, - ui->buttonMiscButtonsMinusScreenshot, - ui->bottomLeft, - ui->buttonShoulderButtonsRight, - ui->buttonMiscButtonsPlusHome, - ui->bottomRight, - }; + ApplyConfiguration(); - for (auto* widget : layout_show) { - widget->show(); + if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("Failed to create the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; } - std::vector<QWidget*> layout_hidden; - switch (layout) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::DualJoyconDetached: - case Settings::ControllerType::Handheld: - layout_hidden = { - ui->buttonShoulderButtonsSLSR, - ui->horizontalSpacerShoulderButtonsWidget2, - }; - break; - case Settings::ControllerType::LeftJoycon: - layout_hidden = { - ui->horizontalSpacerShoulderButtonsWidget2, - ui->buttonShoulderButtonsRight, - ui->buttonMiscButtonsPlusHome, - ui->bottomRight, - }; - break; - case Settings::ControllerType::RightJoycon: - layout_hidden = { - ui->horizontalSpacerShoulderButtonsWidget, - ui->buttonShoulderButtonsLeft, - ui->buttonMiscButtonsMinusScreenshot, - ui->bottomLeft, - }; - break; + emit RefreshInputProfiles(player_index); + + ui->comboProfiles->addItem(profile_name); + ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1); +} + +void ConfigureInputPlayer::DeleteProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; } - for (auto* widget : layout_hidden) { - widget->hide(); + if (!profiles->DeleteProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Delete Input Profile"), + tr("Failed to delete the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; } + + emit RefreshInputProfiles(player_index); + + ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex()); + ui->comboProfiles->setCurrentIndex(-1); } -void ConfigureInputPlayer::UpdateMotionButtons() { - if (debug) { - // Motion isn't used with the debug controller, hide both groupboxes. - ui->buttonMotionLeftGroup->hide(); - ui->buttonMotionRightGroup->hide(); +void ConfigureInputPlayer::LoadProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { return; } - // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. - switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { - case Settings::ControllerType::ProController: - case Settings::ControllerType::LeftJoycon: - case Settings::ControllerType::Handheld: - // Show "Motion 1" and hide "Motion 2". - ui->buttonMotionLeftGroup->show(); - ui->buttonMotionRightGroup->hide(); - break; - case Settings::ControllerType::RightJoycon: - // Show "Motion 2" and hide "Motion 1". - ui->buttonMotionLeftGroup->hide(); - ui->buttonMotionRightGroup->show(); - break; - case Settings::ControllerType::DualJoyconDetached: - default: - // Show both "Motion 1/2". - ui->buttonMotionLeftGroup->show(); - ui->buttonMotionRightGroup->show(); - break; + ApplyConfiguration(); + + if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Load Input Profile"), + tr("Failed to load the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; } + + LoadConfiguration(); } -void ConfigureInputPlayer::showEvent(QShowEvent* event) { - if (bottom_row == nullptr) { +void ConfigureInputPlayer::SaveProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Save Input Profile"), + tr("Failed to save the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); return; } - QWidget::showEvent(event); - ui->main->addWidget(bottom_row); } -void ConfigureInputPlayer::ConnectPlayer(bool connected) { - ui->groupConnectedController->setChecked(connected); +void ConfigureInputPlayer::UpdateInputProfiles() { + ui->comboProfiles->clear(); + + for (const auto& profile_name : profiles->GetInputProfileNames()) { + ui->comboProfiles->addItem(QString::fromStdString(profile_name)); + } + + ui->comboProfiles->setCurrentIndex(-1); } diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index c19aefffa..23cf6f958 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -26,6 +26,8 @@ class QString; class QTimer; class QWidget; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -45,14 +47,20 @@ class ConfigureInputPlayer : public QWidget { public: explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug = false); + InputProfiles* profiles_, bool debug = false); ~ConfigureInputPlayer() override; /// Save all button configurations to settings file. void ApplyConfiguration(); + /// Set the connection state checkbox (used to sync state). + void ConnectPlayer(bool connected); + /// Update the input devices combobox. - void UpdateInputDevices(); + void UpdateInputDeviceCombobox(); + + /// Updates the list of controller profiles. + void UpdateInputProfiles(); /// Restore all buttons to their default values. void RestoreDefaults(); @@ -60,9 +68,6 @@ public: /// Clear all input configuration. void ClearAll(); - /// Set the connection state checkbox (used to sync state). - void ConnectPlayer(bool connected); - signals: /// Emitted when this controller is connected by the user. void Connected(bool connected); @@ -70,6 +75,12 @@ signals: void HandheldStateChanged(bool is_handheld); /// Emitted when the input devices combobox is being refreshed. void RefreshInputDevices(); + /** + * Emitted when the input profiles combobox is being refreshed. + * The player_index represents the current player's index, and the profile combobox + * will not be updated for this index as they are already updated by other mechanisms. + */ + void RefreshInputProfiles(std::size_t player_index); protected: void showEvent(QShowEvent* event) override; @@ -89,6 +100,9 @@ private: /// Finish polling and configure input using the input_setter. void SetPollingResult(const Common::ParamPackage& params, bool abort); + /// Checks whether a given input can be accepted. + bool IsInputAcceptable(const Common::ParamPackage& params) const; + /// Handle mouse button press events. void mousePressEvent(QMouseEvent* event) override; @@ -98,8 +112,8 @@ private: /// Update UI to reflect current configuration. void UpdateUI(); - /// Update the controller selection combobox - void UpdateControllerCombobox(); + /// Update the available input devices. + void UpdateInputDevices(); /// Update the current controller icon. void UpdateControllerIcon(); @@ -113,6 +127,18 @@ private: /// Gets the default controller mapping for this device and auto configures the input to match. void UpdateMappingWithDefaults(); + /// Creates a controller profile. + void CreateProfile(); + + /// Deletes the selected controller profile. + void DeleteProfile(); + + /// Loads the selected controller profile. + void LoadProfile(); + + /// Saves the current controller configuration into a selected controller profile. + void SaveProfile(); + std::unique_ptr<Ui::ConfigureInputPlayer> ui; std::size_t player_index; @@ -120,6 +146,8 @@ private: InputCommon::InputSubsystem* input_subsystem; + InputProfiles* profiles; + std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> poll_timer; @@ -159,12 +187,15 @@ private: std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. + bool map_analog_stick_accepted{}; + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. - bool want_keyboard_mouse = false; + bool want_keyboard_mouse{}; /// List of physical devices users can map with. If a SDL backed device is selected, then you - /// can usue this device to get a default mapping. + /// can use this device to get a default mapping. std::vector<Common::ParamPackage> input_devices; /// Bottom row is where console wide settings are held, and its "owned" by the parent diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index e03461d9d..1e78b4c10 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -83,6 +83,12 @@ </property> <item> <widget class="QComboBox" name="comboControllerType"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> <item> <property name="text"> <string>Pro Controller</string> @@ -136,6 +142,12 @@ </property> <item> <widget class="QComboBox" name="comboDevices"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> <item> <property name="text"> <string>Any</string> @@ -152,14 +164,14 @@ <widget class="QPushButton" name="buttonRefreshDevices"> <property name="minimumSize"> <size> - <width>24</width> - <height>22</height> + <width>21</width> + <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>24</width> - <height>22</height> + <width>21</width> + <height>21</height> </size> </property> <property name="styleSheet"> @@ -198,18 +210,25 @@ <number>5</number> </property> <item> - <widget class="QComboBox" name="comboProfiles"/> + <widget class="QComboBox" name="comboProfiles"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>21</height> + </size> + </property> + </widget> </item> <item> <widget class="QPushButton" name="buttonProfilesSave"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Save</string> @@ -220,12 +239,12 @@ <widget class="QPushButton" name="buttonProfilesNew"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>New</string> @@ -236,12 +255,12 @@ <widget class="QPushButton" name="buttonProfilesDelete"> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Delete</string> @@ -393,18 +412,18 @@ <widget class="QPushButton" name="buttonLStickUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -463,18 +482,18 @@ <widget class="QPushButton" name="buttonLStickLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -512,18 +531,18 @@ <widget class="QPushButton" name="buttonLStickRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -594,18 +613,18 @@ <widget class="QPushButton" name="buttonLStickDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -664,18 +683,18 @@ <widget class="QPushButton" name="buttonLStick"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Pressed</string> @@ -713,18 +732,18 @@ <widget class="QPushButton" name="buttonLStickMod"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Modifier</string> @@ -759,13 +778,13 @@ <widget class="QSpinBox" name="spinboxLStickRange"> <property name="minimumSize"> <size> - <width>55</width> + <width>68</width> <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -966,18 +985,18 @@ <widget class="QPushButton" name="buttonDpadUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -1036,18 +1055,18 @@ <widget class="QPushButton" name="buttonDpadLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -1085,18 +1104,18 @@ <widget class="QPushButton" name="buttonDpadRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -1167,18 +1186,18 @@ <widget class="QPushButton" name="buttonDpadDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -1292,18 +1311,18 @@ <widget class="QPushButton" name="buttonL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>L</string> @@ -1341,18 +1360,18 @@ <widget class="QPushButton" name="buttonZL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>ZL</string> @@ -1445,18 +1464,18 @@ <widget class="QPushButton" name="buttonMinus"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Minus</string> @@ -1494,18 +1513,18 @@ <widget class="QPushButton" name="buttonScreenshot"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Capture</string> @@ -1564,18 +1583,18 @@ <widget class="QPushButton" name="buttonPlus"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Plus</string> @@ -1613,18 +1632,18 @@ <widget class="QPushButton" name="buttonHome"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Home</string> @@ -1717,18 +1736,18 @@ <widget class="QPushButton" name="buttonR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>R</string> @@ -1766,18 +1785,18 @@ <widget class="QPushButton" name="buttonZR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>ZR</string> @@ -1870,18 +1889,18 @@ <widget class="QPushButton" name="buttonSL"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>SL</string> @@ -1919,18 +1938,18 @@ <widget class="QPushButton" name="buttonSR"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>SR</string> @@ -2027,18 +2046,18 @@ <widget class="QPushButton" name="buttonMotionLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -2076,18 +2095,18 @@ <widget class="QPushButton" name="buttonMotionRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -2225,18 +2244,18 @@ <widget class="QPushButton" name="buttonX"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>X</string> @@ -2295,18 +2314,18 @@ <widget class="QPushButton" name="buttonY"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Y</string> @@ -2344,18 +2363,18 @@ <widget class="QPushButton" name="buttonA"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>A</string> @@ -2426,18 +2445,18 @@ <widget class="QPushButton" name="buttonB"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>B</string> @@ -2580,18 +2599,18 @@ <widget class="QPushButton" name="buttonRStickUp"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Up</string> @@ -2650,18 +2669,18 @@ <widget class="QPushButton" name="buttonRStickLeft"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Left</string> @@ -2699,18 +2718,18 @@ <widget class="QPushButton" name="buttonRStickRight"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Right</string> @@ -2781,18 +2800,18 @@ <widget class="QPushButton" name="buttonRStickDown"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Down</string> @@ -2851,18 +2870,18 @@ <widget class="QPushButton" name="buttonRStick"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Pressed</string> @@ -2900,18 +2919,18 @@ <widget class="QPushButton" name="buttonRStickMod"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> <property name="styleSheet"> - <string notr="true">min-width: 55px;</string> + <string notr="true">min-width: 68px;</string> </property> <property name="text"> <string>Modifier</string> @@ -2946,13 +2965,13 @@ <widget class="QSpinBox" name="spinboxRStickRange"> <property name="minimumSize"> <size> - <width>55</width> + <width>68</width> <height>21</height> </size> </property> <property name="maximumSize"> <size> - <width>55</width> + <width>68</width> <height>16777215</height> </size> </property> diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp new file mode 100644 index 000000000..1f5cfa75b --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "ui_configure_input_profile_dialog.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_profile_dialog.h" + +ConfigureInputProfileDialog::ConfigureInputProfileDialog( + QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()), + profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) { + ui->setupUi(this); + + ui->controllerLayout->addWidget(profile_widget); + + connect(ui->clear_all_button, &QPushButton::clicked, this, + [this] { profile_widget->ClearAll(); }); + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + [this] { profile_widget->RestoreDefaults(); }); + + RetranslateUI(); +} + +ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default; + +void ConfigureInputProfileDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureInputProfileDialog::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h new file mode 100644 index 000000000..e6386bdbb --- /dev/null +++ b/src/yuzu/configuration/configure_input_profile_dialog.h @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> + +class QPushButton; + +class ConfigureInputPlayer; + +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureInputProfileDialog; +} + +class ConfigureInputProfileDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputProfileDialog(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles); + ~ConfigureInputProfileDialog() override; + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureInputProfileDialog> ui; + + ConfigureInputPlayer* profile_widget; +}; diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui index b92ddb200..726cf6905 100644 --- a/src/yuzu/configuration/configure_input_dialog.ui +++ b/src/yuzu/configuration/configure_input_profile_dialog.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>ConfigureInputDialog</class> - <widget class="QDialog" name="ConfigureInputDialog"> + <class>ConfigureInputProfileDialog</class> + <widget class="QDialog" name="ConfigureInputProfileDialog"> <property name="geometry"> <rect> <x>0</x> @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>Configure Input</string> + <string>Create Input Profile</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> @@ -30,11 +30,25 @@ <number>9</number> </property> <item> - <layout class="QHBoxLayout" name="inputLayout"/> + <layout class="QHBoxLayout" name="controllerLayout"/> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> + <widget class="QPushButton" name="clear_all_button"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Defaults</string> + </property> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="standardButtons"> <set>QDialogButtonBox::Ok</set> @@ -50,7 +64,7 @@ <connection> <sender>buttonBox</sender> <signal>accepted()</signal> - <receiver>ConfigureInputDialog</receiver> + <receiver>ConfigureInputProfileDialog</receiver> <slot>accept()</slot> </connection> </connections> diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui index 602cf8cd8..5b78c5a4b 100644 --- a/src/yuzu/configuration/configure_motion_touch.ui +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -312,16 +312,6 @@ <signal>accepted()</signal> <receiver>ConfigureMotionTouch</receiver> <slot>ApplyConfiguration()</slot> - <hints> - <hint type="sourcelabel"> - <x>220</x> - <y>380</y> - </hint> - <hint type="destinationlabel"> - <x>220</x> - <y>200</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui index 74552fdbd..5b99e1c37 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -15,7 +15,7 @@ </property> <property name="styleSheet"> <string notr="true">QPushButton { - min-width: 55px; + min-width: 60px; }</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -42,13 +42,13 @@ <widget class="QPushButton" name="forward_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -82,7 +82,7 @@ <widget class="QPushButton" name="back_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> @@ -110,7 +110,7 @@ <widget class="QPushButton" name="left_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> @@ -138,13 +138,13 @@ <widget class="QPushButton" name="middle_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -204,13 +204,13 @@ <widget class="QPushButton" name="right_button"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -256,13 +256,13 @@ <widget class="QPushButton" name="buttonClearAll"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -275,13 +275,13 @@ <widget class="QPushButton" name="buttonRestoreDefaults"> <property name="minimumSize"> <size> - <width>57</width> + <width>68</width> <height>0</height> </size> </property> <property name="maximumSize"> <size> - <width>16777215</width> + <width>68</width> <height>16777215</height> </size> </property> @@ -324,32 +324,12 @@ <signal>accepted()</signal> <receiver>ConfigureMouseAdvanced</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>124</x> - <y>266</y> - </hint> - <hint type="destinationlabel"> - <x>124</x> - <y>143</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureMouseAdvanced</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>124</x> - <y>266</y> - </hint> - <hint type="destinationlabel"> - <x>124</x> - <y>143</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 002db3f93..81464dd37 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -29,7 +29,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { - game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false); + game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id), + Config::ConfigType::PerGameConfig); Settings::SetConfiguringGlobal(false); diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index d2057c4ab..25975b3b9 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/src/yuzu/configuration/configure_per_game.ui @@ -319,32 +319,12 @@ <signal>accepted()</signal> <receiver>ConfigurePerGame</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigurePerGame</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui index f581e27e0..757219d54 100644 --- a/src/yuzu/configuration/configure_touch_from_button.ui +++ b/src/yuzu/configuration/configure_touch_from_button.ui @@ -216,16 +216,6 @@ Drag points to change position, or double-click table cells to edit values.</str <signal>rejected()</signal> <receiver>ConfigureTouchFromButton</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>249</x> - <y>428</y> - </hint> - <hint type="destinationlabel"> - <x>249</x> - <y>224</y> - </hint> - </hints> </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui index 1171c2dd1..30ceccddb 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.ui +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -168,32 +168,12 @@ <signal>accepted()</signal> <receiver>ConfigureTouchscreenAdvanced</receiver> <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> </connection> <connection> <sender>buttonBox</sender> <signal>rejected()</signal> <receiver>ConfigureTouchscreenAdvanced</receiver> <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>140</x> - <y>318</y> - </hint> - <hint type="destinationlabel"> - <x>140</x> - <y>169</y> - </hint> - </hints> - </connection> + </connection> </connections> </ui> diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp new file mode 100644 index 000000000..7dcb2c5b9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -0,0 +1,146 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <unordered_map> + +#include <fmt/format.h> + +#include "common/param_package.h" +#include "core/settings.h" +#include "ui_configure_vibration.h" +#include "yuzu/configuration/configure_vibration.h" + +ConfigureVibration::ConfigureVibration(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) { + ui->setupUi(this); + + vibration_groupboxes = { + ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3, + ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6, + ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8, + }; + + vibration_spinboxes = { + ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3, + ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6, + ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8, + }; + + const auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + vibration_groupboxes[i]->setChecked(players[i].vibration_enabled); + vibration_spinboxes[i]->setValue(players[i].vibration_strength); + } + + ui->checkBoxAccurateVibration->setChecked( + Settings::values.enable_accurate_vibrations.GetValue()); + + if (!Settings::IsConfiguringGlobal()) { + ui->checkBoxAccurateVibration->setDisabled(true); + } + + RetranslateUI(); +} + +ConfigureVibration::~ConfigureVibration() = default; + +void ConfigureVibration::ApplyConfiguration() { + auto& players = Settings::values.players.GetValue(); + + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { + players[i].vibration_enabled = vibration_groupboxes[i]->isChecked(); + players[i].vibration_strength = vibration_spinboxes[i]->value(); + } + + Settings::values.enable_accurate_vibrations.SetValue( + ui->checkBoxAccurateVibration->isChecked()); +} + +void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { + using namespace Settings::NativeButton; + static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{ + {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons + {A, B, X, Y, R, ZR}, // Right Buttons + }}; + + auto& player = Settings::values.players.GetValue()[player_index]; + + for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) { + std::unordered_map<std::string, int> params_count; + + for (const auto button_index : buttons[device_idx]) { + const auto& player_button = player.buttons[button_index]; + + if (params_count.find(player_button) != params_count.end()) { + ++params_count[player_button]; + continue; + } + + params_count.insert_or_assign(player_button, 1); + } + + const auto it = std::max_element( + params_count.begin(), params_count.end(), + [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; }); + + auto& vibration_param_str = player.vibrations[device_idx]; + vibration_param_str.clear(); + + if (it->first.empty()) { + continue; + } + + const auto param = Common::ParamPackage(it->first); + + const auto engine = param.Get("engine", ""); + const auto guid = param.Get("guid", ""); + const auto port = param.Get("port", ""); + + if (engine.empty() || engine == "keyboard" || engine == "mouse") { + continue; + } + + vibration_param_str += fmt::format("engine:{}", engine); + + if (!port.empty()) { + vibration_param_str += fmt::format(",port:{}", port); + } + if (!guid.empty()) { + vibration_param_str += fmt::format(",guid:{}", guid); + } + } + + if (player.vibrations[0] != player.vibrations[1]) { + return; + } + + if (!player.vibrations[0].empty() && + player.controller_type != Settings::ControllerType::RightJoycon) { + player.vibrations[1].clear(); + } else if (!player.vibrations[1].empty() && + player.controller_type == Settings::ControllerType::RightJoycon) { + player.vibrations[0].clear(); + } +} + +void ConfigureVibration::SetAllVibrationDevices() { + // Set vibration devices for all player indices including handheld + for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) { + SetVibrationDevices(player_idx); + } +} + +void ConfigureVibration::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureVibration::RetranslateUI() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h new file mode 100644 index 000000000..07411a86f --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.h @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <QDialog> + +class QGroupBox; +class QSpinBox; + +namespace Ui { +class ConfigureVibration; +} + +class ConfigureVibration : public QDialog { + Q_OBJECT + +public: + explicit ConfigureVibration(QWidget* parent); + ~ConfigureVibration() override; + + void ApplyConfiguration(); + + static void SetVibrationDevices(std::size_t player_index); + static void SetAllVibrationDevices(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + std::unique_ptr<Ui::ConfigureVibration> ui; + + static constexpr std::size_t NUM_PLAYERS = 8; + + // Groupboxes encapsulating the vibration strength spinbox. + std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes; + + // Spinboxes representing the vibration strength percentage. + std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes; +}; diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui new file mode 100644 index 000000000..efdf317a9 --- /dev/null +++ b/src/yuzu/configuration/configure_vibration.ui @@ -0,0 +1,546 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureVibration</class> + <widget class="QDialog" name="ConfigureVibration"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>364</width> + <height>242</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Vibration</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="vibrationStrengthGroup"> + <property name="title"> + <string>Vibration</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0"> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <widget class="QWidget" name="player14Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <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="vibrationGroupPlayer1"> + <property name="title"> + <string>Player 1</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer1"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer2"> + <property name="title"> + <string>Player 2</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer2"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer3"> + <property name="title"> + <string>Player 3</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer3"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer4"> + <property name="title"> + <string>Player 4</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer4"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="player58Widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <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="vibrationGroupPlayer7"> + <property name="title"> + <string>Player 5</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer7"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer8"> + <property name="title"> + <string>Player 6</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer8"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer5"> + <property name="title"> + <string>Player 7</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer5"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationGroupPlayer6"> + <property name="title"> + <string>Player 8</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <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> + <widget class="QSpinBox" name="vibrationSpinPlayer6"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>21</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>150</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="vibrationSettingsGroup"> + <property name="title"> + <string>Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="checkBoxAccurateVibration"> + <property name="text"> + <string>Enable Accurate Vibration</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="spacerVibration"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>167</width> + <height>55</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBoxVibration"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBoxVibration</sender> + <signal>accepted()</signal> + <receiver>ConfigureVibration</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBoxVibration</sender> + <signal>rejected()</signal> + <receiver>ConfigureVibration</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp new file mode 100644 index 000000000..e87aededb --- /dev/null +++ b/src/yuzu/configuration/input_profiles.cpp @@ -0,0 +1,131 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/format.h> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/input_profiles.h" + +namespace FS = Common::FS; + +namespace { + +bool ProfileExistsInFilesystem(std::string_view profile_name) { + return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); +} + +bool IsINI(std::string_view filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return false; + } + + return filename.substr(index) == ".ini"; +} + +std::string GetNameWithoutExtension(const std::string& filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return filename; + } + + return filename.substr(0, index); +} + +} // namespace + +InputProfiles::InputProfiles() { + const std::string input_profile_loc = + fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); + + FS::ForeachDirectoryEntry( + nullptr, input_profile_loc, + [this](u64* entries_out, const std::string& directory, const std::string& filename) { + if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { + map_profiles.insert_or_assign( + GetNameWithoutExtension(filename), + std::make_unique<Config>(GetNameWithoutExtension(filename), + Config::ConfigType::InputProfile)); + } + return true; + }); +} + +InputProfiles::~InputProfiles() = default; + +std::vector<std::string> InputProfiles::GetInputProfileNames() { + std::vector<std::string> profile_names; + profile_names.reserve(map_profiles.size()); + + for (const auto& [profile_name, config] : map_profiles) { + if (!ProfileExistsInFilesystem(profile_name)) { + DeleteProfile(profile_name); + continue; + } + + profile_names.push_back(profile_name); + } + + return profile_names; +} + +bool InputProfiles::IsProfileNameValid(std::string_view profile_name) { + return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; +} + +bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) { + if (ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles.insert_or_assign( + profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); + + return SaveProfile(profile_name, player_index); +} + +bool InputProfiles::DeleteProfile(const std::string& profile_name) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name) || + FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { + map_profiles.erase(profile_name); + } + + return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); +} + +bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name)) { + map_profiles.erase(profile_name); + return false; + } + + map_profiles[profile_name]->ReadControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles[profile_name]->SaveControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const { + return map_profiles.find(profile_name) != map_profiles.end(); +} diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h new file mode 100644 index 000000000..cb41fd9be --- /dev/null +++ b/src/yuzu/configuration/input_profiles.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <string_view> +#include <unordered_map> + +class Config; + +class InputProfiles { + +public: + explicit InputProfiles(); + virtual ~InputProfiles(); + + std::vector<std::string> GetInputProfileNames(); + + static bool IsProfileNameValid(std::string_view profile_name); + + bool CreateProfile(const std::string& profile_name, std::size_t player_index); + bool DeleteProfile(const std::string& profile_name); + bool LoadProfile(const std::string& profile_name, std::size_t player_index); + bool SaveProfile(const std::string& profile_name, std::size_t player_index); + +private: + bool ProfileExistsInMap(const std::string& profile_name) const; + + std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; +}; |