Add variable action options

* Current scene
* Previous scene
* Preview scene
* Current transition
* Date / time
* Date / time with offset
* Unix timestamp
* Time since last scene change
* Json array length
* Set json field
* Format number
This commit is contained in:
WarmUpTill
2026-10-19 20:29:53 +02:00
parent cc051753aa
commit ff5a4796a8
12 changed files with 553 additions and 2 deletions

View File

@@ -129,6 +129,8 @@ target_sources(
lib/macro/macro-action-variable-scene.hpp
lib/macro/macro-action-variable-string.cpp
lib/macro/macro-action-variable-string.hpp
lib/macro/macro-action-variable-time.cpp
lib/macro/macro-action-variable-time.hpp
lib/macro/macro-action-variable.cpp
lib/macro/macro-action-variable.hpp
lib/macro/macro-action.cpp

View File

@@ -1382,6 +1382,15 @@ AdvSceneSwitcher.action.variable.type.mathExpression="Mathematical expression"
AdvSceneSwitcher.action.variable.type.askForValue="Get user input"
AdvSceneSwitcher.action.variable.type.environmentVariable="Set to environment variable value"
AdvSceneSwitcher.action.variable.type.sceneItemCount="Set to scene item count of scene"
AdvSceneSwitcher.action.variable.type.currentScene="Set to current scene name"
AdvSceneSwitcher.action.variable.type.previousScene="Set to previous scene name"
AdvSceneSwitcher.action.variable.type.previewScene="Set to preview scene name"
AdvSceneSwitcher.action.variable.type.currentTransition="Set to current transition name"
AdvSceneSwitcher.action.variable.type.currentDateTime="Set to current date and time"
AdvSceneSwitcher.action.variable.type.dateTimeWithOffset="Set to date and time with offset"
AdvSceneSwitcher.action.variable.type.unixTimestamp="Set to Unix timestamp"
AdvSceneSwitcher.action.variable.type.timeSinceSceneChange="Set to time since last scene change"
AdvSceneSwitcher.action.variable.type.currentDateTime.help="Uses the Qt date format syntax.\nFor example:\n\n • yyyy-MM-dd -> 2026-10-19\n • dd.MM.yyyy hh:mm:ss -> 19.10.2026 14:05:09\n • dddd, MMMM d -> Monday, October 19\n\nText in single quotes is copied as is."
AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string"
AdvSceneSwitcher.action.variable.type.extractJsonField="Extract JSON field with name"
AdvSceneSwitcher.action.variable.type.queryJson="Query JSON"
@@ -1389,6 +1398,9 @@ AdvSceneSwitcher.action.variable.type.queryJson.info="You can use \"JSONPath\" (
AdvSceneSwitcher.action.variable.type.queryJson.extractSingle="Extract value if single result"
AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info="When enabled and the query returns exactly one result, the value is extracted from the result array and stored directly.\nIf the query matches zero or more than one element, the full JSON array is stored as usual."
AdvSceneSwitcher.action.variable.type.accessJsonArray="Access JSON array at index"
AdvSceneSwitcher.action.variable.type.jsonArrayLength="Get length of JSON array"
AdvSceneSwitcher.action.variable.type.setJsonField="Set JSON field"
AdvSceneSwitcher.action.variable.type.formatNumber="Format number"
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable."
AdvSceneSwitcher.action.variable.type.sceneItemName="Set to scene item name at index"
@@ -1425,6 +1437,7 @@ AdvSceneSwitcher.action.variable.layout.variable2="{{actions}}{{variables}}{{var
AdvSceneSwitcher.action.variable.layout.changeCase="{{actions}}{{variables}}{{caseType}}"
AdvSceneSwitcher.action.variable.layout.queryJson="{{actions}}{{variables}}{{jsonQuery}}{{jsonQueryHelp}}"
AdvSceneSwitcher.action.variable.layout.accessJsonArray="{{actions}}{{variables}}{{jsonIndex}}"
AdvSceneSwitcher.action.variable.layout.jsonArrayLength="{{actions}}{{variables2}}and store it in{{variables}}"
AdvSceneSwitcher.action.variable.layout.setToTempvar="{{actions}}{{variables}}{{tempVars}}{{tempVarsHelp}}"
AdvSceneSwitcher.action.variable.layout.segmentValue="{{actions}}{{variables}}{{segmentIndex}}"
AdvSceneSwitcher.action.variable.layout.mathExpression="{{actions}}{{variables}}{{mathExpression}}"
@@ -1432,6 +1445,11 @@ AdvSceneSwitcher.action.variable.layout.numValue="{{actions}}{{variables}}{{numV
AdvSceneSwitcher.action.variable.layout.environmentVariable="{{actions}}{{variables}}{{envVariableName}}"
AdvSceneSwitcher.action.variable.layout.sceneItemName="{{actions}}{{variables}}{{scenes}}{{sceneItemIndex}}"
AdvSceneSwitcher.action.variable.layout.sceneItemCount="{{actions}}{{variables}}{{scenes}}"
AdvSceneSwitcher.action.variable.layout.currentDateTime="{{actions}}{{variables}}using format{{dateFormat}}{{dateFormatHelp}}"
AdvSceneSwitcher.action.variable.layout.dateTimeWithOffset="{{actions}}{{variables}}shifted by{{dateOffset}}{{dateOffsetUnit}}using format{{dateFormat}}{{dateFormatHelp}}"
AdvSceneSwitcher.action.variable.layout.timeUnit="{{actions}}{{variables}}in{{timeUnit}}"
AdvSceneSwitcher.action.variable.layout.setJsonField="{{actions}}{{strValue}}of{{variables}}to{{jsonValue}}"
AdvSceneSwitcher.action.variable.layout.formatNumber="{{actions}}{{variables}}with{{decimalPlaces}}decimal places{{groupDigits}}use thousands separator"
AdvSceneSwitcher.action.variable.layout.copy="{{actions}}{{variables2}}to{{variables}}"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"

View File

@@ -17,6 +17,12 @@ static bool registered = RegisterVariableActionGroup(
{Action::ARRAY_JSON,
"AdvSceneSwitcher.action.variable.type.accessJsonArray",
"AdvSceneSwitcher.action.variable.layout.accessJsonArray"},
{Action::JSON_ARRAY_LENGTH,
"AdvSceneSwitcher.action.variable.type.jsonArrayLength",
"AdvSceneSwitcher.action.variable.layout.jsonArrayLength"},
{Action::SET_JSON_FIELD,
"AdvSceneSwitcher.action.variable.type.setJsonField",
"AdvSceneSwitcher.action.variable.layout.setJsonField", true},
},
[]() { return std::make_unique<VariableActionJson>(); },
VariableActionJsonEdit::Create});
@@ -33,6 +39,7 @@ void VariableActionJson::Save(obs_data_t *obj,
_jsonIndex.Save(obj, "jsonIndex");
obs_data_set_bool(obj, "jsonQueryExtractSingle",
_jsonQueryExtractSingle);
_jsonValue.Save(obj, "jsonValue");
}
void VariableActionJson::Load(obs_data_t *obj, MacroActionVariable &)
@@ -41,12 +48,16 @@ void VariableActionJson::Load(obs_data_t *obj, MacroActionVariable &)
_jsonIndex.Load(obj, "jsonIndex");
_jsonQueryExtractSingle =
obs_data_get_bool(obj, "jsonQueryExtractSingle");
if (obs_data_has_user_value(obj, "jsonValue")) {
_jsonValue.Load(obj, "jsonValue");
}
}
void VariableActionJson::ResolveVariables()
{
_jsonQuery.ResolveVariables();
_jsonIndex.ResolveVariables();
_jsonValue.ResolveVariables();
}
bool VariableActionJson::Perform(MacroActionVariable &action,
@@ -89,6 +100,27 @@ bool VariableActionJson::Perform(MacroActionVariable &action,
var.SetValue(*value);
return true;
}
case Action::JSON_ARRAY_LENGTH: {
auto input = action._variable2.lock();
if (!input) {
return true;
}
auto length = GetJsonArrayLength(input->Value());
if (!length.has_value()) {
return true;
}
var.SetValue(*length);
return true;
}
case Action::SET_JSON_FIELD: {
auto value =
SetJsonField(var.Value(), action._strValue, _jsonValue);
if (!value.has_value()) {
return true;
}
var.SetValue(*value);
return true;
}
default:
return true;
}
@@ -112,7 +144,8 @@ VariableActionJsonEdit::VariableActionJsonEdit(
_jsonExtractSingleHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info"),
this))
this)),
_jsonValue(new VariableTextEdit(this, 5, 1, 1))
{
_jsonIndex->setMaximum(999);
@@ -124,6 +157,8 @@ VariableActionJsonEdit::VariableActionJsonEdit(
this, SLOT(JsonIndexChanged(const NumberVariable<int> &)));
QWidget::connect(_jsonExtractSingle, SIGNAL(stateChanged(int)), this,
SLOT(JsonQueryExtractSingleChanged(int)));
QWidget::connect(_jsonValue, SIGNAL(textChanged()), this,
SLOT(JsonValueChanged()));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
@@ -147,6 +182,7 @@ VariableActionJsonEdit::GetInlineWidgets()
{"{{jsonQuery}}", _jsonQuery},
{"{{jsonQueryHelp}}", _jsonQueryHelp},
{"{{jsonIndex}}", _jsonIndex},
{"{{jsonValue}}", _jsonValue},
};
}
@@ -159,6 +195,7 @@ void VariableActionJsonEdit::UpdateEntryData()
_jsonQuery->setText(_data->_jsonQuery);
_jsonIndex->SetValue(_data->_jsonIndex);
_jsonExtractSingle->setChecked(_data->_jsonQueryExtractSingle);
_jsonValue->setPlainText(_data->_jsonValue);
_loading = false;
}
@@ -186,4 +223,11 @@ void VariableActionJsonEdit::JsonQueryExtractSingleChanged(int value)
_data->_jsonQueryExtractSingle = value;
}
void VariableActionJsonEdit::JsonValueChanged()
{
GUARD_LOADING_AND_LOCK();
_data->_jsonValue = _jsonValue->toPlainText().toStdString();
UpdateSize();
}
} // namespace advss

