InputManager: Support for inverted bindings (i.e. pedals)

This commit is contained in:
Connor McLaughlin 2022-12-17 22:10:33 +10:00 committed by refractionpcsx2
parent 7cbede9190
commit 9388c483ec
10 changed files with 132 additions and 33 deletions

View File

@ -152,6 +152,7 @@ void InputBindingDialog::onInputListenTimerTimeout()
void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{
m_value_ranges.clear();
m_new_bindings.clear();
m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled();
m_input_listen_start_position = QCursor::pos();
@ -266,14 +267,36 @@ void InputBindingDialog::saveListToSettings()
void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value)
{
const float abs_value = std::abs(value);
if (!isListeningForInput())
return;
for (InputBindingKey other_key : m_new_bindings)
float initial_value = value;
float min_value = value;
auto it = std::find_if(m_value_ranges.begin(), m_value_ranges.end(), [key](const auto& it) { return it.first.bits == key.bits; });
if (it != m_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
m_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
const float abs_value = std::abs(value);
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
for (InputBindingKey& other_key : m_new_bindings)
{
if (other_key.MaskDirection() == key.MaskDirection())
{
if (abs_value < 0.5f)
// for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
// if this key is in our new binding list, it's a "release", and we're done
addNewBinding();
stopListeningForInput();
@ -286,10 +309,11 @@ void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float val
}
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f)
if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{
InputBindingKey key_to_add = key;
key_to_add.modifier = value < 0.0f ? InputModifier::Negate : InputModifier::None;
key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
m_new_bindings.push_back(key_to_add);
}
}

View File

@ -20,6 +20,7 @@
#include <QtWidgets/QDialog>
#include <optional>
#include <string>
#include <utility>
#include <vector>
class SettingsInterface;
@ -68,6 +69,7 @@ protected:
std::string m_key_name;
std::vector<std::string> m_bindings;
std::vector<InputBindingKey> m_new_bindings;
std::vector<std::pair<InputBindingKey, std::pair<float, float>>> m_value_ranges;
QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0;

View File

@ -300,6 +300,7 @@ void InputBindingWidget::onInputListenTimerTimeout()
void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
{
m_value_ranges.clear();
m_new_bindings.clear();
m_mouse_mapping_enabled = isMouseMappingEnabled();
m_input_listen_start_position = QCursor::pos();
@ -334,14 +335,36 @@ void InputBindingWidget::stopListeningForInput()
void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float value)
{
const float abs_value = std::abs(value);
if (!isListeningForInput())
return;
for (InputBindingKey other_key : m_new_bindings)
float initial_value = value;
float min_value = value;
auto it = std::find_if(m_value_ranges.begin(), m_value_ranges.end(), [key](const auto& it) { return it.first.bits == key.bits; });
if (it != m_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
m_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
const float abs_value = std::abs(value);
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
for (InputBindingKey& other_key : m_new_bindings)
{
if (other_key.MaskDirection() == key.MaskDirection())
{
if (abs_value < 0.5f)
// for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
// if this key is in our new binding list, it's a "release", and we're done
setNewBinding();
stopListeningForInput();
@ -353,11 +376,13 @@ void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float val
}
}
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f)
if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{
InputBindingKey key_to_add = key;
key_to_add.modifier = value < 0.0f ? InputModifier::Negate : InputModifier::None;
key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
m_new_bindings.push_back(key_to_add);
}
}

View File

@ -19,6 +19,8 @@
#include "pcsx2/Config.h"
#include <QtWidgets/QPushButton>
#include <optional>
#include <utility>
#include <vector>
class QTimer;
@ -76,6 +78,7 @@ protected:
std::string m_key_name;
std::vector<std::string> m_bindings;
std::vector<InputBindingKey> m_new_bindings;
std::vector<std::pair<InputBindingKey, std::pair<float, float>>> m_value_ranges;
QTimer* m_input_listen_timer = nullptr;
u32 m_input_listen_remaining_seconds = 0;
QPoint m_input_listen_start_position{};

View File

@ -51,7 +51,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_DISCORD_PRESENCE;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_DISCORD_PRESENCE;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;SDL_BUILD;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>

View File

@ -339,13 +339,28 @@ std::optional<InputBindingKey> DInputSource::ParseKeyString(const std::string_vi
if (StringUtil::StartsWith(binding, "+Axis") || StringUtil::StartsWith(binding, "-Axis"))
{
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5));
std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5), 10, &end);
if (!axis_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value();
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
else if (StringUtil::StartsWith(binding, "FullAxis"))
{
std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(8), 10, &end);
if (!axis_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value();
key.modifier = InputModifier::FullAxis;
key.invert = (end == "~");
return key;
}
else if (StringUtil::StartsWith(binding, "Hat"))
@ -391,8 +406,8 @@ std::string DInputSource::ConvertKeyToString(InputBindingKey key)
{
if (key.source_subtype == InputSubclass::ControllerAxis)
{
ret =
fmt::format("DInput-{}/{}Axis{}", u32(key.source_index), key.modifier == InputModifier::Negate ? '-' : '+', u32(key.data));
const char* modifier = (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
ret = fmt::format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), key.invert ? "~" : "");
}
else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS)
{

View File

@ -57,6 +57,8 @@
#include <array>
#include <bitset>
#include <thread>
#include <utility>
#include <vector>
#ifdef ENABLE_ACHIEVEMENTS
#include "Frontend/Achievements.h"
@ -372,6 +374,7 @@ namespace FullscreenUI
static std::string s_input_binding_key;
static std::string s_input_binding_display_name;
static std::vector<InputBindingKey> s_input_binding_new_bindings;
static std::vector<std::pair<InputBindingKey, std::pair<float, float>>> s_input_binding_value_ranges;
static Common::Timer s_input_binding_timer;
//////////////////////////////////////////////////////////////////////////
@ -1266,6 +1269,7 @@ void FullscreenUI::ClearInputBindingVariables()
s_input_binding_key = {};
s_input_binding_display_name = {};
s_input_binding_new_bindings = {};
s_input_binding_value_ranges = {};
}
void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const std::string_view& section,
@ -1282,23 +1286,47 @@ void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::T
s_input_binding_key = key;
s_input_binding_display_name = display_name;
s_input_binding_new_bindings = {};
s_input_binding_value_ranges = {};
s_input_binding_timer.Reset();
const bool game_settings = IsEditingGameSettings(bsi);
InputManager::SetHook([game_settings](InputBindingKey key, float value) -> InputInterceptHook::CallbackResult {
if (s_input_binding_type == InputBindingInfo::Type::Unknown)
return InputInterceptHook::CallbackResult::StopProcessingEvent;
// holding the settings lock here will protect the input binding list
auto lock = Host::GetSettingsLock();
const float abs_value = std::abs(value);
for (InputBindingKey other_key : s_input_binding_new_bindings)
float initial_value = value;
float min_value = value;
auto it = std::find_if(s_input_binding_value_ranges.begin(), s_input_binding_value_ranges.end(),
[key](const auto& it) { return it.first.bits == key.bits; });
if (it != s_input_binding_value_ranges.end())
{
initial_value = it->second.first;
min_value = it->second.second = std::min(it->second.second, value);
}
else
{
s_input_binding_value_ranges.emplace_back(key, std::make_pair(initial_value, min_value));
}
const float abs_value = std::abs(value);
const bool reverse_threshold = (key.source_subtype == InputSubclass::ControllerAxis && initial_value > 0.5f);
for (InputBindingKey& other_key : s_input_binding_new_bindings)
{
// if this key is in our new binding list, it's a "release", and we're done
if (other_key.MaskDirection() == key.MaskDirection())
{
if (abs_value < 0.5f)
// for pedals, we wait for it to go back to near its starting point to commit the binding
if ((reverse_threshold ? ((initial_value - value) <= 0.25f) : (abs_value < 0.5f)))
{
// if this key is in our new binding list, it's a "release", and we're done
// did we go the full range?
if (reverse_threshold && initial_value > 0.5f && min_value <= -0.5f)
other_key.modifier = InputModifier::FullAxis;
SettingsInterface* bsi = GetEditingSettingsInterface(game_settings);
const std::string new_binding(InputManager::ConvertInputBindingKeysToString(
s_input_binding_type, s_input_binding_new_bindings.data(), s_input_binding_new_bindings.size()));
@ -1314,10 +1342,11 @@ void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::T
}
// new binding, add it to the list, but wait for a decent distance first, and then wait for release
if (abs_value >= 0.5f)
if ((reverse_threshold ? (abs_value < 0.5f) : (abs_value >= 0.5f)))
{
InputBindingKey key_to_add = key;
key_to_add.modifier = (value < 0.0f) ? InputModifier::Negate : InputModifier::None;
key_to_add.modifier = (value < 0.0f && !reverse_threshold) ? InputModifier::Negate : InputModifier::None;
key_to_add.invert = reverse_threshold;
s_input_binding_new_bindings.push_back(key_to_add);
}

View File

@ -834,6 +834,9 @@ bool InputManager::ProcessEvent(InputBindingKey key, float value, bool skip_butt
break;
}
// handle inverting, needed for some wheels.
value_to_pass = binding->keys[i].invert ? (1.0f - value_to_pass) : value_to_pass;
// axes are fired regardless of a state change, unless they're zero
// (but going from not-zero to zero will still fire, because of the full state)
// for buttons, we can use the state of the last chord key, because it'll be 1 on press,

View File

@ -74,7 +74,8 @@ union InputBindingKey
u32 source_index : 8; ///< controller number
InputSubclass source_subtype : 2; ///< if 1, binding is for an axis and not a button (used for controllers)
InputModifier modifier : 2;
u32 unused : 16;
u32 invert : 1; ///< if 1, value is inverted prior to being sent to the sink
u32 unused : 15;
u32 data;
};
@ -90,6 +91,7 @@ union InputBindingKey
InputBindingKey r;
r.bits = bits;
r.modifier = InputModifier::None;
r.invert = 0;
return r;
}
};

View File

@ -265,11 +265,13 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_
if (StringUtil::StartsWith(axis_name, "Axis"))
{
if (auto value = StringUtil::FromChars<u32>(axis_name.substr(4)))
std::string_view end;
if (auto value = StringUtil::FromChars<u32>(axis_name.substr(4), 10, &end))
{
key.source_subtype = InputSubclass::ControllerAxis;
key.data = *value;
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
}
@ -348,17 +350,11 @@ std::string SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
if (key.source_subtype == InputSubclass::ControllerAxis)
{
const char* modifier = key.modifier == InputModifier::Negate ? "-" : "+";
const char* modifier = (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
if (key.data < std::size(s_sdl_axis_names))
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%s%s", key.source_index, modifier, s_sdl_axis_names[key.data]);
}
else
{
if (key.modifier == InputModifier::FullAxis)
modifier = "Full";
ret = StringUtil::StdStringFromFormat("SDL-%u/%sAxis%u", key.source_index, modifier, key.data);
}
ret = StringUtil::StdStringFromFormat("SDL-%u/%sAxis%u%s", key.source_index, modifier, key.data, key.invert ? "~" : "");
}
else if (key.source_subtype == InputSubclass::ControllerButton)
{
@ -685,7 +681,7 @@ bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev)
if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button])
return false; // Will get handled by GC event
const u32 button = ev->button + std::size(s_sdl_button_names); // Ensure we don't conflict with GC buttons
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, button));
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button));
InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f);
return true;
}
@ -797,7 +793,7 @@ bool SDLInputSource::GetGenericBindingMapping(const std::string_view& device, In
}
else
{
// joysticks, which we haven't implemented yet anyway.
// joysticks have arbitrary axis numbers, so automapping isn't going to work here.
return false;
}
}