mirror of https://github.com/PCSX2/pcsx2.git
InputManager: Support for inverted bindings (i.e. pedals)
This commit is contained in:
parent
7cbede9190
commit
9388c483ec
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
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));
|
||||
}
|
||||
|
||||
for (InputBindingKey other_key : s_input_binding_new_bindings)
|
||||
{
|
||||
if (other_key.MaskDirection() == key.MaskDirection())
|
||||
{
|
||||
if (abs_value < 0.5f)
|
||||
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())
|
||||
{
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue