Compare commits

...

18 Commits

Author SHA1 Message Date
WarmUpTill
9bc59c1082 Add option to not save remaining time 2021-10-25 11:19:53 -07:00
WarmUpTill
8b9f03a349 Add timer action
This action can be used to control the state of the timers on macro
conditions
2021-10-23 13:54:26 -07:00
WarmUpTill
093b0f9ec0 Add pause / continue and save / load remaining time for time condition 2021-10-23 13:54:26 -07:00
WarmUpTill
254eabfa8d Fix exact image match not wokring when using images with alpha channel
The screenshots taken from video sources were using the image format
Format_RGBX8888 instead of Format_RGBA8888.
The alpha channel of screenshots taken from OBS would be blended with a
black background while this step would be not performed for files loaded
to macht match the video sources.
2021-10-23 13:54:15 -07:00
WarmUpTill
b2da8faf37 Improve performance by not creating pattern data each interval 2021-10-23 13:54:15 -07:00
WarmUpTill
3eba20e2d8 Add option to use alpha channel as mask for pattern match 2021-10-23 13:54:15 -07:00
WarmUpTill
08841aa88e Use duration control class / widget for scene triggers 2021-10-23 13:53:56 -07:00
WarmUpTill
2e96c226b5 Improve performance of expand / collapse all 2021-10-23 13:53:56 -07:00
WarmUpTill
c34308f743 Fix scroll position being lost when adding or removing macro segments 2021-10-23 13:53:56 -07:00
WarmUpTill
4af07d9d55 Fix incorrectly set start scene type when extending scquence with groups
This would result in extended sequences being aborted early whenever one
would try to extend after a scene group entry
2021-10-23 13:53:56 -07:00
WarmUpTill
d743260ca8 Add wait for transition to complete option for currrent scene check
Previous behaviour was to always wait for the transition to complete, as
switcher->currentScene is only updated on
OBS_FRONTEND_EVENT_SCENE_CHANGED.
2021-10-23 13:53:56 -07:00
WarmUpTill
8ff50056f0 Fix crash when switching scene collection during macro wait
Also update comment stating why the main mutex needs to be unlocked
during the "action" phase of running macros
2021-10-23 13:53:56 -07:00
WarmUpTill
b786831f3b Remove unnecessary return value check 2021-10-23 13:53:56 -07:00
WarmUpTill
32e2a5f44c Add git tag to version info 2021-10-19 13:02:15 -07:00
WarmUpTill
e3f277c32a Reword date repeat option 2021-10-19 13:01:24 -07:00
WarmUpTill
5a7d76061c Clean up general tab
- Remove unnecessary spacer next to priority list
- Remove horizontal line in General behviour section
2021-10-19 13:01:01 -07:00
WarmUpTill
39a8c34fa6 Add description for the "min neighbors" parameter 2021-10-09 08:08:15 -07:00
WarmUpTill
ead51e2f13 Fix minNeighbors parameter not loading correctly 2021-10-09 08:08:15 -07:00
33 changed files with 724 additions and 174 deletions

View File

@@ -42,6 +42,7 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
include(GetGitRevisionDescription) include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY) "${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
@@ -218,6 +219,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-scene-visibility.hpp src/headers/macro-action-scene-visibility.hpp
src/headers/macro-action-source.hpp src/headers/macro-action-source.hpp
src/headers/macro-action-streaming.hpp src/headers/macro-action-streaming.hpp
src/headers/macro-action-timer.hpp
src/headers/macro-action-transition.hpp src/headers/macro-action-transition.hpp
src/headers/macro-action-virtual-cam.hpp src/headers/macro-action-virtual-cam.hpp
src/headers/macro-action-wait.hpp src/headers/macro-action-wait.hpp
@@ -308,6 +310,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-scene-visibility.cpp src/macro-action-scene-visibility.cpp
src/macro-action-source.cpp src/macro-action-source.cpp
src/macro-action-streaming.cpp src/macro-action-streaming.cpp
src/macro-action-timer.cpp
src/macro-action-transition.cpp src/macro-action-transition.cpp
src/macro-action-virtual-cam.cpp src/macro-action-virtual-cam.cpp
src/macro-action-wait.cpp src/macro-action-wait.cpp

View File

@@ -1,3 +1,5 @@
#include "src/headers/version.h" #include "src/headers/version.h"
#define GIT_SHA1 "@GIT_SHA1@" #define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1; #define GIT_TAG "@GIT_TAG@"
const char g_GIT_SHA1[] = GIT_SHA1;
const char g_GIT_TAG[] = GIT_TAG;

View File

@@ -97,7 +97,8 @@ AdvSceneSwitcher.condition.scene.type.current="Current scene is"
AdvSceneSwitcher.condition.scene.type.previous="Previous scene is" AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
AdvSceneSwitcher.condition.scene.type.changed="Scene chagned" AdvSceneSwitcher.condition.scene.type.changed="Scene chagned"
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed" AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}}" AdvSceneSwitcher.condition.scene.waitForTransition="Wait for transition to complete"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}} {{waitForTransition}}"
AdvSceneSwitcher.condition.window="Window" AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..." AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed" AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
@@ -120,8 +121,10 @@ AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: " AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely." AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: " AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load." AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches."
AdvSceneSwitcher.condition.video.showMatch="Show match" AdvSceneSwitcher.condition.video.showMatch="Show match"
AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!" AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!"
AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!" AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
@@ -150,9 +153,11 @@ AdvSceneSwitcher.condition.pluginState="Plugin state"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval" AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}" AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
AdvSceneSwitcher.condition.timer="Timer" AdvSceneSwitcher.condition.timer="Timer"
AdvSceneSwitcher.condition.timer.pause="Pause"
AdvSceneSwitcher.condition.timer.continue="Continue"
AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} have passed" AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} have passed"
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds" AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds"
AdvSceneSwitcher.condition.timer.entry.line3="{{reset}} {{autoReset}} Automatically reset timer after duration was reached" AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Save time remaining {{autoReset}} Automatically reset timer after duration was reached"
AdvSceneSwitcher.condition.timer.reset="Reset" AdvSceneSwitcher.condition.timer.reset="Reset"
AdvSceneSwitcher.condition.macro="Macro" AdvSceneSwitcher.condition.macro="Macro"
AdvSceneSwitcher.condition.macro.type.count="Count" AdvSceneSwitcher.condition.macro.type.count="Count"
@@ -208,7 +213,7 @@ AdvSceneSwitcher.condition.date.state.after="After"
AdvSceneSwitcher.condition.date.state.before="Before" AdvSceneSwitcher.condition.date.state.before="Before"
AdvSceneSwitcher.condition.date.state.between="Between" AdvSceneSwitcher.condition.date.state.between="Between"
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component" AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component"
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Reschedule {{duration}} later when matching" AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Repeat every {{duration}} on date match"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform" AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform" AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions" AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
@@ -329,6 +334,12 @@ AdvSceneSwitcher.action.SceneSwap.entry="Swap preview and program scene in studi
AdvSceneSwitcher.action.transition="Transition" AdvSceneSwitcher.action.transition="Transition"
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}" AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds" AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds"
AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pause"
AdvSceneSwitcher.action.timer.type.continue="Continue"
AdvSceneSwitcher.action.timer.type.reset="Reset"
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Set time remaining of"
AdvSceneSwitcher.action.timer.entry="{{timerAction}} timers on {{macros}} {{duration}}"
; Transition Tab ; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition" AdvSceneSwitcher.transitionTab.title="Transition"

