Qt: Support axes in input binding widgets

This commit is contained in:
Connor McLaughlin 2020-02-18 00:06:11 +09:00
parent 149cbf6457
commit b7dfe06f74
3 changed files with 204 additions and 82 deletions

View File

@ -6,20 +6,102 @@
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interface, QString setting_name, InputBindingWidget::InputBindingWidget(QtHostInterface* host_interface, QString setting_name, QWidget* parent)
QWidget* parent)
: QPushButton(parent), m_host_interface(host_interface), m_setting_name(std::move(setting_name)) : 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(); m_current_binding_value = m_host_interface->getSettingValue(m_setting_name).toString();
setText(m_current_binding_value); 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() InputButtonBindingWidget::~InputButtonBindingWidget()
{ {
if (isListeningForInput()) if (isListeningForInput())
stopListeningForInput(); InputButtonBindingWidget::stopListeningForInput();
} }
bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event)
@ -41,45 +123,8 @@ bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event)
return true; return true;
} }
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease ||
event_type == QEvent::MouseButtonDblClick)
{
return true;
}
return false; return InputBindingWidget::eventFilter(watched, event);
}
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));
} }
void InputButtonBindingWidget::hookControllerInput() void InputButtonBindingWidget::hookControllerInput()
@ -88,6 +133,10 @@ void InputButtonBindingWidget::hookControllerInput()
g_sdl_controller_interface.SetHook([this](const SDLControllerInterface::Hook& ei) { g_sdl_controller_interface.SetHook([this](const SDLControllerInterface::Hook& ei) {
if (ei.type == SDLControllerInterface::Hook::Type::Axis) 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" // TODO: this probably should consider the "last value"
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); 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() void InputButtonBindingWidget::startListeningForInput()
{ {
m_input_listen_timer = new QTimer(this); InputBindingWidget::startListeningForInput();
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();
hookControllerInput(); hookControllerInput();
} }
void InputButtonBindingWidget::stopListeningForInput() void InputButtonBindingWidget::stopListeningForInput()
{ {
setText(m_current_binding_value);
delete m_input_listen_timer;
m_input_listen_timer = nullptr;
unhookControllerInput(); unhookControllerInput();
releaseMouse(); InputBindingWidget::stopListeningForInput();
releaseKeyboard(); }
removeEventFilter(this);
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();
} }

View File

@ -6,7 +6,37 @@ class QTimer;
class QtHostInterface; 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 Q_OBJECT
@ -18,23 +48,30 @@ protected:
bool eventFilter(QObject* watched, QEvent* event) override; bool eventFilter(QObject* watched, QEvent* event) override;
private Q_SLOTS: private Q_SLOTS:
void onPressed();
void onInputListenTimerTimeout();
void bindToControllerAxis(int controller_index, int axis_index, bool positive); void bindToControllerAxis(int controller_index, int axis_index, bool positive);
void bindToControllerButton(int controller_index, int button_index); void bindToControllerButton(int controller_index, int button_index);
private: protected:
bool isListeningForInput() const { return m_input_listen_timer != nullptr; } void startListeningForInput() override;
void startListeningForInput(); void stopListeningForInput() override;
void stopListeningForInput(); void hookControllerInput();
void setNewBinding(); 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 hookControllerInput();
void unhookControllerInput(); 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;
}; };

View File

@ -5,8 +5,8 @@
#include "core/host_interface.h" #include "core/host_interface.h"
#include "core/system.h" #include "core/system.h"
#include "sdl_initializer.h" #include "sdl_initializer.h"
#include <cmath>
#include <SDL.h> #include <SDL.h>
#include <cmath>
Log_SetChannel(SDLControllerInterface); Log_SetChannel(SDLControllerInterface);
SDLControllerInterface g_sdl_controller_interface; SDLControllerInterface g_sdl_controller_interface;
@ -330,16 +330,13 @@ void SDLControllerInterface::SetDefaultBindings()
bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev) 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. // TODO: Make deadzone customizable.
static constexpr float deadzone = 8192.0f / 32768.0f; static constexpr float deadzone = 8192.0f / 32768.0f;
const float value = static_cast<float>(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f); const float value = static_cast<float>(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 (DoEventHook(Hook::Type::Axis, ev->caxis.which, ev->caxis.axis, value))
if (outside_deadzone && DoEventHook(Hook::Type::Axis, ev->caxis.which, ev->caxis.axis, value))
return true; return true;
auto it = m_controllers.find(ev->caxis.which); 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 // 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 bool positive = (value >= 0.0f);
const ButtonCallback& other_button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(!positive)]; 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)]; const ButtonCallback& button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(positive)];