From 5f963b5b7d042a6941e3e6ac9422fa1f41645767 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Sat, 21 Jun 2025 19:52:10 +0200 Subject: [PATCH] Refactor macro edit area into separate class This will enable support for nested macros --- CMakeLists.txt | 2 + forms/advanced-scene-switcher.ui | 371 +---- forms/macro-edit.ui | 388 +++++ lib/advanced-scene-switcher.cpp | 26 +- lib/advanced-scene-switcher.hpp | 79 +- lib/general.cpp | 2 +- lib/macro/macro-action-edit.cpp | 469 ------ lib/macro/macro-condition-edit.cpp | 285 ---- lib/macro/macro-edit.cpp | 1824 ++++++++++++++++++++++++ lib/macro/macro-edit.hpp | 144 ++ lib/macro/macro-segment-copy-paste.cpp | 22 +- lib/macro/macro-segment-copy-paste.hpp | 4 +- lib/macro/macro-tab.cpp | 1020 +------------ lib/utils/splitter-helpers.cpp | 32 + lib/utils/splitter-helpers.hpp | 8 + lib/utils/tab-helpers.hpp | 1 + 16 files changed, 2468 insertions(+), 2209 deletions(-) create mode 100644 forms/macro-edit.ui create mode 100644 lib/macro/macro-edit.cpp create mode 100644 lib/macro/macro-edit.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ab6e44b..dfb31afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,8 @@ target_sources( lib/macro/macro-condition.hpp lib/macro/macro-dock.cpp lib/macro/macro-dock.hpp + lib/macro/macro-edit.cpp + lib/macro/macro-edit.hpp lib/macro/macro-export-import-dialog.cpp lib/macro/macro-export-import-dialog.hpp lib/macro/macro-helpers.cpp diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui index 276bad52..879d08aa 100644 --- a/forms/advanced-scene-switcher.ui +++ b/forms/advanced-scene-switcher.ui @@ -790,7 +790,7 @@ - + AdvSceneSwitcher.macroTab.edit @@ -896,339 +896,7 @@ - - - Qt::Vertical - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 1 - - - - - - - - 9 - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionAddButton - - - addIconSmall - - - icon-plus - - - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionRemoveButton - - - removeIconSmall - - - icon-trash - - - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionTopButton - - - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionUpButton - - - - - - upArrowIconSmall - - - icon-up - - - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionDownButton - - - downArrowIconSmall - - - icon-down - - - - - - - AdvSceneSwitcher.macroTab.tooltip.conditionBottomButton - - - - - - - - - - Qt::Vertical - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 1 - - - - - - - - 9 - - - - - AdvSceneSwitcher.macroTab.tooltip.actionAddButton - - - addIconSmall - - - icon-plus - - - - - - - AdvSceneSwitcher.macroTab.tooltip.actionRemoveButton - - - removeIconSmall - - - icon-trash - - - - - - - AdvSceneSwitcher.macroTab.tooltip.actionTopButton - - - - - - - AdvSceneSwitcher.macroTab.tooltip.actionUpButton - - - upArrowIconSmall - - - icon-up - - - - - - - AdvSceneSwitcher.macroTab.tooltip.actionDownButton - - - downArrowIconSmall - - - icon-down - - - - - - - AdvSceneSwitcher.macroTab.tooltip.actionBottomButton - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - AdvSceneSwitcher.macroTab.tooltip.toggleShowElseSection - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 1 - - - - - - - - 9 - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionAddButton - - - addIconSmall - - - icon-plus - - - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionRemoveButton - - - removeIconSmall - - - icon-trash - - - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionTopButton - - - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton - - - upArrowIconSmall - - - icon-up - - - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton - - - downArrowIconSmall - - - icon-down - - - - - - - AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton - - - - - - - - - + @@ -4440,12 +4108,6 @@ QListView
macro-tree.hpp
- - advss::MacroSegmentList - QScrollArea -
macro-segment-list.hpp
- 1 -
advss::MacroRunButton QPushButton @@ -4456,6 +4118,12 @@ QWidget
duration-control.hpp
+ + advss::MacroEdit + QWidget +
macro-edit.hpp
+ 1 +
tabWidget @@ -4492,28 +4160,7 @@ runMacroInParallel runMacroOnChange macroSettings - conditionsList - conditionAdd - conditionRemove - conditionTop - conditionUp - conditionDown - conditionBottom - actionsList - actionAdd - actionRemove - actionTop - actionUp - actionDown - actionBottom - toggleElseActions - elseActionsList - elseActionAdd - elseActionRemove - elseActionTop - elseActionUp - elseActionDown - elseActionBottom + macroEdit sceneGroups sceneGroupAdd sceneGroupRemove diff --git a/forms/macro-edit.ui b/forms/macro-edit.ui new file mode 100644 index 00000000..84883133 --- /dev/null +++ b/forms/macro-edit.ui @@ -0,0 +1,388 @@ + + + MacroEdit + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Qt::Vertical + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + + + + + 9 + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionAddButton + + + addIconSmall + + + icon-plus + + + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionRemoveButton + + + removeIconSmall + + + icon-trash + + + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionTopButton + + + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionUpButton + + + + + + upArrowIconSmall + + + icon-up + + + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionDownButton + + + downArrowIconSmall + + + icon-down + + + + + + + AdvSceneSwitcher.macroTab.tooltip.conditionBottomButton + + + + + + + + + + Qt::Vertical + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + + + + + 9 + + + + + AdvSceneSwitcher.macroTab.tooltip.actionAddButton + + + addIconSmall + + + icon-plus + + + + + + + AdvSceneSwitcher.macroTab.tooltip.actionRemoveButton + + + removeIconSmall + + + icon-trash + + + + + + + AdvSceneSwitcher.macroTab.tooltip.actionTopButton + + + + + + + AdvSceneSwitcher.macroTab.tooltip.actionUpButton + + + upArrowIconSmall + + + icon-up + + + + + + + AdvSceneSwitcher.macroTab.tooltip.actionDownButton + + + downArrowIconSmall + + + icon-down + + + + + + + AdvSceneSwitcher.macroTab.tooltip.actionBottomButton + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + AdvSceneSwitcher.macroTab.tooltip.toggleShowElseSection + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + + + + + 9 + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionAddButton + + + addIconSmall + + + icon-plus + + + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionRemoveButton + + + removeIconSmall + + + icon-trash + + + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionTopButton + + + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton + + + upArrowIconSmall + + + icon-up + + + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton + + + downArrowIconSmall + + + icon-down + + + + + + + AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton + + + + + + + + + + + + + + + advss::MacroSegmentList + QScrollArea +
macro-segment-list.hpp
+ 1 +
+
+ + conditionsList + conditionAdd + conditionRemove + conditionTop + conditionUp + conditionDown + conditionBottom + actionsList + actionAdd + actionRemove + actionTop + actionUp + actionDown + actionBottom + toggleElseActions + elseActionsList + elseActionAdd + elseActionRemove + elseActionTop + elseActionUp + elseActionDown + elseActionBottom + + + +
diff --git a/lib/advanced-scene-switcher.cpp b/lib/advanced-scene-switcher.cpp index 5914e8c3..85abdfa3 100644 --- a/lib/advanced-scene-switcher.cpp +++ b/lib/advanced-scene-switcher.cpp @@ -146,16 +146,7 @@ void AdvSceneSwitcher::LoadUI() bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event) { auto eventType = event->type(); - if (obj == ui->macroElseActions && eventType == QEvent::Resize) { - QResizeEvent *resizeEvent = static_cast(event); - - if (resizeEvent->size().height() == 0) { - SetElseActionsStateToHidden(); - return QDialog::eventFilter(obj, event); - } - - SetElseActionsStateToVisible(); - } else if (eventType == QEvent::KeyPress) { + if (eventType == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); auto pressedKey = keyEvent->key(); @@ -744,6 +735,21 @@ void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable) highlight = HighlightWidget(ui->macroSettings, Qt::green); } +void AdvSceneSwitcher::HighlightAction(int idx, QColor color) const +{ + ui->macroEdit->HighlightAction(idx, color); +} + +void AdvSceneSwitcher::HighlightElseAction(int idx, QColor color) const +{ + ui->macroEdit->HighlightElseAction(idx, color); +} + +void AdvSceneSwitcher::HighlightCondition(int idx, QColor color) const +{ + ui->macroEdit->HighlightCondition(idx, color); +} + void HighlightMacroSettingsButton(bool enable) { auto window = GetSettingsWindow(); diff --git a/lib/advanced-scene-switcher.hpp b/lib/advanced-scene-switcher.hpp index 33d5d149..7049114e 100644 --- a/lib/advanced-scene-switcher.hpp +++ b/lib/advanced-scene-switcher.hpp @@ -79,6 +79,7 @@ public slots: /* --- Begin of macro tab section --- */ public: void SetupMacroTab(); + bool MacroTabIsInFocus(); bool AddNewMacro(std::shared_ptr &res, std::string &name, std::string format = ""); void RemoveMacro(std::shared_ptr &); @@ -86,7 +87,6 @@ public: void RenameMacro(std::shared_ptr &, const QString &name); std::shared_ptr GetSelectedMacro() const; std::vector> GetSelectedMacros() const; - void SetEditMacro(Macro &m); void SetMacroEditAreaDisabled(bool) const; void HighlightAction(int idx, QColor color = QColor(Qt::green)) const; void HighlightElseAction(int idx, @@ -111,82 +111,14 @@ public slots: void on_macroName_editingFinished(); void on_runMacroInParallel_stateChanged(int value) const; void on_runMacroOnChange_stateChanged(int value) const; - void on_conditionAdd_clicked(); - void on_conditionRemove_clicked(); - void on_conditionTop_clicked(); - void on_conditionUp_clicked(); - void on_conditionDown_clicked(); - void on_conditionBottom_clicked(); - void on_actionAdd_clicked(); - void on_actionRemove_clicked(); - void on_actionTop_clicked(); - void on_actionUp_clicked(); - void on_actionDown_clicked(); - void on_actionBottom_clicked(); - void on_toggleElseActions_clicked() const; - void on_elseActionAdd_clicked(); - void on_elseActionRemove_clicked(); - void on_elseActionTop_clicked(); - void on_elseActionUp_clicked(); - void on_elseActionDown_clicked(); - void on_elseActionBottom_clicked(); - void MacroSelectionAboutToChange() const; void MacroSelectionChanged(); - void UpMacroSegementHotkey(); - void DownMacroSegementHotkey(); - void DeleteMacroSegementHotkey(); void ShowMacroContextMenu(const QPoint &); - void ShowMacroActionsContextMenu(const QPoint &); - void ShowMacroElseActionsContextMenu(const QPoint &); - void ShowMacroConditionsContextMenu(const QPoint &); void CopyMacro(); void RenameSelectedMacro(); void ExportMacros() const; void ImportMacros(); - void ExpandAllActions() const; - void ExpandAllElseActions() const; - void ExpandAllConditions() const; - void CollapseAllActions() const; - void CollapseAllElseActions() const; - void CollapseAllConditions() const; - void MinimizeActions() const; - void MaximizeActions() const; - void MinimizeElseActions() const; - void MaximizeElseActions() const; - void MinimizeConditions() const; - void MaximizeConditions() const; - void SetElseActionsStateToHidden() const; - void SetElseActionsStateToVisible() const; - void MacroActionSelectionChanged(int idx); - void MacroActionReorder(int to, int target); - void AddMacroAction(Macro *macro, int idx, const std::string &id, - obs_data_t *data); - void AddMacroAction(int idx); - void RemoveMacroAction(int idx); - void MoveMacroActionUp(int idx); - void MoveMacroActionDown(int idx); - void MacroElseActionSelectionChanged(int idx); - void MacroElseActionReorder(int to, int target); - void AddMacroElseAction(Macro *macro, int idx, const std::string &id, - obs_data_t *data); - void AddMacroElseAction(int idx); - void RemoveMacroElseAction(int idx); - void SwapElseActions(Macro *m, int pos1, int pos2); - void MoveMacroElseActionUp(int idx); - void MoveMacroElseActionDown(int idx); - void MacroConditionSelectionChanged(int idx); - void MacroConditionReorder(int to, int target); - void AddMacroCondition(int idx); - void AddMacroCondition(Macro *macro, int idx, const std::string &id, - obs_data_t *data, Logic::Type logic); - void RemoveMacroCondition(int idx); - void MoveMacroConditionUp(int idx); - void MoveMacroConditionDown(int idx); - void HighlightControls() const; void HighlightOnChange() const; void on_macroSettings_clicked(); - void CopyMacroSegment(); - void PasteMacroSegment(); signals: void MacroAdded(const QString &name); @@ -201,16 +133,7 @@ signals: void ConnectionRemoved(const QString &); private: - enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS }; - - void SetupMacroSegmentSelection(MacroSection type, int idx); bool ResolveMacroImportNameConflict(std::shared_ptr &); - bool MacroTabIsInFocus(); - - MacroSection lastInteracted = MacroSection::CONDITIONS; - int currentConditionIdx = -1; - int currentActionIdx = -1; - int currentElseActionIdx = -1; /* --- End of macro tab section --- */ diff --git a/lib/general.cpp b/lib/general.cpp index 10961c27..6991116f 100644 --- a/lib/general.cpp +++ b/lib/general.cpp @@ -163,7 +163,7 @@ void AdvSceneSwitcher::closeEvent(QCloseEvent *) switcher->windowSize = this->size(); switcher->macroListMacroEditSplitterPosition = ui->macroListMacroEditSplitter->sizes(); - MacroSelectionAboutToChange(); // Trigger saving of splitter states + ui->macroEdit->SetMacro(nullptr); // Trigger saving of splitter states obs_frontend_save(); } diff --git a/lib/macro/macro-action-edit.cpp b/lib/macro/macro-action-edit.cpp index c5465be0..9c7b13e1 100644 --- a/lib/macro/macro-action-edit.cpp +++ b/lib/macro/macro-action-edit.cpp @@ -158,473 +158,4 @@ std::shared_ptr MacroActionEdit::Data() const return *_entryData; } -void AdvSceneSwitcher::AddMacroAction(Macro *macro, int idx, - const std::string &id, obs_data_t *data) -{ - if (idx < 0 || idx > (int)macro->Actions().size()) { - assert(false); - return; - } - - { - auto lock = LockContext(); - macro->Actions().emplace(macro->Actions().begin() + idx, - MacroActionFactory::Create(id, macro)); - if (data) { - macro->Actions().at(idx)->Load(data); - } - macro->Actions().at(idx)->PostLoad(); - RunPostLoadSteps(); - macro->UpdateActionIndices(); - ui->actionsList->Insert( - idx, - new MacroActionEdit(this, ¯o->Actions()[idx], id)); - SetActionData(*macro); - } - HighlightAction(idx); - ui->actionsList->SetHelpMsgVisible(false); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::AddMacroAction(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx > (int)macro->Actions().size()) { - assert(false); - return; - } - - std::string id; - if (idx - 1 >= 0) { - id = macro->Actions().at(idx - 1)->GetId(); - } else { - id = MacroAction::GetDefaultID(); - } - - OBSDataAutoRelease data; - if (idx - 1 >= 0) { - data = obs_data_create(); - macro->Actions().at(idx - 1)->Save(data); - } - AddMacroAction(macro.get(), idx, id, data); -} - -void AdvSceneSwitcher::on_actionAdd_clicked() -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (currentActionIdx == -1) { - AddMacroAction((int)macro->Actions().size()); - } else { - AddMacroAction(currentActionIdx + 1); - } - if (currentActionIdx != -1) { - MacroActionSelectionChanged(currentActionIdx + 1); - } -} - -void AdvSceneSwitcher::RemoveMacroAction(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->Actions().size()) { - return; - } - - { - auto lock = LockContext(); - ui->actionsList->Remove(idx); - macro->Actions().erase(macro->Actions().begin() + idx); - SetMacroAbortWait(true); - GetMacroWaitCV().notify_all(); - macro->UpdateActionIndices(); - SetActionData(*macro); - } - MacroActionSelectionChanged(-1); - lastInteracted = MacroSection::ACTIONS; - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::on_actionRemove_clicked() -{ - if (currentActionIdx == -1) { - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - RemoveMacroAction((int)macro->Actions().size() - 1); - } else { - RemoveMacroAction(currentActionIdx); - } - MacroActionSelectionChanged(-1); -} - -void AdvSceneSwitcher::on_actionTop_clicked() -{ - if (currentActionIdx == -1) { - return; - } - MacroActionReorder(0, currentActionIdx); - MacroActionSelectionChanged(0); -} - -void AdvSceneSwitcher::on_actionUp_clicked() -{ - if (currentActionIdx == -1 || currentActionIdx == 0) { - return; - } - MoveMacroActionUp(currentActionIdx); - MacroActionSelectionChanged(currentActionIdx - 1); -} - -void AdvSceneSwitcher::on_actionDown_clicked() -{ - if (currentActionIdx == -1 || - currentActionIdx == ui->actionsList->ContentLayout()->count() - 1) { - return; - } - MoveMacroActionDown(currentActionIdx); - MacroActionSelectionChanged(currentActionIdx + 1); -} - -void AdvSceneSwitcher::on_actionBottom_clicked() -{ - if (currentActionIdx == -1) { - return; - } - const int newIdx = ui->actionsList->ContentLayout()->count() - 1; - MacroActionReorder(newIdx, currentActionIdx); - MacroActionSelectionChanged(newIdx); -} - -void AdvSceneSwitcher::on_elseActionAdd_clicked() -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (currentElseActionIdx == -1) { - AddMacroElseAction((int)macro->ElseActions().size()); - } else { - AddMacroElseAction(currentElseActionIdx + 1); - } - if (currentElseActionIdx != -1) { - MacroElseActionSelectionChanged(currentElseActionIdx + 1); - } -} - -void AdvSceneSwitcher::on_elseActionRemove_clicked() -{ - if (currentElseActionIdx == -1) { - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - RemoveMacroElseAction((int)macro->ElseActions().size() - 1); - } else { - RemoveMacroElseAction(currentElseActionIdx); - } - MacroElseActionSelectionChanged(-1); -} - -void AdvSceneSwitcher::on_elseActionTop_clicked() -{ - if (currentElseActionIdx == -1) { - return; - } - MacroElseActionReorder(0, currentElseActionIdx); - MacroElseActionSelectionChanged(0); -} - -void AdvSceneSwitcher::on_elseActionUp_clicked() -{ - if (currentElseActionIdx == -1 || currentElseActionIdx == 0) { - return; - } - MoveMacroElseActionUp(currentElseActionIdx); - MacroElseActionSelectionChanged(currentElseActionIdx - 1); -} - -void AdvSceneSwitcher::on_elseActionDown_clicked() -{ - if (currentElseActionIdx == -1 || - currentElseActionIdx == - ui->elseActionsList->ContentLayout()->count() - 1) { - return; - } - MoveMacroElseActionDown(currentElseActionIdx); - MacroElseActionSelectionChanged(currentElseActionIdx + 1); -} - -void AdvSceneSwitcher::on_elseActionBottom_clicked() -{ - if (currentElseActionIdx == -1) { - return; - } - const int newIdx = ui->elseActionsList->ContentLayout()->count() - 1; - MacroElseActionReorder(newIdx, currentElseActionIdx); - MacroElseActionSelectionChanged(newIdx); -} - -void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2) -{ - if (pos1 == pos2) { - return; - } - if (pos1 > pos2) { - std::swap(pos1, pos2); - } - - auto lock = LockContext(); - iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2); - m->UpdateActionIndices(); - auto widget1 = static_cast( - ui->actionsList->ContentLayout()->takeAt(pos1)->widget()); - auto widget2 = static_cast( - ui->actionsList->ContentLayout()->takeAt(pos2 - 1)->widget()); - ui->actionsList->Insert(pos1, widget2); - ui->actionsList->Insert(pos2, widget1); - SetActionData(*m); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::MoveMacroActionUp(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 1 || idx >= (int)macro->Actions().size()) { - return; - } - - SwapActions(macro.get(), idx, idx - 1); - HighlightAction(idx - 1); -} - -void AdvSceneSwitcher::MoveMacroActionDown(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->Actions().size() - 1) { - return; - } - - SwapActions(macro.get(), idx, idx + 1); - HighlightAction(idx + 1); -} - -void AdvSceneSwitcher::MacroElseActionSelectionChanged(int idx) -{ - SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx); -} - -void AdvSceneSwitcher::MacroElseActionReorder(int to, int from) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (to == from || from < 0 || from > (int)macro->ElseActions().size() || - to < 0 || to > (int)macro->ElseActions().size()) { - return; - } - { - auto lock = LockContext(); - auto action = macro->ElseActions().at(from); - macro->ElseActions().erase(macro->ElseActions().begin() + from); - macro->ElseActions().insert(macro->ElseActions().begin() + to, - action); - macro->UpdateElseActionIndices(); - ui->elseActionsList->ContentLayout()->insertItem( - to, ui->elseActionsList->ContentLayout()->takeAt(from)); - SetElseActionData(*macro); - } - HighlightElseAction(to); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::AddMacroElseAction(Macro *macro, int idx, - const std::string &id, - obs_data_t *data) -{ - if (idx < 0 || idx > (int)macro->ElseActions().size()) { - assert(false); - return; - } - - { - auto lock = LockContext(); - macro->ElseActions().emplace(macro->ElseActions().begin() + idx, - MacroActionFactory::Create(id, - macro)); - if (data) { - macro->ElseActions().at(idx)->Load(data); - } - macro->ElseActions().at(idx)->PostLoad(); - RunPostLoadSteps(); - macro->UpdateElseActionIndices(); - ui->elseActionsList->Insert( - idx, new MacroActionEdit( - this, ¯o->ElseActions()[idx], id)); - SetElseActionData(*macro); - } - HighlightElseAction(idx); - ui->elseActionsList->SetHelpMsgVisible(false); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::AddMacroElseAction(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx > (int)macro->ElseActions().size()) { - assert(false); - return; - } - - std::string id; - if (idx - 1 >= 0) { - id = macro->ElseActions().at(idx - 1)->GetId(); - } else { - id = MacroAction::GetDefaultID(); - } - - OBSDataAutoRelease data; - if (idx - 1 >= 0) { - data = obs_data_create(); - macro->ElseActions().at(idx - 1)->Save(data); - } - AddMacroElseAction(macro.get(), idx, id, data); -} - -void AdvSceneSwitcher::RemoveMacroElseAction(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->ElseActions().size()) { - return; - } - - { - auto lock = LockContext(); - ui->elseActionsList->Remove(idx); - macro->ElseActions().erase(macro->ElseActions().begin() + idx); - SetMacroAbortWait(true); - GetMacroWaitCV().notify_all(); - macro->UpdateElseActionIndices(); - SetElseActionData(*macro); - } - MacroElseActionSelectionChanged(-1); - lastInteracted = MacroSection::ELSE_ACTIONS; - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::SwapElseActions(Macro *m, int pos1, int pos2) -{ - if (pos1 == pos2) { - return; - } - if (pos1 > pos2) { - std::swap(pos1, pos2); - } - - auto lock = LockContext(); - iter_swap(m->ElseActions().begin() + pos1, - m->ElseActions().begin() + pos2); - m->UpdateElseActionIndices(); - auto widget1 = static_cast( - ui->elseActionsList->ContentLayout()->takeAt(pos1)->widget()); - auto widget2 = static_cast( - ui->elseActionsList->ContentLayout() - ->takeAt(pos2 - 1) - ->widget()); - ui->elseActionsList->Insert(pos1, widget2); - ui->elseActionsList->Insert(pos2, widget1); - SetElseActionData(*m); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::MoveMacroElseActionUp(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 1 || idx >= (int)macro->ElseActions().size()) { - return; - } - - SwapElseActions(macro.get(), idx, idx - 1); - HighlightElseAction(idx - 1); -} - -void AdvSceneSwitcher::MoveMacroElseActionDown(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->ElseActions().size() - 1) { - return; - } - - SwapElseActions(macro.get(), idx, idx + 1); - HighlightElseAction(idx + 1); -} - -void AdvSceneSwitcher::MacroActionSelectionChanged(int idx) -{ - SetupMacroSegmentSelection(MacroSection::ACTIONS, idx); -} - -void AdvSceneSwitcher::MacroActionReorder(int to, int from) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (to == from || from < 0 || from > (int)macro->Actions().size() || - to < 0 || to > (int)macro->Actions().size()) { - return; - } - { - auto lock = LockContext(); - auto action = macro->Actions().at(from); - macro->Actions().erase(macro->Actions().begin() + from); - macro->Actions().insert(macro->Actions().begin() + to, action); - macro->UpdateActionIndices(); - ui->actionsList->ContentLayout()->insertItem( - to, ui->actionsList->ContentLayout()->takeAt(from)); - SetActionData(*macro); - } - HighlightAction(to); - emit(MacroSegmentOrderChanged()); -} - } // namespace advss diff --git a/lib/macro/macro-condition-edit.cpp b/lib/macro/macro-condition-edit.cpp index 756aaa4a..a931c8d4 100644 --- a/lib/macro/macro-condition-edit.cpp +++ b/lib/macro/macro-condition-edit.cpp @@ -275,289 +275,4 @@ std::shared_ptr MacroConditionEdit::Data() const return *_entryData; } -void AdvSceneSwitcher::AddMacroCondition(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx > (int)macro->Conditions().size()) { - assert(false); - return; - } - - std::string id; - Logic::Type logic; - if (idx >= 1) { - id = macro->Conditions().at(idx - 1)->GetId(); - if (idx == 1) { - logic = Logic::Type::OR; - } else { - logic = macro->Conditions().at(idx - 1)->GetLogicType(); - } - } else { - id = MacroCondition::GetDefaultID(); - logic = Logic::Type::ROOT_NONE; - } - - OBSDataAutoRelease data; - if (idx - 1 >= 0) { - data = obs_data_create(); - macro->Conditions().at(idx - 1)->Save(data); - } - AddMacroCondition(macro.get(), idx, id, data.Get(), logic); -} - -void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx, - const std::string &id, - obs_data_t *data, Logic::Type logic) -{ - if (idx < 0 || idx > (int)macro->Conditions().size()) { - assert(false); - return; - } - - { - auto lock = LockContext(); - auto cond = macro->Conditions().emplace( - macro->Conditions().begin() + idx, - MacroConditionFactory::Create(id, macro)); - if (data) { - macro->Conditions().at(idx)->Load(data); - } - macro->Conditions().at(idx)->PostLoad(); - RunPostLoadSteps(); - (*cond)->SetLogicType(logic); - macro->UpdateConditionIndices(); - ui->conditionsList->Insert( - idx, - new MacroConditionEdit(this, ¯o->Conditions()[idx], - id, idx == 0)); - SetConditionData(*macro); - } - HighlightCondition(idx); - ui->conditionsList->SetHelpMsgVisible(false); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::on_conditionAdd_clicked() -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (currentConditionIdx == -1) { - AddMacroCondition((int)macro->Conditions().size()); - } else { - AddMacroCondition(currentConditionIdx + 1); - } - if (currentConditionIdx != -1) { - MacroConditionSelectionChanged(currentConditionIdx + 1); - } -} - -void AdvSceneSwitcher::RemoveMacroCondition(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->Conditions().size()) { - return; - } - - { - auto lock = LockContext(); - ui->conditionsList->Remove(idx); - macro->Conditions().erase(macro->Conditions().begin() + idx); - macro->UpdateConditionIndices(); - if (idx == 0 && macro->Conditions().size() > 0) { - auto newRoot = macro->Conditions().at(0); - newRoot->SetLogicType(Logic::Type::ROOT_NONE); - static_cast( - ui->conditionsList->WidgetAt(0)) - ->SetRootNode(true); - } - SetConditionData(*macro); - } - MacroConditionSelectionChanged(-1); - lastInteracted = MacroSection::CONDITIONS; - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::on_conditionRemove_clicked() -{ - if (currentConditionIdx == -1) { - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - RemoveMacroCondition((int)macro->Conditions().size() - 1); - } else { - RemoveMacroCondition(currentConditionIdx); - } - MacroConditionSelectionChanged(-1); -} - -void AdvSceneSwitcher::on_conditionTop_clicked() -{ - if (currentConditionIdx == -1) { - return; - } - MacroConditionReorder(0, currentConditionIdx); - MacroConditionSelectionChanged(0); -} - -void AdvSceneSwitcher::on_conditionUp_clicked() -{ - if (currentConditionIdx == -1 || currentConditionIdx == 0) { - return; - } - MoveMacroConditionUp(currentConditionIdx); - MacroConditionSelectionChanged(currentConditionIdx - 1); -} - -void AdvSceneSwitcher::on_conditionDown_clicked() -{ - if (currentConditionIdx == -1 || - currentConditionIdx == - ui->conditionsList->ContentLayout()->count() - 1) { - return; - } - MoveMacroConditionDown(currentConditionIdx); - MacroConditionSelectionChanged(currentConditionIdx + 1); -} - -void AdvSceneSwitcher::on_conditionBottom_clicked() -{ - if (currentConditionIdx == -1) { - return; - } - const int newIdx = ui->conditionsList->ContentLayout()->count() - 1; - MacroConditionReorder(newIdx, currentConditionIdx); - MacroConditionSelectionChanged(newIdx); -} - -void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2) -{ - if (pos1 == pos2) { - return; - } - if (pos1 > pos2) { - std::swap(pos1, pos2); - } - - bool root = pos1 == 0; - auto lock = LockContext(); - iter_swap(m->Conditions().begin() + pos1, - m->Conditions().begin() + pos2); - m->UpdateConditionIndices(); - - auto c1 = m->Conditions().begin() + pos1; - auto c2 = m->Conditions().begin() + pos2; - if (root) { - auto logic1 = (*c1)->GetLogicType(); - auto logic2 = (*c2)->GetLogicType(); - (*c1)->SetLogicType(logic2); - (*c2)->SetLogicType(logic1); - } - - auto widget1 = static_cast( - ui->conditionsList->ContentLayout()->takeAt(pos1)->widget()); - auto widget2 = static_cast( - ui->conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget()); - ui->conditionsList->Insert(pos1, widget2); - ui->conditionsList->Insert(pos2, widget1); - SetConditionData(*m); - widget2->SetRootNode(root); - widget1->SetRootNode(false); - emit(MacroSegmentOrderChanged()); -} - -void AdvSceneSwitcher::MoveMacroConditionUp(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 1 || idx >= (int)macro->Conditions().size()) { - return; - } - - SwapConditions(macro.get(), idx, idx - 1); - HighlightCondition(idx - 1); -} - -void AdvSceneSwitcher::MoveMacroConditionDown(int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (idx < 0 || idx >= (int)macro->Conditions().size() - 1) { - return; - } - - SwapConditions(macro.get(), idx, idx + 1); - HighlightCondition(idx + 1); -} - -void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx) -{ - SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx); -} - -void AdvSceneSwitcher::MacroConditionReorder(int to, int from) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - if (to == from || from < 0 || from > (int)macro->Conditions().size() || - to < 0 || to > (int)macro->Conditions().size()) { - return; - } - { - auto lock = LockContext(); - auto condition = macro->Conditions().at(from); - if (to == 0) { - condition->SetLogicType(Logic::Type::ROOT_NONE); - static_cast( - ui->conditionsList->WidgetAt(from)) - ->SetRootNode(true); - macro->Conditions().at(0)->SetLogicType( - Logic::Type::AND); - static_cast( - ui->conditionsList->WidgetAt(0)) - ->SetRootNode(false); - } - if (from == 0) { - condition->SetLogicType(Logic::Type::AND); - static_cast( - ui->conditionsList->WidgetAt(from)) - ->SetRootNode(false); - macro->Conditions().at(1)->SetLogicType( - Logic::Type::ROOT_NONE); - static_cast( - ui->conditionsList->WidgetAt(1)) - ->SetRootNode(true); - } - macro->Conditions().erase(macro->Conditions().begin() + from); - macro->Conditions().insert(macro->Conditions().begin() + to, - condition); - macro->UpdateConditionIndices(); - ui->conditionsList->ContentLayout()->insertItem( - to, ui->conditionsList->ContentLayout()->takeAt(from)); - SetConditionData(*macro); - } - HighlightCondition(to); - emit(MacroSegmentOrderChanged()); -} - } // namespace advss diff --git a/lib/macro/macro-edit.cpp b/lib/macro/macro-edit.cpp new file mode 100644 index 00000000..9dc3d2e2 --- /dev/null +++ b/lib/macro/macro-edit.cpp @@ -0,0 +1,1824 @@ +#include "macro-edit.hpp" +#include "cursor-shape-changer.hpp" +#include "macro.hpp" +#include "macro-action-edit.hpp" +#include "macro-action-macro.hpp" +#include "macro-condition-edit.hpp" +#include "macro-segment-copy-paste.hpp" +#include "macro-segment-list.hpp" +#include "macro-settings.hpp" +#include "math-helpers.hpp" +#include "name-dialog.hpp" +#include "path-helpers.hpp" +#include "obs-module-helper.hpp" +#include "plugin-state-helpers.hpp" +#include "splitter-helpers.hpp" +#include "tab-helpers.hpp" +#include "ui-helpers.hpp" + +#include +#include +#include +#include + +namespace advss { + +static void moveControlsToSplitter(QSplitter *splitter, int idx, + QLayoutItem *item) +{ + static int splitterHandleWidth = 32; + auto handle = splitter->handle(idx); + auto layout = item->layout(); + int leftMargin, rightMargin; + layout->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr); + layout->setContentsMargins(leftMargin, 0, rightMargin, 0); + handle->setLayout(layout); + splitter->setHandleWidth(splitterHandleWidth); + splitter->setStyleSheet("QSplitter::handle {background: transparent;}"); +} + +static QToolBar * +setupToolBar(const std::initializer_list> + &widgetGroups) +{ + auto toolbar = new QToolBar(); + toolbar->setIconSize({16, 16}); + + QAction *lastSeperator = nullptr; + + for (const auto &widgetGroup : widgetGroups) { + for (const auto &widget : widgetGroup) { + toolbar->addWidget(widget); + } + lastSeperator = toolbar->addSeparator(); + } + + if (lastSeperator) { + toolbar->removeAction(lastSeperator); + } + + // Prevent "extension" button from showing up + toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, + QSizePolicy::Preferred); + return toolbar; +} + +MacroEdit::MacroEdit(QWidget *parent, QStringList helpMsg) + : ui(new Ui_MacroEdit) +{ + if (helpMsg.size() != 3) { + helpMsg << "AdvSceneSwitcher.macroTab.editConditionHelp" + << "AdvSceneSwitcher.macroTab.editActionHelp" + << "AdvSceneSwitcher.macroTab.editElseActionHelp"; + } + + ui->setupUi(this); + ui->macroElseActions->installEventFilter(this); + + ui->conditionsList->SetHelpMsg( + obs_module_text(helpMsg[0].toStdString().c_str())); + connect(ui->conditionsList, &MacroSegmentList::SelectionChanged, this, + &MacroEdit::MacroConditionSelectionChanged); + connect(ui->conditionsList, &MacroSegmentList::Reorder, this, + &MacroEdit::MacroConditionReorder); + + ui->actionsList->SetHelpMsg( + obs_module_text(helpMsg[1].toStdString().c_str())); + connect(ui->actionsList, &MacroSegmentList::SelectionChanged, this, + &MacroEdit::MacroActionSelectionChanged); + connect(ui->actionsList, &MacroSegmentList::Reorder, this, + &MacroEdit::MacroActionReorder); + + ui->elseActionsList->SetHelpMsg( + obs_module_text(helpMsg[2].toStdString().c_str())); + connect(ui->elseActionsList, &MacroSegmentList::SelectionChanged, this, + &MacroEdit::MacroElseActionSelectionChanged); + connect(ui->elseActionsList, &MacroSegmentList::Reorder, this, + &MacroEdit::MacroElseActionReorder); + + ui->actionsList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->actionsList, &QWidget::customContextMenuRequested, this, + &MacroEdit::ShowMacroActionsContextMenu); + ui->elseActionsList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->elseActionsList, &QWidget::customContextMenuRequested, this, + &MacroEdit::ShowMacroElseActionsContextMenu); + ui->conditionsList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->conditionsList, &QWidget::customContextMenuRequested, this, + &MacroEdit::ShowMacroConditionsContextMenu); + + // Set action and condition toolbars + const std::string pathPrefix = + GetDataFilePath("res/images/" + GetThemeTypeName()); + SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str()); + SetButtonIcon(ui->actionBottom, + (pathPrefix + "DoubleDown.svg").c_str()); + SetButtonIcon(ui->elseActionTop, (pathPrefix + "DoubleUp.svg").c_str()); + SetButtonIcon(ui->elseActionBottom, + (pathPrefix + "DoubleDown.svg").c_str()); + SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str()); + SetButtonIcon(ui->conditionBottom, + (pathPrefix + "DoubleDown.svg").c_str()); + SetButtonIcon(ui->toggleElseActions, + (pathPrefix + "NotEqual.svg").c_str()); + + auto conditionToolbar = + setupToolBar({{ui->conditionAdd, ui->conditionRemove}, + {ui->conditionTop, ui->conditionUp, + ui->conditionDown, ui->conditionBottom}}); + auto actionToolbar = setupToolBar({{ui->actionAdd, ui->actionRemove}, + {ui->actionTop, ui->actionUp, + ui->actionDown, ui->actionBottom}}); + auto elseActionToolbar = + setupToolBar({{ui->elseActionAdd, ui->elseActionRemove}, + {ui->elseActionTop, ui->elseActionUp, + ui->elseActionDown, ui->elseActionBottom}}); + + ui->conditionControlsLayout->addWidget(conditionToolbar); + ui->actionControlsLayout->insertWidget(0, actionToolbar); + ui->elseActionControlsLayout->addWidget(elseActionToolbar); + + // Move condition controls into splitter handle layout + moveControlsToSplitter(ui->macroActionConditionSplitter, 1, + ui->macroConditionsLayout->takeAt(1)); + moveControlsToSplitter(ui->macroElseActionSplitter, 1, + ui->macroActionsLayout->takeAt(1)); + + // Override splitter cursor icon when hovering over controls in splitter + SetCursorOnWidgetHover(ui->conditionAdd, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->conditionRemove, + Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->conditionTop, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->conditionUp, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->conditionDown, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->conditionBottom, + Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionAdd, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionRemove, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionTop, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionUp, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionDown, Qt::CursorShape::ArrowCursor); + SetCursorOnWidgetHover(ui->actionBottom, Qt::CursorShape::ArrowCursor); + + CenterSplitterPosition(ui->macroActionConditionSplitter); + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + + // Macro segment highlight + auto timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, + [this]() { RunSegmentHighlightChecks(); }); + timer->start(1500); + + SetupSegmentCopyPasteShortcutHandlers(this); +} + +void MacroEdit::SetMacro(const std::shared_ptr ¯o) +{ + if (_currentMacro) { + _currentMacro->SetActionConditionSplitterPosition( + ui->macroActionConditionSplitter->sizes()); + + auto elsePos = ui->macroElseActionSplitter->sizes(); + // If only conditions are visible maximize the actions to avoid neither + // actions nor elseActions being visible when the condition <-> action + // splitter is moved + if (elsePos[0] == 0 && elsePos[1] == 0) { + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + } else { + _currentMacro->SetElseActionSplitterPosition( + ui->macroElseActionSplitter->sizes()); + } + + ui->conditionsList->CacheCurrentWidgetsFor(_currentMacro.get()); + ui->actionsList->CacheCurrentWidgetsFor(_currentMacro.get()); + ui->elseActionsList->CacheCurrentWidgetsFor( + _currentMacro.get()); + } + + ui->conditionsList->Clear(); + ui->actionsList->Clear(); + ui->elseActionsList->Clear(); + + currentActionIdx = -1; + currentElseActionIdx = -1; + currentConditionIdx = -1; + + HighlightControls(); + + _currentMacro = macro; + + if (!macro) { + CenterSplitterPosition(ui->macroActionConditionSplitter); + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + ui->conditionsList->SetHelpMsgVisible(true); + ui->actionsList->SetHelpMsgVisible(true); + ui->elseActionsList->SetHelpMsgVisible(true); + return; + } + + PopulateMacroConditions(*macro); + PopulateMacroActions(*macro); + PopulateMacroElseActions(*macro); + + if (macro->IsGroup()) { + CenterSplitterPosition(ui->macroActionConditionSplitter); + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + return; + } + + if (macro->HasValidSplitterPositions()) { + ui->macroActionConditionSplitter->setSizes( + macro->GetActionConditionSplitterPosition()); + ui->macroElseActionSplitter->setSizes( + macro->GetElseActionSplitterPosition()); + } else { + CenterSplitterPosition(ui->macroActionConditionSplitter); + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + } +} + +void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const +{ + ui->conditionsList->ClearWidgetsFromCacheFor(macro); + ui->actionsList->ClearWidgetsFromCacheFor(macro); + ui->elseActionsList->ClearWidgetsFromCacheFor(macro); +} + +void MacroEdit::SetControlsDisabled(bool disable) const +{ + ui->macroActions->setDisabled(disable); + ui->macroElseActions->setDisabled(disable); + ui->macroConditions->setDisabled(disable); + ui->macroActionConditionSplitter->setDisabled(disable); +} + +bool MacroEdit::eventFilter(QObject *obj, QEvent *event) +{ + if (obj != ui->macroElseActions || event->type() != QEvent::Resize) { + return QWidget::eventFilter(obj, event); + } + + auto resizeEvent = static_cast(event); + if (resizeEvent->size().height() == 0) { + SetElseActionsStateToHidden(); + return QWidget::eventFilter(obj, event); + } + + SetElseActionsStateToVisible(); + return QWidget::eventFilter(obj, event); +} + +static bool +isValidMacroSegmentIdx(const std::deque> &list, + int idx) +{ + return (idx > 0 || (unsigned)idx < list.size()); +} + +void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + MacroSegmentList *setList = nullptr, *resetList1 = nullptr, + *resetList2 = nullptr; + int *setIdx = nullptr, *resetIdx1 = nullptr, *resetIdx2 = nullptr; + std::deque> segments; + + switch (type) { + case MacroEdit::MacroSection::CONDITIONS: + setList = ui->conditionsList; + setIdx = ¤tConditionIdx; + segments = {macro->Conditions().begin(), + macro->Conditions().end()}; + + resetList1 = ui->actionsList; + resetList2 = ui->elseActionsList; + resetIdx1 = ¤tActionIdx; + resetIdx2 = ¤tElseActionIdx; + break; + case MacroEdit::MacroSection::ACTIONS: + setList = ui->actionsList; + setIdx = ¤tActionIdx; + segments = {macro->Actions().begin(), macro->Actions().end()}; + + resetList1 = ui->conditionsList; + resetList2 = ui->elseActionsList; + resetIdx1 = ¤tConditionIdx; + resetIdx2 = ¤tElseActionIdx; + break; + case MacroEdit::MacroSection::ELSE_ACTIONS: + setList = ui->elseActionsList; + setIdx = ¤tElseActionIdx; + segments = {macro->ElseActions().begin(), + macro->ElseActions().end()}; + + resetList1 = ui->actionsList; + resetList2 = ui->conditionsList; + resetIdx1 = ¤tActionIdx; + resetIdx2 = ¤tConditionIdx; + break; + default: + break; + } + + setList->SetSelection(idx); + resetList1->SetSelection(-1); + resetList2->SetSelection(-1); + if (isValidMacroSegmentIdx(segments, idx)) { + *setIdx = idx; + } else { + *setIdx = -1; + } + *resetIdx1 = -1; + *resetIdx2 = -1; + + lastInteracted = type; + HighlightControls(); +} + +static void runSegmentHighlightChecksHelper(MacroSegmentList *list) +{ + MacroSegmentEdit *widget = nullptr; + for (int i = 0; (widget = list->WidgetAt(i)); i++) { + const auto data = widget->Data(); + + // No need to highlight nested macro action as it itself will + // highlight its segments if required + auto macroAction = dynamic_cast(data.get()); + if (macroAction && + macroAction->_action == + MacroActionMacro::Action::NESTED_MACRO) { + continue; + } + + if (data && data->GetHighlightAndReset()) { + list->Highlight(i); + } + } +} + +void MacroEdit::RunSegmentHighlightChecks() +{ + if (!HighlightUIElementsEnabled()) { + return; + } + + auto macro = _currentMacro; + if (!macro) { + return; + } + + const auto &settings = GetGlobalMacroSettings(); + if (settings._highlightConditions) { + runSegmentHighlightChecksHelper(ui->conditionsList); + } + if (settings._highlightActions) { + runSegmentHighlightChecksHelper(ui->actionsList); + runSegmentHighlightChecksHelper(ui->elseActionsList); + } +} + +bool MacroEdit::ElseSectionIsVisible() const +{ + const auto elsePosition = ui->macroElseActionSplitter->sizes(); + return elsePosition[1] != 0; +} + +void MacroEdit::PopulateMacroActions(Macro &m, uint32_t afterIdx) +{ + auto &actions = m.Actions(); + ui->actionsList->SetHelpMsgVisible(actions.size() == 0); + + if (ui->actionsList->PopulateWidgetsFromCache(&m)) { + return; + } + + // The layout system has not completed geometry propagation yet, so we + // can skip those checks for now + ui->actionsList->SetVisibilityCheckEnable(false); + + for (; afterIdx < actions.size(); afterIdx++) { + auto newEntry = new MacroActionEdit(this, &actions[afterIdx], + actions[afterIdx]->GetId()); + ui->actionsList->Add(newEntry); + } + + // Give the layout system time before enabling the visibility checks and + // fully constructing the visible macro segments + QTimer::singleShot(0, this, [this]() { + ui->actionsList->SetVisibilityCheckEnable(true); + }); +} + +void MacroEdit::PopulateMacroElseActions(Macro &m, uint32_t afterIdx) +{ + auto &actions = m.ElseActions(); + ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0); + + if (ui->elseActionsList->PopulateWidgetsFromCache(&m)) { + return; + } + + // The layout system has not completed geometry propagation yet, so we + // can skip those checks for now + ui->elseActionsList->SetVisibilityCheckEnable(false); + + for (; afterIdx < actions.size(); afterIdx++) { + auto newEntry = new MacroActionEdit(this, &actions[afterIdx], + actions[afterIdx]->GetId()); + ui->elseActionsList->Add(newEntry); + } + + // Give the layout system time before enabling the visibility checks and + // fully constructing the visible macro segments + QTimer::singleShot(0, this, [this]() { + ui->elseActionsList->SetVisibilityCheckEnable(true); + }); +} + +void MacroEdit::PopulateMacroConditions(Macro &m, uint32_t afterIdx) +{ + bool root = afterIdx == 0; + auto &conditions = m.Conditions(); + ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0); + + if (ui->conditionsList->PopulateWidgetsFromCache(&m)) { + return; + } + + // The layout system has not completed geometry propagation yet, so we + // can skip those checks for now + ui->conditionsList->SetVisibilityCheckEnable(false); + + for (; afterIdx < conditions.size(); afterIdx++) { + auto newEntry = new MacroConditionEdit( + this, &conditions[afterIdx], + conditions[afterIdx]->GetId(), root); + ui->conditionsList->Add(newEntry); + root = false; + } + + // Give the layout system time before enabling the visibility checks and + // fully constructing the visible macro segments + QTimer::singleShot(0, this, [this]() { + ui->conditionsList->SetVisibilityCheckEnable(true); + }); +} + +static void setCollapsedHelper(const std::shared_ptr &m, + MacroSegmentList *list, bool collapsed) +{ + if (!m) { + return; + } + list->SetCollapsed(collapsed); +} + +void MacroEdit::ExpandAllActions() const +{ + setCollapsedHelper(_currentMacro, ui->actionsList, false); +} + +void MacroEdit::ExpandAllElseActions() const +{ + setCollapsedHelper(_currentMacro, ui->elseActionsList, false); +} + +void MacroEdit::ExpandAllConditions() const +{ + setCollapsedHelper(_currentMacro, ui->conditionsList, false); +} + +void MacroEdit::CollapseAllActions() const +{ + setCollapsedHelper(_currentMacro, ui->actionsList, true); +} + +void MacroEdit::CollapseAllElseActions() const +{ + setCollapsedHelper(_currentMacro, ui->elseActionsList, true); +} + +void MacroEdit::CollapseAllConditions() const +{ + setCollapsedHelper(_currentMacro, ui->conditionsList, true); +} + +void MacroEdit::MinimizeActions() const +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + if (macro->ElseActions().size() == 0) { + ReduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); + } else { + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + ReduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); + } +} + +void MacroEdit::MaximizeActions() const +{ + MinimizeElseActions(); + MinimizeConditions(); +} + +void MacroEdit::MinimizeElseActions() const +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + if (macro->ElseActions().size() == 0) { + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); + } else { + ReduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 1); + } +} + +void MacroEdit::MaximizeElseActions() const +{ + MinimizeConditions(); + ReduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 0); +} + +void MacroEdit::MinimizeConditions() const +{ + ReduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 0); +} + +void MacroEdit::MaximizeConditions() const +{ + MinimizeElseActions(); + MinimizeActions(); +} + +void MacroEdit::on_toggleElseActions_clicked() const +{ + if (!ElseSectionIsVisible()) { + CenterSplitterPosition(ui->macroElseActionSplitter); + return; + } + + MaximizeFirstSplitterEntry(ui->macroElseActionSplitter); +} + +void MacroEdit::SetElseActionsStateToHidden() const +{ + ui->toggleElseActions->setToolTip(obs_module_text( + "AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip")); + ui->toggleElseActions->setChecked(false); +} + +void MacroEdit::SetElseActionsStateToVisible() const +{ + ui->toggleElseActions->setToolTip(obs_module_text( + "AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip")); + ui->toggleElseActions->setChecked(true); +} + +static void fade(QWidget *widget, bool fadeOut) +{ + const double fadeOutOpacity = 0.3; + // Don't use exactly 1.0 as for some reason this causes buttons in + // macroSplitter handle layout to not be redrawn unless mousing over + // them + const double fadeInOpacity = 0.99; + auto curEffect = widget->graphicsEffect(); + if (curEffect) { + auto curOpacity = + dynamic_cast(curEffect); + if (curOpacity && + ((fadeOut && DoubleEquals(curOpacity->opacity(), + fadeOutOpacity, 0.0001)) || + (!fadeOut && DoubleEquals(curOpacity->opacity(), + fadeInOpacity, 0.0001)))) { + return; + } + } else if (!fadeOut) { + return; + } + delete curEffect; + QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(); + widget->setGraphicsEffect(opacityEffect); + QPropertyAnimation *animation = + new QPropertyAnimation(opacityEffect, "opacity"); + animation->setDuration(350); + animation->setStartValue(fadeOut ? fadeInOpacity : fadeOutOpacity); + animation->setEndValue(fadeOut ? fadeOutOpacity : fadeInOpacity); + animation->setEasingCurve(QEasingCurve::OutQuint); + animation->start(QPropertyAnimation::DeleteWhenStopped); +} + +static void fadeWidgets(const std::vector &widgets, bool fadeOut) +{ + for (const auto &widget : widgets) { + fade(widget, fadeOut); + } +} + +void MacroEdit::HighlightAction(int idx, QColor color) const +{ + ui->actionsList->Highlight(idx, color); +} + +void MacroEdit::HighlightElseAction(int idx, QColor color) const +{ + ui->elseActionsList->Highlight(idx, color); +} + +void MacroEdit::HighlightCondition(int idx, QColor color) const +{ + ui->conditionsList->Highlight(idx, color); +} + +static void resetSegmentHighlights(MacroSegmentList *list) +{ + MacroSegmentEdit *widget = nullptr; + for (int i = 0; (widget = list->WidgetAt(i)); i++) { + if (widget && widget->Data()) { + (void)widget->Data()->GetHighlightAndReset(); + } + } +} + +void MacroEdit::ResetConditionHighlights() +{ + resetSegmentHighlights(ui->conditionsList); +} + +void MacroEdit::ResetActionHighlights() +{ + resetSegmentHighlights(ui->actionsList); + resetSegmentHighlights(ui->elseActionsList); +} + +void MacroEdit::HighlightControls() const +{ + const std::vector conditionControls{ + ui->conditionAdd, ui->conditionRemove, ui->conditionTop, + ui->conditionUp, ui->conditionDown, ui->conditionBottom, + }; + const std::vector actionControls{ + ui->actionAdd, ui->actionRemove, ui->actionTop, + ui->actionUp, ui->actionDown, ui->actionBottom, + }; + const std::vector elseActionControls{ + ui->elseActionAdd, ui->elseActionRemove, ui->elseActionTop, + ui->elseActionUp, ui->elseActionDown, ui->elseActionBottom, + }; + + if ((currentActionIdx == -1 && currentConditionIdx == -1 && + currentElseActionIdx == -1)) { + fadeWidgets(conditionControls, false); + fadeWidgets(actionControls, false); + fadeWidgets(elseActionControls, false); + } else if (currentConditionIdx != -1) { + fadeWidgets(conditionControls, false); + fadeWidgets(actionControls, true); + fadeWidgets(elseActionControls, true); + } else if (currentActionIdx != -1) { + fadeWidgets(conditionControls, true); + fadeWidgets(actionControls, false); + fadeWidgets(elseActionControls, true); + } else if (currentElseActionIdx != -1) { + fadeWidgets(conditionControls, true); + fadeWidgets(actionControls, true); + fadeWidgets(elseActionControls, false); + } else { + assert(false); + } +} + +void MacroEdit::SetActionData(Macro &m) const +{ + auto &actions = m.Actions(); + for (int idx = 0; idx < ui->actionsList->ContentLayout()->count(); + idx++) { + auto item = ui->actionsList->ContentLayout()->itemAt(idx); + if (!item) { + continue; + } + auto widget = static_cast(item->widget()); + if (!widget) { + continue; + } + widget->SetEntryData(&*(actions.begin() + idx)); + } +} + +void MacroEdit::SetElseActionData(Macro &m) const +{ + auto &actions = m.ElseActions(); + for (int idx = 0; idx < ui->elseActionsList->ContentLayout()->count(); + idx++) { + auto item = ui->elseActionsList->ContentLayout()->itemAt(idx); + if (!item) { + continue; + } + auto widget = static_cast(item->widget()); + if (!widget) { + continue; + } + widget->SetEntryData(&*(actions.begin() + idx)); + } +} + +void MacroEdit::SetConditionData(Macro &m) const +{ + auto &conditions = m.Conditions(); + for (int idx = 0; idx < ui->conditionsList->ContentLayout()->count(); + idx++) { + auto item = ui->conditionsList->ContentLayout()->itemAt(idx); + if (!item) { + continue; + } + auto widget = static_cast(item->widget()); + if (!widget) { + continue; + } + widget->SetEntryData(&*(conditions.begin() + idx)); + } +} + +static void setupCopyPasteContextMenuEnry(MacroEdit *edit, + QWidget *macroActions, + QWidget *macroElseActions, + MacroSegmentEdit *segmentEdit, + QMenu &menu) +{ + auto copy = menu.addAction( + obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), edit, + [edit]() { edit->CopyMacroSegment(); }); + copy->setEnabled(!!segmentEdit); + + bool pasteAsElseAction = true; + const char *pasteText = "AdvSceneSwitcher.macroTab.segment.paste"; + if (MacroActionIsInClipboard()) { + if (IsCursorInWidgetArea(macroActions)) { + pasteAsElseAction = false; + pasteText = + "AdvSceneSwitcher.macroTab.segment.pasteAction"; + } else if (IsCursorInWidgetArea(macroElseActions)) { + pasteAsElseAction = true; + pasteText = + "AdvSceneSwitcher.macroTab.segment.pasteElseAction"; + } + } + auto paste = menu.addAction( + obs_module_text(pasteText), edit, [edit, pasteAsElseAction]() { + SetCopySegmentTargetActionType(pasteAsElseAction); + edit->PasteMacroSegment(); + }); + paste->setEnabled(MacroSegmentIsInClipboard()); +} + +static void setupRemoveContextMenuEnry( + MacroEdit *edit, const std::function &remove, + const QPoint &pos, MacroSegmentList *list, QMenu &menu) +{ + const auto segmentEditIndex = list->IndexAt(pos); + if (segmentEditIndex == -1) { + return; + } + + menu.addAction( + obs_module_text("AdvSceneSwitcher.macroTab.segment.remove"), + edit, [edit, remove, segmentEditIndex]() { + remove(edit, segmentEditIndex); + }); +} + +static bool handleCustomLabelRename(MacroSegmentEdit *segmentEdit) +{ + std::string label; + auto segment = segmentEdit->Data(); + if (!segment) { + return false; + } + + bool accepted = NameDialog::AskForName( + GetSettingsWindow(), + obs_module_text( + "AdvSceneSwitcher.macroTab.segment.setCustomLabel"), + "", label, QString::fromStdString(segment->GetCustomLabel())); + if (!accepted) { + return false; + } + + segment->SetCustomLabel(label); + segmentEdit->HeaderInfoChanged(""); + return true; +} + +static void handleCustomLabelEnableChange(MacroSegmentEdit *segmentEdit, + QAction *contextMenuOption) +{ + bool enable = contextMenuOption->isChecked(); + auto segment = segmentEdit->Data(); + segment->SetUseCustomLabel(enable); + if (!enable) { + segmentEdit->HeaderInfoChanged( + QString::fromStdString(segment->GetShortDesc())); + return; + } + + if (!handleCustomLabelRename(segmentEdit)) { + segment->SetUseCustomLabel(false); + } +} + +static void setupSegmentLabelContextMenuEntries(MacroSegmentEdit *segmentEdit, + QMenu &menu) +{ + if (!segmentEdit) { + return; + } + + auto segment = segmentEdit ? segmentEdit->Data() : nullptr; + const bool customLabelIsEnabled = segment && + segment->GetUseCustomLabel(); + + auto enableCustomLabel = menu.addAction(obs_module_text( + "AdvSceneSwitcher.macroTab.segment.useCustomLabel")); + enableCustomLabel->setCheckable(true); + enableCustomLabel->setChecked(customLabelIsEnabled); + QWidget::connect(enableCustomLabel, &QAction::triggered, + [segmentEdit, enableCustomLabel]() { + handleCustomLabelEnableChange( + segmentEdit, enableCustomLabel); + }); + + if (!customLabelIsEnabled) { + return; + } + + auto customLabelRename = menu.addAction(obs_module_text( + "AdvSceneSwitcher.macroTab.segment.customLabelRename")); + QWidget::connect(customLabelRename, &QAction::triggered, + [segmentEdit]() { + handleCustomLabelRename(segmentEdit); + }); +} + +void MacroEdit::SetupContextMenu( + const QPoint &pos, const std::function &remove, + const std::function &expand, + const std::function &collapse, + const std::function &maximize, + const std::function &minimize, + MacroSegmentList *list) +{ + QMenu menu; + auto segmentEdit = list->WidgetAt(pos); + + setupCopyPasteContextMenuEnry(this, ui->macroActions, + ui->macroElseActions, segmentEdit, menu); + menu.addSeparator(); + setupRemoveContextMenuEnry(this, remove, pos, list, menu); + menu.addSeparator(); + setupSegmentLabelContextMenuEntries(segmentEdit, menu); + menu.addSeparator(); + menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"), + this, [this, expand]() { expand(this); }); + menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), + this, [this, collapse]() { collapse(this); }); + menu.addSeparator(); + menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"), + this, [this, maximize]() { maximize(this); }); + menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"), + this, [this, minimize]() { minimize(this); }); + menu.exec(list->mapToGlobal(pos)); +} + +void MacroEdit::ShowMacroActionsContextMenu(const QPoint &pos) +{ + SetupContextMenu(pos, &MacroEdit::RemoveMacroAction, + &MacroEdit::ExpandAllActions, + &MacroEdit::CollapseAllActions, + &MacroEdit::MaximizeActions, + &MacroEdit::MinimizeActions, ui->actionsList); +} + +void MacroEdit::ShowMacroElseActionsContextMenu(const QPoint &pos) +{ + SetupContextMenu(pos, &MacroEdit::RemoveMacroElseAction, + &MacroEdit::ExpandAllElseActions, + &MacroEdit::CollapseAllElseActions, + &MacroEdit::MaximizeElseActions, + &MacroEdit::MinimizeElseActions, ui->elseActionsList); +} + +void MacroEdit::ShowMacroConditionsContextMenu(const QPoint &pos) +{ + SetupContextMenu(pos, &MacroEdit::RemoveMacroCondition, + &MacroEdit::ExpandAllConditions, + &MacroEdit::CollapseAllConditions, + &MacroEdit::MaximizeConditions, + &MacroEdit::MinimizeConditions, ui->conditionsList); +} + +void MacroEdit::UpMacroSegmentHotkey() +{ + if (!MacroTabIsInFocus()) { + return; + } + + auto macro = _currentMacro; + if (!macro) { + return; + } + int actionSize = macro->Actions().size(); + int conditionSize = macro->Conditions().size(); + + if (currentActionIdx == -1 && currentConditionIdx == -1) { + if (lastInteracted == MacroSection::CONDITIONS) { + if (conditionSize == 0) { + MacroActionSelectionChanged(0); + } else { + MacroConditionSelectionChanged(0); + } + } else { + if (actionSize == 0) { + MacroConditionSelectionChanged(0); + } else { + MacroActionSelectionChanged(0); + } + } + return; + } + + if (currentActionIdx > 0) { + MacroActionSelectionChanged(currentActionIdx - 1); + return; + } + if (currentConditionIdx > 0) { + MacroConditionSelectionChanged(currentConditionIdx - 1); + return; + } + if (currentActionIdx == 0) { + if (conditionSize == 0) { + MacroActionSelectionChanged(actionSize - 1); + } else { + MacroConditionSelectionChanged(conditionSize - 1); + } + return; + } + if (currentConditionIdx == 0) { + if (actionSize == 0) { + MacroConditionSelectionChanged(conditionSize - 1); + } else { + MacroActionSelectionChanged(actionSize - 1); + } + return; + } +} + +void MacroEdit::DownMacroSegmentHotkey() +{ + if (!MacroTabIsInFocus()) { + return; + } + + auto macro = _currentMacro; + if (!macro) { + return; + } + int actionSize = macro->Actions().size(); + int conditionSize = macro->Conditions().size(); + + if (currentActionIdx == -1 && currentConditionIdx == -1) { + if (lastInteracted == MacroSection::CONDITIONS) { + if (conditionSize == 0) { + MacroActionSelectionChanged(0); + } else { + MacroConditionSelectionChanged(0); + } + } else { + if (actionSize == 0) { + MacroConditionSelectionChanged(0); + } else { + MacroActionSelectionChanged(0); + } + } + return; + } + + if (currentActionIdx < actionSize - 1) { + MacroActionSelectionChanged(currentActionIdx + 1); + return; + } + if (currentConditionIdx < conditionSize - 1) { + MacroConditionSelectionChanged(currentConditionIdx + 1); + return; + } + if (currentActionIdx == actionSize - 1) { + if (conditionSize == 0) { + MacroActionSelectionChanged(0); + } else { + MacroConditionSelectionChanged(0); + } + return; + } + if (currentConditionIdx == conditionSize - 1) { + if (actionSize == 0) { + MacroConditionSelectionChanged(0); + } else { + MacroActionSelectionChanged(0); + } + return; + } +} + +void MacroEdit::DeleteMacroSegmentHotkey() +{ + if (!MacroTabIsInFocus()) { + return; + } + + if (currentActionIdx != -1) { + RemoveMacroAction(currentActionIdx); + } else if (currentConditionIdx != -1) { + RemoveMacroCondition(currentConditionIdx); + } +} + +void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id, + obs_data_t *data) +{ + if (idx < 0 || idx > (int)macro->Actions().size()) { + assert(false); + return; + } + + { + auto lock = LockContext(); + macro->Actions().emplace(macro->Actions().begin() + idx, + MacroActionFactory::Create(id, macro)); + if (data) { + macro->Actions().at(idx)->Load(data); + } + macro->Actions().at(idx)->PostLoad(); + RunPostLoadSteps(); + macro->UpdateActionIndices(); + ui->actionsList->Insert( + idx, + new MacroActionEdit(this, ¯o->Actions()[idx], id)); + SetActionData(*macro); + } + HighlightAction(idx); + ui->actionsList->SetHelpMsgVisible(false); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::AddMacroAction(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx > (int)macro->Actions().size()) { + assert(false); + return; + } + + std::string id; + if (idx - 1 >= 0) { + id = macro->Actions().at(idx - 1)->GetId(); + } else { + id = MacroAction::GetDefaultID(); + } + + OBSDataAutoRelease data; + if (idx - 1 >= 0) { + data = obs_data_create(); + macro->Actions().at(idx - 1)->Save(data); + } + AddMacroAction(macro.get(), idx, id, data); +} + +void MacroEdit::on_actionAdd_clicked() +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (currentActionIdx == -1) { + AddMacroAction((int)macro->Actions().size()); + } else { + AddMacroAction(currentActionIdx + 1); + } + if (currentActionIdx != -1) { + MacroActionSelectionChanged(currentActionIdx + 1); + } +} + +void MacroEdit::RemoveMacroAction(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->Actions().size()) { + return; + } + + { + auto lock = LockContext(); + ui->actionsList->Remove(idx); + macro->Actions().erase(macro->Actions().begin() + idx); + SetMacroAbortWait(true); + GetMacroWaitCV().notify_all(); + macro->UpdateActionIndices(); + SetActionData(*macro); + } + MacroActionSelectionChanged(-1); + lastInteracted = MacroSection::ACTIONS; + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::on_actionRemove_clicked() +{ + if (currentActionIdx == -1) { + auto macro = _currentMacro; + if (!macro) { + return; + } + RemoveMacroAction((int)macro->Actions().size() - 1); + } else { + RemoveMacroAction(currentActionIdx); + } + MacroActionSelectionChanged(-1); +} + +void MacroEdit::on_actionTop_clicked() +{ + if (currentActionIdx == -1) { + return; + } + MacroActionReorder(0, currentActionIdx); + MacroActionSelectionChanged(0); +} + +void MacroEdit::on_actionUp_clicked() +{ + if (currentActionIdx == -1 || currentActionIdx == 0) { + return; + } + MoveMacroActionUp(currentActionIdx); + MacroActionSelectionChanged(currentActionIdx - 1); +} + +void MacroEdit::on_actionDown_clicked() +{ + if (currentActionIdx == -1 || + currentActionIdx == ui->actionsList->ContentLayout()->count() - 1) { + return; + } + MoveMacroActionDown(currentActionIdx); + MacroActionSelectionChanged(currentActionIdx + 1); +} + +void MacroEdit::on_actionBottom_clicked() +{ + if (currentActionIdx == -1) { + return; + } + const int newIdx = ui->actionsList->ContentLayout()->count() - 1; + MacroActionReorder(newIdx, currentActionIdx); + MacroActionSelectionChanged(newIdx); +} + +void MacroEdit::on_elseActionAdd_clicked() +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (currentElseActionIdx == -1) { + AddMacroElseAction((int)macro->ElseActions().size()); + } else { + AddMacroElseAction(currentElseActionIdx + 1); + } + if (currentElseActionIdx != -1) { + MacroElseActionSelectionChanged(currentElseActionIdx + 1); + } +} + +void MacroEdit::on_elseActionRemove_clicked() +{ + if (currentElseActionIdx == -1) { + auto macro = _currentMacro; + if (!macro) { + return; + } + RemoveMacroElseAction((int)macro->ElseActions().size() - 1); + } else { + RemoveMacroElseAction(currentElseActionIdx); + } + MacroElseActionSelectionChanged(-1); +} + +void MacroEdit::on_elseActionTop_clicked() +{ + if (currentElseActionIdx == -1) { + return; + } + MacroElseActionReorder(0, currentElseActionIdx); + MacroElseActionSelectionChanged(0); +} + +void MacroEdit::on_elseActionUp_clicked() +{ + if (currentElseActionIdx == -1 || currentElseActionIdx == 0) { + return; + } + MoveMacroElseActionUp(currentElseActionIdx); + MacroElseActionSelectionChanged(currentElseActionIdx - 1); +} + +void MacroEdit::on_elseActionDown_clicked() +{ + if (currentElseActionIdx == -1 || + currentElseActionIdx == + ui->elseActionsList->ContentLayout()->count() - 1) { + return; + } + MoveMacroElseActionDown(currentElseActionIdx); + MacroElseActionSelectionChanged(currentElseActionIdx + 1); +} + +void MacroEdit::on_elseActionBottom_clicked() +{ + if (currentElseActionIdx == -1) { + return; + } + const int newIdx = ui->elseActionsList->ContentLayout()->count() - 1; + MacroElseActionReorder(newIdx, currentElseActionIdx); + MacroElseActionSelectionChanged(newIdx); +} + +void MacroEdit::SwapActions(Macro *m, int pos1, int pos2) +{ + if (pos1 == pos2) { + return; + } + if (pos1 > pos2) { + std::swap(pos1, pos2); + } + + auto lock = LockContext(); + iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2); + m->UpdateActionIndices(); + auto widget1 = static_cast( + ui->actionsList->ContentLayout()->takeAt(pos1)->widget()); + auto widget2 = static_cast( + ui->actionsList->ContentLayout()->takeAt(pos2 - 1)->widget()); + ui->actionsList->Insert(pos1, widget2); + ui->actionsList->Insert(pos2, widget1); + SetActionData(*m); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::MoveMacroActionUp(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 1 || idx >= (int)macro->Actions().size()) { + return; + } + + SwapActions(macro.get(), idx, idx - 1); + HighlightAction(idx - 1); +} + +void MacroEdit::MoveMacroActionDown(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->Actions().size() - 1) { + return; + } + + SwapActions(macro.get(), idx, idx + 1); + HighlightAction(idx + 1); +} + +void MacroEdit::MacroElseActionSelectionChanged(int idx) +{ + SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx); +} + +void MacroEdit::MacroElseActionReorder(int to, int from) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (to == from || from < 0 || from > (int)macro->ElseActions().size() || + to < 0 || to > (int)macro->ElseActions().size()) { + return; + } + { + auto lock = LockContext(); + auto action = macro->ElseActions().at(from); + macro->ElseActions().erase(macro->ElseActions().begin() + from); + macro->ElseActions().insert(macro->ElseActions().begin() + to, + action); + macro->UpdateElseActionIndices(); + ui->elseActionsList->ContentLayout()->insertItem( + to, ui->elseActionsList->ContentLayout()->takeAt(from)); + SetElseActionData(*macro); + } + HighlightElseAction(to); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id, + obs_data_t *data) +{ + if (idx < 0 || idx > (int)macro->ElseActions().size()) { + assert(false); + return; + } + + { + auto lock = LockContext(); + macro->ElseActions().emplace(macro->ElseActions().begin() + idx, + MacroActionFactory::Create(id, + macro)); + if (data) { + macro->ElseActions().at(idx)->Load(data); + } + macro->ElseActions().at(idx)->PostLoad(); + RunPostLoadSteps(); + macro->UpdateElseActionIndices(); + ui->elseActionsList->Insert( + idx, new MacroActionEdit( + this, ¯o->ElseActions()[idx], id)); + SetElseActionData(*macro); + } + HighlightElseAction(idx); + ui->elseActionsList->SetHelpMsgVisible(false); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::AddMacroElseAction(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx > (int)macro->ElseActions().size()) { + assert(false); + return; + } + + std::string id; + if (idx - 1 >= 0) { + id = macro->ElseActions().at(idx - 1)->GetId(); + } else { + id = MacroAction::GetDefaultID(); + } + + OBSDataAutoRelease data; + if (idx - 1 >= 0) { + data = obs_data_create(); + macro->ElseActions().at(idx - 1)->Save(data); + } + AddMacroElseAction(macro.get(), idx, id, data); +} + +void MacroEdit::RemoveMacroElseAction(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->ElseActions().size()) { + return; + } + + { + auto lock = LockContext(); + ui->elseActionsList->Remove(idx); + macro->ElseActions().erase(macro->ElseActions().begin() + idx); + SetMacroAbortWait(true); + GetMacroWaitCV().notify_all(); + macro->UpdateElseActionIndices(); + SetElseActionData(*macro); + } + MacroElseActionSelectionChanged(-1); + lastInteracted = MacroSection::ELSE_ACTIONS; + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::SwapElseActions(Macro *m, int pos1, int pos2) +{ + if (pos1 == pos2) { + return; + } + if (pos1 > pos2) { + std::swap(pos1, pos2); + } + + auto lock = LockContext(); + iter_swap(m->ElseActions().begin() + pos1, + m->ElseActions().begin() + pos2); + m->UpdateElseActionIndices(); + auto widget1 = static_cast( + ui->elseActionsList->ContentLayout()->takeAt(pos1)->widget()); + auto widget2 = static_cast( + ui->elseActionsList->ContentLayout() + ->takeAt(pos2 - 1) + ->widget()); + ui->elseActionsList->Insert(pos1, widget2); + ui->elseActionsList->Insert(pos2, widget1); + SetElseActionData(*m); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::MoveMacroElseActionUp(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 1 || idx >= (int)macro->ElseActions().size()) { + return; + } + + SwapElseActions(macro.get(), idx, idx - 1); + HighlightElseAction(idx - 1); +} + +void MacroEdit::MoveMacroElseActionDown(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->ElseActions().size() - 1) { + return; + } + + SwapElseActions(macro.get(), idx, idx + 1); + HighlightElseAction(idx + 1); +} + +void MacroEdit::MacroActionSelectionChanged(int idx) +{ + SetupMacroSegmentSelection(MacroSection::ACTIONS, idx); +} + +void MacroEdit::MacroActionReorder(int to, int from) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (to == from || from < 0 || from > (int)macro->Actions().size() || + to < 0 || to > (int)macro->Actions().size()) { + return; + } + { + auto lock = LockContext(); + auto action = macro->Actions().at(from); + macro->Actions().erase(macro->Actions().begin() + from); + macro->Actions().insert(macro->Actions().begin() + to, action); + macro->UpdateActionIndices(); + ui->actionsList->ContentLayout()->insertItem( + to, ui->actionsList->ContentLayout()->takeAt(from)); + SetActionData(*macro); + } + HighlightAction(to); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::AddMacroCondition(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx > (int)macro->Conditions().size()) { + assert(false); + return; + } + + std::string id; + Logic::Type logic; + if (idx >= 1) { + id = macro->Conditions().at(idx - 1)->GetId(); + if (idx == 1) { + logic = Logic::Type::OR; + } else { + logic = macro->Conditions().at(idx - 1)->GetLogicType(); + } + } else { + id = MacroCondition::GetDefaultID(); + logic = Logic::Type::ROOT_NONE; + } + + OBSDataAutoRelease data; + if (idx - 1 >= 0) { + data = obs_data_create(); + macro->Conditions().at(idx - 1)->Save(data); + } + AddMacroCondition(macro.get(), idx, id, data.Get(), logic); +} + +void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id, + obs_data_t *data, Logic::Type logic) +{ + if (idx < 0 || idx > (int)macro->Conditions().size()) { + assert(false); + return; + } + + { + auto lock = LockContext(); + auto cond = macro->Conditions().emplace( + macro->Conditions().begin() + idx, + MacroConditionFactory::Create(id, macro)); + if (data) { + macro->Conditions().at(idx)->Load(data); + } + macro->Conditions().at(idx)->PostLoad(); + RunPostLoadSteps(); + (*cond)->SetLogicType(logic); + macro->UpdateConditionIndices(); + ui->conditionsList->Insert( + idx, + new MacroConditionEdit(this, ¯o->Conditions()[idx], + id, idx == 0)); + SetConditionData(*macro); + } + HighlightCondition(idx); + ui->conditionsList->SetHelpMsgVisible(false); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::on_conditionAdd_clicked() +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (currentConditionIdx == -1) { + AddMacroCondition((int)macro->Conditions().size()); + } else { + AddMacroCondition(currentConditionIdx + 1); + } + if (currentConditionIdx != -1) { + MacroConditionSelectionChanged(currentConditionIdx + 1); + } +} + +void MacroEdit::RemoveMacroCondition(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->Conditions().size()) { + return; + } + + { + auto lock = LockContext(); + ui->conditionsList->Remove(idx); + macro->Conditions().erase(macro->Conditions().begin() + idx); + macro->UpdateConditionIndices(); + if (idx == 0 && macro->Conditions().size() > 0) { + auto newRoot = macro->Conditions().at(0); + newRoot->SetLogicType(Logic::Type::ROOT_NONE); + static_cast( + ui->conditionsList->WidgetAt(0)) + ->SetRootNode(true); + } + SetConditionData(*macro); + } + MacroConditionSelectionChanged(-1); + lastInteracted = MacroSection::CONDITIONS; + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::on_conditionRemove_clicked() +{ + if (currentConditionIdx == -1) { + auto macro = _currentMacro; + if (!macro) { + return; + } + RemoveMacroCondition((int)macro->Conditions().size() - 1); + } else { + RemoveMacroCondition(currentConditionIdx); + } + MacroConditionSelectionChanged(-1); +} + +void MacroEdit::on_conditionTop_clicked() +{ + if (currentConditionIdx == -1) { + return; + } + MacroConditionReorder(0, currentConditionIdx); + MacroConditionSelectionChanged(0); +} + +void MacroEdit::on_conditionUp_clicked() +{ + if (currentConditionIdx == -1 || currentConditionIdx == 0) { + return; + } + MoveMacroConditionUp(currentConditionIdx); + MacroConditionSelectionChanged(currentConditionIdx - 1); +} + +void MacroEdit::on_conditionDown_clicked() +{ + if (currentConditionIdx == -1 || + currentConditionIdx == + ui->conditionsList->ContentLayout()->count() - 1) { + return; + } + MoveMacroConditionDown(currentConditionIdx); + MacroConditionSelectionChanged(currentConditionIdx + 1); +} + +void MacroEdit::on_conditionBottom_clicked() +{ + if (currentConditionIdx == -1) { + return; + } + const int newIdx = ui->conditionsList->ContentLayout()->count() - 1; + MacroConditionReorder(newIdx, currentConditionIdx); + MacroConditionSelectionChanged(newIdx); +} + +void MacroEdit::SwapConditions(Macro *m, int pos1, int pos2) +{ + if (pos1 == pos2) { + return; + } + if (pos1 > pos2) { + std::swap(pos1, pos2); + } + + bool root = pos1 == 0; + auto lock = LockContext(); + iter_swap(m->Conditions().begin() + pos1, + m->Conditions().begin() + pos2); + m->UpdateConditionIndices(); + + auto c1 = m->Conditions().begin() + pos1; + auto c2 = m->Conditions().begin() + pos2; + if (root) { + auto logic1 = (*c1)->GetLogicType(); + auto logic2 = (*c2)->GetLogicType(); + (*c1)->SetLogicType(logic2); + (*c2)->SetLogicType(logic1); + } + + auto widget1 = static_cast( + ui->conditionsList->ContentLayout()->takeAt(pos1)->widget()); + auto widget2 = static_cast( + ui->conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget()); + ui->conditionsList->Insert(pos1, widget2); + ui->conditionsList->Insert(pos2, widget1); + SetConditionData(*m); + widget2->SetRootNode(root); + widget1->SetRootNode(false); + emit(MacroSegmentOrderChanged()); +} + +void MacroEdit::MoveMacroConditionUp(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 1 || idx >= (int)macro->Conditions().size()) { + return; + } + + SwapConditions(macro.get(), idx, idx - 1); + HighlightCondition(idx - 1); +} + +void MacroEdit::MoveMacroConditionDown(int idx) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (idx < 0 || idx >= (int)macro->Conditions().size() - 1) { + return; + } + + SwapConditions(macro.get(), idx, idx + 1); + HighlightCondition(idx + 1); +} + +void MacroEdit::MacroConditionSelectionChanged(int idx) +{ + SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx); +} + +void MacroEdit::MacroConditionReorder(int to, int from) +{ + auto macro = _currentMacro; + if (!macro) { + return; + } + + if (to == from || from < 0 || from > (int)macro->Conditions().size() || + to < 0 || to > (int)macro->Conditions().size()) { + return; + } + { + auto lock = LockContext(); + auto condition = macro->Conditions().at(from); + if (to == 0) { + condition->SetLogicType(Logic::Type::ROOT_NONE); + static_cast( + ui->conditionsList->WidgetAt(from)) + ->SetRootNode(true); + macro->Conditions().at(0)->SetLogicType( + Logic::Type::AND); + static_cast( + ui->conditionsList->WidgetAt(0)) + ->SetRootNode(false); + } + if (from == 0) { + condition->SetLogicType(Logic::Type::AND); + static_cast( + ui->conditionsList->WidgetAt(from)) + ->SetRootNode(false); + macro->Conditions().at(1)->SetLogicType( + Logic::Type::ROOT_NONE); + static_cast( + ui->conditionsList->WidgetAt(1)) + ->SetRootNode(true); + } + macro->Conditions().erase(macro->Conditions().begin() + from); + macro->Conditions().insert(macro->Conditions().begin() + to, + condition); + macro->UpdateConditionIndices(); + ui->conditionsList->ContentLayout()->insertItem( + to, ui->conditionsList->ContentLayout()->takeAt(from)); + SetConditionData(*macro); + } + HighlightCondition(to); + emit(MacroSegmentOrderChanged()); +} + +bool MacroEdit::IsEmpty() const +{ + if (!_currentMacro) { + return true; + } + + return ui->conditionsList->IsEmpty() && ui->actionsList->IsEmpty() && + ui->elseActionsList->IsEmpty(); +} + +void MacroEdit::ShowAllMacroSections() +{ + if (!ElseSectionIsVisible()) { + on_toggleElseActions_clicked(); + } + + SetSplitterPositionByFraction(ui->macroActionConditionSplitter, 0.3); + CenterSplitterPosition(ui->macroElseActionSplitter); + + adjustSize(); + updateGeometry(); +} + +} // namespace advss diff --git a/lib/macro/macro-edit.hpp b/lib/macro/macro-edit.hpp new file mode 100644 index 00000000..acba6b0f --- /dev/null +++ b/lib/macro/macro-edit.hpp @@ -0,0 +1,144 @@ +#pragma once +#include "condition-logic.hpp" + +#include + +namespace advss { + +class Macro; +class MacroSegment; + +/******************************************************************************* + * Advanced Scene Switcher window + *******************************************************************************/ +class MacroEdit : public QWidget { + Q_OBJECT + +public: + MacroEdit(QWidget *parent, QStringList helpMsg = {}); + void SetMacro(const std::shared_ptr &); + void ClearSegmentWidgetCacheFor(Macro *) const; + void SetControlsDisabled(bool disable) const; + void HighlightAction(int idx, QColor color = QColor(Qt::green)) const; + void HighlightElseAction(int idx, + QColor color = QColor(Qt::green)) const; + void HighlightCondition(int idx, + QColor color = QColor(Qt::green)) const; + void ResetConditionHighlights(); + void ResetActionHighlights(); + void SetActionData(Macro &m) const; + void SetElseActionData(Macro &m) const; + void SetConditionData(Macro &m) const; + void SwapActions(Macro *m, int pos1, int pos2); + void SwapElseActions(Macro *m, int pos1, int pos2); + void SwapConditions(Macro *m, int pos1, int pos2); + void CopyMacroSegment(); + void PasteMacroSegment(); + bool IsEmpty() const; + void ShowAllMacroSections(); + +private slots: + void on_conditionAdd_clicked(); + void on_conditionRemove_clicked(); + void on_conditionTop_clicked(); + void on_conditionUp_clicked(); + void on_conditionDown_clicked(); + void on_conditionBottom_clicked(); + void on_actionAdd_clicked(); + void on_actionRemove_clicked(); + void on_actionTop_clicked(); + void on_actionUp_clicked(); + void on_actionDown_clicked(); + void on_actionBottom_clicked(); + void on_toggleElseActions_clicked() const; + void on_elseActionAdd_clicked(); + void on_elseActionRemove_clicked(); + void on_elseActionTop_clicked(); + void on_elseActionUp_clicked(); + void on_elseActionDown_clicked(); + void on_elseActionBottom_clicked(); + void UpMacroSegmentHotkey(); + void DownMacroSegmentHotkey(); + void DeleteMacroSegmentHotkey(); + void ShowMacroActionsContextMenu(const QPoint &); + void ShowMacroElseActionsContextMenu(const QPoint &); + void ShowMacroConditionsContextMenu(const QPoint &); + void ExpandAllActions() const; + void ExpandAllElseActions() const; + void ExpandAllConditions() const; + void CollapseAllActions() const; + void CollapseAllElseActions() const; + void CollapseAllConditions() const; + void MinimizeActions() const; + void MaximizeActions() const; + void MinimizeElseActions() const; + void MaximizeElseActions() const; + void MinimizeConditions() const; + void MaximizeConditions() const; + void SetElseActionsStateToHidden() const; + void SetElseActionsStateToVisible() const; + void MacroActionSelectionChanged(int idx); + void MacroActionReorder(int to, int target); + void AddMacroAction(Macro *macro, int idx, const std::string &id, + obs_data_t *data); + void AddMacroAction(int idx); + void RemoveMacroAction(int idx); + void MoveMacroActionUp(int idx); + void MoveMacroActionDown(int idx); + void MacroElseActionSelectionChanged(int idx); + void MacroElseActionReorder(int to, int target); + void AddMacroElseAction(Macro *macro, int idx, const std::string &id, + obs_data_t *data); + void AddMacroElseAction(int idx); + void RemoveMacroElseAction(int idx); + void MoveMacroElseActionUp(int idx); + void MoveMacroElseActionDown(int idx); + void MacroConditionSelectionChanged(int idx); + void MacroConditionReorder(int to, int target); + void AddMacroCondition(int idx); + void AddMacroCondition(Macro *macro, int idx, const std::string &id, + obs_data_t *data, Logic::Type logic); + void RemoveMacroCondition(int idx); + void MoveMacroConditionUp(int idx); + void MoveMacroConditionDown(int idx); + void HighlightControls() const; + +signals: + void MacroAdded(const QString &name); + void MacroRemoved(const QString &name); + void MacroRenamed(const QString &oldName, const QString &newName); + void MacroSegmentOrderChanged(); + void SegmentTempVarsChanged(MacroSegment *); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS }; + + void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0); + void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0); + void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0); + void SetupMacroSegmentSelection(MacroSection type, int idx); + void + SetupContextMenu(const QPoint &pos, + const std::function &remove, + const std::function &expand, + const std::function &collapse, + const std::function &maximize, + const std::function &minimize, + MacroSegmentList *list); + void RunSegmentHighlightChecks(); + bool ElseSectionIsVisible() const; + + MacroSection lastInteracted = MacroSection::CONDITIONS; + int currentConditionIdx = -1; + int currentActionIdx = -1; + int currentElseActionIdx = -1; + + std::shared_ptr _currentMacro; + + std::unique_ptr ui; +}; + +} // namespace advss diff --git a/lib/macro/macro-segment-copy-paste.cpp b/lib/macro/macro-segment-copy-paste.cpp index b86ead13..e3eea81e 100644 --- a/lib/macro/macro-segment-copy-paste.cpp +++ b/lib/macro/macro-segment-copy-paste.cpp @@ -13,7 +13,7 @@ struct MacroSegmentCopyInfo { }; static MacroSegmentCopyInfo copyInfo; -void AdvSceneSwitcher::CopyMacroSegment() +void MacroEdit::CopyMacroSegment() { copyInfo.segment.reset(); copyInfo.type = MacroSegmentCopyInfo::Type::NONE; @@ -23,7 +23,7 @@ void AdvSceneSwitcher::CopyMacroSegment() return; } - auto macro = GetSelectedMacro(); + auto macro = _currentMacro; if (!macro) { return; } @@ -43,13 +43,13 @@ void AdvSceneSwitcher::CopyMacroSegment() } } -void AdvSceneSwitcher::PasteMacroSegment() +void MacroEdit::PasteMacroSegment() { if (copyInfo.type == MacroSegmentCopyInfo::Type::NONE) { return; } - auto macro = GetSelectedMacro(); + auto macro = _currentMacro; if (!macro || !copyInfo.segment) { return; } @@ -113,14 +113,14 @@ void SetCopySegmentTargetActionType(bool setToElseAction) } } -void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window) +void SetupSegmentCopyPasteShortcutHandlers(MacroEdit *edit) { - auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), window); - QWidget::connect(copyShortcut, &QShortcut::activated, window, - &AdvSceneSwitcher::CopyMacroSegment); - auto pasteShortcut = new QShortcut(QKeySequence("Ctrl+V"), window); - QWidget::connect(pasteShortcut, &QShortcut::activated, window, - &AdvSceneSwitcher::PasteMacroSegment); + auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), edit); + QWidget::connect(copyShortcut, &QShortcut::activated, edit, + &MacroEdit::CopyMacroSegment); + auto pasteShortcut = new QShortcut(QKeySequence("Ctrl+V"), edit); + QWidget::connect(pasteShortcut, &QShortcut::activated, edit, + &MacroEdit::PasteMacroSegment); } } // namespace advss diff --git a/lib/macro/macro-segment-copy-paste.hpp b/lib/macro/macro-segment-copy-paste.hpp index 4af31d4a..503c0c1c 100644 --- a/lib/macro/macro-segment-copy-paste.hpp +++ b/lib/macro/macro-segment-copy-paste.hpp @@ -1,10 +1,10 @@ namespace advss { -class AdvSceneSwitcher; +class MacroEdit; bool MacroSegmentIsInClipboard(); bool MacroActionIsInClipboard(); void SetCopySegmentTargetActionType(bool setToElseAction); -void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window); +void SetupSegmentCopyPasteShortcutHandlers(MacroEdit *edit); } // namespace advss diff --git a/lib/macro/macro-tab.cpp b/lib/macro/macro-tab.cpp index 38ddde84..5e0b6024 100644 --- a/lib/macro/macro-tab.cpp +++ b/lib/macro/macro-tab.cpp @@ -1,16 +1,15 @@ #include "advanced-scene-switcher.hpp" #include "action-queue.hpp" -#include "cursor-shape-changer.hpp" #include "macro-action-edit.hpp" #include "macro-condition-edit.hpp" #include "macro-export-import-dialog.hpp" #include "macro-settings.hpp" -#include "macro-segment-copy-paste.hpp" #include "macro-tree.hpp" #include "macro.hpp" #include "math-helpers.hpp" #include "name-dialog.hpp" #include "path-helpers.hpp" +#include "splitter-helpers.hpp" #include "switcher-data.hpp" #include "ui-helpers.hpp" #include "utility.hpp" @@ -18,9 +17,7 @@ #include #include -#include #include -#include namespace advss { @@ -193,20 +190,14 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr ¯o) } } - const auto clearWidgetCache = [this](Macro *macro) { - ui->conditionsList->ClearWidgetsFromCacheFor(macro); - ui->actionsList->ClearWidgetsFromCacheFor(macro); - ui->elseActionsList->ClearWidgetsFromCacheFor(macro); - }; - if (macro->IsGroup()) { std::vector> macros = {macro}; addGroupSubitems(macros, macro); for (auto macro : macros) { - clearWidgetCache(macro.get()); + ui->macroEdit->ClearSegmentWidgetCacheFor(macro.get()); } } else { - clearWidgetCache(macro.get()); + ui->macroEdit->ClearSegmentWidgetCacheFor(macro.get()); } // Don't cache widgets for about to be deleted macros @@ -339,77 +330,6 @@ void AdvSceneSwitcher::ExportMacros() const MacroExportImportDialog::ExportMacros(exportString); } -static bool -isValidMacroSegmentIdx(const std::deque> &list, - int idx) -{ - return (idx > 0 || (unsigned)idx < list.size()); -} - -void AdvSceneSwitcher::SetupMacroSegmentSelection(MacroSection type, int idx) -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - - MacroSegmentList *setList = nullptr, *resetList1 = nullptr, - *resetList2 = nullptr; - int *setIdx = nullptr, *resetIdx1 = nullptr, *resetIdx2 = nullptr; - std::deque> segements; - - switch (type) { - case AdvSceneSwitcher::MacroSection::CONDITIONS: - setList = ui->conditionsList; - setIdx = ¤tConditionIdx; - segements = {macro->Conditions().begin(), - macro->Conditions().end()}; - - resetList1 = ui->actionsList; - resetList2 = ui->elseActionsList; - resetIdx1 = ¤tActionIdx; - resetIdx2 = ¤tElseActionIdx; - break; - case AdvSceneSwitcher::MacroSection::ACTIONS: - setList = ui->actionsList; - setIdx = ¤tActionIdx; - segements = {macro->Actions().begin(), macro->Actions().end()}; - - resetList1 = ui->conditionsList; - resetList2 = ui->elseActionsList; - resetIdx1 = ¤tConditionIdx; - resetIdx2 = ¤tElseActionIdx; - break; - case AdvSceneSwitcher::MacroSection::ELSE_ACTIONS: - setList = ui->elseActionsList; - setIdx = ¤tElseActionIdx; - segements = {macro->ElseActions().begin(), - macro->ElseActions().end()}; - - resetList1 = ui->actionsList; - resetList2 = ui->conditionsList; - resetIdx1 = ¤tActionIdx; - resetIdx2 = ¤tConditionIdx; - break; - default: - break; - } - - setList->SetSelection(idx); - resetList1->SetSelection(-1); - resetList2->SetSelection(-1); - if (isValidMacroSegmentIdx(segements, idx)) { - *setIdx = idx; - } else { - *setIdx = -1; - } - *resetIdx1 = -1; - *resetIdx2 = -1; - - lastInteracted = type; - HighlightControls(); -} - bool AdvSceneSwitcher::ResolveMacroImportNameConflict( std::shared_ptr ¯o) { @@ -564,223 +484,13 @@ void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) const macro->SetMatchOnChange(value); } -void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx) -{ - auto &actions = m.Actions(); - ui->actionsList->SetHelpMsgVisible(actions.size() == 0); - - if (ui->actionsList->PopulateWidgetsFromCache(&m)) { - return; - } - - // The layout system has not completed geometry propagation yet, so we - // can skip those checks for now - ui->actionsList->SetVisibilityCheckEnable(false); - - for (; afterIdx < actions.size(); afterIdx++) { - auto newEntry = new MacroActionEdit(this, &actions[afterIdx], - actions[afterIdx]->GetId()); - ui->actionsList->Add(newEntry); - } - - // Give the layout system time before enabling the visibility checks and - // fully constructing the visible macro segments - QTimer::singleShot(0, this, [this]() { - ui->actionsList->SetVisibilityCheckEnable(true); - }); -} - -void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx) -{ - auto &actions = m.ElseActions(); - ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0); - - if (ui->elseActionsList->PopulateWidgetsFromCache(&m)) { - return; - } - - // The layout system has not completed geometry propagation yet, so we - // can skip those checks for now - ui->elseActionsList->SetVisibilityCheckEnable(false); - - for (; afterIdx < actions.size(); afterIdx++) { - auto newEntry = new MacroActionEdit(this, &actions[afterIdx], - actions[afterIdx]->GetId()); - ui->elseActionsList->Add(newEntry); - } - - // Give the layout system time before enabling the visibility checks and - // fully constructing the visible macro segments - QTimer::singleShot(0, this, [this]() { - ui->elseActionsList->SetVisibilityCheckEnable(true); - }); -} - -void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx) -{ - bool root = afterIdx == 0; - auto &conditions = m.Conditions(); - ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0); - - if (ui->conditionsList->PopulateWidgetsFromCache(&m)) { - return; - } - - // The layout system has not completed geometry propagation yet, so we - // can skip those checks for now - ui->conditionsList->SetVisibilityCheckEnable(false); - - for (; afterIdx < conditions.size(); afterIdx++) { - auto newEntry = new MacroConditionEdit( - this, &conditions[afterIdx], - conditions[afterIdx]->GetId(), root); - ui->conditionsList->Add(newEntry); - root = false; - } - - // Give the layout system time before enabling the visibility checks and - // fully constructing the visible macro segments - QTimer::singleShot(0, this, [this]() { - ui->conditionsList->SetVisibilityCheckEnable(true); - }); -} - -void AdvSceneSwitcher::SetActionData(Macro &m) const -{ - auto &actions = m.Actions(); - for (int idx = 0; idx < ui->actionsList->ContentLayout()->count(); - idx++) { - auto item = ui->actionsList->ContentLayout()->itemAt(idx); - if (!item) { - continue; - } - auto widget = static_cast(item->widget()); - if (!widget) { - continue; - } - widget->SetEntryData(&*(actions.begin() + idx)); - } -} - -void AdvSceneSwitcher::SetElseActionData(Macro &m) const -{ - auto &actions = m.ElseActions(); - for (int idx = 0; idx < ui->elseActionsList->ContentLayout()->count(); - idx++) { - auto item = ui->elseActionsList->ContentLayout()->itemAt(idx); - if (!item) { - continue; - } - auto widget = static_cast(item->widget()); - if (!widget) { - continue; - } - widget->SetEntryData(&*(actions.begin() + idx)); - } -} - -void AdvSceneSwitcher::SetConditionData(Macro &m) const -{ - auto &conditions = m.Conditions(); - for (int idx = 0; idx < ui->conditionsList->ContentLayout()->count(); - idx++) { - auto item = ui->conditionsList->ContentLayout()->itemAt(idx); - if (!item) { - continue; - } - auto widget = static_cast(item->widget()); - if (!widget) { - continue; - } - widget->SetEntryData(&*(conditions.begin() + idx)); - } -} - -static void maximizeFirstSplitterEntry(QSplitter *splitter) -{ - QList newSizes; - newSizes << 999999; - for (int i = 0; i < splitter->sizes().size() - 1; i++) { - newSizes << 0; - } - splitter->setSizes(newSizes); -} - -static void centerSplitterPosition(QSplitter *splitter) -{ - splitter->setSizes(QList() << 999999 << 999999); -} - -void AdvSceneSwitcher::SetEditMacro(Macro &m) -{ - { - const QSignalBlocker b1(ui->macroName); - const QSignalBlocker b2(ui->runMacroInParallel); - const QSignalBlocker b3(ui->runMacroOnChange); - ui->macroName->setText(m.Name().c_str()); - ui->runMacroInParallel->setChecked(m.RunInParallel()); - ui->runMacroOnChange->setChecked(m.MatchOnChange()); - } - ui->conditionsList->Clear(); - ui->actionsList->Clear(); - ui->elseActionsList->Clear(); - - m.ResetUIHelpers(); - - PopulateMacroConditions(m); - PopulateMacroActions(m); - PopulateMacroElseActions(m); - SetMacroEditAreaDisabled(false); - - currentActionIdx = -1; - currentElseActionIdx = -1; - currentConditionIdx = -1; - HighlightControls(); - - if (m.IsGroup()) { - SetMacroEditAreaDisabled(true); - ui->macroName->setEnabled(true); - centerSplitterPosition(ui->macroActionConditionSplitter); - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - return; - } - - if (!m.HasValidSplitterPositions()) { - centerSplitterPosition(ui->macroActionConditionSplitter); - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - return; - } - - ui->macroActionConditionSplitter->setSizes( - m.GetActionConditionSplitterPosition()); - ui->macroElseActionSplitter->setSizes( - m.GetElseActionSplitterPosition()); -} - void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const { ui->macroName->setDisabled(disable); ui->runMacro->setDisabled(disable); ui->runMacroInParallel->setDisabled(disable); ui->runMacroOnChange->setDisabled(disable); - ui->macroActions->setDisabled(disable); - ui->macroConditions->setDisabled(disable); - ui->macroActionConditionSplitter->setDisabled(disable); -} - -void AdvSceneSwitcher::HighlightAction(int idx, QColor color) const -{ - ui->actionsList->Highlight(idx, color); -} - -void AdvSceneSwitcher::HighlightElseAction(int idx, QColor color) const -{ - ui->elseActionsList->Highlight(idx, color); -} - -void AdvSceneSwitcher::HighlightCondition(int idx, QColor color) const -{ - ui->conditionsList->Highlight(idx, color); + ui->macroEdit->SetControlsDisabled(disable); } std::shared_ptr AdvSceneSwitcher::GetSelectedMacro() const @@ -793,40 +503,6 @@ std::vector> AdvSceneSwitcher::GetSelectedMacros() const return ui->macros->GetCurrentMacros(); } -void AdvSceneSwitcher::MacroSelectionAboutToChange() const -{ - if (loading) { - return; - } - - if (!ui->macroName->isEnabled()) { // No macro is selected - return; - } - - auto macro = GetMacroByQString(ui->macroName->text()); - if (!macro) { - return; - } - - macro->SetActionConditionSplitterPosition( - ui->macroActionConditionSplitter->sizes()); - - auto elsePos = ui->macroElseActionSplitter->sizes(); - // If only conditions are visible maximize the actions to avoid neither - // actions nor elseActions being visible when the condition <-> action - // splitter is moved - if (elsePos[0] == 0 && elsePos[1] == 0) { - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - return; - } - macro->SetElseActionSplitterPosition( - ui->macroElseActionSplitter->sizes()); - - ui->conditionsList->CacheCurrentWidgetsFor(macro); - ui->actionsList->CacheCurrentWidgetsFor(macro); - ui->elseActionsList->CacheCurrentWidgetsFor(macro); -} - void AdvSceneSwitcher::MacroSelectionChanged() { if (loading) { @@ -836,17 +512,28 @@ void AdvSceneSwitcher::MacroSelectionChanged() auto macro = GetSelectedMacro(); if (!macro) { SetMacroEditAreaDisabled(true); - ui->conditionsList->Clear(); - ui->actionsList->Clear(); - ui->elseActionsList->Clear(); - ui->conditionsList->SetHelpMsgVisible(true); - ui->actionsList->SetHelpMsgVisible(true); - ui->elseActionsList->SetHelpMsgVisible(true); - centerSplitterPosition(ui->macroActionConditionSplitter); - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); + ui->macroEdit->SetMacro(macro); + return; + } + + { + const QSignalBlocker b1(ui->macroName); + const QSignalBlocker b2(ui->runMacroInParallel); + const QSignalBlocker b3(ui->runMacroOnChange); + ui->macroName->setText(macro->Name().c_str()); + ui->runMacroInParallel->setChecked(macro->RunInParallel()); + ui->runMacroOnChange->setChecked(macro->MatchOnChange()); + } + + macro->ResetUIHelpers(); + ui->macroEdit->SetMacro(macro); + SetMacroEditAreaDisabled(false); + + if (macro->IsGroup()) { + SetMacroEditAreaDisabled(true); + ui->macroName->setEnabled(true); return; } - SetEditMacro(*macro); if (GetGlobalMacroSettings()._saveSettingsOnMacroChange) { obs_frontend_save(); @@ -871,16 +558,6 @@ void AdvSceneSwitcher::HighlightOnChange() const } } -static void resetSegmentHighlights(MacroSegmentList *list) -{ - MacroSegmentEdit *widget = nullptr; - for (int i = 0; (widget = list->WidgetAt(i)); i++) { - if (widget && widget->Data()) { - (void)widget->Data()->GetHighlightAndReset(); - } - } -} - void AdvSceneSwitcher::on_macroSettings_clicked() { GlobalMacroSettings prop = GetGlobalMacroSettings(); @@ -897,30 +574,15 @@ void AdvSceneSwitcher::on_macroSettings_clicked() // would have been highlighted at least once since the moment the // highlighting was disabled if (prop._highlightConditions) { - resetSegmentHighlights(ui->conditionsList); + ui->macroEdit->ResetConditionHighlights(); } if (prop._highlightActions) { - resetSegmentHighlights(ui->actionsList); - resetSegmentHighlights(ui->elseActionsList); + ui->macroEdit->ResetActionHighlights(); } SetCheckIntervalTooLowVisibility(); } -static void moveControlsToSplitter(QSplitter *splitter, int idx, - QLayoutItem *item) -{ - static int splitterHandleWidth = 32; - auto handle = splitter->handle(idx); - auto layout = item->layout(); - int leftMargin, rightMargin; - layout->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr); - layout->setContentsMargins(leftMargin, 0, rightMargin, 0); - handle->setLayout(layout); - splitter->setHandleWidth(splitterHandleWidth); - splitter->setStyleSheet("QSplitter::handle {background: transparent;}"); -} - static bool shouldRestoreSplitter(const QList &pos) { if (pos.size() == 0) { @@ -935,36 +597,6 @@ static bool shouldRestoreSplitter(const QList &pos) return true; } -static void runSegmentHighligtChecksHelper(MacroSegmentList *list) -{ - MacroSegmentEdit *widget = nullptr; - for (int i = 0; (widget = list->WidgetAt(i)); i++) { - if (widget->Data() && widget->Data()->GetHighlightAndReset()) { - list->Highlight(i); - } - } -} - -static void runSegmentHighligtChecks(AdvSceneSwitcher *ss) -{ - if (!ss || !HighlightUIElementsEnabled()) { - return; - } - auto macro = ss->GetSelectedMacro(); - if (!macro) { - return; - } - - const auto &settings = GetGlobalMacroSettings(); - if (settings._highlightConditions) { - runSegmentHighligtChecksHelper(ss->ui->conditionsList); - } - if (settings._highlightActions) { - runSegmentHighligtChecksHelper(ss->ui->actionsList); - runSegmentHighligtChecksHelper(ss->ui->elseActionsList); - } -} - static QToolBar * setupToolBar(const std::initializer_list> &widgetGroups) @@ -993,7 +625,6 @@ setupToolBar(const std::initializer_list> void AdvSceneSwitcher::SetupMacroTab() { - ui->macroElseActions->installEventFilter(this); ui->macros->installEventFilter(this); if (GetMacros().size() == 0 && !switcher->disableHints) { @@ -1006,45 +637,13 @@ void AdvSceneSwitcher::SetupMacroTab() ui->macros->Reset(GetMacros(), GetGlobalMacroSettings()._highlightExecuted); - connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this, - SLOT(MacroSelectionAboutToChange())); connect(ui->macros, SIGNAL(MacroSelectionChanged()), this, SLOT(MacroSelectionChanged())); ui->runMacro->SetMacroTree(ui->macros); - ui->conditionsList->SetHelpMsg( - obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp")); - connect(ui->conditionsList, &MacroSegmentList::SelectionChanged, this, - &AdvSceneSwitcher::MacroConditionSelectionChanged); - connect(ui->conditionsList, &MacroSegmentList::Reorder, this, - &AdvSceneSwitcher::MacroConditionReorder); - - ui->actionsList->SetHelpMsg( - obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp")); - connect(ui->actionsList, &MacroSegmentList::SelectionChanged, this, - &AdvSceneSwitcher::MacroActionSelectionChanged); - connect(ui->actionsList, &MacroSegmentList::Reorder, this, - &AdvSceneSwitcher::MacroActionReorder); - - ui->elseActionsList->SetHelpMsg(obs_module_text( - "AdvSceneSwitcher.macroTab.editElseActionHelp")); - connect(ui->elseActionsList, &MacroSegmentList::SelectionChanged, this, - &AdvSceneSwitcher::MacroElseActionSelectionChanged); - connect(ui->elseActionsList, &MacroSegmentList::Reorder, this, - &AdvSceneSwitcher::MacroElseActionReorder); - ui->macros->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->macros, &QWidget::customContextMenuRequested, this, &AdvSceneSwitcher::ShowMacroContextMenu); - ui->actionsList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->actionsList, &QWidget::customContextMenuRequested, this, - &AdvSceneSwitcher::ShowMacroActionsContextMenu); - ui->elseActionsList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->elseActionsList, &QWidget::customContextMenuRequested, this, - &AdvSceneSwitcher::ShowMacroElseActionsContextMenu); - ui->conditionsList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->conditionsList, &QWidget::customContextMenuRequested, this, - &AdvSceneSwitcher::ShowMacroConditionsContextMenu); SetMacroEditAreaDisabled(true); ui->macroPriorityWarning->setVisible( @@ -1055,66 +654,10 @@ void AdvSceneSwitcher::SetupMacroTab() SLOT(HighlightOnChange())); onChangeHighlightTimer.start(); - // Set action and condition toolbars - const std::string pathPrefix = - GetDataFilePath("res/images/" + GetThemeTypeName()); - SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str()); - SetButtonIcon(ui->actionBottom, - (pathPrefix + "DoubleDown.svg").c_str()); - SetButtonIcon(ui->elseActionTop, (pathPrefix + "DoubleUp.svg").c_str()); - SetButtonIcon(ui->elseActionBottom, - (pathPrefix + "DoubleDown.svg").c_str()); - SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str()); - SetButtonIcon(ui->conditionBottom, - (pathPrefix + "DoubleDown.svg").c_str()); - SetButtonIcon(ui->toggleElseActions, - (pathPrefix + "NotEqual.svg").c_str()); - - auto conditionToolbar = - setupToolBar({{ui->conditionAdd, ui->conditionRemove}, - {ui->conditionTop, ui->conditionUp, - ui->conditionDown, ui->conditionBottom}}); - auto actionToolbar = setupToolBar({{ui->actionAdd, ui->actionRemove}, - {ui->actionTop, ui->actionUp, - ui->actionDown, ui->actionBottom}}); - auto elseActionToolbar = - setupToolBar({{ui->elseActionAdd, ui->elseActionRemove}, - {ui->elseActionTop, ui->elseActionUp, - ui->elseActionDown, ui->elseActionBottom}}); - - ui->conditionControlsLayout->addWidget(conditionToolbar); - ui->actionControlsLayout->insertWidget(0, actionToolbar); - ui->elseActionControlsLayout->addWidget(elseActionToolbar); - - // Move condition controls into splitter handle layout - moveControlsToSplitter(ui->macroActionConditionSplitter, 1, - ui->macroConditionsLayout->takeAt(1)); - moveControlsToSplitter(ui->macroElseActionSplitter, 1, - ui->macroActionsLayout->takeAt(1)); - - // Override splitter cursor icon when hovering over controls in splitter - SetCursorOnWidgetHover(ui->conditionAdd, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->conditionRemove, - Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->conditionTop, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->conditionUp, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->conditionDown, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->conditionBottom, - Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionAdd, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionRemove, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionTop, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionUp, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionDown, Qt::CursorShape::ArrowCursor); - SetCursorOnWidgetHover(ui->actionBottom, Qt::CursorShape::ArrowCursor); - // Reserve more space for macro edit area than for the macro list ui->macroListMacroEditSplitter->setStretchFactor(0, 1); ui->macroListMacroEditSplitter->setStretchFactor(1, 4); - centerSplitterPosition(ui->macroActionConditionSplitter); - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - if (switcher->saveWindowGeo) { if (shouldRestoreSplitter( switcher->macroListMacroEditSplitterPosition)) { @@ -1122,14 +665,6 @@ void AdvSceneSwitcher::SetupMacroTab() switcher->macroListMacroEditSplitterPosition); } } - - SetupSegmentCopyPasteShortcutHandlers(this); - - // Macro segment highlight - auto timer = new QTimer(this); - connect(timer, &QTimer::timeout, - [this]() { runSegmentHighligtChecks(this); }); - timer->start(1500); } void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos) @@ -1192,183 +727,6 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos) menu.exec(globalPos); } -static bool handleCustomLabelRename(MacroSegmentEdit *segmentEdit) -{ - std::string label; - auto segment = segmentEdit->Data(); - if (!segment) { - return false; - } - - bool accepted = NameDialog::AskForName( - GetSettingsWindow(), - obs_module_text( - "AdvSceneSwitcher.macroTab.segment.setCustomLabel"), - "", label, QString::fromStdString(segment->GetCustomLabel())); - if (!accepted) { - return false; - } - - segment->SetCustomLabel(label); - segmentEdit->HeaderInfoChanged(""); - return true; -} - -static void handleCustomLabelEnableChange(MacroSegmentEdit *segmentEdit, - QAction *contextMenuOption) -{ - bool enable = contextMenuOption->isChecked(); - auto segment = segmentEdit->Data(); - segment->SetUseCustomLabel(enable); - if (!enable) { - segmentEdit->HeaderInfoChanged( - QString::fromStdString(segment->GetShortDesc())); - return; - } - - if (!handleCustomLabelRename(segmentEdit)) { - segment->SetUseCustomLabel(false); - } -} - -static void setupSegmentLabelContextMenuEntries(MacroSegmentEdit *segmentEdit, - QMenu &menu) -{ - if (!segmentEdit) { - return; - } - - auto segment = segmentEdit ? segmentEdit->Data() : nullptr; - const bool customLabelIsEnabled = segment && - segment->GetUseCustomLabel(); - - auto enableCustomLabel = menu.addAction(obs_module_text( - "AdvSceneSwitcher.macroTab.segment.useCustomLabel")); - enableCustomLabel->setCheckable(true); - enableCustomLabel->setChecked(customLabelIsEnabled); - QWidget::connect(enableCustomLabel, &QAction::triggered, - [segmentEdit, enableCustomLabel]() { - handleCustomLabelEnableChange( - segmentEdit, enableCustomLabel); - }); - - if (!customLabelIsEnabled) { - return; - } - - auto customLabelRename = menu.addAction(obs_module_text( - "AdvSceneSwitcher.macroTab.segment.customLabelRename")); - QWidget::connect(customLabelRename, &QAction::triggered, - [segmentEdit]() { - handleCustomLabelRename(segmentEdit); - }); -} - -static void setupCopyPasteContextMenuEnry(AdvSceneSwitcher *ss, - MacroSegmentEdit *segmentEdit, - QMenu &menu) -{ - auto copy = menu.addAction( - obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), ss, - [ss]() { ss->CopyMacroSegment(); }); - copy->setEnabled(!!segmentEdit); - - bool pasteAsElseAction = true; - const char *pasteText = "AdvSceneSwitcher.macroTab.segment.paste"; - if (MacroActionIsInClipboard()) { - if (IsCursorInWidgetArea(ss->ui->macroActions)) { - pasteAsElseAction = false; - pasteText = - "AdvSceneSwitcher.macroTab.segment.pasteAction"; - } else if (IsCursorInWidgetArea(ss->ui->macroElseActions)) { - pasteAsElseAction = true; - pasteText = - "AdvSceneSwitcher.macroTab.segment.pasteElseAction"; - } - } - auto paste = menu.addAction( - obs_module_text(pasteText), ss, [ss, pasteAsElseAction]() { - SetCopySegmentTargetActionType(pasteAsElseAction); - ss->PasteMacroSegment(); - }); - paste->setEnabled(MacroSegmentIsInClipboard()); -} - -static void setupRemoveContextMenuEnry( - AdvSceneSwitcher *ss, - const std::function &remove, - const QPoint &pos, MacroSegmentList *list, QMenu &menu) -{ - const auto segmentEditIndex = list->IndexAt(pos); - if (segmentEditIndex == -1) { - return; - } - - menu.addAction( - obs_module_text("AdvSceneSwitcher.macroTab.segment.remove"), ss, - [ss, remove, segmentEditIndex]() { - remove(ss, segmentEditIndex); - }); -} - -static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos, - std::function remove, - std::function expand, - std::function collapse, - std::function maximize, - std::function minimize, - MacroSegmentList *list) -{ - QMenu menu; - auto segmentEdit = list->WidgetAt(pos); - - setupCopyPasteContextMenuEnry(ss, segmentEdit, menu); - menu.addSeparator(); - setupRemoveContextMenuEnry(ss, remove, pos, list, menu); - menu.addSeparator(); - setupSegmentLabelContextMenuEntries(segmentEdit, menu); - menu.addSeparator(); - menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"), - ss, [ss, expand]() { expand(ss); }); - menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), - ss, [ss, collapse]() { collapse(ss); }); - menu.addSeparator(); - menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"), - ss, [ss, maximize]() { maximize(ss); }); - menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"), - ss, [ss, minimize]() { minimize(ss); }); - menu.exec(list->mapToGlobal(pos)); -} - -void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos) -{ - setupConextMenu(this, pos, &AdvSceneSwitcher::RemoveMacroAction, - &AdvSceneSwitcher::ExpandAllActions, - &AdvSceneSwitcher::CollapseAllActions, - &AdvSceneSwitcher::MaximizeActions, - &AdvSceneSwitcher::MinimizeActions, ui->actionsList); -} - -void AdvSceneSwitcher::ShowMacroElseActionsContextMenu(const QPoint &pos) -{ - setupConextMenu(this, pos, &AdvSceneSwitcher::RemoveMacroElseAction, - &AdvSceneSwitcher::ExpandAllElseActions, - &AdvSceneSwitcher::CollapseAllElseActions, - &AdvSceneSwitcher::MaximizeElseActions, - &AdvSceneSwitcher::MinimizeElseActions, - ui->elseActionsList); -} - -void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos) -{ - setupConextMenu(this, pos, &AdvSceneSwitcher::RemoveMacroCondition, - &AdvSceneSwitcher::ExpandAllConditions, - &AdvSceneSwitcher::CollapseAllConditions, - &AdvSceneSwitcher::MaximizeConditions, - &AdvSceneSwitcher::MinimizeConditions, - ui->conditionsList); -} - void AdvSceneSwitcher::CopyMacro() { const auto macro = GetSelectedMacro(); @@ -1396,128 +754,10 @@ void AdvSceneSwitcher::CopyMacro() emit MacroAdded(QString::fromStdString(name)); } -static void setCollapsedHelper(const std::shared_ptr &m, - MacroSegmentList *list, bool collapsed) +bool MacroTabIsInFocus() { - if (!m) { - return; - } - list->SetCollapsed(collapsed); -} - -void AdvSceneSwitcher::ExpandAllActions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->actionsList, false); -} - -void AdvSceneSwitcher::ExpandAllElseActions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, false); -} - -void AdvSceneSwitcher::ExpandAllConditions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, false); -} - -void AdvSceneSwitcher::CollapseAllActions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->actionsList, true); -} - -void AdvSceneSwitcher::CollapseAllElseActions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, true); -} - -void AdvSceneSwitcher::CollapseAllConditions() const -{ - setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, true); -} - -static void reduceSizeOfSplitterIdx(QSplitter *splitter, int idx) -{ - auto sizes = splitter->sizes(); - int sum = sizes[0] + sizes[1]; - int reducedSize = sum / 10; - sizes[idx] = reducedSize; - sizes[(idx + 1) % 2] = sum - reducedSize; - splitter->setSizes(sizes); -} - -void AdvSceneSwitcher::MinimizeActions() const -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - if (macro->ElseActions().size() == 0) { - reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); - } else { - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); - } -} - -void AdvSceneSwitcher::MaximizeActions() const -{ - MinimizeElseActions(); - MinimizeConditions(); -} - -void AdvSceneSwitcher::MinimizeElseActions() const -{ - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - if (macro->ElseActions().size() == 0) { - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); - } else { - reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 1); - } -} - -void AdvSceneSwitcher::MaximizeElseActions() const -{ - MinimizeConditions(); - reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 0); -} - -void AdvSceneSwitcher::MinimizeConditions() const -{ - reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 0); -} - -void AdvSceneSwitcher::MaximizeConditions() const -{ - MinimizeElseActions(); - MinimizeActions(); -} - -void AdvSceneSwitcher::on_toggleElseActions_clicked() const -{ - auto elsePosition = ui->macroElseActionSplitter->sizes(); - if (elsePosition[1] == 0) { - centerSplitterPosition(ui->macroElseActionSplitter); - return; - } - - maximizeFirstSplitterEntry(ui->macroElseActionSplitter); -} - -void AdvSceneSwitcher::SetElseActionsStateToHidden() const -{ - ui->toggleElseActions->setToolTip(obs_module_text( - "AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip")); - ui->toggleElseActions->setChecked(false); -} - -void AdvSceneSwitcher::SetElseActionsStateToVisible() const -{ - ui->toggleElseActions->setToolTip(obs_module_text( - "AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip")); - ui->toggleElseActions->setChecked(true); + return AdvSceneSwitcher::window && + AdvSceneSwitcher::window->MacroTabIsInFocus(); } bool AdvSceneSwitcher::MacroTabIsInFocus() @@ -1526,206 +766,4 @@ bool AdvSceneSwitcher::MacroTabIsInFocus() (ui->tabWidget->currentWidget()->objectName() == "macroTab"); } -void AdvSceneSwitcher::UpMacroSegementHotkey() -{ - if (!MacroTabIsInFocus()) { - return; - } - - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - int actionSize = macro->Actions().size(); - int conditionSize = macro->Conditions().size(); - - if (currentActionIdx == -1 && currentConditionIdx == -1) { - if (lastInteracted == MacroSection::CONDITIONS) { - if (conditionSize == 0) { - MacroActionSelectionChanged(0); - } else { - MacroConditionSelectionChanged(0); - } - } else { - if (actionSize == 0) { - MacroConditionSelectionChanged(0); - } else { - MacroActionSelectionChanged(0); - } - } - return; - } - - if (currentActionIdx > 0) { - MacroActionSelectionChanged(currentActionIdx - 1); - return; - } - if (currentConditionIdx > 0) { - MacroConditionSelectionChanged(currentConditionIdx - 1); - return; - } - if (currentActionIdx == 0) { - if (conditionSize == 0) { - MacroActionSelectionChanged(actionSize - 1); - } else { - MacroConditionSelectionChanged(conditionSize - 1); - } - return; - } - if (currentConditionIdx == 0) { - if (actionSize == 0) { - MacroConditionSelectionChanged(conditionSize - 1); - } else { - MacroActionSelectionChanged(actionSize - 1); - } - return; - } -} - -void AdvSceneSwitcher::DownMacroSegementHotkey() -{ - if (!MacroTabIsInFocus()) { - return; - } - - auto macro = GetSelectedMacro(); - if (!macro) { - return; - } - int actionSize = macro->Actions().size(); - int conditionSize = macro->Conditions().size(); - - if (currentActionIdx == -1 && currentConditionIdx == -1) { - if (lastInteracted == MacroSection::CONDITIONS) { - if (conditionSize == 0) { - MacroActionSelectionChanged(0); - } else { - MacroConditionSelectionChanged(0); - } - } else { - if (actionSize == 0) { - MacroConditionSelectionChanged(0); - } else { - MacroActionSelectionChanged(0); - } - } - return; - } - - if (currentActionIdx < actionSize - 1) { - MacroActionSelectionChanged(currentActionIdx + 1); - return; - } - if (currentConditionIdx < conditionSize - 1) { - MacroConditionSelectionChanged(currentConditionIdx + 1); - return; - } - if (currentActionIdx == actionSize - 1) { - if (conditionSize == 0) { - MacroActionSelectionChanged(0); - } else { - MacroConditionSelectionChanged(0); - } - return; - } - if (currentConditionIdx == conditionSize - 1) { - if (actionSize == 0) { - MacroConditionSelectionChanged(0); - } else { - MacroActionSelectionChanged(0); - } - return; - } -} - -void AdvSceneSwitcher::DeleteMacroSegementHotkey() -{ - if (!MacroTabIsInFocus()) { - return; - } - - if (currentActionIdx != -1) { - RemoveMacroAction(currentActionIdx); - } else if (currentConditionIdx != -1) { - RemoveMacroCondition(currentConditionIdx); - } -} - -static void fade(QWidget *widget, bool fadeOut) -{ - const double fadeOutOpacity = 0.3; - // Don't use exactly 1.0 as for some reason this causes buttons in - // macroSplitter handle layout to not be redrawn unless mousing over - // them - const double fadeInOpacity = 0.99; - auto curEffect = widget->graphicsEffect(); - if (curEffect) { - auto curOpacity = - dynamic_cast(curEffect); - if (curOpacity && - ((fadeOut && DoubleEquals(curOpacity->opacity(), - fadeOutOpacity, 0.0001)) || - (!fadeOut && DoubleEquals(curOpacity->opacity(), - fadeInOpacity, 0.0001)))) { - return; - } - } else if (!fadeOut) { - return; - } - delete curEffect; - QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(); - widget->setGraphicsEffect(opacityEffect); - QPropertyAnimation *animation = - new QPropertyAnimation(opacityEffect, "opacity"); - animation->setDuration(350); - animation->setStartValue(fadeOut ? fadeInOpacity : fadeOutOpacity); - animation->setEndValue(fadeOut ? fadeOutOpacity : fadeInOpacity); - animation->setEasingCurve(QEasingCurve::OutQuint); - animation->start(QPropertyAnimation::DeleteWhenStopped); -} - -static void fadeWidgets(const std::vector &widgets, bool fadeOut) -{ - for (const auto &widget : widgets) { - fade(widget, fadeOut); - } -} - -void AdvSceneSwitcher::HighlightControls() const -{ - const std::vector conditionControls{ - ui->conditionAdd, ui->conditionRemove, ui->conditionTop, - ui->conditionUp, ui->conditionDown, ui->conditionBottom, - }; - const std::vector actionControls{ - ui->actionAdd, ui->actionRemove, ui->actionTop, - ui->actionUp, ui->actionDown, ui->actionBottom, - }; - const std::vector elseActionControls{ - ui->elseActionAdd, ui->elseActionRemove, ui->elseActionTop, - ui->elseActionUp, ui->elseActionDown, ui->elseActionBottom, - }; - - if ((currentActionIdx == -1 && currentConditionIdx == -1 && - currentElseActionIdx == -1)) { - fadeWidgets(conditionControls, false); - fadeWidgets(actionControls, false); - fadeWidgets(elseActionControls, false); - } else if (currentConditionIdx != -1) { - fadeWidgets(conditionControls, false); - fadeWidgets(actionControls, true); - fadeWidgets(elseActionControls, true); - } else if (currentActionIdx != -1) { - fadeWidgets(conditionControls, true); - fadeWidgets(actionControls, false); - fadeWidgets(elseActionControls, true); - } else if (currentElseActionIdx != -1) { - fadeWidgets(conditionControls, true); - fadeWidgets(actionControls, true); - fadeWidgets(elseActionControls, false); - } else { - assert(false); - } -} - } // namespace advss diff --git a/lib/utils/splitter-helpers.cpp b/lib/utils/splitter-helpers.cpp index 6cb5b2b4..a77c7b6f 100644 --- a/lib/utils/splitter-helpers.cpp +++ b/lib/utils/splitter-helpers.cpp @@ -30,4 +30,36 @@ void LoadSplitterPos(QList &sizes, obs_data_t *obj, obs_data_array_release(array); } +void CenterSplitterPosition(QSplitter *splitter) +{ + SetSplitterPositionByFraction(splitter, 0.5); +} + +void SetSplitterPositionByFraction(QSplitter *splitter, double fraction) +{ + int value1 = (double)QWIDGETSIZE_MAX * fraction; + int value2 = (double)QWIDGETSIZE_MAX * (1.0 - fraction); + splitter->setSizes(QList() << value1 << value2); +} + +void MaximizeFirstSplitterEntry(QSplitter *splitter) +{ + QList newSizes; + newSizes << QWIDGETSIZE_MAX; + for (int i = 0; i < splitter->sizes().size() - 1; i++) { + newSizes << 0; + } + splitter->setSizes(newSizes); +} + +void ReduceSizeOfSplitterIdx(QSplitter *splitter, int idx) +{ + auto sizes = splitter->sizes(); + int sum = sizes[0] + sizes[1]; + int reducedSize = sum / 10; + sizes[idx] = reducedSize; + sizes[(idx + 1) % 2] = sum - reducedSize; + splitter->setSizes(sizes); +} + } // namespace advss diff --git a/lib/utils/splitter-helpers.hpp b/lib/utils/splitter-helpers.hpp index 1c77115a..6c1bee3f 100644 --- a/lib/utils/splitter-helpers.hpp +++ b/lib/utils/splitter-helpers.hpp @@ -2,7 +2,10 @@ #include "export-symbol-helper.hpp" #include + #include +#include + #include namespace advss { @@ -12,4 +15,9 @@ void SaveSplitterPos(const QList &sizes, obs_data_t *obj, void LoadSplitterPos(QList &sizes, obs_data_t *obj, const std::string &name); +void CenterSplitterPosition(QSplitter *splitter); +void SetSplitterPositionByFraction(QSplitter *splitter, double fraction); +void MaximizeFirstSplitterEntry(QSplitter *splitter); +void ReduceSizeOfSplitterIdx(QSplitter *splitter, int idx); + } // namespace advss diff --git a/lib/utils/tab-helpers.hpp b/lib/utils/tab-helpers.hpp index 3dd5f2bd..b90060bf 100644 --- a/lib/utils/tab-helpers.hpp +++ b/lib/utils/tab-helpers.hpp @@ -18,5 +18,6 @@ void SetCurrentTab(QTabWidget *tabWidget); void SetupOtherTabs(QTabWidget *tabWidget); void SaveTabOrder(obs_data_t *obj); void LoadTabOrder(obs_data_t *obj); +bool MacroTabIsInFocus(); } // namespace advss