提交 544953c8 编写于 作者: J jp9000

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.
上级 6f98bd9f
......@@ -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
......
......@@ -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"
......
......@@ -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>
</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>
<layout class="QHBoxLayout" name="previewLayout">
<property name="spacing">
<number>2</number>
</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>
</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="text">
<string>Basic.Main.StartRecording</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="text">
<string>Settings</string>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</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="exitButton">
<property name="text">
<string>Exit</string>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>4</number>
</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>
<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>
......
......@@ -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);
......
此差异已折叠。
......@@ -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;
......
......@@ -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();
......
......@@ -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;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册