diff --git a/CMakeLists.txt b/CMakeLists.txt index 3babd649..e58e4a14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,8 @@ target_sources( src/macro-core/macro-condition-streaming.hpp src/macro-core/macro-condition-studio-mode.cpp src/macro-core/macro-condition-studio-mode.hpp + src/macro-core/macro-condition-tempvar.cpp + src/macro-core/macro-condition-tempvar.hpp src/macro-core/macro-condition-timer.cpp src/macro-core/macro-condition-timer.hpp src/macro-core/macro-condition-transition.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 660f2ca0..29ad12c1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -463,6 +463,7 @@ AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event" AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions" AdvSceneSwitcher.condition.websocket.entry.request="{{type}}was received:" AdvSceneSwitcher.condition.websocket.entry.event="{{type}}was received from{{connection}}:" +AdvSceneSwitcher.condition.temporaryVariable="Macro property" AdvSceneSwitcher.condition.variable="Variable" AdvSceneSwitcher.condition.variable.type.compare="equals" AdvSceneSwitcher.condition.variable.type.empty="is empty" diff --git a/src/macro-core/macro-condition-tempvar.cpp b/src/macro-core/macro-condition-tempvar.cpp new file mode 100644 index 00000000..66670285 --- /dev/null +++ b/src/macro-core/macro-condition-tempvar.cpp @@ -0,0 +1,368 @@ +#include "macro-condition-tempvar.hpp" +#include "math-helpers.hpp" +#include "utility.hpp" + +namespace advss { + +const std::string MacroConditionTempVar::id = "temp_var"; + +bool MacroConditionTempVar::_registered = MacroConditionFactory::Register( + MacroConditionTempVar::id, + {MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create, + "AdvSceneSwitcher.condition.temporaryVariable"}); + +const static std::map + conditionTypes = { + {MacroConditionTempVar::Condition::EQUALS, + "AdvSceneSwitcher.condition.variable.type.compare"}, + {MacroConditionTempVar::Condition::IS_EMPTY, + "AdvSceneSwitcher.condition.variable.type.empty"}, + {MacroConditionTempVar::Condition::IS_NUMBER, + "AdvSceneSwitcher.condition.variable.type.number"}, + {MacroConditionTempVar::Condition::LESS_THAN, + "AdvSceneSwitcher.condition.variable.type.lessThan"}, + {MacroConditionTempVar::Condition::GREATER_THAN, + "AdvSceneSwitcher.condition.variable.type.greaterThan"}, + {MacroConditionTempVar::Condition::VALUE_CHANGED, + "AdvSceneSwitcher.condition.variable.type.valueChanged"}, + {MacroConditionTempVar::Condition::EQUALS_VARIABLE, + "AdvSceneSwitcher.condition.variable.type.equalsVariable"}, + {MacroConditionTempVar::Condition::LESS_THAN_VARIABLE, + "AdvSceneSwitcher.condition.variable.type.lessThanVariable"}, + {MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE, + "AdvSceneSwitcher.condition.variable.type.greaterThanVariable"}, +}; + +static bool isNumber(const TempVariable &var) +{ + auto value = var.Value(); + if (!value) { + return false; + } + + return GetDouble(*value).has_value(); +} + +static bool compareNumber(const TempVariable &var, double value, bool less) +{ + auto tempVarValue = var.Value(); + if (!tempVarValue) { + return false; + } + + auto doubleValue = GetDouble(*tempVarValue); + if (!doubleValue.has_value()) { + return false; + } + if (less) { + return doubleValue < value; + } + return doubleValue > value; +} + +bool MacroConditionTempVar::Compare(const TempVariable &var) const +{ + auto value = var.Value(); + if (!value) { + return false; + } + + if (_regex.Enabled()) { + auto expr = _regex.GetRegularExpression(_strValue); + if (!expr.isValid()) { + return false; + } + auto match = expr.match(QString::fromStdString(*value)); + return match.hasMatch(); + } + + return std::string(_strValue) == *value; +} + +bool MacroConditionTempVar::ValueChanged(const TempVariable &var) +{ + auto value = var.Value(); + if (!value) { + return false; + } + bool changed = *value != _lastValue; + if (changed) { + _lastValue = *value; + } + return changed; +} + +bool MacroConditionTempVar::CompareVariables() +{ + auto var1 = _tempVar.GetTempVariable(GetMacro()); + auto var2 = _variable2.lock(); + if (!var1 || !var2) { + return false; + } + + auto tempVarValue = var1->Value(); + if (!tempVarValue) { + return false; + } + auto val1 = GetDouble(*tempVarValue); + auto val2 = var2->DoubleValue(); + bool validNumbers = val1.has_value() && val2.has_value(); + + switch (_type) { + case MacroConditionTempVar::Condition::EQUALS_VARIABLE: + return *tempVarValue == var2->Value() || + (validNumbers && val1 == val2); + case MacroConditionTempVar::Condition::LESS_THAN_VARIABLE: + return validNumbers && val1 < val2; + case MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE: + return validNumbers && val1 > val2; + default: + blog(LOG_WARNING, + "Unexpected call of %s with condition type %d", __func__, + static_cast(_type)); + } + + return false; +} + +bool MacroConditionTempVar::CheckCondition() +{ + auto var = _tempVar.GetTempVariable(GetMacro()); + if (!var) { + return false; + } + + switch (_type) { + case MacroConditionTempVar::Condition::EQUALS: + return Compare(*var); + case MacroConditionTempVar::Condition::IS_EMPTY: + return var->Value().has_value() && var->Value()->empty(); + case MacroConditionTempVar::Condition::IS_NUMBER: + return isNumber(*var); + case MacroConditionTempVar::Condition::LESS_THAN: + return compareNumber(*var, _numValue, true); + case MacroConditionTempVar::Condition::GREATER_THAN: + return compareNumber(*var, _numValue, false); + case MacroConditionTempVar::Condition::VALUE_CHANGED: + return ValueChanged(*var); + case MacroConditionTempVar::Condition::EQUALS_VARIABLE: + return CompareVariables(); + case MacroConditionTempVar::Condition::LESS_THAN_VARIABLE: + return CompareVariables(); + case MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE: + return CompareVariables(); + } + + return false; +} + +bool MacroConditionTempVar::Save(obs_data_t *obj) const +{ + MacroCondition::Save(obj); + _tempVar.Save(obj); + obs_data_set_string(obj, "variableName", + GetWeakVariableName(_variable2).c_str()); + _strValue.Save(obj, "strValue"); + obs_data_set_double(obj, "numValue", _numValue); + obs_data_set_int(obj, "condition", static_cast(_type)); + _regex.Save(obj); + return true; +} + +bool MacroConditionTempVar::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _tempVar.Load(obj, GetMacro()); + _variable2 = + GetWeakVariableByName(obs_data_get_string(obj, "variableName")); + _strValue.Load(obj, "strValue"); + _numValue = obs_data_get_double(obj, "numValue"); + _type = static_cast(obs_data_get_int(obj, "condition")); + _regex.Load(obj); + // TODO: remove in future version + if (obs_data_has_user_value(obj, "regex")) { + _regex.CreateBackwardsCompatibleRegex( + obs_data_get_bool(obj, "regex")); + } + return true; +} + +std::string MacroConditionTempVar::GetShortDesc() const +{ + auto var = _tempVar.GetTempVariable(GetMacro()); + if (!var) { + return ""; + } + return var->Name(); +} + +static inline void populateConditionSelection(QComboBox *list) +{ + for (auto entry : conditionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroConditionTempVarEdit::MacroConditionTempVarEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _tempVars(new TempVariableSelection(this)), + _variables2(new VariableSelection(this)), + _conditions(new QComboBox()), + _strValue(new VariableTextEdit(this, 5, 1, 1)), + _numValue(new QDoubleSpinBox()), + _regex(new RegexConfigWidget(parent)) +{ + _numValue->setMinimum(-9999999999); + _numValue->setMaximum(9999999999); + populateConditionSelection(_conditions); + + QWidget::connect(_tempVars, + SIGNAL(SelectionChanged(const TempVariableRef &)), + this, SLOT(VariableChanged(const TempVariableRef &))); + QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)), + this, SLOT(Variable2Changed(const QString &))); + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_strValue, SIGNAL(textChanged()), this, + SLOT(StrValueChanged())); + QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this, + SLOT(NumValueChanged(double))); + QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this, + SLOT(RegexChanged(RegexConfig))); + + std::unordered_map widgetPlaceholders = { + {"{{variables}}", _tempVars}, + {"{{variables2}}", _variables2}, + {"{{conditions}}", _conditions}, + {"{{strValue}}", _strValue}, + {"{{numValue}}", _numValue}, + {"{{regex}}", _regex}, + }; + + auto layout = new QHBoxLayout; + PlaceWidgets( + obs_module_text("AdvSceneSwitcher.condition.variable.entry"), + layout, widgetPlaceholders); + setLayout(layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionTempVarEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _tempVars->SetVariable(_entryData->_tempVar); + _variables2->SetVariable(_entryData->_variable2); + _conditions->setCurrentIndex(static_cast(_entryData->_type)); + _strValue->setPlainText(_entryData->_strValue); + _numValue->setValue(_entryData->_numValue); + _regex->SetRegexConfig(_entryData->_regex); + SetWidgetVisibility(); +} + +void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_tempVar = var; +} + +void MacroConditionTempVarEdit::Variable2Changed(const QString &text) +{ + + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_variable2 = GetWeakVariableByQString(text); +} + +void MacroConditionTempVarEdit::ConditionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_type = + static_cast(value); + SetWidgetVisibility(); +} + +void MacroConditionTempVarEdit::StrValueChanged() +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_strValue = _strValue->toPlainText().toStdString(); + adjustSize(); + updateGeometry(); +} + +void MacroConditionTempVarEdit::NumValueChanged(double val) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_numValue = val; +} + +void MacroConditionTempVarEdit::RegexChanged(RegexConfig conf) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_regex = conf; + adjustSize(); + updateGeometry(); +} + +void MacroConditionTempVarEdit::SetWidgetVisibility() +{ + if (!_entryData) { + return; + } + + _regex->setVisible(_entryData->_type == + MacroConditionTempVar::Condition::EQUALS); + _strValue->setVisible(_entryData->_type == + MacroConditionTempVar::Condition::EQUALS); + _numValue->setVisible( + _entryData->_type == + MacroConditionTempVar::Condition::LESS_THAN || + _entryData->_type == + MacroConditionTempVar::Condition::GREATER_THAN); + _numValue->setVisible( + _entryData->_type == + MacroConditionTempVar::Condition::LESS_THAN || + _entryData->_type == + MacroConditionTempVar::Condition::GREATER_THAN); + _variables2->setVisible( + _entryData->_type == + MacroConditionTempVar::Condition::EQUALS_VARIABLE || + _entryData->_type == + MacroConditionTempVar::Condition::LESS_THAN_VARIABLE || + _entryData->_type == + MacroConditionTempVar::Condition::GREATER_THAN_VARIABLE); + + adjustSize(); + updateGeometry(); +} + +} // namespace advss diff --git a/src/macro-core/macro-condition-tempvar.hpp b/src/macro-core/macro-condition-tempvar.hpp new file mode 100644 index 00000000..7a03b827 --- /dev/null +++ b/src/macro-core/macro-condition-tempvar.hpp @@ -0,0 +1,100 @@ +#pragma once +#include "macro-condition-edit.hpp" +#include "resizing-text-edit.hpp" +#include "variable.hpp" +#include "regex-config.hpp" +#include "variable-text-edit.hpp" + +#include +#include +#include +#include + +namespace advss { + +class MacroConditionTempVar : public MacroCondition { +public: + MacroConditionTempVar(Macro *m) : MacroCondition(m) {} + bool CheckCondition(); + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetShortDesc() const; + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + enum class Condition { + EQUALS, + IS_EMPTY, + IS_NUMBER, + LESS_THAN, + GREATER_THAN, + VALUE_CHANGED, + EQUALS_VARIABLE, + LESS_THAN_VARIABLE, + GREATER_THAN_VARIABLE, + }; + + Condition _type = Condition::EQUALS; + TempVariableRef _tempVar; + std::weak_ptr _variable2; + StringVariable _strValue = ""; + double _numValue = 0; + RegexConfig _regex; + +private: + bool Compare(const TempVariable &) const; + bool ValueChanged(const TempVariable &); + bool CompareVariables(); + + std::string _lastValue = ""; + + static bool _registered; + static const std::string id; +}; + +class MacroConditionTempVarEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionTempVarEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionTempVarEdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void VariableChanged(const TempVariableRef &); + void Variable2Changed(const QString &); + void ConditionChanged(int); + void StrValueChanged(); + void NumValueChanged(double); + void RegexChanged(RegexConfig); + +signals: + void HeaderInfoChanged(const QString &); + +protected: + TempVariableSelection *_tempVars; + VariableSelection *_variables2; + QComboBox *_conditions; + VariableTextEdit *_strValue; + QDoubleSpinBox *_numValue; + RegexConfigWidget *_regex; + std::shared_ptr _entryData; + +private: + void SetWidgetVisibility(); + + bool _loading = true; +}; + +} // namespace advss