From b7dfe06f748d3e095153a8a12d74ac45a264d54d Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 18 Feb 2020 00:06:11 +0900 Subject: [PATCH] Qt: Support axes in input binding widgets --- src/duckstation-qt/inputbindingwidgets.cpp | 209 +++++++++++++----- src/duckstation-qt/inputbindingwidgets.h | 67 ++++-- .../sdl_controller_interface.cpp | 10 +- 3 files changed, 204 insertions(+), 82 deletions(-) diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 485dd13eb..ec5f9f87e 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -6,20 +6,102 @@ #include #include -InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, QString setting_name, - QWidget* parent) +InputBindingWidget::InputBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent) : QPushButton(parent), m_host_interface(host_interface), m_setting_name(std::move(setting_name)) { m_current_binding_value = m_host_interface->getSettingValue(m_setting_name).toString(); setText(m_current_binding_value); - connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed); + connect(this, &QPushButton::pressed, this, &InputBindingWidget::onPressed); +} + +InputBindingWidget::~InputBindingWidget() +{ + Q_ASSERT(!isListeningForInput()); +} + +bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event) +{ + const QEvent::Type event_type = event->type(); + + if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || + event_type == QEvent::MouseButtonDblClick) + { + return true; + } + + return false; +} + +void InputBindingWidget::setNewBinding() +{ + if (m_new_binding_value.isEmpty()) + return; + + m_host_interface->putSettingValue(m_setting_name, m_new_binding_value); + m_host_interface->updateInputMap(); + + m_current_binding_value = std::move(m_new_binding_value); + m_new_binding_value.clear(); +} + +void InputBindingWidget::onPressed() +{ + if (isListeningForInput()) + stopListeningForInput(); + + startListeningForInput(); +} + +void InputBindingWidget::onInputListenTimerTimeout() +{ + m_input_listen_remaining_seconds--; + if (m_input_listen_remaining_seconds == 0) + { + stopListeningForInput(); + return; + } + + setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); +} + +void InputBindingWidget::startListeningForInput() +{ + m_input_listen_timer = new QTimer(this); + m_input_listen_timer->setSingleShot(false); + m_input_listen_timer->start(1000); + + m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this, + &InputBindingWidget::onInputListenTimerTimeout); + m_input_listen_remaining_seconds = 5; + setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); + + installEventFilter(this); + grabKeyboard(); + grabMouse(); +} + +void InputBindingWidget::stopListeningForInput() +{ + setText(m_current_binding_value); + delete m_input_listen_timer; + m_input_listen_timer = nullptr; + + releaseMouse(); + releaseKeyboard(); + removeEventFilter(this); +} + +InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, QString setting_name, + QWidget* parent) + : InputBindingWidget(host_interface, setting_name, parent) +{ } InputButtonBindingWidget::~InputButtonBindingWidget() { if (isListeningForInput()) - stopListeningForInput(); + InputButtonBindingWidget::stopListeningForInput(); } bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) @@ -41,45 +123,8 @@ bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) return true; } - else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || - event_type == QEvent::MouseButtonDblClick) - { - return true; - } - return false; -} - -void InputButtonBindingWidget::setNewBinding() -{ - if (m_new_binding_value.isEmpty()) - return; - - m_host_interface->putSettingValue(m_setting_name, m_new_binding_value); - m_host_interface->updateInputMap(); - - m_current_binding_value = std::move(m_new_binding_value); - m_new_binding_value.clear(); -} - -void InputButtonBindingWidget::onPressed() -{ - if (isListeningForInput()) - stopListeningForInput(); - - startListeningForInput(); -} - -void InputButtonBindingWidget::onInputListenTimerTimeout() -{ - m_input_listen_remaining_seconds--; - if (m_input_listen_remaining_seconds == 0) - { - stopListeningForInput(); - return; - } - - setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds)); + return InputBindingWidget::eventFilter(watched, event); } void InputButtonBindingWidget::hookControllerInput() @@ -88,6 +133,10 @@ void InputButtonBindingWidget::hookControllerInput() g_sdl_controller_interface.SetHook([this](const SDLControllerInterface::Hook& ei) { if (ei.type == SDLControllerInterface::Hook::Type::Axis) { + // wait until it's at least half pushed so we don't get confused between axises with small movement + if (std::abs(ei.value) < 0.5f) + return SDLControllerInterface::Hook::CallbackResult::ContinueMonitoring; + // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); @@ -127,29 +176,67 @@ void InputButtonBindingWidget::bindToControllerButton(int controller_index, int void InputButtonBindingWidget::startListeningForInput() { - m_input_listen_timer = new QTimer(this); - m_input_listen_timer->setSingleShot(false); - m_input_listen_timer->start(1000); - - m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this, - &InputButtonBindingWidget::onInputListenTimerTimeout); - m_input_listen_remaining_seconds = 5; - setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds)); - - installEventFilter(this); - grabKeyboard(); - grabMouse(); + InputBindingWidget::startListeningForInput(); hookControllerInput(); } void InputButtonBindingWidget::stopListeningForInput() { - setText(m_current_binding_value); - delete m_input_listen_timer; - m_input_listen_timer = nullptr; - unhookControllerInput(); - releaseMouse(); - releaseKeyboard(); - removeEventFilter(this); + InputBindingWidget::stopListeningForInput(); +} + +InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent) + : InputBindingWidget(host_interface, setting_name, parent) +{ +} + +InputAxisBindingWidget::~InputAxisBindingWidget() +{ + if (isListeningForInput()) + InputAxisBindingWidget::stopListeningForInput(); +} + +void InputAxisBindingWidget::hookControllerInput() +{ + m_host_interface->enableBackgroundControllerPolling(); + g_sdl_controller_interface.SetHook([this](const SDLControllerInterface::Hook& ei) { + if (ei.type == SDLControllerInterface::Hook::Type::Axis) + { + // wait until it's at least half pushed so we don't get confused between axises with small movement + if (std::abs(ei.value) < 0.5f) + return SDLControllerInterface::Hook::CallbackResult::ContinueMonitoring; + + QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), + Q_ARG(int, ei.button_or_axis_number)); + return SDLControllerInterface::Hook::CallbackResult::StopMonitoring; + } + + return SDLControllerInterface::Hook::CallbackResult::ContinueMonitoring; + }); +} + +void InputAxisBindingWidget::unhookControllerInput() +{ + g_sdl_controller_interface.ClearHook(); + m_host_interface->disableBackgroundControllerPolling(); +} + +void InputAxisBindingWidget::bindToControllerAxis(int controller_index, int axis_index) +{ + m_new_binding_value = QStringLiteral("Controller%1/Axis%2").arg(controller_index).arg(axis_index); + setNewBinding(); + stopListeningForInput(); +} + +void InputAxisBindingWidget::startListeningForInput() +{ + InputBindingWidget::startListeningForInput(); + hookControllerInput(); +} + +void InputAxisBindingWidget::stopListeningForInput() +{ + unhookControllerInput(); + InputBindingWidget::stopListeningForInput(); } diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index e8d1c392d..865bad197 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -6,7 +6,37 @@ class QTimer; class QtHostInterface; -class InputButtonBindingWidget : public QPushButton +class InputBindingWidget : public QPushButton +{ + Q_OBJECT + +public: + InputBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent); + ~InputBindingWidget(); + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event) override; + +protected Q_SLOTS: + void onPressed(); + void onInputListenTimerTimeout(); + +protected: + virtual void startListeningForInput(); + virtual void stopListeningForInput(); + + bool isListeningForInput() const { return m_input_listen_timer != nullptr; } + void setNewBinding(); + + QtHostInterface* m_host_interface; + QString m_setting_name; + QString m_current_binding_value; + QString m_new_binding_value; + QTimer* m_input_listen_timer = nullptr; + u32 m_input_listen_remaining_seconds = 0; +}; + +class InputButtonBindingWidget : public InputBindingWidget { Q_OBJECT @@ -18,23 +48,30 @@ protected: bool eventFilter(QObject* watched, QEvent* event) override; private Q_SLOTS: - void onPressed(); - void onInputListenTimerTimeout(); void bindToControllerAxis(int controller_index, int axis_index, bool positive); void bindToControllerButton(int controller_index, int button_index); -private: - bool isListeningForInput() const { return m_input_listen_timer != nullptr; } - void startListeningForInput(); - void stopListeningForInput(); - void setNewBinding(); +protected: + void startListeningForInput() override; + void stopListeningForInput() override; + void hookControllerInput(); + void unhookControllerInput(); +}; + +class InputAxisBindingWidget : public InputBindingWidget +{ + Q_OBJECT + +public: + InputAxisBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent); + ~InputAxisBindingWidget(); + +private Q_SLOTS: + void bindToControllerAxis(int controller_index, int axis_index); + +protected: + void startListeningForInput() override; + void stopListeningForInput() override; void hookControllerInput(); void unhookControllerInput(); - - QtHostInterface* m_host_interface; - QString m_setting_name; - QString m_current_binding_value; - QString m_new_binding_value; - QTimer* m_input_listen_timer = nullptr; - u32 m_input_listen_remaining_seconds = 0; }; diff --git a/src/frontend-common/sdl_controller_interface.cpp b/src/frontend-common/sdl_controller_interface.cpp index 589d54bce..ebb0173e0 100644 --- a/src/frontend-common/sdl_controller_interface.cpp +++ b/src/frontend-common/sdl_controller_interface.cpp @@ -5,8 +5,8 @@ #include "core/host_interface.h" #include "core/system.h" #include "sdl_initializer.h" -#include #include +#include Log_SetChannel(SDLControllerInterface); SDLControllerInterface g_sdl_controller_interface; @@ -330,16 +330,13 @@ void SDLControllerInterface::SetDefaultBindings() bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev) { - Log_DebugPrintf("controller %d axis %d %d", ev->caxis.which, ev->caxis.axis, ev->caxis.value); - // TODO: Make deadzone customizable. static constexpr float deadzone = 8192.0f / 32768.0f; const float value = static_cast(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f); - const bool outside_deadzone = (std::abs(value) >= deadzone); + Log_DebugPrintf("controller %d axis %d %d %f", ev->caxis.which, ev->caxis.axis, ev->caxis.value, value); - // only send monitor events if it's outside of the deadzone, otherwise it's really hard to bind - if (outside_deadzone && DoEventHook(Hook::Type::Axis, ev->caxis.which, ev->caxis.axis, value)) + if (DoEventHook(Hook::Type::Axis, ev->caxis.which, ev->caxis.axis, value)) return true; auto it = m_controllers.find(ev->caxis.which); @@ -355,6 +352,7 @@ bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev) } // set the other direction to false so large movements don't leave the opposite on + const bool outside_deadzone = (std::abs(value) >= deadzone); const bool positive = (value >= 0.0f); const ButtonCallback& other_button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(!positive)]; const ButtonCallback& button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(positive)];