summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2020-09-26 00:36:16 -0400
committerGitHub <noreply@github.com>2020-09-26 00:36:16 -0400
commitc64d41d3e783441e4f0b34d4b70cc754ca610b15 (patch)
tree99fc4c788520c1ca60fa0e58412ae1ef7353dbb3 /Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
parent57f14b260bf043b6c261ca284b6c45fa3a7932de (diff)
parent431eb4d60d8a413ad9a4a72b1305f8090410a741 (diff)
Merge pull request #8428 from jordan-woyak/better-hotkeys
InputCommon: Add hotkey support to input expressions.
Diffstat (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp')
-rw-r--r--Source/Core/InputCommon/ControlReference/ExpressionParser.cpp217
1 files changed, 217 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
index 1b1a24753c..d4f93450e3 100644
--- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
+++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
@@ -2,9 +2,11 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
+#include <algorithm>
#include <cassert>
#include <cmath>
#include <iostream>
+#include <map>
#include <memory>
#include <regex>
#include <string>
@@ -21,6 +23,55 @@ namespace ciface::ExpressionParser
{
using namespace ciface::Core;
+class ControlExpression;
+
+class HotkeySuppressions
+{
+public:
+ using Modifiers = std::vector<std::unique_ptr<ControlExpression>>;
+
+ struct InvokingDeleter
+ {
+ template <typename T>
+ void operator()(T* func)
+ {
+ (*func)();
+ }
+ };
+
+ using Suppressor = std::unique_ptr<std::function<void()>, InvokingDeleter>;
+
+ bool IsSuppressed(Device::Input* input) const
+ {
+ // Input is suppressed if it exists in the map at all.
+ return m_suppressions.lower_bound({input, nullptr}) !=
+ m_suppressions.lower_bound({input + 1, nullptr});
+ }
+
+ bool IsSuppressedIgnoringModifiers(Device::Input* input, const Modifiers& ignore_modifiers) const;
+
+ // Suppresses each input + modifier pair.
+ // The returned object removes the suppression on destruction.
+ Suppressor MakeSuppressor(const Modifiers* modifiers,
+ const std::unique_ptr<ControlExpression>* final_input);
+
+private:
+ using Suppression = std::pair<Device::Input*, Device::Input*>;
+ using SuppressionLevel = u16;
+
+ void RemoveSuppression(Device::Input* modifier, Device::Input* final_input)
+ {
+ auto it = m_suppressions.find({final_input, modifier});
+ if ((--it->second) == 0)
+ m_suppressions.erase(it);
+ }
+
+ // Holds counts of suppressions for each input/modifier pair.
+ std::map<Suppression, SuppressionLevel> m_suppressions;
+};
+
+static HotkeySuppressions s_hotkey_suppressions;
+
Token::Token(TokenType type_) : type(type_)
{
}
@@ -112,6 +163,8 @@ Token Lexer::NextToken()
return Token(TOK_LPAREN);
case ')':
return Token(TOK_RPAREN);
+ case '@':
+ return Token(TOK_HOTKEY);
case '&':
return Token(TOK_AND);
case '|':
@@ -196,8 +249,17 @@ public:
std::shared_ptr<Device> m_device;
explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {}
+
ControlState GetValue() const override
{
+ if (s_hotkey_suppressions.IsSuppressed(input))
+ return 0;
+ else
+ return GetValueIgnoringSuppression();
+ }
+
+ ControlState GetValueIgnoringSuppression() const
+ {
if (!input)
return 0.0;
@@ -222,12 +284,46 @@ public:
output = env.FindOutput(qualifier);
}
+ Device::Input* GetInput() const { return input; };
+
private:
ControlQualifier qualifier;
Device::Input* input = nullptr;
Device::Output* output = nullptr;
};
+bool HotkeySuppressions::IsSuppressedIgnoringModifiers(Device::Input* input,
+ const Modifiers& ignore_modifiers) const
+{
+ // Input is suppressed if it exists in the map with a modifier that we aren't ignoring.
+ auto it = m_suppressions.lower_bound({input, nullptr});
+ auto it_end = m_suppressions.lower_bound({input + 1, nullptr});
+
+ // We need to ignore L_Ctrl R_Ctrl when supplied Ctrl and vice-versa.
+ const auto is_same_modifier = [](Device::Input* i1, Device::Input* i2) {
+ return i1 == i2 || i1->IsChild(i2) || i2->IsChild(i1);
+ };
+
+ return std::any_of(it, it_end, [&](auto& s) {
+ return std::none_of(begin(ignore_modifiers), end(ignore_modifiers),
+ [&](auto& m) { return is_same_modifier(m->GetInput(), s.first.second); });
+ });
+}
+
+HotkeySuppressions::Suppressor
+HotkeySuppressions::MakeSuppressor(const Modifiers* modifiers,
+ const std::unique_ptr<ControlExpression>* final_input)
+{
+ for (auto& modifier : *modifiers)
+ ++m_suppressions[{(*final_input)->GetInput(), modifier->GetInput()}];
+
+ return Suppressor(std::make_unique<std::function<void()>>([this, modifiers, final_input]() {
+ for (auto& modifier : *modifiers)
+ RemoveSuppression(modifier->GetInput(), (*final_input)->GetInput());
+ }).release(),
+ InvokingDeleter{});
+}
+
class BinaryExpression : public Expression
{
public:
@@ -374,6 +470,90 @@ protected:
ControlState* m_value_ptr{};
};
+class HotkeyExpression : public Expression
+{
+public:
+ HotkeyExpression(std::vector<std::unique_ptr<ControlExpression>> inputs)
+ : m_modifiers(std::move(inputs))
+ {
+ m_final_input = std::move(m_modifiers.back());
+ m_modifiers.pop_back();
+ }
+
+ ControlState GetValue() const override
+ {
+ const bool modifiers_pressed = std::all_of(m_modifiers.begin(), m_modifiers.end(),
+ [](const std::unique_ptr<ControlExpression>& input) {
+ return input->GetValue() > CONDITION_THRESHOLD;
+ });
+
+ const auto final_input_state = m_final_input->GetValueIgnoringSuppression();
+
+ if (modifiers_pressed)
+ {
+ // Ignore suppression of our own modifiers. This also allows superset modifiers to function.
+ const bool is_suppressed = s_hotkey_suppressions.IsSuppressedIgnoringModifiers(
+ m_final_input->GetInput(), m_modifiers);
+
+ if (final_input_state < CONDITION_THRESHOLD)
+ m_is_blocked = false;
+
+ // If some other hotkey suppressed us, require a release of final input to be ready again.
+ if (is_suppressed)
+ m_is_blocked = true;
+
+ if (m_is_blocked)
+ return 0;
+
+ EnableSuppression();
+
+ // Our modifiers are active. Pass through the final input.
+ return final_input_state;
+ }
+ else
+ {
+ m_suppressor = {};
+ m_is_blocked = final_input_state > CONDITION_THRESHOLD;
+ }
+
+ return 0;
+ }
+
+ void SetValue(ControlState) override {}
+
+ int CountNumControls() const override
+ {
+ int result = 0;
+ for (auto& input : m_modifiers)
+ result += input->CountNumControls();
+ return result + m_final_input->CountNumControls();
+ }
+
+ void UpdateReferences(ControlEnvironment& env) override
+ {
+ for (auto& input : m_modifiers)
+ input->UpdateReferences(env);
+
+ m_final_input->UpdateReferences(env);
+
+ // We must update our suppression with valid pointers.
+ if (m_suppressor)
+ EnableSuppression();
+ }
+
+private:
+ void EnableSuppression() const
+ {
+ if (!m_suppressor)
+ m_suppressor = s_hotkey_suppressions.MakeSuppressor(&m_modifiers, &m_final_input);
+ }
+
+ HotkeySuppressions::Modifiers m_modifiers;
+ std::unique_ptr<ControlExpression> m_final_input;
+ mutable HotkeySuppressions::Suppressor m_suppressor;
+ mutable bool m_is_blocked = false;
+};
+
// This class proxies all methods to its either left-hand child if it has bound controls, or its
// right-hand child. Its intended use is for supporting old-style barewords expressions.
class CoalesceExpression : public Expression
@@ -600,6 +780,10 @@ private:
{
return ParseParens();
}
+ case TOK_HOTKEY:
+ {
+ return ParseHotkeys();
+ }
case TOK_SUB:
{
// An atom was expected but we got a subtraction symbol.
@@ -684,6 +868,39 @@ private:
return result;
}
+ ParseResult ParseHotkeys()
+ {
+ Token tok = Chew();
+ if (tok.type != TOK_LPAREN)
+ return ParseResult::MakeErrorResult(tok, _trans("Expected opening paren."));
+
+ std::vector<std::unique_ptr<ControlExpression>> inputs;
+
+ while (true)
+ {
+ tok = Chew();
+
+ if (tok.type != TOK_CONTROL && tok.type != TOK_BAREWORD)
+ return ParseResult::MakeErrorResult(tok, _trans("Expected name of input."));
+
+ ControlQualifier cq;
+ cq.FromString(tok.data);
+ inputs.emplace_back(std::make_unique<ControlExpression>(std::move(cq)));
+
+ tok = Chew();
+
+ if (tok.type == TOK_ADD)
+ continue;
+
+ if (tok.type == TOK_RPAREN)
+ break;
+
+ return ParseResult::MakeErrorResult(tok, _trans("Expected + or closing paren."));
+ }
+
+ return ParseResult::MakeSuccessfulResult(std::make_unique<HotkeyExpression>(std::move(inputs)));
+ }
+
ParseResult ParseToplevel() { return ParseBinary(); }
}; // namespace ExpressionParser