From 7947ad2608a44db4b0b7ef534353a2ce2a23a8a4 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Wed, 24 Feb 2021 18:19:23 +0100 Subject: [PATCH] Add option to extend scene sequences (#121) Extending a scene sequence allows for more complex switching setups, but should also simplify some setups. Sequences like "A -> B -> A -> C", which previously were only possible by creating a copy of A, can now be specified in a single entry. To extend a sequence either select the sequence you want to modify and click the extend sequence button or simply double click the entry. --- data/locale/en-US.ini | 2 + forms/advanced-scene-switcher.ui | 14 + src/advanced-scene-switcher.cpp | 106 ++--- src/headers/advanced-scene-switcher.hpp | 7 +- src/headers/switch-generic.hpp | 2 +- src/headers/switch-sequence.hpp | 45 ++- src/headers/switcher-data-structs.hpp | 10 +- src/switch-sequence.cpp | 511 +++++++++++++++++++++--- src/switcher-data-structs.cpp | 10 + 9 files changed, 592 insertions(+), 115 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e920df88..edd9829c 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -186,6 +186,8 @@ AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)" AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible" AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence" AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} {{delayUnits}} using {{transitions}} {{interruptible}}" +AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence" +AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} switch to {{scenes}} using {{transitions}}" AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue." ; Audio Tab diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui index a0f6b8db..ec8cc3db 100644 --- a/forms/advanced-scene-switcher.ui +++ b/forms/advanced-scene-switcher.ui @@ -2824,6 +2824,20 @@ + + + + Qt::Vertical + + + + + + + AdvSceneSwitcher.sceneSequenceTab.extendEdit + + + diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index d7ed0161..4d8e735d 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -469,61 +469,17 @@ void SwitcherData::Thread() linger = 0; switcher->Prune(); - if (switcher->stop) { break; } - if (checkPause()) { continue; } - - for (int switchFuncName : functionNamesByPriority) { - switch (switchFuncName) { - case read_file_func: - checkSwitchInfoFromFile(match, scene, - transition); - checkFileContent(match, scene, transition); - break; - case idle_func: - checkIdleSwitch(match, scene, transition); - break; - case exe_func: - checkExeSwitch(match, scene, transition); - break; - case screen_region_func: - checkScreenRegionSwitch(match, scene, - transition); - break; - case window_title_func: - checkWindowTitleSwitch(match, scene, - transition); - break; - case round_trip_func: - checkSceneSequence(match, scene, transition, - linger); - break; - case media_func: - checkMediaSwitch(match, scene, transition); - break; - case time_func: - checkTimeSwitch(match, scene, transition); - break; - case audio_func: - checkAudioSwitch(match, scene, transition); - break; - } - - if (switcher->stop) { - goto endLoop; - } - if (match) { - break; - } + match = checkForMatch(scene, transition, linger); + if (switcher->stop) { + break; } - checkNoMatchSwitch(match, scene, transition, sleep); - checkSwitchCooldown(match); if (linger) { @@ -561,10 +517,64 @@ void SwitcherData::Thread() writeSceneInfoToFile(); } -endLoop: + blog(LOG_INFO, "stopped"); } +bool SwitcherData::checkForMatch(OBSWeakSource &scene, + OBSWeakSource &transition, int &linger) +{ + bool match = false; + + if (uninterruptibleSceneSequenceActive) { + checkSceneSequence(match, scene, transition, linger); + if (match) { + return match; + } + } + + for (int switchFuncName : functionNamesByPriority) { + switch (switchFuncName) { + case read_file_func: + checkSwitchInfoFromFile(match, scene, transition); + checkFileContent(match, scene, transition); + break; + case idle_func: + checkIdleSwitch(match, scene, transition); + break; + case exe_func: + checkExeSwitch(match, scene, transition); + break; + case screen_region_func: + checkScreenRegionSwitch(match, scene, transition); + break; + case window_title_func: + checkWindowTitleSwitch(match, scene, transition); + break; + case round_trip_func: + checkSceneSequence(match, scene, transition, linger); + break; + case media_func: + checkMediaSwitch(match, scene, transition); + break; + case time_func: + checkTimeSwitch(match, scene, transition); + break; + case audio_func: + checkAudioSwitch(match, scene, transition); + break; + } + + if (switcher->stop) { + return false; + } + if (match) { + break; + } + } + return match; +} + void switchScene(OBSWeakSource &scene, OBSWeakSource &transition, bool &transitionOverrideOverride) { diff --git a/src/headers/advanced-scene-switcher.hpp b/src/headers/advanced-scene-switcher.hpp index 04e5c331..6f6e7d24 100644 --- a/src/headers/advanced-scene-switcher.hpp +++ b/src/headers/advanced-scene-switcher.hpp @@ -40,6 +40,7 @@ public: int IgnoreIdleWindowsFindByData(const QString &window); void UpdateNonMatchingScene(const QString &name); + void OpenSequenceExtendEdit(SequenceWidget *sw); void SetEditSceneGroup(SceneGroup &sg); void loadUI(); @@ -136,10 +137,12 @@ public slots: void on_sceneSequenceAdd_clicked(); void on_sceneSequenceRemove_clicked(); - void on_sceneSequenceSave_clicked(); - void on_sceneSequenceLoad_clicked(); void on_sceneSequenceUp_clicked(); void on_sceneSequenceDown_clicked(); + void on_sceneSequenceSave_clicked(); + void on_sceneSequenceLoad_clicked(); + void on_sequenceEdit_clicked(); + void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item); void on_verboseLogging_stateChanged(int state); void on_uiHintsDisable_stateChanged(int state); diff --git a/src/headers/switch-generic.hpp b/src/headers/switch-generic.hpp index f33788df..372217d9 100644 --- a/src/headers/switch-generic.hpp +++ b/src/headers/switch-generic.hpp @@ -73,7 +73,7 @@ public: static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2); -private slots: +protected slots: void SceneChanged(const QString &text); void TransitionChanged(const QString &text); void SceneGroupAdd(const QString &name); diff --git a/src/headers/switch-sequence.hpp b/src/headers/switch-sequence.hpp index 432048cf..10912631 100644 --- a/src/headers/switch-sequence.hpp +++ b/src/headers/switch-sequence.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include "switch-generic.hpp" constexpr auto round_trip_func = 1; @@ -12,42 +14,77 @@ typedef enum { struct SceneSequenceSwitch : SceneSwitcherEntry { static bool pause; + SwitchTargetType startTargetType = SwitchTargetType::Scene; OBSWeakSource startScene = nullptr; double delay = 0; int delayMultiplier = 1; bool interruptible = false; unsigned int matchCount = 0; + // nullptr marks start point and reaching end of extended sequence + SceneSequenceSwitch *activeSequence = nullptr; + + std::unique_ptr extendedSequence = nullptr; + const char *getType() { return "sequence"; } bool initialized(); bool valid(); - void save(obs_data_t *obj); - void load(obs_data_t *obj); + void save(obs_data_t *obj, bool saveExt = true); + void load(obs_data_t *obj, bool saveExt = true); + + bool reduce(); + SceneSequenceSwitch *extend(); + + bool checkMatch(OBSWeakSource currentScene, int &linger, + SceneSequenceSwitch *root = nullptr); + bool checkDurationMatchInterruptible(); + void prepareUninterruptibleMatch(OBSWeakSource currentScene, + int &linger); + void advanceActiveSequence(); + void logAdvanceSequence(); + void logSequenceCanceled(); }; class SequenceWidget : public SwitchWidget { Q_OBJECT public: - SequenceWidget(QWidget *parent, SceneSequenceSwitch *s); + SequenceWidget(QWidget *parent, SceneSequenceSwitch *s, + bool extendSequence = false, + bool editExtendMode = false); SceneSequenceSwitch *getSwitchData(); void setSwitchData(SceneSequenceSwitch *s); static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2); void UpdateDelay(); + void UpdateExtendText(); + void setExtendedSequenceStartScene(); private slots: + void SceneChanged(const QString &text); void DelayChanged(double delay); void DelayUnitsChanged(int idx); void StartSceneChanged(const QString &text); void InterruptibleChanged(int state); + void ExtendClicked(); + void ReduceClicked(); -private: +protected: QDoubleSpinBox *delay; QComboBox *delayUnits; QComboBox *startScenes; QCheckBox *interruptible; + QVBoxLayout *extendSequenceLayout; + QPushButton *extend; + QPushButton *reduce; + + // I would prefer having a list of only widgets of type editExtendMode + // but I am not sure how to best implement that using a QListWidget. + // + // So use edit button to bring up edit widget and + // add a label to disaplay current extended sequence state. + QLabel *extendText; SceneSequenceSwitch *switchData; }; diff --git a/src/headers/switcher-data-structs.hpp b/src/headers/switcher-data-structs.hpp index eec830c5..d02993d6 100644 --- a/src/headers/switcher-data-structs.hpp +++ b/src/headers/switcher-data-structs.hpp @@ -85,6 +85,7 @@ struct SwitcherData { std::vector ignoreWindowsSwitches; + bool uninterruptibleSceneSequenceActive = false; std::deque sceneSequenceSwitches; std::deque randomSwitches; @@ -160,11 +161,14 @@ struct SwitcherData { void Stop(); bool sceneChangedDuringWait(); + bool prioFuncsValid(); + void writeSceneInfoToFile(); void writeToStatusFile(QString status); - bool checkPause(); - void checkDefaultSceneTransitions(); + + bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition, + int &linger); void checkSceneSequence(bool &match, OBSWeakSource &scene, OBSWeakSource &transition, int &linger); void checkIdleSwitch(bool &match, OBSWeakSource &scene, @@ -193,6 +197,8 @@ struct SwitcherData { OBSWeakSource &transition, int &sleep); void checkSwitchCooldown(bool &match); void checkTriggers(); + bool checkPause(); + void checkDefaultSceneTransitions(); void saveSettings(obs_data_t *obj); void saveWindowTitleSwitches(obs_data_t *obj); diff --git a/src/switch-sequence.cpp b/src/switch-sequence.cpp index 1a2bccf3..bc1a8225 100644 --- a/src/switch-sequence.cpp +++ b/src/switch-sequence.cpp @@ -4,6 +4,8 @@ #include "headers/advanced-scene-switcher.hpp" #include "headers/utility.hpp" +constexpr auto max_extend_text_size = 150; + bool SceneSequenceSwitch::pause = false; static QMetaObject::Connection addPulse; @@ -159,30 +161,41 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked() close(); } -bool matchInterruptible(SwitcherData *switcher, SceneSequenceSwitch &s) +void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw) { - bool durationReached = s.matchCount * (switcher->interval / 1000.0) >= - s.delay; + QDialog edit; + SequenceWidget editWidget(this, sw->getSwitchData(), false, true); + QHBoxLayout layout; + layout.setSizeConstraint(QLayout::SetFixedSize); + layout.addWidget(&editWidget); + edit.setLayout(&layout); + edit.setWindowTitle(obs_module_text( + "AdvSceneSwitcher.sceneSequenceTab.extendEdit")); + edit.exec(); - s.matchCount++; - - if (durationReached) { - return true; - } - return false; + sw->UpdateExtendText(); } -bool matchUninterruptible(SwitcherData *switcher, SceneSequenceSwitch &s, - obs_source_t *currentSource, int &linger) +void AdvSceneSwitcher::on_sequenceEdit_clicked() { - // scene was already active for the previous cycle so remove this time - int dur = s.delay * 1000 - switcher->interval; - if (dur > 0) { - switcher->waitScene = currentSource; - linger = dur; + int index = ui->sceneSequenceSwitches->currentRow(); + if (index == -1) { + return; } - return true; + SequenceWidget *currentWidget = + (SequenceWidget *)ui->sceneSequenceSwitches->itemWidget( + ui->sceneSequenceSwitches->item(index)); + + OpenSequenceExtendEdit(currentWidget); +} + +void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked( + QListWidgetItem *item) +{ + SequenceWidget *currentWidget = + (SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(item); + OpenSequenceExtendEdit(currentWidget); } void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene, @@ -192,38 +205,53 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene, return; } - obs_source_t *currentSource = obs_frontend_get_current_scene(); - obs_weak_source_t *ws = obs_source_get_weak_source(currentSource); + obs_source_t *currentSceneSource = obs_frontend_get_current_scene(); + obs_weak_source_t *currentScene = + obs_source_get_weak_source(currentSceneSource); for (SceneSequenceSwitch &s : sceneSequenceSwitches) { - if (!s.initialized()) { + // Continue the active uninterruptible sequence and skip others + if (uninterruptibleSceneSequenceActive && + s.activeSequence == nullptr) { continue; } - if (s.startScene == ws) { - if (!match) { - if (s.interruptible) { - match = matchInterruptible(switcher, s); - } else { - match = matchUninterruptible( - switcher, s, currentSource, - linger); - } + bool matched = s.checkMatch(currentScene, linger); - if (match) { - scene = s.getScene(); - transition = s.transition; - if (switcher->verbose) { - s.logMatch(); - } + if (!match && matched) { + match = matched; + + if (s.activeSequence) { + scene = s.activeSequence->getScene(); + transition = s.activeSequence->transition; + } else { + scene = s.getScene(); + transition = s.transition; + if (verbose) { + s.logMatch(); } } - } else { - s.matchCount = 0; + + s.advanceActiveSequence(); + if (verbose) { + s.logAdvanceSequence(); + } + + // Ignore other switching methods if sequence is not + // interruptible and has not reached its end + if (s.activeSequence) { + uninterruptibleSceneSequenceActive = + !s.interruptible; + } } } - obs_source_release(currentSource); - obs_weak_source_release(ws); + + if (!match) { + uninterruptibleSceneSequenceActive = false; + } + + obs_source_release(currentSceneSource); + obs_weak_source_release(currentScene); } void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj) @@ -291,18 +319,32 @@ bool SceneSequenceSwitch::valid() (SceneSwitcherEntry::valid() && WeakSourceValid(startScene)); } -void SceneSequenceSwitch::save(obs_data_t *obj) +void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt) { SceneSwitcherEntry::save(obj); + obs_data_set_int(obj, "startTargetType", + static_cast(startTargetType)); obs_data_set_string(obj, "startScene", GetWeakSourceName(startScene).c_str()); - obs_data_set_double(obj, "delay", delay); - obs_data_set_int(obj, "delayMultiplier", delayMultiplier); - obs_data_set_bool(obj, "interruptible", interruptible); + + if (saveExt) { + auto cur = extendedSequence.get(); + + obs_data_array_t *extendScenes = obs_data_array_create(); + while (cur) { + obs_data_t *array_obj = obs_data_create(); + cur->save(array_obj, false); + obs_data_array_push_back(extendScenes, array_obj); + obs_data_release(array_obj); + cur = cur->extendedSequence.get(); + } + obs_data_set_array(obj, "extendScenes", extendScenes); + obs_data_array_release(extendScenes); + } } // To be removed in future version @@ -341,14 +383,15 @@ bool loadOldScequence(obs_data_t *obj, SceneSequenceSwitch *s) return true; } -void SceneSequenceSwitch::load(obs_data_t *obj) +void SceneSequenceSwitch::load(obs_data_t *obj, bool saveExt) { if (loadOldScequence(obj, this)) { return; } SceneSwitcherEntry::load(obj); - + startTargetType = static_cast( + obs_data_get_int(obj, "startTargetType")); const char *scene = obs_data_get_string(obj, "startScene"); startScene = GetWeakSourceByName(scene); @@ -360,6 +403,173 @@ void SceneSequenceSwitch::load(obs_data_t *obj) delayMultiplier = 1; interruptible = obs_data_get_bool(obj, "interruptible"); + + if (saveExt) { + auto cur = this; + + obs_data_array_t *extendScenes = + obs_data_get_array(obj, "extendScenes"); + size_t count = obs_data_array_count(extendScenes); + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = + obs_data_array_item(extendScenes, i); + + cur->extendedSequence = + std::make_unique(); + + cur->extendedSequence->load(array_obj, false); + + cur = cur->extendedSequence.get(); + obs_data_release(array_obj); + } + obs_data_array_release(extendScenes); + } +} + +bool SceneSequenceSwitch::reduce() +{ + // Reset activeSequence just in case it was one of the deleted entries + activeSequence = nullptr; + + if (!extendedSequence) { + return true; + } + if (extendedSequence->reduce()) { + extendedSequence.reset(nullptr); + } + return false; +} + +SceneSequenceSwitch *SceneSequenceSwitch::extend() +{ + if (extendedSequence) { + return extendedSequence->extend(); + } + extendedSequence = std::make_unique(); + extendedSequence->startScene = scene; + return extendedSequence.get(); +} + +bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger, + SceneSequenceSwitch *root) +{ + if (!initialized()) { + if (root) { + root->activeSequence = nullptr; + } + return false; + } + + bool match = false; + + if (activeSequence) { + return activeSequence->checkMatch(currentScene, linger, this); + } + + if (startScene == currentScene) { + if (interruptible) { + match = checkDurationMatchInterruptible(); + } else { + match = true; + prepareUninterruptibleMatch(currentScene, linger); + } + } else { + matchCount = 0; + + if (root) { + root->activeSequence = nullptr; + logSequenceCanceled(); + } + } + + return match; +} + +bool SceneSequenceSwitch::checkDurationMatchInterruptible() +{ + bool durationReached = matchCount * (switcher->interval / 1000.0) >= + delay; + matchCount++; + if (durationReached) { + matchCount = 0; + return true; + } + return false; +} + +void SceneSequenceSwitch::prepareUninterruptibleMatch( + OBSWeakSource currentScene, int &linger) +{ + int dur = delay * 1000; + if (dur > 0) { + switcher->waitScene = obs_weak_source_get_source(currentScene); + obs_source_release(switcher->waitScene); + linger = dur; + } +} + +void SceneSequenceSwitch::advanceActiveSequence() +{ + // Set start Scene + OBSWeakSource currentSceneGroupScene = nullptr; + if (targetType == SwitchTargetType::SceneGroup && group) { + currentSceneGroupScene = group->getCurrentScene(); + } + + if (activeSequence) { + activeSequence = activeSequence->extendedSequence.get(); + } else { + activeSequence = extendedSequence.get(); + } + + if (activeSequence) { + if (activeSequence->startTargetType == + SwitchTargetType::SceneGroup) { + activeSequence->startScene = currentSceneGroupScene; + } + if (activeSequence->targetType == SwitchTargetType::Scene && + !activeSequence->scene) { + blog(LOG_WARNING, + "cannot advance sequence - null scene set"); + activeSequence = nullptr; + } + if (activeSequence->targetType == + SwitchTargetType::SceneGroup && + activeSequence->group && + activeSequence->group->scenes.empty()) { + blog(LOG_WARNING, + "cannot advance sequence - no scenes specified in '%s'", + activeSequence->group->name.c_str()); + activeSequence = nullptr; + return; + } + + // Reinit old matchCount value in case it was previously set + activeSequence->matchCount = 0; + } +} + +void SceneSequenceSwitch::logAdvanceSequence() +{ + if (activeSequence) { + std::string targetName = + GetWeakSourceName(activeSequence->scene); + + if (activeSequence->targetType == + SwitchTargetType::SceneGroup && + activeSequence->group) { + targetName = activeSequence->group->name; + } + + blog(LOG_INFO, "continuing sequence with '%s' -> '%s'", + GetWeakSourceName(activeSequence->startScene).c_str(), + targetName.c_str()); + } +} + +void SceneSequenceSwitch::logSequenceCanceled() +{ + blog(LOG_INFO, "unexpected scene change - cancel sequence"); } void populateDelayUnits(QComboBox *list) @@ -369,14 +579,82 @@ void populateDelayUnits(QComboBox *list) list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours")); } -SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s) - : SwitchWidget(parent, s, true, true) +QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0) { + if (!s) { + return ""; + } + + QString ext = ""; + + ext = QString::number(s->delay / s->delayMultiplier) + " "; + + switch (s->delayMultiplier) { + case 1: + ext += obs_module_text("AdvSceneSwitcher.unit.secends"); + break; + case 60: + ext += obs_module_text("AdvSceneSwitcher.unit.minutes"); + break; + case 60 * 60: + ext += obs_module_text("AdvSceneSwitcher.unit.hours"); + break; + default: + ext += obs_module_text("?????"); + break; + } + + QString sceneName = GetWeakSourceName(s->scene).c_str(); + if (s->targetType == SwitchTargetType::SceneGroup && s->group) { + sceneName = QString::fromStdString(s->group->name); + } + if (sceneName.isEmpty()) { + sceneName = obs_module_text("AdvSceneSwitcher.selectScene"); + } + ext += " -> [" + sceneName + "]"; + + if (ext.length() + curLen > max_extend_text_size) { + return "..."; + } + + if (s->extendedSequence.get()) { + return ext += + " | " + makeExtendText(s->extendedSequence.get(), + curLen + ext.length()); + } else { + return ext; + } +} + +SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s, + bool extendSequence, bool editExtendMode) + : SwitchWidget(parent, s, !extendSequence, true) +{ + this->setParent(parent); + delay = new QDoubleSpinBox(); delayUnits = new QComboBox(); startScenes = new QComboBox(); interruptible = new QCheckBox(obs_module_text( "AdvSceneSwitcher.sceneSequenceTab.interruptible")); + extendText = new QLabel(); + extend = new QPushButton(); + reduce = new QPushButton(); + + extend->setProperty("themeID", + QVariant(QStringLiteral("addIconSmall"))); + reduce->setProperty("themeID", + QVariant(QStringLiteral("removeIconSmall"))); + + extend->setMaximumSize(22, 22); + reduce->setMaximumSize(22, 22); + + // We need to extend the generic SwitchWidget::SceneChanged() + // with our own SequenceWidget::SceneChanged() + // so the old singal / slot needs to be disconnected + scenes->disconnect(); + QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(SceneChanged(const QString &))); QWidget::connect(delay, SIGNAL(valueChanged(double)), this, SLOT(DelayChanged(double))); @@ -388,6 +666,11 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s) QWidget::connect(interruptible, SIGNAL(stateChanged(int)), this, SLOT(InterruptibleChanged(int))); + QWidget::connect(extend, SIGNAL(clicked()), this, + SLOT(ExtendClicked())); + QWidget::connect(reduce, SIGNAL(clicked()), this, + SLOT(ReduceClicked())); + delay->setMaximum(99999.000000); AdvSceneSwitcher::populateSceneSelection(startScenes, false); populateDelayUnits(delayUnits); @@ -413,17 +696,62 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s) interruptible->setChecked(s->interruptible); } - QHBoxLayout *mainLayout = new QHBoxLayout; - std::unordered_map widgetPlaceholders = { - {"{{startScenes}}", startScenes}, - {"{{scenes}}", scenes}, - {"{{delay}}", delay}, - {"{{delayUnits}}", delayUnits}, - {"{{transitions}}", transitions}, - {"{{interruptible}}", interruptible}}; - placeWidgets(obs_module_text("AdvSceneSwitcher.sceneSequenceTab.entry"), - mainLayout, widgetPlaceholders); - setLayout(mainLayout); + if (extendSequence) { + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{scenes}}", scenes}, + {"{{delay}}", delay}, + {"{{delayUnits}}", delayUnits}, + {"{{transitions}}", transitions}}; + placeWidgets( + obs_module_text( + "AdvSceneSwitcher.sceneSequenceTab.extendEntry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + } else { + QHBoxLayout *startSequence = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{startScenes}}", startScenes}, + {"{{scenes}}", scenes}, + {"{{delay}}", delay}, + {"{{delayUnits}}", delayUnits}, + {"{{transitions}}", transitions}, + {"{{interruptible}}", interruptible}}; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.sceneSequenceTab.entry"), + startSequence, widgetPlaceholders); + + //exetend widgets placed here + extendSequenceLayout = new QVBoxLayout; + if (s) { + if (!editExtendMode) { + extendText->setText(makeExtendText( + s->extendedSequence.get())); + } else { + auto cur = s->extendedSequence.get(); + while (cur != nullptr) { + extendSequenceLayout->addWidget( + new SequenceWidget(parent, cur, + true, true)); + cur = cur->extendedSequence.get(); + } + } + } + + QHBoxLayout *extendSequenceControlsLayout = new QHBoxLayout; + if (editExtendMode) { + extendSequenceControlsLayout->addWidget(extend); + extendSequenceControlsLayout->addWidget(reduce); + } + extendSequenceControlsLayout->addStretch(); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(startSequence); + mainLayout->addLayout(extendSequenceLayout); + mainLayout->addWidget(extendText); + mainLayout->addLayout(extendSequenceControlsLayout); + setLayout(mainLayout); + } switchData = s; @@ -493,6 +821,32 @@ void SequenceWidget::DelayUnitsChanged(int idx) UpdateDelay(); } +void SequenceWidget::setExtendedSequenceStartScene() +{ + switchData->extendedSequence->startScene = switchData->scene; + switchData->extendedSequence->startTargetType = SwitchTargetType::Scene; + + if (switchData->targetType == SwitchTargetType::SceneGroup) { + switchData->extendedSequence->startScene = nullptr; + switchData->extendedSequence->startTargetType = + SwitchTargetType::SceneGroup; + } +} + +void SequenceWidget::SceneChanged(const QString &text) +{ + if (loading || !switchData) { + return; + } + + SwitchWidget::SceneChanged(text); + std::lock_guard lock(switcher->m); + + if (switchData->extendedSequence) { + setExtendedSequenceStartScene(); + } +} + void SequenceWidget::StartSceneChanged(const QString &text) { if (loading || !switchData) { @@ -511,4 +865,45 @@ void SequenceWidget::InterruptibleChanged(int state) std::lock_guard lock(switcher->m); switchData->interruptible = state; + + auto cur = switchData->extendedSequence.get(); + while (cur != nullptr) { + cur->interruptible = state; + cur = cur->extendedSequence.get(); + } +} + +void SequenceWidget::UpdateExtendText() +{ + extendText->setText(makeExtendText(switchData->extendedSequence.get())); +} + +void SequenceWidget::ExtendClicked() +{ + if (loading || !switchData) { + return; + } + + std::lock_guard lock(switcher->m); + auto es = switchData->extend(); + + SequenceWidget *ew = new SequenceWidget(this->parentWidget(), es, true); + extendSequenceLayout->addWidget(ew); +} + +void SequenceWidget::ReduceClicked() +{ + if (loading || !switchData) { + return; + } + + std::lock_guard lock(switcher->m); + switchData->reduce(); + + int count = extendSequenceLayout->count(); + auto item = extendSequenceLayout->takeAt(count - 1); + if (item) { + item->widget()->setVisible(false); + delete item; + } } diff --git a/src/switcher-data-structs.cpp b/src/switcher-data-structs.cpp index e1e257ca..b889bd69 100644 --- a/src/switcher-data-structs.cpp +++ b/src/switcher-data-structs.cpp @@ -43,6 +43,16 @@ void SwitcherData::Prune() sceneSequenceSwitches.erase( sceneSequenceSwitches.begin() + i--); } + + auto cur = &s; + while (cur != nullptr) { + if (cur->extendedSequence && + !cur->extendedSequence->valid()) { + cur->extendedSequence.reset(nullptr); + s.activeSequence = nullptr; + } + cur = cur->extendedSequence.get(); + } } for (size_t i = 0; i < sceneTransitions.size(); i++) {