diff --git a/CMakeLists.txt b/CMakeLists.txt index f3da0a71..437e7f0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,9 @@ target_sources( # Maro sources target_sources( ${LIB_NAME} - PRIVATE src/macro-core/macro-action-audio.cpp + PRIVATE src/macro-core/action-queue.cpp + src/macro-core/action-queue.hpp + src/macro-core/macro-action-audio.cpp src/macro-core/macro-action-audio.hpp src/macro-core/macro-action-clipboard.cpp src/macro-core/macro-action-clipboard.hpp @@ -115,6 +117,8 @@ target_sources( src/macro-core/macro-action-profile.hpp src/macro-core/macro-action-projector.cpp src/macro-core/macro-action-projector.hpp + src/macro-core/macro-action-queue.cpp + src/macro-core/macro-action-queue.hpp src/macro-core/macro-action-random.cpp src/macro-core/macro-action-random.hpp src/macro-core/macro-action-recording.cpp @@ -193,6 +197,8 @@ target_sources( src/macro-core/macro-condition-process.hpp src/macro-core/macro-condition-profile.cpp src/macro-core/macro-condition-profile.hpp + src/macro-core/macro-condition-queue.cpp + src/macro-core/macro-condition-queue.hpp src/macro-core/macro-condition-recording.cpp src/macro-core/macro-condition-recording.hpp src/macro-core/macro-condition-replay-buffer.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 40371f9d..5426a81f 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -557,6 +557,12 @@ AdvSceneSwitcher.condition.twitch.entry="Channel{{channel}}{{conditions}}{{point AdvSceneSwitcher.condition.twitch.entry.account="Check using account{{account}}" AdvSceneSwitcher.condition.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" AdvSceneSwitcher.condition.twitch.title.title="Enter title" +AdvSceneSwitcher.condition.queue="Queue" +AdvSceneSwitcher.condition.queue.type.started="Started" +AdvSceneSwitcher.condition.queue.type.stopped="Stopped" +AdvSceneSwitcher.condition.queue.type.size="Size" +AdvSceneSwitcher.condition.queue.entry.startStop="Queue{{queues}}is{{conditions}}" +AdvSceneSwitcher.condition.queue.entry.size="Queue{{queues}}{{conditions}}is less than{{size}}" ; Macro Actions AdvSceneSwitcher.action.scene="Switch scene" @@ -897,6 +903,13 @@ AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image" AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder="Enter text" AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder="Enter direct image URL" AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip="Currently supported formats: PNG, JPG/JPEG, BMP, GIF." +AdvSceneSwitcher.action.queue="Queue" +AdvSceneSwitcher.action.queue.type.add="Add" +AdvSceneSwitcher.action.queue.type.clear="Clear" +AdvSceneSwitcher.action.queue.type.start="Start" +AdvSceneSwitcher.action.queue.type.stop="Stop" +AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to queue{{queues}}" +AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}" ; Hotkey AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" @@ -962,6 +975,20 @@ AdvSceneSwitcher.connection.status.connecting="Connecting" AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated" AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated" +AdvSceneSwitcher.actionQueues.select="--select queue--" +AdvSceneSwitcher.actionQueues.add="Add new action queue" +AdvSceneSwitcher.actionQueues.nameNotAvailable="Action queue name already in use" +AdvSceneSwitcher.actionQueues.configure="Configure action queue settings" +AdvSceneSwitcher.actionQueues.invalid="Invalid action queue selection" +AdvSceneSwitcher.actionQueues.name="Name:" +AdvSceneSwitcher.actionQueues.runOnStartup="Run action queue when starting the plugin" +AdvSceneSwitcher.actionQueues.running="Queue is running" +AdvSceneSwitcher.actionQueues.stopped="Queue is stopped" +AdvSceneSwitcher.actionQueues.start="Start action queue" +AdvSceneSwitcher.actionQueues.stop="Stop action queue" +AdvSceneSwitcher.actionQueues.size="Current size: %1" +AdvSceneSwitcher.actionQueues.clear="Clear action queue" + AdvSceneSwitcher.regex.enable="Enable regular expressions" AdvSceneSwitcher.regex.configure="Configure regular expression settings" AdvSceneSwitcher.regex.partialMatch="Allow partial match" diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index 92735351..f021daeb 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -434,11 +434,14 @@ static void ResetMacros() } } +void AutoStartActionQueues(); + void SwitcherData::Start() { if (!(th && th->isRunning())) { ResetForNextInterval(); ResetMacros(); + AutoStartActionQueues(); stop = false; th = new SwitcherThread(); @@ -465,6 +468,7 @@ void SwitcherData::Start() } bool CloseAllInputDialogs(); +void StopAndClearAllActionQueues(); void SwitcherData::Stop() { @@ -474,6 +478,7 @@ void SwitcherData::Stop() SetMacroAbortWait(true); GetMacroWaitCV().notify_all(); GetMacroTransitionCV().notify_all(); + StopAndClearAllActionQueues(); // Not waiting if a dialog was closed is a workaround to avoid // deadlocks when a variable input dialog is opened while Stop() @@ -750,6 +755,7 @@ QWidget *GetSettingsWindow() void SetupConnectionManager(); void SetupWebsocketHelpers(); +void SetupActionQueues(); extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate) { @@ -763,6 +769,7 @@ extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate) SetupDock(); SetupConnectionManager(); SetupWebsocketHelpers(); + SetupActionQueues(); obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); obs_frontend_add_event_callback(OBSEvent, switcher); diff --git a/src/macro-core/action-queue.cpp b/src/macro-core/action-queue.cpp new file mode 100644 index 00000000..8975377e --- /dev/null +++ b/src/macro-core/action-queue.cpp @@ -0,0 +1,380 @@ +#include "action-queue.hpp" +#include "plugin-state-helpers.hpp" + +namespace advss { + +static std::deque> queues; + +void SetupActionQueues() +{ + static bool done = false; + if (done) { + return; + } + AddSaveStep(SaveActionQueues); + AddLoadStep(LoadActionQueues); + done = true; +} + +ActionQueue::ActionQueue() : Item() {} + +ActionQueue::~ActionQueue() +{ + Stop(); +} + +std::shared_ptr ActionQueue::Create() +{ + return std::make_shared(); +} + +void ActionQueue::Save(obs_data_t *obj) const +{ + obs_data_set_string(obj, "name", _name.c_str()); + obs_data_set_bool(obj, "runOnStartup", _runOnStartup); +} + +void ActionQueue::Load(obs_data_t *obj) +{ + std::lock_guard lock(_mutex); + _name = obs_data_get_string(obj, "name"); + _runOnStartup = obs_data_get_bool(obj, "runOnStartup"); + + if (_runOnStartup) { + Start(); + } +} + +void ActionQueue::Start() +{ + if (_thread.joinable()) { + return; + } + _stop = false; + _thread = std::thread(&ActionQueue::RunActions, this); +} + +void ActionQueue::Stop() +{ + _stop = true; + _cv.notify_all(); + if (_thread.joinable()) { + _thread.join(); + } +} + +bool ActionQueue::IsRunning() const +{ + return _thread.joinable(); +} + +bool ActionQueue::RunsOnStartup() const +{ + return _runOnStartup; +} + +void ActionQueue::Clear() +{ + std::lock_guard lock(_mutex); + _actions.clear(); +} + +void ActionQueue::Add(const std::shared_ptr &actions) +{ + std::lock_guard lock(_mutex); + _actions.emplace_back(actions); + _cv.notify_all(); +} + +bool ActionQueue::IsEmpty() +{ + std::lock_guard lock(_mutex); + return _actions.empty(); +} + +size_t ActionQueue::Size() +{ + std::lock_guard lock(_mutex); + return _actions.size(); +} + +void ActionQueue::RunActions() +{ + std::shared_ptr action; + while (true) { + { // Grab next action to run + std::unique_lock lock(_mutex); + while (_actions.empty() && !_stop) { + _cv.wait(lock); + } + + if (_stop) { + return; + } + action = _actions.front(); + _actions.pop_front(); + } + + if (!action) { + continue; + } + + vblog(LOG_INFO, "Performing action '%s' in queue '%s'", + action->GetId().c_str(), _name.c_str()); + action->PerformAction(); + } +} + +ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent, + ActionQueue &settings) + : ItemSettingsDialog(settings, queues, + "AdvSceneSwitcher.actionQueues.select", + "AdvSceneSwitcher.actionQueues.add", + "AdvSceneSwitcher.actionQueues.nameNotAvailable", + parent), + _queueRunStatus(new QLabel()), + _startStopToggle(new QPushButton()), + _queueSize(new QLabel()), + _clear(new QPushButton( + obs_module_text("AdvSceneSwitcher.actionQueues.clear"))), + _runOnStartup(new QCheckBox()), + _queue(settings) +{ + QWidget::connect(_startStopToggle, SIGNAL(clicked()), this, + SLOT(StartStopClicked())); + QWidget::connect(_clear, SIGNAL(clicked()), this, SLOT(ClearClicked())); + + _runOnStartup->setChecked(settings._runOnStartup); + UpdateLabels(); + + auto layout = new QGridLayout(); + int row = 0; + layout->addWidget(new QLabel(obs_module_text( + "AdvSceneSwitcher.actionQueues.name")), + row, 0); + auto nameLayout = new QHBoxLayout(); + nameLayout->setContentsMargins(0, 0, 0, 0); + nameLayout->addWidget(_name); + nameLayout->addWidget(_nameHint); + layout->addLayout(nameLayout, row, 1); + ++row; + layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.actionQueues.runOnStartup")), + row, 0); + layout->addWidget(_runOnStartup, row, 1); + _runOnStartup->setToolTip( + obs_module_text("AdvSceneSwitcher.actionQueues.runOnStartup")); + ++row; + layout->addWidget(_queueRunStatus, row, 0); + layout->addWidget(_startStopToggle, row, 1); + ++row; + layout->addWidget(_queueSize, row, 0); + layout->addWidget(_clear, row, 1); + ++row; + layout->addWidget(_buttonbox, row, 0, 1, -1); + layout->setSizeConstraint(QLayout::SetFixedSize); + setLayout(layout); + + auto timer = new QTimer(this); + QWidget::connect(timer, &QTimer::timeout, this, + &ActionQueueSettingsDialog::UpdateLabels); + timer->start(300); +} + +bool ActionQueueSettingsDialog::AskForSettings(QWidget *parent, + ActionQueue &settings) +{ + ActionQueueSettingsDialog dialog(parent, settings); + dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); + if (dialog.exec() != DialogCode::Accepted) { + return false; + } + + settings._name = dialog._name->text().toStdString(); + settings._runOnStartup = dialog._runOnStartup->isChecked(); + return true; +} + +void ActionQueueSettingsDialog::StartStopClicked() +{ + if (_queue.IsRunning()) { + _queue.Stop(); + } else { + _queue.Start(); + } + UpdateLabels(); +} + +void ActionQueueSettingsDialog::ClearClicked() +{ + _queue.Clear(); +} + +void ActionQueueSettingsDialog::UpdateLabels() +{ + _queueRunStatus->setText(obs_module_text( + _queue.IsRunning() ? "AdvSceneSwitcher.actionQueues.running" + : "AdvSceneSwitcher.actionQueues.stopped")); + _startStopToggle->setText( + _queue.IsRunning() + ? obs_module_text("AdvSceneSwitcher.actionQueues.stop") + : obs_module_text( + "AdvSceneSwitcher.actionQueues.start")); + _queueSize->setText( + QString(obs_module_text("AdvSceneSwitcher.actionQueues.size")) + .arg(QString::number(_queue.Size()))); +} + +static bool AskForSettingsWrapper(QWidget *parent, Item &settings) +{ + ActionQueue &queue = dynamic_cast(settings); + if (ActionQueueSettingsDialog::AskForSettings(parent, queue)) { + return true; + } + return false; +} + +void ActionQueueSelection::SetActionQueue( + const std::weak_ptr &queue_) +{ + const QSignalBlocker blocker(_selection); + auto queue = queue_.lock(); + if (queue) { + _selection->setCurrentText( + QString::fromStdString(queue->Name())); + } else { + _selection->setCurrentIndex(-1); + } +} + +ActionQueueSelection::ActionQueueSelection(QWidget *parent) + : ItemSelection(queues, ActionQueue::Create, AskForSettingsWrapper, + "AdvSceneSwitcher.actionQueues.select", + "AdvSceneSwitcher.actionQueues.add", + "AdvSceneSwitcher.item.nameNotAvailable", + "AdvSceneSwitcher.actionQueues.configure", parent) +{ + // Connect to slots + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Rename(const QString &, const QString &)), this, + SLOT(RenameItem(const QString &, const QString &))); + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Add(const QString &)), this, + SLOT(AddItem(const QString &))); + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Remove(const QString &)), this, + SLOT(RemoveItem(const QString &))); + + // Forward signals + QWidget::connect(this, + SIGNAL(ItemRenamed(const QString &, const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Rename(const QString &, const QString &))); + QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Add(const QString &))); + QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Remove(const QString &))); +} + +ActionQueueSignalManager::ActionQueueSignalManager(QObject *parent) + : QObject(parent) +{ + QWidget::connect(this, SIGNAL(Add(const QString &)), this, + SLOT(StartNewQueue(const QString &))); +} + +ActionQueueSignalManager *ActionQueueSignalManager::Instance() +{ + static ActionQueueSignalManager manager; + return &manager; +} + +void ActionQueueSignalManager::StartNewQueue(const QString &name) +{ + auto weakQueue = GetWeakActionQueueByQString(name); + auto queue = weakQueue.lock(); + if (!queue) { + return; + } + if (queue->RunsOnStartup()) { + queue->Start(); + } +} + +void SaveActionQueues(obs_data_t *obj) +{ + OBSDataArrayAutoRelease array = obs_data_array_create(); + for (const auto &queue : queues) { + OBSDataAutoRelease obj = obs_data_create(); + queue->Save(obj); + obs_data_array_push_back(array, obj); + } + obs_data_set_array(obj, "actionQueues", array); +} + +void LoadActionQueues(obs_data_t *obj) +{ + queues.clear(); + + OBSDataArrayAutoRelease array = obs_data_get_array(obj, "actionQueues"); + size_t count = obs_data_array_count(array); + + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease obj = obs_data_array_item(array, i); + auto queue = ActionQueue::Create(); + queues.emplace_back(queue); + queues.back()->Load(obj); + } +} + +std::weak_ptr GetWeakActionQueueByName(const std::string &name) +{ + for (const auto &queue : queues) { + if (queue->Name() == name) { + std::weak_ptr wp = + std::dynamic_pointer_cast(queue); + return wp; + } + } + return std::weak_ptr(); +} + +std::weak_ptr GetWeakActionQueueByQString(const QString &name) +{ + return GetWeakActionQueueByName(name.toStdString()); +} + +std::string GetActionQueueName(const std::weak_ptr &queue_) +{ + auto queue = queue_.lock(); + if (!queue) { + return obs_module_text("AdvSceneSwitcher.actionQueues.invalid"); + } + return queue->Name(); +} + +void AutoStartActionQueues() +{ + for (auto &queue : queues) { + auto actionQueue = + std::dynamic_pointer_cast(queue); + if (actionQueue->RunsOnStartup()) { + actionQueue->Start(); + } + } +} + +void StopAndClearAllActionQueues() +{ + for (auto &queue : queues) { + auto actionQueue = + std::dynamic_pointer_cast(queue); + actionQueue->Stop(); + actionQueue->Clear(); + } +} + +} // namespace advss diff --git a/src/macro-core/action-queue.hpp b/src/macro-core/action-queue.hpp new file mode 100644 index 00000000..fa27c45b --- /dev/null +++ b/src/macro-core/action-queue.hpp @@ -0,0 +1,105 @@ +#pragma once +#include "item-selection-helpers.hpp" +#include "macro-action.hpp" + +#include +#include +#include +#include +#include + +namespace advss { + +class ActionQueueSelection; +class ActionQueueSettingsDialog; + +class ActionQueue : public Item { +public: + ActionQueue(); + ~ActionQueue(); + + static std::shared_ptr Create(); + + void Save(obs_data_t *obj) const; + void Load(obs_data_t *obj); + + void Start(); + void Stop(); + bool IsRunning() const; + bool RunsOnStartup() const; + + void Clear(); + bool IsEmpty(); + + void Add(const std::shared_ptr &); + size_t Size(); + +private: + void RunActions(); + + bool _runOnStartup = true; + std::atomic_bool _stop = {false}; + std::mutex _mutex; + std::condition_variable _cv; + std::thread _thread; + std::deque> _actions; + + friend ActionQueueSelection; + friend ActionQueueSettingsDialog; +}; + +class ActionQueueSettingsDialog : public ItemSettingsDialog { + Q_OBJECT + +public: + ActionQueueSettingsDialog(QWidget *parent, ActionQueue &); + static bool AskForSettings(QWidget *parent, ActionQueue &settings); + +private slots: + void StartStopClicked(); + void ClearClicked(); + void UpdateLabels(); + +private: + QLabel *_queueRunStatus; + QPushButton *_startStopToggle; + QLabel *_queueSize; + QPushButton *_clear; + QCheckBox *_runOnStartup; + + ActionQueue &_queue; +}; + +class ActionQueueSelection : public ItemSelection { + Q_OBJECT + +public: + ActionQueueSelection(QWidget *parent = 0); + void SetActionQueue(const std::weak_ptr &); +}; + +class ActionQueueSignalManager : public QObject { + Q_OBJECT +public: + ActionQueueSignalManager(QObject *parent = nullptr); + static ActionQueueSignalManager *Instance(); + +private slots: + void StartNewQueue(const QString &); + +signals: + void Rename(const QString &, const QString &); + void Add(const QString &); + void Remove(const QString &); +}; + +void SetupActionQueues(); +void SaveActionQueues(obs_data_t *); +void LoadActionQueues(obs_data_t *); +std::weak_ptr GetWeakActionQueueByName(const std::string &name); +std::weak_ptr GetWeakActionQueueByQString(const QString &name); +std::string GetActionQueueName(const std::weak_ptr &); +void AutoStartActionQueues(); +void StopAndClearAllActionQueues(); + +} // namespace advss diff --git a/src/macro-core/macro-action-queue.cpp b/src/macro-core/macro-action-queue.cpp new file mode 100644 index 00000000..8f7ab0b7 --- /dev/null +++ b/src/macro-core/macro-action-queue.cpp @@ -0,0 +1,218 @@ +#include "macro-action-queue.hpp" +#include "macro-helpers.hpp" +#include "utility.hpp" + +namespace advss { + +const std::string MacroActionQueue::id = "queue"; + +bool MacroActionQueue::_registered = MacroActionFactory::Register( + MacroActionQueue::id, + {MacroActionQueue::Create, MacroActionQueueEdit::Create, + "AdvSceneSwitcher.action.queue"}); + +const static std::map actionTypes = { + {MacroActionQueue::Action::ADD_TO_QUEUE, + "AdvSceneSwitcher.action.queue.type.add"}, + {MacroActionQueue::Action::START_QUEUE, + "AdvSceneSwitcher.action.queue.type.start"}, + {MacroActionQueue::Action::STOP_QUEUE, + "AdvSceneSwitcher.action.queue.type.stop"}, + {MacroActionQueue::Action::CLEAR_QUEUE, + "AdvSceneSwitcher.action.queue.type.clear"}, +}; + +void MacroActionQueue::AddActions(ActionQueue *queue) +{ + auto macro = _macro.GetMacro(); + if (!macro) { + return; + } + + auto actions = *GetMacroActions(macro.get()); + for (const auto &action : actions) { + queue->Add(action); + } +} + +bool MacroActionQueue::PerformAction() +{ + auto queue = _queue.lock(); + if (!queue) { + return true; + } + + switch (_action) { + case Action::ADD_TO_QUEUE: + AddActions(queue.get()); + break; + case Action::CLEAR_QUEUE: + queue->Clear(); + break; + case Action::START_QUEUE: + queue->Start(); + break; + case Action::STOP_QUEUE: + queue->Stop(); + break; + default: + break; + } + return true; +} + +void MacroActionQueue::LogAction() const +{ + auto macro = _macro.GetMacro(); + if (!macro) { + return; + } + switch (_action) { + case Action::ADD_TO_QUEUE: + vblog(LOG_INFO, "queued actions of \"%s\" to \"%s\"", + GetMacroName(macro.get()).c_str(), + GetActionQueueName(_queue).c_str()); + break; + case Action::START_QUEUE: + vblog(LOG_INFO, "start queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + case Action::STOP_QUEUE: + vblog(LOG_INFO, "stop queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + case Action::CLEAR_QUEUE: + vblog(LOG_INFO, "cleared queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + default: + break; + } +} + +bool MacroActionQueue::Save(obs_data_t *obj) const +{ + MacroAction::Save(obj); + _macro.Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); + obs_data_set_string(obj, "queue", GetActionQueueName(_queue).c_str()); + return true; +} + +bool MacroActionQueue::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _macro.Load(obj); + _action = static_cast( + obs_data_get_int(obj, "action")); + _queue = GetWeakActionQueueByName(obs_data_get_string(obj, "queue")); + return true; +} + +std::string MacroActionQueue::GetShortDesc() const +{ + return GetActionQueueName(_queue); +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (const auto &[_, name] : actionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + +MacroActionQueueEdit::MacroActionQueueEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _macros(new MacroSelection(parent)), + _queues(new ActionQueueSelection()), + _actions(new QComboBox()), + _layout(new QHBoxLayout()) +{ + populateActionSelection(_actions); + + QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(MacroChanged(const QString &))); + QWidget::connect(_queues, SIGNAL(SelectionChanged(const QString &)), + this, SLOT(QueueChanged(const QString &))); + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + + setLayout(_layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionQueueEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _macros->SetCurrentMacro(_entryData->_macro); + _queues->SetActionQueue(_entryData->_queue); + SetWidgetVisibility(); +} + +void MacroActionQueueEdit::MacroChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_macro = text; + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroActionQueueEdit::QueueChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_queue = GetWeakActionQueueByQString(text); +} + +void MacroActionQueueEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_action = static_cast(value); + SetWidgetVisibility(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroActionQueueEdit::SetWidgetVisibility() +{ + _layout->removeWidget(_actions); + _layout->removeWidget(_queues); + _layout->removeWidget(_macros); + + ClearLayout(_layout); + + PlaceWidgets( + obs_module_text( + _entryData->_action == + MacroActionQueue::Action::ADD_TO_QUEUE + ? "AdvSceneSwitcher.action.queue.entry.add" + : "AdvSceneSwitcher.action.queue.entry.other"), + _layout, + {{"{{actions}}", _actions}, + {"{{queues}}", _queues}, + {"{{macros}}", _macros}}); + + _macros->setVisible(_entryData->_action == + MacroActionQueue::Action::ADD_TO_QUEUE); + _macros->HideSelectedMacro(); +} + +} // namespace advss diff --git a/src/macro-core/macro-action-queue.hpp b/src/macro-core/macro-action-queue.hpp new file mode 100644 index 00000000..3d60ee5d --- /dev/null +++ b/src/macro-core/macro-action-queue.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "macro-selection.hpp" +#include "action-queue.hpp" + +#include + +namespace advss { + +class MacroActionQueue : public MacroRefAction { +public: + MacroActionQueue(Macro *m) : MacroAction(m), MacroRefAction(m) {} + bool PerformAction(); + void LogAction() const; + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetShortDesc() const; + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + enum class Action { + ADD_TO_QUEUE, + START_QUEUE, + STOP_QUEUE, + CLEAR_QUEUE, + }; + Action _action = Action::ADD_TO_QUEUE; + std::weak_ptr _queue; + +private: + void AddActions(ActionQueue *); + + static bool _registered; + static const std::string id; +}; + +class MacroActionQueueEdit : public QWidget { + Q_OBJECT + +public: + MacroActionQueueEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionQueueEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void MacroChanged(const QString &text); + void QueueChanged(const QString &); + void ActionChanged(int value); +signals: + void HeaderInfoChanged(const QString &); + +private: + void SetWidgetVisibility(); + + MacroSelection *_macros; + ActionQueueSelection *_queues; + QComboBox *_actions; + QHBoxLayout *_layout; + + std::shared_ptr _entryData; + bool _loading = true; +}; + +} // namespace advss diff --git a/src/macro-core/macro-condition-queue.cpp b/src/macro-core/macro-condition-queue.cpp new file mode 100644 index 00000000..201ade61 --- /dev/null +++ b/src/macro-core/macro-condition-queue.cpp @@ -0,0 +1,163 @@ +#include "macro-condition-queue.hpp" +#include "utility.hpp" + +namespace advss { + +const std::string MacroConditionQueue::id = "queue"; + +bool MacroConditionQueue::_registered = MacroConditionFactory::Register( + MacroConditionQueue::id, + {MacroConditionQueue::Create, MacroConditionQueueEdit::Create, + "AdvSceneSwitcher.condition.queue"}); + +const static std::map + conditionTypes = { + {MacroConditionQueue::Condition::STARTED, + "AdvSceneSwitcher.condition.queue.type.started"}, + {MacroConditionQueue::Condition::STOPPED, + "AdvSceneSwitcher.condition.queue.type.stopped"}, + {MacroConditionQueue::Condition::SIZE, + "AdvSceneSwitcher.condition.queue.type.size"}, +}; + +bool MacroConditionQueue::CheckCondition() +{ + auto queue = _queue.lock(); + if (!queue) { + return false; + } + + switch (_condition) { + case Condition::STARTED: + return queue->IsRunning(); + case Condition::STOPPED: + return !queue->IsRunning(); + case Condition::SIZE: + return queue->Size() < _size; + default: + break; + } + return false; +} + +bool MacroConditionQueue::Save(obs_data_t *obj) const +{ + MacroCondition::Save(obj); + obs_data_set_int(obj, "condition", static_cast(_condition)); + obs_data_set_string(obj, "queue", GetActionQueueName(_queue).c_str()); + _size.Save(obj, "size"); + return true; +} + +bool MacroConditionQueue::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _condition = static_cast(obs_data_get_int(obj, "condition")); + _queue = GetWeakActionQueueByName(obs_data_get_string(obj, "queue")); + _size.Load(obj, "size"); + return true; +} + +static inline void populateQueueTypeSelection(QComboBox *list) +{ + for (const auto &[_, name] : conditionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + +MacroConditionQueueEdit::MacroConditionQueueEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _conditions(new QComboBox()), + _queues(new ActionQueueSelection()), + _size(new VariableSpinBox()), + _layout(new QHBoxLayout()) +{ + populateQueueTypeSelection(_conditions); + + _size->setMinimum(1); + + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_queues, SIGNAL(SelectionChanged(const QString &)), + this, SLOT(QueueChanged(const QString &))); + QWidget::connect( + _size, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, SLOT(SizeChanged(const NumberVariable &))); + + setLayout(_layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionQueueEdit::ConditionChanged(int condition) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_condition = + static_cast(condition); + SetWidgetVisibility(); +} + +void MacroConditionQueueEdit::QueueChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_queue = GetWeakActionQueueByQString(text); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroConditionQueueEdit::SizeChanged(const NumberVariable &value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_size = value; +} + +void MacroConditionQueueEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + _queues->SetActionQueue(_entryData->_queue); + SetWidgetVisibility(); +} + +void MacroConditionQueueEdit::SetWidgetVisibility() +{ + _layout->removeWidget(_conditions); + _layout->removeWidget(_queues); + _layout->removeWidget(_size); + + ClearLayout(_layout); + + PlaceWidgets( + obs_module_text( + _entryData->_condition == + MacroConditionQueue::Condition::SIZE + ? "AdvSceneSwitcher.condition.queue.entry.size" + : "AdvSceneSwitcher.condition.queue.entry.startStop"), + _layout, + {{"{{conditions}}", _conditions}, + {"{{queues}}", _queues}, + {"{{size}}", _size}}); + + _size->setVisible(_entryData->_condition == + MacroConditionQueue::Condition::SIZE); +} + +} // namespace advss diff --git a/src/macro-core/macro-condition-queue.hpp b/src/macro-core/macro-condition-queue.hpp new file mode 100644 index 00000000..92d03033 --- /dev/null +++ b/src/macro-core/macro-condition-queue.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "macro-condition-edit.hpp" +#include "action-queue.hpp" + +#include +#include +#include +#include + +namespace advss { + +class MacroConditionQueue : public MacroCondition { +public: + MacroConditionQueue(Macro *m) : MacroCondition(m, true) {} + bool CheckCondition(); + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + enum class Condition { + STARTED, + STOPPED, + SIZE, + }; + Condition _condition = Condition::STARTED; + std::weak_ptr _queue; + IntVariable _size = 1; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionQueueEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionQueueEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionQueueEdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void ConditionChanged(int); + void QueueChanged(const QString &); + void SizeChanged(const NumberVariable &); + +signals: + void HeaderInfoChanged(const QString &); + +private: + void SetWidgetVisibility(); + + QComboBox *_conditions; + ActionQueueSelection *_queues; + VariableSpinBox *_size; + QHBoxLayout *_layout; + + std::shared_ptr _entryData; + bool _loading = true; +}; + +} // namespace advss