Browse Source

UI: Show spacing helpers in preview

This shows distance between sides of preview and edges of sources.
This will allow users to more easily align sources.

Co-authored-by: Palakis <[email protected]>
Clayton Groeneveld 4 years ago
parent
commit
2d6a9c9cc1

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

@@ -821,6 +821,7 @@ Basic.Settings.General.ScreenSnapping="Snap Sources to edge of screen"
 Basic.Settings.General.CenterSnapping="Snap Sources to horizontal and vertical center"
 Basic.Settings.General.SourceSnapping="Snap Sources to other sources"
 Basic.Settings.General.SnapDistance="Snap Sensitivity"
+Basic.Settings.General.SpacingHelpers="Show pixel alignment guides"
 Basic.Settings.General.RecordWhenStreaming="Automatically record when streaming"
 Basic.Settings.General.KeepRecordingWhenStreamStops="Keep recording when stream stops"
 Basic.Settings.General.ReplayBufferWhileStreaming="Automatically start replay buffer when streaming"

+ 7 - 0
UI/forms/OBSBasicSettings.ui

@@ -628,6 +628,13 @@
                      </property>
                     </widget>
                    </item>
+                   <item row="4" column="1">
+                    <widget class="QCheckBox" name="previewSpacingHelpers">
+                     <property name="text">
+                      <string>Basic.Settings.General.SpacingHelpers</string>
+                     </property>
+                    </widget>
+                   </item>
                    <item row="0" column="1">
                     <widget class="QCheckBox" name="overflowHide">
                      <property name="text">

+ 2 - 0
UI/obs-app.cpp

@@ -451,6 +451,8 @@ bool OBSApp::InitGlobalConfigDefaults()
 				false);
 	config_set_default_double(globalConfig, "BasicWindow", "SnapDistance",
 				  10.0);
+	config_set_default_bool(globalConfig, "BasicWindow",
+				"SpacingHelpersEnabled", true);
 	config_set_default_bool(globalConfig, "BasicWindow",
 				"RecordWhenStreaming", false);
 	config_set_default_bool(globalConfig, "BasicWindow",

+ 10 - 0
UI/window-basic-main.cpp

@@ -469,6 +469,7 @@ OBSBasic::OBSBasic(QWidget *parent)
 		&OBSBasic::BroadcastButtonClicked);
 
 	UpdatePreviewSafeAreas();
+	UpdatePreviewSpacingHelpers();
 }
 
 static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
@@ -4246,6 +4247,9 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy)
 		RenderSafeAreas(window->rightLine, targetCX, targetCY);
 	}
 
+	if (window->drawSpacingHelpers)
+		window->ui->preview->DrawSpacingHelpers();
+
 	/* --------------------------------------- */
 
 	gs_projection_pop();
@@ -10177,3 +10181,9 @@ QColor OBSBasic::GetHoverColor() const
 		return QColor::fromRgb(0, 127, 255);
 	}
 }
+
+void OBSBasic::UpdatePreviewSpacingHelpers()
+{
+	drawSpacingHelpers = config_get_bool(
+		App()->GlobalConfig(), "BasicWindow", "SpacingHelpersEnabled");
+}

+ 3 - 0
UI/window-basic-main.hpp

@@ -632,6 +632,9 @@ private:
 	QColor GetCropColor() const;
 	QColor GetHoverColor() const;
 
+	void UpdatePreviewSpacingHelpers();
+	bool drawSpacingHelpers = true;
+
 public slots:
 	void DeferSaveBegin();
 	void DeferSaveEnd();

+ 296 - 0
UI/window-basic-preview.cpp

@@ -12,6 +12,7 @@
 
 #define HANDLE_RADIUS 4.0f
 #define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f)
+#define HELPER_ROT_BREAKPONT 45.0f
 
 /* TODO: make C++ math classes and clean up code here later */
 
@@ -2286,3 +2287,298 @@ OBSBasicPreview *OBSBasicPreview::Get()
 {
 	return OBSBasic::Get()->ui->preview;
 }
