From 3ebb3b64874eed69a749905c92f0b8cb656da0cc Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 18 Oct 2019 14:54:02 -0500 Subject: [PATCH] InputCommon: Allow controller settings specified with input expresions. --- .../Config/Mapping/MappingButton.cpp | 4 +- .../Config/Mapping/MappingNumeric.cpp | 45 +++++++++++- .../DolphinQt/Config/Mapping/MappingNumeric.h | 2 + .../Config/Mapping/MappingWidget.cpp | 28 +++++++- .../DolphinQt/Config/Mapping/MappingWidget.h | 1 - .../ControlReference/ControlReference.h | 16 +++++ .../ControlReference/FunctionExpression.cpp | 1 - .../ControlReference/FunctionExpression.h | 3 +- .../ControllerEmu/ControlGroup/Buttons.h | 4 +- .../ControlGroup/ModifySettingsButton.cpp | 8 +-- .../ControllerEmu/ControllerEmu.cpp | 4 ++ .../ControllerEmu/Setting/NumericSetting.h | 72 +++++++++++++++++-- 12 files changed, 163 insertions(+), 25 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index a9a142276f43..b210fd54633f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -122,11 +122,9 @@ void MappingButton::UpdateIndicator() if (!isActiveWindow()) return; - const auto state = m_reference->State(); - QFont f = m_parent->font(); - if (state > ControllerEmu::Buttons::ACTIVATION_THRESHOLD) + if (m_reference->GetState()) f.setBold(true); setFont(f); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp index a0921ec9f3d8..c9e6f128a44a 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp @@ -12,11 +12,8 @@ MappingDouble::MappingDouble(MappingWidget* parent, ControllerEmu::NumericSetting* setting) : QDoubleSpinBox(parent), m_setting(*setting) { - setRange(m_setting.GetMinValue(), m_setting.GetMaxValue()); setDecimals(2); - setFixedWidth(WIDGET_MAX_WIDTH); - if (const auto ui_suffix = m_setting.GetUISuffix()) setSuffix(QLatin1Char{' '} + tr(ui_suffix)); @@ -26,10 +23,12 @@ MappingDouble::MappingDouble(MappingWidget* parent, ControllerEmu::NumericSettin connect(this, QOverload::of(&QDoubleSpinBox::valueChanged), this, [this, parent](double value) { m_setting.SetValue(value); + ConfigChanged(); parent->SaveSettings(); }); connect(parent, &MappingWidget::ConfigChanged, this, &MappingDouble::ConfigChanged); + connect(parent, &MappingWidget::Update, this, &MappingDouble::Update); } // Overriding QDoubleSpinBox's fixup to set the default value when input is cleared. @@ -41,22 +40,62 @@ void MappingDouble::fixup(QString& input) const void MappingDouble::ConfigChanged() { const QSignalBlocker blocker(this); + + if (m_setting.IsSimpleValue()) + { + setRange(m_setting.GetMinValue(), m_setting.GetMaxValue()); + setPrefix({}); + setButtonSymbols(ButtonSymbols::UpDownArrows); + } + else + { + setRange(-std::numeric_limits::infinity(), std::numeric_limits::infinity()); + setPrefix(QString::fromUtf8("🎮 ")); + setButtonSymbols(ButtonSymbols::NoButtons); + } + setValue(m_setting.GetValue()); } +void MappingDouble::Update() +{ + if (!m_setting.IsSimpleValue() && !hasFocus()) + { + const QSignalBlocker blocker(this); + setValue(m_setting.GetValue()); + } +} + MappingBool::MappingBool(MappingWidget* parent, ControllerEmu::NumericSetting* setting) : QCheckBox(parent), m_setting(*setting) { connect(this, &QCheckBox::stateChanged, this, [this, parent](int value) { m_setting.SetValue(value != 0); + ConfigChanged(); parent->SaveSettings(); }); connect(parent, &MappingWidget::ConfigChanged, this, &MappingBool::ConfigChanged); + connect(parent, &MappingWidget::Update, this, &MappingBool::Update); } void MappingBool::ConfigChanged() { const QSignalBlocker blocker(this); + + if (m_setting.IsSimpleValue()) + setText({}); + else + setText(QString::fromUtf8("🎮")); + setChecked(m_setting.GetValue()); } + +void MappingBool::Update() +{ + if (!m_setting.IsSimpleValue()) + { + const QSignalBlocker blocker(this); + setChecked(m_setting.GetValue()); + } +} diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h index 49817ea40c47..267f556e7ebf 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h @@ -21,6 +21,7 @@ class MappingDouble : public QDoubleSpinBox void fixup(QString& input) const override; void ConfigChanged(); + void Update(); ControllerEmu::NumericSetting& m_setting; }; @@ -32,6 +33,7 @@ class MappingBool : public QCheckBox private: void ConfigChanged(); + void Update(); ControllerEmu::NumericSetting& m_setting; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index b9149027ea7b..73c3a6033dc9 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -113,8 +113,6 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con const QString translated_name = translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name); form_layout->addRow(translated_name, button); - - m_buttons.push_back(button); } for (auto& setting : group->numeric_settings) @@ -135,7 +133,31 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con } if (setting_widget) - form_layout->addRow(tr(setting->GetUIName()), setting_widget); + { + const auto hbox = new QHBoxLayout; + hbox->addWidget(setting_widget); + + const auto advanced_button = new QPushButton(tr("...")); + advanced_button->setFixedWidth( + QFontMetrics(font()).boundingRect(advanced_button->text()).width() * 2); + + hbox->addWidget(advanced_button); + + advanced_button->connect( + advanced_button, &QPushButton::clicked, [this, &setting = *setting.get()]() { + setting.SetExpressionFromValue(); + + IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::Input); + io.exec(); + + setting.SimplifyIfPossible(); + + ConfigChanged(); + SaveSettings(); + }); + + form_layout->addRow(tr(setting->GetUIName()), hbox); + } } return group_box; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index cd06a3ded883..15a0699739a3 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -61,5 +61,4 @@ class MappingWidget : public QWidget private: MappingWindow* m_parent; bool m_first = true; - std::vector m_buttons; }; diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 3f2c20550174..78604cd26912 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -7,6 +7,7 @@ #include #include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControlReference/FunctionExpression.h" #include "InputCommon/ControllerInterface/Device.h" // ControlReference @@ -28,6 +29,9 @@ class ControlReference virtual ControlState State(const ControlState state = 0) = 0; virtual bool IsInput() const = 0; + template + T GetState(); + int BoundCount() const; ciface::ExpressionParser::ParseStatus GetParseStatus() const; void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env); @@ -43,6 +47,18 @@ class ControlReference ciface::ExpressionParser::ParseStatus m_parse_status; }; +template <> +inline bool ControlReference::GetState() +{ + return State() > ciface::ExpressionParser::CONDITION_THRESHOLD; +} + +template +T ControlReference::GetState() +{ + return State(); +} + // // InputReference // diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp index 18b98c5e1917..3fa15982b9fd 100644 --- a/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp @@ -12,7 +12,6 @@ namespace ciface namespace ExpressionParser { constexpr int LOOP_MAX_REPS = 10000; -constexpr ControlState CONDITION_THRESHOLD = 0.5; using Clock = std::chrono::steady_clock; using FSec = std::chrono::duration; diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.h b/Source/Core/InputCommon/ControlReference/FunctionExpression.h index f5332a0d256f..4f97d6e04dfc 100644 --- a/Source/Core/InputCommon/ControlReference/FunctionExpression.h +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.h @@ -10,12 +10,13 @@ #include #include "InputCommon/ControlReference/ExpressionParser.h" -#include "InputCommon/ControlReference/FunctionExpression.h" namespace ciface { namespace ExpressionParser { +constexpr ControlState CONDITION_THRESHOLD = 0.5; + class FunctionExpression : public Expression { public: diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h index c2311415e504..9560a37843db 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Buttons.h @@ -24,13 +24,11 @@ class Buttons : public ControlGroup { for (auto& control : controls) { - if (control->control_ref->State() > ACTIVATION_THRESHOLD) + if (control->control_ref->GetState()) *buttons |= *bitmasks; bitmasks++; } } - - static constexpr ControlState ACTIVATION_THRESHOLD = 0.5; }; } // namespace ControllerEmu diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp index 908de81a966a..a90203479560 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/ModifySettingsButton.cpp @@ -35,19 +35,19 @@ void ModifySettingsButton::GetState() { for (size_t i = 0; i < controls.size(); ++i) { - ControlState state = controls[i]->control_ref->State(); + const bool state = controls[i]->control_ref->GetState(); if (!associated_settings_toggle[i]) { // not toggled - associated_settings[i] = state > ACTIVATION_THRESHOLD; + associated_settings[i] = state; } else { // toggle (loading savestates does not en-/disable toggle) // after we passed the threshold, we en-/disable. but after that, we don't change it // anymore - if (!threshold_exceeded[i] && state > ACTIVATION_THRESHOLD) + if (!threshold_exceeded[i] && state) { associated_settings[i] = !associated_settings[i]; @@ -59,7 +59,7 @@ void ModifySettingsButton::GetState() threshold_exceeded[i] = true; } - if (state < ACTIVATION_THRESHOLD) + if (!state) threshold_exceeded[i] = false; } } diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp index c5ff0705a23c..fe1bf7b058d2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp @@ -14,6 +14,7 @@ #include "InputCommon/ControllerEmu/Control/Control.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" +#include "InputCommon/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" namespace ControllerEmu @@ -54,6 +55,9 @@ void EmulatedController::UpdateReferences(ciface::ExpressionParser::ControlEnvir for (auto& control : ctrlGroup->controls) control->control_ref->UpdateReference(env); + for (auto& setting : ctrlGroup->numeric_settings) + setting->UpdateReference(env); + // Attachments: if (ctrlGroup->type == GroupType::Attachments) { diff --git a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h index 108b22804927..7445c95727ae 100644 --- a/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h +++ b/Source/Core/InputCommon/ControllerEmu/Setting/NumericSetting.h @@ -9,6 +9,7 @@ #include "Common/CommonTypes.h" #include "Common/IniFile.h" +#include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerInterface/Device.h" namespace ControllerEmu @@ -52,6 +53,11 @@ class NumericSettingBase virtual void LoadFromIni(const IniFile::Section& section, const std::string& group_name) = 0; virtual void SaveToIni(IniFile::Section& section, const std::string& group_name) const = 0; + virtual void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env) = 0; + virtual InputReference& GetInputReference() = 0; + virtual void SimplifyIfPossible() = 0; + virtual void SetExpressionFromValue() = 0; + virtual SettingType GetType() const = 0; const char* GetUIName() const; @@ -84,16 +90,53 @@ class NumericSetting : public NumericSettingBase void LoadFromIni(const IniFile::Section& section, const std::string& group_name) override { - ValueType value; - section.Get(group_name + m_details.ini_name, &value, m_default_value); - SetValue(value); + std::string str_value; + if (section.Get(group_name + m_details.ini_name, &str_value)) + { + m_value.m_input.SetExpression(std::move(str_value)); + SimplifyIfPossible(); + } + else + { + SetValue(m_default_value); + } } void SaveToIni(IniFile::Section& section, const std::string& group_name) const override { - section.Set(group_name + m_details.ini_name, GetValue(), m_default_value); + if (IsSimpleValue()) + section.Set(group_name + m_details.ini_name, GetValue(), m_default_value); + else + section.Set(group_name + m_details.ini_name, m_value.m_input.GetExpression(), ""); + } + + void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env) override + { + m_value.m_input.UpdateReference(env); } + bool IsSimpleValue() const { return m_value.IsSimpleValue(); } + + // Convert a simple literal expression to a regular value. + void SimplifyIfPossible() override + { + ValueType value; + if (TryParse(m_value.m_input.GetExpression(), &value)) + m_value.SetValue(value); + } + + // Convert a simple value to an expression (used before expression editing). + void SetExpressionFromValue() override + { + if (IsSimpleValue()) + { + // Cast to double to prevent bool -> "true"/"false" strings. + m_value.m_input.SetExpression(ValueToString(static_cast(GetValue()))); + } + } + + InputReference& GetInputReference() override { return m_value.m_input; } + ValueType GetValue() const { return m_value.GetValue(); } void SetValue(ValueType value) { m_value.SetValue(value); } @@ -119,13 +162,30 @@ class SettingValue friend class NumericSetting; public: - ValueType GetValue() const { return m_value; } + ValueType GetValue() const + { + if (IsSimpleValue()) + return m_value; + else + return m_input.GetState(); + } + + bool IsSimpleValue() const { return m_input.GetExpression().empty(); } private: - void SetValue(ValueType value) { m_value = value; } + void SetValue(ValueType value) + { + m_value = value; + + // Clear the expression to use our new "simple" value. + m_input.SetExpression(""); + } // Values are R/W by both UI and CPU threads. std::atomic m_value = {}; + + // Unfortunately InputReference's state grabbing is non-const requiring mutable here. + mutable InputReference m_input; }; } // namespace ControllerEmu