From 19253ace4b55ddb832f397bc90c410f0f7ddec31 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:16:51 +0200 Subject: [PATCH] Add "Sequence" option to the first run wizard --- CMakeLists.txt | 2 + data/locale/en-US.ini | 27 +- lib/utils/first-run-wizard-sequence.cpp | 499 ++++++++++++++++++++++++ lib/utils/first-run-wizard-sequence.hpp | 101 +++++ lib/utils/first-run-wizard.cpp | 37 +- lib/utils/first-run-wizard.hpp | 21 +- 6 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 lib/utils/first-run-wizard-sequence.cpp create mode 100644 lib/utils/first-run-wizard-sequence.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ef43b9c..ef29d3b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,8 @@ target_sources( lib/utils/first-run-wizard.cpp lib/utils/first-run-wizard.hpp lib/utils/first-run-wizard-helpers.hpp + lib/utils/first-run-wizard-sequence.cpp + lib/utils/first-run-wizard-sequence.hpp lib/utils/first-run-wizard-window.cpp lib/utils/first-run-wizard-window.hpp lib/utils/help-icon.hpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 0e6761f6..e7e71792 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -2679,7 +2679,12 @@ FirstRunWizard.windowTitle="Advanced Scene Switcher - Setup Wizard" FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher" FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps." -FirstRunWizard.welcome.body="

Advanced Scene Switcher lets you build Macros - rules of the form:

When [condition] -> perform [action]

This wizard creates a macro that switches to a chosen OBS scene whenever a specific window comes into focus.

You can skip at any time. Re-open this wizard later from the General tab inside the Advanced Scene Switcher dialog.

" +FirstRunWizard.welcome.body="

Advanced Scene Switcher lets you build Macros - rules of the form:

When [condition] -> perform [action]

This wizard will guide you through creating your first automation. On the next page, choose the type of macro you want to set up.

You can skip at any time. Re-open this wizard later from the General tab inside the Advanced Scene Switcher dialog.

" + +FirstRunWizard.template.title="Choose an Automation Type" +FirstRunWizard.template.subtitle="Select the kind of macro you would like to create." +FirstRunWizard.template.window="Switch to a scene when a window comes into focus" +FirstRunWizard.template.sequence="Run a timed sequence of scene switches" FirstRunWizard.scene.title="Choose a Target Scene" FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?" @@ -2700,6 +2705,26 @@ FirstRunWizard.review.summary="Macro name: Window -> %1

Conditi FirstRunWizard.review.errorTitle="Macro Creation Failed" FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\"" +FirstRunWizard.seqTrigger.title="Choose a Trigger Scene" +FirstRunWizard.seqTrigger.subtitle="The sequence will start when you have been on this scene for the specified duration." +FirstRunWizard.seqTrigger.scene="Trigger scene:{{scene}}" +FirstRunWizard.seqTrigger.delay="Start sequence after{{duration}}" + +FirstRunWizard.seqScenes.title="Build Your Sequence" +FirstRunWizard.seqScenes.subtitle="Add at least two scenes. The plugin will switch through them in order, waiting the configured time between each switch." +FirstRunWizard.seqScenes.triggerInfo="When on \"%1\" for %2, switch to:" +FirstRunWizard.seqScenes.addScene="Add scene" +FirstRunWizard.seqScenes.removeTooltip="Remove this scene from the sequence" +FirstRunWizard.seqScenes.switchTo="Switch to{{scene}}" +FirstRunWizard.seqScenes.thenWait="then wait{{duration}}" + +FirstRunWizard.seqReview.title="Review Your Sequence" +FirstRunWizard.seqReview.subtitle="Click Back to make changes, or Finish to create the macro." +FirstRunWizard.seqReview.trigger="When on %1 for at least %2:" +FirstRunWizard.seqReview.step="After %1, switch to %2" +FirstRunWizard.seqReview.errorTitle="Macro Creation Failed" +FirstRunWizard.seqReview.errorBody="The sequence macro could not be created. Please check the OBS log for details." + FirstRunWizard.done.title="You're all set!" FirstRunWizard.done.subtitle="Your first macro has been created and is now active." FirstRunWizard.done.body="

You can view and edit it on the Macros tab of the Advanced Scene Switcher dialog.

To learn more:

" diff --git a/lib/utils/first-run-wizard-sequence.cpp b/lib/utils/first-run-wizard-sequence.cpp new file mode 100644 index 00000000..0a7ed82c --- /dev/null +++ b/lib/utils/first-run-wizard-sequence.cpp @@ -0,0 +1,499 @@ +#include "first-run-wizard-sequence.hpp" +#include "first-run-wizard-helpers.hpp" + +#include "layout-helpers.hpp" +#include "log-helper.hpp" +#include "macro-settings.hpp" +#include "selection-helpers.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace advss { + +namespace wiz { + +// Builds the obs_data blob for a "current scene == sceneName for at least +// triggerDuration" condition, matching MacroConditionScene::Save(). +// +// { +// "segmentSettings": { "enabled": true, "version": 2 }, +// "id": "scene", +// "logic": 0, +// "durationModifier": { +// "time_constraint": 1, // AT_LEAST +// "seconds": +// }, +// "sceneSelection": { "type": 0, "name": "", "canvasSelection": "Main" }, +// "type": 10, // CURRENT_SCENE +// "version": 1 +// } +static OBSDataAutoRelease +BuildSceneConditionData(const QString &scene, const Duration &triggerDuration) +{ + OBSDataAutoRelease seg = obs_data_create(); + obs_data_set_bool(seg, "enabled", true); + obs_data_set_int(seg, "version", 2); + + OBSDataAutoRelease durMod = obs_data_create(); + obs_data_set_int(durMod, "time_constraint", 1); + triggerDuration.Save(durMod, "seconds"); + + OBSDataAutoRelease sceneSel = obs_data_create(); + obs_data_set_int(sceneSel, "type", 0); + obs_data_set_string(sceneSel, "name", scene.toUtf8().constData()); + obs_data_set_string(sceneSel, "canvasSelection", "Main"); + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_obj(data, "segmentSettings", seg); + obs_data_set_string(data, "id", "scene"); + obs_data_set_int(data, "logic", 0); + obs_data_set_obj(data, "durationModifier", durMod); + obs_data_set_obj(data, "sceneSelection", sceneSel); + obs_data_set_int(data, "type", 10); + obs_data_set_int(data, "version", 1); + + return data; +} + +// Builds the obs_data blob for a scene-switch action, +// matching MacroActionSwitchScene::Save(). +// +// { +// "segmentSettings": { "enabled": true, "version": 2 }, +// "id": "scene_switch", +// "action": 0, +// "sceneSelection": { "type": 0, "name": "", "canvasSelection": "Main" }, +// "transitionType": 1, // scene's default transition +// "blockUntilTransitionDone": true, +// "sceneType": 0 +// } +static OBSDataAutoRelease BuildSceneSwitchData(const QString &scene) +{ + OBSDataAutoRelease seg = obs_data_create(); + obs_data_set_bool(seg, "enabled", true); + obs_data_set_int(seg, "version", 2); + + OBSDataAutoRelease sceneSel = obs_data_create(); + obs_data_set_int(sceneSel, "type", 0); + obs_data_set_string(sceneSel, "name", scene.toUtf8().constData()); + obs_data_set_string(sceneSel, "canvasSelection", "Main"); + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_obj(data, "segmentSettings", seg); + obs_data_set_string(data, "id", "scene_switch"); + obs_data_set_int(data, "action", 0); + obs_data_set_obj(data, "sceneSelection", sceneSel); + obs_data_set_int(data, "transitionType", 1); + obs_data_set_bool(data, "blockUntilTransitionDone", true); + obs_data_set_int(data, "sceneType", 0); + + return data; +} + +// Builds the obs_data blob for a wait action, matching MacroActionWait::Save(). +// +// { +// "segmentSettings": { "enabled": true, "version": 2 }, +// "id": "wait", +// "duration": , +// "waitType": 0, +// "version": 1 +// } +static OBSDataAutoRelease BuildWaitData(const Duration &duration) +{ + OBSDataAutoRelease seg = obs_data_create(); + obs_data_set_bool(seg, "enabled", true); + obs_data_set_int(seg, "version", 2); + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_obj(data, "segmentSettings", seg); + obs_data_set_string(data, "id", "wait"); + duration.Save(data, "duration"); + obs_data_set_int(data, "waitType", 0); + obs_data_set_int(data, "version", 1); + + return data; +} + +// =========================================================================== +// SequenceTriggerPage +// =========================================================================== + +SequenceTriggerPage::SequenceTriggerPage(QWidget *parent) + : QWizardPage(parent), + _sceneCombo(new QComboBox(this)), + _delaySelection(new DurationSelection(this, true, 0.0)) +{ + setTitle(obs_module_text("FirstRunWizard.seqTrigger.title")); + setSubTitle(obs_module_text("FirstRunWizard.seqTrigger.subtitle")); + + _sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _delaySelection->SetDuration(Duration(5.0)); + + registerField("seqTriggerScene*", _sceneCombo, "currentText", + SIGNAL(currentTextChanged(QString))); + + connect(_sceneCombo, &QComboBox::currentTextChanged, this, + &QWizardPage::completeChanged); + + auto *sceneRow = new QHBoxLayout; + PlaceWidgets(obs_module_text("FirstRunWizard.seqTrigger.scene"), + sceneRow, {{"{{scene}}", _sceneCombo}}, false); + + auto *delayRow = new QHBoxLayout; + PlaceWidgets(obs_module_text("FirstRunWizard.seqTrigger.delay"), + delayRow, {{"{{duration}}", _delaySelection}}, false); + + auto *layout = new QVBoxLayout(this); + layout->addLayout(sceneRow); + layout->addLayout(delayRow); + layout->addStretch(); +} + +void SequenceTriggerPage::initializePage() +{ + _sceneCombo->clear(); + for (const QString &name : GetSceneNames()) { + _sceneCombo->addItem(name); + } +} + +bool SequenceTriggerPage::isComplete() const +{ + return _sceneCombo->count() > 0 && + !_sceneCombo->currentText().isEmpty(); +} + +Duration SequenceTriggerPage::GetTriggerDuration() const +{ + return _delaySelection->GetDuration(); +} + +// =========================================================================== +// SequenceScenesPage +// =========================================================================== + +SequenceScenesPage::SequenceScenesPage(QWidget *parent) + : QWizardPage(parent), + _triggerInfoLabel(new QLabel(this)), + _stepsContainer(new QWidget(this)), + _stepsLayout(new QVBoxLayout(_stepsContainer)) +{ + setTitle(obs_module_text("FirstRunWizard.seqScenes.title")); + setSubTitle(obs_module_text("FirstRunWizard.seqScenes.subtitle")); + + _triggerInfoLabel->setWordWrap(true); + _triggerInfoLabel->setFrameShape(QFrame::StyledPanel); + _triggerInfoLabel->setContentsMargins(8, 4, 8, 4); + + _stepsLayout->setContentsMargins(0, 0, 0, 0); + _stepsLayout->setSpacing(4); + + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidget(_stepsContainer); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + + auto *addBtn = new QPushButton( + obs_module_text("FirstRunWizard.seqScenes.addScene"), this); + connect(addBtn, &QPushButton::clicked, this, + &SequenceScenesPage::onAddStepClicked); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(_triggerInfoLabel); + layout->addWidget(scrollArea, 1); + layout->addWidget(addBtn, 0, Qt::AlignLeft); +} + +void SequenceScenesPage::initializePage() +{ + const QString triggerScene = field("seqTriggerScene").toString(); + auto *triggerPage = qobject_cast( + wizard()->page(PAGE_SEQ_TRIGGER)); + const Duration triggerDuration = + triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0); + _triggerInfoLabel->setText( + QString(obs_module_text("FirstRunWizard.seqScenes.triggerInfo")) + .arg(triggerScene) + .arg(QString::fromStdString( + triggerDuration.ToString()))); + + if (_initialized) { + return; + } + _initialized = true; + + // Pre-select the scenes after the trigger scene so the user doesn't + // have to start by deselecting it manually. + const QStringList scenes = GetSceneNames(); + const int count = static_cast(scenes.size()); + const int triggerIdx = scenes.indexOf(triggerScene); + const int firstIdx = count > 0 ? (triggerIdx + 1) % count : 0; + const int secondIdx = count > 0 ? (triggerIdx + 2) % count : 0; + AddStep(firstIdx); + AddStep(secondIdx); +} + +bool SequenceScenesPage::isComplete() const +{ + return _rows.size() >= 2; +} + +QVector> SequenceScenesPage::GetSteps() const +{ + QVector> steps; + steps.reserve(_rows.size()); + for (int i = 0; i < _rows.size(); ++i) { + const Duration delay = (i < _rows.size() - 1) + ? _rows[i].delay->GetDuration() + : Duration(); + steps.append({_rows[i].scene->currentText(), delay}); + } + return steps; +} + +void SequenceScenesPage::onAddStepClicked() +{ + const QStringList scenes = GetSceneNames(); + const int count = static_cast(scenes.size()); + int nextIdx = 0; + if (!_rows.isEmpty() && count > 0) { + const int lastIdx = _rows.last().scene->currentIndex(); + nextIdx = (lastIdx + 1) % count; + } + AddStep(nextIdx); + emit completeChanged(); +} + +void SequenceScenesPage::AddStep(int defaultSceneIndex) +{ + auto *row = new QWidget(_stepsContainer); + auto *rowLayout = new QHBoxLayout(row); + rowLayout->setContentsMargins(0, 0, 0, 0); + + auto *sceneCombo = new QComboBox(row); + sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + const QStringList scenes = GetSceneNames(); + for (const QString &name : scenes) { + sceneCombo->addItem(name); + } + if (defaultSceneIndex >= 0 && defaultSceneIndex < scenes.size()) { + sceneCombo->setCurrentIndex(defaultSceneIndex); + } + PlaceWidgets(obs_module_text("FirstRunWizard.seqScenes.switchTo"), + rowLayout, {{"{{scene}}", sceneCombo}}, false); + + auto *delayWidget = new QWidget(row); + auto *delayLayout = new QHBoxLayout(delayWidget); + delayLayout->setContentsMargins(0, 0, 0, 0); + auto *durationSel = new DurationSelection(delayWidget, true, 0.1); + durationSel->SetDuration(Duration(5.0)); + PlaceWidgets(obs_module_text("FirstRunWizard.seqScenes.thenWait"), + delayLayout, {{"{{duration}}", durationSel}}, false); + rowLayout->addWidget(delayWidget); + + auto *removeBtn = new QPushButton(row); + removeBtn->setProperty("themeID", + QVariant(QString::fromUtf8("removeIconSmall"))); + removeBtn->setProperty("class", + QVariant(QString::fromUtf8("icon-trash"))); + removeBtn->setToolTip( + obs_module_text("FirstRunWizard.seqScenes.removeTooltip")); + removeBtn->setMaximumSize(22, 22); + rowLayout->addWidget(removeBtn); + + _rows.append({row, sceneCombo, delayWidget, durationSel, removeBtn}); + _stepsLayout->addWidget(row); + + UpdateDelayVisibility(); + UpdateRemoveButtons(); + + connect(removeBtn, &QPushButton::clicked, this, + [this, row]() { RemoveStep(row); }); +} + +void SequenceScenesPage::RemoveStep(QWidget *rowWidget) +{ + int idx = -1; + for (int i = 0; i < _rows.size(); ++i) { + if (_rows[i].row == rowWidget) { + idx = i; + break; + } + } + if (idx < 0) { + return; + } + + _stepsLayout->removeWidget(_rows[idx].row); + _rows[idx].row->deleteLater(); + _rows.removeAt(idx); + + UpdateDelayVisibility(); + UpdateRemoveButtons(); + emit completeChanged(); +} + +void SequenceScenesPage::UpdateDelayVisibility() +{ + for (int i = 0; i < _rows.size(); ++i) { + _rows[i].delayWidget->setVisible(i < _rows.size() - 1); + } +} + +void SequenceScenesPage::UpdateRemoveButtons() +{ + const bool canRemove = _rows.size() > 1; + for (auto &step : _rows) { + step.remove->setEnabled(canRemove); + } +} + +// =========================================================================== +// SequenceReviewPage +// =========================================================================== + +SequenceReviewPage::SequenceReviewPage(QWidget *parent, + std::shared_ptr ¯o) + : QWizardPage(parent), + _summary(new QLabel(this)), + _macro(macro) +{ + setTitle(obs_module_text("FirstRunWizard.seqReview.title")); + setSubTitle(obs_module_text("FirstRunWizard.seqReview.subtitle")); + + detail::setupSummaryLabel(_summary); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(_summary); + layout->addStretch(); +} + +void SequenceReviewPage::initializePage() +{ + const QString triggerScene = field("seqTriggerScene").toString(); + + auto *triggerPage = qobject_cast( + wizard()->page(PAGE_SEQ_TRIGGER)); + const Duration triggerDuration = + triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0); + + auto *seqPage = qobject_cast( + wizard()->page(PAGE_SEQ_SCENES)); + const auto steps = seqPage ? seqPage->GetSteps() + : QVector>{}; + + QString html = + QString("

%1

") + .arg(QString(obs_module_text( + "FirstRunWizard.seqReview.trigger")) + .arg(triggerScene.toHtmlEscaped()) + .arg(QString::fromStdString( + triggerDuration.ToString()))); + + html += "
    "; + for (int i = 0; i < steps.size(); ++i) { + const QString scene = steps[i].first.toHtmlEscaped(); + if (i == 0) { + html += "
  1. " + scene + "
  2. "; + } else { + const QString waitStr = QString::fromStdString( + steps[i - 1].second.ToString()); + html += "
  3. " + + QString(obs_module_text( + "FirstRunWizard.seqReview.step")) + .arg(waitStr) + .arg(scene) + + "
  4. "; + } + } + html += "
"; + + _summary->setText(html); +} + +bool SequenceReviewPage::validatePage() +{ + const QString triggerScene = field("seqTriggerScene").toString(); + const std::string name = ("Sequence: " + triggerScene).toStdString(); + + auto *triggerPage = qobject_cast( + wizard()->page(PAGE_SEQ_TRIGGER)); + const Duration triggerDuration = + triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0); + + auto *seqPage = qobject_cast( + wizard()->page(PAGE_SEQ_SCENES)); + if (!seqPage) { + return true; + } + const auto steps = seqPage->GetSteps(); + + _macro = std::make_shared(name, GetGlobalMacroSettings()); + if (!_macro) { + blog(LOG_WARNING, + "FirstRunWizard: sequence macro allocation failed"); + return true; + } + _macro->SetRunInParallel(true); + + // --- Condition --- + OBSDataAutoRelease condData = + BuildSceneConditionData(triggerScene, triggerDuration); + if (!detail::addCondition(_macro.get(), "scene", condData)) { + _macro.reset(); + QMessageBox::warning( + this, + obs_module_text("FirstRunWizard.seqReview.errorTitle"), + obs_module_text("FirstRunWizard.seqReview.errorBody")); + return true; + } + + // --- Actions: scene_switch interleaved with wait --- + for (int i = 0; i < steps.size(); ++i) { + OBSDataAutoRelease switchData = + BuildSceneSwitchData(steps[i].first); + if (!detail::addAction(_macro.get(), "scene_switch", + switchData)) { + _macro.reset(); + QMessageBox::warning( + this, + obs_module_text( + "FirstRunWizard.seqReview.errorTitle"), + obs_module_text( + "FirstRunWizard.seqReview.errorBody")); + return true; + } + + const bool isLastStep = (i == steps.size() - 1); + if (!isLastStep) { + OBSDataAutoRelease waitData = + BuildWaitData(steps[i].second); + if (!detail::addAction(_macro.get(), "wait", + waitData)) { + _macro.reset(); + QMessageBox::warning( + this, + obs_module_text( + "FirstRunWizard.seqReview.errorTitle"), + obs_module_text( + "FirstRunWizard.seqReview.errorBody")); + return true; + } + } + } + + blog(LOG_INFO, "FirstRunWizard: created sequence macro '%s'", + name.c_str()); + return true; +} + +} // namespace wiz + +} // namespace advss diff --git a/lib/utils/first-run-wizard-sequence.hpp b/lib/utils/first-run-wizard-sequence.hpp new file mode 100644 index 00000000..7789e741 --- /dev/null +++ b/lib/utils/first-run-wizard-sequence.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "duration-control.hpp" +#include "duration.hpp" +#include "first-run-wizard.hpp" + +#include +#include +#include +#include +#include + +#include + +class QVBoxLayout; + +namespace advss { + +namespace wiz { + +// --------------------------------------------------------------------------- +// SequenceTriggerPage +// Registers wizard field "seqTriggerScene" (QString). +// Trigger duration is read back via GetTriggerDuration(). +// --------------------------------------------------------------------------- +class SequenceTriggerPage : public QWizardPage { + Q_OBJECT +public: + explicit SequenceTriggerPage(QWidget *parent = nullptr); + void initializePage() override; + bool isComplete() const override; + int nextId() const override { return PAGE_SEQ_SCENES; } + + Duration GetTriggerDuration() const; + +private: + QComboBox *_sceneCombo; + DurationSelection *_delaySelection; +}; + +// --------------------------------------------------------------------------- +// SequenceScenesPage +// Lets the user build an ordered list of scenes with delays between them. +// The delay after the last scene is ignored when building the macro. +// --------------------------------------------------------------------------- +struct SequenceStep { + QWidget *row; + QComboBox *scene; + QWidget *delayWidget; // hidden for the last row + DurationSelection *delay; + QPushButton *remove; +}; + +class SequenceScenesPage : public QWizardPage { + Q_OBJECT +public: + explicit SequenceScenesPage(QWidget *parent = nullptr); + void initializePage() override; + bool isComplete() const override; + int nextId() const override { return PAGE_SEQ_REVIEW; } + + // Returns (sceneName, delayAfter) pairs. The last entry's delayAfter + // is always a default-constructed Duration and must be ignored by the caller. + QVector> GetSteps() const; + +private slots: + void onAddStepClicked(); + +private: + void AddStep(int defaultSceneIndex = 0); + void RemoveStep(QWidget *rowWidget); + void UpdateDelayVisibility(); + void UpdateRemoveButtons(); + + QLabel *_triggerInfoLabel; + QWidget *_stepsContainer; + QVBoxLayout *_stepsLayout; + QVector _rows; + bool _initialized = false; +}; + +// --------------------------------------------------------------------------- +// SequenceReviewPage +// Displays a summary and builds the macro from wizard fields on Finish. +// --------------------------------------------------------------------------- +class SequenceReviewPage : public QWizardPage { + Q_OBJECT +public: + explicit SequenceReviewPage(QWidget *parent, + std::shared_ptr ¯o); + void initializePage() override; + bool validatePage() override; + int nextId() const override { return PAGE_DONE; } + +private: + QLabel *_summary; + std::shared_ptr &_macro; +}; + +} // namespace wiz +} // namespace advss diff --git a/lib/utils/first-run-wizard.cpp b/lib/utils/first-run-wizard.cpp index 3386cde6..45310c62 100644 --- a/lib/utils/first-run-wizard.cpp +++ b/lib/utils/first-run-wizard.cpp @@ -1,4 +1,5 @@ #include "first-run-wizard.hpp" +#include "first-run-wizard-sequence.hpp" #include "first-run-wizard-window.hpp" #include "macro.hpp" @@ -64,6 +65,36 @@ WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent) layout->addStretch(); } +// =========================================================================== +// TemplatePage +// =========================================================================== + +TemplatePage::TemplatePage(QWidget *parent) + : QWizardPage(parent), + _windowRadio(new QRadioButton( + obs_module_text("FirstRunWizard.template.window"), this)), + _sequenceRadio(new QRadioButton( + obs_module_text("FirstRunWizard.template.sequence"), this)) +{ + setTitle(obs_module_text("FirstRunWizard.template.title")); + setSubTitle(obs_module_text("FirstRunWizard.template.subtitle")); + + _windowRadio->setChecked(true); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(_windowRadio); + layout->addWidget(_sequenceRadio); + layout->addStretch(); +} + +int TemplatePage::nextId() const +{ + if (_sequenceRadio->isChecked()) { + return PAGE_SEQ_TRIGGER; + } + return PAGE_WINDOW_SCENE; +} + // =========================================================================== // DonePage // =========================================================================== @@ -92,12 +123,16 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QWizard(parent) { setWindowTitle(obs_module_text("FirstRunWizard.windowTitle")); setWizardStyle(QWizard::ModernStyle); - setMinimumSize(540, 420); + setMinimumSize(600, 420); setPage(PAGE_WELCOME, new WelcomePage(this)); + setPage(PAGE_TEMPLATE, new TemplatePage(this)); setPage(PAGE_WINDOW_SCENE, new WindowSceneSelectionPage(this)); setPage(PAGE_WINDOW_CONDITION, new WindowConditionPage(this)); setPage(PAGE_WINDOW_REVIEW, new WindowReviewPage(this, _macro)); + setPage(PAGE_SEQ_TRIGGER, new SequenceTriggerPage(this)); + setPage(PAGE_SEQ_SCENES, new SequenceScenesPage(this)); + setPage(PAGE_SEQ_REVIEW, new SequenceReviewPage(this, _macro)); setPage(PAGE_DONE, new DonePage(this)); setStartId(PAGE_WELCOME); diff --git a/lib/utils/first-run-wizard.hpp b/lib/utils/first-run-wizard.hpp index 457d33fc..4aba3d48 100644 --- a/lib/utils/first-run-wizard.hpp +++ b/lib/utils/first-run-wizard.hpp @@ -19,9 +19,13 @@ namespace wiz { // --------------------------------------------------------------------------- enum WizardPageId { PAGE_WELCOME = 0, + PAGE_TEMPLATE, PAGE_WINDOW_SCENE, PAGE_WINDOW_CONDITION, PAGE_WINDOW_REVIEW, + PAGE_SEQ_TRIGGER, + PAGE_SEQ_SCENES, + PAGE_SEQ_REVIEW, PAGE_DONE, }; @@ -32,7 +36,22 @@ class WelcomePage : public QWizardPage { Q_OBJECT public: explicit WelcomePage(QWidget *parent = nullptr); - int nextId() const override { return PAGE_WINDOW_SCENE; } + int nextId() const override { return PAGE_TEMPLATE; } +}; + +// --------------------------------------------------------------------------- +// TemplatePage +// Lets the user choose which kind of automation to create. +// --------------------------------------------------------------------------- +class TemplatePage : public QWizardPage { + Q_OBJECT +public: + explicit TemplatePage(QWidget *parent = nullptr); + int nextId() const override; + +private: + QRadioButton *_windowRadio; + QRadioButton *_sequenceRadio; }; // ---------------------------------------------------------------------------