/****************************************************************************** Copyright (C) 2023 by Lain Bailey Zachary Lund Philippe Groarke 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 . ******************************************************************************/ #include "OBSBasic.hpp" #include "OBSProjector.hpp" #include #include #include #include #include void OBSBasic::CreateProgramDisplay() { program = new OBSQTDisplay(); program->setContextMenuPolicy(Qt::CustomContextMenu); connect(program.data(), &QWidget::customContextMenuRequested, this, &OBSBasic::ProgramViewContextMenuRequested); auto displayResize = [this]() { struct obs_video_info ovi; if (obs_get_video_info(&ovi)) ResizeProgram(ovi.base_width, ovi.base_height); }; connect(program.data(), &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.data(), &OBSQTDisplay::DisplayCreated, addDisplay); program->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } #define T_BAR_PRECISION 1024 #define T_BAR_PRECISION_F ((float)T_BAR_PRECISION) #define T_BAR_CLAMP (T_BAR_PRECISION / 10) void OBSBasic::CreateProgramOptions() { programOptions = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(); layout->setSpacing(4); QPushButton *configTransitions = new QPushButton(); configTransitions->setProperty("class", "icon-dots-vert"); QHBoxLayout *mainButtonLayout = new QHBoxLayout(); mainButtonLayout->setSpacing(2); transitionButton = new QPushButton(QTStr("Transition")); transitionButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QHBoxLayout *quickTransitionsLayout = new QHBoxLayout(); quickTransitionsLayout->setSpacing(2); QPushButton *addQuickTransition = new QPushButton(); addQuickTransition->setProperty("class", "icon-plus"); QLabel *quickTransitionsLabel = new QLabel(QTStr("QuickTransitions")); quickTransitionsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); quickTransitionsLayout->addWidget(quickTransitionsLabel); quickTransitionsLayout->addWidget(addQuickTransition); mainButtonLayout->addWidget(transitionButton); mainButtonLayout->addWidget(configTransitions); tBar = new SliderIgnoreClick(Qt::Horizontal); tBar->setMinimum(0); tBar->setMaximum(T_BAR_PRECISION - 1); tBar->setProperty("class", "slider-tbar"); connect(tBar, &QSlider::valueChanged, this, &OBSBasic::TBarChanged); connect(tBar, &QSlider::sliderReleased, this, &OBSBasic::TBarReleased); layout->addStretch(0); layout->addLayout(mainButtonLayout); layout->addLayout(quickTransitionsLayout); layout->addWidget(tBar); layout->addStretch(0); programOptions->setLayout(layout); auto onAdd = [this]() { QScopedPointer 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.data(), &QAbstractButton::clicked, this, &OBSBasic::TransitionClicked); connect(addQuickTransition, &QAbstractButton::clicked, onAdd); connect(configTransitions, &QAbstractButton::clicked, onConfig); } void OBSBasic::TogglePreviewProgramMode() { SetPreviewProgramMode(!IsPreviewProgramMode()); } void OBSBasic::SetPreviewProgramMode(bool enabled) { if (IsPreviewProgramMode() == enabled) return; os_atomic_set_bool(&previewProgramMode, enabled); emit PreviewProgramModeChanged(enabled); if (IsPreviewProgramMode()) { if (!previewEnabled) EnablePreviewDisplay(true); CreateProgramDisplay(); CreateProgramOptions(); OBSScene curScene = GetCurrentScene(); OBSSceneAutoRelease dup; if (sceneDuplicationMode) { dup = obs_scene_duplicate(curScene, obs_source_get_name(obs_scene_get_source(curScene)), editPropertiesMode ? OBS_SCENE_DUP_PRIVATE_COPY : OBS_SCENE_DUP_PRIVATE_REFS); } else { dup = std::move(OBSScene(curScene)); } OBSSourceAutoRelease transition = obs_get_output_source(0); obs_source_t *dup_source = obs_scene_get_source(dup); obs_transition_set(transition, dup_source); if (curScene) { obs_source_t *source = obs_scene_get_source(curScene); obs_source_inc_showing(source); lastScene = OBSGetWeakRef(source); programScene = OBSGetWeakRef(source); } RefreshQuickTransitions(); programLabel = new QLabel(QTStr("StudioMode.ProgramSceneLabel"), this); programLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); programLabel->setProperty("class", "label-preview-title"); programWidget = new QWidget(); programLayout = new QVBoxLayout(); programLayout->setContentsMargins(0, 0, 0, 0); programLayout->setSpacing(0); programLayout->addWidget(programLabel); programLayout->addWidget(program); programWidget->setLayout(programLayout); ui->previewLayout->addWidget(programOptions); ui->previewLayout->addWidget(programWidget); ui->previewLayout->setAlignment(programOptions, Qt::AlignCenter); OnEvent(OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED); blog(LOG_INFO, "Switched to Preview/Program mode"); blog(LOG_INFO, "-----------------------------" "-------------------"); } else { OBSSource actualProgramScene = OBSGetStrongRef(programScene); if (!actualProgramScene) actualProgramScene = GetCurrentSceneSource(); else SetCurrentScene(actualProgramScene, true); TransitionToScene(actualProgramScene, true); delete programOptions; delete program; delete programLabel; delete programWidget; if (lastScene) { OBSSource actualLastScene = OBSGetStrongRef(lastScene); if (actualLastScene) obs_source_dec_showing(actualLastScene); lastScene = nullptr; } programScene = nullptr; swapScene = nullptr; prevFTBSource = nullptr; for (QuickTransition &qt : quickTransitions) qt.button = nullptr; if (!previewEnabled) EnablePreviewDisplay(false); ui->transitions->setEnabled(true); tBarActive = false; OnEvent(OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED); blog(LOG_INFO, "Switched to regular Preview mode"); blog(LOG_INFO, "-----------------------------" "-------------------"); } ResetUI(); UpdateTitleBar(); } void OBSBasic::RenderProgram(void *data, uint32_t, uint32_t) { GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderProgram"); OBSBasic *window = static_cast(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); obs_render_main_texture_src_color_only(); gs_load_vertexbuffer(nullptr); /* --------------------------------------- */ gs_projection_pop(); gs_viewport_pop(); GS_DEBUG_MARKER_END(); } 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); } void OBSBasic::UpdatePreviewProgramIndicators() { bool labels = previewProgramMode ? config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioModeLabels") : false; ui->previewLabel->setVisible(labels); if (programLabel) programLabel->setVisible(labels); if (!labels) return; QString preview = QTStr("StudioMode.PreviewSceneName").arg(QT_UTF8(obs_source_get_name(GetCurrentSceneSource()))); QString program = QTStr("StudioMode.ProgramSceneName").arg(QT_UTF8(obs_source_get_name(GetProgramSource()))); if (ui->previewLabel->text() != preview) ui->previewLabel->setText(preview); if (programLabel && programLabel->text() != program) programLabel->setText(program); } OBSSource OBSBasic::GetProgramSource() { return OBSGetStrongRef(programScene); } void OBSBasic::ProgramViewContextMenuRequested() { QMenu popup(this); QPointer studioProgramProjector; studioProgramProjector = new QMenu(QTStr("Projector.Open.Program")); AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); studioProgramProjector->addSeparator(); studioProgramProjector->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenStudioProgramWindow); popup.addMenu(studioProgramProjector); popup.addSeparator(); popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); popup.exec(QCursor::pos()); } void OBSBasic::EnablePreviewProgram() { SetPreviewProgramMode(true); } void OBSBasic::DisablePreviewProgram() { SetPreviewProgramMode(false); } void OBSBasic::OpenStudioProgramProjector() { int monitor = sender()->property("monitor").toInt(); OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); } void OBSBasic::OpenStudioProgramWindow() { OpenProjector(nullptr, -1, ProjectorType::StudioProgram); }