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();