View File

@@ -258,13 +258,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_52"> <layout class="QHBoxLayout" name="horizontalLayout_52">
<item> <item>
@@ -362,20 +355,8 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_41"> <layout class="QHBoxLayout" name="horizontalLayout_41">
<item> <item>
<widget class="QListWidget" name="priorityList"/> <widget class="QListWidget" name="priorityList">
</item> </widget>
<item>
<spacer name="horizontalSpacer_30">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item> </item>
</layout> </layout>
</item> </item>

View File

@@ -232,12 +232,17 @@ void SwitcherData::Thread()
} }
} }
// After this point we will call frontend functions // After this point we will call frontend functions like
// obs_frontend_set_current_scene() and // obs_frontend_set_current_scene() and
// obs_frontend_set_current_transition() // obs_frontend_set_current_transition()
// //
// During this time SaveSceneSwitcher() could be called // During this time SaveSceneSwitcher() could be called
// leading to a deadlock, so we have to unlock() // leading to a deadlock with the frontend function being stuck
// in QMetaObject::invokeMethod() holding the mutex and
// OBS being stuck in SaveSceneSwitcher().
//
// So we have to unlock() risking race conditions as these are
// less frequent than the above described deadlock
lock.unlock(); lock.unlock();
if (match) { if (match) {
@@ -599,6 +604,7 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
extern "C" void InitSceneSwitcher() extern "C" void InitSceneSwitcher()
{ {
blog(LOG_INFO, "version: %s", g_GIT_TAG);
blog(LOG_INFO, "version: %s", g_GIT_SHA1); blog(LOG_INFO, "version: %s", g_GIT_SHA1);
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction( QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(

View File

@@ -46,6 +46,13 @@ double Duration::TimeRemaining()
return (seconds * 1000 - runTime.count()) / 1000.; return (seconds * 1000 - runTime.count()) / 1000.;
} }
void Duration::SetTimeRemaining(double remaining)
{
long long msPassed = (seconds - remaining) * 1000;
_startTime = std::chrono::high_resolution_clock::now() -
std::chrono::milliseconds(msPassed);
}
void Duration::Reset() void Duration::Reset()
{ {
_startTime = {}; _startTime = {};

View File

@@ -887,4 +887,6 @@ void AdvSceneSwitcher::setupGeneralTab()
QTimer *statusTimer = new QTimer(this); QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
statusTimer->start(1000); statusTimer->start(1000);
setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
} }

View File

@@ -52,6 +52,8 @@ public:
void SetEditMacro(Macro &m); void SetEditMacro(Macro &m);
void HighlightAction(int idx); void HighlightAction(int idx);
void HighlightCondition(int idx); void HighlightCondition(int idx);
void PopulateMacroActions(Macro &m, int afterIdx = 0);
void PopulateMacroConditions(Macro &m, int afterIdx = 0);
void ConnectControlSignals(MacroActionEdit *); void ConnectControlSignals(MacroActionEdit *);
void ConnectControlSignals(MacroConditionEdit *); void ConnectControlSignals(MacroConditionEdit *);
void SwapActions(Macro *m, int pos1, int pos2); void SwapActions(Macro *m, int pos1, int pos2);

View File

@@ -22,6 +22,7 @@ public:
bool DurationReached(); bool DurationReached();
double TimeRemaining(); double TimeRemaining();
void SetTimeRemaining(double);
void Reset(); void Reset();
std::string ToString(); std::string ToString();

View File

@@ -0,0 +1,72 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include "macro-selection.hpp"
#include <QDoubleSpinBox>
#include <QHBoxLayout>
enum class TimerAction {
PAUSE,
CONTINUE,
RESET,
SET_TIME_REMAINING,
};
class MacroActionTimer : public MacroRefAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetShortDesc();
std::string GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionTimer>();
}
Duration _duration;
TimerAction _actionType = TimerAction::PAUSE;
private:
static bool _registered;
static const std::string id;
};
class MacroActionTimerEdit : public QWidget {
Q_OBJECT
public:
MacroActionTimerEdit(
QWidget *parent,
std::shared_ptr<MacroActionTimer> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionTimerEdit(
parent,
std::dynamic_pointer_cast<MacroActionTimer>(action));
}
private slots:
void MacroChanged(const QString &text);
void MacroRemove(const QString &name);
void DurationChanged(double value);
void DurationUnitChanged(DurationUnit unit);
void ActionTypeChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
void SetWidgetVisibility();
MacroSelection *_macros;
DurationSelection *_duration;
QComboBox *_timerAction;
std::shared_ptr<MacroActionTimer> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -4,6 +4,7 @@
#include <QWidget> #include <QWidget>
#include <QComboBox> #include <QComboBox>
#include <QCheckBox>
enum class SceneType { enum class SceneType {
CURRENT, CURRENT,
@@ -24,6 +25,7 @@ public:
return std::make_shared<MacroConditionScene>(); return std::make_shared<MacroConditionScene>();
} }
bool _waitForTransition = false;
SceneSelection _scene; SceneSelection _scene;
SceneType _type; SceneType _type;
@@ -52,12 +54,14 @@ public:
private slots: private slots:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
void TypeChanged(int value); void TypeChanged(int value);
void WaitForTransitionChanged(int state);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
SceneSelectionWidget *_scenes; SceneSelectionWidget *_scenes;
QComboBox *_sceneType; QComboBox *_sceneType;
QCheckBox *_waitForTransition;
std::shared_ptr<MacroConditionScene> _entryData; std::shared_ptr<MacroConditionScene> _entryData;
private: private:

View File

@@ -16,8 +16,14 @@ public:
{ {
return std::make_shared<MacroConditionTimer>(); return std::make_shared<MacroConditionTimer>();
} }
void Pause();
void Continue();
void Reset();
Duration _duration; Duration _duration;
bool _paused = false;
bool _saveRemaining = false;
double _remaining = 0.;
bool _oneshot = false; bool _oneshot = false;
private: private:
@@ -44,14 +50,20 @@ public:
private slots: private slots:
void DurationChanged(double seconds); void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit); void DurationUnitChanged(DurationUnit unit);
void SaveRemainingChanged(int state);
void AutoResetChanged(int state); void AutoResetChanged(int state);
void PauseContinueClicked();
void ResetClicked(); void ResetClicked();
void UpdateTimeRemaining(); void UpdateTimeRemaining();
protected: protected:
void SetPauseContinueButtonLabel();
DurationSelection *_duration; DurationSelection *_duration;
QCheckBox *_autoReset; QCheckBox *_autoReset;
QCheckBox *_saveRemaining;
QPushButton *_reset; QPushButton *_reset;
QPushButton *_pauseConinue;
QLabel *_remaining; QLabel *_remaining;
std::shared_ptr<MacroConditionTimer> _entryData; std::shared_ptr<MacroConditionTimer> _entryData;

View File

@@ -20,6 +20,12 @@ enum class VideoCondition {
OBJECT, OBJECT,
}; };
struct PatternMatchData {
cv::Mat4b rgbaPattern;
cv::Mat3b rgbPattern;
cv::Mat1b mask;
};
constexpr int minMinNeighbors = 3; constexpr int minMinNeighbors = 3;
constexpr int maxMinNeighbors = 6; constexpr int maxMinNeighbors = 6;
@@ -44,6 +50,8 @@ public:
OBSWeakSource _videoSource; OBSWeakSource _videoSource;
VideoCondition _condition = VideoCondition::MATCH; VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
bool _useAlphaAsMask = false;
PatternMatchData _patternData;
double _patternThreshold = 0.8; double _patternThreshold = 0.8;
cv::CascadeClassifier _objectCascade; cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1; double _scaleFactor = 1.1;
@@ -120,6 +128,7 @@ private slots:
void ImagePathChanged(const QString &text); void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked(); void ImageBrowseButtonClicked();
void PatternThresholdChanged(double); void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void ModelPathChanged(const QString &text); void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(double); void ObjectScaleThresholdChanged(double);
@@ -141,12 +150,14 @@ protected:
FileSelection *_imagePath; FileSelection *_imagePath;
ThresholdSlider *_patternThreshold; ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask;
FileSelection *_modelDataPath; FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout; QHBoxLayout *_modelPathLayout;
ThresholdSlider *_objectScaleThreshold; ThresholdSlider *_objectScaleThreshold;
QHBoxLayout *_neighborsControlLayout; QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors; QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
QHBoxLayout *_minSizeControlLayout; QHBoxLayout *_minSizeControlLayout;
QSpinBox *_minSizeX; QSpinBox *_minSizeX;
QSpinBox *_minSizeY; QSpinBox *_minSizeY;

View File

@@ -30,6 +30,7 @@ public:
// Use this function to avoid accidental edits when scrolling through // Use this function to avoid accidental edits when scrolling through
// list of actions and conditions // list of actions and conditions
void SetFocusPolicyOfWidgets(); void SetFocusPolicyOfWidgets();
void SetCollapsed(bool collapsed);
protected slots: protected slots:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);

