Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions frontend/OBSApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1;
extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#endif

static constexpr double kDefaultSnapDistance = 5.0;

namespace {

typedef struct UncleanLaunchAction {
Expand Down Expand Up @@ -361,7 +363,7 @@ void OBSApp::InitUserConfigDefaults()
config_set_default_bool(userConfig, "BasicWindow", "ScreenSnapping", true);
config_set_default_bool(userConfig, "BasicWindow", "SourceSnapping", true);
config_set_default_bool(userConfig, "BasicWindow", "CenterSnapping", false);
config_set_default_double(userConfig, "BasicWindow", "SnapDistance", 10.0);
config_set_default_double(userConfig, "BasicWindow", "SnapDistance", kDefaultSnapDistance);
config_set_default_bool(userConfig, "BasicWindow", "SpacingHelpersEnabled", true);
config_set_default_bool(userConfig, "BasicWindow", "RecordWhenStreaming", false);
config_set_default_bool(userConfig, "BasicWindow", "KeepRecordingWhenStreamStops", false);
Expand Down Expand Up @@ -628,7 +630,8 @@ bool OBSApp::InitUserConfig(std::filesystem::path &userConfigLocation, uint32_t

void OBSApp::MigrateLegacySettings(const uint32_t lastVersion)
{
bool hasChanges = false;
bool hasUserConfigChanges = false;
bool hasGlobalConfigChanges = false;

const uint32_t v19 = MAKE_SEMANTIC_VERSION(19, 0, 0);
const uint32_t v21 = MAKE_SEMANTIC_VERSION(21, 0, 0);
Expand All @@ -644,7 +647,7 @@ void OBSApp::MigrateLegacySettings(const uint32_t lastVersion)
bool useOldDefaults = lastVersion && lastVersion < version;
config_set_bool(userConfig, "General", configKey.c_str(), useOldDefaults);

hasChanges = true;
hasUserConfigChanges = true;
}
}

Expand All @@ -653,7 +656,7 @@ void OBSApp::MigrateLegacySettings(const uint32_t lastVersion)

bool layoutUpdated = UpdatePre22MultiviewLayout(layout);

hasChanges = hasChanges | layoutUpdated;
hasUserConfigChanges = hasUserConfigChanges | layoutUpdated;
}

if (lastVersion && lastVersion < v24) {
Expand All @@ -663,12 +666,34 @@ void OBSApp::MigrateLegacySettings(const uint32_t lastVersion)
config_set_string(userConfig, "General", "HotkeyFocusType", "DisableHotkeysInFocus");
}

hasChanges = true;
hasUserConfigChanges = true;
}

if (lastVersion && lastVersion < MAKE_SEMANTIC_VERSION(32, 2, 0)) {
bool migratedUserSettings = config_has_user_value(appConfig, "General", "Pre32.2Migrated");

if (!migratedUserSettings) {
double currentSnapDistance = config_get_double(userConfig, "BasicWindow", "SnapDistance");

if (currentSnapDistance == 10.0) {
double newDefaultSnapDistance = kDefaultSnapDistance;
config_set_double(userConfig, "BasicWindow", "SnapDistance", newDefaultSnapDistance);
}

config_set_bool(appConfig, "General", "Pre32.2Migrated", true);

hasUserConfigChanges = true;
hasGlobalConfigChanges = true;
}
}

if (hasChanges) {
if (hasUserConfigChanges) {
userConfig.SaveSafe("tmp");
}

if (hasGlobalConfigChanges) {
appConfig.SaveSafe("tmp");
}
}

static constexpr string_view OBSGlobalIniPath = "/obs-studio/global.ini";
Expand Down
1 change: 1 addition & 0 deletions frontend/widgets/OBSBasic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ private slots:
void TogglePreview();

public:
void addSnapGuide(SnapGuide guide);
inline void GetDisplayRect(int &x, int &y, int &cx, int &cy)
{
x = previewX;
Expand Down
218 changes: 180 additions & 38 deletions frontend/widgets/OBSBasicPreview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@
#define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f)
#define HELPER_ROT_BREAKPOINT 45.0f

namespace {
bool checkEdgeSnap(float moveAxis, float checkAxis, float clampDistance, float &offset)
{
double dist = fabsf(checkAxis - moveAxis);
if (dist < clampDistance && fabsf(offset) < EPSILON) {
offset = checkAxis - moveAxis;
return true;
}

return false;
}
} // namespace

/* TODO: make C++ math classes and clean up code here later */

OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags) : OBSQTDisplay(parent, flags)
Expand Down Expand Up @@ -130,6 +143,9 @@ static bool FindItemAtPos(obs_scene_t * /* scene */, obs_sceneitem_t *item, void
if (obs_sceneitem_locked(item)) {
return true;
}
if (!obs_sceneitem_visible(item)) {
return true;
}

vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);

Expand Down Expand Up @@ -177,6 +193,11 @@ static inline vec2 GetOBSScreenSize()
return size;
}

void OBSBasicPreview::addSnapGuide(SnapGuide guide)
{
snapGuides.push_back(guide);
}

