From 616664e6f3fc397e9824cf32a25623168f46bf33 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:46:28 +0200 Subject: [PATCH] Add "Loop" action --- CMakeLists.txt | 2 + data/locale/en-US.ini | 6 + lib/macro/macro-action-loop.cpp | 196 ++++++++++++++++++++++++++++++++ lib/macro/macro-action-loop.hpp | 64 +++++++++++ 4 files changed, 268 insertions(+) create mode 100644 lib/macro/macro-action-loop.cpp create mode 100644 lib/macro/macro-action-loop.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 544622c3..1b971d9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,8 @@ target_sources( lib/macro/macro-action-edit.hpp lib/macro/macro-action-factory.cpp lib/macro/macro-action-factory.hpp + lib/macro/macro-action-loop.cpp + lib/macro/macro-action-loop.hpp lib/macro/macro-action-macro.cpp lib/macro/macro-action-macro.hpp lib/macro/macro-action-queue.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 68a898b3..5fd94fcd 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1122,6 +1122,12 @@ AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro" AdvSceneSwitcher.action.macro.actionSelectionType.index="at index" AdvSceneSwitcher.action.macro.actionSelectionType.label="with label" AdvSceneSwitcher.action.macro.actionSelectionType.id="of action type" +AdvSceneSwitcher.action.loop="Loop" +AdvSceneSwitcher.action.loop.maxIterations="Max iterations" +AdvSceneSwitcher.action.loop.conditionHelp="Define the loop condition. The actions below will be repeated while these conditions are met.\n\nClick the plus button below to add a new condition." +AdvSceneSwitcher.action.loop.actionHelp="Define the actions to perform each iteration.\n\nClick the plus button below to add a new action." +AdvSceneSwitcher.tempVar.loop.count="Iteration count" +AdvSceneSwitcher.tempVar.loop.count.description="The total number of iterations completed." AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp="This section allows you to define macro conditions.\n\nClick the plus button below to add a new condition." AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nClick the plus button below to add a new action." AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nClick the plus button below to add a new action." diff --git a/lib/macro/macro-action-loop.cpp b/lib/macro/macro-action-loop.cpp new file mode 100644 index 00000000..e69f7310 --- /dev/null +++ b/lib/macro/macro-action-loop.cpp @@ -0,0 +1,196 @@ +#include "macro-action-loop.hpp" + +#include "layout-helpers.hpp" +#include "macro-action-factory.hpp" +#include "macro.hpp" + +namespace advss { + +const std::string MacroActionLoop::id = "loop"; + +bool MacroActionLoop::_registered = MacroActionFactory::Register( + MacroActionLoop::id, + {MacroActionLoop::Create, MacroActionLoopEdit::Create, + "AdvSceneSwitcher.action.loop"}); + +bool MacroActionLoop::PerformAction() +{ + int iterations = 0; + const int maxIterations = _maxIterations; + Macro *parentMacro = GetMacro(); + + if (maxIterations <= 0) { + blog(LOG_WARNING, + "loop action has invalid max iterations value of %d", + maxIterations); + return true; + } + + while (_loopMacro->CheckConditions()) { + if (iterations >= maxIterations) { + blog(LOG_WARNING, + "loop action reached iteration limit of %d", + maxIterations); + break; + } + ablog(LOG_INFO, "loop iteration %d", iterations); + if (!_loopMacro->PerformActions(true, false, true)) { + break; + } + ++iterations; + if (parentMacro && parentMacro->GetStop()) { + break; + } + } + ablog(LOG_INFO, "loop completed after %d iteration(s)", iterations); + SetTempVarValue("count", std::to_string(iterations)); + return true; +} + +void MacroActionLoop::LogAction() const +{ + ablog(LOG_INFO, "running loop (max %d iterations)", + _maxIterations.GetValue()); +} + +bool MacroActionLoop::Save(obs_data_t *obj) const +{ + MacroAction::Save(obj); + _maxIterations.Save(obj, "maxIterations"); + OBSDataAutoRelease loopMacroData = obs_data_create(); + _loopMacro->Save(loopMacroData); + obs_data_set_obj(obj, "loopMacro", loopMacroData); + obs_data_set_int(obj, "customWidgetHeight", _customWidgetHeight); + return true; +} + +bool MacroActionLoop::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _maxIterations.Load(obj, "maxIterations"); + if (obs_data_has_user_value(obj, "loopMacro")) { + OBSDataAutoRelease loopMacroData = + obs_data_get_obj(obj, "loopMacro"); + _loopMacro = std::make_shared(); + _loopMacro->Load(loopMacroData); + } + _customWidgetHeight = obs_data_get_int(obj, "customWidgetHeight"); + return true; +} + +bool MacroActionLoop::PostLoad() +{ + MacroAction::PostLoad(); + _loopMacro->PostLoad(); + _loopMacro->SetActionTriggerMode(Macro::ActionTriggerMode::ALWAYS); + return true; +} + +std::shared_ptr MacroActionLoop::Create(Macro *m) +{ + return std::make_shared(m); +} + +std::shared_ptr MacroActionLoop::Copy() const +{ + auto copy = std::make_shared(*this); + + OBSDataAutoRelease data = obs_data_create(); + _loopMacro->Save(data); + + copy->_loopMacro = std::make_shared(); + copy->_loopMacro->Load(data); + copy->_loopMacro->PostLoad(); + + return copy; +} + +void MacroActionLoop::ResolveVariablesToFixedValues() +{ + _maxIterations.ResolveVariables(); + for (auto &action : _loopMacro->Actions()) { + action->ResolveVariablesToFixedValues(); + } +} + +void MacroActionLoop::SetupTempVars() +{ + MacroAction::SetupTempVars(); + AddTempvar("count", + obs_module_text("AdvSceneSwitcher.tempVar.loop.count"), + obs_module_text( + "AdvSceneSwitcher.tempVar.loop.count.description")); +} + +MacroActionLoopEdit::MacroActionLoopEdit( + QWidget *parent, std::shared_ptr entryData) + : ResizableWidget(parent), + _macroEdit(new MacroEdit( + this, QStringList() + << "AdvSceneSwitcher.action.loop.conditionHelp" + << "AdvSceneSwitcher.action.loop.actionHelp" + << "")), + _maxIterations(new VariableSpinBox()) +{ + _maxIterations->setMinimum(1); + _maxIterations->setMaximum(10000000); + + QWidget::connect( + _maxIterations, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, SLOT(MaxIterationsChanged(const NumberVariable &))); + + auto controlsLayout = new QHBoxLayout(); + controlsLayout->addWidget(new QLabel( + obs_module_text("AdvSceneSwitcher.action.loop.maxIterations"))); + controlsLayout->addWidget(_maxIterations); + controlsLayout->addStretch(); + + auto layout = new QVBoxLayout(); + layout->addLayout(controlsLayout); + layout->addWidget(_macroEdit); + setLayout(layout); + + _macroEdit->HideElseSection(); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +MacroActionLoopEdit::~MacroActionLoopEdit() +{ + if (!_entryData) { + return; + } + _entryData->_customWidgetHeight = GetCustomHeight(); + _macroEdit->SetMacro({}); +} + +void MacroActionLoopEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _maxIterations->SetValue(_entryData->_maxIterations); + _macroEdit->SetMacro(_entryData->_loopMacro); + + if (_macroEdit->IsEmpty()) { + _macroEdit->ShowAllMacroSections(); + _entryData->_customWidgetHeight = 600; + } + SetResizingEnabled(true); + SetCustomHeight(_entryData->_customWidgetHeight); + + adjustSize(); + updateGeometry(); +} + +void MacroActionLoopEdit::MaxIterationsChanged(const NumberVariable &value) +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_maxIterations = value; +} + +} // namespace advss diff --git a/lib/macro/macro-action-loop.hpp b/lib/macro/macro-action-loop.hpp new file mode 100644 index 00000000..11b64054 --- /dev/null +++ b/lib/macro/macro-action-loop.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "macro-edit.hpp" +#include "resizable-widget.hpp" +#include "variable-spinbox.hpp" + +#include +#include + +namespace advss { + +class MacroActionLoop : public MacroAction { +public: + MacroActionLoop(Macro *m) : MacroAction(m) {} + bool PerformAction(); + void LogAction() const; + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + bool PostLoad(); + std::string GetId() const { return id; } + static std::shared_ptr Create(Macro *m); + std::shared_ptr Copy() const; + void ResolveVariablesToFixedValues(); + + std::shared_ptr _loopMacro = std::make_shared(); + IntVariable _maxIterations = 100; + int _customWidgetHeight = 0; + +private: + void SetupTempVars(); + + static bool _registered; + static const std::string id; +}; + +class MacroActionLoopEdit : public ResizableWidget { + Q_OBJECT + +public: + MacroActionLoopEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + ~MacroActionLoopEdit(); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionLoopEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void MaxIterationsChanged(const NumberVariable &value); + +private: + MacroEdit *_macroEdit; + VariableSpinBox *_maxIterations; + + std::shared_ptr _entryData; + bool _loading = true; +}; + +} // namespace advss