+
+static obs_source_t *CreateLabel()
+{
+	OBSDataAutoRelease settings = obs_data_create();
+	OBSDataAutoRelease font = obs_data_create();
+
+#if defined(_WIN32)
+	obs_data_set_string(font, "face", "Arial");
+#elif defined(__APPLE__)
+	obs_data_set_string(font, "face", "Helvetica");
+#else
+	obs_data_set_string(font, "face", "Monospace");
+#endif
+	obs_data_set_int(font, "flags", 1); // Bold text
+	obs_data_set_int(font, "size", 16);
+
+	obs_data_set_obj(settings, "font", font);
+	obs_data_set_bool(settings, "outline", true);
+
+#ifdef _WIN32
+	obs_data_set_int(settings, "outline_color", 0x000000);
+	obs_data_set_int(settings, "outline_size", 3);
+	const char *text_source_id = "text_gdiplus";
+#else
+	const char *text_source_id = "text_ft2_source";
+#endif
+
+	OBSSource txtSource =
+		obs_source_create_private(text_source_id, NULL, settings);
+
+	return txtSource;
+}
+
+static void SetLabelText(int sourceIndex, int px)
+{
+	OBSBasicPreview *prev = OBSBasicPreview::Get();
+
+	if (px == prev->spacerPx[sourceIndex])
+		return;
+
+	std::string text = std::to_string(px) + " px";
+
+	obs_source_t *source = prev->spacerLabel[sourceIndex];
+
+	OBSDataAutoRelease settings = obs_source_get_settings(source);
+	obs_data_set_string(settings, "text", text.c_str());
+	obs_source_update(source, settings);
+
+	prev->spacerPx[sourceIndex] = px;
+}
+
+static void DrawLabel(OBSSource source, vec3 &pos, vec3 &viewport)
+{
+	if (!source)
+		return;
+
+	vec3_mul(&pos, &pos, &viewport);
+
+	gs_matrix_push();
+	gs_matrix_identity();
+	gs_matrix_translate(&pos);
+	obs_source_video_render(source);
+	gs_matrix_pop();
+}
+
+static void DrawSpacingLine(vec3 &start, vec3 &end, vec3 &viewport)
+{
+	matrix4 transform;
+	matrix4_identity(&transform);
+	transform.x.x = viewport.x;
+	transform.y.y = viewport.y;
+
+	gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
+	gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
+
+	vec4 color;
+	vec4_set(&color, 1.0f, 0.0f, 0.0f, 1.0f);
+	gs_effect_set_vec4(gs_effect_get_param_by_name(solid, "color"), &color);
+
+	gs_technique_begin(tech);
+	gs_technique_begin_pass(tech, 0);
+
+	gs_matrix_push();
+	gs_matrix_mul(&transform);
+
+	vec2 scale;
+	vec2_set(&scale, viewport.x, viewport.y);
+
+	DrawLine(start.x, start.y, end.x, end.y, HANDLE_RADIUS / 2, scale);
+
+	gs_matrix_pop();
+
+	gs_load_vertexbuffer(nullptr);
+
+	gs_technique_end_pass(tech);
+	gs_technique_end(tech);
+}
+
+static void RenderSpacingHelper(int sourceIndex, vec3 &start, vec3 &end,
+				vec3 &viewport)
+{
+	bool horizontal = (sourceIndex == 2 || sourceIndex == 3);
+
+	// If outside of preview, don't render
+	if (!((horizontal && (end.x >= start.x)) ||
+	      (!horizontal && (end.y >= start.y))))
+		return;
+
+	float length = vec3_dist(&start, &end);
+
+	obs_video_info ovi;
+	obs_get_video_info(&ovi);
+
+	float px;
+
+	if (horizontal) {
+		px = length * ovi.base_width;
+	} else {
+		px = length * ovi.base_height;
+	}
+
+	if (px <= 0.0f)
+		return;
+
+	OBSBasicPreview *prev = OBSBasicPreview::Get();
+	obs_source_t *source = prev->spacerLabel[sourceIndex];
+	vec3 labelSize, labelPos;
+	vec3_set(&labelSize, obs_source_get_width(source),
+		 obs_source_get_height(source), 1.0f);
+
+	vec3_div(&labelSize, &labelSize, &viewport);
+
+	vec3 labelMargin;
+	vec3_set(&labelMargin, SPACER_LABEL_MARGIN, SPACER_LABEL_MARGIN, 1.0f);
+	vec3_div(&labelMargin, &labelMargin, &viewport);
+
+	vec3_set(&labelPos, end.x, end.y, end.z);
+	if (horizontal) {
+		labelPos.x -= (end.x - start.x) / 2;
+		labelPos.x -= labelSize.x / 2;
+		labelPos.y -= labelMargin.y + (labelSize.y / 2) +
+			      (HANDLE_RADIUS / viewport.y);
+	} else {
+		labelPos.y -= (end.y - start.y) / 2;
+		labelPos.y -= labelSize.y / 2;
+		labelPos.x += labelMargin.x;
+	}
+
+	DrawSpacingLine(start, end, viewport);
+	SetLabelText(sourceIndex, (int)px);
+	DrawLabel(source, labelPos, viewport);
+}
+
+void OBSBasicPreview::DrawSpacingHelpers()
+{
+	if (locked)
+		return;
+
+	OBSBasic *main = OBSBasic::Get();
+
+	if (main->ui->sources->selectionModel()->selectedIndexes().count() > 1)
+		return;
+
+	OBSSceneItem item = main->GetCurrentSceneItem();
+	if (!item)
+		return;
+
+	if (obs_sceneitem_locked(item))
+		return;
+
+	vec2 itemSize = GetItemSize(item);
+	if (itemSize.x == 0.0f || itemSize.y == 0.0f)
+		return;
+
+	matrix4 boxTransform;
+	obs_sceneitem_get_box_transform(item, &boxTransform);
+
+	obs_transform_info oti;
+	obs_sceneitem_get_info(item, &oti);
+
+	obs_video_info ovi;
+	obs_get_video_info(&ovi);
+
+	vec3 size;
+	vec3_set(&size, ovi.base_width, ovi.base_height, 1.0f);
+
+	// Init box transform side locations
+	vec3 left, right, top, bottom;
+
+	vec3_set(&left, 0.0f, 0.5f, 1.0f);
+	vec3_set(&right, 1.0f, 0.5f, 1.0f);
+	vec3_set(&top, 0.5f, 0.0f, 1.0f);
+	vec3_set(&bottom, 0.5f, 1.0f, 1.0f);
+
+	// Decide which side to use with box transform, based on rotation
+	// Seems hacky, probably a better way to do it
+	float rot = oti.rot;
+
+	if (rot >= HELPER_ROT_BREAKPONT) {
+		for (float i = HELPER_ROT_BREAKPONT; i <= 360.0f; i += 90.0f) {
+			if (rot < i)
+				break;
+
+			vec3 l = left;
+			vec3 r = right;
+			vec3 t = top;
+			vec3 b = bottom;
+
+			vec3_copy(&top, &l);
+			vec3_copy(&right, &t);
+			vec3_copy(&bottom, &r);
+			vec3_copy(&left, &b);
+		}
+	} else if (rot <= -HELPER_ROT_BREAKPONT) {
+		for (float i = -HELPER_ROT_BREAKPONT; i >= -360.0f;
+		     i -= 90.0f) {
+			if (rot > i)
+				break;
+
+			vec3 l = left;
+			vec3 r = right;
+			vec3 t = top;
+			vec3 b = bottom;
+
+			vec3_copy(&top, &r);
+			vec3_copy(&right, &b);
+			vec3_copy(&bottom, &l);
+			vec3_copy(&left, &t);
+		}
+	}
+
+	// Switch top/bottom or right/left if scale is negative
+	if (oti.scale.x < 0.0f) {
+		vec3 l = left;
+		vec3 r = right;
+
+		vec3_copy(&left, &r);
+		vec3_copy(&right, &l);
+	}
+
+	if (oti.scale.y < 0.0f) {
+		vec3 t = top;
+		vec3 b = bottom;
+
+		vec3_copy(&top, &b);
+		vec3_copy(&bottom, &t);
+	}
+
+	// Get sides of box transform
+	left = GetTransformedPos(left.x, left.y, boxTransform);
+	right = GetTransformedPos(right.x, right.y, boxTransform);
+	top = GetTransformedPos(top.x, top.y, boxTransform);
+	bottom = GetTransformedPos(bottom.x, bottom.y, boxTransform);
+
+	bottom.y = size.y - bottom.y;
+	right.x = size.x - right.x;
+
+	// Init viewport
+	vec3 viewport;
+	vec3_set(&viewport, main->previewCX, main->previewCY, 1.0f);
+
+	vec3_div(&left, &left, &viewport);
+	vec3_div(&right, &right, &viewport);
+	vec3_div(&top, &top, &viewport);
+	vec3_div(&bottom, &bottom, &viewport);
+
+	vec3_mulf(&left, &left, main->previewScale);
+	vec3_mulf(&right, &right, main->previewScale);
+	vec3_mulf(&top, &top, main->previewScale);
+	vec3_mulf(&bottom, &bottom, main->previewScale);
+
+	// Draw spacer lines and labels
+	vec3 start, end;
+
+	for (int i = 0; i < 4; i++) {
+		if (!spacerLabel[i])
+			spacerLabel[i] = CreateLabel();
+	}
+
+	vec3_set(&start, top.x, 0.0f, 1.0f);
+	vec3_set(&end, top.x, top.y, 1.0f);
+	RenderSpacingHelper(0, start, end, viewport);
+
+	vec3_set(&start, bottom.x, 1.0f - bottom.y, 1.0f);
+	vec3_set(&end, bottom.x, 1.0f, 1.0f);
+	RenderSpacingHelper(1, start, end, viewport);
+
+	vec3_set(&start, 0.0f, left.y, 1.0f);
+	vec3_set(&end, left.x, left.y, 1.0f);
+	RenderSpacingHelper(2, start, end, viewport);
+
+	vec3_set(&start, 1.0f - right.x, right.y, 1.0f);
+	vec3_set(&end, 1.0f, right.y, 1.0f);
+	RenderSpacingHelper(3, start, end, viewport);
+}