View File

@@ -18,6 +18,7 @@ public:
StringVariable _jsonQuery = "$.some.nested.value";
IntVariable _jsonIndex = 0;
bool _jsonQueryExtractSingle = true;
StringVariable _jsonValue = "value";
};
class VariableActionJsonEdit : public VariableActionGroupEdit {
@@ -36,6 +37,7 @@ private slots:
void JsonQueryChanged();
void JsonIndexChanged(const NumberVariable<int> &);
void JsonQueryExtractSingleChanged(int);
void JsonValueChanged();
private:
VariableActionJson *_data;
@@ -45,6 +47,7 @@ private:
VariableSpinBox *_jsonIndex;
QCheckBox *_jsonExtractSingle;
HelpIcon *_jsonExtractSingleHelp;
VariableTextEdit *_jsonValue;
};
} // namespace advss

View File

@@ -3,6 +3,8 @@
#include "log-helper.hpp"
#include "math-helpers.hpp"
#include <QLocale>
#include <random>
namespace advss {
@@ -24,6 +26,9 @@ static bool registered = RegisterVariableActionGroup(
"AdvSceneSwitcher.action.variable.layout.numValue"},
{Action::ROUND_TO_INT,
"AdvSceneSwitcher.action.variable.type.roundToInt"},
{Action::FORMAT_NUMBER,
"AdvSceneSwitcher.action.variable.type.formatNumber",
"AdvSceneSwitcher.action.variable.layout.formatNumber"},
{Action::RANDOM_NUMBER,
"AdvSceneSwitcher.action.variable.type.randomNumber"},
{Action::RANDOM_LIST_VALUE,
@@ -61,6 +66,8 @@ void VariableActionMath::Save(obs_data_t *obj,
obs_data_set_bool(obj, "generateInteger", _generateInteger);
_randomValues.Save(obj, "randomValues", "value");
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
_decimalPlaces.Save(obj, "decimalPlaces");
obs_data_set_bool(obj, "groupDigits", _groupDigits);
}
void VariableActionMath::Load(obs_data_t *obj, MacroActionVariable &)
@@ -80,11 +87,31 @@ void VariableActionMath::Load(obs_data_t *obj, MacroActionVariable &)
_generateInteger = obs_data_get_bool(obj, "generateInteger");
_randomValues.Load(obj, "randomValues", "value");
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
if (obs_data_has_user_value(obj, "decimalPlaces")) {
_decimalPlaces.Load(obj, "decimalPlaces");
}
_groupDigits = obs_data_get_bool(obj, "groupDigits");
}
void VariableActionMath::ResolveVariables()
{
_mathExpression.ResolveVariables();
_decimalPlaces.ResolveVariables();
}
void VariableActionMath::FormatNumber(Variable &var)
{
auto value = var.DoubleValue();
if (!value.has_value()) {
return;
}
// C locale to keep the output parsable by other variable actions
auto locale = QLocale::c();
locale.setNumberOptions(_groupDigits ? QLocale::NumberOptions()
: QLocale::OmitGroupSeparator);
var.SetValue(locale.toString(*value, 'f', _decimalPlaces.GetValue())
.toStdString());
}
void VariableActionMath::HandleMathExpression(Variable &var)
@@ -177,6 +204,9 @@ bool VariableActionMath::Perform(MacroActionVariable &,
var.SetValue(std::to_string(int(std::round(*curValue))));
break;
}
case Action::FORMAT_NUMBER:
FormatNumber(var);
break;
case Action::RANDOM_NUMBER:
GenerateRandomNumber(var);
break;
@@ -209,8 +239,12 @@ VariableActionMathEdit::VariableActionMathEdit(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.allowRepeat"),
this)),
_randomValueLayout(new QVBoxLayout())
_randomValueLayout(new QVBoxLayout()),
_decimalPlaces(new VariableSpinBox(this)),
_groupDigits(new QCheckBox(this))
{
_decimalPlaces->setMinimum(0);
_decimalPlaces->setMaximum(15);
_numValue->setMinimum(-9999999999);
_numValue->setMaximum(9999999999);
_randomNumberStart->setMinimum(-9999999999);
@@ -242,6 +276,12 @@ VariableActionMathEdit::VariableActionMathEdit(
SLOT(RandomValueListChanged(const StringList &)));
QWidget::connect(_allowRepeatValues, SIGNAL(stateChanged(int)), this,
SLOT(AllowRepeatValuesChanged(int)));
QWidget::connect(
_decimalPlaces,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(DecimalPlacesChanged(const NumberVariable<int> &)));
QWidget::connect(_groupDigits, SIGNAL(stateChanged(int)), this,
SLOT(GroupDigitsChanged(int)));
auto randomLayout = new QHBoxLayout();
PlaceWidgets(
@@ -277,6 +317,8 @@ VariableActionMathEdit::GetInlineWidgets()
return {
{"{{numValue}}", _numValue},
{"{{mathExpression}}", _mathExpression},
{"{{decimalPlaces}}", _decimalPlaces},
{"{{groupDigits}}", _groupDigits},
};
}
@@ -293,6 +335,8 @@ void VariableActionMathEdit::UpdateEntryData()
_generateInteger->setChecked(_data->_generateInteger);
_allowRepeatValues->setChecked(_data->_allowRepeatValues);
_randomValues->SetStringList(_data->_randomValues);
_decimalPlaces->SetValue(_data->_decimalPlaces);
_groupDigits->setChecked(_data->_groupDigits);
_loading = false;
}
@@ -363,4 +407,17 @@ void VariableActionMathEdit::AllowRepeatValuesChanged(int value)
_data->_allowRepeatValues = value;
}
void VariableActionMathEdit::DecimalPlacesChanged(
const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_data->_decimalPlaces = value;
}
void VariableActionMathEdit::GroupDigitsChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_data->_groupDigits = value;
}
} // namespace advss

