yuzu-emu
/
yuzu-mainline
Archived
1
0
Fork 0

yuzu: Add custom ringcon configuration

This commit is contained in:
german77 2022-01-08 23:23:40 -06:00 committed by Narr the Reg
parent b2359f1527
commit d2f9412cf1
19 changed files with 992 additions and 65 deletions

View File

@ -590,6 +590,9 @@ struct Values {
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
std::vector<TouchFromButtonMap> touch_from_button_maps;
BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"};
RingconRaw ringcon_analogs;
// Data Storage
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};

View File

@ -357,6 +357,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
using RingconRaw = std::string;
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;

View File

@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
ReloadInput();
}
@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() {
key_index++;
}
ring_analog_device = Common::Input::CreateDevice<Common::Input::InputDevice>(ring_params);
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
@ -120,6 +123,13 @@ void EmulatedDevices::ReloadInput() {
},
});
}
if (ring_analog_device) {
ring_analog_device->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
});
}
}
void EmulatedDevices::UnloadInput() {
@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
Settings::values.ringcon_analogs = ring_params.Serialize();
}
void EmulatedDevices::RestoreConfig() {
@ -164,6 +175,15 @@ void EmulatedDevices::RestoreConfig() {
ReloadFromSettings();
}
Common::ParamPackage EmulatedDevices::GetRingParam() const {
return ring_params;
}
void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
ring_params = std::move(param);
ReloadInput();
}
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.keyboard_values.size()) {
@ -410,6 +430,23 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
std::lock_guard lock{mutex};
const auto force_value = TransformToStick(callback);
device_status.ring_analog_value = force_value.x;
if (is_configuring) {
device_status.ring_analog_value = {};
TriggerOnChange(DeviceTriggerType::RingController);
return;
}
device_status.ring_analog_state.force = force_value.x.value;
TriggerOnChange(DeviceTriggerType::RingController);
}
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_values;
@ -425,6 +462,10 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
return device_status.mouse_button_values;
}
RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
return device_status.ring_analog_value;
}
KeyboardKey EmulatedDevices::GetKeyboard() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_state;
@ -450,6 +491,10 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
return device_status.mouse_wheel_state;
}
RingSensorForce EmulatedDevices::GetRingSensorForce() const {
return device_status.ring_analog_state;
}
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {

View File

@ -26,9 +26,11 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
using RingAnalogParams = Common::ParamPackage;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@ -39,12 +41,17 @@ using MouseButtonValues =
using MouseAnalogValues =
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickValue = Common::Input::TouchStatus;
using RingAnalogValue = Common::Input::AnalogStatus;
struct MousePosition {
f32 x;
f32 y;
};
struct RingSensorForce {
f32 force;
};
struct DeviceStatus {
// Data from input_common
KeyboardValues keyboard_values{};
@ -52,6 +59,7 @@ struct DeviceStatus {
MouseButtonValues mouse_button_values{};
MouseAnalogValues mouse_analog_values{};
MouseStickValue mouse_stick_value{};
RingAnalogValue ring_analog_value{};
// Data for HID serices
KeyboardKey keyboard_state{};
@ -59,12 +67,14 @@ struct DeviceStatus {
MouseButton mouse_button_state{};
MousePosition mouse_position_state{};
AnalogStickState mouse_wheel_state{};
RingSensorForce ring_analog_state{};
};
enum class DeviceTriggerType {
Keyboard,
KeyboardModdifier,
Mouse,
RingController,
};
struct InterfaceUpdateCallback {
@ -110,6 +120,15 @@ public:
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
// Returns the current mapped ring device
Common::ParamPackage GetRingParam() const;
/**
* Updates the current mapped ring device
* @param param ParamPackage with ring sensor data to be mapped
*/
void SetRingParam(Common::ParamPackage param);
/// Returns the latest status of button input from the keyboard with parameters
KeyboardValues GetKeyboardValues() const;
@ -119,6 +138,9 @@ public:
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
/// Returns the latest status of analog input from the ring sensor with parameters
RingAnalogValue GetRingSensorValues() const;
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
@ -134,6 +156,9 @@ public:
/// Returns the latest mouse wheel change
AnalogStickState GetMouseWheel() const;
/// Returns the latest ringcon force sensor value
RingSensorForce GetRingSensorForce() const;
/**
* Adds a callback to the list of events
* @param update_callback InterfaceUpdateCallback that will be triggered
@ -185,6 +210,12 @@ private:
*/
void SetMouseStick(const Common::Input::CallbackStatus& callback);
/**
* Updates the ring analog sensor status of the ring controller
* @param callback A CallbackStatus containing the force status
*/
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
/**
* Triggers a callback that something has changed on the device status
* @param type Input type of the event to trigger
@ -193,11 +224,14 @@ private:
bool is_configuring{false};
RingAnalogParams ring_params;
KeyboardDevices keyboard_devices;
KeyboardModifierDevices keyboard_modifier_devices;
MouseButtonDevices mouse_button_devices;
MouseAnalogDevices mouse_analog_devices;
MouseStickDevice mouse_stick_device;
RingAnalogDevice ring_analog_device;
mutable std::mutex mutex;
mutable std::mutex callback_mutex;

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
@ -190,6 +191,7 @@ void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(is_attached);
return;
}
LOG_ERROR(Service_HID, "Invalid handle");
@ -217,7 +219,7 @@ void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
const auto entry_index = devices[device_index.value()].handle.internal_index;
auto& cur_entry = hidbus_status.entries[entry_index];
if (bus_handle_.internal_index == 0) {
if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
MakeDevice<RingController>(bus_handle_);
devices[device_index.value()].is_device_initializated = true;
devices[device_index.value()].device->ActivateDevice();

View File

@ -12,7 +12,7 @@ namespace Service::HID {
HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
: service_context(service_context_) {
send_command_asyc_event = service_context.CreateEvent("hidbus:SendCommandAsycEvent");
send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
}
HidbusBase::~HidbusBase() = default;
@ -66,7 +66,7 @@ void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
}
Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
return send_command_asyc_event->GetReadableEvent();
return send_command_async_event->GetReadableEvent();
}
} // namespace Service::HID

View File

@ -165,6 +165,7 @@ protected:
bool device_enabled{};
bool polling_mode_enabled{};
JoyPollingMode polling_mode = {};
// TODO(German77): All data accessors need to be replaced with a ring lifo object
JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
ButtonOnlyPollingDataAccessor button_only_data{};
@ -172,7 +173,7 @@ protected:
u8* transfer_memory{nullptr};
bool is_transfer_memory_set{};
Kernel::KEvent* send_command_asyc_event;
Kernel::KEvent* send_command_async_event;
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID

View File

@ -2,7 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hid/emulated_controller.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
@ -13,9 +13,7 @@ namespace Service::HID {
RingController::RingController(Core::HID::HIDCore& hid_core_,
KernelHelpers::ServiceContext& service_context_)
: HidbusBase(service_context_) {
// Use the horizontal axis of left stick for emulating input
// There is no point on adding a frontend implementation since Ring Fit Adventure doesn't work
input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
input = hid_core_.GetEmulatedDevices();
}
RingController::~RingController() = default;
@ -41,6 +39,8 @@ void RingController::OnUpdate() {
return;
}
// TODO: Increment multitasking counters from motion and sensor data
switch (polling_mode) {
case JoyPollingMode::SixAxisSensorEnable: {
enable_sixaxis_data.header.total_entries = 10;
@ -74,9 +74,8 @@ RingController::RingConData RingController::GetSensorValue() const {
.data = 0,
};
const f32 stick_value = static_cast<f32>(input->GetSticks().left.x) / 32767.0f;
ringcon_sensor_value.data = static_cast<s16>(stick_value * range) + idle_value;
const f32 force_value = input->GetRingSensorForce().force * range;
ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
return ringcon_sensor_value;
}
@ -105,6 +104,8 @@ std::vector<u8> RingController::GetReply() const {
return GetReadRepCountReply();
case RingConCommands::ReadTotalPushCount:
return GetReadTotalPushCountReply();
case RingConCommands::ResetRepCount:
return GetResetRepCountReply();
case RingConCommands::SaveCalData:
return GetSaveDataReply();
default:
@ -119,36 +120,9 @@ bool RingController::SetCommand(const std::vector<u8>& data) {
return false;
}
// There must be a better way to do this
const u32 command_id =
u32{data[0]} + (u32{data[1]} << 8) + (u32{data[2]} << 16) + (u32{data[3]} << 24);
static constexpr std::array supported_commands = {
RingConCommands::GetFirmwareVersion,
RingConCommands::ReadId,
RingConCommands::c20105,
RingConCommands::ReadUnkCal,
RingConCommands::ReadFactoryCal,
RingConCommands::ReadUserCal,
RingConCommands::ReadRepCount,
RingConCommands::ReadTotalPushCount,
RingConCommands::SaveCalData,
};
std::memcpy(&command, data.data(), sizeof(RingConCommands));
for (RingConCommands cmd : supported_commands) {
if (command_id == static_cast<u32>(cmd)) {
return ExcecuteCommand(cmd, data);
}
}
LOG_ERROR(Service_HID, "Command not implemented {}", command_id);
command = RingConCommands::Error;
// Signal a reply to avoid softlocking
send_command_asyc_event->GetWritableEvent().Signal();
return false;
}
bool RingController::ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data) {
switch (cmd) {
switch (command) {
case RingConCommands::GetFirmwareVersion:
case RingConCommands::ReadId:
case RingConCommands::c20105:
@ -158,23 +132,27 @@ bool RingController::ExcecuteCommand(RingConCommands cmd, const std::vector<u8>&
case RingConCommands::ReadRepCount:
case RingConCommands::ReadTotalPushCount:
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
command = cmd;
send_command_asyc_event->GetWritableEvent().Signal();
send_command_async_event->GetWritableEvent().Signal();
return true;
case RingConCommands::ResetRepCount:
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
total_rep_count = 0;
send_command_async_event->GetWritableEvent().Signal();
return true;
case RingConCommands::SaveCalData: {
ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
SaveCalData save_info{};
std::memcpy(&save_info, &data, sizeof(SaveCalData));
std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
user_calibration = save_info.calibration;
command = cmd;
send_command_asyc_event->GetWritableEvent().Signal();
send_command_async_event->GetWritableEvent().Signal();
return true;
}
default:
LOG_ERROR(Service_HID, "Command not implemented {}", cmd);
LOG_ERROR(Service_HID, "Command not implemented {}", command);
command = RingConCommands::Error;
// Signal a reply to avoid softlocking the game
send_command_async_event->GetWritableEvent().Signal();
return false;
}
}
@ -240,27 +218,29 @@ std::vector<u8> RingController::GetReadUserCalReply() const {
}
std::vector<u8> RingController::GetReadRepCountReply() const {
// The values are hardcoded from a real joycon
const GetThreeByteReply reply{
.status = DataValid::Valid,
.data = {30, 0, 0},
.crc = GetCrcValue({30, 0, 0, 0}),
.data = {total_rep_count, 0, 0},
.crc = GetCrcValue({total_rep_count, 0, 0, 0}),
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadTotalPushCountReply() const {
// The values are hardcoded from a real joycon
const GetThreeByteReply reply{
.status = DataValid::Valid,
.data = {30, 0, 0},
.crc = GetCrcValue({30, 0, 0, 0}),
.data = {total_push_count, 0, 0},
.crc = GetCrcValue({total_push_count, 0, 0, 0}),
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetResetRepCountReply() const {
return GetReadRepCountReply();
}
std::vector<u8> RingController::GetSaveDataReply() const {
const StatusReply reply{
.status = DataValid::Valid,

View File

@ -10,7 +10,7 @@
#include "core/hle/service/hid/hidbus/hidbus_base.h"
namespace Core::HID {
class EmulatedController;
class EmulatedDevices;
} // namespace Core::HID
namespace Service::HID {
@ -43,6 +43,7 @@ private:
static constexpr s16 idle_deadzone = 120;
static constexpr s16 range = 2500;
// Most missing command names are leftovers from other firmware versions
enum class RingConCommands : u32 {
GetFirmwareVersion = 0x00020000,
ReadId = 0x00020100,
@ -60,10 +61,10 @@ private:
ReadUserCal = 0x00021A04,
ReadRepCount = 0x00023104,
ReadTotalPushCount = 0x00023204,
Unknown9 = 0x04013104,
Unknown10 = 0x04011104,
Unknown11 = 0x04011204,
Unknown12 = 0x04011304,
ResetRepCount = 0x04013104,
Unknown8 = 0x04011104,
Unknown9 = 0x04011204,
Unknown10 = 0x04011304,
SaveCalData = 0x10011A04,
Error = 0xFFFFFFFF,
};
@ -180,9 +181,6 @@ private:
};
static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
// Executes the command requested
bool ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data);
// Returns RingConData struct with pressure sensor values
RingConData GetSensorValue() const;
@ -204,12 +202,15 @@ private:
// Returns 20 byte reply with user calibration values
std::vector<u8> GetReadUserCalReply() const;
// (STUBBED) Returns 8 byte reply
// Returns 8 byte reply
std::vector<u8> GetReadRepCountReply() const;
// (STUBBED) Returns 8 byte reply
// Returns 8 byte reply
std::vector<u8> GetReadTotalPushCountReply() const;
// Returns 8 byte reply
std::vector<u8> GetResetRepCountReply() const;
// Returns 4 byte save data reply
std::vector<u8> GetSaveDataReply() const;
@ -225,6 +226,12 @@ private:
RingConCommands command{RingConCommands::Error};
// These counters are used in multitasking mode while the switch is sleeping
// Total steps taken
u8 total_rep_count = 0;
// Total times the ring was pushed
u8 total_push_count = 0;
const u8 device_id = 0x20;
const FirmwareVersion version = {
.sub = 0x0,
@ -242,6 +249,6 @@ private:
.zero = {.value = idle_value, .crc = 225},
};
Core::HID::EmulatedController* input;
Core::HID::EmulatedDevices* input;
};
} // namespace Service::HID

View File

@ -99,6 +99,9 @@ add_executable(yuzu
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_profile_manager.ui
configuration/configure_ringcon.cpp
configuration/configure_ringcon.h
configuration/configure_ringcon.ui
configuration/configure_network.cpp
configuration/configure_network.h
configuration/configure_network.ui

View File

@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = {
0,
};
const std::array<int, 2> Config::default_ringcon_analogs{{
Qt::Key_A,
Qt::Key_D,
}};
// 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
@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() {
ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
}
void Config::ReadHidbusValues() {
Settings::values.enable_ring_controller =
ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
auto& ringcon_analogs = Settings::values.ringcon_analogs;
ringcon_analogs =
qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
.toString()
.toStdString();
if (ringcon_analogs.empty()) {
ringcon_analogs = default_param;
}
}
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
@ -369,6 +391,7 @@ void Config::ReadControlValues() {
ReadMouseValues();
ReadTouchscreenValues();
ReadMotionTouchValues();
ReadHidbusValues();
#ifdef _WIN32
ReadBasicSetting(Settings::values.enable_raw_input);
@ -962,6 +985,16 @@ void Config::SaveMotionTouchValues() {
qt_config->endArray();
}
void Config::SaveHidbusValues() {
WriteBasicSetting(Settings::values.enable_ring_controller);
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
WriteSetting(QStringLiteral("ring_controller"),
QString::fromStdString(Settings::values.ringcon_analogs),
QString::fromStdString(default_param));
}
void Config::SaveValues() {
if (global) {
SaveControlValues();
@ -1002,6 +1035,7 @@ void Config::SaveControlValues() {
SaveMouseValues();
SaveTouchscreenValues();
SaveMotionTouchValues();
SaveHidbusValues();
WriteGlobalSetting(Settings::values.use_docked_mode);
WriteGlobalSetting(Settings::values.vibration_enabled);

View File

@ -42,6 +42,7 @@ public:
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<int, 2> default_stick_mod;
static const std::array<int, 2> default_ringcon_analogs;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@ -66,6 +67,7 @@ private:
void ReadMouseValues();
void ReadTouchscreenValues();
void ReadMotionTouchValues();
void ReadHidbusValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@ -93,6 +95,7 @@ private:
void SaveMouseValues();
void SaveTouchscreenValues();
void SaveMotionTouchValues();
void SaveHidbusValues();
// Save functions based off the respective config section names.
void SaveAudioValues();

View File

@ -20,6 +20,7 @@
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_ringcon.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
[this, input_subsystem] {
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog,
[this, input_subsystem, &hid_core] {
CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
});
connect(ui->vibrationButton, &QPushButton::clicked,
[this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });

View File

@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->debug_configure, &QPushButton::clicked, this,
[this] { CallDebugControllerDialog(); });
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
[this] { CallTouchscreenConfigDialog(); });
connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
&ConfigureInputAdvanced::CallMotionTouchConfigDialog);
[this] { CallMotionTouchConfigDialog(); });
connect(ui->ring_controller_configure, &QPushButton::clicked, this,
[this] { CallRingControllerDialog(); });
#ifndef _WIN32
ui->enable_raw_input->setVisible(false);
@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
UpdateUIEnabled();
}
@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() {
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
}

View File

@ -29,6 +29,7 @@ signals:
void CallMouseConfigDialog();
void CallTouchscreenConfigDialog();
void CallMotionTouchConfigDialog();
void CallRingControllerDialog();
private:
void changeEvent(QEvent* event) override;

View File

@ -2603,6 +2603,20 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="enable_ring_controller">
<property name="text">
<string>Ring Controller</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QPushButton" name="ring_controller_configure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -0,0 +1,424 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <QKeyEvent>
#include <QMenu>
#include <QTimer>
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/main.h"
#include "ui_configure_ringcon.h"
#include "yuzu/bootmanager.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_ringcon.h"
const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
ConfigureRingController::analog_sub_buttons{{
"left",
"right",
}};
namespace {
QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return {};
default:
return QKeySequence(key_code).toString();
}
}
QString GetButtonName(Common::Input::ButtonNames button_name) {
switch (button_name) {
case Common::Input::ButtonNames::ButtonLeft:
return QObject::tr("Left");
case Common::Input::ButtonNames::ButtonRight:
return QObject::tr("Right");
case Common::Input::ButtonNames::ButtonDown:
return QObject::tr("Down");
case Common::Input::ButtonNames::ButtonUp:
return QObject::tr("Up");
case Common::Input::ButtonNames::TriggerZ:
return QObject::tr("Z");
case Common::Input::ButtonNames::TriggerR:
return QObject::tr("R");
case Common::Input::ButtonNames::TriggerL:
return QObject::tr("L");
case Common::Input::ButtonNames::ButtonA:
return QObject::tr("A");
case Common::Input::ButtonNames::ButtonB:
return QObject::tr("B");
case Common::Input::ButtonNames::ButtonX:
return QObject::tr("X");
case Common::Input::ButtonNames::ButtonY:
return QObject::tr("Y");
case Common::Input::ButtonNames::ButtonStart:
return QObject::tr("Start");
case Common::Input::ButtonNames::L1:
return QObject::tr("L1");
case Common::Input::ButtonNames::L2:
return QObject::tr("L2");
case Common::Input::ButtonNames::L3:
return QObject::tr("L3");
case Common::Input::ButtonNames::R1:
return QObject::tr("R1");
case Common::Input::ButtonNames::R2:
return QObject::tr("R2");
case Common::Input::ButtonNames::R3:
return QObject::tr("R3");
case Common::Input::ButtonNames::Circle:
return QObject::tr("Circle");
case Common::Input::ButtonNames::Cross:
return QObject::tr("Cross");
case Common::Input::ButtonNames::Square:
return QObject::tr("Square");
case Common::Input::ButtonNames::Triangle:
return QObject::tr("Triangle");
case Common::Input::ButtonNames::Share:
return QObject::tr("Share");
case Common::Input::ButtonNames::Options:
return QObject::tr("Options");
default:
return QObject::tr("[undefined]");
}
}
void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
const std::string& button_name) {
// The poller returned a complete axis, so set all the buttons
if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
analog_param = input_param;
return;
}
// Check if the current configuration has either no engine or an axis binding.
// Clears out the old binding and adds one with analog_from_button.
if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
analog_param = {
{"engine", "analog_from_button"},
};
}
analog_param.Set(button_name, input_param.Serialize());
}
} // namespace
ConfigureRingController::ConfigureRingController(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem_,
Core::HID::HIDCore& hid_core_)
: QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},
ui(std::make_unique<Ui::ConfigureRingController>()) {
ui->setupUi(this);
analog_map_buttons = {
ui->buttonRingAnalogPull,
ui->buttonRingAnalogPush,
};
emulated_device = hid_core_.GetEmulatedDevices();
emulated_device->SaveCurrentConfig();
emulated_device->EnableConfiguration();
LoadConfiguration();
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[sub_button_id];
if (analog_button == nullptr) {
continue;
}
connect(analog_button, &QPushButton::clicked, [=, this] {
HandleClick(
analog_map_buttons[sub_button_id],
[=, this](const Common::ParamPackage& params) {
Common::ParamPackage param = emulated_device->GetRingParam();
SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
emulated_device->SetRingParam(param);
},
InputCommon::Polling::InputType::Stick);
});
analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
connect(analog_button, &QPushButton::customContextMenuRequested,
[=, this](const QPoint& menu_location) {
QMenu context_menu;
Common::ParamPackage param = emulated_device->GetRingParam();
context_menu.addAction(tr("Clear"), [&] {
emulated_device->SetRingParam({});
analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Invert axis"), [&] {
const bool invert_value = param.Get("invert_x", "+") == "-";
const std::string invert_str = invert_value ? "+" : "-";
param.Set("invert_x", invert_str);
emulated_device->SetRingParam(param);
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
++sub_button_id) {
analog_map_buttons[sub_button_id]->setText(
AnalogToText(param, analog_sub_buttons[sub_button_id]));
}
});
context_menu.exec(
analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
});
}
connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
Common::ParamPackage param = emulated_device->GetRingParam();
const auto slider_value = ui->sliderRingAnalogDeadzone->value();
ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
param.Set("deadzone", slider_value / 100.0f);
emulated_device->SetRingParam(param);
});
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
&ConfigureRingController::RestoreDefaults);
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this] {
const auto& params = input_subsystem->GetNextInput();
if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
});
resize(0, 0);
}
ConfigureRingController::~ConfigureRingController() {
emulated_device->DisableConfiguration();
};
void ConfigureRingController::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureRingController::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureRingController::UpdateUI() {
RetranslateUI();
const Common::ParamPackage param = emulated_device->GetRingParam();
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[sub_button_id];
if (analog_button == nullptr) {
continue;
}
analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
}
const auto deadzone_label = ui->labelRingAnalogDeadzone;
const auto deadzone_slider = ui->sliderRingAnalogDeadzone;
int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
deadzone_slider->setValue(slider_value);
}
void ConfigureRingController::ApplyConfiguration() {
emulated_device->DisableConfiguration();
emulated_device->SaveCurrentConfig();
emulated_device->EnableConfiguration();
}
void ConfigureRingController::LoadConfiguration() {
UpdateUI();
}
void ConfigureRingController::RestoreDefaults() {
const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
UpdateUI();
}
void ConfigureRingController::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::InputType type) {
button->setText(tr("[waiting]"));
button->setFocus();
input_setter = new_input_setter;
input_subsystem->BeginMapping(type);
QWidget::grabMouse();
QWidget::grabKeyboard();
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(25); // Check for new inputs every 25ms
}
void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
input_subsystem->StopMapping();
QWidget::releaseMouse();
QWidget::releaseKeyboard();
if (!abort) {
(*input_setter)(params);
}
UpdateUI();
input_setter = std::nullopt;
}
bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
return true;
}
void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
}
void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
event->ignore();
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
input_subsystem->GetKeyboard()->PressKey(event->key());
}
}
QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
const auto common_button_name = input_subsystem->GetButtonName(param);
// Retrieve the names from Qt
if (param.Get("engine", "") == "keyboard") {
const QString button_str = GetKeyName(param.Get("code", 0));
return QObject::tr("%1%2").arg(toggle, button_str);
}
if (common_button_name == Common::Input::ButtonNames::Invalid) {
return QObject::tr("[invalid]");
}
if (common_button_name == Common::Input::ButtonNames::Engine) {
return QString::fromStdString(param.Get("engine", ""));
}
if (common_button_name == Common::Input::ButtonNames::Value) {
if (param.Has("hat")) {
const QString hat = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
}
if (param.Has("axis")) {
const QString axis = QString::fromStdString(param.Get("axis", ""));
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
}
if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
}
if (param.Has("motion")) {
const QString motion = QString::fromStdString(param.Get("motion", ""));
return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
}
if (param.Has("button")) {
const QString button = QString::fromStdString(param.Get("button", ""));
return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
}
}
QString button_name = GetButtonName(common_button_name);
if (param.Has("hat")) {
return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
}
if (param.Has("axis")) {
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
}
if (param.Has("motion")) {
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
}
if (param.Has("button")) {
return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
}
return QObject::tr("[unknown]");
}
QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
const std::string& dir) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
if (param.Get("engine", "") == "analog_from_button") {
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
}
if (!param.Has("axis_x") || !param.Has("axis_y")) {
return QObject::tr("[unknown]");
}
const auto engine_str = param.Get("engine", "");
const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
const bool invert_x = param.Get("invert_x", "+") == "-";
const bool invert_y = param.Get("invert_y", "+") == "-";
if (dir == "modifier") {
return QObject::tr("[unused]");
}
if (dir == "left") {
const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
}
if (dir == "right") {
const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
}
if (dir == "up") {
const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
}
if (dir == "down") {
const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
}
return QObject::tr("[unknown]");
}

View File

@ -0,0 +1,85 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <QDialog>
namespace InputCommon {
class InputSubsystem;
} // namespace InputCommon
namespace Core::HID {
class HIDCore;
class EmulatedDevices;
} // namespace Core::HID
namespace Ui {
class ConfigureRingController;
} // namespace Ui
class ConfigureRingController : public QDialog {
Q_OBJECT
public:
explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
Core::HID::HIDCore& hid_core_);
~ConfigureRingController() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void UpdateUI();
/// Load configuration settings.
void LoadConfiguration();
/// Restore all buttons to their default values.
void RestoreDefaults();
/// Called when the button was pressed.
void HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> 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 key press events.
void keyPressEvent(QKeyEvent* event) override;
QString ButtonToText(const Common::ParamPackage& param);
QString AnalogToText(const Common::ParamPackage& param, const std::string& dir);
static constexpr int ANALOG_SUB_BUTTONS_NUM = 2;
// A group of four QPushButtons represent one analog input. The buttons each represent left,
// right, respectively.
std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
InputCommon::InputSubsystem* input_subsystem;
Core::HID::EmulatedDevices* emulated_device;
std::unique_ptr<Ui::ConfigureRingController> ui;
};

View File

@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureRingController</class>
<widget class="QDialog" name="ConfigureRingController">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>298</width>
<height>339</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Ring Controller</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>280</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="RingAnalog">
<property name="title">
<string>Ring Sensor Parameters</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout">
<property name="spacing">
<number>3</number>
</property>
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="buttonRingAnalogPullGroup">
<property name="title">
<string>Pull</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout">
<property name="spacing">
<number>3</number>
</property>
<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="QPushButton" name="buttonRingAnalogPull">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Pull</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="buttonRingAnalogPushGroup">
<property name="title">
<string>Push</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout">
<property name="spacing">
<number>3</number>
</property>
<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="QPushButton" name="buttonRingAnalogPush">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Push</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
<item>
<widget class="QLabel" name="labelRingAnalogDeadzone">
<property name="text">
<string>Deadzone: 0%</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSlider" name="sliderRingAnalogDeadzone">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="restore_defaults_button">
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureRingController</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureRingController</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>