diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 476904a80..f2da3fee9 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -358,6 +358,7 @@ Basic.Settings.General.WarnBeforeStoppingStream="Show confirmation dialog when s Basic.Settings.General.Snapping="Source Alignment Snapping" 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 mode 'stream' settings diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index f94c7d013..c2d69774b 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -243,6 +243,16 @@ + + + + Basic.Settings.General.SourceSnapping + + + true + + + @@ -3626,6 +3636,22 @@ 476 202 + + 466 + 253 + + + + + snappingEnabled + toggled(bool) + sourceSnapping + setEnabled(bool) + + + 518 + 204 + 515 277 diff --git a/obs/obs-app.cpp b/obs/obs-app.cpp index f435a22a4..9a5654923 100644 --- a/obs/obs-app.cpp +++ b/obs/obs-app.cpp @@ -343,6 +343,8 @@ bool OBSApp::InitGlobalConfigDefaults() "SnappingEnabled", true); config_set_default_bool(globalConfig, "BasicWindow", "ScreenSnapping", true); + config_set_default_bool(globalConfig, "BasicWindow", + "SourceSnapping", true); config_set_default_bool(globalConfig, "BasicWindow", "CenterSnapping", false); config_set_default_double(globalConfig, "BasicWindow", diff --git a/obs/window-basic-preview.cpp b/obs/window-basic-preview.cpp index 7d6ebdf84..1b995c421 100644 --- a/obs/window-basic-preview.cpp +++ b/obs/window-basic-preview.cpp @@ -502,6 +502,66 @@ static bool AddItemBounds(obs_scene_t *scene, obs_sceneitem_t *item, return true; } +struct OffsetData { + float clampDist; + vec3 tl, br, offset; +}; + +static bool GetSourceSnapOffset(obs_scene_t *scene, obs_sceneitem_t *item, + void *param) +{ + OffsetData *data = reinterpret_cast(param); + + if (obs_sceneitem_selected(item)) + return true; + + matrix4 boxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + + vec3 t[4] = { + GetTransformedPos(0.0f, 0.0f, boxTransform), + GetTransformedPos(1.0f, 0.0f, boxTransform), + GetTransformedPos(0.0f, 1.0f, boxTransform), + GetTransformedPos(1.0f, 1.0f, boxTransform) + }; + + bool first = true; + vec3 tl, br; + vec3_zero(&tl); + vec3_zero(&br); + for (const vec3 &v : t) { + if (first) { + vec3_copy(&tl, &v); + vec3_copy(&br, &v); + first = false; + } else { + vec3_min(&tl, &tl, &v); + vec3_max(&br, &br, &v); + } + } + + // Snap to other source edges +#define EDGE_SNAP(l, r, x, y) \ + do { \ + double dist = fabsf(l.x - data->r.x); \ + if (dist < data->clampDist && \ + fabsf(data->offset.x) < EPSILON && \ + data->tl.y < br.y && \ + data->br.y > tl.y && \ + (fabsf(data->offset.x) > dist || data->offset.x < EPSILON)) \ + data->offset.x = l.x - data->r.x; \ + } while (false) + + EDGE_SNAP(tl, br, x, y); + EDGE_SNAP(tl, br, y, x); + EDGE_SNAP(br, tl, x, y); + EDGE_SNAP(br, tl, y, x); +#undef EDGE_SNAP + + UNUSED_PARAMETER(scene); + return true; +} + void OBSBasicPreview::SnapItemMovement(vec2 &offset) { OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); @@ -519,11 +579,35 @@ void OBSBasicPreview::SnapItemMovement(vec2 &offset) const bool snap = config_get_bool(GetGlobalConfig(), "BasicWindow", "SnappingEnabled"); + const bool sourcesSnap = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SourceSnapping"); if (snap == false) return; + if (sourcesSnap == false) { + offset.x += snapOffset.x; + offset.y += snapOffset.y; + return; + } - offset.x += snapOffset.x; - offset.y += snapOffset.y; + const float clampDist = config_get_double(GetGlobalConfig(), + "BasicWindow", "SnapDistance") / main->previewScale; + + OffsetData offsetData; + offsetData.clampDist = clampDist; + offsetData.tl = data.tl; + offsetData.br = data.br; + vec3_copy(&offsetData.offset, &snapOffset); + + obs_scene_enum_items(scene, GetSourceSnapOffset, &offsetData); + + if (fabsf(offsetData.offset.x) > EPSILON || + fabsf(offsetData.offset.y) > EPSILON) { + offset.x += offsetData.offset.x; + offset.y += offsetData.offset.y; + } else { + offset.x += snapOffset.x; + offset.y += snapOffset.y; + } } static bool move_items(obs_scene_t *scene, obs_sceneitem_t *item, void *param) diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index c3302e886..6a8f3e4a9 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -269,6 +269,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->snappingEnabled, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->screenSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->centerSnapping, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->sourceSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->snapDistance, SCROLL_CHANGED, GENERAL_CHANGED); HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->streamType, COMBO_CHANGED, STREAM1_CHANGED); @@ -779,6 +780,10 @@ void OBSBasicSettings::LoadGeneralSettings() "BasicWindow", "CenterSnapping"); ui->centerSnapping->setChecked(centerSnapping); + bool sourceSnapping = config_get_bool(GetGlobalConfig(), + "BasicWindow", "SourceSnapping"); + ui->sourceSnapping->setChecked(sourceSnapping); + double snapDistance = config_get_double(GetGlobalConfig(), "BasicWindow", "SnapDistance"); ui->snapDistance->setValue(snapDistance); @@ -2071,6 +2076,10 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_bool(GetGlobalConfig(), "BasicWindow", "CenterSnapping", ui->centerSnapping->isChecked()); + if (WidgetChanged(ui->sourceSnapping)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "SourceSnapping", + ui->sourceSnapping->isChecked()); if (WidgetChanged(ui->snapDistance)) config_set_double(GetGlobalConfig(), "BasicWindow", "SnapDistance",