nested macros

This commit is contained in:
WarmUpTill
2026-08-29 20:56:45 +02:00
parent 0ea98073d7
commit e2e7bec135
9 changed files with 208 additions and 11 deletions

View File

@@ -199,6 +199,7 @@ AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Are you sure you want to
AdvSceneSwitcher.macroTab.removeMultipleMacrosPopup.text="Are you sure you want to remove %1 macros/groups?"
AdvSceneSwitcher.macroTab.removeGroupPopup.text="Are you sure you want to remove \"%1\" group and all its elements?"
AdvSceneSwitcher.undo.renameMacro="Rename Macro '%1'"
AdvSceneSwitcher.undo.modifyMacro="Modify Macro '%1'"
AdvSceneSwitcher.undo.addMacro="Add Macro '%1'"
AdvSceneSwitcher.undo.removeMacro="Remove Macro '%1'"
AdvSceneSwitcher.undo.addSegment="Add %1 in '%2'"

View File

@@ -112,6 +112,14 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
return;
}
Macro *const parentMacro =
(*_entryData)->GetMacro()->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
const std::string oldId = (*_entryData)->GetId();
OBSDataAutoRelease oldData = obs_data_create();
(*_entryData)->Save(oldData);
@@ -141,8 +149,13 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
OBSDataAutoRelease newData = obs_data_create();
(*_entryData)->Save(newData);
RegisterSegmentTypeChangeUndoRedo(macro, segmentType, idx, oldId,
oldData, 0, id, newData, 0);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentTypeChangeUndoRedo(macro, segmentType, idx,
oldId, oldData, 0, id,
newData, 0);
}
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));

View File

@@ -83,6 +83,7 @@ bool MacroActionLoop::PostLoad()
MacroAction::PostLoad();
_loopMacro->PostLoad();
_loopMacro->SetActionTriggerMode(Macro::ActionTriggerMode::ALWAYS);
_loopMacro->SetNestedParentMacro(GetMacro());
return true;
}

View File

@@ -281,6 +281,7 @@ bool MacroActionMacro::PostLoad()
MacroAction::PostLoad();
_runOptions.macro.PostLoad();
_nestedMacro->PostLoad();
_nestedMacro->SetNestedParentMacro(GetMacro());
return true;
}

View File

@@ -251,6 +251,14 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
return;
}
Macro *const parentMacro =
(*_entryData)->GetMacro()->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
const std::string oldId = (*_entryData)->GetId();
const int oldLogic = (int)(*_entryData)->GetLogicType();
OBSDataAutoRelease oldData = obs_data_create();
@@ -276,9 +284,14 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
OBSDataAutoRelease newData = obs_data_create();
(*_entryData)->Save(newData);
RegisterSegmentTypeChangeUndoRedo(
macro, MacroEdit::SegmentType::CONDITION, idx, oldId, oldData,
oldLogic, id, newData, (int)(*_entryData)->GetLogicType());
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentTypeChangeUndoRedo(
macro, MacroEdit::SegmentType::CONDITION, idx, oldId,
oldData, oldLogic, id, newData,
(int)(*_entryData)->GetLogicType());
}
auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),

View File

@@ -1171,6 +1171,13 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
return;
}
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{
auto lock = LockContext();
macro->Actions().emplace(macro->Actions().begin() + idx,
@@ -1185,7 +1192,11 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
idx, new MacroActionEdit(this, &macro->Actions()[idx]));
SetActionData(*macro);
}
RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx);
}
HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
@@ -1251,7 +1262,12 @@ void MacroEdit::RemoveMacroAction(int idx)
return;
}
{
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
{
@@ -1273,6 +1289,9 @@ void MacroEdit::RemoveMacroAction(int idx)
macro->UpdateActionIndices();
SetActionData(*macro);
}
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroActionSelectionChanged(-1);
lastInteracted = SegmentType::ACTION;
emit(MacroSegmentOrderChanged());
@@ -1491,6 +1510,13 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
return;
}
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{
auto lock = LockContext();
macro->ElseActions().emplace(macro->ElseActions().begin() + idx,
@@ -1507,7 +1533,12 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro);
}
RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION, idx);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION,
idx);
}
HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
@@ -1557,7 +1588,12 @@ void MacroEdit::RemoveMacroElseAction(int idx)
return;
}
{
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
{
@@ -1580,6 +1616,9 @@ void MacroEdit::RemoveMacroElseAction(int idx)
macro->UpdateElseActionIndices();
SetElseActionData(*macro);
}
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroElseActionSelectionChanged(-1);
lastInteracted = SegmentType::ELSE_ACTION;
emit(MacroSegmentOrderChanged());
@@ -1715,6 +1754,13 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
return;
}
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{
auto lock = LockContext();
auto cond = macro->Conditions().emplace(
@@ -1733,7 +1779,11 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
idx == 0));
SetConditionData(*macro);
}
RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx);
}
HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
@@ -1769,7 +1819,12 @@ void MacroEdit::RemoveMacroCondition(int idx)
return;
}
{
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
int logic;
@@ -1798,6 +1853,9 @@ void MacroEdit::RemoveMacroCondition(int idx)
}
SetConditionData(*macro);
}
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroConditionSelectionChanged(-1);
lastInteracted = SegmentType::CONDITION;
emit(MacroSegmentOrderChanged());