View File

@@ -25,9 +25,12 @@ public:
bool _generateInteger = true;
StringList _randomValues = {"value1", "value2", "value3"};
bool _allowRepeatValues = true;
IntVariable _decimalPlaces = 2;
bool _groupDigits = false;
private:
void HandleMathExpression(Variable &);
void FormatNumber(Variable &);
void GenerateRandomNumber(Variable &);
void PickRandomValue(Variable &);
@@ -54,6 +57,8 @@ private slots:
void GenerateIntegerChanged(int);
void RandomValueListChanged(const StringList &);
void AllowRepeatValuesChanged(int);
void DecimalPlacesChanged(const NumberVariable<int> &);
void GroupDigitsChanged(int);
private:
VariableActionMath *_data;
@@ -68,6 +73,8 @@ private:
StringListEdit *_randomValues;
QCheckBox *_allowRepeatValues;
QVBoxLayout *_randomValueLayout;
VariableSpinBox *_decimalPlaces;
QCheckBox *_groupDigits;
};
} // namespace advss

View File

@@ -1,6 +1,9 @@
#include "macro-action-variable-scene.hpp"
#include "scene-switch-helpers.hpp"
#include "source-helpers.hpp"
#include <obs-frontend-api.h>
namespace advss {
using Action = MacroActionVariable::Action;
@@ -8,6 +11,14 @@ using Action = MacroActionVariable::Action;
static bool registered = RegisterVariableActionGroup(
{60,
{
{Action::CURRENT_SCENE,
"AdvSceneSwitcher.action.variable.type.currentScene"},
{Action::PREVIOUS_SCENE,
"AdvSceneSwitcher.action.variable.type.previousScene"},
{Action::PREVIEW_SCENE,
"AdvSceneSwitcher.action.variable.type.previewScene"},
{Action::CURRENT_TRANSITION,
"AdvSceneSwitcher.action.variable.type.currentTransition"},
{Action::SCENE_ITEM_NAME,
"AdvSceneSwitcher.action.variable.type.sceneItemName",
"AdvSceneSwitcher.action.variable.layout.sceneItemName"},
@@ -96,6 +107,25 @@ bool VariableActionScene::Perform(MacroActionVariable &,
case Action::SCENE_ITEM_COUNT:
var.SetValue(GetSceneItemCount(_scene.GetScene(false)));
break;
case Action::CURRENT_SCENE:
var.SetValue(GetWeakSourceName(GetCurrentScene()));
break;
case Action::PREVIOUS_SCENE:
var.SetValue(GetWeakSourceName(GetPreviousScene()));
break;
case Action::PREVIEW_SCENE: {
// Only available in studio mode
OBSSourceAutoRelease scene =
obs_frontend_get_current_preview_scene();
var.SetValue(scene ? obs_source_get_name(scene) : "");
break;
}
case Action::CURRENT_TRANSITION: {
OBSSourceAutoRelease transition =
obs_frontend_get_current_transition();
var.SetValue(transition ? obs_source_get_name(transition) : "");
break;
}
default:
break;
}

View File

@@ -0,0 +1,268 @@
#include "macro-action-variable-time.hpp"
#include "scene-switch-helpers.hpp"
#include <QDateTime>
namespace advss {
using Action = MacroActionVariable::Action;
static bool registered = RegisterVariableActionGroup(
{70,
{
{Action::CURRENT_DATE_TIME,
"AdvSceneSwitcher.action.variable.type.currentDateTime",
"AdvSceneSwitcher.action.variable.layout.currentDateTime",
true},
{Action::DATE_TIME_WITH_OFFSET,
"AdvSceneSwitcher.action.variable.type.dateTimeWithOffset",
"AdvSceneSwitcher.action.variable.layout.dateTimeWithOffset",
true},
{Action::UNIX_TIMESTAMP,
"AdvSceneSwitcher.action.variable.type.unixTimestamp",
"AdvSceneSwitcher.action.variable.layout.timeUnit"},
{Action::TIME_SINCE_SCENE_CHANGE,
"AdvSceneSwitcher.action.variable.type.timeSinceSceneChange",
"AdvSceneSwitcher.action.variable.layout.timeUnit"},
},
[]() { return std::make_unique<VariableActionTime>(); },
VariableActionTimeEdit::Create});
std::unique_ptr<VariableActionGroup> VariableActionTime::Copy() const
{
return std::make_unique<VariableActionTime>(*this);
}
void VariableActionTime::Save(obs_data_t *obj,
const MacroActionVariable &) const
{
_dateFormat.Save(obj, "dateFormat");
obs_data_set_int(obj, "timeUnit", static_cast<int>(_timeUnit));
_dateOffset.Save(obj, "dateOffset");
obs_data_set_int(obj, "dateOffsetUnit",
static_cast<int>(_dateOffsetUnit));
}
void VariableActionTime::Load(obs_data_t *obj, MacroActionVariable &)
{
if (obs_data_has_user_value(obj, "dateFormat")) {
_dateFormat.Load(obj, "dateFormat");
}
_timeUnit = static_cast<TimeUnit>(obs_data_get_int(obj, "timeUnit"));
if (obs_data_has_user_value(obj, "dateOffset")) {
_dateOffset.Load(obj, "dateOffset");
}
obs_data_set_default_int(obj, "dateOffsetUnit",
static_cast<int>(OffsetUnit::DAYS));
_dateOffsetUnit = static_cast<OffsetUnit>(
obs_data_get_int(obj, "dateOffsetUnit"));
}
void VariableActionTime::ResolveVariables()
{
_dateFormat.ResolveVariables();
_dateOffset.ResolveVariables();
}
void VariableActionTime::SetToTimeSinceSceneChange(Variable &var) const
{
const auto lastChange = GetLastSceneChangeTime();
// Default constructed until the first scene change was observed
if (lastChange == std::chrono::high_resolution_clock::time_point{}) {
return;
}
const auto elapsed =
std::chrono::high_resolution_clock::now() - lastChange;
if (_timeUnit == TimeUnit::MILLISECONDS) {
var.SetValue(std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(
elapsed)
.count()));
return;
}
var.SetValue(std::to_string(
std::chrono::duration_cast<std::chrono::seconds>(elapsed)
.count()));
}
QDateTime VariableActionTime::GetDateTimeWithOffset() const
{
const auto now = QDateTime::currentDateTime();
const int offset = _dateOffset.GetValue();
switch (_dateOffsetUnit) {
case OffsetUnit::SECONDS:
return now.addSecs(offset);
case OffsetUnit::MINUTES:
return now.addSecs(static_cast<qint64>(offset) * 60);
case OffsetUnit::HOURS:
return now.addSecs(static_cast<qint64>(offset) * 60 * 60);
case OffsetUnit::DAYS:
return now.addDays(offset);
case OffsetUnit::WEEKS:
return now.addDays(static_cast<qint64>(offset) * 7);
case OffsetUnit::MONTHS:
return now.addMonths(offset);
case OffsetUnit::YEARS:
return now.addYears(offset);
}
return now;
}
bool VariableActionTime::Perform(MacroActionVariable &,
MacroActionVariable::Action type,
Variable &var)
{
const auto format = QString::fromStdString(_dateFormat);
switch (type) {
case Action::CURRENT_DATE_TIME:
var.SetValue(QDateTime::currentDateTime()
.toString(format)
.toStdString());
break;
case Action::DATE_TIME_WITH_OFFSET:
var.SetValue(
GetDateTimeWithOffset().toString(format).toStdString());
break;
case Action::UNIX_TIMESTAMP: {
const auto now = QDateTime::currentDateTime();
const auto timestamp = _timeUnit == TimeUnit::MILLISECONDS
? now.toMSecsSinceEpoch()
: now.toSecsSinceEpoch();
var.SetValue(std::to_string(timestamp));
break;
}
case Action::TIME_SINCE_SCENE_CHANGE:
SetToTimeSinceSceneChange(var);
break;
default:
break;
}
return true;
}
static void populateTimeUnitSelection(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.unit.seconds"),
static_cast<int>(VariableActionTime::TimeUnit::SECONDS));
list->addItem(
obs_module_text("AdvSceneSwitcher.unit.milliseconds"),
static_cast<int>(VariableActionTime::TimeUnit::MILLISECONDS));
}
static void populateOffsetUnitSelection(QComboBox *list)
{
using Unit = VariableActionTime::OffsetUnit;
const std::vector<std::pair<Unit, const char *>> units = {
{Unit::SECONDS, "AdvSceneSwitcher.unit.seconds"},
{Unit::MINUTES, "AdvSceneSwitcher.unit.minutes"},
{Unit::HOURS, "AdvSceneSwitcher.unit.hours"},
{Unit::DAYS, "AdvSceneSwitcher.unit.days"},
{Unit::WEEKS, "AdvSceneSwitcher.unit.weeks"},
{Unit::MONTHS, "AdvSceneSwitcher.unit.months"},
{Unit::YEARS, "AdvSceneSwitcher.unit.years"},
};
for (const auto &[unit, name] : units) {
list->addItem(obs_module_text(name), static_cast<int>(unit));
}
}
VariableActionTimeEdit::VariableActionTimeEdit(
QWidget *parent, std::shared_ptr<MacroActionVariable> entryData)
: VariableActionGroupEdit(parent, entryData),
_data(entryData ? entryData->GetGroup<VariableActionTime>()
: nullptr),
_dateFormat(new VariableLineEdit(this)),
_dateFormatHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.currentDateTime.help"),
this)),
_timeUnit(new QComboBox(this)),
_dateOffset(new VariableSpinBox(this)),
_dateOffsetUnit(new QComboBox(this))
{
_dateOffset->setMinimum(-99999);
_dateOffset->setMaximum(99999);
populateTimeUnitSelection(_timeUnit);
populateOffsetUnitSelection(_dateOffsetUnit);
QWidget::connect(_dateFormat, SIGNAL(editingFinished()), this,
SLOT(DateFormatChanged()));
QWidget::connect(_timeUnit, SIGNAL(currentIndexChanged(int)), this,
SLOT(TimeUnitChanged(int)));
QWidget::connect(
_dateOffset,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(DateOffsetChanged(const NumberVariable<int> &)));
QWidget::connect(_dateOffsetUnit, SIGNAL(currentIndexChanged(int)),
this, SLOT(DateOffsetUnitChanged(int)));
}
VariableActionGroupEdit *
VariableActionTimeEdit::Create(QWidget *parent,
std::shared_ptr<MacroActionVariable> entryData)
{
return new VariableActionTimeEdit(parent, entryData);
}
std::unordered_map<std::string, QWidget *>
VariableActionTimeEdit::GetInlineWidgets()
{
return {
{"{{dateFormat}}", _dateFormat},
{"{{dateFormatHelp}}", _dateFormatHelp},
{"{{timeUnit}}", _timeUnit},
{"{{dateOffset}}", _dateOffset},
{"{{dateOffsetUnit}}", _dateOffsetUnit},
};
}
void VariableActionTimeEdit::UpdateEntryData()
{
if (!_data) {
return;
}
_dateFormat->setText(_data->_dateFormat);
_timeUnit->setCurrentIndex(
_timeUnit->findData(static_cast<int>(_data->_timeUnit)));
_dateOffset->SetValue(_data->_dateOffset);
_dateOffsetUnit->setCurrentIndex(_dateOffsetUnit->findData(
static_cast<int>(_data->_dateOffsetUnit)));
_loading = false;
}
void VariableActionTimeEdit::SetWidgetVisibility(MacroActionVariable::Action)
{
hide();
}
void VariableActionTimeEdit::DateFormatChanged()
{
GUARD_LOADING_AND_LOCK();
_data->_dateFormat = _dateFormat->text().toStdString();
}
void VariableActionTimeEdit::TimeUnitChanged(int)
{
GUARD_LOADING_AND_LOCK();
_data->_timeUnit = static_cast<VariableActionTime::TimeUnit>(
_timeUnit->currentData().toInt());
}
void VariableActionTimeEdit::DateOffsetChanged(const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_data->_dateOffset = value;
}
void VariableActionTimeEdit::DateOffsetUnitChanged(int)
{
GUARD_LOADING_AND_LOCK();
_data->_dateOffsetUnit = static_cast<VariableActionTime::OffsetUnit>(
_dateOffsetUnit->currentData().toInt());
}
} // namespace advss

