UI: Implement transitions and preview/program mode

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.
This commit is contained in:
jp9000 2016-01-23 11:19:29 -08:00
parent 6f98bd9fed
commit 544953c870
8 changed files with 1368 additions and 109 deletions

View file

@ -105,6 +105,7 @@ set(obs_SOURCES
window-basic-main-outputs.cpp
window-basic-source-select.cpp
window-basic-main-scene-collections.cpp
window-basic-main-transitions.cpp
window-basic-main-profiles.cpp
window-license-agreement.cpp
window-basic-status-bar.cpp

View file

@ -46,6 +46,22 @@ Enable="Enable"
DisableOSXVSync="Disable OSX V-Sync"
ResetOSXVSyncOnExit="Reset OSX V-Sync on Exit"
HighResourceUsage="Encoding overloaded! Consider turning down video settings or using a faster encoding preset."
Transition="Transition"
QuickTransitions="Quick Transitions"
# quick transitions
QuickTransitions.SwapScenes="Swap Preview/Output Scenes After Transitioning"
QuickTransitions.SwapScenesTT="Swaps the preview and output scenes after transitioning (if the output's original scene still exists).\nThis will not undo any changes that may have been made to the output's original scene."
QuickTransitions.DuplicateScene="Duplicate Scene"
QuickTransitions.DuplicateSceneTT="When editing the same scene, allows editing transform/visibility of sources without modifying the output.\nTo edit properties of sources without modifying the output, enable 'Duplicate Sources'.\nChanging this value will reset the current output scene (if it still exists)."
QuickTransitions.EditProperties="Duplicate Sources"
QuickTransitions.EditPropertiesTT="When editing the same scene, allows editing properties of sources without modifying the output.\nThis can only be used if 'Duplicate Scene' is enabled.\nCertain sources (such as capture or media sources) do not support this and cannot be edited separately.\nChanging this value will reset the current output scene (if it still exists).\n\nWarning: Because sources will be duplicated, this may require extra system or video resources."
QuickTransitions.HotkeyName="Quick Transition: %1"
# transitions
Basic.SceneTransitions="Scene Transitions"
Basic.TransitionDuration="Duration"
Basic.TogglePreviewProgramMode="Studio Mode"
# title bar strings
TitleBar.Profile="Profile"

View file

@ -46,26 +46,40 @@
<widget class="QWidget" name="verticalLayoutWidget_7">
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="OBSBasicPreview" name="preview" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<layout class="QHBoxLayout" name="previewLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
<item>
<layout class="QVBoxLayout" name="previewTextLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="OBSBasicPreview" name="preview" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="previewDisabledLabel">
@ -90,6 +104,103 @@
</widget>
<widget class="QWidget" name="gridLayoutWidget_2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="4">
<layout class="QVBoxLayout" name="buttonsVLayout">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="streamButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Basic.Main.StartStreaming</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="recordButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Basic.Main.StartRecording</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="modeSwitch">
<property name="text">
<string>Basic.TogglePreviewProgramMode</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Settings</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
<item>
<spacer name="expVSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="2">
<widget class="VScrollArea" name="scrollArea">
<property name="minimumSize">
@ -118,7 +229,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>242</width>
<width>201</width>
<height>16</height>
</rect>
</property>
@ -460,62 +571,117 @@
</widget>
</item>
<item row="1" column="3">
<layout class="QVBoxLayout" name="buttonsVLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
<number>4</number>
</property>
<item>
<widget class="QPushButton" name="streamButton">
<property name="enabled">
<bool>true</bool>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="text">
<string>Basic.Main.StartStreaming</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
<item>
<widget class="QPushButton" name="transitionProps">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="icon">
<iconset resource="obs.qrc">
<normaloff>:/res/images/configuration21_16.png</normaloff>:/res/images/configuration21_16.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">configIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="transitions">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="recordButton">
<property name="enabled">
<bool>true</bool>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>4</number>
</property>
<property name="text">
<string>Basic.Main.StartRecording</string>
</property>
</widget>
<item>
<widget class="QLabel" name="transitionDurationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Basic.TransitionDuration</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="transitionDuration">
<property name="suffix">
<string>ms</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="text">
<string>Settings</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exitButton">
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
<item>
<spacer name="expVSpacer">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Basic.SceneTransitions</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

View file

@ -333,6 +333,12 @@ bool OBSApp::InitGlobalConfigDefaults()
config_set_default_bool(globalConfig, "BasicWindow", "PreviewEnabled",
true);
config_set_default_bool(globalConfig, "BasicWindow",
"PreviewProgramMode", false);
config_set_default_bool(globalConfig, "BasicWindow",
"SceneDuplicationMode", true);
config_set_default_bool(globalConfig, "BasicWindow",
"SwapScenesMode", true);
#ifdef __APPLE__
config_set_default_bool(globalConfig, "Video", "DisableOSXVSync", true);

View file

@ -0,0 +1,867 @@
/******************************************************************************
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);
}

View file

@ -56,8 +56,6 @@
#include <QScreen>
#include <QWindow>
#define PREVIEW_EDGE_SIZE 10
using namespace std;
namespace {
@ -247,7 +245,9 @@ static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
obs_source_release(source);
}
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder)
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
obs_data_array_t *quickTransitionData, int transitionDuration,
OBSScene &scene, OBSSource &curProgramScene)
{
obs_data_t *saveData = obs_data_create();
@ -273,18 +273,26 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder)
return (*static_cast<FilterAudioSources_t*>(data))(source);
}, static_cast<void*>(&FilterAudioSources));
obs_source_t *currentScene = obs_get_output_source(0);
obs_source_t *transition = obs_get_output_source(0);
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 *sceneCollection = config_get_string(App()->GlobalConfig(),
"Basic", "SceneCollection");
obs_data_set_string(saveData, "current_scene", sceneName);
obs_data_set_string(saveData, "current_program_scene", programName);
obs_data_set_array(saveData, "scene_order", 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_array_release(sourcesArray);
obs_source_release(currentScene);
obs_data_set_string(saveData, "current_transition",
obs_source_get_name(transition));
obs_data_set_int(saveData, "transition_duration", transitionDuration);
obs_source_release(transition);
return saveData;
}
@ -338,14 +346,23 @@ obs_data_array_t *OBSBasic::SaveSceneListOrder()
void OBSBasic::Save(const char *file)
{
OBSScene scene = GetCurrentScene();
OBSSource curProgramScene = OBSGetStrongRef(programScene);
if (!curProgramScene)
curProgramScene = obs_scene_get_source(scene);
obs_data_array_t *sceneOrder = SaveSceneListOrder();
obs_data_t *saveData = GenerateSaveData(sceneOrder);
obs_data_array_t *quickTrData = SaveQuickTransitions();
obs_data_t *saveData = GenerateSaveData(sceneOrder, quickTrData,
ui->transitionDuration->value(),
scene, curProgramScene);
if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
blog(LOG_ERROR, "Could not save scene data to %s", file);
obs_data_release(saveData);
obs_data_array_release(sceneOrder);
obs_data_array_release(quickTrData);
}
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
@ -399,14 +416,18 @@ void OBSBasic::CreateDefaultScene(bool firstStart)
disableSaving++;
ClearSceneData();
InitDefaultTransitions();
CreateDefaultQuickTransitions();
ui->transitionDuration->setValue(300);
SetTransition(fadeTransition);
obs_scene_t *scene = obs_scene_create(Str("Basic.Scene"));
if (firstStart)
CreateFirstRunSources();
obs_set_output_source(0, obs_scene_get_source(scene));
AddScene(obs_scene_get_source(scene));
SetCurrentScene(scene, true);
obs_scene_release(scene);
disableSaving--;
@ -463,11 +484,23 @@ void OBSBasic::Load(const char *file)
}
ClearSceneData();
InitDefaultTransitions();
obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
obs_data_array_t *sources = obs_data_get_array(data, "sources");
const char *sceneName = obs_data_get_string(data,
"current_scene");
const char *programSceneName = obs_data_get_string(data,
"current_program_scene");
const char *transitionName = obs_data_get_string(data,
"current_transition");
int newDuration = obs_data_get_int(data, "transition_duration");
if (!newDuration)
newDuration = 300;
if (!transitionName)
transitionName = obs_source_get_name(fadeTransition);
const char *curSceneCollection = config_get_string(
App()->GlobalConfig(), "Basic", "SceneCollection");
@ -476,6 +509,8 @@ void OBSBasic::Load(const char *file)
const char *name = obs_data_get_string(data, "name");
obs_source_t *curScene;
obs_source_t *curProgramScene;
obs_source_t *curTransition;
if (!name || !*name)
name = curSceneCollection;
@ -491,9 +526,25 @@ void OBSBasic::Load(const char *file)
if (sceneOrder)
LoadSceneListOrder(sceneOrder);
curTransition = FindTransition(transitionName);
if (!curTransition)
curTransition = fadeTransition;
ui->transitionDuration->setValue(newDuration);
SetTransition(curTransition);
curScene = obs_get_source_by_name(sceneName);
obs_set_output_source(0, curScene);
curProgramScene = obs_get_source_by_name(programSceneName);
if (!curProgramScene) {
curProgramScene = curScene;
obs_source_addref(curScene);
}
SetCurrentScene(curScene, true);
if (IsPreviewProgramMode())
TransitionToScene(curProgramScene, true);
obs_source_release(curScene);
obs_source_release(curProgramScene);
obs_data_array_release(sources);
obs_data_array_release(sceneOrder);
@ -506,6 +557,13 @@ void OBSBasic::Load(const char *file)
config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile",
file_base.c_str());
obs_data_array_t *quickTransitionData = obs_data_get_array(data,
"quick_transitions");
LoadQuickTransitions(quickTransitionData);
obs_data_array_release(quickTransitionData);
RefreshQuickTransitions();
obs_data_release(data);
disableSaving--;
@ -772,8 +830,6 @@ void OBSBasic::InitOBSCallbacks()
OBSBasic::SourceLoaded, this);
signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove",
OBSBasic::SourceRemoved, this);
signalHandlers.emplace_back(obs_get_signal_handler(), "channel_change",
OBSBasic::ChannelChanged, this);
signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate",
OBSBasic::SourceActivated, this);
signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate",
@ -885,6 +941,15 @@ void OBSBasic::OBSInit()
InitPrimitives();
sceneDuplicationMode = config_get_bool(App()->GlobalConfig(),
"BasicWindow", "SceneDuplicationMode");
swapScenesMode = config_get_bool(App()->GlobalConfig(),
"BasicWindow", "SwapScenesMode");
editPropertiesMode = config_get_bool(App()->GlobalConfig(),
"BasicWindow", "EditPropertiesMode");
SetPreviewProgramMode(config_get_bool(App()->GlobalConfig(),
"BasicWindow", "PreviewProgramMode"));
{
ProfileScope("OBSBasic::Load");
disableSaving--;
@ -895,11 +960,13 @@ void OBSBasic::OBSInit()
TimedCheckForUpdates();
loaded = true;
bool previewEnabled = config_get_bool(App()->GlobalConfig(),
previewEnabled = config_get_bool(App()->GlobalConfig(),
"BasicWindow", "PreviewEnabled");
if (!previewEnabled)
QMetaObject::invokeMethod(this, "TogglePreview",
Qt::QueuedConnection);
if (!previewEnabled && !IsPreviewProgramMode())
QMetaObject::invokeMethod(this, "EnablePreviewDisplay",
Qt::QueuedConnection,
Q_ARG(bool, previewEnabled));
#ifdef _WIN32
uint32_t winVer = GetWindowsVersion();
@ -1121,8 +1188,36 @@ void OBSBasic::CreateHotkeys()
this, this);
LoadHotkeyPair(recordingHotkeys,
"OBSBasic.StartRecording", "OBSBasic.StopRecording");
#undef MAKE_CALLBACK
auto togglePreviewProgram = [] (void *data, obs_hotkey_id,
obs_hotkey_t*, bool pressed)
{
if (pressed)
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
"on_modeSwitch_clicked",
Qt::QueuedConnection);
};
togglePreviewProgramHotkey = obs_hotkey_register_frontend(
"OBSBasic.TogglePreviewProgram",
Str("Basic.TogglePreviewProgramMode"),
togglePreviewProgram, this);
LoadHotkey(togglePreviewProgramHotkey, "OBSBasic.TogglePreviewProgram");
auto transition = [] (void *data, obs_hotkey_id, obs_hotkey_t*,
bool pressed)
{
if (pressed)
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
"TransitionClicked",
Qt::QueuedConnection);
};
transitionHotkey = obs_hotkey_register_frontend(
"OBSBasic.Transition",
Str("Transition"), transition, this);
LoadHotkey(transitionHotkey, "OBSBasic.Transition");
}
void OBSBasic::ClearHotkeys()
@ -1130,11 +1225,14 @@ void OBSBasic::ClearHotkeys()
obs_hotkey_pair_unregister(streamingHotkeys);
obs_hotkey_pair_unregister(recordingHotkeys);
obs_hotkey_unregister(forceStreamingStopHotkey);
obs_hotkey_unregister(togglePreviewProgramHotkey);
obs_hotkey_unregister(transitionHotkey);
}
OBSBasic::~OBSBasic()
{
bool previewEnabled = obs_display_enabled(ui->preview->GetDisplay());
delete programOptions;
delete program;
/* XXX: any obs data must be released before calling obs_shutdown.
* currently, we can't automate this with C++ RAII because of the
@ -1206,6 +1304,14 @@ OBSBasic::~OBSBasic()
previewEnabled);
config_set_bool(App()->GlobalConfig(), "BasicWindow", "AlwaysOnTop",
alwaysOnTop);
config_set_bool(App()->GlobalConfig(), "BasicWindow",
"SceneDuplicationMode", sceneDuplicationMode);
config_set_bool(App()->GlobalConfig(), "BasicWindow",
"SwapScenesMode", swapScenesMode);
config_set_bool(App()->GlobalConfig(), "BasicWindow",
"EditPropertiesMode", editPropertiesMode);
config_set_bool(App()->GlobalConfig(), "BasicWindow",
"PreviewProgramMode", IsPreviewProgramMode());
config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
#ifdef _WIN32
@ -1358,10 +1464,13 @@ void OBSBasic::AddScene(OBSSource source)
[](void *data,
obs_hotkey_id, obs_hotkey_t*, bool pressed)
{
OBSBasic *main =
reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
auto potential_source = static_cast<obs_source_t*>(data);
auto source = obs_source_get_ref(potential_source);
if (source && pressed)
obs_set_output_source(0, source);
main->SetCurrentScene(source);
obs_source_release(source);
}, static_cast<obs_source_t*>(source));
@ -1771,10 +1880,10 @@ void OBSBasic::DuplicateSelectedScene()
obs_scene_t *scene = obs_scene_duplicate(curScene,
name.c_str(), OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene);
AddScene(source);
SetCurrentScene(source, true);
obs_scene_release(scene);
obs_set_output_source(0, source);
return;
break;
}
}
@ -1967,17 +2076,6 @@ void OBSBasic::SourceRenamed(void *data, calldata_t *params)
Q_ARG(QString, QT_UTF8(prevName)));
}
void OBSBasic::ChannelChanged(void *data, calldata_t *params)
{
obs_source_t *source = (obs_source_t*)calldata_ptr(params, "source");
uint32_t channel = (uint32_t)calldata_int(params, "channel");
if (channel == 0)
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
"UpdateSceneSelection",
Q_ARG(OBSSource, OBSSource(source)));
}
void OBSBasic::DrawBackdrop(float cx, float cy)
{
if (!box)
@ -2029,7 +2127,14 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));
obs_render_main_view();
if (window->IsPreviewProgramMode()) {
OBSScene scene = window->GetCurrentScene();
obs_source_t *source = obs_scene_get_source(scene);
if (source)
obs_source_video_render(source);
} else {
obs_render_main_view();
}
gs_load_vertexbuffer(nullptr);
/* --------------------------------------- */
@ -2267,6 +2372,8 @@ void OBSBasic::ClearSceneData()
ClearVolumeControls();
ClearListItems(ui->scenes);
ClearListItems(ui->sources);
ClearQuickTransitions();
ui->transitions->clear();
obs_set_output_source(0, nullptr);
obs_set_output_source(1, nullptr);
@ -2274,6 +2381,9 @@ void OBSBasic::ClearSceneData()
obs_set_output_source(3, nullptr);
obs_set_output_source(4, nullptr);
obs_set_output_source(5, nullptr);
lastScene = nullptr;
swapScene = nullptr;
programScene = nullptr;
auto cb = [](void *unused, obs_source_t *source)
{
@ -2393,8 +2503,7 @@ void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
source = obs_scene_get_source(scene);
}
/* TODO: allow transitions */
obs_set_output_source(0, source);
SetCurrentScene(source);
UNUSED_PARAMETER(prev);
}
@ -2519,7 +2628,7 @@ void OBSBasic::on_actionAddScene_triggered()
obs_scene_t *scene = obs_scene_create(name.c_str());
source = obs_scene_get_source(scene);
AddScene(source);
obs_set_output_source(0, source);
SetCurrentScene(source);
obs_scene_release(scene);
}
}
@ -2627,6 +2736,8 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
action->setCheckable(true);
action->setChecked(
obs_display_enabled(ui->preview->GetDisplay()));
if (IsPreviewProgramMode())
action->setEnabled(false);
previewProjector = new QMenu(QTStr("PreviewProjector"));
AddProjectorMenuMonitors(previewProjector, this,
@ -3694,12 +3805,17 @@ void OBSBasic::on_actionCenterToScreen_triggered()
obs_scene_enum_items(GetCurrentScene(), func, nullptr);
}
void OBSBasic::EnablePreviewDisplay(bool enable)
{
obs_display_set_enabled(ui->preview->GetDisplay(), enable);
ui->preview->setVisible(enable);
ui->previewDisabledLabel->setVisible(!enable);
}
void OBSBasic::TogglePreview()
{
bool enabled = !obs_display_enabled(ui->preview->GetDisplay());
obs_display_set_enabled(ui->preview->GetDisplay(), enabled);
ui->preview->setVisible(enabled);
ui->previewDisabledLabel->setVisible(!enabled);
previewEnabled = !previewEnabled;
EnablePreviewDisplay(previewEnabled);
}
void OBSBasic::Nudge(int dist, MoveDir dir)
@ -3791,7 +3907,11 @@ void OBSBasic::UpdateTitleBar()
const char *sceneCollection = config_get_string(App()->GlobalConfig(),
"Basic", "SceneCollection");
name << "OBS " << App()->GetVersionString();
name << "OBS ";
if (previewProgramMode)
name << "Studio ";
name << App()->GetVersionString();
name << " - " << Str("TitleBar.Profile") << ": " << profile;
name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection;

