Skip to content

Commit

Permalink
fix: Avoid adjusting a line connection when it's not on the outline
Browse files Browse the repository at this point in the history
- Vertices are now able to connect to shapes from anywhere
  • Loading branch information
miyanokomiya committed Nov 28, 2024
1 parent f549b4c commit b190cc3
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 6 deletions.
29 changes: 27 additions & 2 deletions src/composables/lineSnapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { newShapeComposite } from "./shapeComposite";
import { TextShape } from "../shapes/text";
import { TwoSidedArrowShape } from "../shapes/twoSidedArrow";
import { newShapeSnapping } from "./shapeSnapping";
import { RoundedRectangleShape } from "../shapes/polygons/roundedRectangle";

describe("newLineSnapping", () => {
describe("testConnection", () => {
Expand Down Expand Up @@ -819,13 +820,13 @@ describe("patchLinesConnectedToShapeOutline", () => {
});
});

test("should reconnect lines to the outline of the shape: rectangle -> star", () => {
test("should reconnect lines to the outline of the shape: rectangle -> two_sided_arrow", () => {
const arrow = createShape<TwoSidedArrowShape>(getCommonStruct, "two_sided_arrow", {
id: "a",
width: 100,
height: 50,
});
const shapeComposite = newShapeComposite({ shapes: [line, arrow], getStruct: getCommonStruct });
const shapeComposite = newShapeComposite({ shapes: [line, shapeA], getStruct: getCommonStruct });
const res = patchLinesConnectedToShapeOutline(shapeComposite, arrow);
expect(res).toEqual({
line: {
Expand Down Expand Up @@ -875,6 +876,30 @@ describe("patchLinesConnectedToShapeOutline", () => {
expect(res).toEqual({});
});

test("should ignore connections that are not on the outline", () => {
const shapeB = createShape<RoundedRectangleShape>(getCommonStruct, "rounded_rectangle", {
id: "b",
p: { x: 0, y: 0 },
width: 100,
height: 100,
rx: 10,
ry: 10,
});
const lineB = createShape<LineShape>(getCommonStruct, "line", {
id: "line_b",
p: { x: -50, y: -50 },
q: { x: 0, y: 0 },
qConnection: { id: shapeB.id, rate: { x: 0, y: 0 } },
});
const shapeComposite = newShapeComposite({ shapes: [lineB, shapeB], getStruct: getCommonStruct });
const res = patchLinesConnectedToShapeOutline(shapeComposite, {
...shapeB,
rx: 20,
ry: 20,
} as RoundedRectangleShape);
expect(res).toEqual({});
});

test("should ignore connections unrelated to the shape", () => {
const shapeComposite = newShapeComposite({ shapes: [line, shapeA], getStruct: getCommonStruct });
const res = patchLinesConnectedToShapeOutline(shapeComposite, { ...shapeA, id: "b" });
Expand Down
25 changes: 21 additions & 4 deletions src/composables/lineSnapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
extendSegment,
getClosestPointTo,
getD2,
getLocationFromRateOnRectPath,
isRectOverlappedH,
isRectOverlappedV,
isSameValue,
Expand Down Expand Up @@ -553,10 +554,8 @@ function patchLineConnectedToShapeOutline(
): Partial<LineShape> {
const pConnection = line.pConnection;
const qConnection = line.qConnection;
const shouldCheckP =
pConnection && pConnection.id === shape.id && !isConnectedToCenter(pConnection) && !pConnection.optimized;
const shouldCheckQ =
qConnection && qConnection.id === shape.id && !isConnectedToCenter(qConnection) && !qConnection.optimized;
const shouldCheckP = shouldReconnectToOutline(shapeComposite, shape.id, pConnection);
const shouldCheckQ = shouldReconnectToOutline(shapeComposite, shape.id, qConnection);
if (!shouldCheckP && !shouldCheckQ) return {};

const points = getLinePath(line);
Expand Down Expand Up @@ -586,6 +585,24 @@ function patchLineConnectedToShapeOutline(
return ret;
}

function shouldReconnectToOutline(
shapeComposite: ShapeComposite,
shapeId: string,
connection?: ConnectionPoint,
): connection is ConnectionPoint {
if (connection?.id !== shapeId) return false;
if (isConnectedToCenter(connection) || connection.optimized) return false;

// Check if the connection point is on the outline.
const shape = shapeComposite.shapeMap[shapeId];
const rectPath = shapeComposite.getLocalRectPolygon(shape);
const p = getLocationFromRateOnRectPath(rectPath, shape.rotation, connection.rate);
// Set the threshold a bit loose.
// Whether the connection should be reconnected or not isn't always obvious either way.
const closestOutline = getClosestOutline(shapeComposite.getShapeStruct, shape, p, 0.01);
return !!closestOutline;
}

/**
* Returns the closest intersection on the segment.
* - Picks the closest one to the original point when there're multiple candidates.
Expand Down

0 comments on commit b190cc3

Please sign in to comment.