View File

@@ -17,6 +17,7 @@
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <QCoreApplication>
#include <QString>
namespace advss {
@@ -630,6 +631,78 @@ static void replaceSegmentFromData(const char *jsonData)
}
}
// ---------------------------------------------------------------------------
// Macro state restore callback (for nested macro edits)
// ---------------------------------------------------------------------------
// Callback data format: {"name": "MacroName", "macro": {...}}
static void restoreMacroStateFromData(const char *jsonData)
{
OBSDataAutoRelease data = obs_data_create_from_json(jsonData);
if (!data) {
return;
}
const std::string macroName = obs_data_get_string(data, "name");
OBSDataAutoRelease macroData = obs_data_get_obj(data, "macro");
if (!macroData) {
return;
}
const auto macro = GetWeakMacroByName(macroName.c_str()).lock();
if (!macro) {
return;
}
auto *const window = AdvSceneSwitcher::window;
const bool uiVisible = SettingsWindowIsOpened() && window;
auto *const macroEdit = uiVisible ? window->ui->macroEdit : nullptr;
const bool isCurrent = macroEdit && macroEdit->GetMacro() &&
macroEdit->GetMacro()->Name() == macroName;
// Clear widgets while data is still valid; also saves splitter positions.
if (isCurrent) {
MacroSegmentList::SetCachingEnabled(false);
macroEdit->SetMacro({});
}
// Preserve splitter positions: Load() overwrites them with snapshot values.
const QList<int> savedCondSplitter =
macro->GetActionConditionSplitterPosition();
const QList<int> savedElseSplitter =
macro->GetElseActionSplitterPosition();
// Destroy deleteLater()-queued widgets now so their timers cannot fire
// with a dangling _entryData after the deques are cleared below.
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
if (isCurrent) {
MacroSegmentList::SetCachingEnabled(true);
}
{
auto lock = LockContext();
// Load() appends; clear first to avoid duplicate segments.
macro->Conditions().clear();
macro->Actions().clear();
macro->ElseActions().clear();
macro->Load(macroData);
macro->PostLoad();
RunAndClearPostLoadSteps();
}
if (isCurrent) {
if (!savedCondSplitter.isEmpty()) {
macro->SetActionConditionSplitterPosition(
savedCondSplitter);
}
if (!savedElseSplitter.isEmpty()) {
macro->SetElseActionSplitterPosition(savedElseSplitter);
}
macroEdit->SetMacro(macro);
}
}
// ---------------------------------------------------------------------------
// Rename undo/redo callback
// ---------------------------------------------------------------------------
@@ -1062,4 +1135,32 @@ void RegisterSegmentTypeChangeUndoRedo(Macro *macro, SegmentType type,
obs_data_get_json(redoData), false);
}
void RegisterMacroModifyUndoRedo(Macro *parentMacro, obs_data_t *beforeData)
{
if (!parentMacro || !beforeData) {
return;
}
const std::string macroName = parentMacro->Name();
OBSDataAutoRelease afterMacroData = obs_data_create();
parentMacro->Save(afterMacroData);
OBSDataAutoRelease undoData = obs_data_create();
obs_data_set_string(undoData, "name", macroName.c_str());
obs_data_set_obj(undoData, "macro", beforeData);
OBSDataAutoRelease redoData = obs_data_create();
obs_data_set_string(redoData, "name", macroName.c_str());
obs_data_set_obj(redoData, "macro", afterMacroData);
const std::string actionName =
fmtUndoName("AdvSceneSwitcher.undo.modifyMacro", macroName);
obs_frontend_add_undo_redo_action(actionName.c_str(),
&restoreMacroStateFromData,
&restoreMacroStateFromData,
obs_data_get_json(undoData),
obs_data_get_json(redoData), false);
}
} // namespace advss

View File

@@ -26,5 +26,6 @@ void RegisterMacroRenameUndoRedo(const std::string &oldName,
void RegisterGroupCreateUndoRedo(const std::string &groupName);
void RegisterGroupRemoveUndoRedo(const std::string &groupName);
void RegisterGroupDeleteUndoRedo(Macro *macro);
void RegisterMacroModifyUndoRedo(Macro *parentMacro, obs_data_t *beforeData);
} // namespace advss

View File

@@ -130,6 +130,13 @@ public:
void SetParent(std::shared_ptr<Macro> m) { _parent = m; }
std::shared_ptr<Macro> Parent() const;
// For nested macros embedded in actions (e.g. loop, nested-macro action)
void SetNestedParentMacro(Macro *parent)
{
_nestedParentMacro = parent;
}
Macro *GetNestedParentMacro() const { return _nestedParentMacro; }
// Saving and loading
bool Save(obs_data_t *obj, bool saveForCopy = false) const;
bool Load(obs_data_t *obj);
@@ -187,6 +194,7 @@ private:
std::deque<std::shared_ptr<MacroAction>> _elseActions;
std::weak_ptr<Macro> _parent;
Macro *_nestedParentMacro = nullptr;
uint32_t _groupSize = 0;
bool _isGroup = false;
bool _isCollapsed = false;