diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f428563..57215d39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,6 +241,7 @@ set(advanced-scene-switcher_HEADERS src/headers/macro-condition-source.hpp src/headers/macro-condition-streaming.hpp src/headers/macro-condition-timer.hpp + src/headers/macro-condition-transition.hpp src/headers/macro-condition-video.hpp src/headers/macro-condition-virtual-cam.hpp src/headers/macro-condition-window.hpp @@ -330,6 +331,7 @@ set(advanced-scene-switcher_SOURCES src/macro-condition-source.cpp src/macro-condition-streaming.cpp src/macro-condition-timer.cpp + src/macro-condition-transition.cpp src/macro-condition-video.cpp src/macro-condition-virtual-cam.cpp src/macro-condition-window.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 77541aad..c41d2af6 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -215,6 +215,13 @@ AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions" AdvSceneSwitcher.condition.sceneTransform.entry.line1="On {{scenes}} {{sources}} matches transform" AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}" AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}" +AdvSceneSwitcher.condition.transition="Transition" +AdvSceneSwitcher.condition.transition.type.current="Current transition type is" +AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is" +AdvSceneSwitcher.condition.transition.type.started="Transition active" +AdvSceneSwitcher.condition.transition.type.ended="Transition ended" +AdvSceneSwitcher.condition.transition.durationSuffix="seconds" +AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}" ; Macro Actions AdvSceneSwitcher.action.switchScene="Switch scene" diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index ef45352d..ab6182f7 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -547,6 +547,12 @@ void setReplayBufferSaved() switcher->replayBufferSaved = true; } +void setTranstionEnd() +{ + switcher->lastTransitionEndTime = + std::chrono::high_resolution_clock::now(); +} + // Note to future self: // be careful using switcher->m here as there is potential for deadlocks when using // frontend functions such as obs_frontend_set_current_scene() @@ -583,6 +589,9 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher) setReplayBufferSaved(); break; #endif + case OBS_FRONTEND_EVENT_TRANSITION_STOPPED: + setTranstionEnd(); + break; default: break; } diff --git a/src/headers/macro-condition-transition.hpp b/src/headers/macro-condition-transition.hpp new file mode 100644 index 00000000..23ab0da9 --- /dev/null +++ b/src/headers/macro-condition-transition.hpp @@ -0,0 +1,79 @@ +#pragma once +#include "macro.hpp" +#include "duration-control.hpp" +#include "transition-selection.hpp" + +#include +#include + +enum class TransitionCondition { + CURRENT, + DURATION, + STARTED, + ENDED, +}; + +class MacroConditionTransition : public MacroCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetShortDesc(); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + void ConnectToTransitionSignals(); + void DisconnectTransitionSignals(); + + TransitionCondition _condition = TransitionCondition::CURRENT; + TransitionSelection _transition; + Duration _duration; + +private: + static void TransitionStarted(void *data, calldata_t *); + static void TransitionEnded(void *data, calldata_t *); + + bool _started = false; + bool _ended = false; + std::chrono::high_resolution_clock::time_point _lastTransitionEndTime{}; + static bool _registered; + static const std::string id; +}; + +class MacroConditionTransitionEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionTransitionEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionTransitionEdit( + parent, + std::dynamic_pointer_cast( + cond)); + } + +private slots: + void ConditionChanged(int cond); + void TransitionChanged(const TransitionSelection &); + void DurationChanged(double seconds); +signals: + void HeaderInfoChanged(const QString &); + +protected: + QComboBox *_conditions; + TransitionSelectionWidget *_transitions; + DurationSelection *_duration; + QLabel *_durationSuffix; + std::shared_ptr _entryData; + +private: + void SetWidgetVisibility(); + bool _loading = true; +}; diff --git a/src/headers/switcher-data-structs.hpp b/src/headers/switcher-data-structs.hpp index 5493147a..4a03ac61 100644 --- a/src/headers/switcher-data-structs.hpp +++ b/src/headers/switcher-data-structs.hpp @@ -80,6 +80,7 @@ struct SwitcherData { OBSWeakSource currentScene = nullptr; OBSWeakSource previousScene = nullptr; std::chrono::high_resolution_clock::time_point lastSceneChangeTime{}; + std::chrono::high_resolution_clock::time_point lastTransitionEndTime{}; OBSWeakSource lastRandomScene; SceneGroup *lastRandomSceneGroup; OBSWeakSource nonMatchingScene; diff --git a/src/macro-condition-transition.cpp b/src/macro-condition-transition.cpp new file mode 100644 index 00000000..4f8a42c7 --- /dev/null +++ b/src/macro-condition-transition.cpp @@ -0,0 +1,272 @@ +#include "headers/macro-condition-edit.hpp" +#include "headers/macro-condition-transition.hpp" +#include "headers/utility.hpp" +#include "headers/advanced-scene-switcher.hpp" + +const std::string MacroConditionTransition::id = "transition"; + +bool MacroConditionTransition::_registered = MacroConditionFactory::Register( + MacroConditionTransition::id, + {MacroConditionTransition::Create, MacroConditionTransitionEdit::Create, + "AdvSceneSwitcher.condition.transition", false}); + +static std::map filterConditionTypes = { + {TransitionCondition::CURRENT, + "AdvSceneSwitcher.condition.transition.type.current"}, + {TransitionCondition::DURATION, + "AdvSceneSwitcher.condition.transition.type.duration"}, + {TransitionCondition::STARTED, + "AdvSceneSwitcher.condition.transition.type.started"}, + {TransitionCondition::ENDED, + "AdvSceneSwitcher.condition.transition.type.ended"}, +}; + +bool isCurrentTransition(OBSWeakSource &t) +{ + bool match; + auto tSource = obs_frontend_get_current_transition(); + auto tWeakSource = obs_source_get_weak_source(tSource); + match = t == tWeakSource; + obs_weak_source_release(tWeakSource); + obs_source_release(tSource); + return match; +} + +// Relies on the fact that switcher->currentScene will only be updated on event +// OBS_FRONTEND_EVENT_SCENE_CHANGED but obs_frontend_get_current_scene() will +// already return the scene to be transitioned to. +bool anySceneTransitionStarted() +{ + bool ret; + auto currentSceneSrouce = obs_frontend_get_current_scene(); + auto currentScene = obs_source_get_weak_source(currentSceneSrouce); + ret = switcher->currentScene != currentScene; + obs_weak_source_release(currentScene); + obs_source_release(currentSceneSrouce); + return ret; +} + +bool MacroConditionTransition::CheckCondition() +{ + bool anyTransitionEnded = _lastTransitionEndTime != + switcher->lastTransitionEndTime; + bool transitionStarted = false; + bool transitionEnded = false; + + if (_transition.GetType() == TransitionSelectionType::ANY) { + transitionStarted = anySceneTransitionStarted(); + transitionEnded = anyTransitionEnded; + } else { + transitionStarted = _started; + transitionEnded = _ended; + } + + bool ret = false; + switch (_condition) { + case TransitionCondition::CURRENT: { + auto transition = _transition.GetTransition(); + ret = isCurrentTransition(transition); + break; + } + case TransitionCondition::DURATION: + ret = _duration.seconds * 1000 == + obs_frontend_get_transition_duration(); + break; + case TransitionCondition::STARTED: + ret = transitionStarted; + break; + case TransitionCondition::ENDED: + ret = transitionEnded; + break; + default: + break; + } + + // Reset for next interval + if (_started) { + _started = false; + } + if (_ended) { + _ended = false; + } + if (anyTransitionEnded) { + _lastTransitionEndTime = switcher->lastTransitionEndTime; + } + + return ret; +} + +void MacroConditionTransition::ConnectToTransitionSignals() +{ + auto source = obs_weak_source_get_source(_transition.GetTransition()); + auto sh = obs_source_get_signal_handler(source); + signal_handler_connect(sh, "transition_start", TransitionStarted, this); + signal_handler_connect(sh, "transition_stop", TransitionEnded, this); + obs_source_release(source); +} + +void MacroConditionTransition::DisconnectTransitionSignals() +{ + auto source = obs_weak_source_get_source(_transition.GetTransition()); + auto sh = obs_source_get_signal_handler(source); + signal_handler_disconnect(sh, "transition_start", TransitionStarted, + this); + signal_handler_disconnect(sh, "transition_stop", TransitionEnded, this); + obs_source_release(source); +} + +void MacroConditionTransition::TransitionStarted(void *data, calldata_t *) +{ + auto *transitionCond = static_cast(data); + transitionCond->_started = true; +} + +void MacroConditionTransition::TransitionEnded(void *data, calldata_t *) +{ + auto *transitionCond = static_cast(data); + transitionCond->_ended = true; +} + +bool MacroConditionTransition::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + obs_data_set_int(obj, "condition", static_cast(_condition)); + _transition.Save(obj); + _duration.Save(obj); + return true; +} + +bool MacroConditionTransition::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _condition = static_cast( + obs_data_get_int(obj, "condition")); + _transition.Load(obj); + _duration.Load(obj); + ConnectToTransitionSignals(); + return true; +} + +std::string MacroConditionTransition::GetShortDesc() +{ + return _transition.ToString(); +} + +static inline void populateConditionSelection(QComboBox *list) +{ + for (auto entry : filterConditionTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + +MacroConditionTransitionEdit::MacroConditionTransitionEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _conditions = new QComboBox(); + _transitions = new TransitionSelectionWidget(this, true, true); + _duration = new DurationSelection(this, false); + _durationSuffix = new QLabel(obs_module_text( + "AdvSceneSwitcher.condition.transition.durationSuffix")); + + populateConditionSelection(_conditions); + + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_transitions, + SIGNAL(TransitionChanged(const TransitionSelection &)), + this, + SLOT(TransitionChanged(const TransitionSelection &))); + QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, + SLOT(DurationChanged(double))); + + QHBoxLayout *mainLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{conditions}}", _conditions}, + {"{{transitions}}", _transitions}, + {"{{duration}}", _duration}, + {"{{durationSuffix}}", _durationSuffix}, + }; + placeWidgets( + obs_module_text("AdvSceneSwitcher.condition.transition.entry"), + mainLayout, widgetPlaceholders); + setLayout(mainLayout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionTransitionEdit::ConditionChanged(int index) +{ + if (_loading || !_entryData) { + return; + } + + { + std::lock_guard lock(switcher->m); + _entryData->_condition = + static_cast(index); + } + SetWidgetVisibility(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroConditionTransitionEdit::TransitionChanged( + const TransitionSelection &t) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->DisconnectTransitionSignals(); + _entryData->_transition = t; + _entryData->ConnectToTransitionSignals(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroConditionTransitionEdit::DurationChanged(double seconds) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_duration.seconds = seconds; +} + +void MacroConditionTransitionEdit::SetWidgetVisibility() +{ + if (!_entryData) { + return; + } + + _transitions->setVisible( + _entryData->_condition == TransitionCondition::CURRENT || + _entryData->_condition == TransitionCondition::STARTED || + _entryData->_condition == TransitionCondition::ENDED); + _duration->setVisible(_entryData->_condition == + TransitionCondition::DURATION); + _durationSuffix->setVisible(_entryData->_condition == + TransitionCondition::DURATION); + + bool addCurrent = _entryData->_condition == + TransitionCondition::DURATION; + bool addAny = _entryData->_condition == TransitionCondition::STARTED || + _entryData->_condition == TransitionCondition::ENDED; + _transitions->Repopulate(addCurrent, addAny); +} + +void MacroConditionTransitionEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + SetWidgetVisibility(); + _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + _transitions->SetTransition(_entryData->_transition); + _duration->SetDuration(_entryData->_duration); +}