View File

@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "switch-generic.hpp" #include "switch-generic.hpp"
#include "duration-control.hpp"
enum class sceneTriggerType { enum class sceneTriggerType {
NONE = 0, NONE = 0,
@@ -43,7 +44,7 @@ struct SceneTrigger : SceneSwitcherEntry {
static bool pause; static bool pause;
sceneTriggerType triggerType = sceneTriggerType::NONE; sceneTriggerType triggerType = sceneTriggerType::NONE;
sceneTriggerAction triggerAction = sceneTriggerAction::NONE; sceneTriggerAction triggerAction = sceneTriggerAction::NONE;
double duration = 0; Duration duration;
OBSWeakSource audioSource = nullptr; OBSWeakSource audioSource = nullptr;
const char *getType() { return "trigger"; } const char *getType() { return "trigger"; }
@@ -70,13 +71,14 @@ public:
private slots: private slots:
void TriggerTypeChanged(int index); void TriggerTypeChanged(int index);
void TriggerActionChanged(int index); void TriggerActionChanged(int index);
void DurationChanged(double dur); void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
void AudioSourceChanged(const QString &text); void AudioSourceChanged(const QString &text);
private: private:
QComboBox *triggers; QComboBox *triggers;
QComboBox *actions; QComboBox *actions;
QDoubleSpinBox *duration; DurationSelection *duration;
QComboBox *audioSources; QComboBox *audioSources;
SceneTrigger *switchData; SceneTrigger *switchData;

View File

@@ -16,6 +16,7 @@ public:
void SetContent(QWidget *w, bool collapsed = true); void SetContent(QWidget *w, bool collapsed = true);
void AddHeaderWidget(QWidget *); void AddHeaderWidget(QWidget *);
void SetCollapsed(bool);
protected: protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;

View File

@@ -44,7 +44,7 @@ void placeWidgets(std::string text, QBoxLayout *layout,
std::unordered_map<std::string, QWidget *> placeholders, std::unordered_map<std::string, QWidget *> placeholders,
bool addStretch = true); bool addStretch = true);
void deleteLayoutItem(QLayoutItem *item); void deleteLayoutItem(QLayoutItem *item);
void clearLayout(QLayout *layout); void clearLayout(QLayout *layout, int afterIdx = 0);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor, QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor = QColor(0, 0, 0, 0), QColor = QColor(0, 0, 0, 0),
QString specifier = "QLabel ", QString specifier = "QLabel ",

View File

@@ -1 +1,2 @@
extern const char g_GIT_SHA1[]; extern const char g_GIT_SHA1[];
extern const char g_GIT_TAG[];

View File

@@ -152,12 +152,8 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
} }
macro->UpdateActionIndices(); macro->UpdateActionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditActionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroActions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightAction(idx); HighlightAction(idx);
} }
@@ -189,12 +185,8 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
switcher->macroWaitCv.notify_one(); switcher->macroWaitCv.notify_one();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditActionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroActions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
} }
void AdvSceneSwitcher::on_actionRemove_clicked() void AdvSceneSwitcher::on_actionRemove_clicked()

228
src/macro-action-timer.cpp Normal file
View File

@@ -0,0 +1,228 @@
#include "headers/macro-action-timer.hpp"
#include "headers/macro-condition-timer.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include <random>
const std::string MacroActionTimer::id = "timer";
bool MacroActionTimer::_registered = MacroActionFactory::Register(
MacroActionTimer::id,
{MacroActionTimer::Create, MacroActionTimerEdit::Create,
"AdvSceneSwitcher.action.Timer"});
static std::map<TimerAction, std::string> timerActions = {
{TimerAction::PAUSE, "AdvSceneSwitcher.action.timer.type.pause"},
{TimerAction::CONTINUE, "AdvSceneSwitcher.action.timer.type.continue"},
{TimerAction::RESET, "AdvSceneSwitcher.action.timer.type.reset"},
{TimerAction::SET_TIME_REMAINING,
"AdvSceneSwitcher.action.timer.type.setTimeRemaining"},
};
bool MacroActionTimer::PerformAction()
{
if (!_macro.get()) {
return true;
}
for (auto c : _macro->Conditions()) {
if (c->GetId() != "timer") {
continue;
}
auto timerCondition =
dynamic_cast<MacroConditionTimer *>(c.get());
if (!timerCondition) {
continue;
}
switch (_actionType) {
case TimerAction::PAUSE:
timerCondition->Pause();
break;
case TimerAction::CONTINUE:
timerCondition->Continue();
break;
case TimerAction::RESET:
timerCondition->Reset();
break;
case TimerAction::SET_TIME_REMAINING:
timerCondition->_duration.SetTimeRemaining(
_duration.seconds);
break;
default:
break;
}
}
return true;
}
void MacroActionTimer::LogAction()
{
if (!_macro.get()) {
return;
}
switch (_actionType) {
case TimerAction::PAUSE:
vblog(LOG_INFO, "paused timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::CONTINUE:
vblog(LOG_INFO, "continued timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::RESET:
vblog(LOG_INFO, "reset timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::SET_TIME_REMAINING:
vblog(LOG_INFO,
"set time remaining of timers on \"%s\" to \"%s\"",
_macro->Name().c_str(), _duration.ToString().c_str());
break;
default:
break;
}
}
bool MacroActionTimer::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_macro.Save(obj);
_duration.Save(obj);
obs_data_set_int(obj, "actionType", static_cast<int>(_actionType));
return true;
}
bool MacroActionTimer::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_macro.Load(obj);
_duration.Load(obj);
_actionType =
static_cast<TimerAction>(obs_data_get_int(obj, "actionType"));
return true;
}
std::string MacroActionTimer::GetShortDesc()
{
if (_macro.get()) {
return _macro->Name();
}
return "";
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : timerActions) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionTimerEdit::MacroActionTimerEdit(
QWidget *parent, std::shared_ptr<MacroActionTimer> entryData)
: QWidget(parent)
{
_macros = new MacroSelection(parent);
_duration = new DurationSelection();
_timerAction = new QComboBox();
populateTypeSelection(_timerAction);
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &)));
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
SLOT(MacroRemove(const QString &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionTypeChanged(int)));
_mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
{"{{duration}}", _duration},
{"{{timerAction}}", _timerAction},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"),
_mainLayout, widgetPlaceholders);
setLayout(_mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionTimerEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_macros->SetCurrentMacro(_entryData->_macro.get());
_duration->SetDuration(_entryData->_duration);
_timerAction->setCurrentIndex(
static_cast<int>(_entryData->_actionType));
SetWidgetVisibility();
}
void MacroActionTimerEdit::ActionTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_actionType = static_cast<TimerAction>(value);
SetWidgetVisibility();
}
void MacroActionTimerEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_duration->setVisible(_entryData->_actionType ==
TimerAction::SET_TIME_REMAINING);
adjustSize();
}
void MacroActionTimerEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroActionTimerEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroActionTimerEdit::MacroChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macro.UpdateRef(text);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionTimerEdit::MacroRemove(const QString &)
{
if (_entryData) {
_entryData->_macro.UpdateRef();
}
}