View File

@@ -0,0 +1,69 @@
#pragma once
#include "macro-action-variable-group.hpp"
#include "help-icon.hpp"
#include "variable-line-edit.hpp"
#include "variable-spinbox.hpp"
#include <QDateTime>
namespace advss {
class VariableActionTime : public VariableActionGroup {
public:
std::unique_ptr<VariableActionGroup> Copy() const override;
void Save(obs_data_t *, const MacroActionVariable &) const override;
void Load(obs_data_t *, MacroActionVariable &) override;
void ResolveVariables() override;
bool Perform(MacroActionVariable &, MacroActionVariable::Action,
Variable &) override;
enum class TimeUnit { SECONDS, MILLISECONDS };
enum class OffsetUnit {
SECONDS,
MINUTES,
HOURS,
DAYS,
WEEKS,
MONTHS,
YEARS,
};
StringVariable _dateFormat = "yyyy-MM-dd hh:mm:ss";
TimeUnit _timeUnit = TimeUnit::SECONDS;
IntVariable _dateOffset = 1;
OffsetUnit _dateOffsetUnit = OffsetUnit::DAYS;
private:
void SetToTimeSinceSceneChange(Variable &) const;
QDateTime GetDateTimeWithOffset() const;
};
class VariableActionTimeEdit : public VariableActionGroupEdit {
Q_OBJECT
public:
VariableActionTimeEdit(QWidget *parent,
std::shared_ptr<MacroActionVariable> entryData);
static VariableActionGroupEdit *
Create(QWidget *parent, std::shared_ptr<MacroActionVariable> entryData);
std::unordered_map<std::string, QWidget *> GetInlineWidgets() override;
void UpdateEntryData() override;
void SetWidgetVisibility(MacroActionVariable::Action) override;
private slots:
void DateFormatChanged();
void TimeUnitChanged(int);
void DateOffsetChanged(const NumberVariable<int> &);
void DateOffsetUnitChanged(int);
private:
VariableActionTime *_data;
VariableLineEdit *_dateFormat;
HelpIcon *_dateFormatHelp;
QComboBox *_timeUnit;
VariableSpinBox *_dateOffset;
QComboBox *_dateOffsetUnit;
};
} // namespace advss

