Переглянути джерело

UI: Add new Multiview Layout for up to 24 scenes

The variable name changes were done with the intent to ease the
abstraction of the scene, preview and program width/height size
so its not related with the canvas size but directly related with
our concept of scenes.
Shaolin 7 роки тому
батько
коміт
c145b129f3
4 змінених файлів з 185 додано та 123 видалено
  1. 1 0
      UI/data/locale/en-US.ini
  2. 3 0
      UI/window-basic-settings.cpp
  3. 173 116
      UI/window-projector.cpp
  4. 8 7
      UI/window-projector.hpp

+ 1 - 0
UI/data/locale/en-US.ini

@@ -580,6 +580,7 @@ Basic.Settings.General.MultiviewLayout.Horizontal.Top="Horizontal, Top (8 Scenes
 Basic.Settings.General.MultiviewLayout.Horizontal.Bottom="Horizontal, Bottom (8 Scenes)"
 Basic.Settings.General.MultiviewLayout.Vertical.Left="Vertical, Left (8 Scenes)"
 Basic.Settings.General.MultiviewLayout.Vertical.Right="Vertical, Right (8 Scenes)"
+Basic.Settings.General.MultiviewLayout.Horizontal.Extended.Top="Horizontal, Top (24 Scenes)"
 
 # basic mode 'stream' settings
 Basic.Settings.Stream="Stream"

+ 3 - 0
UI/window-basic-settings.cpp

@@ -1127,6 +1127,9 @@ void OBSBasicSettings::LoadGeneralSettings()
 	ui->multiviewLayout->addItem(QTStr(
 			"Basic.Settings.General.MultiviewLayout.Vertical.Right"),
 			static_cast<int>(MultiviewLayout::VERTICAL_RIGHT_8_SCENES));