View File

@@ -276,12 +276,8 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
(*cond)->SetLogicType(logic); (*cond)->SetLogicType(logic);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditConditionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroConditions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightCondition(idx); HighlightCondition(idx);
} }
@@ -311,21 +307,13 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
macro->Conditions().erase(macro->Conditions().begin() + idx); macro->Conditions().erase(macro->Conditions().begin() + idx);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
if (macro->Conditions().size() == 0) {
ui->macroEditConditionHelp->setVisible(true);
}
if (idx == 0 && macro->Conditions().size() > 0) { if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0); auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE); newRoot->SetLogicType(LogicType::ROOT_NONE);
} }
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditConditionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroConditions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
} }
void AdvSceneSwitcher::on_conditionRemove_clicked() void AdvSceneSwitcher::on_conditionRemove_clicked()

View File

@@ -28,7 +28,18 @@ bool MacroConditionScene::CheckCondition()
switch (_type) { switch (_type) {
case SceneType::CURRENT: case SceneType::CURRENT:
return switcher->currentScene == _scene.GetScene(false); if (_waitForTransition) {
return switcher->currentScene == _scene.GetScene(false);
} else {
bool match = false;
auto current = obs_frontend_get_current_scene();
auto weak = obs_source_get_weak_source(current);
match = weak == _scene.GetScene(false);
obs_weak_source_release(weak);
obs_source_release(current);
return match;
}
case SceneType::PREVIOUS: case SceneType::PREVIOUS:
return switcher->previousScene == _scene.GetScene(false); return switcher->previousScene == _scene.GetScene(false);
case SceneType::CHANGED: case SceneType::CHANGED:
@@ -47,6 +58,7 @@ bool MacroConditionScene::Save(obs_data_t *obj)
MacroCondition::Save(obj); MacroCondition::Save(obj);
_scene.Save(obj); _scene.Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type)); obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_bool(obj, "waitForTransition", _waitForTransition);
return true; return true;
} }
@@ -55,6 +67,12 @@ bool MacroConditionScene::Load(obs_data_t *obj)
MacroCondition::Load(obj); MacroCondition::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_type = static_cast<SceneType>(obs_data_get_int(obj, "type")); _type = static_cast<SceneType>(obs_data_get_int(obj, "type"));
if (!obs_data_has_user_value(obj, "waitForTransition")) {
_waitForTransition = true;
} else {
_waitForTransition =
obs_data_get_bool(obj, "waitForTransition");
}
return true; return true;
} }
@@ -76,11 +94,15 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
{ {
_scenes = new SceneSelectionWidget(window(), false, false, false); _scenes = new SceneSelectionWidget(window(), false, false, false);
_sceneType = new QComboBox(); _sceneType = new QComboBox();
_waitForTransition = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.scene.waitForTransition"));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &))); this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int))); SLOT(TypeChanged(int)));
QWidget::connect(_waitForTransition, SIGNAL(stateChanged(int)), this,
SLOT(WaitForTransitionChanged(int)));
populateTypeSelection(_sceneType); populateTypeSelection(_sceneType);
@@ -88,6 +110,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{scenes}}", _scenes},
{"{{sceneType}}", _sceneType}, {"{{sceneType}}", _sceneType},
{"{{waitForTransition}}", _waitForTransition},
}; };
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"), placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"),
mainLayout, widgetPlaceholders); mainLayout, widgetPlaceholders);
@@ -121,10 +144,21 @@ void MacroConditionSceneEdit::TypeChanged(int value)
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroConditionSceneEdit::WaitForTransitionChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_waitForTransition = state;
}
void MacroConditionSceneEdit::SetWidgetVisibility() void MacroConditionSceneEdit::SetWidgetVisibility()
{ {
_scenes->setVisible(_entryData->_type == SceneType::CURRENT || _scenes->setVisible(_entryData->_type == SceneType::CURRENT ||
_entryData->_type == SceneType::PREVIOUS); _entryData->_type == SceneType::PREVIOUS);
_waitForTransition->setVisible(_entryData->_type == SceneType::CURRENT);
} }
void MacroConditionSceneEdit::UpdateEntryData() void MacroConditionSceneEdit::UpdateEntryData()
@@ -135,5 +169,6 @@ void MacroConditionSceneEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene); _scenes->SetScene(_entryData->_scene);
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type)); _sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
_waitForTransition->setChecked(_entryData->_waitForTransition);
SetWidgetVisibility(); SetWidgetVisibility();
} }

View File