+ 7 - 0
UI/window-basic-preview.hpp

@@ -20,6 +20,8 @@ class QMouseEvent;
 
 #define ZOOM_SENSITIVITY 1.125f
 
+#define SPACER_LABEL_MARGIN 6.0f
+
 enum class ItemHandle : uint32_t {
 	None = 0,
 	TopLeft = ITEM_TOP | ITEM_LEFT,
@@ -166,4 +168,9 @@ public:
 	 * byte boundary. */
 	static inline void *operator new(size_t size) { return bmalloc(size); }
 	static inline void operator delete(void *ptr) { bfree(ptr); }
+
+	OBSSourceAutoRelease spacerLabel[4];
+	int spacerPx[4] = {0};
+
+	void DrawSpacingHelpers();
 };

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

@@ -407,6 +407,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 	HookWidget(ui->overflowSelectionHide,CHECK_CHANGED,  GENERAL_CHANGED);
 	HookWidget(ui->previewSafeAreas,     CHECK_CHANGED,  GENERAL_CHANGED);
 	HookWidget(ui->automaticSearch,      CHECK_CHANGED,  GENERAL_CHANGED);
+	HookWidget(ui->previewSpacingHelpers,CHECK_CHANGED,  GENERAL_CHANGED);
 	HookWidget(ui->doubleClickSwitch,    CHECK_CHANGED,  GENERAL_CHANGED);
 	HookWidget(ui->studioPortraitLayout, CHECK_CHANGED,  GENERAL_CHANGED);
 	HookWidget(ui->prevProgLabelToggle,  CHECK_CHANGED,  GENERAL_CHANGED);