View File

@@ -67,6 +67,17 @@ public:
ARRAY_JSON,
COPY_VAR,
RANDOM_LIST_VALUE,
CURRENT_SCENE,
PREVIOUS_SCENE,
PREVIEW_SCENE,
CURRENT_TRANSITION,
CURRENT_DATE_TIME,
DATE_TIME_WITH_OFFSET,
UNIX_TIMESTAMP,
TIME_SINCE_SCENE_CHANGE,
JSON_ARRAY_LENGTH,
SET_JSON_FIELD,
FORMAT_NUMBER,
};
Action _action = Action::SET_VALUE;

View File

@@ -121,6 +121,44 @@ ExtractSingleJsonArrayElement(const std::string &jsonStr)
return {};
}
std::optional<int> GetJsonArrayLength(const std::string &jsonStr)
{
try {
nlohmann::json json = nlohmann::json::parse(jsonStr);
if (!json.is_array()) {
return {};
}
return static_cast<int>(json.size());
} catch (const nlohmann::json::exception &) {
return {};
}
}
std::optional<std::string> SetJsonField(const std::string &jsonStr,
const std::string &id,
const std::string &value)
{
try {
auto json = jsonStr.empty() ? nlohmann::json::object()
: nlohmann::json::parse(jsonStr);
if (!json.is_object()) {
return {};
}
// Store valid JSON values (numbers, objects, ...) as is and
// everything else as a plain string
auto parsedValue = nlohmann::json::parse(value, nullptr, false);
if (parsedValue.is_discarded()) {
json[id] = value;
} else {
json[id] = parsedValue;
}
return json.dump();
} catch (const nlohmann::json::exception &) {
return {};
}
}
static void collectStrings(const nlohmann::json &json,
std::vector<std::pair<std::string, int>> &result,
std::unordered_map<std::string, size_t> &index)

View File

@@ -21,6 +21,10 @@ EXPORT std::optional<std::string> AccessJsonArrayIndex(const std::string &json,
const int index);
EXPORT std::optional<std::string>
ExtractSingleJsonArrayElement(const std::string &json);
EXPORT std::optional<int> GetJsonArrayLength(const std::string &json);
EXPORT std::optional<std::string> SetJsonField(const std::string &json,
const std::string &id,
const std::string &value);
EXPORT std::vector<std::pair<std::string, int>>
CollectDistinctJsonStringValues(const std::string &json);