Implements transitions, and introduces "Studio Mode" which allows live editing of the same or different scenes while preserving what's currently being displayed. Studio Mode offers a number of new features: - The ability to edit different scenes or the same scene without modifying what's currently being displayed (of course) - The ability to set up "quick transitions" with a desired transition and duration that can be assigned hotkeys - The option to create full copies of all sources in the program scene to allow editing of source properties of the same scene live without modifying the output, or (by default) just use references. (Note however that certain sources cannot be duplicated, such as capture sources, media sources, and device sources) - Swap Mode (enabled by default) which swaps the program scene with the preview scene when a transition completes Currently, only non-configurable transitions (transitions without properties) are listed, and the only transitions available as of this writing are fade and cut. In future versions more transitions will be added, such as swipe, stingers, and many other various sort of transitions, and the UI will support being able to add/configure/remove those sort of configurable transitions.
868 lines
22 KiB
C++
868 lines
22 KiB
C++
/******************************************************************************
|
|
Copyright (C) 2016 by Hugh Bailey <obs.jim@gmail.com>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
******************************************************************************/
|
|
|
|
#include <QSpinBox>
|
|
#include <QWidgetAction>
|
|
#include <QToolTip>
|
|
#include <util/dstr.hpp>
|
|
#include "window-basic-main.hpp"
|
|
#include "display-helpers.hpp"
|
|
#include "menu-button.hpp"
|
|
#include "qt-wrappers.hpp"
|
|
|
|
using namespace std;
|
|
|
|
Q_DECLARE_METATYPE(OBSScene);
|
|
Q_DECLARE_METATYPE(OBSSource);
|
|
Q_DECLARE_METATYPE(QuickTransition);
|
|
|
|
static inline QString MakeQuickTransitionText(QuickTransition *qt)
|
|
{
|
|
QString name = QT_UTF8(obs_source_get_name(qt->source));
|
|
if (!obs_transition_fixed(qt->source))
|
|
name += QString(" (%1ms)").arg(QString::number(qt->duration));
|
|
return name;
|
|
}
|
|
|
|
void OBSBasic::InitDefaultTransitions()
|
|
{
|
|
std::vector<OBSSource> transitions;
|
|
size_t idx = 0;
|
|
const char *id;
|
|
|
|
/* automatically add transitions that have no configuration (things
|
|
* such as cut/fade/etc) */
|
|
while (obs_enum_transition_types(idx++, &id)) {
|
|
if (!obs_is_source_configurable(id)) {
|
|
const char *name = obs_source_get_display_name(id);
|
|
|
|
obs_source_t *tr = obs_source_create_private(
|
|
id, name, NULL);
|
|
InitTransition(tr);
|
|
transitions.emplace_back(tr);
|
|
|
|
if (strcmp(id, "fade_transition") == 0)
|
|
fadeTransition = tr;
|
|
|
|
obs_source_release(tr);
|
|
}
|
|
}
|
|
|
|
for (OBSSource &tr : transitions) {
|
|
ui->transitions->addItem(QT_UTF8(obs_source_get_name(tr)),
|
|
QVariant::fromValue(OBSSource(tr)));
|
|
}
|
|
}
|
|
|
|
void OBSBasic::AddQuickTransitionHotkey(QuickTransition *qt)
|
|
{
|
|
DStr hotkeyId;
|
|
QString hotkeyName;
|
|
|
|
dstr_printf(hotkeyId, "OBSBasic.QuickTransition.%d", qt->id);
|
|
hotkeyName = QTStr("QuickTransitions.HotkeyName")
|
|
.arg(MakeQuickTransitionText(qt));
|
|
|
|
auto quickTransition = [] (void *data, obs_hotkey_id, obs_hotkey_t*,
|
|
bool pressed)
|
|
{
|
|
int id = (int)(uintptr_t)data;
|
|
OBSBasic *main =
|
|
reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
|
|
|
|
if (pressed)
|
|
QMetaObject::invokeMethod(main,
|
|
"TriggerQuickTransition",
|
|
Qt::QueuedConnection,
|
|
Q_ARG(int, id));
|
|
};
|
|
|
|
qt->hotkey = obs_hotkey_register_frontend(hotkeyId->array,
|
|
QT_TO_UTF8(hotkeyName), quickTransition,
|
|
(void*)(uintptr_t)qt->id);
|
|
}
|
|
|
|
void OBSBasic::TriggerQuickTransition(int id)
|
|
{
|
|
QuickTransition *qt = GetQuickTransition(id);
|
|
|
|
if (qt && previewProgramMode) {
|
|
OBSScene scene = GetCurrentScene();
|
|
obs_source_t *source = obs_scene_get_source(scene);
|
|
|
|
ui->transitionDuration->setValue(qt->duration);
|
|
if (GetCurrentTransition() != qt->source)
|
|
SetTransition(qt->source);
|
|
|
|
TransitionToScene(source);
|
|
}
|
|
}
|
|
|
|
void OBSBasic::RemoveQuickTransitionHotkey(QuickTransition *qt)
|
|
{
|
|
obs_hotkey_unregister(qt->hotkey);
|
|
}
|
|
|
|
void OBSBasic::InitTransition(obs_source_t *transition)
|
|
{
|
|
auto onTransitionStop = [] (void *data, calldata_t*) {
|
|
OBSBasic *window = (OBSBasic*)data;
|
|
QMetaObject::invokeMethod(window, "TransitionStopped",
|
|
Qt::QueuedConnection);
|
|
};
|
|
|
|
signal_handler_t *handler = obs_source_get_signal_handler(transition);
|
|
signal_handler_connect(handler, "transition_video_stop",
|
|
onTransitionStop, this);
|
|
}
|
|
|
|
static inline OBSSource GetTransitionComboItem(QComboBox *combo, int idx)
|
|
{
|
|
return combo->itemData(idx).value<OBSSource>();
|
|
}
|
|
|
|
void OBSBasic::CreateDefaultQuickTransitions()
|
|
{
|
|
/* non-configurable transitions are always available, so add them
|
|
* to the "default quick transitions" list */
|
|
quickTransitions.emplace_back(
|
|
GetTransitionComboItem(ui->transitions, 0),
|
|
300, quickTransitionIdCounter++);
|
|
quickTransitions.emplace_back(
|
|
GetTransitionComboItem(ui->transitions, 1),
|
|
300, quickTransitionIdCounter++);
|
|
}
|
|
|
|
void OBSBasic::LoadQuickTransitions(obs_data_array_t *array)
|
|
{
|
|
size_t count = obs_data_array_count(array);
|
|
|
|
quickTransitionIdCounter = 1;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
obs_data_t *data = obs_data_array_item(array, i);
|
|
obs_data_array_t *hotkeys = obs_data_get_array(data, "hotkeys");
|
|
const char *name = obs_data_get_string(data, "name");
|
|
int duration = obs_data_get_int(data, "duration");
|
|
int id = obs_data_get_int(data, "id");
|
|
|
|
if (id) {
|
|
obs_source_t *source = FindTransition(name);
|
|
quickTransitions.emplace_back(source, duration, id);
|
|
|
|
if (quickTransitionIdCounter <= id)
|
|
quickTransitionIdCounter = id + 1;
|
|
|
|
int idx = (int)quickTransitions.size() - 1;
|
|
AddQuickTransitionHotkey(&quickTransitions[idx]);
|
|
obs_hotkey_load(quickTransitions[idx].hotkey, hotkeys);
|
|
}
|
|
|
|
obs_data_release(data);
|
|
obs_data_array_release(hotkeys);
|
|
}
|
|
}
|
|
|
|
obs_data_array_t *OBSBasic::SaveQuickTransitions()
|
|
{
|
|
obs_data_array_t *array = obs_data_array_create();
|
|
|
|
for (QuickTransition &qt : quickTransitions) {
|
|
obs_data_t *data = obs_data_create();
|
|
obs_data_array_t *hotkeys = obs_hotkey_save(qt.hotkey);
|
|
|
|
obs_data_set_string(data, "name",
|
|
obs_source_get_name(qt.source));
|
|
obs_data_set_int(data, "duration", qt.duration);
|
|
obs_data_set_array(data, "hotkeys", hotkeys);
|
|
obs_data_set_int(data, "id", qt.id);
|
|
|
|
obs_data_array_push_back(array, data);
|
|
|
|
obs_data_release(data);
|
|
obs_data_array_release(hotkeys);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
obs_source_t *OBSBasic::FindTransition(const char *name)
|
|
{
|
|
for (int i = 0; i < ui->transitions->count(); i++) {
|
|
OBSSource tr = ui->transitions->itemData(i)
|
|
.value<OBSSource>();
|
|
|
|
const char *trName = obs_source_get_name(tr);
|
|
if (strcmp(trName, name) == 0)
|
|
return tr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void OBSBasic::TransitionToScene(obs_scene_t *scene, bool force)
|
|
{
|
|
obs_source_t *source = obs_scene_get_source(scene);
|
|
TransitionToScene(source, force);
|
|
}
|
|
|
|
void OBSBasic::TransitionStopped()
|
|
{
|
|
if (swapScenesMode) {
|
|
OBSSource scene = OBSGetStrongRef(swapScene);
|
|
if (scene)
|
|
SetCurrentScene(scene);
|
|
}
|
|
|
|
swapScene = nullptr;
|
|
}
|
|
|
|
void OBSBasic::TransitionToScene(obs_source_t *source, bool force)
|
|
{
|
|
obs_scene_t *scene = obs_scene_from_source(source);
|
|
bool usingPreviewProgram = IsPreviewProgramMode();
|
|
if (!scene)
|
|
return;
|
|
|
|
OBSWeakSource lastProgramScene;
|
|
|
|
if (usingPreviewProgram) {
|
|
lastProgramScene = programScene;
|
|
programScene = OBSGetWeakRef(source);
|
|
|
|
if (swapScenesMode && !force) {
|
|
OBSSource newScene = OBSGetStrongRef(lastProgramScene);
|
|
|
|
if (!sceneDuplicationMode && newScene == source)
|
|
return;
|
|
|
|
if (newScene && newScene != GetCurrentSceneSource())
|
|
swapScene = lastProgramScene;
|
|
}
|
|
}
|
|
|
|
if (usingPreviewProgram && sceneDuplicationMode) {
|
|
scene = obs_scene_duplicate(scene, NULL,
|
|
editPropertiesMode ?
|
|
OBS_SCENE_DUP_PRIVATE_COPY :
|
|
OBS_SCENE_DUP_PRIVATE_REFS);
|
|
source = obs_scene_get_source(scene);
|
|
}
|
|
|
|
obs_source_t *transition = obs_get_output_source(0);
|
|
|
|
if (force)
|
|
obs_transition_set(transition, source);
|
|
else
|
|
obs_transition_start(transition, OBS_TRANSITION_MODE_AUTO,
|
|
ui->transitionDuration->value(), source);
|
|
|
|
if (usingPreviewProgram && sceneDuplicationMode)
|
|
obs_scene_release(scene);
|
|
|
|
obs_source_release(transition);
|
|
}
|
|
|
|
static inline void SetComboTransition(QComboBox *combo, obs_source_t *tr)
|
|
{
|
|
int idx = combo->findData(QVariant::fromValue<OBSSource>(tr));
|
|
if (idx != -1) {
|
|
combo->blockSignals(true);
|
|
combo->setCurrentIndex(idx);
|
|
combo->blockSignals(false);
|
|
}
|
|
}
|
|
|
|
void OBSBasic::SetTransition(obs_source_t *transition)
|
|
{
|
|
obs_source_t *oldTransition = obs_get_output_source(0);
|
|
|
|
if (oldTransition && transition) {
|
|
obs_transition_swap_begin(transition, oldTransition);
|
|
if (transition != GetCurrentTransition())
|
|
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);
|
|
}
|
|
|
|
if (oldTransition)
|
|
obs_source_release(oldTransition);
|
|
|
|
bool fixed = transition ? obs_transition_fixed(transition) : false;
|
|
ui->transitionDurationLabel->setVisible(!fixed);
|
|
ui->transitionDuration->setVisible(!fixed);
|
|
}
|
|
|
|
OBSSource OBSBasic::GetCurrentTransition()
|
|
{
|
|
return ui->transitions->currentData().value<OBSSource>();
|
|
}
|
|
|
|
void OBSBasic::on_transitions_currentIndexChanged(int)
|
|
{
|
|
OBSSource transition = GetCurrentTransition();
|
|
SetTransition(transition);
|
|
}
|
|
|
|
void OBSBasic::on_transitionProps_clicked()
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
QuickTransition *OBSBasic::GetQuickTransition(int id)
|
|
{
|
|
for (QuickTransition &qt : quickTransitions) {
|
|
if (qt.id == id)
|
|
return &qt;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int OBSBasic::GetQuickTransitionIdx(int id)
|
|
{
|
|
for (int idx = 0; idx < (int)quickTransitions.size(); idx++) {
|
|
QuickTransition &qt = quickTransitions[idx];
|
|
|
|
if (qt.id == id)
|
|
return idx;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void OBSBasic::SetCurrentScene(obs_scene_t *scene, bool force)
|
|
{
|
|
obs_source_t *source = obs_scene_get_source(scene);
|
|
SetCurrentScene(source, force);
|
|
}
|
|
|
|
template <typename T>
|
|
static T GetOBSRef(QListWidgetItem *item)
|
|
{
|
|
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
|
|
}
|
|
|
|
void OBSBasic::SetCurrentScene(obs_source_t *scene, bool force)
|
|
{
|
|
if (!IsPreviewProgramMode()) {
|
|
TransitionToScene(scene, force);
|
|
|
|
} else {
|
|
OBSSource actualLastScene = OBSGetStrongRef(lastScene);
|
|
if (actualLastScene != scene) {
|
|
if (scene)
|
|
obs_source_inc_showing(scene);
|
|
if (actualLastScene)
|
|
obs_source_dec_showing(actualLastScene);
|
|
lastScene = OBSGetWeakRef(scene);
|
|
}
|
|
}
|
|
|
|
if (obs_scene_get_source(GetCurrentScene()) != scene) {
|
|
for (int i = 0; i < ui->scenes->count(); i++) {
|
|
QListWidgetItem *item = ui->scenes->item(i);
|
|
OBSScene itemScene = GetOBSRef<OBSScene>(item);
|
|
obs_source_t *source = obs_scene_get_source(itemScene);
|
|
|
|
if (source == scene) {
|
|
ui->scenes->blockSignals(true);
|
|
ui->scenes->setCurrentItem(item);
|
|
ui->scenes->blockSignals(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateSceneSelection(scene);
|
|
}
|
|
|
|
void OBSBasic::CreateProgramDisplay()
|
|
{
|
|
program = new OBSQTDisplay();
|
|
|
|
auto displayResize = [this]() {
|
|
struct obs_video_info ovi;
|
|
|
|
if (obs_get_video_info(&ovi))
|
|
ResizeProgram(ovi.base_width, ovi.base_height);
|
|
};
|
|
|
|
connect(program, &OBSQTDisplay::DisplayResized,
|
|
displayResize);
|
|
|
|
auto addDisplay = [this] (OBSQTDisplay *window)
|
|
{
|
|
obs_display_add_draw_callback(window->GetDisplay(),
|
|
OBSBasic::RenderProgram, this);
|
|
|
|
struct obs_video_info ovi;
|
|
if (obs_get_video_info(&ovi))
|
|
ResizeProgram(ovi.base_width, ovi.base_height);
|
|
};
|
|
|
|
connect(program, &OBSQTDisplay::DisplayCreated, addDisplay);
|
|
|
|
program->setSizePolicy(QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding);
|
|
}
|
|
|
|
void OBSBasic::TransitionClicked()
|
|
{
|
|
if (previewProgramMode)
|
|
TransitionToScene(GetCurrentScene());
|
|
}
|
|
|
|
void OBSBasic::CreateProgramOptions()
|
|
{
|
|
programOptions = new QWidget();
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->setSpacing(4);
|
|
|
|
QPushButton *configTransitions = new QPushButton();
|
|
configTransitions->setMaximumSize(22, 22);
|
|
configTransitions->setProperty("themeID", "configIconSmall");
|
|
configTransitions->setFlat(true);
|
|
|
|
QHBoxLayout *mainButtonLayout = new QHBoxLayout();
|
|
mainButtonLayout->setSpacing(2);
|
|
|
|
QPushButton *transitionButton = new QPushButton(QTStr("Transition"));
|
|
QHBoxLayout *quickTransitions = new QHBoxLayout();
|
|
quickTransitions->setSpacing(2);
|
|
|
|
QPushButton *addQuickTransition = new QPushButton();
|
|
addQuickTransition->setMaximumSize(22, 22);
|
|
addQuickTransition->setProperty("themeID", "addIconSmall");
|
|
addQuickTransition->setFlat(true);
|
|
|
|
QLabel *quickTransitionsLabel = new QLabel(QTStr("QuickTransitions"));
|
|
|
|
quickTransitions->addWidget(quickTransitionsLabel);
|
|
quickTransitions->addWidget(addQuickTransition);
|
|
|
|
mainButtonLayout->addWidget(transitionButton);
|
|
mainButtonLayout->addWidget(configTransitions);
|
|
|
|
layout->addStretch(0);
|
|
layout->addLayout(mainButtonLayout);
|
|
layout->addLayout(quickTransitions);
|
|
layout->addStretch(0);
|
|
|
|
programOptions->setLayout(layout);
|
|
|
|
auto onAdd = [this] () {
|
|
QPointer<QMenu> menu = CreateTransitionMenu(this, nullptr);
|
|
menu->exec(QCursor::pos());
|
|
};
|
|
|
|
auto onConfig = [this] () {
|
|
QMenu menu(this);
|
|
QAction *action;
|
|
|
|
auto toggleEditProperties = [this] () {
|
|
editPropertiesMode = !editPropertiesMode;
|
|
|
|
OBSSource actualScene = OBSGetStrongRef(programScene);
|
|
if (actualScene)
|
|
TransitionToScene(actualScene, true);
|
|
};
|
|
|
|
auto toggleSwapScenesMode = [this] () {
|
|
swapScenesMode = !swapScenesMode;
|
|
};
|
|
|
|
auto toggleSceneDuplication = [this] () {
|
|
sceneDuplicationMode = !sceneDuplicationMode;
|
|
|
|
OBSSource actualScene = OBSGetStrongRef(programScene);
|
|
if (actualScene)
|
|
TransitionToScene(actualScene, true);
|
|
};
|
|
|
|
auto showToolTip = [&] () {
|
|
QAction *act = menu.activeAction();
|
|
QToolTip::showText(QCursor::pos(), act->toolTip(),
|
|
&menu, menu.actionGeometry(act));
|
|
};
|
|
|
|
action = menu.addAction(QTStr("QuickTransitions.DuplicateScene"));
|
|
action->setToolTip(QTStr("QuickTransitions.DuplicateSceneTT"));
|
|
action->setCheckable(true);
|
|
action->setChecked(sceneDuplicationMode);
|
|
connect(action, &QAction::triggered, toggleSceneDuplication);
|
|
connect(action, &QAction::hovered, showToolTip);
|
|
|
|
action = menu.addAction(QTStr("QuickTransitions.EditProperties"));
|
|
action->setToolTip(QTStr("QuickTransitions.EditPropertiesTT"));
|
|
action->setCheckable(true);
|
|
action->setChecked(editPropertiesMode);
|
|
action->setEnabled(sceneDuplicationMode);
|
|
connect(action, &QAction::triggered, toggleEditProperties);
|
|
connect(action, &QAction::hovered, showToolTip);
|
|
|
|
action = menu.addAction(QTStr("QuickTransitions.SwapScenes"));
|
|
action->setToolTip(QTStr("QuickTransitions.SwapScenesTT"));
|
|
action->setCheckable(true);
|
|
action->setChecked(swapScenesMode);
|
|
connect(action, &QAction::triggered, toggleSwapScenesMode);
|
|
connect(action, &QAction::hovered, showToolTip);
|
|
|
|
menu.exec(QCursor::pos());
|
|
};
|
|
|
|
connect(transitionButton, &QAbstractButton::clicked,
|
|
this, &OBSBasic::TransitionClicked);
|
|
connect(addQuickTransition, &QAbstractButton::clicked, onAdd);
|
|
connect(configTransitions, &QAbstractButton::clicked, onConfig);
|
|
}
|
|
|
|
void OBSBasic::on_modeSwitch_clicked()
|
|
{
|
|
SetPreviewProgramMode(!IsPreviewProgramMode());
|
|
}
|
|
|
|
static inline void ResetQuickTransitionText(QuickTransition *qt)
|
|
{
|
|
qt->button->setText(MakeQuickTransitionText(qt));
|
|
}
|
|
|
|
QMenu *OBSBasic::CreateTransitionMenu(QWidget *parent, QuickTransition *qt)
|
|
{
|
|
QMenu *menu = new QMenu(parent);
|
|
QAction *action;
|
|
|
|
if (qt) {
|
|
action = menu->addAction(QTStr("Remove"));
|
|
action->setProperty("id", qt->id);
|
|
connect(action, &QAction::triggered,
|
|
this, &OBSBasic::QuickTransitionRemoveClicked);
|
|
|
|
menu->addSeparator();
|
|
}
|
|
|
|
QSpinBox *duration = new QSpinBox(menu);
|
|
if (qt)
|
|
duration->setProperty("id", qt->id);
|
|
duration->setMinimum(50);
|
|
duration->setSuffix("ms");
|
|
duration->setMaximum(20000);
|
|
duration->setSingleStep(50);
|
|
duration->setValue(qt ? qt->duration : 300);
|
|
|
|
if (qt) {
|
|
connect(duration, (void (QSpinBox::*)(int))&QSpinBox::valueChanged,
|
|
this, &OBSBasic::QuickTransitionChangeDuration);
|
|
}
|
|
|
|
for (int i = 0; i < ui->transitions->count(); i++) {
|
|
OBSSource tr = GetTransitionComboItem(ui->transitions, i);
|
|
|
|
action = menu->addAction(obs_source_get_name(tr));
|
|
action->setProperty("transition_index", i);
|
|
|
|
if (qt) {
|
|
action->setProperty("id", qt->id);
|
|
connect(action, &QAction::triggered, this,
|
|
&OBSBasic::QuickTransitionChange);
|
|
} else {
|
|
action->setProperty("duration",
|
|
QVariant::fromValue<QWidget*>(duration));
|
|
connect(action, &QAction::triggered, this,
|
|
&OBSBasic::AddQuickTransition);
|
|
}
|
|
}
|
|
|
|
QWidgetAction *durationAction = new QWidgetAction(menu);
|
|
durationAction->setDefaultWidget(duration);
|
|
|
|
menu->addSeparator();
|
|
menu->addAction(durationAction);
|
|
return menu;
|
|
}
|
|
|
|
void OBSBasic::AddQuickTransitionId(int id)
|
|
{
|
|
QuickTransition *qt = GetQuickTransition(id);
|
|
if (!qt)
|
|
return;
|
|
|
|
/* --------------------------------- */
|
|
|
|
QPushButton *button = new MenuButton();
|
|
button->setProperty("id", id);
|
|
|
|
qt->button = button;
|
|
ResetQuickTransitionText(qt);
|
|
|
|
/* --------------------------------- */
|
|
|
|
QMenu *buttonMenu = CreateTransitionMenu(button, qt);
|
|
|
|
/* --------------------------------- */
|
|
|
|
button->setMenu(buttonMenu);
|
|
connect(button, &QAbstractButton::clicked,
|
|
this, &OBSBasic::QuickTransitionClicked);
|
|
|
|
QVBoxLayout *programLayout =
|
|
reinterpret_cast<QVBoxLayout*>(programOptions->layout());
|
|
|
|
int idx = 3;
|
|
for (;; idx++) {
|
|
QLayoutItem *item = programLayout->itemAt(idx);
|
|
if (!item)
|
|
break;
|
|
|
|
QWidget *widget = item->widget();
|
|
if (!widget || !widget->property("id").isValid())
|
|
break;
|
|
}
|
|
|
|
programLayout->insertWidget(idx, button);
|
|
}
|
|
|
|
void OBSBasic::AddQuickTransition()
|
|
{
|
|
int trIdx = sender()->property("transition_index").toInt();
|
|
QSpinBox *duration = sender()->property("duration").value<QSpinBox*>();
|
|
OBSSource transition = GetTransitionComboItem(ui->transitions, trIdx);
|
|
int id = quickTransitionIdCounter++;
|
|
|
|
quickTransitions.emplace_back(transition, duration->value(), id);
|
|
AddQuickTransitionId(id);
|
|
|
|
int idx = (int)quickTransitions.size() - 1;
|
|
AddQuickTransitionHotkey(&quickTransitions[idx]);
|
|
}
|
|
|
|
void OBSBasic::ClearQuickTransitions()
|
|
{
|
|
for (QuickTransition &qt : quickTransitions)
|
|
RemoveQuickTransitionHotkey(&qt);
|
|
quickTransitions.clear();
|
|
|
|
if (!programOptions)
|
|
return;
|
|
|
|
QVBoxLayout *programLayout =
|
|
reinterpret_cast<QVBoxLayout*>(programOptions->layout());
|
|
|
|
for (int idx = 0;; idx++) {
|
|
QLayoutItem *item = programLayout->itemAt(idx);
|
|
if (!item)
|
|
break;
|
|
|
|
QWidget *widget = item->widget();
|
|
if (!widget)
|
|
continue;
|
|
|
|
int id = widget->property("id").toInt();
|
|
if (id != 0) {
|
|
delete widget;
|
|
idx--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OBSBasic::QuickTransitionClicked()
|
|
{
|
|
int id = sender()->property("id").toInt();
|
|
TriggerQuickTransition(id);
|
|
}
|
|
|
|
void OBSBasic::QuickTransitionChange()
|
|
{
|
|
int id = sender()->property("id").toInt();
|
|
int trIdx = sender()->property("transition_index").toInt();
|
|
QuickTransition *qt = GetQuickTransition(id);
|
|
|
|
if (qt) {
|
|
qt->source = GetTransitionComboItem(ui->transitions, trIdx);
|
|
ResetQuickTransitionText(qt);
|
|
}
|
|
}
|
|
|
|
void OBSBasic::QuickTransitionChangeDuration(int value)
|
|
{
|
|
int id = sender()->property("id").toInt();
|
|
QuickTransition *qt = GetQuickTransition(id);
|
|
|
|
if (qt) {
|
|
qt->duration = value;
|
|
ResetQuickTransitionText(qt);
|
|
}
|
|
}
|
|
|
|
void OBSBasic::QuickTransitionRemoveClicked()
|
|
{
|
|
int id = sender()->property("id").toInt();
|
|
int idx = GetQuickTransitionIdx(id);
|
|
if (idx == -1)
|
|
return;
|
|
|
|
QuickTransition &qt = quickTransitions[idx];
|
|
|
|
if (qt.button)
|
|
qt.button->deleteLater();
|
|
|
|
RemoveQuickTransitionHotkey(&qt);
|
|
quickTransitions.erase(quickTransitions.begin() + idx);
|
|
}
|
|
|
|
void OBSBasic::RefreshQuickTransitions()
|
|
{
|
|
if (!IsPreviewProgramMode())
|
|
return;
|
|
|
|
for (QuickTransition &qt : quickTransitions)
|
|
AddQuickTransitionId(qt.id);
|
|
}
|
|
|
|
void OBSBasic::SetPreviewProgramMode(bool enabled)
|
|
{
|
|
if (IsPreviewProgramMode() == enabled)
|
|
return;
|
|
|
|
ui->modeSwitch->setChecked(enabled);
|
|
os_atomic_set_bool(&previewProgramMode, enabled);
|
|
|
|
if (IsPreviewProgramMode()) {
|
|
if (!previewEnabled)
|
|
EnablePreviewDisplay(true);
|
|
|
|
CreateProgramDisplay();
|
|
CreateProgramOptions();
|
|
|
|
OBSScene curScene = GetCurrentScene();
|
|
|
|
obs_scene_t *dup = obs_scene_duplicate(curScene, nullptr,
|
|
editPropertiesMode ?
|
|
OBS_SCENE_DUP_PRIVATE_COPY :
|
|
OBS_SCENE_DUP_PRIVATE_REFS);
|
|
|
|
obs_source_t *transition = obs_get_output_source(0);
|
|
obs_source_t *dup_source = obs_scene_get_source(dup);
|
|
obs_transition_set(transition, dup_source);
|
|
obs_source_release(transition);
|
|
obs_scene_release(dup);
|
|
|
|
if (curScene) {
|
|
obs_source_t *source = obs_scene_get_source(curScene);
|
|
obs_source_inc_showing(source);
|
|
lastScene = OBSGetWeakRef(source);
|
|
programScene = OBSGetWeakRef(source);
|
|
}
|
|
|
|
RefreshQuickTransitions();
|
|
|
|
ui->previewLayout->addWidget(programOptions);
|
|
ui->previewLayout->addWidget(program);
|
|
program->show();
|
|
|
|
blog(LOG_INFO, "Switched to Preview/Program mode");
|
|
blog(LOG_INFO, "-----------------------------"
|
|
"-------------------");
|
|
} else {
|
|
OBSSource actualProgramScene = OBSGetStrongRef(programScene);
|
|
if (!actualProgramScene)
|
|
actualProgramScene = GetCurrentSceneSource();
|
|
else
|
|
SetCurrentScene(actualProgramScene);
|
|
TransitionToScene(actualProgramScene, true);
|
|
|
|
delete programOptions;
|
|
delete program;
|
|
|
|
if (lastScene) {
|
|
OBSSource actualLastScene = OBSGetStrongRef(lastScene);
|
|
if (actualLastScene)
|
|
obs_source_dec_showing(actualLastScene);
|
|
lastScene = nullptr;
|
|
}
|
|
|
|
programScene = nullptr;
|
|
swapScene = nullptr;
|
|
|
|
for (QuickTransition &qt : quickTransitions)
|
|
qt.button = nullptr;
|
|
|
|
if (!previewEnabled)
|
|
EnablePreviewDisplay(false);
|
|
|
|
blog(LOG_INFO, "Switched to regular Preview mode");
|
|
blog(LOG_INFO, "-----------------------------"
|
|
"-------------------");
|
|
}
|
|
|
|
UpdateTitleBar();
|
|
}
|
|
|
|
void OBSBasic::RenderProgram(void *data, uint32_t cx, uint32_t cy)
|
|
{
|
|
OBSBasic *window = static_cast<OBSBasic*>(data);
|
|
obs_video_info ovi;
|
|
|
|
obs_get_video_info(&ovi);
|
|
|
|
window->programCX = int(window->programScale * float(ovi.base_width));
|
|
window->programCY = int(window->programScale * float(ovi.base_height));
|
|
|
|
gs_viewport_push();
|
|
gs_projection_push();
|
|
|
|
/* --------------------------------------- */
|
|
|
|
gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height),
|
|
-100.0f, 100.0f);
|
|
gs_set_viewport(window->programX, window->programY,
|
|
window->programCX, window->programCY);
|
|
|
|
window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));
|
|
|
|
obs_render_main_view();
|
|
gs_load_vertexbuffer(nullptr);
|
|
|
|
/* --------------------------------------- */
|
|
|
|
gs_projection_pop();
|
|
gs_viewport_pop();
|
|
|
|
UNUSED_PARAMETER(cx);
|
|
UNUSED_PARAMETER(cy);
|
|
}
|
|
|
|
void OBSBasic::ResizeProgram(uint32_t cx, uint32_t cy)
|
|
{
|
|
QSize targetSize;
|
|
|
|
/* resize program panel to fix to the top section of the window */
|
|
targetSize = GetPixelSize(program);
|
|
GetScaleAndCenterPos(int(cx), int(cy),
|
|
targetSize.width() - PREVIEW_EDGE_SIZE * 2,
|
|
targetSize.height() - PREVIEW_EDGE_SIZE * 2,
|
|
programX, programY, programScale);
|
|
|
|
programX += float(PREVIEW_EDGE_SIZE);
|
|
programY += float(PREVIEW_EDGE_SIZE);
|
|
}
|