mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-14 17:54:47 +01:00
fix: Alt precise positioning
This commit is contained in:
@@ -1,26 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
KEYS,
|
KEYS,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
debugDrawLine,
|
|
||||||
getFeatureFlag,
|
getFeatureFlag,
|
||||||
invariant,
|
invariant,
|
||||||
isTransparent,
|
isTransparent,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
|
||||||
pointFrom,
|
|
||||||
pointRotateRads,
|
|
||||||
type GlobalPoint,
|
|
||||||
vectorFromPoint,
|
|
||||||
pointDistanceSq,
|
|
||||||
clamp,
|
|
||||||
pointDistance,
|
|
||||||
pointFromVector,
|
|
||||||
vectorScale,
|
|
||||||
vectorNormalize,
|
|
||||||
PRECISION,
|
PRECISION,
|
||||||
lineSegmentIntersectionPoints,
|
clamp,
|
||||||
|
lineSegment,
|
||||||
|
pointDistance,
|
||||||
|
pointDistanceSq,
|
||||||
|
pointFrom,
|
||||||
|
pointFromVector,
|
||||||
|
pointRotateRads,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorNormalize,
|
||||||
|
vectorScale,
|
||||||
|
type GlobalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
|
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
@@ -56,33 +54,33 @@ import {
|
|||||||
isBindableElement,
|
isBindableElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isRectangularElement,
|
|
||||||
isRectanguloidElement,
|
isRectanguloidElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
import { aabbForElement, elementCenterPoint } from "./bounds";
|
import { aabbForElement, elementCenterPoint } from "./bounds";
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||||
|
import { projectFixedPointOntoDiagonal } from "./utils";
|
||||||
|
|
||||||
import type { Scene } from "./Scene";
|
import type { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
import type { ElementUpdate } from "./mutateElement";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawBindableElement,
|
BindMode,
|
||||||
ExcalidrawElement,
|
|
||||||
NonDeleted,
|
|
||||||
NonDeletedExcalidrawElement,
|
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
NonDeletedSceneElementsMap,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawTextElement,
|
||||||
FixedPoint,
|
FixedPoint,
|
||||||
FixedPointBinding,
|
FixedPointBinding,
|
||||||
PointsPositionUpdates,
|
NonDeleted,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
NonDeletedSceneElementsMap,
|
||||||
Ordered,
|
Ordered,
|
||||||
BindMode,
|
PointsPositionUpdates,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export type BindingStrategy =
|
export type BindingStrategy =
|
||||||
@@ -423,7 +421,7 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
|
|||||||
invariant(false, "New arrow creation should not reach here");
|
invariant(false, "New arrow creation should not reach here");
|
||||||
};
|
};
|
||||||
|
|
||||||
const bindingStrategyForSimpleArrowEndpointDragging = (
|
const bindingStrategyForSimpleArrowEndpointDragging_complex = (
|
||||||
point: GlobalPoint,
|
point: GlobalPoint,
|
||||||
currentBinding: FixedPointBinding | null,
|
currentBinding: FixedPointBinding | null,
|
||||||
oppositeBinding: FixedPointBinding | null,
|
oppositeBinding: FixedPointBinding | null,
|
||||||
@@ -689,7 +687,7 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
focusPoint: globalPoint,
|
focusPoint: globalPoint,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
mode: "orbit",
|
mode: opts?.altKey ? "inside" : "orbit",
|
||||||
element: hit,
|
element: hit,
|
||||||
focusPoint: opts?.altKey
|
focusPoint: opts?.altKey
|
||||||
? globalPoint
|
? globalPoint
|
||||||
@@ -738,109 +736,6 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectFixedPointOntoDiagonal = (
|
|
||||||
arrow: ExcalidrawArrowElement,
|
|
||||||
point: GlobalPoint,
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
startOrEnd: "start" | "end",
|
|
||||||
elementsMap: ElementsMap,
|
|
||||||
) => {
|
|
||||||
const center = elementCenterPoint(element, elementsMap);
|
|
||||||
const diagonalOne = isRectangularElement(element)
|
|
||||||
? lineSegment<GlobalPoint>(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x, element.y),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + element.width,
|
|
||||||
element.y + element.height,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: lineSegment<GlobalPoint>(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x + element.width / 2, element.y),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + element.width / 2,
|
|
||||||
element.y + element.height,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const diagonalTwo = isRectangularElement(element)
|
|
||||||
? lineSegment<GlobalPoint>(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x + element.width, element.y),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x, element.y + element.height),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: lineSegment<GlobalPoint>(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x, element.y + element.height / 2),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + element.width,
|
|
||||||
element.y + element.height / 2,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
invariant(arrow.points.length >= 2, "Arrow must have at least two points");
|
|
||||||
|
|
||||||
const a = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
|
||||||
arrow,
|
|
||||||
startOrEnd === "start" ? 1 : arrow.points.length - 2,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
const b = pointFromVector<GlobalPoint>(
|
|
||||||
vectorScale(
|
|
||||||
vectorFromPoint(point, a),
|
|
||||||
2 * pointDistance(a, point) +
|
|
||||||
Math.max(
|
|
||||||
pointDistance(diagonalOne[0], diagonalOne[1]),
|
|
||||||
pointDistance(diagonalTwo[0], diagonalTwo[1]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
a,
|
|
||||||
);
|
|
||||||
const intersector = lineSegment<GlobalPoint>(a, b);
|
|
||||||
const p1 = lineSegmentIntersectionPoints(diagonalOne, intersector);
|
|
||||||
const p2 = lineSegmentIntersectionPoints(diagonalTwo, intersector);
|
|
||||||
const d1 = p1 && pointDistance(a, p1);
|
|
||||||
const d2 = p2 && pointDistance(a, p2);
|
|
||||||
|
|
||||||
debugDrawLine(diagonalOne, { color: "purple" });
|
|
||||||
debugDrawLine(diagonalTwo, { color: "purple" });
|
|
||||||
debugDrawLine(intersector, { color: "orange" });
|
|
||||||
|
|
||||||
if (d1 != null && d2 != null) {
|
|
||||||
return d1 < d2 ? p1 : p2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return p1 || p2 || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
||||||
arrow: NonDeleted<ExcalidrawArrowElement>,
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
draggingPoints: PointsPositionUpdates,
|
draggingPoints: PointsPositionUpdates,
|
||||||
@@ -926,16 +821,17 @@ const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { current, other } = bindingStrategyForSimpleArrowEndpointDragging(
|
const { current, other } =
|
||||||
globalPoint,
|
bindingStrategyForSimpleArrowEndpointDragging_complex(
|
||||||
arrow.startBinding,
|
globalPoint,
|
||||||
arrow.endBinding,
|
arrow.startBinding,
|
||||||
elementsMap,
|
arrow.endBinding,
|
||||||
elements,
|
elementsMap,
|
||||||
globalBindMode,
|
elements,
|
||||||
arrow,
|
globalBindMode,
|
||||||
opts?.finalize,
|
arrow,
|
||||||
);
|
opts?.finalize,
|
||||||
|
);
|
||||||
|
|
||||||
return { start: current, end: other };
|
return { start: current, end: other };
|
||||||
}
|
}
|
||||||
@@ -949,16 +845,17 @@ const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
|||||||
localPoint,
|
localPoint,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
const { current, other } = bindingStrategyForSimpleArrowEndpointDragging(
|
const { current, other } =
|
||||||
globalPoint,
|
bindingStrategyForSimpleArrowEndpointDragging_complex(
|
||||||
arrow.endBinding,
|
globalPoint,
|
||||||
arrow.startBinding,
|
arrow.endBinding,
|
||||||
elementsMap,
|
arrow.startBinding,
|
||||||
elements,
|
elementsMap,
|
||||||
globalBindMode,
|
elements,
|
||||||
arrow,
|
globalBindMode,
|
||||||
opts?.finalize,
|
arrow,
|
||||||
);
|
opts?.finalize,
|
||||||
|
);
|
||||||
|
|
||||||
return { start: other, end: current };
|
return { start: other, end: current };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
|
debugDrawLine,
|
||||||
DEFAULT_ADAPTIVE_RADIUS,
|
DEFAULT_ADAPTIVE_RADIUS,
|
||||||
DEFAULT_PROPORTIONAL_RADIUS,
|
DEFAULT_PROPORTIONAL_RADIUS,
|
||||||
|
invariant,
|
||||||
LINE_CONFIRM_THRESHOLD,
|
LINE_CONFIRM_THRESHOLD,
|
||||||
ROUNDNESS,
|
ROUNDNESS,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
@@ -10,10 +12,15 @@ import {
|
|||||||
curveCatmullRomCubicApproxPoints,
|
curveCatmullRomCubicApproxPoints,
|
||||||
curveOffsetPoints,
|
curveOffsetPoints,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
|
lineSegmentIntersectionPoints,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
|
pointFromVector,
|
||||||
|
pointRotateRads,
|
||||||
rectangle,
|
rectangle,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorScale,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
@@ -21,11 +28,16 @@ import type { Curve, LineSegment, LocalPoint } from "@excalidraw/math";
|
|||||||
|
|
||||||
import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
|
import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import { getDiamondPoints } from "./bounds";
|
import { elementCenterPoint, getDiamondPoints } from "./bounds";
|
||||||
|
|
||||||
import { generateLinearCollisionShape } from "./shape";
|
import { generateLinearCollisionShape } from "./shape";
|
||||||
|
|
||||||
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
import { isRectangularElement } from "./typeChecks";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ElementsMap,
|
||||||
|
ExcalidrawArrowElement,
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
@@ -471,3 +483,118 @@ export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDiagonalsForBindableElement = (
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
const center = elementCenterPoint(element, elementsMap);
|
||||||
|
const diagonalOne = isRectangularElement(element)
|
||||||
|
? lineSegment<GlobalPoint>(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x, element.y),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width,
|
||||||
|
element.y + element.height,
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: lineSegment<GlobalPoint>(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x + element.width / 2, element.y),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height,
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const diagonalTwo = isRectangularElement(element)
|
||||||
|
? lineSegment<GlobalPoint>(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x + element.width, element.y),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x, element.y + element.height),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: lineSegment<GlobalPoint>(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x, element.y + element.height / 2),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
element.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [diagonalOne, diagonalTwo];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const projectFixedPointOntoDiagonal = (
|
||||||
|
arrow: ExcalidrawArrowElement,
|
||||||
|
point: GlobalPoint,
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
const [diagonalOne, diagonalTwo] = getDiagonalsForBindableElement(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
invariant(arrow.points.length >= 2, "Arrow must have at least two points");
|
||||||
|
|
||||||
|
const a = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
startOrEnd === "start" ? 1 : arrow.points.length - 2,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
const b = pointFromVector<GlobalPoint>(
|
||||||
|
vectorScale(
|
||||||
|
vectorFromPoint(point, a),
|
||||||
|
2 * pointDistance(a, point) +
|
||||||
|
Math.max(
|
||||||
|
pointDistance(diagonalOne[0], diagonalOne[1]),
|
||||||
|
pointDistance(diagonalTwo[0], diagonalTwo[1]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
a,
|
||||||
|
);
|
||||||
|
const intersector = lineSegment<GlobalPoint>(a, b);
|
||||||
|
const p1 = lineSegmentIntersectionPoints(diagonalOne, intersector);
|
||||||
|
const p2 = lineSegmentIntersectionPoints(diagonalTwo, intersector);
|
||||||
|
const d1 = p1 && pointDistance(a, p1);
|
||||||
|
const d2 = p2 && pointDistance(a, p2);
|
||||||
|
|
||||||
|
debugDrawLine(diagonalOne, { color: "purple" });
|
||||||
|
debugDrawLine(diagonalTwo, { color: "purple" });
|
||||||
|
debugDrawLine(intersector, { color: "orange" });
|
||||||
|
|
||||||
|
if (d1 != null && d2 != null) {
|
||||||
|
return d1 < d2 ? p1 : p2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p1 || p2 || null;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user