@@ -12,6 +12,9 @@ bool MacroConditionTimer::_registered = MacroConditionFactory::Register(
bool MacroConditionTimer::CheckCondition() bool MacroConditionTimer::CheckCondition()
{ {
if (_paused) {
return _remaining == 0.;
}
if (_duration.DurationReached()) { if (_duration.DurationReached()) {
if (!_oneshot) { if (!_oneshot) {
_duration.Reset(); _duration.Reset();
@@ -25,6 +28,16 @@ bool MacroConditionTimer::Save(obs_data_t *obj)
{ {
MacroCondition::Save(obj); MacroCondition::Save(obj);
_duration.Save(obj); _duration.Save(obj);
if (!_paused) {
_remaining = _duration.TimeRemaining();
}
if (_saveRemaining) {
obs_data_set_double(obj, "remaining", _remaining);
} else {
obs_data_set_double(obj, "remaining", _duration.seconds);
}
obs_data_set_bool(obj, "saveRemaining", _saveRemaining);
obs_data_set_bool(obj, "paused", _paused);
obs_data_set_bool(obj, "oneshot", _oneshot); obs_data_set_bool(obj, "oneshot", _oneshot);
return true; return true;
} }
@@ -33,20 +46,49 @@ bool MacroConditionTimer::Load(obs_data_t *obj)
{ {
MacroCondition::Load(obj); MacroCondition::Load(obj);
_duration.Load(obj); _duration.Load(obj);
_remaining = obs_data_get_double(obj, "remaining");
_paused = obs_data_get_bool(obj, "paused");
_saveRemaining = obs_data_get_bool(obj, "saveRemaining");
if (!obs_data_has_user_value(obj, "oneshot")) { if (!obs_data_has_user_value(obj, "oneshot")) {
_oneshot = false; _oneshot = false;
} else { } else {
_oneshot = obs_data_get_bool(obj, "oneshot"); _oneshot = obs_data_get_bool(obj, "oneshot");
} }
_duration.SetTimeRemaining(_remaining);
return true; return true;
} }
void MacroConditionTimer::Pause()
{
if (!_paused) {
_paused = true;
_remaining = _duration.TimeRemaining();
}
}
void MacroConditionTimer::Continue()
{
if (_paused) {
_paused = false;
_duration.SetTimeRemaining(_remaining);
}
}
void MacroConditionTimer::Reset()
{
_remaining = _duration.seconds;
_duration.Reset();
}
MacroConditionTimerEdit::MacroConditionTimerEdit( MacroConditionTimerEdit::MacroConditionTimerEdit(
QWidget *parent, std::shared_ptr<MacroConditionTimer> entryData) QWidget *parent, std::shared_ptr<MacroConditionTimer> entryData)
: QWidget(parent) : QWidget(parent)
{ {
_duration = new DurationSelection(); _duration = new DurationSelection();
_autoReset = new QCheckBox(); _autoReset = new QCheckBox();
_saveRemaining = new QCheckBox();
_pauseConinue = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.timer.pause"));
_reset = new QPushButton( _reset = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.timer.reset")); obs_module_text("AdvSceneSwitcher.condition.timer.reset"));
_remaining = new QLabel(); _remaining = new QLabel();
@@ -55,16 +97,22 @@ MacroConditionTimerEdit::MacroConditionTimerEdit(
SLOT(DurationChanged(double))); SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit))); SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_pauseConinue, SIGNAL(clicked()), this,
SLOT(PauseContinueClicked()));
QWidget::connect(_reset, SIGNAL(clicked()), this, SLOT(ResetClicked())); QWidget::connect(_reset, SIGNAL(clicked()), this, SLOT(ResetClicked()));
QWidget::connect(_autoReset, SIGNAL(stateChanged(int)), this, QWidget::connect(_autoReset, SIGNAL(stateChanged(int)), this,
SLOT(AutoResetChanged(int))); SLOT(AutoResetChanged(int)));
QWidget::connect(_saveRemaining, SIGNAL(stateChanged(int)), this,
SLOT(SaveRemainingChanged(int)));
auto line1Layout = new QHBoxLayout; auto line1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", _duration}, {"{{duration}}", _duration},
{"{{autoReset}}", _autoReset}, {"{{autoReset}}", _autoReset},
{"{{remaining}}", _remaining}, {"{{remaining}}", _remaining},
{"{{pauseContinue}}", _pauseConinue},
{"{{reset}}", _reset}, {"{{reset}}", _reset},
{"{{saveRemaining}}", _saveRemaining},
}; };
placeWidgets( placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.timer.entry.line1"), obs_module_text("AdvSceneSwitcher.condition.timer.entry.line1"),
@@ -112,6 +160,16 @@ void MacroConditionTimerEdit::DurationUnitChanged(DurationUnit unit)
_entryData->_duration.displayUnit = unit; _entryData->_duration.displayUnit = unit;
} }
void MacroConditionTimerEdit::SaveRemainingChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_saveRemaining = state;
}
void MacroConditionTimerEdit::AutoResetChanged(int state) void MacroConditionTimerEdit::AutoResetChanged(int state)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -122,6 +180,23 @@ void MacroConditionTimerEdit::AutoResetChanged(int state)
_entryData->_oneshot = !state; _entryData->_oneshot = !state;
} }
void MacroConditionTimerEdit::PauseContinueClicked()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (_entryData->_paused) {
timer.start(1000);
_entryData->Continue();
} else {
_entryData->Pause();
timer.stop();
}
SetPauseContinueButtonLabel();
}
void MacroConditionTimerEdit::ResetClicked() void MacroConditionTimerEdit::ResetClicked()
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -129,7 +204,7 @@ void MacroConditionTimerEdit::ResetClicked()
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.Reset(); _entryData->Reset();
} }
void MacroConditionTimerEdit::UpdateTimeRemaining() void MacroConditionTimerEdit::UpdateTimeRemaining()
@@ -138,8 +213,28 @@ void MacroConditionTimerEdit::UpdateTimeRemaining()
_remaining->setText("-"); _remaining->setText("-");
return; return;
} }
_remaining->setText(
QString::number(_entryData->_duration.TimeRemaining())); if (_entryData->_paused) {
_remaining->setText(QString::number(_entryData->_remaining));
} else {
_remaining->setText(
QString::number(_entryData->_duration.TimeRemaining()));
}
}
void MacroConditionTimerEdit::SetPauseContinueButtonLabel()
{
if (!_entryData) {
return;
}
if (_entryData->_paused) {
_pauseConinue->setText(obs_module_text(
"AdvSceneSwitcher.condition.timer.continue"));
} else {
_pauseConinue->setText(obs_module_text(
"AdvSceneSwitcher.condition.timer.pause"));
}
} }
void MacroConditionTimerEdit::UpdateEntryData() void MacroConditionTimerEdit::UpdateEntryData()
@@ -150,4 +245,6 @@ void MacroConditionTimerEdit::UpdateEntryData()
_duration->SetDuration(_entryData->_duration); _duration->SetDuration(_entryData->_duration);
_autoReset->setChecked(!_entryData->_oneshot); _autoReset->setChecked(!_entryData->_oneshot);
_saveRemaining->setChecked(_entryData->_saveRemaining);
SetPauseContinueButtonLabel();
} }

View File