vec3 OBSBasicPreview::GetSnapOffset(const vec3 &tl, const vec3 &br)
{
OBSBasic *main = OBSBasic::Get();
Expand All @@ -198,32 +219,42 @@ vec3 OBSBasicPreview::GetSnapOffset(const vec3 &tl, const vec3 &br)
const float centerX = br.x - (br.x - tl.x) / 2.0f;
const float centerY = br.y - (br.y - tl.y) / 2.0f;

// Left screen edge.
if (screenSnap && fabsf(tl.x) < clampDist) {
clampOffset.x = -tl.x;
}
// Right screen edge.
if (screenSnap && fabsf(clampOffset.x) < EPSILON && fabsf(screenSize.x - br.x) < clampDist) {
clampOffset.x = screenSize.x - br.x;
}
// Horizontal center.
if (centerSnap && fabsf(screenSize.x - (br.x - tl.x)) > clampDist &&
fabsf(screenSize.x / 2.0f - centerX) < clampDist) {
clampOffset.x = screenSize.x / 2.0f - centerX;
}
if (screenSnap) {
Comment thread
Warchamp7 marked this conversation as resolved.
// Left screen edge.
if (checkEdgeSnap(tl.x, 0, clampDist, clampOffset.x)) {
main->addSnapGuide(SnapGuide(0, 0, 0, screenSize.y));
}

// Top screen edge.
if (screenSnap && fabsf(tl.y) < clampDist) {
clampOffset.y = -tl.y;
}
// Bottom screen edge.
if (screenSnap && fabsf(clampOffset.y) < EPSILON && fabsf(screenSize.y - br.y) < clampDist) {
clampOffset.y = screenSize.y - br.y;
// Right screen edge.
if (checkEdgeSnap(br.x, screenSize.x, clampDist, clampOffset.x)) {
main->addSnapGuide(SnapGuide(screenSize.x, 0, screenSize.x, screenSize.y));
}

// Top screen edge.
if (checkEdgeSnap(tl.y, 0, clampDist, clampOffset.y)) {
main->addSnapGuide(SnapGuide(0, 0, screenSize.x, 0));
}

// Bottom screen edge.
if (checkEdgeSnap(br.y, screenSize.y, clampDist, clampOffset.y)) {
main->addSnapGuide(SnapGuide(0, screenSize.y, screenSize.x, screenSize.y));
}
}
// Vertical center.
if (centerSnap && fabsf(screenSize.y - (br.y - tl.y)) > clampDist &&
fabsf(screenSize.y / 2.0f - centerY) < clampDist) {
clampOffset.y = screenSize.y / 2.0f - centerY;

if (centerSnap) {
// Horizontal center.
if (fabsf(screenSize.x - (br.x - tl.x)) > clampDist &&
fabsf(screenSize.x / 2.0f - centerX) < clampDist) {
clampOffset.x = screenSize.x / 2.0f - centerX;
main->addSnapGuide(SnapGuide(screenSize.x / 2.0f, 0, screenSize.x / 2.0f, screenSize.y));
}

// Vertical center.
if (fabsf(screenSize.y - (br.y - tl.y)) > clampDist &&
fabsf(screenSize.y / 2.0f - centerY) < clampDist) {
clampOffset.y = screenSize.y / 2.0f - centerY;
main->addSnapGuide(SnapGuide(0, screenSize.y / 2.0f, screenSize.x, screenSize.y / 2.0f));
}
}

return clampOffset;
Expand Down Expand Up @@ -754,6 +785,7 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
return;
}

OBSBasic *main = OBSBasic::Get();
if (mouseDown) {
vec2 pos = GetMouseEventPos(event);

Expand Down Expand Up @@ -807,8 +839,9 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
hoveredPreviewItems.clear();
hoveredPreviewItems.push_back(item);
selectedItems.clear();
snapGuides.clear();
}
OBSBasic *main = OBSBasic::Get();

OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(main->GetCurrentScene(), true);

auto undo_redo = [](const std::string &data) {
Expand Down Expand Up @@ -893,15 +926,32 @@ static bool AddItemBounds(obs_scene_t * /* scene */, obs_sceneitem_t *item, void
struct OffsetData {
float clampDist;
vec3 tl, br, offset;

float left() { return tl.x; }
float top() { return tl.y; }
float right() { return br.x; }
float bottom() { return br.y; }

vec2 center()
{
const float centerX = right() - (right() - left()) / 2.0f;
const float centerY = bottom() - (bottom() - top()) / 2.0f;

return {centerX, centerY};
}
};

static bool GetSourceSnapOffset(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
{
OBSBasic *main = OBSBasic::Get();
OffsetData *data = static_cast<OffsetData *>(param);

if (obs_sceneitem_selected(item)) {
return true;
}
if (!obs_sceneitem_visible(item)) {
return true;
}

matrix4 boxTransform;
obs_sceneitem_get_box_transform(item, &boxTransform);
Expand All @@ -924,20 +974,58 @@ static bool GetSourceSnapOffset(obs_scene_t * /* scene */, obs_sceneitem_t *item
}
}

// 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)
const auto screen = GetOBSScreenSize();

QRectF moveRect{data->left(), data->top(), data->right() - data->left(), data->bottom() - data->top()};
QRectF itemRect{tl.x, tl.y, br.x - tl.x, br.y - tl.y};

const QPointF centerDelta{moveRect.center().x() - itemRect.center().x(),
moveRect.center().y() - itemRect.center().y()};

QPoint itemEdge{};
QPoint movingEdge{};

itemEdge.rx() = centerDelta.x() < 0 ? itemRect.left() : itemRect.right();
movingEdge.rx() = centerDelta.x() < 0 ? moveRect.left() : moveRect.right();

itemEdge.ry() = centerDelta.y() < 0 ? itemRect.top() : itemRect.bottom();
movingEdge.ry() = centerDelta.y() < 0 ? moveRect.top() : moveRect.bottom();

// Horizontal snapping
// Check if the delta between centers is larger than the sum of half widths minus the clamp distance.
// Subtracting the clamp distance allows snapping from the "inner" edges when overlapping.
if (std::fabs(centerDelta.x()) > (moveRect.width() + itemRect.width() - (data->clampDist * 2)) / 2) {
// Moving item is not overlapping
movingEdge.rx() = centerDelta.x() < 0 ? moveRect.right() : moveRect.left();
} else if (moveRect.width() > itemRect.width()) {
// Moving item is overlapping but larger, invert checked edges
itemEdge.rx() = centerDelta.x() > 0 ? itemRect.left() : itemRect.right();
movingEdge.rx() = centerDelta.x() > 0 ? moveRect.left() : moveRect.right();
}

// Vertical snapping
// Check if the delta between centers is larger than the sum of half widths minus the clamp distance.
// Subtracting the clamp distance allows snapping from the "inner" edges when overlapping.
if (std::fabs(centerDelta.y()) > (moveRect.height() + itemRect.height() - (data->clampDist * 2)) / 2) {
// Moving item is not overlapping
movingEdge.ry() = centerDelta.y() < 0 ? moveRect.bottom() : moveRect.top();
} else if (moveRect.height() > itemRect.height()) {
// Moving item is overlapping but larger, invert checked edges
itemEdge.ry() = centerDelta.y() > 0 ? itemRect.top() : itemRect.bottom();
movingEdge.ry() = centerDelta.y() > 0 ? moveRect.top() : moveRect.bottom();
}

if (checkEdgeSnap(movingEdge.x(), itemEdge.x(), data->clampDist, data->offset.x)) {
main->addSnapGuide(SnapGuide(itemEdge.x(), 0.0f, itemEdge.x(), screen.y));
} else if (checkEdgeSnap(moveRect.center().x(), itemRect.center().x(), data->clampDist, data->offset.x)) {
main->addSnapGuide(SnapGuide(itemRect.center().x(), 0.0f, itemRect.center().x(), screen.y));
}

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
if (checkEdgeSnap(movingEdge.y(), itemEdge.y(), data->clampDist, data->offset.y)) {
main->addSnapGuide(SnapGuide(0.0f, itemEdge.y(), screen.x, itemEdge.y()));
} else if (checkEdgeSnap(moveRect.center().y(), itemRect.center().y(), data->clampDist, data->offset.y)) {
main->addSnapGuide(SnapGuide(0.0f, itemRect.center().y(), screen.x, itemRect.center().y()));
}

return true;
}
Expand Down Expand Up @@ -1632,6 +1720,8 @@ void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
pos.x = std::round(pos.x);
pos.y = std::round(pos.y);

snapGuides.clear();

if (stretchHandle != ItemHandle::None) {
if (obs_sceneitem_locked(stretchItem)) {
return;
Expand Down Expand Up @@ -2180,6 +2270,58 @@ bool OBSBasicPreview::DrawSelectionBox(float x1, float y1, float x2, float y2, g
return true;
}

void OBSBasicPreview::DrawSnapGuides()
Comment thread
Warchamp7 marked this conversation as resolved.
{
if (snapGuides.empty()) {
return;
}

OBSBasic *main = OBSBasic::Get();

vec2 viewport;
vec2_set(&viewport, main->previewCX, main->previewCY);

float pixelRatio = main->GetDevicePixelRatio();

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 snapColor;
vec4_set(&snapColor, 0.0f, 1.0f, 1.0f, 0.8f);

gs_effect_set_vec4(gs_effect_get_param_by_name(solid, "color"), &snapColor);

gs_technique_begin(tech);
gs_technique_begin_pass(tech, 0);

gs_matrix_push();
gs_matrix_mul(&transform);

for (const SnapGuide &guide : snapGuides) {
vec2 start, end;

vec2_div(&start, &guide.start, &viewport);
vec2_div(&end, &guide.end, &viewport);

vec2_mulf(&start, &start, main->previewScale);
vec2_mulf(&end, &end, main->previewScale);

DrawLine(start.x, start.y, end.x, end.y, HANDLE_RADIUS * pixelRatio / 2, viewport);
}

gs_matrix_pop();

gs_load_vertexbuffer(nullptr);

gs_technique_end_pass(tech);
gs_technique_end(tech);
}

void OBSBasicPreview::DrawOverflow()
{
if (locked) {
Expand Down
Loading
Loading