View file

@ -30,6 +30,7 @@
#include "window-basic-filters.hpp"
#include <util/platform.h>
#include <util/threading.h>
#include <util/util.hpp>
#include <QPointer>
@ -49,6 +50,8 @@ class QNetworkReply;
#define SIMPLE_ENCODER_X264 "x264"
#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu"
#define PREVIEW_EDGE_SIZE 10
struct BasicOutputHandler;
enum class QtDataRole {
@ -56,6 +59,21 @@ enum class QtDataRole {
OBSSignals,
};
struct QuickTransition {
QPushButton *button = nullptr;
OBSSource source;
obs_hotkey_id hotkey = 0;
int duration = 0;
int id = 0;
inline QuickTransition() {}
inline QuickTransition(OBSSource source_, int duration_, int id_)
: source (source_),
duration (duration_),
id (id_)
{}
};
class OBSBasic : public OBSMainWindow {
Q_OBJECT
@ -77,6 +95,7 @@ private:
bool loaded = false;
long disableSaving = 1;
bool projectChanged = false;
bool previewEnabled = true;
QPointer<QThread> updateCheckThread;
QPointer<QThread> logUploadThread;
@ -194,6 +213,65 @@ private:
obs_hotkey_pair_id streamingHotkeys, recordingHotkeys;
obs_hotkey_id forceStreamingStopHotkey;
void InitDefaultTransitions();
void InitTransition(obs_source_t *transition);
void TransitionToScene(obs_scene_t *scene, bool force = false);
void TransitionToScene(obs_source_t *scene, bool force = false);
obs_source_t *FindTransition(const char *name);
void SetTransition(obs_source_t *transition);
OBSSource GetCurrentTransition();
obs_source_t *fadeTransition;
void CreateProgramDisplay();
void CreateProgramOptions();
void AddQuickTransitionId(int id);
void AddQuickTransition();
void AddQuickTransitionHotkey(QuickTransition *qt);
void RemoveQuickTransitionHotkey(QuickTransition *qt);
void LoadQuickTransitions(obs_data_array_t *array);
obs_data_array_t *SaveQuickTransitions();
void RefreshQuickTransitions();
void CreateDefaultQuickTransitions();
QuickTransition *GetQuickTransition(int id);
int GetQuickTransitionIdx(int id);
QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt);
void ClearQuickTransitions();
void QuickTransitionClicked();
void QuickTransitionChange();
void QuickTransitionChangeDuration(int value);
void QuickTransitionRemoveClicked();
void SetPreviewProgramMode(bool enabled);
void ResizeProgram(uint32_t cx, uint32_t cy);
void SetCurrentScene(obs_scene_t *scene, bool force = false);
void SetCurrentScene(obs_source_t *scene, bool force = false);
static void RenderProgram(void *data, uint32_t cx, uint32_t cy);
std::vector<QuickTransition> quickTransitions;
QPointer<QWidget> programOptions;
QPointer<OBSQTDisplay> program;
OBSWeakSource lastScene;
OBSWeakSource swapScene;
OBSWeakSource programScene;
bool editPropertiesMode = false;
bool sceneDuplicationMode = true;
bool swapScenesMode = true;
volatile bool previewProgramMode = false;
obs_hotkey_id togglePreviewProgramHotkey = 0;
obs_hotkey_id transitionHotkey = 0;
int quickTransitionIdCounter = 1;
int programX = 0, programY = 0;
int programCX = 0, programCY = 0;
float programScale = 0.0f;
inline bool IsPreviewProgramMode() const
{
return os_atomic_load_bool(&previewProgramMode);
}
public slots:
void StartStreaming();
void StopStreaming();
@ -219,7 +297,6 @@ private slots:
void RemoveSceneItem(OBSSceneItem item);
void AddScene(OBSSource source);
void RemoveScene(OBSSource source);
void UpdateSceneSelection(OBSSource source);
void RenameSources(QString newName, QString prevName);
void SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select);
@ -237,6 +314,10 @@ private slots:
void ProcessHotkey(obs_hotkey_id id, bool pressed);
void TransitionClicked();
void TransitionStopped();
void TriggerQuickTransition(int id);
private:
/* OBS Callbacks */
static void SceneReordered(void *data, calldata_t *params);
@ -249,7 +330,6 @@ private:
static void SourceActivated(void *data, calldata_t *params);
static void SourceDeactivated(void *data, calldata_t *params);
static void SourceRenamed(void *data, calldata_t *params);
static void ChannelChanged(void *data, calldata_t *params);
static void RenderMain(void *data, uint32_t cx, uint32_t cy);
void ResizePreview(uint32_t cx, uint32_t cy);
@ -307,6 +387,7 @@ public:
void CreateSourcePopupMenu(QListWidgetItem *item, bool preview);
void UpdateTitleBar();
void UpdateSceneSelection(OBSSource source);
protected:
virtual void closeEvent(QCloseEvent *event) override;
@ -383,6 +464,11 @@ private slots:
void on_actionAlwaysOnTop_triggered();
void on_transitions_currentIndexChanged(int index);
void on_transitionProps_clicked();
void on_modeSwitch_clicked();
void logUploadFinished(const QString &text, const QString &error);
void updateFileFinished(const QString &text, const QString &error);
@ -403,6 +489,7 @@ private slots:
void OpenSceneFilters();
void OpenFilters();
void EnablePreviewDisplay(bool enable);
void TogglePreview();
void NudgeUp();

