InputCommon: Support detecting combinations of inputs. (Hotkeys)

This commit is contained in:
Jordan Woyak 2019-10-23 17:20:42 -05:00
parent e6ba495486
commit b3acc7403f
3 changed files with 44 additions and 15 deletions

View file

@ -68,7 +68,7 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev
// Avoid that the button press itself is registered as an event // Avoid that the button press itself is registered as an event
Common::SleepCurrentThread(50); Common::SleepCurrentThread(50);
const auto [device, input] = device_container.DetectInput(INPUT_DETECT_TIME, device_strings); const auto detections = device_container.DetectInput(INPUT_DETECT_TIME, device_strings);
const auto timer = new QTimer(button); const auto timer = new QTimer(button);
@ -83,14 +83,24 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev
button->setText(old_text); button->setText(old_text);
if (!input) QString full_expression;
return {};
ciface::Core::DeviceQualifier device_qualifier; for (auto [device, input] : detections)
device_qualifier.FromDevice(device.get()); {
ciface::Core::DeviceQualifier device_qualifier;
device_qualifier.FromDevice(device.get());
return MappingCommon::GetExpressionForControl(QString::fromStdString(input->GetName()), if (!full_expression.isEmpty())
device_qualifier, default_device, quote); full_expression += QChar::fromLatin1('+');
full_expression += MappingCommon::GetExpressionForControl(
QString::fromStdString(input->GetName()), device_qualifier, default_device, quote);
}
if (detections.size() > 1)
return QStringLiteral("@(%1)").arg(std::move(full_expression));
return full_expression;
} }
void TestOutput(QPushButton* button, OutputReference* reference) void TestOutput(QPushButton* button, OutputReference* reference)

View file

@ -18,6 +18,7 @@
namespace ciface::Core namespace ciface::Core
{ {
// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0). // Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0).
// Note: Detect() logic assumes this is greater than 0.5.
constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55;
Device::~Device() Device::~Device()
@ -253,13 +254,14 @@ bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const
// Inputs are considered if they are first seen in a neutral state. // Inputs are considered if they are first seen in a neutral state.
// This is useful for crazy flightsticks that have certain buttons that are always held down // This is useful for crazy flightsticks that have certain buttons that are always held down
// and also properly handles detection when using "FullAnalogSurface" inputs. // and also properly handles detection when using "FullAnalogSurface" inputs.
// Upon input, return the detected Device and Input, else return nullptrs // Detects multiple inputs if they are pressed before others are released.
std::pair<std::shared_ptr<Device>, Device::Input*> // Upon input, return the detected Device and Input pairs, else return an empty container
std::vector<std::pair<std::shared_ptr<Device>, Device::Input*>>
DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const
{ {
struct InputState struct InputState
{ {
ciface::Core::Device::Input& input; ciface::Core::Device::Input* input;
ControlState initial_state; ControlState initial_state;
}; };
@ -291,7 +293,7 @@ DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device
// Undesirable axes will have negative values here when trying to map a // Undesirable axes will have negative values here when trying to map a
// "FullAnalogSurface". // "FullAnalogSurface".
input_states.push_back({*input, input->GetState()}); input_states.push_back({input, input->GetState()});
} }
if (!input_states.empty()) if (!input_states.empty())
@ -301,6 +303,8 @@ DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device
if (device_states.empty()) if (device_states.empty())
return {}; return {};
std::vector<std::pair<std::shared_ptr<Device>, Device::Input*>> detections;
u32 time = 0; u32 time = 0;
while (time < wait_ms) while (time < wait_ms)
{ {
@ -309,16 +313,31 @@ DeviceContainer::DetectInput(u32 wait_ms, const std::vector<std::string>& device
for (auto& device_state : device_states) for (auto& device_state : device_states)
{ {
for (auto& input_state : device_state.input_states) for (std::size_t i = 0; i != device_state.input_states.size(); ++i)
{ {
auto& input_state = device_state.input_states[i];
// We want an input that was initially 0.0 and currently 1.0. // We want an input that was initially 0.0 and currently 1.0.
const auto detection_score = const auto detection_score =
(input_state.input.GetState() - std::abs(input_state.initial_state)); (input_state.input->GetState() - std::abs(input_state.initial_state));
if (detection_score > INPUT_DETECT_THRESHOLD) if (detection_score > INPUT_DETECT_THRESHOLD)
return {device_state.device, &input_state.input}; {
// We found an input. Add it to our detections.
detections.emplace_back(device_state.device, input_state.input);
// And remove from input_states to prevent more detections.
device_state.input_states.erase(device_state.input_states.begin() + i--);
}
} }
} }
for (auto& detection : detections)
{
// If one of our detected inputs is released we are done.
if (detection.second->GetState() < (1 - INPUT_DETECT_THRESHOLD))
return detections;
}
} }
// No input was detected. :'( // No input was detected. :'(

View file

@ -194,7 +194,7 @@ public:
bool HasConnectedDevice(const DeviceQualifier& qualifier) const; bool HasConnectedDevice(const DeviceQualifier& qualifier) const;
std::pair<std::shared_ptr<Device>, Device::Input*> std::vector<std::pair<std::shared_ptr<Device>, Device::Input*>>
DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const; DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const;
protected: protected: