diff options
| author | JMC47 <JMC4789@gmail.com> | 2020-09-26 00:36:16 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-26 00:36:16 -0400 |
| commit | c64d41d3e783441e4f0b34d4b70cc754ca610b15 (patch) | |
| tree | 99fc4c788520c1ca60fa0e58412ae1ef7353dbb3 /Source/Core/InputCommon/ControlReference/ExpressionParser.cpp | |
| parent | 57f14b260bf043b6c261ca284b6c45fa3a7932de (diff) | |
| parent | 431eb4d60d8a413ad9a4a72b1305f8090410a741 (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.cpp | 217 |
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 |
