diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index 43910f316..118328dae 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -567,6 +567,11 @@ Basic.Settings.General.SystemTrayHideMinimize="Always minimize to system tray in
Basic.Settings.General.SaveProjectors="Save projectors on exit"
Basic.Settings.General.SwitchOnDoubleClick="Transition to scene when double-clicked"
Basic.Settings.General.StudioPortraitLayout="Enable portrait/vertical layout"
+Basic.Settings.General.MultiviewLayout="Multiview Layout"
+Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, Top"
+Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, Bottom"
+Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, Left"
+Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, Right"
# basic mode 'stream' settings
Basic.Settings.Stream="Stream"
diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui
index 8909c4780..8430b10a3 100644
--- a/UI/forms/OBSBasicSettings.ui
+++ b/UI/forms/OBSBasicSettings.ui
@@ -146,7 +146,7 @@
0
0
801
- 715
+ 1044
@@ -549,6 +549,9 @@
QFormLayout::AllNonFixedFieldsGrow
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
2
@@ -579,6 +582,19 @@
+ -
+
+
+ -
+
+
+ Basic.Settings.General.MultiviewLayout
+
+
+ multiviewLayout
+
+
+
diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp
index f73ec4892..6db249f14 100644
--- a/UI/window-basic-settings.cpp
+++ b/UI/window-basic-settings.cpp
@@ -45,6 +45,7 @@
#include "window-basic-main.hpp"
#include "window-basic-settings.hpp"
#include "window-basic-main-outputs.hpp"
+#include "window-projector.hpp"
#include
@@ -317,6 +318,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
HookWidget(ui->snapDistance, DSCROLL_CHANGED,GENERAL_CHANGED);
HookWidget(ui->doubleClickSwitch, CHECK_CHANGED, GENERAL_CHANGED);
HookWidget(ui->studioPortraitLayout, CHECK_CHANGED, GENERAL_CHANGED);
+ HookWidget(ui->multiviewLayout, COMBO_CHANGED, GENERAL_CHANGED);
HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->streamType, COMBO_CHANGED, STREAM1_CHANGED);
HookWidget(ui->simpleOutputPath, EDIT_CHANGED, OUTPUTS_CHANGED);
@@ -1087,6 +1089,31 @@ void OBSBasicSettings::LoadGeneralSettings()
"BasicWindow", "StudioPortraitLayout");
ui->studioPortraitLayout->setChecked(studioPortraitLayout);
+ ui->multiviewLayout->addItem(QTStr(
+ "Basic.Settings.General.MultiviewLayout.Horizontal.Top"),
+ QT_UTF8("horizontaltop"));
+ ui->multiviewLayout->addItem(QTStr(
+ "Basic.Settings.General.MultiviewLayout.Horizontal.Bottom"),
+ QT_UTF8("horizontalbottom"));
+ ui->multiviewLayout->addItem(QTStr(
+ "Basic.Settings.General.MultiviewLayout.Vertical.Left"),
+ QT_UTF8("verticalleft"));
+ ui->multiviewLayout->addItem(QTStr(
+ "Basic.Settings.General.MultiviewLayout.Vertical.Right"),
+ QT_UTF8("verticalright"));
+
+ const char *multiviewLayoutText = config_get_string(GetGlobalConfig(),
+ "BasicWindow", "MultiviewLayout");
+
+ if (astrcmpi(multiviewLayoutText, "horizontalbottom") == 0)
+ ui->multiviewLayout->setCurrentIndex(1);
+ else if (astrcmpi(multiviewLayoutText, "verticalleft") == 0)
+ ui->multiviewLayout->setCurrentIndex(2);
+ else if (astrcmpi(multiviewLayoutText, "verticalright") == 0)
+ ui->multiviewLayout->setCurrentIndex(3);
+ else
+ ui->multiviewLayout->setCurrentIndex(0);
+
loading = false;
}
@@ -2656,6 +2683,14 @@ void OBSBasicSettings::SaveGeneralSettings()
main->ResetUI();
}
+
+ if (WidgetChanged(ui->multiviewLayout)) {
+ config_set_string(GetGlobalConfig(), "BasicWindow",
+ "MultiviewLayout",
+ QT_TO_UTF8(GetComboData(ui->multiviewLayout)));
+
+ OBSProjector::UpdateMultiviewProjectors();
+ }
}
void OBSBasicSettings::SaveStream1Settings()
diff --git a/UI/window-projector.cpp b/UI/window-projector.cpp
index 62ef69af1..3c14815ca 100644
--- a/UI/window-projector.cpp
+++ b/UI/window-projector.cpp
@@ -8,8 +8,14 @@
#include "qt-wrappers.hpp"
#include "platform.hpp"
+#define HORIZONTAL_TOP 0
+#define HORIZONTAL_BOTTOM 1
+#define VERTICAL_LEFT 2
+#define VERTICAL_RIGHT 3
+
static QList multiviewProjectors;
static bool updatingMultiview = false;
+static int multiviewLayout = HORIZONTAL_TOP;
OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_, bool window)
: OBSQTDisplay (widget,
@@ -245,10 +251,10 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
OBSBasic *main = (OBSBasic *)obs_frontend_get_main_window();
uint32_t targetCX, targetCY;
int x, y;
- float fX, fY, halfCX, halfCY, sourceX, sourceY,
- quarterCX, quarterCY, scale, targetCXF, targetCYF,
- hiCX, hiCY, qiX, qiY, qiCX, qiCY,
- hiScaleX, hiScaleY, qiScaleX, qiScaleY;
+ float fX, fY, halfCX, halfCY, sourceX, sourceY, labelX, labelY,
+ quarterCX, quarterCY, scale, targetCXF, targetCYF,
+ hiCX, hiCY, qiX, qiY, qiCX, qiCY, hiScaleX, hiScaleY,
+ qiScaleX, qiScaleY;
uint32_t offset;
gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
@@ -317,6 +323,86 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
gs_projection_pop();
};
+ auto calcBaseSource = [&](int i)
+ {
+ switch (multiviewLayout) {
+ case VERTICAL_LEFT:
+ sourceX = halfCX;
+ sourceY = (i / 2 ) * quarterCY;
+ if (i % 2 != 0)
+ sourceX = halfCX + quarterCX;
+ break;
+ case VERTICAL_RIGHT:
+ sourceX = 0;
+ sourceY = (i / 2 ) * quarterCY;
+ if (i % 2 != 0)
+ sourceX = quarterCX;
+ break;
+ case HORIZONTAL_BOTTOM:
+ if (i < 4) {
+ sourceX = (float(i) * quarterCX);
+ sourceY = 0;
+ } else {
+ sourceX = (float(i - 4) * quarterCX);
+ sourceY = quarterCY;
+ }
+ break;
+ default: //HORIZONTAL_TOP:
+ if (i < 4) {
+ sourceX = (float(i) * quarterCX);
+ sourceY = halfCY;
+ } else {
+ sourceX = (float(i - 4) * quarterCX);
+ sourceY = halfCY + quarterCY;
+ }
+ }
+ };
+
+ auto calcPreviewProgram = [&](bool program)
+ {
+ switch (multiviewLayout) {
+ case VERTICAL_LEFT:
+ sourceX = 2.0f;
+ sourceY = halfCY + 2.0f;
+ labelX = offset;
+ labelY = halfCY * 1.8f;
+ if (program) {
+ sourceY = 2.0f;
+ labelY = halfCY * 0.8f;
+ }
+ break;
+ case VERTICAL_RIGHT:
+ sourceX = halfCX + 2.0f;
+ sourceY = halfCY + 2.0f;
+ labelX = halfCX + offset;
+ labelY = halfCY * 1.8f;
+ if (program) {
+ sourceY = 2.0f;
+ labelY = halfCY * 0.8f;
+ }
+ break;
+ case HORIZONTAL_BOTTOM:
+ sourceX = 2.0f;
+ sourceY = halfCY + 2.0f;
+ labelX = offset;
+ labelY = halfCY * 1.8f;
+ if (program) {
+ sourceX = halfCX + 2.0f;
+ labelX = halfCX + offset;
+ }
+ break;
+ default: //HORIZONTAL_TOP:
+ sourceX = 2.0f;
+ sourceY = 2.0f;
+ labelX = offset;
+ labelY = halfCY * 0.8f;
+ if (program) {
+ sourceX = halfCX + 2.0f;
+ labelX = halfCX + offset;
+ }
+ }
+ };
+
/* ----------------------------- */
/* draw sources */
@@ -334,13 +420,7 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
if (!label)
continue;
- if (i < 4) {
- sourceX = (float(i) * quarterCX);
- sourceY = halfCY;
- } else {
- sourceX = (float(i - 4) * quarterCX);
- sourceY = halfCY + quarterCY;
- }
+ calcBaseSource(i);
qiX = sourceX + 4.0f;
qiY = sourceY + 4.0f;
@@ -399,11 +479,15 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------------------------- */
/* draw preview */
+ obs_source_t *previewLabel = window->multiviewLabels[0];
+ offset = labelOffset(previewLabel, halfCX);
+ calcPreviewProgram(false);
+
gs_matrix_push();
- gs_matrix_translate3f(2.0f, 2.0f, 0.0f);
+ gs_matrix_translate3f(sourceX, sourceY, 0.0f);
gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
- setRegion(2.0f, 2.0f, hiCX, hiCY);
+ setRegion(sourceX, sourceY, hiCX, hiCY);
if (studioMode) {
obs_source_video_render(previewSrc);
@@ -418,7 +502,8 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------- */
gs_matrix_push();
- gs_matrix_scale3f(0.5f, 0.5f, 1.0f);
+ gs_matrix_translate3f(sourceX, sourceY, 0.0f);
+ gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
renderVB(solid, window->outerBox, targetCX, targetCY);
renderVB(solid, window->innerBox, targetCX, targetCY);
@@ -432,13 +517,11 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------- */
- obs_source_t *previewLabel = window->multiviewLabels[0];
- offset = labelOffset(previewLabel, halfCX);
cx = obs_source_get_width(previewLabel);
cy = obs_source_get_height(previewLabel);
gs_matrix_push();
- gs_matrix_translate3f(offset, (halfCY * 0.8f), 0.0f);
+ gs_matrix_translate3f(labelX, labelY, 0.0f);
drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
obs_source_video_render(previewLabel);
@@ -448,11 +531,15 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------------------------- */
/* draw program */
+ obs_source_t *programLabel = window->multiviewLabels[1];
+ offset = labelOffset(programLabel, halfCX);
+ calcPreviewProgram(true);
+
gs_matrix_push();
- gs_matrix_translate3f(halfCX + 2.0, 2.0f, 0.0f);
+ gs_matrix_translate3f(sourceX, sourceY, 0.0f);
gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
- setRegion(halfCX + 2.0f, 2.0f, hiCX, hiCY);
+ setRegion(sourceX, sourceY, hiCX, hiCY);
obs_render_main_texture();
resetRegion();
@@ -461,8 +548,8 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------- */
gs_matrix_push();
- gs_matrix_translate3f(halfCX, 0.0f, 0.0f);
- gs_matrix_scale3f(0.5f, 0.5f, 1.0f);
+ gs_matrix_translate3f(sourceX, sourceY, 0.0f);
+ gs_matrix_scale3f(hiScaleX, hiScaleY, 1.0f);
renderVB(solid, window->outerBox, targetCX, targetCY);
@@ -470,13 +557,11 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
/* ----------- */
- obs_source_t *programLabel = window->multiviewLabels[1];
- offset = labelOffset(programLabel, halfCX);
cx = obs_source_get_width(programLabel);
cy = obs_source_get_height(programLabel);
gs_matrix_push();
- gs_matrix_translate3f(halfCX + offset, (halfCY * 0.8f), 0.0f);
+ gs_matrix_translate3f(labelX, labelY, 0.0f);
drawBox(cx, cy + int(halfCX * 0.015f), 0xD91F1F1F);
obs_source_video_render(programLabel);
@@ -556,6 +641,129 @@ void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params)
UNUSED_PARAMETER(params);
}
+static int getSourceByPosition(int x, int y)
+{
+ struct obs_video_info ovi;
+ obs_get_video_info(&ovi);
+ float ratio = float(ovi.base_width) / float(ovi.base_height);
+
+ QWidget *rec = QApplication::activeWindow();
+ int cx = rec->width();
+ int cy = rec->height();
+ int minX = 0;
+ int minY = 0;
+ int maxX = cx;
+ int maxY = cy;
+ int halfX = cx / 2;
+ int halfY = cy / 2;
+ int pos = -1;
+
+ switch (multiviewLayout) {
+ case VERTICAL_LEFT:
+ if (float(cx) / float(cy) > ratio) {
+ int validX = cy * ratio;
+ maxX = halfX + (validX / 2);
+ } else {
+ int validY = cx / ratio;
+ minY = halfY - (validY / 2);
+ maxY = halfY + (validY / 2);
+ }
+
+ minX = halfX;
+
+ if (x < minX || x > maxX || y < minY || y > maxY)
+ break;
+
+ pos = 2 * ((y - minY) / ((maxY - minY) / 4));
+ if (x > minX + ((maxX - minX) / 2))
+ pos++;
+ break;
+ case VERTICAL_RIGHT:
+ if (float(cx) / float(cy) > ratio) {
+ int validX = cy * ratio;
+ minX = halfX - (validX / 2);
+ } else {
+ int validY = cx / ratio;
+ minY = halfY - (validY / 2);
+ maxY = halfY + (validY / 2);
+ }
+
+ maxX = halfX;
+
+ if (x < minX || x > maxX || y < minY || y > maxY)
+ break;
+
+ pos = 2 * ((y - minY) / ((maxY - minY) / 4));
+ if (x > minX + ((maxX - minX) / 2))
+ pos++;
+ break;
+ case HORIZONTAL_BOTTOM:
+ if (float(cx) / float(cy) > ratio) {
+ int validX = cy * ratio;
+ minX = halfX - (validX / 2);
+ maxX = halfX + (validX / 2);
+ } else {
+ int validY = cx / ratio;
+ minY = halfY - (validY / 2);
+ }
+
+ maxY = halfY;
+
+ if (x < minX || x > maxX || y < minY || y > maxY)
+ break;
+
+ pos = (x - minX) / ((maxX - minX) / 4);
+ if (y > minY + ((maxY - minY) / 2))
+ pos += 4;
+ break;
+ default: // HORIZONTAL_TOP
+ if (float(cx) / float(cy) > ratio) {
+ int validX = cy * ratio;
+ minX = halfX - (validX / 2);
+ maxX = halfX + (validX / 2);
+ } else {
+ int validY = cx / ratio;
+ maxY = halfY + (validY / 2);
+ }
+
+ minY = halfY;
+
+ if (x < minX || x > maxX || y < minY || y > maxY)
+ break;
+
+ pos = (x - minX) / ((maxX - minX) / 4);
+ if (y > minY + ((maxY - minY) / 2))
+ pos += 4;
+ }
+
+ return pos;
+}
+
+void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ OBSQTDisplay::mouseDoubleClickEvent(event);
+
+ if (!config_get_bool(GetGlobalConfig(), "BasicWindow",
+ "TransitionOnDoubleClick"))
+ return;
+
+ OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
+ if (!main->IsPreviewProgramMode())
+ return;
+
+ if (event->button() == Qt::LeftButton) {
+ int pos = getSourceByPosition(event->x(), event->y());
+ if (pos < 0)
+ return;
+ OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
+ if (!src)
+ return;
+
+ if (main->GetProgramSource() != src)
+ main->TransitionToScene(src);
+ }
+}
+
void OBSProjector::mousePressEvent(QMouseEvent *event)
{
OBSQTDisplay::mousePressEvent(event);
@@ -565,6 +773,19 @@ void OBSProjector::mousePressEvent(QMouseEvent *event)
popup.addAction(QTStr("Close"), this, SLOT(EscapeTriggered()));
popup.exec(QCursor::pos());
}
+
+ if (event->button() == Qt::LeftButton) {
+ int pos = getSourceByPosition(event->x(), event->y());
+ if (pos < 0)
+ return;
+ OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
+ if (!src)
+ return;
+
+ OBSBasic *main = (OBSBasic*)obs_frontend_get_main_window();
+ if (main->GetCurrentSceneSource() != src)
+ main->SetCurrentScene(src, false);
+ }
}
void OBSProjector::EscapeTriggered()
@@ -623,6 +844,18 @@ void OBSProjector::UpdateMultiview()
}
obs_frontend_source_list_free(&scenes);
+
+ const char *multiviewLayoutText = config_get_string(GetGlobalConfig(),
+ "BasicWindow", "MultiviewLayout");
+
+ if (astrcmpi(multiviewLayoutText, "horizontalbottom") == 0)
+ multiviewLayout = HORIZONTAL_BOTTOM;
+ else if (astrcmpi(multiviewLayoutText, "verticalleft") == 0)
+ multiviewLayout = VERTICAL_LEFT;
+ else if (astrcmpi(multiviewLayoutText, "verticalright") == 0)
+ multiviewLayout = VERTICAL_RIGHT;
+ else
+ multiviewLayout = HORIZONTAL_TOP;
}
void OBSProjector::UpdateMultiviewProjectors()
diff --git a/UI/window-projector.hpp b/UI/window-projector.hpp
index 526f30e5b..606ab616b 100644
--- a/UI/window-projector.hpp
+++ b/UI/window-projector.hpp
@@ -18,6 +18,7 @@ private:
static void OBSSourceRemoved(void *data, calldata_t *params);
void mousePressEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
int savedMonitor = 0;
bool isWindow = false;