From 0255bd0fa9869b9018c783da2661e2c3cab07c07 Mon Sep 17 00:00:00 2001 From: Clayton Groeneveld Date: Thu, 10 Sep 2020 21:18:58 -0500 Subject: [PATCH] UI: Add scene setup wizard This adds a scene setup wizard where users can select a template and easily start streaming/recording. --- UI/CMakeLists.txt | 9 +- UI/data/locale/en-US.ini | 13 + UI/data/templates/mac/.keep | 0 UI/data/templates/nix/.keep | 0 UI/data/templates/windows/.keep | 0 UI/data/themes/Acri.qss | 17 + UI/data/themes/Dark.qss | 17 + UI/data/themes/Rachni.qss | 17 + UI/data/themes/System.qss | 7 + UI/forms/OBSBasic.ui | 21 +- UI/forms/OBSSceneWizard.ui | 571 ++++++++++++++++++++++++++++++++ UI/forms/images/no-image.png | Bin 0 -> 6965 bytes UI/forms/obs.qrc | 1 + UI/obs-app.cpp | 8 + UI/obs-app.hpp | 1 + UI/scene-wizard.cpp | 444 +++++++++++++++++++++++++ UI/scene-wizard.hpp | 59 ++++ UI/volume-control.hpp | 3 + UI/window-basic-main.cpp | 10 + UI/window-basic-main.hpp | 4 + 20 files changed, 1192 insertions(+), 10 deletions(-) create mode 100644 UI/data/templates/mac/.keep create mode 100644 UI/data/templates/nix/.keep create mode 100644 UI/data/templates/windows/.keep create mode 100644 UI/forms/OBSSceneWizard.ui create mode 100644 UI/forms/images/no-image.png create mode 100644 UI/scene-wizard.cpp create mode 100644 UI/scene-wizard.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index df4d264500419c..90c5a054490a4e 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -252,7 +252,8 @@ set(obs_SOURCES obs-proxy-style.cpp locked-checkbox.cpp visibility-checkbox.cpp - media-slider.cpp) + media-slider.cpp + scene-wizard.cpp) set(obs_HEADERS ${obs_PLATFORM_HEADERS} @@ -322,7 +323,8 @@ set(obs_HEADERS log-viewer.hpp obs-proxy-style.hpp obs-proxy-style.hpp - media-slider.hpp) + media-slider.hpp + scene-wizard.hpp) set(obs_importers_HEADERS importers/importers.hpp) @@ -362,7 +364,8 @@ set(obs_UI forms/OBSUpdate.ui forms/OBSRemux.ui forms/OBSImporter.ui - forms/OBSAbout.ui) + forms/OBSAbout.ui + forms/OBSSceneWizard.ui) set(obs_QRC forms/obs.qrc) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 5d3870adbe6b54..4bf75694afcd05 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -1053,3 +1053,16 @@ ContextBar.MediaControls.PlaylistNext="Next in Playlist" ContextBar.MediaControls.PlaylistPrevious="Previous in Playlist" ContextBar.MediaControls.MediaProperties="Media Properties" ContextBar.MediaControls.BlindSeek="Media Seek Widget" + +# SceneWizard +SceneWizard="Scene Wizard" +RunSceneWizard="Run Scene Wizard" +SceneWizard.OptionsQuestion="How do you want to use OBS?" +SceneWizard.ImportButton="I want to import my existing scenes" +SceneWizard.TemplateButton="I want to choose a scene template" +SceneWizard.SetupOwnButton="I will setup OBS on my own" +SceneWizard.SelectTemplate="Select template" +SceneWizard.NoTemplates="No templates available" +SceneWizard.SelectWebcam="Select webcam" +SceneWizard.SelectMicrophone="Select microphone" +SceneWizard.SelectDesktopAudio="Select desktop audio" diff --git a/UI/data/templates/mac/.keep b/UI/data/templates/mac/.keep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/UI/data/templates/nix/.keep b/UI/data/templates/nix/.keep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/UI/data/templates/windows/.keep b/UI/data/templates/windows/.keep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 8a6fdf7bb574e0..a064c75b933b7d 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -1152,3 +1152,20 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { * [themeID="previousIcon"] { qproperty-icon: url(./Dark/media/media_previous.svg); } + +/* Scene wizard */ + +QListWidget[themeID="templateList"] { + background-color: #181819; + border: none; +} + +QListWidget::item:selected[themeID="templateList"] { + background-color: #162458; + color: rgb(225, 224, 225); +} + +QListWidget::item[themeID="templateList"] { + border: 0px; + padding: 10px; +} diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index b9ab60a5a8bc71..eb0f882ace8946 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -878,3 +878,20 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { * [themeID="previousIcon"] { qproperty-icon: url(./Dark/media/media_previous.svg); } + +/* Scene wizard */ + +QListWidget[themeID="templateList"] { + background-color: rgb(58, 57, 58); + border: none; +} + +QListWidget::item:selected[themeID="templateList"] { + background-color: rgb(88, 87, 88); + color: rgb(225, 224, 225); +} + +QListWidget::item[themeID="templateList"] { + border: 0px; + padding: 10px; +} diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 5ae927c40f7d67..f6d1cea61241f3 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -1459,3 +1459,20 @@ QPushButton#sourceFiltersButton { * [themeID="nextIcon"] { qproperty-icon: url(./Dark/media/media_next.svg); } + +/* Scene wizard */ + +QListWidget[themeID="templateList"] { + background-color: rgb(49, 54, 59); /* Blue-gray */ + border: none; +} + +QListWidget::item:selected[themeID="templateList"] { + background-color: rgb(0, 188, 212);; /* Cyan (Primary) */ + color: rgb(225, 224, 225); +} + +QListWidget::item[themeID="templateList"] { + border: 0px; + padding: 10px; +} diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 125c5089eda30b..bb530029d8bfb5 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -296,3 +296,10 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { * [themeID="previousIcon"] { qproperty-icon: url(./Dark/media/media_previous.svg); } + +/* Scene wizard */ + +QListWidget::item[themeID="templateList"] { + border: 0px; + padding: 10px; +} diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 8cc20521926764..00929623c6bb72 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -426,7 +426,7 @@ 0 0 1079 - 21 + 22 @@ -587,6 +587,8 @@ Basic.MainMenu.SceneCollection + + @@ -649,7 +651,7 @@ - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + QDockWidget::AllDockWidgetFeatures Basic.Main.Scenes @@ -784,7 +786,7 @@ - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + QDockWidget::AllDockWidgetFeatures Basic.Main.Sources @@ -920,7 +922,7 @@ - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + QDockWidget::AllDockWidgetFeatures Mixer @@ -971,7 +973,7 @@ 0 0 - 94 + 74 16 @@ -1060,7 +1062,7 @@ - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + QDockWidget::AllDockWidgetFeatures Basic.SceneTransitions @@ -1221,7 +1223,7 @@ - QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable + QDockWidget::AllDockWidgetFeatures Basic.Main.Controls @@ -2005,6 +2007,11 @@ Basic.MainMenu.View.ContextBar + + + RunSceneWizard + + diff --git a/UI/forms/OBSSceneWizard.ui b/UI/forms/OBSSceneWizard.ui new file mode 100644 index 00000000000000..d1c7942c4da1bf --- /dev/null +++ b/UI/forms/OBSSceneWizard.ui @@ -0,0 +1,571 @@ + + + SceneWizard + + + + 0 + 0 + 709 + 503 + + + + SceneWizard + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 20 + + + 20 + + + + + + 75 + true + + + + Back + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + Next + + + + + + + + + + + + 1 + + + + + 20 + + + 10 + + + 20 + + + 10 + + + 0 + + + 6 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 12 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 75 + true + + + + SceneWizard.OptionsQuestion + + + + + + + + 400 + 32 + + + + SceneWizard.TemplateButton + + + + + + + + 250 + 32 + + + + SceneWizard.ImportButton + + + + + + + + 400 + 32 + + + + SceneWizard.SetupOwnButton + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 20 + + + 10 + + + 20 + + + 10 + + + 0 + + + + + + + + 0 + 0 + + + + + 75 + true + + + + SceneWizard.SelectTemplate + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + SceneWizard.NoTemplates + + + + + + + + 200 + 200 + + + + QListView::Static + + + QListView::Adjust + + + 6 + + + + 220 + 220 + + + + QListView::IconMode + + + true + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + + + + + 20 + + + 10 + + + 20 + + + 10 + + + 0 + + + 6 + + + + + 6 + + + + + 0 + + + + + + 75 + true + + + + SceneWizard.SelectWebcam + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Disable + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 75 + true + + + + SceneWizard.SelectMicrophone + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Disable + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + + 75 + true + + + + SceneWizard.SelectDesktopAudio + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Disable + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + ClickableLabel + QLabel +
clickable-label.hpp
+
+
+ + +
diff --git a/UI/forms/images/no-image.png b/UI/forms/images/no-image.png new file mode 100644 index 0000000000000000000000000000000000000000..afe66312346b199e4791ab4e8b77a101765b09b2 GIT binary patch literal 6965 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/media/media_previous.svg images/media/media_restart.svg images/media/media_stop.svg + images/no-image.png images/settings/output.svg diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 136761e3b5b11b..ec285eb7dcf45a 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -1460,12 +1460,15 @@ bool OBSApp::IsUpdaterDisabled() #ifdef __APPLE__ #define INPUT_AUDIO_SOURCE "coreaudio_input_capture" #define OUTPUT_AUDIO_SOURCE "coreaudio_output_capture" +#define VIDEO_CAPTURE_SOURCE "av_capture_input" #elif _WIN32 #define INPUT_AUDIO_SOURCE "wasapi_input_capture" #define OUTPUT_AUDIO_SOURCE "wasapi_output_capture" +#define VIDEO_CAPTURE_SOURCE "dshow_input" #else #define INPUT_AUDIO_SOURCE "pulse_input_capture" #define OUTPUT_AUDIO_SOURCE "pulse_output_capture" +#define VIDEO_CAPTURE_SOURCE "v4l2_input" #endif const char *OBSApp::InputAudioSource() const @@ -1478,6 +1481,11 @@ const char *OBSApp::OutputAudioSource() const return OUTPUT_AUDIO_SOURCE; } +const char *OBSApp::VideoCaptureSource() const +{ + return VIDEO_CAPTURE_SOURCE; +} + const char *OBSApp::GetLastLog() const { return lastLogFile.c_str(); diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp index cb67df0e4f8738..909ff6618b9033 100644 --- a/UI/obs-app.hpp +++ b/UI/obs-app.hpp @@ -153,6 +153,7 @@ class OBSApp : public QApplication { const char *InputAudioSource() const; const char *OutputAudioSource() const; + const char *VideoCaptureSource() const; const char *GetRenderModule() const; diff --git a/UI/scene-wizard.cpp b/UI/scene-wizard.cpp new file mode 100644 index 00000000000000..4d95dd6546cb83 --- /dev/null +++ b/UI/scene-wizard.cpp @@ -0,0 +1,444 @@ +#include "scene-wizard.hpp" +#include "obs-app.hpp" +#include "platform.hpp" +#include "qt-wrappers.hpp" +#include "display-helpers.hpp" + +#include + +static void SaveSourceSettings(OBSSource source, const char *id) +{ + if (!source) + return; + + OBSData settings = obs_data_create(); + obs_data_release(settings); + + obs_data_set_string(settings, "device_id", id); + obs_source_update(source, settings); +} + +void SceneWizard::SaveMicrophone() +{ + if (ui->disableMic->isChecked()) { + obs_set_output_source(3, nullptr); + return; + } + + OBSSource source = obs_get_output_source(3); + obs_source_release(source); + + if (!source) + return; + + int index = ui->micCombo->currentIndex(); + QString id = ui->micCombo->itemData(index).toString(); + SaveSourceSettings(source, QT_TO_UTF8(id)); +} + +void SceneWizard::SaveDesktopAudio() +{ + if (ui->disableDesktopAudio->isChecked()) { + obs_set_output_source(1, nullptr); + return; + } + + OBSSource source = obs_get_output_source(1); + obs_source_release(source); + + if (!source) + return; + + int index = ui->desktopAudioCombo->currentIndex(); + QString id = ui->desktopAudioCombo->itemData(index).toString(); + SaveSourceSettings(source, QT_TO_UTF8(id)); +} + +void SceneWizard::RemoveWebcam() +{ + if (ui->disableWebcam->isChecked()) + return; + + auto cb = [](void *, obs_source_t *source) { + if (!source) + return true; + + const char *id = obs_source_get_id(source); + + if (strcmp(id, App()->VideoCaptureSource()) == 0) + obs_source_remove(source); + + return true; + }; + + obs_enum_sources(cb, nullptr); +} + +void SceneWizard::SaveWebcam() +{ + if (ui->disableWebcam->isChecked()) { + RemoveWebcam(); + return; + } + + int index = ui->webcamCombo->currentIndex(); + + if (index == 0) + return; + + const char *id = + QT_TO_UTF8(ui->webcamCombo->itemData(index).toString()); + + auto cb = [](void *data, obs_source_t *source) { + if (!source) + return true; + + const char *id = obs_source_get_id(source); + const char *deviceID = reinterpret_cast(data); + + if (strcmp(id, App()->VideoCaptureSource()) == 0) + SaveSourceSettings(source, deviceID); + + return true; + }; + + obs_enum_sources(cb, &id); +} + +void SceneWizard::on_micCombo_currentIndexChanged(int index) +{ + QString id = ui->desktopAudioCombo->itemData(index).toString(); + SaveSourceSettings(mic, QT_TO_UTF8(id)); +} + +void SceneWizard::on_desktopAudioCombo_currentIndexChanged(int index) +{ + QString id = ui->desktopAudioCombo->itemData(index).toString(); + SaveSourceSettings(desktopAudio, QT_TO_UTF8(id)); +} + +void SceneWizard::on_webcamCombo_currentIndexChanged(int index) +{ + QString id = ui->webcamCombo->itemData(index).toString(); + SaveSourceSettings(cam, QT_TO_UTF8(id)); +} + +static inline void LoadListValue(QComboBox *widget, const char *text, + const char *val) +{ + widget->addItem(QT_UTF8(text), QT_UTF8(val)); +} + +static void LoadListValues(QComboBox *widget, obs_property_t *prop) +{ + size_t count = obs_property_list_item_count(prop); + + for (size_t i = 0; i < count; i++) { + const char *name = obs_property_list_item_name(prop, i); + const char *val = obs_property_list_item_string(prop, i); + LoadListValue(widget, name, val); + } +} + +void SceneWizard::LoadDevices() +{ + obs_properties_t *cam_props = obs_source_properties(cam); + obs_properties_t *input_props = obs_source_properties(mic); + obs_properties_t *output_props = obs_source_properties(desktopAudio); + + if (cam_props) { + obs_property_t *cams = + obs_properties_get(cam_props, "device_id"); + LoadListValues(ui->webcamCombo, cams); + obs_properties_destroy(cam_props); + } + + if (input_props) { + obs_property_t *inputs = + obs_properties_get(input_props, "device_id"); + LoadListValues(ui->micCombo, inputs); + obs_properties_destroy(input_props); + } + + if (output_props) { + obs_property_t *outputs = + obs_properties_get(output_props, "device_id"); + LoadListValues(ui->desktopAudioCombo, outputs); + obs_properties_destroy(output_props); + } +} + +void SceneWizard::OBSRender(void *data, uint32_t cx, uint32_t cy) +{ + SceneWizard *window = static_cast(data); + + if (!window->cam) + return; + + uint32_t sourceCX = std::max(obs_source_get_width(window->cam), 1u); + uint32_t sourceCY = std::max(obs_source_get_height(window->cam), 1u); + + int x, y; + int newCX, newCY; + float scale; + + GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale); + + newCX = int(scale * float(sourceCX)); + newCY = int(scale * float(sourceCY)); + + gs_viewport_push(); + gs_projection_push(); + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); + gs_set_viewport(x, y, newCX, newCY); + + obs_source_video_render(window->cam); + + gs_projection_pop(); + gs_viewport_pop(); +} + +SceneWizard::SceneWizard(QWidget *parent) + : QDialog(parent), ui(new Ui::SceneWizard) +{ + ui->setupUi(this); + setFixedSize(750, 750); + + ui->back->setText("" + + QTStr("Back") + ""); + ui->next->setText("" + + QTStr("Next") + ""); + ui->bottomWidget->setProperty("themeID", "aboutHLayout"); + ui->back->setProperty("themeID", "aboutHLayout"); + ui->next->setProperty("themeID", "aboutHLayout"); + ui->templateList->setProperty("themeID", "templateList"); + + style()->unpolish(ui->templateList); + style()->polish(ui->templateList); + + ui->noTemplates->hide(); + + InitTemplates(); + + mic = obs_source_create(App()->InputAudioSource(), "microphone", + nullptr, nullptr); + desktopAudio = obs_source_create(App()->OutputAudioSource(), + "desktop_audio", nullptr, nullptr); + cam = obs_source_create_private(App()->VideoCaptureSource(), nullptr, + nullptr); + + obs_source_release(mic); + obs_source_release(desktopAudio); + obs_source_release(cam); + + preview = new OBSQTDisplay(ui->devicePage); + preview->setMinimumSize(20, 150); + preview->setSizePolicy( + QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + + auto addDrawCallback = [this]() { + obs_display_add_draw_callback(preview->GetDisplay(), + SceneWizard::OBSRender, this); + }; + + preview->show(); + connect(preview.data(), &OBSQTDisplay::DisplayCreated, addDrawCallback); + + LoadDevices(); + + micVol = new VolControl(mic, true, false); + desktopVol = new VolControl(desktopAudio, true, false); + + micVol->slider->hide(); + micVol->config->hide(); + micVol->nameLabel->hide(); + micVol->volLabel->hide(); + micVol->mute->hide(); + + desktopVol->slider->hide(); + desktopVol->config->hide(); + desktopVol->nameLabel->hide(); + desktopVol->volLabel->hide(); + desktopVol->mute->hide(); + + ui->deviceLayout->insertWidget(2, preview); + ui->deviceLayout->insertWidget(6, micVol); + ui->deviceLayout->insertWidget(10, desktopVol); + + ui->templateList->setCurrentRow(0); + GoToOptionsPage(); +} + +SceneWizard::~SceneWizard() +{ + obs_display_remove_draw_callback(preview->GetDisplay(), + SceneWizard::OBSRender, this); +} + +void SceneWizard::UpdateSceneCollection() +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + QString templatePath; + + QListWidgetItem *item = ui->templateList->currentItem(); + + if (item) + templatePath = item->data(Qt::UserRole).toString(); + + if (!templatePath.isEmpty()) { + main->SaveProjectNow(); + main->Load(QT_TO_UTF8(templatePath)); + main->RefreshSceneCollections(); + + const char *newName = config_get_string( + App()->GlobalConfig(), "Basic", "SceneCollection"); + const char *newFile = config_get_string( + App()->GlobalConfig(), "Basic", "SceneCollectionFile"); + + blog(LOG_INFO, "Switched to scene collection '%s' (%s.json)", + newName, newFile); + blog(LOG_INFO, + "------------------------------------------------"); + + main->UpdateTitleBar(); + + if (main->api) + main->api->on_event( + OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); + } + + SaveMicrophone(); + SaveDesktopAudio(); + SaveWebcam(); + + close(); +} + +void SceneWizard::on_importButton_clicked() +{ + close(); + + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + main->on_actionImportSceneCollection_triggered(); +} + +void SceneWizard::on_templateButton_clicked() +{ + GoToTemplatePage(); +} + +void SceneWizard::on_setupOnOwnButton_clicked() +{ + close(); +} + +void SceneWizard::on_disableWebcam_toggled(bool checked) +{ + ui->webcamCombo->setEnabled(!checked); +} + +void SceneWizard::on_disableMic_toggled(bool checked) +{ + ui->micCombo->setEnabled(!checked); +} + +void SceneWizard::on_disableDesktopAudio_toggled(bool checked) +{ + ui->desktopAudioCombo->setEnabled(!checked); +} + +void SceneWizard::GoToOptionsPage() +{ + ui->stackedWidget->setCurrentWidget(ui->optionPage); + ui->bottomWidget->hide(); +} + +void SceneWizard::GoToTemplatePage() +{ + ui->stackedWidget->setCurrentWidget(ui->templatePage); + ui->bottomWidget->show(); +} + +void SceneWizard::GoToDevicesPage() +{ + ui->stackedWidget->setCurrentWidget(ui->devicePage); + ui->bottomWidget->show(); + ui->next->setText("" + + QTStr("Finish") + ""); +} + +void SceneWizard::on_back_clicked() +{ + QWidget *widget = ui->stackedWidget->currentWidget(); + + if (widget == ui->devicePage) + GoToTemplatePage(); + else if (widget == ui->templatePage) + GoToOptionsPage(); +} + +void SceneWizard::on_next_clicked() +{ + QWidget *widget = ui->stackedWidget->currentWidget(); + + if (widget == ui->devicePage) { + UpdateSceneCollection(); + close(); + } else if (widget == ui->templatePage) { + GoToDevicesPage(); + } +} + +void SceneWizard::closeEvent(QCloseEvent *event) +{ + deleteLater(); + QDialog::closeEvent(event); +} + +#ifdef __APPLE__ +#define TEMPLATE_FOLDER "mac" +#elif _WIN32 +#define TEMPLATE_FOLDER "windows" +#else +#define TEMPLATE_FOLDER "nix" +#endif + +void SceneWizard::InitTemplates() +{ + std::string path; + + if (!GetDataFilePath("templates/", path)) + return; + + QDir folder(QT_UTF8(path.c_str()) + TEMPLATE_FOLDER + "/"); + + QStringList files = + folder.entryList(QStringList() << "*.json", QDir::Files); + + if (files.count() == 0) { + ui->noTemplates->show(); + return; + } + + for (auto file : files) { + QString filename; + filename += QT_UTF8(path.c_str()) + TEMPLATE_FOLDER + "/"; + filename += file; + + QListWidgetItem *item = new QListWidgetItem(); + item->setData(Qt::UserRole, filename); + item->setText(file.replace(".json", "")); + + QPixmap pixmap(filename.replace(".json", ".png")); + + if (!pixmap) + pixmap.load(QString(":res/images/no-image.png")); + + QIcon icon; + icon.addPixmap(pixmap, QIcon::Normal); + icon.addPixmap(pixmap, QIcon::Selected); + item->setIcon(icon); + + ui->templateList->addItem(item); + } +} diff --git a/UI/scene-wizard.hpp b/UI/scene-wizard.hpp new file mode 100644 index 00000000000000..3dce33efd98f58 --- /dev/null +++ b/UI/scene-wizard.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include "ui_OBSSceneWizard.h" +#include "window-basic-main.hpp" +#include "qt-display.hpp" +#include "volume-control.hpp" +#include "mute-checkbox.hpp" + +class SceneWizard : public QDialog { + Q_OBJECT + +public: + SceneWizard(QWidget *parent = nullptr); + ~SceneWizard(); + + OBSSource webcam = nullptr; + OBSSource mic = nullptr; + OBSSource desktopAudio = nullptr; + OBSSource cam = nullptr; + QPointer preview; + +private: + std::unique_ptr ui; + void InitTemplates(); + void UpdateSceneCollection(); + void LoadDevices(); + void SaveMicrophone(); + void SaveDesktopAudio(); + void SaveWebcam(); + void RemoveWebcam(); + + QPointer micVol; + QPointer desktopVol; + + static void OBSRender(void *data, uint32_t cx, uint32_t cy); + +private slots: + void on_templateButton_clicked(); + void on_importButton_clicked(); + void on_setupOnOwnButton_clicked(); + + void on_back_clicked(); + void on_next_clicked(); + void on_desktopAudioCombo_currentIndexChanged(int index); + void on_micCombo_currentIndexChanged(int index); + void on_webcamCombo_currentIndexChanged(int index); + void on_disableWebcam_toggled(bool checked); + void on_disableMic_toggled(bool checked); + void on_disableDesktopAudio_toggled(bool checked); + + void GoToOptionsPage(); + void GoToTemplatePage(); + void GoToDevicesPage(); + +protected: + virtual void closeEvent(QCloseEvent *event); +}; diff --git a/UI/volume-control.hpp b/UI/volume-control.hpp index 82a2bcb50b99d6..01cdc38bbe3eed 100644 --- a/UI/volume-control.hpp +++ b/UI/volume-control.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "scene-wizard.hpp" class QPushButton; class VolumeMeterTimer; @@ -206,6 +207,8 @@ class MuteCheckBox; class VolControl : public QWidget { Q_OBJECT + friend class SceneWizard; + private: OBSSource source; QLabel *nameLabel; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 4b5db15f43eea9..df70f2792b77d9 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -58,6 +58,7 @@ #include "remote-text.hpp" #include "ui-validation.hpp" #include "media-controls.hpp" +#include "scene-wizard.hpp" #include #include @@ -7932,6 +7933,9 @@ void OBSBasic::on_autoConfigure_triggered() test.setModal(true); test.show(); test.exec(); + + QMetaObject::invokeMethod(this, "on_actionRunSceneWizard_triggered", + Qt::QueuedConnection); } void OBSBasic::on_stats_triggered() @@ -8367,3 +8371,9 @@ void OBSBasic::on_sourceFiltersButton_clicked() { OpenFilters(); } + +void OBSBasic::on_actionRunSceneWizard_triggered() +{ + SceneWizard sw(this); + sw.exec(); +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 8e960c675fdaa3..9b761ceb386239 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -35,6 +35,7 @@ #include "window-basic-about.hpp" #include "auth-base.hpp" #include "log-viewer.hpp" +#include "scene-wizard.hpp" #include @@ -167,6 +168,7 @@ class OBSBasic : public OBSMainWindow { friend class DeviceToolbarPropertiesThread; friend struct BasicOutputHandler; friend struct OBSStudioAPI; + friend class SceneWizard; enum class MoveDir { Up, Down, Left, Right }; @@ -870,6 +872,8 @@ private slots: void on_actionVerticalCenter_triggered(); void on_actionHorizontalCenter_triggered(); + void on_actionRunSceneWizard_triggered(); + void on_customContextMenuRequested(const QPoint &pos); void on_scenes_currentItemChanged(QListWidgetItem *current,