@@ -100,6 +100,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
obs_data_set_int(obj, "condition", static_cast<int>(_condition)); obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str()); obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_double(obj, "threshold", _patternThreshold); obs_data_set_double(obj, "threshold", _patternThreshold);
obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask);
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str()); obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
obs_data_set_double(obj, "scaleFactor", _scaleFactor); obs_data_set_double(obj, "scaleFactor", _scaleFactor);
obs_data_set_int(obj, "minNeighbors", _minNeighbors); obs_data_set_int(obj, "minNeighbors", _minNeighbors);
@@ -132,13 +133,14 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
static_cast<VideoCondition>(obs_data_get_int(obj, "condition")); static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath"); _file = obs_data_get_string(obj, "filePath");
_patternThreshold = obs_data_get_double(obj, "threshold"); _patternThreshold = obs_data_get_double(obj, "threshold");
_useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask");
_modelDataPath = obs_data_get_string(obj, "modelDataPath"); _modelDataPath = obs_data_get_string(obj, "modelDataPath");
_scaleFactor = obs_data_get_double(obj, "scaleFactor"); _scaleFactor = obs_data_get_double(obj, "scaleFactor");
if (!isScaleFactorValid(_scaleFactor)) { if (!isScaleFactorValid(_scaleFactor)) {
_scaleFactor = 1.1; _scaleFactor = 1.1;
} }
_minNeighbors = obs_data_get_int(obj, "minNeighbors"); _minNeighbors = obs_data_get_int(obj, "minNeighbors");
if (!isMinNeighborsValid(_scaleFactor)) { if (!isMinNeighborsValid(_minNeighbors)) {
_minNeighbors = minMinNeighbors; _minNeighbors = minMinNeighbors;
} }
_minSizeX = obs_data_get_int(obj, "minSizeX"); _minSizeX = obs_data_get_int(obj, "minSizeX");
@@ -174,26 +176,7 @@ void MacroConditionVideo::GetScreenshot()
obs_source_release(source); obs_source_release(source);
} }
bool MacroConditionVideo::LoadImageFromFile() // Assumption is that QImage uses Format_RGBA8888.
{
if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
return false;
}
_matchImage =
_matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
return true;
}
bool MacroConditionVideo::LoadModelData(std::string &path)
{
_modelDataPath = path;
_objectCascade = initObjectCascade(path);
return !_objectCascade.empty();
}
// Assumption is that QImage uses Format_RGBX8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV // Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img) cv::Mat QImageToMat(const QImage &img)
{ {
@@ -210,31 +193,94 @@ QImage MatToQImage(const cv::Mat &mat)
return QImage(); return QImage();
} }
return QImage(mat.data, mat.cols, mat.rows, mat.step, return QImage(mat.data, mat.cols, mat.rows, mat.step,
QImage::Format::Format_RGBX8888); QImage::Format::Format_RGBA8888);
} }
void matchPattern(QImage &img, QImage &pattern, double threshold, PatternMatchData createPatternData(QImage &pattern)
cv::Mat &result)
{ {
if (img.isNull() || pattern.isNull()) { PatternMatchData data;
if (pattern.isNull()) {
return data;
}
data.rgbaPattern = QImageToMat(pattern);
std::vector<cv::Mat1b> rgbaChannelsPattern;
cv::split(data.rgbaPattern, rgbaChannelsPattern);
std::vector<cv::Mat1b> rgbChanlesPattern(
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
cv::merge(rgbChanlesPattern, data.rgbPattern);
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
cv::THRESH_BINARY);
return data;
}
bool MacroConditionVideo::LoadImageFromFile()
{
if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
return false;
}
_matchImage =
_matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
_patternData = createPatternData(_matchImage);
return true;
}
bool MacroConditionVideo::LoadModelData(std::string &path)
{
_modelDataPath = path;
_objectCascade = initObjectCascade(path);
return !_objectCascade.empty();
}
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
cv::Mat &result, bool useAlphaAsMask = true)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
return; return;
} }
if (img.height() < pattern.height() || img.width() < pattern.width()) { if (img.height() < patternData.rgbaPattern.rows ||
img.width() < patternData.rgbaPattern.cols) {
return; return;
} }
auto i = QImageToMat(img); auto i = QImageToMat(img);
auto p = QImageToMat(pattern);
cv::matchTemplate(i, p, result, cv::TM_CCOEFF_NORMED); if (useAlphaAsMask) {
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO); std::vector<cv::Mat1b> rgbaChannelsImage;
cv::split(i, rgbaChannelsImage);
std::vector<cv::Mat1b> rgbChanlesImage(
rgbaChannelsImage.begin(),
rgbaChannelsImage.begin() + 3);
cv::Mat3b rgbImage;
cv::merge(rgbChanlesImage, rgbImage);
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
cv::TM_CCORR_NORMED, patternData.mask);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
} else {
cv::matchTemplate(i, patternData.rgbaPattern, result,
cv::TM_CCOEFF_NORMED);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
}
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask);
} }
bool MacroConditionVideo::ScreenshotContainsPattern() bool MacroConditionVideo::ScreenshotContainsPattern()
{ {
cv::Mat result; cv::Mat result;
matchPattern(_screenshotData->image, _matchImage, _patternThreshold, matchPattern(_screenshotData->image, _patternData, _patternThreshold,
result); result, _useAlphaAsMask);
return countNonZero(result) > 0; return countNonZero(result) > 0;
} }
@@ -358,6 +404,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.patternThreshold"), "AdvSceneSwitcher.condition.video.patternThreshold"),
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdDescription")); "AdvSceneSwitcher.condition.video.patternThresholdDescription"));
_useAlphaAsMask = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"));
_modelDataPath = new FileSelection(); _modelDataPath = new FileSelection();
_objectScaleThreshold = new ThresholdSlider( _objectScaleThreshold = new ThresholdSlider(
@@ -369,6 +417,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_minNeighbors = new QSpinBox(); _minNeighbors = new QSpinBox();
_minNeighbors->setMinimum(minMinNeighbors); _minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors); _minNeighbors->setMaximum(maxMinNeighbors);
_minNeighborsDescription = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minNeighborDescription"));
_minSizeX = new QSpinBox(); _minSizeX = new QSpinBox();
_minSizeY = new QSpinBox(); _minSizeY = new QSpinBox();
_minSizeX->setMaximum(1024); _minSizeX->setMaximum(1024);
@@ -397,6 +447,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(ImageBrowseButtonClicked())); SLOT(ImageBrowseButtonClicked()));
QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)), QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)),
this, SLOT(PatternThresholdChanged(double))); this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_objectScaleThreshold, QWidget::connect(_objectScaleThreshold,
SIGNAL(DoubleValueChanged(double)), this, SIGNAL(DoubleValueChanged(double)), this,
SLOT(ObjectScaleThresholdChanged(double))); SLOT(ObjectScaleThresholdChanged(double)));
@@ -472,9 +524,11 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLine1Layout); mainLayout->addLayout(entryLine1Layout);
mainLayout->addWidget(_patternThreshold); mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addLayout(_modelPathLayout); mainLayout->addLayout(_modelPathLayout);
mainLayout->addWidget(_objectScaleThreshold); mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout); mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addWidget(_minNeighborsDescription);
mainLayout->addLayout(_minSizeControlLayout); mainLayout->addLayout(_minSizeControlLayout);
mainLayout->addLayout(_maxSizeControlLayout); mainLayout->addLayout(_maxSizeControlLayout);
mainLayout->addLayout(showMatchLayout); mainLayout->addLayout(showMatchLayout);
@@ -525,11 +579,6 @@ void MacroConditionVideoEdit::SourceChanged(const QString &text)
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
bool needsPatternThreshold(VideoCondition cond)
{
return cond == VideoCondition::PATTERN;
}
void MacroConditionVideoEdit::ConditionChanged(int cond) void MacroConditionVideoEdit::ConditionChanged(int cond)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -644,6 +693,17 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
_entryData->_patternThreshold = value; _entryData->_patternThreshold = value;
} }
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useAlphaAsMask = value;
_entryData->LoadImageFromFile();
}
void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value) void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -734,7 +794,7 @@ QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
rectangle(matchImg, {col, row}, rectangle(matchImg, {col, row},
cv::Point(col + pattern.width(), cv::Point(col + pattern.width(),
row + pattern.height()), row + pattern.height()),
cv::Scalar(255, 0, 0, 0), 2, 8, 0); cv::Scalar(255, 0, 0, 255), 2, 8, 0);
} }
} }
} }
@@ -748,7 +808,7 @@ QImage markObjects(QImage &image, std::vector<cv::Rect> &objects)
rectangle(frame, cv::Point(objects[i].x, objects[i].y), rectangle(frame, cv::Point(objects[i].x, objects[i].y),
cv::Point(objects[i].x + objects[i].width, cv::Point(objects[i].x + objects[i].width,
objects[i].y + objects[i].height), objects[i].y + objects[i].height),
cv::Scalar(255, 0, 0, 0), 2, 8, 0); cv::Scalar(255, 0, 0, 255), 2, 8, 0);
} }
return MatToQImage(frame); return MatToQImage(frame);
} }
@@ -772,7 +832,8 @@ void MacroConditionVideoEdit::ShowMatchClicked()
if (_entryData->_condition == VideoCondition::PATTERN) { if (_entryData->_condition == VideoCondition::PATTERN) {
cv::Mat result; cv::Mat result;
matchPattern(screenshot->image, pattern, matchPattern(screenshot->image, pattern,
_entryData->_patternThreshold, result); _entryData->_patternThreshold, result,
_entryData->_useAlphaAsMask);
if (countNonZero(result) == 0) { if (countNonZero(result) == 0) {
DisplayMessage(obs_module_text( DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail")); "AdvSceneSwitcher.condition.video.patternMatchFail"));
@@ -851,13 +912,17 @@ void setLayoutVisible(QLayout *layout, bool visible)
void MacroConditionVideoEdit::SetWidgetVisibility() void MacroConditionVideoEdit::SetWidgetVisibility()
{ {
_imagePath->setVisible(requiresFileInput(_entryData->_condition)); _imagePath->setVisible(requiresFileInput(_entryData->_condition));
_patternThreshold->setVisible( _patternThreshold->setVisible(_entryData->_condition ==
needsPatternThreshold(_entryData->_condition)); VideoCondition::PATTERN);
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
_showMatch->setVisible(needsShowMatch(_entryData->_condition)); _showMatch->setVisible(needsShowMatch(_entryData->_condition));
_objectScaleThreshold->setVisible( _objectScaleThreshold->setVisible(
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
setLayoutVisible(_neighborsControlLayout, setLayoutVisible(_neighborsControlLayout,
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
_minNeighborsDescription->setVisible(
needsObjectControls(_entryData->_condition));
setLayoutVisible(_minSizeControlLayout, setLayoutVisible(_minSizeControlLayout,
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
setLayoutVisible(_maxSizeControlLayout, setLayoutVisible(_maxSizeControlLayout,
@@ -880,6 +945,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition)); _condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_imagePath->SetPath(QString::fromStdString(_entryData->_file)); _imagePath->SetPath(QString::fromStdString(_entryData->_file));
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold); _patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str()); _modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor); _objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
_minNeighbors->setValue(_entryData->_minNeighbors); _minNeighbors->setValue(_entryData->_minNeighbors);

View File

@@ -145,3 +145,8 @@ void MacroSegmentEdit::SetFocusPolicyOfWidgets()
w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w)); w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w));
} }
} }
void MacroSegmentEdit::SetCollapsed(bool collapsed)
{
_section->SetCollapsed(collapsed);
}

View File

@@ -170,42 +170,46 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
emit MacroRenamed(oldName, newName); emit MacroRenamed(oldName, newName);
} }
void AdvSceneSwitcher::PopulateMacroActions(Macro &m, int afterIdx)
{
bool root = afterIdx == 0;
auto &actions = m.Actions();
for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
ConnectControlSignals(newEntry);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(false);
root = false;
}
ui->macroEditActionHelp->setVisible(actions.size() == 0);
}
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, int afterIdx)
{
bool root = afterIdx == 0;
auto &conditions = m.Conditions();
for (; afterIdx < conditions.size(); afterIdx++) {
auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx],
conditions[afterIdx]->GetId(), root);
ConnectControlSignals(newEntry);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
root = false;
}
ui->macroEditConditionHelp->setVisible(conditions.size() == 0);
}
void AdvSceneSwitcher::SetEditMacro(Macro &m) void AdvSceneSwitcher::SetEditMacro(Macro &m)
{ {
ui->macroName->setText(m.Name().c_str()); ui->macroName->setText(m.Name().c_str());
clearLayout(ui->macroEditConditionLayout); clearLayout(ui->macroEditConditionLayout);
clearLayout(ui->macroEditActionLayout); clearLayout(ui->macroEditActionLayout);
bool root = true; PopulateMacroConditions(m);
for (auto &c : m.Conditions()) { PopulateMacroActions(m);
auto newEntry =
new MacroConditionEdit(this, &c, c->GetId(), root);
ConnectControlSignals(newEntry);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
root = false;
}
for (auto &a : m.Actions()) {
auto newEntry = new MacroActionEdit(this, &a, a->GetId());
ConnectControlSignals(newEntry);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(false);
}
ui->macroEdit->setDisabled(false); ui->macroEdit->setDisabled(false);
if (m.Conditions().size() == 0) {
ui->macroEditConditionHelp->setVisible(true);
} else {
ui->macroEditConditionHelp->setVisible(false);
}
if (m.Actions().size() == 0) {
ui->macroEditActionHelp->setVisible(true);
} else {
ui->macroEditActionHelp->setVisible(false);
}
} }
void AdvSceneSwitcher::HighlightAction(int idx) void AdvSceneSwitcher::HighlightAction(int idx)
@@ -400,17 +404,25 @@ void AdvSceneSwitcher::CopyMacro()
ui->macros->setCurrentItem(item); ui->macros->setCurrentItem(item);
} }
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < layout->count(); i++) {
item = layout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void AdvSceneSwitcher::ExpandAllActions() void AdvSceneSwitcher::ExpandAllActions()
{ {
auto m = getSelectedMacro(); auto m = getSelectedMacro();
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, false);
for (auto &a : m->Actions()) {
a->SetCollapsed(false);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::ExpandAllConditions() void AdvSceneSwitcher::ExpandAllConditions()
@@ -419,11 +431,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, false);
for (auto &c : m->Conditions()) {
c->SetCollapsed(false);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::CollapseAllActions() void AdvSceneSwitcher::CollapseAllActions()
@@ -432,11 +440,7 @@ void AdvSceneSwitcher::CollapseAllActions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, true);
for (auto &a : m->Actions()) {
a->SetCollapsed(true);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::CollapseAllConditions() void AdvSceneSwitcher::CollapseAllConditions()
@@ -445,9 +449,5 @@ void AdvSceneSwitcher::CollapseAllConditions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, true);
for (auto &c : m->Conditions()) {
c->SetCollapsed(true);
}
SetEditMacro(*m);
} }

