mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
fix preview snapping threshold
This commit is contained in:
@@ -4,9 +4,16 @@ import { useShiftKey } from "@/hooks/use-shift-key";
|
|||||||
import type { TextElement, Transform } from "@/types/timeline";
|
import type { TextElement, Transform } from "@/types/timeline";
|
||||||
import { getVisibleElementsWithBounds } from "@/lib/preview/element-bounds";
|
import { getVisibleElementsWithBounds } from "@/lib/preview/element-bounds";
|
||||||
import { hitTest } from "@/lib/preview/hit-test";
|
import { hitTest } from "@/lib/preview/hit-test";
|
||||||
import { screenToCanvas } from "@/lib/preview/preview-coords";
|
import {
|
||||||
|
screenPixelsToLogicalThreshold,
|
||||||
|
screenToCanvas,
|
||||||
|
} from "@/lib/preview/preview-coords";
|
||||||
import { isVisualElement } from "@/lib/timeline/element-utils";
|
import { isVisualElement } from "@/lib/timeline/element-utils";
|
||||||
import { snapPosition, type SnapLine } from "@/lib/preview/preview-snap";
|
import {
|
||||||
|
SNAP_THRESHOLD_SCREEN_PIXELS,
|
||||||
|
snapPosition,
|
||||||
|
type SnapLine,
|
||||||
|
} from "@/lib/preview/preview-snap";
|
||||||
|
|
||||||
const MIN_DRAG_DISTANCE = 0.5;
|
const MIN_DRAG_DISTANCE = 0.5;
|
||||||
|
|
||||||
@@ -230,11 +237,16 @@ export function usePreviewInteraction({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldSnap = !isShiftHeldRef.current;
|
const shouldSnap = !isShiftHeldRef.current;
|
||||||
|
const snapThreshold = screenPixelsToLogicalThreshold({
|
||||||
|
canvas: canvasRef.current,
|
||||||
|
screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,
|
||||||
|
});
|
||||||
const { snappedPosition, activeLines } = shouldSnap
|
const { snappedPosition, activeLines } = shouldSnap
|
||||||
? snapPosition({
|
? snapPosition({
|
||||||
proposedPosition,
|
proposedPosition,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
elementSize: dragStateRef.current.bounds,
|
elementSize: dragStateRef.current.bounds,
|
||||||
|
snapThreshold,
|
||||||
})
|
})
|
||||||
: {
|
: {
|
||||||
snappedPosition: proposedPosition,
|
snappedPosition: proposedPosition,
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import {
|
|||||||
getVisibleElementsWithBounds,
|
getVisibleElementsWithBounds,
|
||||||
type ElementWithBounds,
|
type ElementWithBounds,
|
||||||
} from "@/lib/preview/element-bounds";
|
} from "@/lib/preview/element-bounds";
|
||||||
import { screenToCanvas } from "@/lib/preview/preview-coords";
|
import {
|
||||||
|
screenPixelsToLogicalThreshold,
|
||||||
|
screenToCanvas,
|
||||||
|
} from "@/lib/preview/preview-coords";
|
||||||
import {
|
import {
|
||||||
MIN_SCALE,
|
MIN_SCALE,
|
||||||
|
SNAP_THRESHOLD_SCREEN_PIXELS,
|
||||||
snapRotation,
|
snapRotation,
|
||||||
snapScale,
|
snapScale,
|
||||||
type SnapLine,
|
type SnapLine,
|
||||||
@@ -228,6 +232,10 @@ export function useTransformHandles({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const canvasSize = editor.project.getActive().settings.canvasSize;
|
const canvasSize = editor.project.getActive().settings.canvasSize;
|
||||||
|
const snapThreshold = screenPixelsToLogicalThreshold({
|
||||||
|
canvas: canvasRef.current,
|
||||||
|
screenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,
|
||||||
|
});
|
||||||
const shouldSnap = !isShiftHeldRef.current;
|
const shouldSnap = !isShiftHeldRef.current;
|
||||||
const { snappedScale, activeLines } = shouldSnap
|
const { snappedScale, activeLines } = shouldSnap
|
||||||
? snapScale({
|
? snapScale({
|
||||||
@@ -236,6 +244,7 @@ export function useTransformHandles({
|
|||||||
baseWidth,
|
baseWidth,
|
||||||
baseHeight,
|
baseHeight,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
snapThreshold,
|
||||||
})
|
})
|
||||||
: { snappedScale: proposedScale, activeLines: [] as SnapLine[] };
|
: { snappedScale: proposedScale, activeLines: [] as SnapLine[] };
|
||||||
|
|
||||||
|
|||||||
@@ -74,3 +74,17 @@ export function getDisplayScale({
|
|||||||
y: canvasRect.height / canvasSize.height,
|
y: canvasRect.height / canvasSize.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function screenPixelsToLogicalThreshold({
|
||||||
|
canvas,
|
||||||
|
screenPixels,
|
||||||
|
}: {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
screenPixels: number;
|
||||||
|
}): { x: number; y: number } {
|
||||||
|
const canvasRect = canvas.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: screenPixels * (canvas.width / canvasRect.width),
|
||||||
|
y: screenPixels * (canvas.height / canvasRect.height),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ export interface SnapLine {
|
|||||||
position: number;
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SNAP_THRESHOLD = 10;
|
|
||||||
const ROTATION_SNAP_STEP_DEGREES = 90;
|
const ROTATION_SNAP_STEP_DEGREES = 90;
|
||||||
const ROTATION_SNAP_THRESHOLD_DEGREES = 5;
|
const ROTATION_SNAP_THRESHOLD_DEGREES = 5;
|
||||||
export const MIN_SCALE = 0.01;
|
export const MIN_SCALE = 0.01;
|
||||||
|
export const SNAP_THRESHOLD_SCREEN_PIXELS = 8;
|
||||||
|
|
||||||
export interface SnapResult {
|
export interface SnapResult {
|
||||||
snappedPosition: { x: number; y: number };
|
snappedPosition: { x: number; y: number };
|
||||||
@@ -17,10 +17,12 @@ export function snapPosition({
|
|||||||
proposedPosition,
|
proposedPosition,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
elementSize,
|
elementSize,
|
||||||
|
snapThreshold,
|
||||||
}: {
|
}: {
|
||||||
proposedPosition: { x: number; y: number };
|
proposedPosition: { x: number; y: number };
|
||||||
canvasSize: { width: number; height: number };
|
canvasSize: { width: number; height: number };
|
||||||
elementSize: { width: number; height: number };
|
elementSize: { width: number; height: number };
|
||||||
|
snapThreshold: { x: number; y: number };
|
||||||
}): SnapResult {
|
}): SnapResult {
|
||||||
const centerX = 0;
|
const centerX = 0;
|
||||||
const centerY = 0;
|
const centerY = 0;
|
||||||
@@ -41,11 +43,13 @@ export function snapPosition({
|
|||||||
|
|
||||||
function getClosestAxisSnap({
|
function getClosestAxisSnap({
|
||||||
candidates,
|
candidates,
|
||||||
|
threshold,
|
||||||
}: {
|
}: {
|
||||||
candidates: AxisSnapCandidate[];
|
candidates: AxisSnapCandidate[];
|
||||||
|
threshold: number;
|
||||||
}): AxisSnapCandidate | null {
|
}): AxisSnapCandidate | null {
|
||||||
const snapCandidatesWithinThreshold = candidates.filter(
|
const snapCandidatesWithinThreshold = candidates.filter(
|
||||||
(candidate) => candidate.distance <= SNAP_THRESHOLD,
|
(candidate) => candidate.distance <= threshold,
|
||||||
);
|
);
|
||||||
if (snapCandidatesWithinThreshold.length === 0) {
|
if (snapCandidatesWithinThreshold.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -95,8 +99,14 @@ export function snapPosition({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const closestX = getClosestAxisSnap({ candidates: xCandidates });
|
const closestX = getClosestAxisSnap({
|
||||||
const closestY = getClosestAxisSnap({ candidates: yCandidates });
|
candidates: xCandidates,
|
||||||
|
threshold: snapThreshold.x,
|
||||||
|
});
|
||||||
|
const closestY = getClosestAxisSnap({
|
||||||
|
candidates: yCandidates,
|
||||||
|
threshold: snapThreshold.y,
|
||||||
|
});
|
||||||
|
|
||||||
const x = closestX?.snappedPosition ?? proposedPosition.x;
|
const x = closestX?.snappedPosition ?? proposedPosition.x;
|
||||||
const y = closestY?.snappedPosition ?? proposedPosition.y;
|
const y = closestY?.snappedPosition ?? proposedPosition.y;
|
||||||
@@ -124,12 +134,14 @@ export function snapScale({
|
|||||||
baseWidth,
|
baseWidth,
|
||||||
baseHeight,
|
baseHeight,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
snapThreshold,
|
||||||
}: {
|
}: {
|
||||||
proposedScale: number;
|
proposedScale: number;
|
||||||
position: { x: number; y: number };
|
position: { x: number; y: number };
|
||||||
baseWidth: number;
|
baseWidth: number;
|
||||||
baseHeight: number;
|
baseHeight: number;
|
||||||
canvasSize: { width: number; height: number };
|
canvasSize: { width: number; height: number };
|
||||||
|
snapThreshold: { x: number; y: number };
|
||||||
}): ScaleSnapResult {
|
}): ScaleSnapResult {
|
||||||
const centerX = 0;
|
const centerX = 0;
|
||||||
const centerY = 0;
|
const centerY = 0;
|
||||||
@@ -162,7 +174,7 @@ export function snapScale({
|
|||||||
|
|
||||||
for (const target of verticalTargets) {
|
for (const target of verticalTargets) {
|
||||||
const distanceLeft = Math.abs(leftEdge - target.position);
|
const distanceLeft = Math.abs(leftEdge - target.position);
|
||||||
if (distanceLeft <= SNAP_THRESHOLD) {
|
if (distanceLeft <= snapThreshold.x) {
|
||||||
const scale = (2 * (position.x - target.position)) / baseWidth;
|
const scale = (2 * (position.x - target.position)) / baseWidth;
|
||||||
if (scale > MIN_SCALE) {
|
if (scale > MIN_SCALE) {
|
||||||
candidates.push({
|
candidates.push({
|
||||||
@@ -173,7 +185,7 @@ export function snapScale({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const distanceRight = Math.abs(rightEdge - target.position);
|
const distanceRight = Math.abs(rightEdge - target.position);
|
||||||
if (distanceRight <= SNAP_THRESHOLD) {
|
if (distanceRight <= snapThreshold.x) {
|
||||||
const scale = (2 * (target.position - position.x)) / baseWidth;
|
const scale = (2 * (target.position - position.x)) / baseWidth;
|
||||||
if (scale > MIN_SCALE) {
|
if (scale > MIN_SCALE) {
|
||||||
candidates.push({
|
candidates.push({
|
||||||
@@ -199,7 +211,7 @@ export function snapScale({
|
|||||||
|
|
||||||
for (const target of horizontalTargets) {
|
for (const target of horizontalTargets) {
|
||||||
const distanceTop = Math.abs(topEdge - target.position);
|
const distanceTop = Math.abs(topEdge - target.position);
|
||||||
if (distanceTop <= SNAP_THRESHOLD) {
|
if (distanceTop <= snapThreshold.y) {
|
||||||
const scale = (2 * (position.y - target.position)) / baseHeight;
|
const scale = (2 * (position.y - target.position)) / baseHeight;
|
||||||
if (scale > MIN_SCALE) {
|
if (scale > MIN_SCALE) {
|
||||||
candidates.push({
|
candidates.push({
|
||||||
@@ -210,7 +222,7 @@ export function snapScale({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const distanceBottom = Math.abs(bottomEdge - target.position);
|
const distanceBottom = Math.abs(bottomEdge - target.position);
|
||||||
if (distanceBottom <= SNAP_THRESHOLD) {
|
if (distanceBottom <= snapThreshold.y) {
|
||||||
const scale = (2 * (target.position - position.y)) / baseHeight;
|
const scale = (2 * (target.position - position.y)) / baseHeight;
|
||||||
if (scale > MIN_SCALE) {
|
if (scale > MIN_SCALE) {
|
||||||
candidates.push({
|
candidates.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user