From 2456f0228ebb6d4145dc8add85b0b027548ab52d Mon Sep 17 00:00:00 2001 From: Clayton Groeneveld Date: Fri, 9 Apr 2021 16:47:21 -0500 Subject: [PATCH] UI: Add ability to set global scenes Also known as a downstream keyer. To use, right click as the scene and select 'Set as Global.' To reset, right click again and click 'Remove as Global.' When the user sets scene as global, a pin icon will be shown with the list item. The currently selected global scene has a red pin icon next to them. This does not implement transitions between the global scenes, as that will have to be a future endeavor. --- UI/data/locale/en-US.ini | 2 + UI/data/themes/Acri.qss | 2 + UI/data/themes/Dark.qss | 2 + UI/data/themes/Dark/pin.svg | 3 + UI/data/themes/Rachni.qss | 2 + UI/data/themes/System.qss | 2 + UI/forms/images/pin.svg | 3 + UI/forms/images/pin_selected.svg | 3 + UI/forms/obs.qrc | 2 + UI/scene-tree.cpp | 1 + UI/window-basic-main-icons.cpp | 20 ++++++ UI/window-basic-main-transitions.cpp | 5 +- UI/window-basic-main.cpp | 97 +++++++++++++++++++++++++++- UI/window-basic-main.hpp | 16 +++++ 14 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 UI/data/themes/Dark/pin.svg create mode 100644 UI/forms/images/pin.svg create mode 100644 UI/forms/images/pin_selected.svg diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 0d898995b83b5d..0f1e92fba79f48 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -101,6 +101,8 @@ LogViewer="Log Viewer" ShowOnStartup="Show on startup" OpenFile="Open file" AddValue="Add %1" +SetAsGlobal="Set as Global" +RemoveAsGlobal="Remove as Global" # warning if program already open AlreadyRunning.Title="OBS is already running" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 1a8d2450140d24..06f61a2e9f4da6 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -1070,6 +1070,8 @@ OBSBasic { qproperty-groupIcon: url(./Dark/sources/group.svg); qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); + qproperty-pinIcon: url(./Dark/pin.svg); + qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg); } /* Scene Tree */ diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index c8612ead0e3062..d927a2920f9a08 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -795,6 +795,8 @@ OBSBasic { qproperty-groupIcon: url(./Dark/sources/group.svg); qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); + qproperty-pinIcon: url(./Dark/pin.svg); + qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg); } /* Scene Tree */ diff --git a/UI/data/themes/Dark/pin.svg b/UI/data/themes/Dark/pin.svg new file mode 100644 index 00000000000000..7fcab91ceef838 --- /dev/null +++ b/UI/data/themes/Dark/pin.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 7e069345f52f19..75a8569a8cd948 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -1381,6 +1381,8 @@ OBSBasic { qproperty-groupIcon: url(./Dark/sources/group.svg); qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); + qproperty-pinIcon: url(./Dark/pin.svg); + qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg); } /* Scene Tree */ diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 93c935a5477c0a..a169562e6dabbc 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -231,6 +231,8 @@ OBSBasic { qproperty-groupIcon: url(:res/images/sources/group.svg); qproperty-sceneIcon: url(:res/images/sources/scene.svg); qproperty-defaultIcon: url(:res/images/sources/default.svg); + qproperty-pinIcon: url(:res/images/pin.svg); + qproperty-pinSelectedIcon: url(:res/images/pin_selected.svg); } /* Scene Tree */ diff --git a/UI/forms/images/pin.svg b/UI/forms/images/pin.svg new file mode 100644 index 00000000000000..abbb76a595e475 --- /dev/null +++ b/UI/forms/images/pin.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/forms/images/pin_selected.svg b/UI/forms/images/pin_selected.svg new file mode 100644 index 00000000000000..bb51797c3df5ae --- /dev/null +++ b/UI/forms/images/pin_selected.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc index 9ff1a1d7e9264d..e41d8488c931b5 100644 --- a/UI/forms/obs.qrc +++ b/UI/forms/obs.qrc @@ -53,6 +53,8 @@ images/media/media_restart.svg images/media/media_stop.svg images/interact.svg + images/pin.svg + images/pin_selected.svg images/settings/output.svg diff --git a/UI/scene-tree.cpp b/UI/scene-tree.cpp index 8e3006305b68e7..b00e3dfc258429 100644 --- a/UI/scene-tree.cpp +++ b/UI/scene-tree.cpp @@ -13,6 +13,7 @@ SceneTree::SceneTree(QWidget *parent_) : QListWidget(parent_) installEventFilter(this); setDragDropMode(InternalMove); setMovement(QListView::Snap); + setIconSize(QSize(16, 16)); } void SceneTree::SetGridMode(bool grid) diff --git a/UI/window-basic-main-icons.cpp b/UI/window-basic-main-icons.cpp index 56096bbed66a15..849455e6f772d6 100644 --- a/UI/window-basic-main-icons.cpp +++ b/UI/window-basic-main-icons.cpp @@ -112,6 +112,16 @@ void OBSBasic::SetDefaultIcon(const QIcon &icon) defaultIcon = icon; } +void OBSBasic::SetPinIcon(const QIcon &icon) +{ + pinIcon = icon; +} + +void OBSBasic::SetPinSelectedIcon(const QIcon &icon) +{ + pinSelectedIcon = icon; +} + QIcon OBSBasic::GetImageIcon() const { return imageIcon; @@ -186,3 +196,13 @@ QIcon OBSBasic::GetDefaultIcon() const { return defaultIcon; } + +QIcon OBSBasic::GetPinIcon() const +{ + return pinIcon; +} + +QIcon OBSBasic::GetPinSelectedIcon() const +{ + return pinSelectedIcon; +} diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index b68e359feb4540..8d77210ae4d7e9 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -722,7 +722,10 @@ template static T GetOBSRef(QListWidgetItem *item) void OBSBasic::SetCurrentScene(OBSSource scene, bool force) { - if (!IsPreviewProgramMode()) { + if (SceneIsGlobal(obs_scene_from_source(scene))) { + obs_set_output_source(7, scene); + UpdatePinIcons(); + } else if (!IsPreviewProgramMode()) { TransitionToScene(scene, force); } else { OBSSource actualLastScene = OBSGetStrongRef(lastScene); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 9ed75982f6ba82..670214628b9c62 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -443,6 +443,8 @@ OBSBasic::OBSBasic(QWidget *parent) connect(ui->scenes, SIGNAL(scenesReordered()), this, SLOT(ScenesReordered())); + + connect(App(), &OBSApp::StyleChanged, this, &OBSBasic::UpdatePinIcons); } static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, @@ -513,14 +515,17 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, /* -------------------------------- */ obs_source_t *transition = obs_get_output_source(0); + obs_source_t *global = obs_get_output_source(7); obs_source_t *currentScene = obs_scene_get_source(scene); const char *sceneName = obs_source_get_name(currentScene); const char *programName = obs_source_get_name(curProgramScene); + const char *globalSceneName = obs_source_get_name(global); const char *sceneCollection = config_get_string( App()->GlobalConfig(), "Basic", "SceneCollection"); obs_data_set_string(saveData, "current_scene", sceneName); + obs_data_set_string(saveData, "current_global_scene", globalSceneName); obs_data_set_string(saveData, "current_program_scene", programName); obs_data_set_array(saveData, "scene_order", sceneOrder); obs_data_set_string(saveData, "name", sceneCollection); @@ -536,6 +541,7 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_source_get_name(transition)); obs_data_set_int(saveData, "transition_duration", transitionDuration); obs_source_release(transition); + obs_source_release(global); return saveData; } @@ -962,6 +968,8 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) obs_data_array_t *groups = obs_data_get_array(data, "groups"); obs_data_array_t *transitions = obs_data_get_array(data, "transitions"); const char *sceneName = obs_data_get_string(data, "current_scene"); + const char *globalSceneName = + obs_data_get_string(data, "current_global_scene"); const char *programSceneName = obs_data_get_string(data, "current_program_scene"); const char *transitionName = @@ -989,6 +997,7 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) obs_source_t *curScene; obs_source_t *curProgramScene; obs_source_t *curTransition; + obs_source_t *curGlobalScene; if (!name || !*name) name = curSceneCollection; @@ -1038,6 +1047,7 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) retryScene: curScene = obs_get_source_by_name(sceneName); curProgramScene = obs_get_source_by_name(programSceneName); + curGlobalScene = obs_get_source_by_name(globalSceneName); /* if the starting scene command line parameter is bad at all, * fall back to original settings */ @@ -1056,11 +1066,11 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) obs_source_addref(curScene); } - SetCurrentScene(curScene, true); if (IsPreviewProgramMode()) TransitionToScene(curProgramScene, true); obs_source_release(curScene); obs_source_release(curProgramScene); + obs_source_release(curGlobalScene); obs_data_array_release(sources); obs_data_array_release(groups); @@ -1175,6 +1185,10 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) obs_missing_files_destroy(files); } + LoadGlobalScenes(); + SetCurrentScene(curGlobalScene); + SetCurrentScene(curScene, true); + disableSaving--; if (api) { @@ -4787,6 +4801,16 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) connect(pasteFilters, SIGNAL(triggered()), this, SLOT(ScenePasteFilters())); + popup.addSeparator(); + + bool global = SceneIsGlobal(GetCurrentScene()); + if (!global) + popup.addAction(QTStr("SetAsGlobal"), this, + SLOT(SetSceneAsGlobal())); + else + popup.addAction(QTStr("RemoveAsGlobal"), this, + SLOT(RemoveSceneAsGlobal())); + popup.addSeparator(); popup.addAction(QTStr("Duplicate"), this, SLOT(DuplicateSelectedScene())); @@ -9175,3 +9199,74 @@ void OBSBasic::ShowStatusBarMessage(const QString &message) ui->statusbar->clearMessage(); ui->statusbar->showMessage(message, 10000); } + +void OBSBasic::LoadGlobalScenes() +{ + for (int i = 0; i < ui->scenes->count(); i++) { + QListWidgetItem *item = ui->scenes->item(i); + OBSScene scene = GetOBSRef(item); + + if (SceneIsGlobal(scene)) + item->setIcon(GetPinIcon()); + } +} + +bool OBSBasic::SceneIsGlobal(OBSScene scene) +{ + OBSSource source = obs_scene_get_source(scene); + obs_data_t *priv_settings = obs_source_get_private_settings(source); + bool dsk = obs_data_get_bool(priv_settings, "is_dsk"); + obs_data_release(priv_settings); + + return dsk; +} + +void OBSBasic::SetSceneAsGlobal() +{ + OBSSource source = GetCurrentSceneSource(); + + obs_data_t *priv_settings = obs_source_get_private_settings(source); + obs_data_set_bool(priv_settings, "is_dsk", true); + obs_data_release(priv_settings); + + QListWidgetItem *item = ui->scenes->currentItem(); + item->setIcon(GetPinIcon()); + + SetCurrentScene(source); +} + +void OBSBasic::RemoveSceneAsGlobal() +{ + OBSSource source = GetCurrentSceneSource(); + + obs_data_t *priv_settings = obs_source_get_private_settings(source); + obs_data_set_bool(priv_settings, "is_dsk", false); + obs_data_release(priv_settings); + + QListWidgetItem *item = ui->scenes->currentItem(); + item->setIcon(QIcon()); + obs_set_output_source(7, nullptr); +} + +void OBSBasic::UpdatePinIcons() +{ + OBSSource globalSource = obs_get_output_source(7); + + if (!globalSource) + return; + + for (int i = 0; i < ui->scenes->count(); i++) { + QListWidgetItem *item = ui->scenes->item(i); + OBSScene scene = GetOBSRef(item); + OBSSource sceneSource = obs_scene_get_source(scene); + + if (SceneIsGlobal(scene)) { + if (globalSource == sceneSource) + item->setIcon(GetPinSelectedIcon()); + else + item->setIcon(GetPinIcon()); + } + } + + obs_source_release(globalSource); +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index e6a1f665ad374f..1ed70818a8e566 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -152,6 +152,10 @@ class OBSBasic : public OBSMainWindow { DESIGNABLE true) Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon DESIGNABLE true) + Q_PROPERTY( + QIcon pinIcon READ GetPinIcon WRITE SetPinIcon DESIGNABLE true) + Q_PROPERTY(QIcon pinSelectedIcon READ GetPinSelectedIcon WRITE + SetPinSelectedIcon DESIGNABLE true) friend class OBSAbout; friend class OBSBasicPreview; @@ -526,6 +530,8 @@ class OBSBasic : public OBSMainWindow { QIcon groupIcon; QIcon sceneIcon; QIcon defaultIcon; + QIcon pinIcon; + QIcon pinSelectedIcon; QIcon GetImageIcon() const; QIcon GetColorIcon() const; @@ -540,6 +546,8 @@ class OBSBasic : public OBSMainWindow { QIcon GetMediaIcon() const; QIcon GetBrowserIcon() const; QIcon GetDefaultIcon() const; + QIcon GetPinIcon() const; + QIcon GetPinSelectedIcon() const; QSlider *tBar; bool tBarActive = false; @@ -709,6 +717,8 @@ private slots: void SetGroupIcon(const QIcon &icon); void SetSceneIcon(const QIcon &icon); void SetDefaultIcon(const QIcon &icon); + void SetPinIcon(const QIcon &icon); + void SetPinSelectedIcon(const QIcon &icon); void TBarChanged(int value); void TBarReleased(); @@ -1048,6 +1058,12 @@ public slots: void UpdateContextBar(bool force = false); void UpdateContextBarDeferred(bool force = false); + bool SceneIsGlobal(OBSScene scene); + void SetSceneAsGlobal(); + void RemoveSceneAsGlobal(); + void LoadGlobalScenes(); + void UpdatePinIcons(); + public: explicit OBSBasic(QWidget *parent = 0); virtual ~OBSBasic();