diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini
index 014035d81..b3140c932 100644
--- a/obs/data/locale/en-US.ini
+++ b/obs/data/locale/en-US.ini
@@ -59,10 +59,17 @@ QuickTransitions.EditPropertiesTT="When editing the same scene, allows editing p
QuickTransitions.HotkeyName="Quick Transition: %1"
# transitions
+Basic.AddTransition="Add Configurable Transition"
+Basic.RemoveTransition="Remove Configurable Transition"
+Basic.TransitionProperties="Transition Properties"
Basic.SceneTransitions="Scene Transitions"
Basic.TransitionDuration="Duration"
Basic.TogglePreviewProgramMode="Studio Mode"
+# transition name dialog
+TransitionNameDlg.Text="Please enter the name of the transition"
+TransitionNameDlg.Title="Transition Name"
+
# title bar strings
TitleBar.Profile="Profile"
TitleBar.Scenes="Scenes"
diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui
index fae7d3375..0bd019194 100644
--- a/obs/forms/OBSBasic.ui
+++ b/obs/forms/OBSBasic.ui
@@ -581,10 +581,103 @@
4
-
-
-
- 2
+
+
+
+ 120
+ 0
+
+
+
+ -
+
+
+ 4
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 22
+ 22
+
+
+
+ Basic.AddTransition
+
+
+ Basic.AddTransition
+
+
+
+
+
+
+ :/res/images/add.png:/res/images/add.png
+
+
+ true
+
+
+ addIconSmall
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 22
+ 22
+
+
+
+ Basic.RemoveTransition
+
+
+ Basic.RemoveTransition
+
+
+
+
+
+
+ :/res/images/list_remove.png:/res/images/list_remove.png
+
+
+ true
+
+
+ removeIconSmall
+
+
+
-
@@ -599,6 +692,12 @@
22
+
+ Basic.TransitionProperties
+
+
+ Basic.TransitionProperties
+
@@ -614,16 +713,6 @@
- -
-
-
-
- 120
- 0
-
-
-
-
-
diff --git a/obs/window-basic-main-transitions.cpp b/obs/window-basic-main-transitions.cpp
index c2488c0d9..f96261256 100644
--- a/obs/window-basic-main-transitions.cpp
+++ b/obs/window-basic-main-transitions.cpp
@@ -18,9 +18,11 @@
#include
#include
#include
+#include
#include
#include "window-basic-main.hpp"
#include "display-helpers.hpp"
+#include "window-namedialog.hpp"
#include "menu-button.hpp"
#include "qt-wrappers.hpp"
@@ -301,9 +303,6 @@ void OBSBasic::SetTransition(obs_source_t *transition)
SetComboTransition(ui->transitions, transition);
obs_set_output_source(0, transition);
obs_transition_swap_end(transition, oldTransition);
-
- bool showPropertiesButton = obs_source_configurable(transition);
- ui->transitionProps->setVisible(showPropertiesButton);
} else {
obs_set_output_source(0, transition);
}
@@ -314,6 +313,10 @@ void OBSBasic::SetTransition(obs_source_t *transition)
bool fixed = transition ? obs_transition_fixed(transition) : false;
ui->transitionDurationLabel->setVisible(!fixed);
ui->transitionDuration->setVisible(!fixed);
+
+ bool configurable = obs_source_configurable(transition);
+ ui->transitionRemove->setEnabled(configurable);
+ ui->transitionProps->setEnabled(configurable);
}
OBSSource OBSBasic::GetCurrentTransition()
@@ -327,9 +330,170 @@ void OBSBasic::on_transitions_currentIndexChanged(int)
SetTransition(transition);
}
+void OBSBasic::AddTransition()
+{
+ QAction *action = reinterpret_cast(sender());
+ QString idStr = action->property("id").toString();
+
+ string name;
+ QString placeHolderText = QT_UTF8(
+ obs_source_get_display_name(QT_TO_UTF8(idStr)));
+ QString format = placeHolderText + " (%1)";
+ obs_source_t *source = nullptr;
+ int i = 1;
+
+ while ((source = FindTransition(QT_TO_UTF8(placeHolderText)))) {
+ placeHolderText = format.arg(++i);
+ }
+
+ bool accepted = NameDialog::AskForName(this,
+ QTStr("TransitionNameDlg.Title"),
+ QTStr("TransitionNameDlg.Text"),
+ name, placeHolderText);
+
+ if (accepted) {
+ if (name.empty()) {
+ QMessageBox::information(this,
+ QTStr("NoNameEntered.Title"),
+ QTStr("NoNameEntered.Text"));
+ AddTransition();
+ return;
+ }
+
+ source = FindTransition(name.c_str());
+ if (source) {
+ QMessageBox::information(this,
+ QTStr("NameExists.Title"),
+ QTStr("NameExists.Text"));
+
+ AddTransition();
+ return;
+ }
+
+ source = obs_source_create_private(QT_TO_UTF8(idStr),
+ name.c_str(), NULL);
+ InitTransition(source);
+ ui->transitions->addItem(QT_UTF8(name.c_str()),
+ QVariant::fromValue(OBSSource(source)));
+ ui->transitions->setCurrentIndex(ui->transitions->count() - 1);
+ CreatePropertiesWindow(source);
+ obs_source_release(source);
+ }
+}
+
+void OBSBasic::on_transitionAdd_clicked()
+{
+ bool foundConfigurableTransitions = false;
+ QMenu menu(this);
+ size_t idx = 0;
+ const char *id;
+
+ while (obs_enum_transition_types(idx++, &id)) {
+ if (obs_is_source_configurable(id)) {
+ const char *name = obs_source_get_display_name(id);
+ QAction *action = new QAction(name, this);
+ action->setProperty("id", id);
+
+ connect(action, SIGNAL(triggered()),
+ this, SLOT(AddTransition()));
+
+ menu.addAction(action);
+ foundConfigurableTransitions = true;
+ }
+ }
+
+ if (foundConfigurableTransitions)
+ menu.exec(QCursor::pos());
+}
+
+void OBSBasic::on_transitionRemove_clicked()
+{
+ OBSSource tr = GetCurrentTransition();
+
+ if (!tr || !obs_source_configurable(tr) || !QueryRemoveSource(tr))
+ return;
+
+ int idx = ui->transitions->findData(QVariant::fromValue(tr));
+ if (idx == -1)
+ return;
+
+ for (size_t i = quickTransitions.size(); i > 0; i--) {
+ QuickTransition &qt = quickTransitions[i - 1];
+ if (qt.source == tr) {
+ if (qt.button)
+ qt.button->deleteLater();
+ RemoveQuickTransitionHotkey(&qt);
+ quickTransitions.erase(quickTransitions.begin() + i - 1);
+ }
+ }
+
+ ui->transitions->removeItem(idx);
+}
+
+void OBSBasic::RenameTransition()
+{
+ QAction *action = reinterpret_cast(sender());
+ QVariant variant = action->property("transition");
+ obs_source_t *transition = variant.value();
+
+ string name;
+ QString placeHolderText = QT_UTF8(obs_source_get_name(transition));
+ obs_source_t *source = nullptr;
+
+ bool accepted = NameDialog::AskForName(this,
+ QTStr("TransitionNameDlg.Title"),
+ QTStr("TransitionNameDlg.Text"),
+ name, placeHolderText);
+
+ if (accepted) {
+ if (name.empty()) {
+ QMessageBox::information(this,
+ QTStr("NoNameEntered.Title"),
+ QTStr("NoNameEntered.Text"));
+ RenameTransition();
+ return;
+ }
+
+ source = FindTransition(name.c_str());
+ if (source) {
+ QMessageBox::information(this,
+ QTStr("NameExists.Title"),
+ QTStr("NameExists.Text"));
+
+ RenameTransition();
+ return;
+ }
+
+ obs_source_set_name(transition, name.c_str());
+ int idx = ui->transitions->findData(variant);
+ if (idx != -1)
+ ui->transitions->setItemText(idx, QT_UTF8(name.c_str()));
+ }
+}
+
void OBSBasic::on_transitionProps_clicked()
{
- // TODO
+ OBSSource source = GetCurrentTransition();
+
+ if (!obs_source_configurable(source))
+ return;
+
+ auto properties = [&] () {
+ CreatePropertiesWindow(source);
+ };
+
+ QMenu menu(this);
+
+ QAction *action = new QAction(QTStr("Rename"), this);
+ connect(action, SIGNAL(triggered()), this, SLOT(RenameTransition()));
+ action->setProperty("transition", QVariant::fromValue(source));
+ menu.addAction(action);
+
+ action = new QAction(QTStr("Properties"), this);
+ connect(action, &QAction::triggered, properties);
+ menu.addAction(action);
+
+ menu.exec(QCursor::pos());
}
QuickTransition *OBSBasic::GetQuickTransition(int id)
@@ -875,3 +1039,51 @@ void OBSBasic::ResizeProgram(uint32_t cx, uint32_t cy)
programX += float(PREVIEW_EDGE_SIZE);
programY += float(PREVIEW_EDGE_SIZE);
}
+
+obs_data_array_t *OBSBasic::SaveTransitions()
+{
+ obs_data_array_t *transitions = obs_data_array_create();
+
+ for (int i = 0; i < ui->transitions->count(); i++) {
+ OBSSource tr = ui->transitions->itemData(i).value();
+ if (!obs_source_configurable(tr))
+ continue;
+
+ obs_data_t *sourceData = obs_data_create();
+ obs_data_t *settings = obs_source_get_settings(tr);
+
+ obs_data_set_string(sourceData, "name", obs_source_get_name(tr));
+ obs_data_set_string(sourceData, "id", obs_obj_get_id(tr));
+ obs_data_set_obj(sourceData, "settings", settings);
+
+ obs_data_array_push_back(transitions, sourceData);
+
+ obs_data_release(settings);
+ obs_data_release(sourceData);
+ }
+
+ return transitions;
+}
+
+void OBSBasic::LoadTransitions(obs_data_array_t *transitions)
+{
+ size_t count = obs_data_array_count(transitions);
+
+ for (size_t i = 0; i < count; i++) {
+ obs_data_t *item = obs_data_array_item(transitions, i);
+ const char *name = obs_data_get_string(item, "name");
+ const char *id = obs_data_get_string(item, "id");
+ obs_data_t *settings = obs_data_get_obj(item, "settings");
+
+ obs_source_t *source = obs_source_create_private(id, name,
+ settings);
+ InitTransition(source);
+ ui->transitions->addItem(QT_UTF8(name),
+ QVariant::fromValue(OBSSource(source)));
+ ui->transitions->setCurrentIndex(ui->transitions->count() - 1);
+ obs_source_release(source);
+
+ obs_data_release(settings);
+ obs_data_release(item);
+ }
+}
diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp
index 78d8954f3..ce205056a 100644
--- a/obs/window-basic-main.cpp
+++ b/obs/window-basic-main.cpp
@@ -247,6 +247,7 @@ static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
obs_data_array_t *quickTransitionData, int transitionDuration,
+ obs_data_array_t *transitions,
OBSScene &scene, OBSSource &curProgramScene)
{
obs_data_t *saveData = obs_data_create();
@@ -287,6 +288,7 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
obs_data_set_string(saveData, "name", sceneCollection);
obs_data_set_array(saveData, "sources", sourcesArray);
obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
+ obs_data_set_array(saveData, "transitions", transitions);
obs_data_array_release(sourcesArray);
obs_data_set_string(saveData, "current_transition",
@@ -352,9 +354,10 @@ void OBSBasic::Save(const char *file)
curProgramScene = obs_scene_get_source(scene);
obs_data_array_t *sceneOrder = SaveSceneListOrder();
+ obs_data_array_t *transitions = SaveTransitions();
obs_data_array_t *quickTrData = SaveQuickTransitions();
obs_data_t *saveData = GenerateSaveData(sceneOrder, quickTrData,
- ui->transitionDuration->value(),
+ ui->transitionDuration->value(), transitions,
scene, curProgramScene);
if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
@@ -363,6 +366,7 @@ void OBSBasic::Save(const char *file)
obs_data_release(saveData);
obs_data_array_release(sceneOrder);
obs_data_array_release(quickTrData);
+ obs_data_array_release(transitions);
}
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
@@ -488,6 +492,7 @@ void OBSBasic::Load(const char *file)
obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
obs_data_array_t *sources = obs_data_get_array(data, "sources");
+ obs_data_array_t *transitions= obs_data_get_array(data, "transitions");
const char *sceneName = obs_data_get_string(data,
"current_scene");
const char *programSceneName = obs_data_get_string(data,
@@ -523,9 +528,13 @@ void OBSBasic::Load(const char *file)
obs_load_sources(sources);
+ if (transitions)
+ LoadTransitions(transitions);
if (sceneOrder)
LoadSceneListOrder(sceneOrder);
+ obs_data_array_release(transitions);
+
curTransition = FindTransition(transitionName);
if (!curTransition)
curTransition = fadeTransition;
diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp
index 8dc646864..7ccf1a262 100644
--- a/obs/window-basic-main.hpp
+++ b/obs/window-basic-main.hpp
@@ -220,6 +220,8 @@ private:
obs_source_t *FindTransition(const char *name);
void SetTransition(obs_source_t *transition);
OBSSource GetCurrentTransition();
+ obs_data_array_t *SaveTransitions();
+ void LoadTransitions(obs_data_array_t *transitions);
obs_source_t *fadeTransition;
@@ -314,6 +316,8 @@ private slots:
void ProcessHotkey(obs_hotkey_id id, bool pressed);
+ void AddTransition();
+ void RenameTransition();
void TransitionClicked();
void TransitionStopped();
void TriggerQuickTransition(int id);
@@ -465,6 +469,8 @@ private slots:
void on_actionAlwaysOnTop_triggered();
void on_transitions_currentIndexChanged(int index);
+ void on_transitionAdd_clicked();
+ void on_transitionRemove_clicked();
void on_transitionProps_clicked();
void on_modeSwitch_clicked();