|
|
@@ -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);
|
|
|
}
|