+	ui->multiviewLayout->addItem(QTStr(
+			"Basic.Settings.General.MultiviewLayout.Horizontal.Extended.Top"),
+			static_cast<int>(MultiviewLayout::HORIZONTAL_TOP_24_SCENES));
 
 	ui->multiviewLayout->setCurrentIndex(
 			config_get_int(GetGlobalConfig(), "BasicWindow",

+ 173 - 116
UI/window-projector.cpp

@@ -256,6 +256,9 @@ static inline uint32_t labelOffset(obs_source_t *label, uint32_t cx)
 
 	int n; // Number of scenes per row
 	switch (multiviewLayout) {
+	case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
+		n = 6;
+		break;
 	default:
 		n = 4;
 		break;
@@ -350,82 +353,100 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	auto calcBaseSource = [&](size_t i)
 	{
 		switch (multiviewLayout) {
+		case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
+			window->sourceX = (i % 6) * window->scenesCX;
+			window->sourceY = window->pvwprgCY +
+					(i / 6) * window->scenesCY;
+			break;
 		case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
-			window->sourceX = window->halfCX;
-			window->sourceY = (i / 2 ) * window->quarterCY;
+			window->sourceX = window->pvwprgCX;
+			window->sourceY = (i / 2 ) * window->scenesCY;
 			if (i % 2 != 0)
-				window->sourceX += window->quarterCX;
+				window->sourceX += window->scenesCX;
 			break;
 		case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
 			window->sourceX = 0;
-			window->sourceY = (i / 2 ) * window->quarterCY;
+			window->sourceY = (i / 2 ) * window->scenesCY;
 			if (i % 2 != 0)
-				window->sourceX = window->quarterCX;
+				window->sourceX = window->scenesCX;
 			break;
 		case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
 			if (i < 4) {
-				window->sourceX = (float(i) * window->quarterCX);
+				window->sourceX = (float(i) * window->scenesCX);
 				window->sourceY = 0;
 			} else {
-				window->sourceX = (float(i - 4) * window->quarterCX);
-				window->sourceY = window->quarterCY;
+				window->sourceX = (float(i - 4) *
+						window->scenesCX);
+				window->sourceY = window->scenesCY;
 			}
 			break;
 		default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES:
 			if (i < 4) {
-				window->sourceX = (float(i) * window->quarterCX);
-				window->sourceY = window->halfCY;
+				window->sourceX = (float(i) * window->scenesCX);
+				window->sourceY = window->pvwprgCY;
 			} else {
-				window->sourceX = (float(i - 4) * window->quarterCX);
-				window->sourceY = window->halfCY +
-						window->quarterCY;
+				window->sourceX = (float(i - 4) *
+						window->scenesCX);
+				window->sourceY = window->pvwprgCY +
+						window->scenesCY;
 			}
 		}
-		window->qiX = window->sourceX + window->thickness;
-		window->qiY = window->sourceY + window->thickness;
+		window->siX = window->sourceX + window->thickness;
+		window->siY = window->sourceY + window->thickness;
 	};
 
 	auto calcPreviewProgram = [&](bool program)
 	{
 		switch (multiviewLayout) {
+		case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
+			window->sourceX = window->thickness +
+					window->pvwprgCX / 2;
+			window->sourceY = window->thickness;
+			window->labelX = window->offset + window->pvwprgCX / 2;
+			window->labelY = window->pvwprgCY * 0.85f;
+			if (program) {
+				window->sourceX += window->pvwprgCX;
+				window->labelX += window->pvwprgCX;
+			}
+			break;
 		case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
 			window->sourceX = window->thickness;
-			window->sourceY = window->halfCY + window->thickness;
+			window->sourceY = window->pvwprgCY + window->thickness;
 			window->labelX = window->offset;
-			window->labelY = window->halfCY * 1.85f;
+			window->labelY = window->pvwprgCY * 1.85f;
 			if (program) {
 				window->sourceY = window->thickness;
-				window->labelY = window->halfCY * 0.85f;
+				window->labelY = window->pvwprgCY * 0.85f;
 			}
 			break;
 		case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
-			window->sourceX = window->halfCX + window->thickness;
-			window->sourceY = window->halfCY + window->thickness;
-			window->labelX = window->halfCX + window->offset;
-			window->labelY = window->halfCY * 1.85f;
+			window->sourceX = window->pvwprgCX + window->thickness;
+			window->sourceY = window->pvwprgCY + window->thickness;
+			window->labelX = window->pvwprgCX + window->offset;
+			window->labelY = window->pvwprgCY * 1.85f;
 			if (program) {
 				window->sourceY = window->thickness;
-				window->labelY = window->halfCY * 0.85f;
+				window->labelY = window->pvwprgCY * 0.85f;
 			}
 			break;
 		case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
 			window->sourceX = window->thickness;
-			window->sourceY = window->halfCY + window->thickness;
+			window->sourceY = window->pvwprgCY + window->thickness;
 			window->labelX = window->offset;
-			window->labelY = window->halfCY * 1.85f;
+			window->labelY = window->pvwprgCY * 1.85f;
 			if (program) {
-				window->sourceX += window->halfCX;
-				window->labelX += window->halfCX;
+				window->sourceX += window->pvwprgCX;
+				window->labelX += window->pvwprgCX;
 			}
 			break;
 		default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES:
 			window->sourceX = window->thickness;
 			window->sourceY = window->thickness;
 			window->labelX = window->offset;
-			window->labelY = window->halfCY * 0.85f;
+			window->labelY = window->pvwprgCY * 0.85f;
 			if (program) {
-				window->sourceX += window->halfCX;
-				window->labelX += window->halfCX;
+				window->sourceX += window->pvwprgCX;
+				window->labelX += window->pvwprgCX;
 			}
 		}
 	};
@@ -449,23 +470,23 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	/* ----------------------------- */
 	/* draw sources                  */
 
-	for (size_t i = 0; i < numSrcs; i++) {
-		OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]);
-
+	for (size_t i = 0; i < maxSrcs; i++) {
 		// Handle all the offsets
 		calcBaseSource(i);
 
-		if (!src) {
+		if (i >= numSrcs) {
 			// Just paint the background and continue
 			paintAreaWithColor(window->sourceX, window->sourceY,
-					window->quarterCX, window->quarterCY,
+					window->scenesCX, window->scenesCY,
 					outerColor);
-			paintAreaWithColor(window->qiX, window->qiY,
-					window->qiCX, window->qiCY,
+			paintAreaWithColor(window->siX, window->siY,
+					window->siCX, window->siCY,
 					backgroundColor);
 			continue;
 		}
 
+		OBSSource src = OBSGetStrongRef(window->multiviewScenes[i]);
+
 		// We have a source. Now chose the proper highlight color
 		uint32_t colorVal = outerColor;
 		if (src == programSrc)
@@ -475,17 +496,17 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 
 		// Paint the background
 		paintAreaWithColor(window->sourceX, window->sourceY,
-				window->quarterCX, window->quarterCY, colorVal);
-		paintAreaWithColor(window->qiX, window->qiY, window->qiCX,
-				window->qiCY, backgroundColor);
+				window->scenesCX, window->scenesCY, colorVal);
+		paintAreaWithColor(window->siX, window->siY, window->siCX,
+				window->siCY, backgroundColor);
 
 		/* ----------- */
 
 		// Render the source
 		gs_matrix_push();
-		gs_matrix_translate3f(window->qiX, window->qiY, 0.0f);
-		gs_matrix_scale3f(window->qiScaleX, window->qiScaleY, 1.0f);
-		setRegion(window->qiX, window->qiY, window->qiCX, window->qiCY);
+		gs_matrix_translate3f(window->siX, window->siY, 0.0f);
+		gs_matrix_scale3f(window->siScaleX, window->siScaleY, 1.0f);
+		setRegion(window->siX, window->siY, window->siCX, window->siCY);
 		obs_source_video_render(src);
 		endRegion();
 		gs_matrix_pop();
@@ -500,13 +521,13 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 		if (!label)
 			continue;
 
-		window->offset = labelOffset(label, window->quarterCX);
+		window->offset = labelOffset(label, window->scenesCX);
 
 		gs_matrix_push();
 		gs_matrix_translate3f(window->sourceX + window->offset,
-				(window->quarterCY * 0.85f) + window->sourceY,
+				(window->scenesCY * 0.85f) + window->sourceY,
 				0.0f);
-		gs_matrix_scale3f(window->hiScaleX, window->hiScaleY, 1.0f);
+		gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
 		drawBox(obs_source_get_width(label),
 				obs_source_get_height(label) +
 				int(window->sourceY * 0.015f), labelColor);
@@ -518,18 +539,19 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	/* draw preview                  */
 
 	obs_source_t *previewLabel = window->multiviewLabels[0];
-	window->offset = labelOffset(previewLabel, window->halfCX);
+	window->offset = labelOffset(previewLabel, window->pvwprgCX);
 	calcPreviewProgram(false);
 
 	// Paint the background
-	paintAreaWithColor(window->sourceX, window->sourceY, window->hiCX,
-			window->hiCY, backgroundColor);
+	paintAreaWithColor(window->sourceX, window->sourceY, window->ppiCX,
+			window->ppiCY, backgroundColor);
 
 	// Scale and Draw the preview
 	gs_matrix_push();
 	gs_matrix_translate3f(window->sourceX, window->sourceY, 0.0f);
-	gs_matrix_scale3f(window->hiScaleX, window->hiScaleY, 1.0f);
-	setRegion(window->sourceX, window->sourceY, window->hiCX, window->hiCY);
+	gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
+	setRegion(window->sourceX, window->sourceY, window->ppiCX,
+			window->ppiCY);
 	if (studioMode)
 		obs_source_video_render(previewSrc);
 	else
@@ -554,10 +576,10 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	if (drawLabel) {
 		gs_matrix_push();
 		gs_matrix_translate3f(window->labelX, window->labelY, 0.0f);
-		gs_matrix_scale3f(window->hiScaleX, window->hiScaleY, 1.0f);
+		gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
 		drawBox(obs_source_get_width(previewLabel),
 				obs_source_get_height(previewLabel) +
-				int(window->halfCX * 0.015f), labelColor);
+				int(window->pvwprgCX * 0.015f), labelColor);
 		obs_source_video_render(previewLabel);
 		gs_matrix_pop();
 	}
@@ -566,14 +588,15 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	/* draw program                  */
 
 	obs_source_t *programLabel = window->multiviewLabels[1];
-	window->offset = labelOffset(programLabel, window->halfCX);
+	window->offset = labelOffset(programLabel, window->pvwprgCX);
 	calcPreviewProgram(true);
 
 	// Scale and Draw the program
 	gs_matrix_push();
 	gs_matrix_translate3f(window->sourceX, window->sourceY, 0.0f);
-	gs_matrix_scale3f(window->hiScaleX, window->hiScaleY, 1.0f);
-	setRegion(window->sourceX, window->sourceY, window->hiCX, window->hiCY);
+	gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
+	setRegion(window->sourceX, window->sourceY, window->ppiCX,
+			window->ppiCY);
 	obs_render_main_texture();
 	endRegion();
 	gs_matrix_pop();
@@ -584,14 +607,27 @@ void OBSProjector::OBSRenderMultiview(void *data, uint32_t cx, uint32_t cy)
 	if (drawLabel) {
 		gs_matrix_push();
 		gs_matrix_translate3f(window->labelX, window->labelY, 0.0f);
-		gs_matrix_scale3f(window->hiScaleX, window->hiScaleY, 1.0f);
+		gs_matrix_scale3f(window->ppiScaleX, window->ppiScaleY, 1.0f);
 		drawBox(obs_source_get_width(programLabel),
 				obs_source_get_height(programLabel) +
-				int(window->halfCX * 0.015f), labelColor);
+				int(window->pvwprgCX * 0.015f), labelColor);
 		obs_source_video_render(programLabel);
 		gs_matrix_pop();
 	}
 
+	// Region for future usage with aditional info.
+	if (multiviewLayout == MultiviewLayout::HORIZONTAL_TOP_24_SCENES) {
+		// Just paint the background for now
+		paintAreaWithColor(window->thickness, window->thickness,
+				window->siCX, window->siCY * 2 +
+				window->thicknessx2, backgroundColor);
+		paintAreaWithColor(window->thickness + 2.5 * (
+				window->thicknessx2 + window->ppiCX),
+				window->thickness, window->siCX,
+				window->siCY * 2 + window->thicknessx2,
+				backgroundColor);
+	}
+
 	endRegion();
 }
 
@@ -666,22 +702,39 @@ static int getSourceByPosition(int x, int y, float ratio)
 	int     minY  = 0;
 	int     maxX  = cx;
 	int     maxY  = cy;
-	int     pvwpgmX = cx / 2;
-	int     pvwpgmY = cy / 2;
 	int     pos   = -1;
 
 	switch (multiviewLayout) {
+	case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
+		if (float(cx) / float(cy) > ratio) {
+			int validX = cy * ratio;
+			minX = (cx / 2) - (validX / 2);
+			maxX = (cx / 2) + (validX / 2);
+			minY = cy / 3;
+		} else {
+			int validY = cx / ratio;
+			maxY = (cy / 2) + (validY / 2);
+			minY = (cy / 2) - (validY / 6);
+		}
+
+		if (x < minX || x > maxX || y < minY || y > maxY)
+			break;
+
+		pos = (x - minX) / ((maxX - minX) / 6);
+		pos += ((y - minY) / ((maxY - minY) / 4)) * 6;
+
+		break;
 	case MultiviewLayout::VERTICAL_LEFT_8_SCENES:
 		if (float(cx) / float(cy) > ratio) {
 			int validX = cy * ratio;
-			maxX = pvwpgmX + (validX / 2);
+			maxX = (cx / 2) + (validX / 2);
 		} else {
 			int validY = cx / ratio;
-			minY = pvwpgmY - (validY / 2);
-			maxY = pvwpgmY + (validY / 2);
+			minY = (cy / 2) - (validY / 2);
+			maxY = (cy / 2) + (validY / 2);
 		}
 
-		minX = pvwpgmX;
+		minX = cx / 2;
 
 		if (x < minX || x > maxX || y < minY || y > maxY)
 			break;
@@ -693,14 +746,14 @@ static int getSourceByPosition(int x, int y, float ratio)
 	case MultiviewLayout::VERTICAL_RIGHT_8_SCENES:
 		if (float(cx) / float(cy) > ratio) {
 			int validX = cy * ratio;
-			minX = pvwpgmX - (validX / 2);
+			minX = (cx / 2) - (validX / 2);
 		} else {
 			int validY = cx / ratio;
-			minY = pvwpgmY - (validY / 2);
-			maxY = pvwpgmY + (validY / 2);
+			minY = (cy / 2) - (validY / 2);
+			maxY = (cy / 2) + (validY / 2);
 		}
 
-		maxX = pvwpgmX;
+		maxX = (cx / 2);
 
 		if (x < minX || x > maxX || y < minY || y > maxY)
 			break;
@@ -712,14 +765,14 @@ static int getSourceByPosition(int x, int y, float ratio)
 	case MultiviewLayout::HORIZONTAL_BOTTOM_8_SCENES:
 		if (float(cx) / float(cy) > ratio) {
 			int validX = cy * ratio;
-			minX = pvwpgmX - (validX / 2);
-			maxX = pvwpgmX + (validX / 2);
+			minX = (cx / 2) - (validX / 2);
+			maxX = (cx / 2) + (validX / 2);
 		} else {
 			int validY = cx / ratio;
-			minY = pvwpgmY - (validY / 2);
+			minY = (cy / 2) - (validY / 2);
 		}
 
-		maxY = pvwpgmY;
+		maxY = (cy / 2);
 
 		if (x < minX || x > maxX || y < minY || y > maxY)
 			break;
@@ -731,14 +784,14 @@ static int getSourceByPosition(int x, int y, float ratio)
 	default: // MultiviewLayout::HORIZONTAL_TOP_8_SCENES
 		if (float(cx) / float(cy) > ratio) {
 			int validX = cy * ratio;
-			minX = pvwpgmX - (validX / 2);
-			maxX = pvwpgmX + (validX / 2);
+			minX = (cx / 2) - (validX / 2);
+			maxX = (cx / 2) + (validX / 2);
 		} else {
 			int validY = cx / ratio;
-			maxY = pvwpgmY + (validY / 2);
+			maxY = (cy / 2) + (validY / 2);
 		}
 
-		minY = pvwpgmY;
+		minY = (cy / 2);
 
 		if (x < minX || x > maxX || y < minY || y > maxY)
 			break;
@@ -767,7 +820,7 @@ void OBSProjector::mouseDoubleClickEvent(QMouseEvent *event)
 
 	if (event->button() == Qt::LeftButton) {
 		int pos = getSourceByPosition(event->x(), event->y(), ratio);
-		if (pos < 0)
+		if (pos < 0 || pos >= (int)numSrcs)
 			return;
 		OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
 		if (!src)
@@ -793,7 +846,7 @@ void OBSProjector::mousePressEvent(QMouseEvent *event)
 
 	if (event->button() == Qt::LeftButton) {
 		int pos = getSourceByPosition(event->x(), event->y(), ratio);
-		if (pos < 0)
+		if (pos < 0 || pos >= (int)numSrcs)
 			return;
 		OBSSource src = OBSGetStrongRef(multiviewScenes[pos]);
 		if (!src)
@@ -812,40 +865,25 @@ void OBSProjector::EscapeTriggered()
 
 void OBSProjector::UpdateMultiview()
 {
-	for (OBSWeakSource &val : multiviewScenes)
-		val = nullptr;
-	for (OBSSource &val : multiviewLabels)
-		val = nullptr;
+	multiviewScenes.clear();
+	multiviewLabels.clear();
 
 	struct obs_video_info ovi;
 	obs_get_video_info(&ovi);
 
 	uint32_t w  = ovi.base_width;
 	uint32_t h  = ovi.base_height;
-	fw       = float(w);
-	fh       = float(h);
-	ratio    = fw / fh;
-	halfCX   = fw / 2;
-	halfCY   = fh / 2;
-	hiCX     = halfCX - thicknessx2;
-	hiCY     = halfCY - thicknessx2;
-	hiScaleX = (halfCX - thicknessx2) / fw;
-	hiScaleY = (halfCY - thicknessx2) / fh;
-
-	quarterCX = halfCX / 2;
-	quarterCY = halfCY / 2;
-	qiCX      = quarterCX - thicknessx2;
-	qiCY      = quarterCY - thicknessx2;
-	qiScaleX  = (quarterCX - thicknessx2) / fw;
-	qiScaleY  = (quarterCY - thicknessx2) / fh;
+	fw        = float(w);
+	fh        = float(h);
+	ratio     = fw / fh;
 
 	struct obs_frontend_source_list scenes = {};
 	obs_frontend_get_scenes(&scenes);
 
-	size_t curIdx = 0;
-
-	multiviewLabels[0] = CreateLabel(Str("StudioMode.Preview"), h / 2);
-	multiviewLabels[1] = CreateLabel(Str("StudioMode.Program"), h / 2);
+	multiviewLabels.emplace_back(CreateLabel(Str("StudioMode.Preview"),
+			h / 2));
+	multiviewLabels.emplace_back(CreateLabel(Str("StudioMode.Program"),
+			h / 2));
 
 	multiviewLayout = static_cast<MultiviewLayout>(config_get_int(
 			GetGlobalConfig(), "BasicWindow", "MultiviewLayout"));
@@ -863,12 +901,34 @@ void OBSProjector::UpdateMultiview()
 			"BasicWindow", "TransitionOnDoubleClick");
 
 	switch(multiviewLayout) {
-		default:
-			maxSrcs = 8;
+	case MultiviewLayout::HORIZONTAL_TOP_24_SCENES:
+		pvwprgCX   = fw / 3;
+		pvwprgCY   = fh / 3;
+
+		maxSrcs = 24;
+		break;
+	default:
+		pvwprgCX   = fw / 2;
+		pvwprgCY   = fh / 2;
+
+		maxSrcs = 8;
 	}
 
-	for (size_t i = 0; i < scenes.sources.num && curIdx < maxSrcs; i++) {
-		obs_source_t *src = scenes.sources.array[i];
+	ppiCX     = pvwprgCX - thicknessx2;
+	ppiCY     = pvwprgCY - thicknessx2;
+	ppiScaleX = (pvwprgCX - thicknessx2) / fw;
+	ppiScaleY = (pvwprgCY - thicknessx2) / fh;
+
+	scenesCX = pvwprgCX / 2;
+	scenesCY = pvwprgCY / 2;
+	siCX      = scenesCX - thicknessx2;
+	siCY      = scenesCY - thicknessx2;
+	siScaleX  = (scenesCX - thicknessx2) / fw;
+	siScaleY  = (scenesCY - thicknessx2) / fh;
+
+	numSrcs = 0;
+	while (numSrcs < scenes.sources.num && numSrcs < maxSrcs) {
+		obs_source_t *src = scenes.sources.array[numSrcs];
 		OBSData data = obs_source_get_private_settings(src);
 		obs_data_release(data);
 
@@ -876,19 +936,16 @@ void OBSProjector::UpdateMultiview()
 		if (!obs_data_get_bool(data, "show_in_multiview"))
 			continue;
 
-		multiviewScenes[curIdx] = OBSGetWeakRef(src);
-		obs_source_inc_showing(src);
-
-		std::string name;
-		name += std::to_string(curIdx + 1);
-		name += " - ";
-		name += obs_source_get_name(src);
+		// We have a displayable source.
+		numSrcs++;
 
-		multiviewLabels[curIdx + 2] = CreateLabel(name.c_str(), h / 3);
+		multiviewScenes.emplace_back(OBSGetWeakRef(src));
+		obs_source_inc_showing(src);
 
-		curIdx++;
+		std::string name = std::to_string(numSrcs) + " - " +
+				obs_source_get_name(src);
+		multiviewLabels.emplace_back(CreateLabel(name.c_str(), h / 3));
 	}
-	numSrcs = curIdx;
 
 	obs_frontend_source_list_free(&scenes);
 }

+ 8 - 7
UI/window-projector.hpp

@@ -17,7 +17,8 @@ enum class MultiviewLayout : uint8_t {
 	HORIZONTAL_TOP_8_SCENES = 0,
 	HORIZONTAL_BOTTOM_8_SCENES = 1,
 	VERTICAL_LEFT_8_SCENES = 2,
-	VERTICAL_RIGHT_8_SCENES = 3
+	VERTICAL_RIGHT_8_SCENES = 3,
+	HORIZONTAL_TOP_24_SCENES = 4
 };
 
 class OBSProjector : public OBSQTDisplay {
@@ -38,8 +39,8 @@ private:
 	bool isWindow;
 	QString projectorTitle;
 	ProjectorType type = ProjectorType::Source;
-	OBSWeakSource multiviewScenes[8];
-	OBSSource     multiviewLabels[10];
+	std::vector<OBSWeakSource> multiviewScenes;
+	std::vector<OBSSource> multiviewLabels;
 	gs_vertbuffer_t *actionSafeMargin      = nullptr;
 	gs_vertbuffer_t *graphicsSafeMargin    = nullptr;
 	gs_vertbuffer_t *fourByThreeSafeMargin = nullptr;
@@ -50,10 +51,10 @@ private:
 	gs_eparam_t *color = nullptr;
 	// Multiview position helpers
 	float thickness = 4;
-	float offset, thicknessx2 = thickness * 2, halfCX,
-		halfCY, sourceX, sourceY, labelX, labelY, quarterCX, quarterCY,
-		hiCX, hiCY, qiX, qiY, qiCX, qiCY, hiScaleX, hiScaleY, qiScaleX,
-		qiScaleY, fw, fh, ratio;
+	float offset, thicknessx2 = thickness * 2, pvwprgCX,
+		pvwprgCY, sourceX, sourceY, labelX, labelY, scenesCX, scenesCY,
+		ppiCX, ppiCY, siX, siY, siCX, siCY, ppiScaleX, ppiScaleY,
+		siScaleX, siScaleY, fw, fh, ratio;
 
 	float lineLength                = 0.1f;
 	// Rec. ITU-R BT.1848-1 / EBU R 95