2013-11-07 16:45:03 -07:00
|
|
|
/******************************************************************************
|
2023-05-18 17:37:26 -07:00
|
|
|
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
|
|
|
|
Zachary Lund <admin@computerquip.com>
|
|
|
|
|
Philippe Groarke <philippe.groarke@gmail.com>
|
2013-11-07 16:45:03 -07:00
|
|
|
|
|
|
|
|
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
|
2013-12-02 22:24:38 -07:00
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
2013-11-07 16:45:03 -07:00
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
2013-11-09 00:19:38 -07:00
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2013-11-07 16:45:03 -07:00
|
|
|
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/>.
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
#include "OBSBasic.hpp"
|
2018-09-16 10:02:43 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
#include <components/ApplicationAudioCaptureToolbar.hpp>
|
|
|
|
|
#include <components/AudioCaptureToolbar.hpp>
|
|
|
|
|
#include <components/BrowserToolbar.hpp>
|
|
|
|
|
#include <components/ColorSourceToolbar.hpp>
|
|
|
|
|
#include <components/DeviceCaptureToolbar.hpp>
|
|
|
|
|
#include <components/DisplayCaptureToolbar.hpp>
|
|
|
|
|
#include <components/GameCaptureToolbar.hpp>
|
|
|
|
|
#include <components/ImageSourceToolbar.hpp>
|
|
|
|
|
#include <components/MediaControls.hpp>
|
|
|
|
|
#include <components/TextSourceToolbar.hpp>
|
|
|
|
|
#include <components/WindowCaptureToolbar.hpp>
|
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.
2016-01-23 11:19:29 -08:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
#include <qt-wrappers.hpp>
|
2014-04-26 23:47:50 -07:00
|
|
|
|
2015-02-17 02:45:34 -05:00
|
|
|
void OBSBasic::copyActionsDynamicProperties()
|
|
|
|
|
{
|
|
|
|
|
// Themes need the QAction dynamic properties
|
|
|
|
|
for (QAction *x : ui->scenesToolbar->actions()) {
|
|
|
|
|
QWidget *temp = ui->scenesToolbar->widgetForAction(x);
|
|
|
|
|
|
2023-02-10 14:52:15 +03:00
|
|
|
if (!temp)
|
|
|
|
|
continue;
|
|
|
|
|
|
2015-02-17 02:45:34 -05:00
|
|
|
for (QByteArray &y : x->dynamicPropertyNames()) {
|
|
|
|
|
temp->setProperty(y, x->property(y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (QAction *x : ui->sourcesToolbar->actions()) {
|
|
|
|
|
QWidget *temp = ui->sourcesToolbar->widgetForAction(x);
|
|
|
|
|
|
2023-02-10 14:52:15 +03:00
|
|
|
if (!temp)
|
|
|
|
|
continue;
|
|
|
|
|
|
2015-02-17 02:45:34 -05:00
|
|
|
for (QByteArray &y : x->dynamicPropertyNames()) {
|
|
|
|
|
temp->setProperty(y, x->property(y));
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-25 07:22:24 -05:00
|
|
|
|
|
|
|
|
for (QAction *x : ui->mixerToolbar->actions()) {
|
|
|
|
|
QWidget *temp = ui->mixerToolbar->widgetForAction(x);
|
|
|
|
|
|
2023-02-10 14:52:15 +03:00
|
|
|
if (!temp)
|
|
|
|
|
continue;
|
|
|
|
|
|
2022-07-25 07:22:24 -05:00
|
|
|
for (QByteArray &y : x->dynamicPropertyNames()) {
|
|
|
|
|
temp->setProperty(y, x->property(y));
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-17 02:45:34 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::ClearContextBar()
|
2018-01-09 18:45:20 -02:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
QLayoutItem *la = ui->emptySpace->layout()->itemAt(0);
|
|
|
|
|
if (la) {
|
|
|
|
|
delete la->widget();
|
|
|
|
|
ui->emptySpace->layout()->removeItem(la);
|
2018-01-09 18:45:20 -02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::UpdateContextBarVisibility()
|
2018-01-19 22:57:36 +01:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
int width = ui->centralwidget->size().width();
|
2018-01-19 22:57:36 +01:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ContextBarSize contextBarSizeNew;
|
|
|
|
|
if (width >= 740) {
|
|
|
|
|
contextBarSizeNew = ContextBarSize_Normal;
|
|
|
|
|
} else if (width >= 600) {
|
|
|
|
|
contextBarSizeNew = ContextBarSize_Reduced;
|
|
|
|
|
} else {
|
|
|
|
|
contextBarSizeNew = ContextBarSize_Minimized;
|
2018-01-19 22:57:36 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (contextBarSize == contextBarSizeNew)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
contextBarSize = contextBarSizeNew;
|
|
|
|
|
UpdateContextBarDeferred();
|
2018-01-19 22:57:36 +01:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
static bool is_network_media_source(obs_source_t *source, const char *id)
|
2014-05-03 22:54:38 -07:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
if (strcmp(id, "ffmpeg_source") != 0)
|
|
|
|
|
return false;
|
2014-05-03 22:54:38 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
OBSDataAutoRelease s = obs_source_get_settings(source);
|
|
|
|
|
bool is_local_file = obs_data_get_bool(s, "is_local_file");
|
2014-05-03 22:54:38 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
return !is_local_file;
|
2021-07-03 17:55:47 +12:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::UpdateContextBarDeferred(bool force)
|
2015-06-25 03:53:48 -07:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force));
|
2015-06-25 03:53:48 -07:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::SourceToolBarActionsSetEnabled()
|
2016-12-29 09:21:53 -06:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
bool enable = false;
|
|
|
|
|
bool disableProps = false;
|
2020-04-27 23:26:20 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
OBSSceneItem item = GetCurrentSceneItem();
|
2020-04-27 23:26:20 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (item) {
|
|
|
|
|
OBSSource source = obs_sceneitem_get_source(item);
|
|
|
|
|
disableProps = !obs_source_configurable(source);
|
2020-04-27 23:26:20 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
enable = true;
|
|
|
|
|
}
|
2020-04-27 23:26:20 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (disableProps)
|
|
|
|
|
ui->actionSourceProperties->setEnabled(false);
|
|
|
|
|
else
|
|
|
|
|
ui->actionSourceProperties->setEnabled(enable);
|
2017-11-08 22:38:44 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->actionRemoveSource->setEnabled(enable);
|
|
|
|
|
ui->actionSourceUp->setEnabled(enable);
|
|
|
|
|
ui->actionSourceDown->setEnabled(enable);
|
2017-10-21 15:51:01 -02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
RefreshToolBarStyling(ui->sourcesToolbar);
|
2017-10-21 15:51:01 -02:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::UpdateContextBar(bool force)
|
2014-04-26 23:47:50 -07:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
SourceToolBarActionsSetEnabled();
|
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.
2016-01-23 11:19:29 -08:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (!ui->contextContainer->isVisible() && !force)
|
|
|
|
|
return;
|
2015-08-21 17:56:37 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
OBSSceneItem item = GetCurrentSceneItem();
|
2016-07-26 01:32:43 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (item) {
|
|
|
|
|
obs_source_t *source = obs_sceneitem_get_source(item);
|
2022-08-22 16:04:13 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
bool updateNeeded = true;
|
|
|
|
|
QLayoutItem *la = ui->emptySpace->layout()->itemAt(0);
|
|
|
|
|
if (la) {
|
|
|
|
|
if (SourceToolbar *toolbar = dynamic_cast<SourceToolbar *>(la->widget())) {
|
|
|
|
|
if (toolbar->GetSource() == source)
|
|
|
|
|
updateNeeded = false;
|
|
|
|
|
} else if (MediaControls *toolbar = dynamic_cast<MediaControls *>(la->widget())) {
|
|
|
|
|
if (toolbar->GetSource() == source)
|
|
|
|
|
updateNeeded = false;
|
|
|
|
|
}
|
2022-08-22 16:04:13 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
const char *id = obs_source_get_unversioned_id(source);
|
|
|
|
|
uint32_t flags = obs_source_get_output_flags(source);
|
2024-04-07 20:31:15 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION);
|
2016-08-28 14:24:14 -07:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) {
|
|
|
|
|
ClearContextBar();
|
|
|
|
|
if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) {
|
|
|
|
|
if (!is_network_media_source(source, id)) {
|
|
|
|
|
MediaControls *mediaControls = new MediaControls(ui->emptySpace);
|
|
|
|
|
mediaControls->SetSource(source);
|
2023-12-19 00:04:46 +01:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->emptySpace->layout()->addWidget(mediaControls);
|
|
|
|
|
}
|
|
|
|
|
} else if (strcmp(id, "browser_source") == 0) {
|
|
|
|
|
BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "wasapi_input_capture") == 0 ||
|
|
|
|
|
strcmp(id, "wasapi_output_capture") == 0 ||
|
|
|
|
|
strcmp(id, "coreaudio_input_capture") == 0 ||
|
|
|
|
|
strcmp(id, "coreaudio_output_capture") == 0 ||
|
|
|
|
|
strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 ||
|
|
|
|
|
strcmp(id, "alsa_input_capture") == 0) {
|
|
|
|
|
AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
c->Init();
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "wasapi_process_output_capture") == 0) {
|
|
|
|
|
ApplicationAudioCaptureToolbar *c =
|
|
|
|
|
new ApplicationAudioCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
c->Init();
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) {
|
|
|
|
|
WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
c->Init();
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 ||
|
|
|
|
|
strcmp(id, "xshm_input") == 0) {
|
|
|
|
|
DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
c->Init();
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "dshow_input") == 0) {
|
|
|
|
|
DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-10-10 20:12:20 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "game_capture") == 0) {
|
|
|
|
|
GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2019-10-15 23:20:35 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "image_source") == 0) {
|
|
|
|
|
ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2020-02-14 23:30:34 +11:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "color_source") == 0) {
|
|
|
|
|
ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
2022-02-22 00:40:17 +11:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
} else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) {
|
|
|
|
|
TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source);
|
|
|
|
|
ui->emptySpace->layout()->addWidget(c);
|
|
|
|
|
}
|
|
|
|
|
} else if (contextBarSize == ContextBarSize_Minimized) {
|
|
|
|
|
ClearContextBar();
|
2022-02-22 00:40:17 +11:00
|
|
|
}
|
2021-08-19 23:56:49 -05:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
QIcon icon;
|
2020-06-27 02:31:08 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (strcmp(id, "scene") == 0)
|
|
|
|
|
icon = GetSceneIcon();
|
|
|
|
|
else if (strcmp(id, "group") == 0)
|
|
|
|
|
icon = GetGroupIcon();
|
|
|
|
|
else
|
|
|
|
|
icon = GetSourceIcon(id);
|
2022-01-27 02:37:32 +01:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
|
|
|
|
ui->contextSourceIcon->setPixmap(pixmap);
|
|
|
|
|
ui->contextSourceIconSpacer->hide();
|
|
|
|
|
ui->contextSourceIcon->show();
|
2021-07-03 17:55:47 +12:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
const char *name = obs_source_get_name(source);
|
|
|
|
|
ui->contextSourceLabel->setText(name);
|
2021-07-03 17:55:47 +12:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->sourceFiltersButton->setEnabled(true);
|
|
|
|
|
ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source));
|
2021-07-03 17:55:47 +12:00
|
|
|
} else {
|
2024-12-11 17:43:35 +01:00
|
|
|
ClearContextBar();
|
|
|
|
|
ui->contextSourceIcon->hide();
|
|
|
|
|
ui->contextSourceIconSpacer->show();
|
|
|
|
|
ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource"));
|
2021-07-03 17:55:47 +12:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->sourceFiltersButton->setEnabled(false);
|
|
|
|
|
ui->sourcePropertiesButton->setEnabled(false);
|
|
|
|
|
ui->sourceInteractButton->setVisible(false);
|
2021-07-03 17:55:47 +12:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
if (contextBarSize == ContextBarSize_Normal) {
|
|
|
|
|
ui->sourcePropertiesButton->setText(QTStr("Properties"));
|
|
|
|
|
ui->sourceFiltersButton->setText(QTStr("Filters"));
|
|
|
|
|
ui->sourceInteractButton->setText(QTStr("Interact"));
|
2021-07-03 17:55:47 +12:00
|
|
|
} else {
|
2024-12-11 17:43:35 +01:00
|
|
|
ui->sourcePropertiesButton->setText("");
|
|
|
|
|
ui->sourceFiltersButton->setText("");
|
|
|
|
|
ui->sourceInteractButton->setText("");
|
2024-07-31 10:53:59 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::ShowContextBar()
|
2024-07-31 10:53:59 -04:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
on_toggleContextBar_toggled(true);
|
|
|
|
|
ui->toggleContextBar->setChecked(true);
|
2024-07-31 10:53:59 -04:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::HideContextBar()
|
2024-07-31 10:53:59 -04:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
on_toggleContextBar_toggled(false);
|
|
|
|
|
ui->toggleContextBar->setChecked(false);
|
2024-07-31 10:53:59 -04:00
|
|
|
}
|
2024-09-03 16:29:30 +02:00
|
|
|
|
2024-12-11 17:43:35 +01:00
|
|
|
void OBSBasic::on_toggleContextBar_toggled(bool visible)
|
2024-09-03 16:29:30 +02:00
|
|
|
{
|
2024-12-11 17:43:35 +01:00
|
|
|
config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible);
|
|
|
|
|
this->ui->contextContainer->setVisible(visible);
|
|
|
|
|
UpdateContextBar(true);
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|