From a2a1f6c14cab686bc8c108b50ebf7ffd2ca44a24 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Fri, 20 Jan 2023 19:20:41 +0100 Subject: [PATCH] narr don't look --- src/citra/config.cpp | 3 +- src/citra_qt/CMakeLists.txt | 2 + src/citra_qt/configuration/config.cpp | 324 +++++++++++------- src/citra_qt/configuration/config.h | 23 +- .../configuration/configure_dialog.cpp | 1 - .../configuration/configure_input.cpp | 274 +++++++++------ src/citra_qt/configuration/configure_input.h | 170 +++++---- src/citra_qt/configuration/configure_input.ui | 16 +- .../configuration/configure_motion_touch.cpp | 41 +-- src/citra_qt/configuration/input_profiles.cpp | 127 +++++++ src/citra_qt/configuration/input_profiles.h | 34 ++ src/citra_qt/main.cpp | 12 +- src/common/file_util.h | 2 +- src/common/settings.cpp | 30 -- src/common/settings.h | 81 +++-- src/core/core.cpp | 2 +- src/core/hid/emulated_console.cpp | 4 +- src/core/hid/emulated_controller.cpp | 4 +- src/input_common/drivers/udp_client.cpp | 8 +- 19 files changed, 738 insertions(+), 420 deletions(-) create mode 100644 src/citra_qt/configuration/input_profiles.cpp create mode 100644 src/citra_qt/configuration/input_profiles.h diff --git a/src/citra/config.cpp b/src/citra/config.cpp index d8d93a9a8..6a0df05fc 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -15,8 +15,8 @@ #include "common/param_package.h" #include "common/settings.h" #include "core/hle/service/service.h" +#include "input_common/drivers/udp_client.h" #include "input_common/main.h" -#include "input_common/udp/client.h" #include "network/network_settings.h" Config::Config() { @@ -97,7 +97,6 @@ void Config::ReadSetting(const std::string& group, Settings::Setting Config::default_buttons = { - Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, - Qt::Key_F, Qt::Key_H, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, - Qt::Key_O, Qt::Key_P, Qt::Key_1, Qt::Key_2, Qt::Key_B, + Qt::Key_C, Qt::Key_X, Qt::Key_V, Qt::Key_Z, Qt::Key_Up, Qt::Key_Down, + Qt::Key_Left, Qt::Key_Right, Qt::Key_Q, Qt::Key_E, Qt::Key_M, Qt::Key_N, + Qt::Key_O, Qt::Key_P, Qt::Key_R, Qt::Key_T, Qt::Key_B, +}; + +const std::array Config::default_motions = { + Qt::Key_7, + Qt::Key_8, }; const std::array, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ { - Qt::Key_Up, - Qt::Key_Down, - Qt::Key_Left, - Qt::Key_Right, + Qt::Key_W, + Qt::Key_S, + Qt::Key_A, Qt::Key_D, }, { @@ -45,10 +49,14 @@ const std::array, Settings::NativeAnalog::NumAnalogs> Config: Qt::Key_K, Qt::Key_J, Qt::Key_L, - Qt::Key_D, }, }}; +const std::array Config::default_stick_mod = { + Qt::Key_Shift, + 0, +}; + // This shouldn't have anything except static initializers (no functions). So // QKeySequence(...).toString() is NOT ALLOWED HERE. // This must be in alphabetical order according to action name as it must have the same order as @@ -97,12 +105,21 @@ void Config::Initialize(const std::string& config_name) { case ConfigType::PerGameConfig: qt_config_loc = fmt::format("{}/custom/{}", fs_config_loc, config_file); break; + case ConfigType::InputProfile: + qt_config_loc = fmt::format("{}/input/{}", fs_config_loc, config_file); + break; } FileUtil::CreateFullPath(qt_config_loc); qt_config = std::make_unique(QString::fromStdString(qt_config_loc), QSettings::IniFormat); - Reload(); + if (type != ConfigType::InputProfile) { + Reload(); + } +} + +bool Config::IsCustomConfig() { + return type == ConfigType::PerGameConfig; } /* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their @@ -317,9 +334,78 @@ void Config::ReadCameraValues() { qt_config->endGroup(); } -void Config::ReadControlValues() { - qt_config->beginGroup(QStringLiteral("Controls")); +void Config::ReadPlayerValues() { + const QString player_prefix = [this] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_"); + } + }(); + auto& player = Settings::values.players.GetValue(); + if (IsCustomConfig()) { + const auto profile_name = + qt_config->value(QStringLiteral("%1profile_name").arg(player_prefix), QString{}) + .toString() + .toStdString(); + if (profile_name.empty()) { + // Use the global input config + player = Settings::values.players.GetValue(true); + return; + } + player.profile_name = profile_name; + } + + 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::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::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; + } + } +} + +void Config::ReadMotionTouchValues() { int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -352,80 +438,26 @@ void Config::ReadControlValues() { } qt_config->endArray(); - Settings::values.current_input_profile_index = - ReadSetting(QStringLiteral("profile"), 0).toInt(); + ReadBasicSetting(Settings::values.touch_device); + ReadBasicSetting(Settings::values.touch_from_button_map_index); + Settings::values.touch_from_button_map_index = std::clamp( + Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); - const auto append_profile = [this, num_touch_from_button_maps] { - Settings::InputProfile profile; - profile.name = - ReadSetting(QStringLiteral("name"), QStringLiteral("default")).toString().toStdString(); - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - profile.buttons[i] = ReadSetting(QString::fromUtf8(Settings::NativeButton::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (profile.buttons[i].empty()) - profile.buttons[i] = default_param; - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_analogs[i][4], 0.5f); - profile.analogs[i] = ReadSetting(QString::fromUtf8(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (profile.analogs[i].empty()) - profile.analogs[i] = default_param; - } - profile.motion_device = - ReadSetting(QStringLiteral("motion_device"), - QStringLiteral( - "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0")) - .toString() - .toStdString(); - profile.touch_device = - ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window")) - .toString() - .toStdString(); - profile.use_touch_from_button = - ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool(); - profile.touch_from_button_map_index = - ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt(); - profile.touch_from_button_map_index = - std::clamp(profile.touch_from_button_map_index, 0, num_touch_from_button_maps - 1); - profile.udp_input_address = - ReadSetting(QStringLiteral("udp_input_address"), QString::fromUtf8("127.0.0.1")) - .toString() - .toStdString(); - profile.udp_input_port = - static_cast(ReadSetting(QStringLiteral("udp_input_port"), 26760).toInt()); - profile.udp_pad_index = - static_cast(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); - Settings::values.input_profiles.emplace_back(std::move(profile)); - }; + ReadBasicSetting(Settings::values.udp_input_address); + ReadBasicSetting(Settings::values.udp_input_port); + ReadBasicSetting(Settings::values.udp_pad_index); +} - int num_input_profiles = qt_config->beginReadArray(QStringLiteral("profiles")); +void Config::ReadControlValues() { + qt_config->beginGroup(QStringLiteral("Controls")); - for (int i = 0; i < num_input_profiles; ++i) { - qt_config->setArrayIndex(i); - append_profile(); + Settings::values.players.SetGlobal(!IsCustomConfig()); + ReadPlayerValues(); + if (IsCustomConfig()) { + qt_config->endGroup(); + return; } - - qt_config->endArray(); - - // create a input profile if no input profiles exist, with the default or old settings - if (num_input_profiles == 0) { - append_profile(); - num_input_profiles = 1; - } - - // ensure that the current input profile index is valid. - Settings::values.current_input_profile_index = - std::clamp(Settings::values.current_input_profile_index, 0, num_input_profiles - 1); - - Settings::LoadProfile(Settings::values.current_input_profile_index); + ReadMotionTouchValues(); qt_config->endGroup(); } @@ -882,60 +914,87 @@ void Config::SaveCameraValues() { qt_config->endGroup(); } -void Config::SaveControlValues() { - qt_config->beginGroup(QStringLiteral("Controls")); +void Config::SavePlayerValues() { + const QString player_prefix = [this] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_"); + } + }(); - WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0); - qt_config->beginWriteArray(QStringLiteral("profiles")); - for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) { - qt_config->setArrayIndex(static_cast(p)); - const auto& profile = Settings::values.input_profiles[p]; - WriteSetting(QStringLiteral("name"), QString::fromStdString(profile.name), - QStringLiteral("default")); - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(profile.buttons[i]), - QString::fromStdString(default_param)); + const auto& player = Settings::values.players.GetValue(); + if (IsCustomConfig()) { + if (player.profile_name.empty()) { + // No custom profile selected + return; } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_analogs[i][4], 0.5f); - WriteSetting(QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(profile.analogs[i]), - QString::fromStdString(default_param)); - } - WriteSetting( - QStringLiteral("motion_device"), QString::fromStdString(profile.motion_device), - QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0")); - WriteSetting(QStringLiteral("touch_device"), QString::fromStdString(profile.touch_device), - QStringLiteral("engine:emu_window")); - WriteSetting(QStringLiteral("use_touch_from_button"), profile.use_touch_from_button, false); - WriteSetting(QStringLiteral("touch_from_button_map"), profile.touch_from_button_map_index, - 0); - WriteSetting(QStringLiteral("udp_input_address"), - QString::fromStdString(profile.udp_input_address), - QString::fromUtf8("127.0.0.1")); - WriteSetting(QStringLiteral("udp_input_port"), profile.udp_input_port, 26760); - WriteSetting(QStringLiteral("udp_pad_index"), profile.udp_pad_index, 0); + WriteSetting(QStringLiteral("%1profile_name").arg(player_prefix), + QString::fromStdString(player.profile_name), QString{}); } - qt_config->endArray(); + + 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::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)); + } +} + +void Config::SaveMotionTouchValues() { + WriteBasicSetting(Settings::values.touch_device); + WriteBasicSetting(Settings::values.touch_from_button_map_index); + + WriteBasicSetting(Settings::values.udp_input_address); + WriteBasicSetting(Settings::values.udp_input_port); + WriteBasicSetting(Settings::values.udp_pad_index); qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps")); for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { qt_config->setArrayIndex(static_cast(p)); - const auto& map = Settings::values.touch_from_button_maps[p]; - WriteSetting(QStringLiteral("name"), QString::fromStdString(map.name), + WriteSetting(QStringLiteral("name"), + QString::fromStdString(Settings::values.touch_from_button_maps[p].name), QStringLiteral("default")); qt_config->beginWriteArray(QStringLiteral("entries")); - for (std::size_t q = 0; q < map.buttons.size(); ++q) { + for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); + ++q) { qt_config->setArrayIndex(static_cast(q)); - WriteSetting(QStringLiteral("bind"), QString::fromStdString(map.buttons[q])); + WriteSetting( + QStringLiteral("bind"), + QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q])); } qt_config->endArray(); } qt_config->endArray(); +} + +void Config::SaveControlValues() { + qt_config->beginGroup(QStringLiteral("Controls")); + + Settings::values.players.SetGlobal(!IsCustomConfig()); + SavePlayerValues(); + if (IsCustomConfig()) { + qt_config->endGroup(); + return; + } + SaveMotionTouchValues(); qt_config->endGroup(); } @@ -1299,3 +1358,26 @@ void Config::Reload() { void Config::Save() { SaveValues(); } + +void Config::ReadControlPlayerValues() { + qt_config->beginGroup(QStringLiteral("Controls")); + ReadPlayerValues(); + qt_config->endGroup(); +} + +void Config::SaveControlPlayerValues() { + qt_config->beginGroup(QStringLiteral("Controls")); + SavePlayerValues(); + qt_config->endGroup(); +} + +void Config::ClearControlPlayerValues() { + qt_config->beginGroup(QStringLiteral("Controls")); + // If key is an empty string, all keys in the current group() are removed. + qt_config->remove(QString{}); + qt_config->endGroup(); +} + +const std::string& Config::GetConfigFilePath() const { + return qt_config_loc; +} \ No newline at end of file diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 572745055..ac13cb26c 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -15,7 +15,11 @@ class QSettings; class Config { public: - enum class ConfigType : u32 { GlobalConfig, PerGameConfig }; + enum class ConfigType : u32 { + GlobalConfig, + PerGameConfig, + InputProfile, + }; explicit Config(const std::string& config_name = "qt-config", ConfigType config_type = ConfigType::GlobalConfig); @@ -24,17 +28,28 @@ public: void Reload(); void Save(); + // Used for input profiles + void ReadControlPlayerValues(); + void SaveControlPlayerValues(); + void ClearControlPlayerValues(); + const std::string& GetConfigFilePath() const; + static const std::array default_buttons; + static const std::array default_motions; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array default_hotkeys; + static const std::array default_stick_mod; private: void Initialize(const std::string& config_name); + bool IsCustomConfig(); void ReadValues(); + void ReadPlayerValues(); + void ReadMotionTouchValues(); + void ReadControlValues(); void ReadAudioValues(); void ReadCameraValues(); - void ReadControlValues(); void ReadCoreValues(); void ReadDataStorageValues(); void ReadDebuggingValues(); @@ -54,9 +69,11 @@ private: void ReadVideoDumpingValues(); void SaveValues(); + void SavePlayerValues(); + void SaveMotionTouchValues(); + void SaveControlValues(); void SaveAudioValues(); void SaveCameraValues(); - void SaveControlValues(); void SaveCoreValues(); void SaveDataStorageValues(); void SaveDebuggingValues(); diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 47145d1f9..b6e8ab5ba 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -61,7 +61,6 @@ void ConfigureDialog::ApplyConfiguration() { ui->generalTab->ApplyConfiguration(); ui->systemTab->ApplyConfiguration(); ui->inputTab->ApplyConfiguration(); - ui->inputTab->ApplyProfile(); ui->hotkeysTab->ApplyConfiguration(registry); ui->graphicsTab->ApplyConfiguration(); ui->enhancementsTab->ApplyConfiguration(); diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 9f622c308..d85402f92 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -5,19 +5,26 @@ #include #include #include +#include #include #include -#include #include #include -#include #include +#include +#include "citra_qt/bootmanager.h" #include "citra_qt/configuration/config.h" #include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_motion_touch.h" +#include "citra_qt/configuration/input_profiles.h" +#include "common/assert.h" #include "common/param_package.h" -#include "core/core.h" #include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/main.h" #include "ui_configure_input.h" const std::array @@ -271,15 +278,10 @@ ConfigureInput::~ConfigureInput() { void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem_) { input_subsystem = input_subsystem_; emulated_controller = Core::System::GetInstance().HIDCore().GetEmulatedController(); + profiles = std::make_unique(); setFocusPolicy(Qt::ClickFocus); - for (const auto& profile : Settings::values.input_profiles) { - ui->profile->addItem(QString::fromStdString(profile.name)); - } - - ui->profile->setCurrentIndex(Settings::values.current_input_profile_index); - button_map = { ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, ui->buttonDpadUp, ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight, @@ -294,14 +296,12 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem_) { ui->buttonCirclePadDown, ui->buttonCirclePadLeft, ui->buttonCirclePadRight, - nullptr, }, { ui->buttonCStickUp, ui->buttonCStickDown, ui->buttonCStickLeft, ui->buttonCStickRight, - nullptr, }, }}; @@ -624,13 +624,12 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem_) { } }); - // UpdateInputProfiles(); - - connect(ui->buttonProfilesNew, &QPushButton::clicked, this, &ConfigureInput::NewProfile); + UpdateInputProfiles(); + connect(ui->buttonProfilesNew, &QPushButton::clicked, this, &ConfigureInput::CreateProfile); connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile); - // connect(ui->comboProfiles, qOverload(&QComboBox::activated), this, - // &ConfigureInput::LoadProfile); - // connect(ui->buttonProfilesSave, &QPushButton::clicked, this, &ConfigureInput::SaveProfile); + connect(ui->comboProfiles, qOverload(&QComboBox::activated), this, + &ConfigureInput::LoadProfile); + connect(ui->buttonProfilesSave, &QPushButton::clicked, this, &ConfigureInput::SaveProfile); connect(ui->buttonMotionTouch, &QPushButton::clicked, [this, input_subsystem_] { QDialog* motion_touch_dialog = new ConfigureMotionTouch(this, input_subsystem_); @@ -648,41 +647,6 @@ void ConfigureInput::ApplyConfiguration() { emulated_controller->EnableConfiguration(); } -void ConfigureInput::ApplyProfile() { - Settings::values.current_input_profile_index = ui->profile->currentIndex(); -} - -void ConfigureInput::EmitInputKeysChanged() { - emit InputKeysChanged(GetUsedKeyboardKeys()); -} - -void ConfigureInput::OnHotkeysChanged(QList new_key_list) { - hotkey_list = new_key_list; -} - -QList ConfigureInput::GetUsedKeyboardKeys() { - QList list; - for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { - const auto& button_param = buttons_param[button]; - if (button_param.Get("engine", "") == "keyboard") { - list << QKeySequence(button_param.Get("code", 0)); - } - } - - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - const auto& analog_param = analogs_param[analog_id]; - if (analog_param.Get("engine", "") == "analog_from_button") { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { - const Common::ParamPackage sub_button{ - analog_param.Get(analog_sub_buttons[sub_button_id], "")}; - list << QKeySequence(sub_button.Get("code", 0)); - } - } - } - - return list; -} - void ConfigureInput::LoadConfiguration() { emulated_controller->ReloadFromSettings(); @@ -813,6 +777,7 @@ void ConfigureInput::UpdateMappingWithDefaults() { button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam( Config::default_buttons[button_id])}); } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { Common::ParamPackage analog_param{}; for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { @@ -821,8 +786,8 @@ void ConfigureInput::UpdateMappingWithDefaults() { SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]); } - /* analog_param.Set("modifier", InputCommon::GenerateKeyboardParam( - Config::default_stick_mod[analog_id]));*/ + analog_param.Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); emulated_controller->SetStickParam(analog_id, analog_param); } @@ -861,6 +826,7 @@ void ConfigureInput::HandleClick(QPushButton* button, std::size_t button_id, if (timeout_timer->isActive()) { return; } + button->setText(tr("[waiting]")); button->setFocus(); input_setter = std::move(new_input_setter); @@ -870,7 +836,7 @@ void ConfigureInput::HandleClick(QPushButton* button, std::size_t button_id, QWidget::grabMouse(); QWidget::grabKeyboard(); - timeout_timer->start(2500); // Cancel after 2.5 seconds + timeout_timer->start(4000); // Cancel after 4 seconds poll_timer->start(25); // Check for new inputs every 25ms } @@ -913,30 +879,44 @@ void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool a input_setter = std::nullopt; } -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) +void ConfigureInput::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { return; - - if (event->key() != Qt::Key_Escape && event->key() != previous_key_code) { - if (want_keyboard_keys) { - // Check if key is already bound - if (hotkey_list.contains(QKeySequence(event->key())) || - GetUsedKeyboardKeys().contains(QKeySequence(event->key()))) { - SetPollingResult({}, true); - QMessageBox::critical(this, tr("Error!"), - tr("You're using a key that's already bound.")); - return; - } - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop - // polling - return; - } } - SetPollingResult({}, true); - previous_key_code = 0; + + const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); + input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); +} + +void ConfigureInput::wheelEvent(QWheelEvent* event) { + const int x = event->angleDelta().x(); + const int y = event->angleDelta().y(); + input_subsystem->GetMouse()->MouseWheelChange(x, y); +} + +void ConfigureInput::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + event->ignore(); + if (event->key() != Qt::Key_Escape) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +bool ConfigureInput::eventFilter(QObject* object, QEvent* event) { + if (object == ui->comboDevices && event->type() == QEvent::MouseButtonPress) { + UpdateInputDeviceCombobox(); + } + return object->eventFilter(object, event); +} + +void ConfigureInput::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); } void ConfigureInput::RetranslateUI() { @@ -1028,60 +1008,128 @@ void ConfigureInput::UpdateInputDeviceCombobox() { } } -void ConfigureInput::NewProfile() { - const QString name = - QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile.")); - if (name.isEmpty()) { +void ConfigureInput::EmitInputKeysChanged() { + emit InputKeysChanged(GetUsedKeyboardKeys()); +} + +void ConfigureInput::OnHotkeysChanged(QList new_key_list) { + hotkey_list = new_key_list; +} + +QList ConfigureInput::GetUsedKeyboardKeys() { + QList list; + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + const auto& button_param = emulated_controller->GetButtonParam(button_id); + if (button_param.Get("engine", "") == "keyboard") { + list << QKeySequence(button_param.Get("code", 0)); + } + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + const auto& analog_param = emulated_controller->GetStickParam(analog_id); + if (analog_param.Get("engine", "") == "analog_from_button") { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + const Common::ParamPackage sub_button{ + analog_param.Get(analog_sub_buttons[sub_button_id], "")}; + list << QKeySequence(sub_button.Get("code", 0)); + } + } + } + + return list; +} + +void ConfigureInput::CreateProfile() { + const QString profile_name = + QInputDialog::getText(this, tr("New Profile"), tr("Enter a profile name:")); + + if (profile_name.isEmpty()) { return; } - if (IsProfileNameDuplicate(name)) { - WarnProposedProfileNameIsDuplicate(); + + if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("The given profile name is not valid!")); return; } ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - Settings::CreateProfile(name.toStdString()); - ui->profile->addItem(name); - ui->profile->setCurrentIndex(Settings::values.current_input_profile_index); - LoadConfiguration(); - ui->buttonProfilesDelete->setEnabled(ui->profile->count() > 1); + + if (!profiles->CreateProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("Failed to create the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + return; + } + + UpdateInputProfiles(); + + ui->comboProfiles->addItem(profile_name); + ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1); } void ConfigureInput::DeleteProfile() { - const auto answer = QMessageBox::question( - this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->profile->currentText())); - if (answer != QMessageBox::Yes) { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { return; } - const int index = ui->profile->currentIndex(); - ui->profile->removeItem(index); - ui->profile->setCurrentIndex(0); - Settings::DeleteProfile(index); + + 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(); + return; + } + + UpdateInputProfiles(); + + ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex()); + ui->comboProfiles->setCurrentIndex(-1); +} + +void ConfigureInput::LoadProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->LoadProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Load Input Profile"), + tr("Failed to load the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + return; + } + LoadConfiguration(); - ui->buttonProfilesDelete->setEnabled(ui->profile->count() > 1); } -void ConfigureInput::RenameProfile() { - const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:")); - if (new_name.isEmpty()) { - return; - } - if (IsProfileNameDuplicate(new_name)) { - WarnProposedProfileNameIsDuplicate(); +void ConfigureInput::SaveProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { return; } - ui->profile->setItemText(ui->profile->currentIndex(), new_name); - Settings::RenameCurrentProfile(new_name.toStdString()); - Settings::SaveProfile(ui->profile->currentIndex()); + ApplyConfiguration(); + + if (!profiles->SaveProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Save Input Profile"), + tr("Failed to save the input profile \"%1\"").arg(profile_name)); + UpdateInputProfiles(); + return; + } } -bool ConfigureInput::IsProfileNameDuplicate(const QString& name) const { - return ui->profile->findText(name, Qt::MatchFixedString | Qt::MatchCaseSensitive) != -1; -} +void ConfigureInput::UpdateInputProfiles() { + ui->comboProfiles->clear(); -void ConfigureInput::WarnProposedProfileNameIsDuplicate() { - QMessageBox::warning(this, tr("Duplicate profile name"), - tr("Profile name already exists. Please choose a different name.")); + for (const auto& profile_name : profiles->GetInputProfileNames()) { + ui->comboProfiles->addItem(QString::fromStdString(profile_name)); + } + + ui->comboProfiles->setCurrentIndex(-1); } diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index 810bb73d3..193032450 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -9,29 +9,42 @@ #include #include #include -#include -#include -#include -#include +#include + #include + #include "common/param_package.h" #include "common/settings.h" -#include "input_common/main.h" +#include "ui_configure_input.h" +class QCheckBox; class QKeyEvent; class QLabel; class QPushButton; class QSlider; +class QSpinBox; class QString; class QTimer; +class QWidget; -namespace Core::HID { -class EmulatedController; -} // namespace Core::HID +class InputProfiles; + +namespace InputCommon { +class InputSubsystem; +} + +namespace InputCommon::Polling { +enum class InputType; +} // namespace InputCommon::Polling namespace Ui { class ConfigureInput; -} +} // namespace Ui + +namespace Core::HID { +class HIDCore; +class EmulatedController; +} // namespace Core::HID class ConfigureInput : public QWidget { Q_OBJECT @@ -40,18 +53,32 @@ public: explicit ConfigureInput(QWidget* parent = nullptr); ~ConfigureInput() override; + /// Initializes the ConfigureInput dialog void Initialize(InputCommon::InputSubsystem* input_subsystem_); - /// Save all button configurations to settings file - void ApplyConfiguration(); - void RetranslateUI(); - /// Load configuration settings. void LoadConfiguration(); + + /// Save all button configurations to settings file. + void ApplyConfiguration(); + + /// Update the input devices combobox. + void UpdateInputDeviceCombobox(); + + /// Updates the list of controller profiles. + void UpdateInputProfiles(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + /// Clear all input configuration. + void ClearAll(); + + /// Updates UI with selected language translations + void RetranslateUI(); + void EmitInputKeysChanged(); - /// Save the current input profile index - void ApplyProfile(); public slots: void OnHotkeysChanged(QList new_key_list); @@ -59,16 +86,65 @@ signals: void InputKeysChanged(QList new_key_list); private: + /// Generates list of all used keys + QList GetUsedKeyboardKeys(); + QString ButtonToText(const Common::ParamPackage& param); + QString AnalogToText(const Common::ParamPackage& param, const std::string& dir); + void changeEvent(QEvent* event) override; + + /// Called when the button was pressed. + void HandleClick(QPushButton* button, std::size_t button_id, + std::function new_input_setter, + InputCommon::Polling::InputType type); + + /// 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; + + /// Handle mouse wheel move events. + void wheelEvent(QWheelEvent* event) override; + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; + + /// Handle combobox list refresh + bool eventFilter(QObject* object, QEvent* event) override; + /// Update UI to reflect current configuration. void UpdateUI(); + /// Update the available input devices. + void UpdateInputDevices(); + + /// 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; InputCommon::InputSubsystem* input_subsystem; + std::unique_ptr profiles; + std::unique_ptr timeout_timer; std::unique_ptr poll_timer; @@ -77,23 +153,18 @@ private: Core::HID::EmulatedController* emulated_controller; - std::array buttons_param; - std::array analogs_param; - - static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; /// Each button input is represented by a QPushButton. std::array button_map; - /// A group of five QPushButtons represent one analog input. The buttons each represent up, - /// down, left, right, and modifier, respectively. + /// A group of four QPushButtons represent one analog input. The buttons each represent up, + /// down, left, right, respectively. std::array, Settings::NativeAnalog::NumAnalogs> analog_map_buttons; - /// Analog inputs are also represented each with a single button, used to configure with an - /// actual analog stick - std::array analog_map_stick; - static const std::array analog_sub_buttons; + /// Each motion input is represented by a QPushButton. + std::array motion_map; std::array analog_map_deadzone_label; std::array analog_map_deadzone_slider; @@ -104,8 +175,7 @@ private: std::array analog_map_range_groupbox; std::array analog_map_range_spinbox; - /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. - bool map_analog_stick_accepted{}; + static const std::array analog_sub_buttons; /** * List of keys currently registered to hotkeys. @@ -114,48 +184,10 @@ private: */ QList hotkey_list; - /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, - /// keyboard events are ignored. - bool want_keyboard_keys = false; + /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. + bool map_analog_stick_accepted{}; + /// List of physical devices users can map with. If a SDL backed device is selected, then you + /// can use this device to get a default mapping. std::vector input_devices; - - /// Generates list of all used keys - QList GetUsedKeyboardKeys(); - - /// Restore all buttons to their default values. - void RestoreDefaults(); - /// Clear all input configuration - void ClearAll(); - - /// Gets the default controller mapping for this device and auto configures the input to match. - void UpdateMappingWithDefaults(); - - /// Called when the button was pressed. - void HandleClick(QPushButton* button, std::size_t button_id, - std::function new_input_setter, - InputCommon::Polling::InputType type); - - /// The key code of the previous state of the key being currently bound. - int previous_key_code; - - /// Checks whether a given input can be accepted. - bool IsInputAcceptable(const Common::ParamPackage& params) const; - - /// Finish polling and configure input using the input_setter - void SetPollingResult(const Common::ParamPackage& params, bool abort); - - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; - - void UpdateInputDevices(); - void UpdateInputDeviceCombobox(); - - /// input profiles - void NewProfile(); - void DeleteProfile(); - void RenameProfile(); - - bool IsProfileNameDuplicate(const QString& name) const; - void WarnProposedProfileNameIsDuplicate(); }; diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2541f767d..9f34e5102 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -68,7 +68,14 @@ 5 - + + + + + + Save + + @@ -77,13 +84,6 @@ - - - - Rename - - - diff --git a/src/citra_qt/configuration/configure_motion_touch.cpp b/src/citra_qt/configuration/configure_motion_touch.cpp index d02c825e3..28448f812 100644 --- a/src/citra_qt/configuration/configure_motion_touch.cpp +++ b/src/citra_qt/configuration/configure_motion_touch.cpp @@ -120,37 +120,23 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, ConfigureMotionTouch::~ConfigureMotionTouch() = default; void ConfigureMotionTouch::SetConfiguration() { - const Common::ParamPackage motion_param(Settings::values.current_input_profile.motion_device); - const Common::ParamPackage touch_param(Settings::values.current_input_profile.touch_device); - const std::string motion_engine = motion_param.Get("engine", "motion_emu"); - const std::string touch_engine = touch_param.Get("engine", "emu_window"); + const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); - ui->motion_provider->setCurrentIndex( - ui->motion_provider->findData(QString::fromStdString(motion_engine))); - ui->touch_provider->setCurrentIndex( - ui->touch_provider->findData(QString::fromStdString(touch_engine))); - ui->touch_from_button_checkbox->setChecked( - Settings::values.current_input_profile.use_touch_from_button); touch_from_button_maps = Settings::values.touch_from_button_maps; for (const auto& touch_map : touch_from_button_maps) { ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); } ui->touch_from_button_map->setCurrentIndex( - Settings::values.current_input_profile.touch_from_button_map_index); - ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); - - guid = motion_param.Get("guid", "0"); - port = motion_param.Get("port", 0); + Settings::values.touch_from_button_map_index.GetValue()); min_x = touch_param.Get("min_x", 100); min_y = touch_param.Get("min_y", 50); max_x = touch_param.Get("max_x", 1800); max_y = touch_param.Get("max_y", 850); - ui->udp_server->setText( - QString::fromStdString(Settings::values.current_input_profile.udp_input_address)); - ui->udp_port->setText(QString::number(Settings::values.current_input_profile.udp_input_port)); - ui->udp_pad_index->setCurrentIndex(Settings::values.current_input_profile.udp_pad_index); + ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address.GetValue())); + ui->udp_port->setText(QString::number(Settings::values.udp_input_port.GetValue())); + ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index.GetValue()); } void ConfigureMotionTouch::UpdateUiDisplay() { @@ -326,19 +312,12 @@ void ConfigureMotionTouch::ApplyConfiguration() { touch_param.Set("max_y", max_y); } - Settings::values.current_input_profile.motion_device = motion_param.Serialize(); - Settings::values.current_input_profile.touch_device = touch_param.Serialize(); - Settings::values.current_input_profile.use_touch_from_button = - ui->touch_from_button_checkbox->isChecked(); - Settings::values.current_input_profile.touch_from_button_map_index = - ui->touch_from_button_map->currentIndex(); + Settings::values.touch_device = touch_param.Serialize(); + Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); Settings::values.touch_from_button_maps = touch_from_button_maps; - Settings::values.current_input_profile.udp_input_address = ui->udp_server->text().toStdString(); - Settings::values.current_input_profile.udp_input_port = - static_cast(ui->udp_port->text().toInt()); - Settings::values.current_input_profile.udp_pad_index = - static_cast(ui->udp_pad_index->currentIndex()); - Settings::SaveProfile(Settings::values.current_input_profile_index); + Settings::values.udp_input_address = ui->udp_server->text().toStdString(); + Settings::values.udp_input_port = static_cast(ui->udp_port->text().toInt()); + Settings::values.udp_pad_index = static_cast(ui->udp_pad_index->currentIndex()); input_subsystem->ReloadInputDevices(); accept(); diff --git a/src/citra_qt/configuration/input_profiles.cpp b/src/citra_qt/configuration/input_profiles.cpp new file mode 100644 index 000000000..e10537c9c --- /dev/null +++ b/src/citra_qt/configuration/input_profiles.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "citra_qt/configuration/config.h" +#include "citra_qt/configuration/input_profiles.h" +#include "common/common_paths.h" +#include "common/file_util.h" + +namespace { + +bool ProfileExistsInFilesystem(std::string_view profile_name) { + return FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "input" + + DIR_SEP + fmt::format("{}.ini", profile_name)); +} + +bool IsINI(const std::string& filename) { + return filename.ends_with(".ini"); +} + +std::string GetNameWithoutExtension(const std::string& filename) { + const auto last_dot = filename.find_last_of("."); + if (last_dot == std::string::npos) { + return filename; + } + return filename.substr(0, last_dot); +} + +} // namespace + +InputProfiles::InputProfiles() { + const auto input_profile_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "input"; + + if (!FileUtil::IsDirectory(input_profile_loc)) { + return; + } + + FileUtil::ForeachDirectoryEntry( + nullptr, input_profile_loc, + [this](u64* num_entries_out, const std::string& directory, const std::string& filename) { + const auto name_without_ext = GetNameWithoutExtension(filename); + + if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { + map_profiles.insert_or_assign( + name_without_ext, + std::make_unique(name_without_ext, Config::ConfigType::InputProfile)); + } + + return true; + }); +} + +InputProfiles::~InputProfiles() = default; + +std::vector InputProfiles::GetInputProfileNames() { + std::vector 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); + } + + std::stable_sort(profile_names.begin(), profile_names.end()); + + 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) { + if (ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles.insert_or_assign( + profile_name, std::make_unique(profile_name, Config::ConfigType::InputProfile)); + + return SaveProfile(profile_name); +} + +bool InputProfiles::DeleteProfile(const std::string& profile_name) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name) || + FileUtil::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) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name)) { + map_profiles.erase(profile_name); + return false; + } + + map_profiles[profile_name]->ReadControlPlayerValues(); + return true; +} + +bool InputProfiles::SaveProfile(const std::string& profile_name) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles[profile_name]->SaveControlPlayerValues(); + return true; +} + +bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const { + return map_profiles.find(profile_name) != map_profiles.end(); +} diff --git a/src/citra_qt/configuration/input_profiles.h b/src/citra_qt/configuration/input_profiles.h new file mode 100644 index 000000000..96579db38 --- /dev/null +++ b/src/citra_qt/configuration/input_profiles.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Core { +class System; +} + +class Config; + +class InputProfiles { + +public: + explicit InputProfiles(); + virtual ~InputProfiles(); + + std::vector GetInputProfileNames(); + + static bool IsProfileNameValid(std::string_view profile_name); + + bool CreateProfile(const std::string& profile_name); + bool DeleteProfile(const std::string& profile_name); + bool LoadProfile(const std::string& profile_name); + bool SaveProfile(const std::string& profile_name); + +private: + bool ProfileExistsInMap(const std::string& profile_name) const; + + std::unordered_map> map_profiles; +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 5956fb0e9..8f13f14f8 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -1966,17 +1966,17 @@ void GMainWindow::OnLoadState() { } void GMainWindow::OnConfigure() { + const auto old_theme = UISettings::values.theme; + const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); + const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps; + game_list->SetDirectoryWatcherEnabled(false); Settings::SetConfiguringGlobal(true); ConfigureDialog configureDialog(this, hotkey_registry, input_subsystem.get(), !multiplayer_state->IsHostingPublicRoom()); connect(&configureDialog, &ConfigureDialog::LanguageChanged, this, &GMainWindow::OnLanguageChanged); - auto old_theme = UISettings::values.theme; - const int old_input_profile_index = Settings::values.current_input_profile_index; - const auto old_input_profiles = Settings::values.input_profiles; - const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps; - const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); + auto result = configureDialog.exec(); game_list->SetDirectoryWatcherEnabled(true); if (result == QDialog::Accepted) { @@ -2001,9 +2001,7 @@ void GMainWindow::OnConfigure() { UpdateSecondaryWindowVisibility(); UpdateBootHomeMenuState(); } else { - Settings::values.input_profiles = old_input_profiles; Settings::values.touch_from_button_maps = old_touch_from_button_maps; - Settings::LoadProfile(old_input_profile_index); } } diff --git a/src/common/file_util.h b/src/common/file_util.h index 41e2af845..c4b5f6e5a 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -112,7 +112,7 @@ bool CreateDir(const std::string& filename); bool CreateFullPath(const std::string& fullPath); // Deletes a given filename, return true on success -// Doesn't supports deleting a directory +// Doesn't support deleting a directory bool Delete(const std::string& filename); // Deletes a directory filename, returns true on success diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 3902456ee..6c000fb58 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -101,7 +101,6 @@ void Apply() { Core::DSP().SetSink(values.output_type.GetValue(), values.output_device.GetValue()); Core::DSP().EnableStretching(values.enable_audio_stretching.GetValue()); -<<<<<<< HEAD auto hid = Service::HID::GetModule(system); if (hid) { hid->ReloadInputDevices(); @@ -120,8 +119,6 @@ void Apply() { if (ir_rst) ir_rst->ReloadInputDevices(); -======= ->>>>>>> 811fc4f3a (Stash "DONE") auto cam = Service::CAM::GetModule(system); if (cam) { cam->ReloadCameraDevices(); @@ -255,31 +252,4 @@ void RestoreGlobalState(bool is_powered_on) { values.preload_textures.SetGlobal(true); } -void LoadProfile(int index) { - Settings::values.current_input_profile = Settings::values.input_profiles[index]; - Settings::values.current_input_profile_index = index; -} - -void SaveProfile(int index) { - Settings::values.input_profiles[index] = Settings::values.current_input_profile; -} - -void CreateProfile(std::string name) { - Settings::InputProfile profile = values.current_input_profile; - profile.name = std::move(name); - Settings::values.input_profiles.push_back(std::move(profile)); - Settings::values.current_input_profile_index = - static_cast(Settings::values.input_profiles.size()) - 1; - Settings::LoadProfile(Settings::values.current_input_profile_index); -} - -void DeleteProfile(int index) { - Settings::values.input_profiles.erase(Settings::values.input_profiles.begin() + index); - Settings::LoadProfile(0); -} - -void RenameCurrentProfile(std::string new_name) { - Settings::values.current_input_profile.name = std::move(new_name); -} - } // namespace Settings diff --git a/src/common/settings.h b/src/common/settings.h index cec580345..8f4ebb105 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -113,7 +113,7 @@ constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN; constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; -static const std::array mapping = {{ +constexpr std::array mapping = {{ "button_a", "button_b", "button_x", @@ -132,13 +132,13 @@ static const std::array mapping = {{ "button_zr", "button_home", }}; - } // namespace NativeButton namespace NativeAnalog { enum Values { CirclePad, CStick, + NumAnalogs, }; @@ -160,9 +160,24 @@ constexpr int MOTION_HID_BEGIN = MotionLeft; constexpr int MOTION_HID_END = NumMotions; constexpr int NUM_MOTIONS_HID = NumMotions; -extern const std::array mapping; +constexpr std::array mapping = {{ + "motionleft", + "motionright", +}}; } // namespace NativeMotion +using AnalogsRaw = std::array; +using ButtonsRaw = std::array; +using MotionsRaw = std::array; + +struct PlayerInput { + ButtonsRaw buttons; + AnalogsRaw analogs; + MotionsRaw motions; + + std::string profile_name; +}; + /** The Setting class is a simple resource manager. It defines a label and default value alongside * the actual value of the setting for simpler and less-error prone use with frontend * configurations. Specifying a default value and label is required. A minimum and maximum range can @@ -393,18 +408,36 @@ protected: Type custom{}; ///< The custom value of the setting }; -struct InputProfile { - std::string name; - std::array buttons; - std::array analogs; - std::array motions; - std::string motion_device; - std::string touch_device; - bool use_touch_from_button; - int touch_from_button_map_index; - std::string udp_input_address; - u16 udp_input_port; - u8 udp_pad_index; +/** + * The InputSetting class allows for getting a reference to either the global or custom members. + * This is required as we cannot easily modify the values of user-defined types within containers + * using the SetValue() member function found in the Setting class. The primary purpose of this + * class is to store an array of 10 PlayerInput structs for both the global and custom setting and + * allows for easily accessing and modifying both settings. + */ +template +class InputSetting final { +public: + InputSetting() = default; + explicit InputSetting(Type val) : Setting(val) {} + ~InputSetting() = default; + void SetGlobal(bool to_global) { + use_global = to_global; + } + [[nodiscard]] bool UsingGlobal() const { + return use_global; + } + [[nodiscard]] Type& GetValue(bool need_global = false) { + if (use_global || need_global) { + return global; + } + return custom; + } + +private: + bool use_global{true}; ///< The setting's global state + Type global{}; ///< The setting + Type custom{}; ///< The custom setting value }; struct TouchFromButtonMap { @@ -418,11 +451,16 @@ static constexpr s32 REGION_VALUE_AUTO_SELECT = -1; struct Values { // Controls - InputProfile current_input_profile; ///< The current input profile - int current_input_profile_index; ///< The current input profile index - std::vector input_profiles; ///< The list of input profiles + InputSetting players; + + Setting touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device"}; + Setting touch_from_button_map_index{0, "touch_from_button_map"}; std::vector touch_from_button_maps; + Setting udp_input_address{"127.0.0.1", "udp_input_address"}; + Setting udp_input_port{26760, "udp_input_port"}; + Setting udp_pad_index{0, "udp_pad_index"}; + // Core Setting use_cpu_jit{true, "use_cpu_jit"}; SwitchableSetting cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"}; @@ -541,11 +579,4 @@ void LogSettings(); // Restore the global state of all applicable settings in the Values struct void RestoreGlobalState(bool is_powered_on); -// Input profiles -void LoadProfile(int index); -void SaveProfile(int index); -void CreateProfile(std::string name); -void DeleteProfile(int index); -void RenameCurrentProfile(std::string new_name); - } // namespace Settings diff --git a/src/core/core.cpp b/src/core/core.cpp index 0815184c7..2aa04bbe6 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -280,7 +280,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st auto n3ds_mode = app_loader->LoadKernelN3dsMode(); ASSERT(n3ds_mode.first); u32 num_cores = 2; - if (Settings::values.is_new_3ds) { + if (Settings::values.is_new_3ds.GetValue()) { num_cores = 4; } ResultStatus init_result{ diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp index e6c561a5f..4cb634f4a 100644 --- a/src/core/hid/emulated_console.cpp +++ b/src/core/hid/emulated_console.cpp @@ -12,7 +12,7 @@ EmulatedConsole::~EmulatedConsole() = default; void EmulatedConsole::ReloadFromSettings() { // Using first motion device from player 1. No need to assign any unique config at the moment - const auto& player = Settings::values.current_input_profile; + const auto& player = Settings::values.players.GetValue(); motion_params = Common::ParamPackage(player.motions[0]); ReloadInput(); @@ -42,7 +42,7 @@ void EmulatedConsole::SetTouchParams() { } const auto button_index = - static_cast(Settings::values.current_input_profile.touch_from_button_map_index); + static_cast(Settings::values.touch_from_button_map_index.GetValue()); const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons; // Map the rest of the fingers from touch from button configuration diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index a638f28ff..d15e4af54 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -168,7 +168,7 @@ void EmulatedController::ReloadInput() { } void EmulatedController::ReloadFromSettings() { - const auto& player = Settings::values.current_input_profile; + const auto& player = Settings::values.players.GetValue(); for (std::size_t i = 0; i < player.buttons.size(); i++) { button_params[i] = Common::ParamPackage(player.buttons[i]); } @@ -183,7 +183,7 @@ void EmulatedController::ReloadFromSettings() { } void EmulatedController::SaveCurrentConfig() { - auto& player = Settings::values.current_input_profile; + auto& player = Settings::values.players.GetValue(); for (std::size_t index = 0; index < player.buttons.size(); ++index) { player.buttons[index] = button_params[index].Serialize(); } diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index bdd302328..e04a054c7 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -151,10 +151,10 @@ UDPClient::ClientConnection::~ClientConnection() = default; void UDPClient::ReloadSockets() { Reset(); - const std::string& host = Settings::values.current_input_profile.udp_input_address; - const u16 udp_input_port = Settings::values.current_input_profile.udp_input_port; - // TODO: Hook it up again - const u8 pad_index = Settings::values.current_input_profile.udp_pad_index; + const std::string& host = Settings::values.udp_input_address.GetValue(); + const u16 udp_input_port = Settings::values.udp_input_port.GetValue(); + // TODO: Hook it up again!!!!!!!!! + const u8 pad_index = Settings::values.udp_pad_index.GetValue(); const std::size_t client_number = GetClientNumber(host, udp_input_port); if (client_number != MAX_UDP_CLIENTS) {