View File

@@ -113,7 +113,7 @@ bool Macro::PerformAction()
return false; return false;
} }
} }
if (ret && _count != std::numeric_limits<int>::max()) { if (_count != std::numeric_limits<int>::max()) {
_count++; _count++;
} }
return ret; return ret;
@@ -492,7 +492,7 @@ bool SwitcherData::checkMacros()
bool SwitcherData::runMacros() bool SwitcherData::runMacros()
{ {
for (auto &m : macros) { for (auto m : macros) {
if (m.Matched()) { if (m.Matched()) {
vblog(LOG_INFO, "running macro: %s", m.Name().c_str()); vblog(LOG_INFO, "running macro: %s", m.Name().c_str());
if (!m.PerformAction()) { if (!m.PerformAction()) {

View File

@@ -156,7 +156,7 @@ void SceneTrigger::logMatch()
blog(LOG_INFO, blog(LOG_INFO,
"scene '%s' in status '%s' triggering action '%s' after %f seconds", "scene '%s' in status '%s' triggering action '%s' after %f seconds",
GetWeakSourceName(scene).c_str(), statusName.c_str(), GetWeakSourceName(scene).c_str(), statusName.c_str(),
actionName.c_str(), duration); actionName.c_str(), duration.seconds);
} }
void frontEndActionThread(sceneTriggerAction action, double delay) void frontEndActionThread(sceneTriggerAction action, double delay)
@@ -265,13 +265,15 @@ void SceneTrigger::performAction()
std::thread t; std::thread t;
if (isFrontendAction(triggerAction)) { if (isFrontendAction(triggerAction)) {
t = std::thread(frontEndActionThread, triggerAction, duration); t = std::thread(frontEndActionThread, triggerAction,
duration.seconds);
} else if (isAudioAction(triggerAction)) { } else if (isAudioAction(triggerAction)) {
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE; bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
t = std::thread(muteThread, audioSource, duration, mute); t = std::thread(muteThread, audioSource, duration.seconds,
mute);
} else if (isSwitcherStatusAction(triggerAction)) { } else if (isSwitcherStatusAction(triggerAction)) {
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER; bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration, stop); t = std::thread(statusThread, duration.seconds, stop);
} else { } else {
blog(LOG_WARNING, "ignoring unknown action '%d'", blog(LOG_WARNING, "ignoring unknown action '%d'",
static_cast<int>(triggerAction)); static_cast<int>(triggerAction));
@@ -374,7 +376,7 @@ void SceneTrigger::save(obs_data_t *obj)
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str()); obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType)); obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction)); obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
obs_data_set_double(obj, "duration", duration); duration.Save(obj, "duration");
obs_data_set_string(obj, "audioSource", obs_data_set_string(obj, "audioSource",
GetWeakSourceName(audioSource).c_str()); GetWeakSourceName(audioSource).c_str());
} }
@@ -388,7 +390,7 @@ void SceneTrigger::load(obs_data_t *obj)
obs_data_get_int(obj, "triggerType")); obs_data_get_int(obj, "triggerType"));
triggerAction = static_cast<sceneTriggerAction>( triggerAction = static_cast<sceneTriggerAction>(
obs_data_get_int(obj, "triggerAction")); obs_data_get_int(obj, "triggerAction"));
duration = obs_data_get_double(obj, "duration"); duration.Load(obj, "duration");
const char *audioSourceName = obs_data_get_string(obj, "audioSource"); const char *audioSourceName = obs_data_get_string(obj, "audioSource");
audioSource = GetWeakSourceByName(audioSourceName); audioSource = GetWeakSourceByName(audioSourceName);
@@ -451,19 +453,17 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
{ {
triggers = new QComboBox(); triggers = new QComboBox();
actions = new QComboBox(); actions = new QComboBox();
duration = new QDoubleSpinBox(); duration = new DurationSelection();
audioSources = new QComboBox(); audioSources = new QComboBox();
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerTypeChanged(int))); SLOT(TriggerTypeChanged(int)));
QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerActionChanged(int))); SLOT(TriggerActionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this, QWidget::connect(duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double))); SLOT(DurationChanged(double)));
QWidget::connect(duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(audioSources, QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(AudioSourceChanged(const QString &))); SLOT(AudioSourceChanged(const QString &)));
@@ -475,7 +475,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
if (s) { if (s) {
triggers->setCurrentIndex(static_cast<int>(s->triggerType)); triggers->setCurrentIndex(static_cast<int>(s->triggerType));
actions->setCurrentIndex(static_cast<int>(s->triggerAction)); actions->setCurrentIndex(static_cast<int>(s->triggerAction));
duration->setValue(s->duration); duration->SetDuration(s->duration);
audioSources->setCurrentText( audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str()); GetWeakSourceName(s->audioSource).c_str());
@@ -552,14 +552,24 @@ void SceneTriggerWidget::TriggerActionChanged(int index)
} }
} }
void SceneTriggerWidget::DurationChanged(double dur) void SceneTriggerWidget::DurationChanged(double seconds)
{ {
if (loading || !switchData) { if (loading || !switchData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur; switchData->duration.seconds = seconds;
}
void SceneTriggerWidget::DurationUnitChanged(DurationUnit unit)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration.displayUnit = unit;
} }
void SceneTriggerWidget::AudioSourceChanged(const QString &text) void SceneTriggerWidget::AudioSourceChanged(const QString &text)

View File

@@ -79,7 +79,7 @@ void AdvSSScreenshotObj::Copy()
uint8_t *videoData = nullptr; uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0; uint32_t videoLinesize = 0;
image = QImage(cx, cy, QImage::Format::Format_RGBX8888); image = QImage(cx, cy, QImage::Format::Format_RGBA8888);
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
int linesize = image.bytesPerLine(); int linesize = image.bytesPerLine();

View File

@@ -93,6 +93,11 @@ void Section::AddHeaderWidget(QWidget *w)
_headerWidgetLayout->addWidget(w); _headerWidgetLayout->addWidget(w);
} }
void Section::SetCollapsed(bool collapsed)
{
Collapse(collapsed);
}
bool Section::eventFilter(QObject *obj, QEvent *event) bool Section::eventFilter(QObject *obj, QEvent *event)
{ {
if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) { if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) {

View File

@@ -392,6 +392,11 @@ SceneSequenceSwitch *SceneSequenceSwitch::extend()
} }
extendedSequence = std::make_unique<SceneSequenceSwitch>(); extendedSequence = std::make_unique<SceneSequenceSwitch>();
extendedSequence->startScene = scene; extendedSequence->startScene = scene;
if (targetType == SwitchTargetType::SceneGroup) {
extendedSequence->startScene = nullptr;
extendedSequence->startTargetType =
SwitchTargetType::SceneGroup;
}
return extendedSequence.get(); return extendedSequence.get();
} }

View File

@@ -266,7 +266,7 @@ bool VideoSwitch::loadImageFromFile()
return false; return false;
} }
matchImage = matchImage =
matchImage.convertToFormat(QImage::Format::Format_RGBX8888); matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
return true; return true;
} }

View File

@@ -213,10 +213,10 @@ void deleteLayoutItem(QLayoutItem *item)
} }
} }
void clearLayout(QLayout *layout) void clearLayout(QLayout *layout, int afterIdx)
{ {
QLayoutItem *item; QLayoutItem *item;
while ((item = layout->takeAt(0))) { while (item = layout->takeAt(afterIdx)) {
if (item->layout()) { if (item->layout()) {
clearLayout(item->layout()); clearLayout(item->layout());
delete item->layout(); delete item->layout();