@@ -1310,6 +1311,10 @@ void OBSBasicSettings::LoadGeneralSettings()
 		GetGlobalConfig(), "BasicWindow", "WarnBeforeStartingStream");
 	ui->warnBeforeStreamStart->setChecked(warnBeforeStreamStart);
 
+	bool spacingHelpersEnabled = config_get_bool(
+		GetGlobalConfig(), "BasicWindow", "SpacingHelpersEnabled");
+	ui->previewSpacingHelpers->setChecked(spacingHelpersEnabled);
+
 	bool warnBeforeStreamStop = config_get_bool(
 		GetGlobalConfig(), "BasicWindow", "WarnBeforeStoppingStream");
 	ui->warnBeforeStreamStop->setChecked(warnBeforeStreamStop);
@@ -3121,6 +3126,14 @@ void OBSBasicSettings::SaveGeneralSettings()
 				ui->previewSafeAreas->isChecked());
 		main->UpdatePreviewSafeAreas();
 	}
+
+	if (WidgetChanged(ui->previewSpacingHelpers)) {
+		config_set_bool(GetGlobalConfig(), "BasicWindow",
+				"SpacingHelpersEnabled",
+				ui->previewSpacingHelpers->isChecked());
+		main->UpdatePreviewSpacingHelpers();
+	}
+
 	if (WidgetChanged(ui->doubleClickSwitch))
 		config_set_bool(GetGlobalConfig(), "BasicWindow",
 				"TransitionOnDoubleClick",