mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-07-12 22:53:55 -05:00
Add "Sequence" option to the first run wizard
This commit is contained in:
parent
b3a69fdb3f
commit
19253ace4b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard creates a macro that <b>switches to a chosen OBS scene whenever a specific window comes into focus</b>.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
||||
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard will guide you through creating your first automation. On the next page, choose the type of macro you want to set up.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
||||
|
||||
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="<b>Macro name:</b> Window -> %1<br><br><b>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 <b>%1</b> for at least <b>%2</b>:"
|
||||
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="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
|
||||
|
|
|
|||
499
lib/utils/first-run-wizard-sequence.cpp
Normal file
499
lib/utils/first-run-wizard-sequence.cpp
Normal file
|
|
@ -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 <obs-data.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
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": <Duration::Save output>
|
||||
// },
|
||||
// "sceneSelection": { "type": 0, "name": "<scene>", "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": "<scene>", "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": <Duration::Save output>,
|
||||
// "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<SequenceTriggerPage *>(
|
||||
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<int>(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<QPair<QString, Duration>> SequenceScenesPage::GetSteps() const
|
||||
{
|
||||
QVector<QPair<QString, Duration>> 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<int>(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<Macro> ¯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<SequenceTriggerPage *>(
|
||||
wizard()->page(PAGE_SEQ_TRIGGER));
|
||||
const Duration triggerDuration =
|
||||
triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0);
|
||||
|
||||
auto *seqPage = qobject_cast<SequenceScenesPage *>(
|
||||
wizard()->page(PAGE_SEQ_SCENES));
|
||||
const auto steps = seqPage ? seqPage->GetSteps()
|
||||
: QVector<QPair<QString, Duration>>{};
|
||||
|
||||
QString html =
|
||||
QString("<p>%1</p>")
|
||||
.arg(QString(obs_module_text(
|
||||
"FirstRunWizard.seqReview.trigger"))
|
||||
.arg(triggerScene.toHtmlEscaped())
|
||||
.arg(QString::fromStdString(
|
||||
triggerDuration.ToString())));
|
||||
|
||||
html += "<ol>";
|
||||
for (int i = 0; i < steps.size(); ++i) {
|
||||
const QString scene = steps[i].first.toHtmlEscaped();
|
||||
if (i == 0) {
|
||||
html += "<li>" + scene + "</li>";
|
||||
} else {
|
||||
const QString waitStr = QString::fromStdString(
|
||||
steps[i - 1].second.ToString());
|
||||
html += "<li>" +
|
||||
QString(obs_module_text(
|
||||
"FirstRunWizard.seqReview.step"))
|
||||
.arg(waitStr)
|
||||
.arg(scene) +
|
||||
"</li>";
|
||||
}
|
||||
}
|
||||
html += "</ol>";
|
||||
|
||||
_summary->setText(html);
|
||||
}
|
||||
|
||||
bool SequenceReviewPage::validatePage()
|
||||
{
|
||||
const QString triggerScene = field("seqTriggerScene").toString();
|
||||
const std::string name = ("Sequence: " + triggerScene).toStdString();
|
||||
|
||||
auto *triggerPage = qobject_cast<SequenceTriggerPage *>(
|
||||
wizard()->page(PAGE_SEQ_TRIGGER));
|
||||
const Duration triggerDuration =
|
||||
triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0);
|
||||
|
||||
auto *seqPage = qobject_cast<SequenceScenesPage *>(
|
||||
wizard()->page(PAGE_SEQ_SCENES));
|
||||
if (!seqPage) {
|
||||
return true;
|
||||
}
|
||||
const auto steps = seqPage->GetSteps();
|
||||
|
||||
_macro = std::make_shared<Macro>(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
|
||||
101
lib/utils/first-run-wizard-sequence.hpp
Normal file
101
lib/utils/first-run-wizard-sequence.hpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include "duration-control.hpp"
|
||||
#include "duration.hpp"
|
||||
#include "first-run-wizard.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<QPair<QString, Duration>> 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<SequenceStep> _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<advss::Macro> ¯o);
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
int nextId() const override { return PAGE_DONE; }
|
||||
|
||||
private:
|
||||
QLabel *_summary;
|
||||
std::shared_ptr<advss::Macro> &_macro;
|
||||
};
|
||||
|
||||
} // namespace wiz
|
||||
} // namespace advss
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user