View file

@ -95,12 +95,12 @@ static void AddSource(void *_data, obs_scene_t *scene)
static void AddExisting(const char *name, const bool visible)
{
obs_source_t *source = obs_get_output_source(0);
obs_scene_t *scene = obs_scene_from_source(source);
OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
OBSScene scene = main->GetCurrentScene();
if (!scene)
return;
source = obs_get_source_by_name(name);
obs_source_t *source = obs_get_source_by_name(name);
if (source) {
AddSourceData data;
data.source = source;
@ -109,20 +109,18 @@ static void AddExisting(const char *name, const bool visible)
obs_source_release(source);
}
obs_scene_release(scene);
}
bool AddNew(QWidget *parent, const char *id, const char *name,
const bool visible, OBSSource &newSource)
{
obs_source_t *source = obs_get_output_source(0);
obs_scene_t *scene = obs_scene_from_source(source);
OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
OBSScene scene = main->GetCurrentScene();
bool success = false;
if (!source)
if (!scene)
return false;
source = obs_get_source_by_name(name);
obs_source_t *source = obs_get_source_by_name(name);
if (source) {
QMessageBox::information(parent,
QTStr("NameExists.Title"),
@ -144,8 +142,6 @@ bool AddNew(QWidget *parent, const char *id, const char *name,
}
obs_source_release(source);
obs_scene_release(scene);
return success;
}