#include "inputbindingmonitor.h" #include ControllerInterface::Hook::CallbackResult InputButtonBindingMonitor::operator()(const ControllerInterface::Hook& ei) const { if (ei.type == ControllerInterface::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(std::get(ei.value)) < 0.5f) return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, false), Q_ARG(std::optional, std::get(ei.value) > 0)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && std::get(ei.value) > 0.0f) { QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Hat) { const std::string_view hat_position = std::get(ei.value); if (!hat_position.empty()) { QString str = QString::fromLatin1(hat_position.data(), static_cast(hat_position.size())); QMetaObject::invokeMethod(m_parent, "bindToControllerHat", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number), Q_ARG(QString, std::move(str))); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } } return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; } ControllerInterface::Hook::CallbackResult InputAxisBindingMonitor::operator()(const ControllerInterface::Hook& ei) const { if (ei.type == ControllerInterface::Hook::Type::Axis) { std::optional half_axis_positive, inverted; if (!ProcessAxisInput(ei, half_axis_positive, inverted)) return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; QMetaObject::invokeMethod(m_parent, "bindToControllerAxis", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, inverted.value_or(false)), Q_ARG(std::optional, half_axis_positive)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half && std::get(ei.value) > 0.0f) { QMetaObject::invokeMethod(m_parent, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; } bool InputAxisBindingMonitor::ProcessAxisInput(const ControllerInterface::Hook& ei, std::optional& half_axis_positive, std::optional& inverted) const { const float value = std::get(ei.value); if (!ei.track_history) // Keyboard, mouse, game controller { // wait until it's at least half pushed so we don't get confused between axises with small movement if (std::abs(value) < 0.5f) return false; return true; } else // Joystick { auto& history = m_context->m_inputs_history; // Reject inputs coming from multiple sources if (!history.empty()) { const auto& item = history.front(); if (ei.controller_index != item.controller_index || ei.button_or_axis_number != item.axis_number) return false; } history.push_back({ei.controller_index, ei.button_or_axis_number, value}); return AnalyzeInputHistory(half_axis_positive, inverted); } } bool InputAxisBindingMonitor::AnalyzeInputHistory(std::optional& half_axis_positive, std::optional& inverted) const { const auto& history = m_context->m_inputs_history; const auto [min, max] = std::minmax_element( history.begin(), history.end(), [](const auto& left, const auto& right) { return left.value < right.value; }); // Ignore small input magnitudes if (std::abs(max->value - min->value) < 0.5f) return false; // Used heuristics: // * If history contains inputs with both - and + sign (ignoring 0), bind a full axis // * If history contains only 0 and inputs of the same sign AND maxes out at 1.0/-1.0, bind a half axis // * Use the direction of input changes to determine whether the axis is inverted or not if (std::signbit(min->value) != std::signbit(max->value)) { if (min->value != 0.0f && max->value != 0.0f) { // If max value comes before the min value, invert the half axis if (m_axis_type == Controller::AxisType::Half) { inverted = std::distance(min, max) < 0; } return true; } } else { if ((std::abs(min->value) > 0.99f || std::abs(max->value) > 0.99f) && (std::abs(min->value) < 0.01f || std::abs(max->value) < 0.01f)) { if (m_axis_type == Controller::AxisType::Half) { half_axis_positive = max->value > 0.0f; } return true; } } return false; } ControllerInterface::Hook::CallbackResult InputRumbleBindingMonitor::operator()(const ControllerInterface::Hook& ei) const { if (ei.type == ControllerInterface::Hook::Type::Button && std::get(ei.value) > 0.0f) { QMetaObject::invokeMethod(m_parent, "bindToControllerRumble", Q_ARG(int, ei.controller_index)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; }