mirror of
https://github.com/excalidraw/excalidraw
synced 2025-07-25 13:58:22 +08:00
Fix fixed angle orbiting
This commit is contained in:
@ -15,7 +15,7 @@ import {
|
||||
PRECISION,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
@ -839,6 +839,7 @@ export const bindPointToSnapToElementOutline = (
|
||||
bindableElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
customIntersector?: LineSegment<GlobalPoint>,
|
||||
): GlobalPoint => {
|
||||
const aabb = aabbForElement(bindableElement, elementsMap);
|
||||
const localP =
|
||||
@ -881,16 +882,18 @@ export const bindPointToSnapToElementOutline = (
|
||||
isHorizontal ? center[0] : snapPoint[0],
|
||||
!isHorizontal ? center[1] : snapPoint[1],
|
||||
);
|
||||
const intersector = lineSegment(
|
||||
otherPoint,
|
||||
pointFromVector(
|
||||
vectorScale(
|
||||
vectorNormalize(vectorFromPoint(snapPoint, otherPoint)),
|
||||
Math.max(bindableElement.width, bindableElement.height) * 2,
|
||||
),
|
||||
const intersector =
|
||||
customIntersector ??
|
||||
lineSegment(
|
||||
otherPoint,
|
||||
),
|
||||
);
|
||||
pointFromVector(
|
||||
vectorScale(
|
||||
vectorNormalize(vectorFromPoint(snapPoint, otherPoint)),
|
||||
Math.max(bindableElement.width, bindableElement.height) * 2,
|
||||
),
|
||||
otherPoint,
|
||||
),
|
||||
);
|
||||
intersection = intersectElementWithLineSegment(
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
@ -898,9 +901,8 @@ export const bindPointToSnapToElementOutline = (
|
||||
FIXED_BINDING_DISTANCE,
|
||||
).sort(pointDistanceSq)[0];
|
||||
} else {
|
||||
intersection = intersectElementWithLineSegment(
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
const intersector =
|
||||
customIntersector ??
|
||||
lineSegment(
|
||||
adjacentPoint,
|
||||
pointFromVector(
|
||||
@ -911,7 +913,11 @@ export const bindPointToSnapToElementOutline = (
|
||||
),
|
||||
adjacentPoint,
|
||||
),
|
||||
),
|
||||
);
|
||||
intersection = intersectElementWithLineSegment(
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
intersector,
|
||||
FIXED_BINDING_DISTANCE,
|
||||
).sort(
|
||||
(g, h) =>
|
||||
@ -936,6 +942,7 @@ export const getOutlineAvoidingPoint = (
|
||||
coords: GlobalPoint,
|
||||
pointIndex: number,
|
||||
elementsMap: ElementsMap,
|
||||
customIntersector?: LineSegment<GlobalPoint>,
|
||||
): GlobalPoint => {
|
||||
if (hoveredElement) {
|
||||
const newPoints = Array.from(element.points);
|
||||
@ -952,6 +959,7 @@ export const getOutlineAvoidingPoint = (
|
||||
hoveredElement,
|
||||
pointIndex === 0 ? "start" : "end",
|
||||
elementsMap,
|
||||
customIntersector,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
vectorFromPoint,
|
||||
curveLength,
|
||||
curvePointAtLength,
|
||||
lineSegment,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
||||
@ -322,6 +323,8 @@ export class LinearElementEditor {
|
||||
const draggingPoint = element.points[lastClickedPoint];
|
||||
|
||||
if (selectedPointsIndices && draggingPoint) {
|
||||
const elements = app.scene.getNonDeletedElements();
|
||||
|
||||
if (
|
||||
shouldRotateWithDiscreteAngle(event) &&
|
||||
selectedPointsIndices.length === 1 &&
|
||||
@ -336,7 +339,6 @@ export class LinearElementEditor {
|
||||
element.points[selectedIndex][1] - referencePoint[1],
|
||||
element.points[selectedIndex][0] - referencePoint[0],
|
||||
);
|
||||
|
||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||
element,
|
||||
elementsMap,
|
||||
@ -345,22 +347,32 @@ export class LinearElementEditor {
|
||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||
customLineAngle,
|
||||
);
|
||||
|
||||
const [x, y] = LinearElementEditor.getPointGlobalCoordinates(
|
||||
element,
|
||||
pointFrom<LocalPoint>(
|
||||
width + referencePoint[0],
|
||||
height + referencePoint[1],
|
||||
),
|
||||
elementsMap,
|
||||
);
|
||||
LinearElementEditor.movePoints(
|
||||
element,
|
||||
app.scene,
|
||||
new Map([
|
||||
[
|
||||
selectedIndex,
|
||||
{
|
||||
point: pointFrom(
|
||||
width + referencePoint[0],
|
||||
height + referencePoint[1],
|
||||
),
|
||||
isDragging: selectedIndex === lastClickedPoint,
|
||||
},
|
||||
],
|
||||
]),
|
||||
pointDraggingUpdates(
|
||||
selectedPointsIndices,
|
||||
0,
|
||||
0,
|
||||
elementsMap,
|
||||
lastClickedPoint,
|
||||
element,
|
||||
x,
|
||||
y,
|
||||
linearElementEditor,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||
elements,
|
||||
app,
|
||||
true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||
@ -372,7 +384,6 @@ export class LinearElementEditor {
|
||||
);
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
const deltaY = newDraggingPointPosition[1] - draggingPoint[1];
|
||||
const elements = app.scene.getNonDeletedElements();
|
||||
|
||||
LinearElementEditor.movePoints(
|
||||
element,
|
||||
@ -995,7 +1006,6 @@ export class LinearElementEditor {
|
||||
|
||||
if (shouldRotateWithDiscreteAngle(event) && points.length >= 2) {
|
||||
const lastCommittedPoint = points[points.length - 2];
|
||||
|
||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||
element,
|
||||
elementsMap,
|
||||
@ -1937,6 +1947,7 @@ const pointDraggingUpdates = (
|
||||
gridSize: NullableGridSize,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
app: AppClassProperties,
|
||||
angleLocked?: boolean,
|
||||
): PointsPositionUpdates => {
|
||||
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap, true);
|
||||
const hasMidPoints =
|
||||
@ -2001,12 +2012,29 @@ const pointDraggingUpdates = (
|
||||
app.state.bindMode === "orbit" &&
|
||||
!otherPointInsideElement
|
||||
) {
|
||||
let customIntersector;
|
||||
if (angleLocked) {
|
||||
const adjacentPointIndex =
|
||||
pointIndex === 0 ? 1 : element.points.length - 2;
|
||||
const globalAdjacentPoint =
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
element,
|
||||
adjacentPointIndex,
|
||||
elementsMap,
|
||||
);
|
||||
customIntersector = lineSegment<GlobalPoint>(
|
||||
globalAdjacentPoint,
|
||||
newGlobalPointPosition,
|
||||
);
|
||||
}
|
||||
|
||||
newGlobalPointPosition = getOutlineAvoidingPoint(
|
||||
element,
|
||||
hoveredElement,
|
||||
newGlobalPointPosition,
|
||||
pointIndex,
|
||||
elementsMap,
|
||||
customIntersector,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
vectorDot,
|
||||
vectorNormalize,
|
||||
pointsEqual,
|
||||
lineSegment,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import {
|
||||
@ -6132,11 +6133,29 @@ class App extends React.Component<AppProps, AppState> {
|
||||
{ informMutation: false, isDragging: false },
|
||||
);
|
||||
} else {
|
||||
let [gridX, gridY] = getGridPoint(
|
||||
const [lastCommittedX, lastCommittedY] =
|
||||
multiElement?.lastCommittedPoint ?? [0, 0];
|
||||
|
||||
// Handle grid snapping
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
||||
);
|
||||
let dxFromLastCommitted = gridX - rx - lastCommittedX;
|
||||
let dyFromLastCommitted = gridY - ry - lastCommittedY;
|
||||
|
||||
if (shouldRotateWithDiscreteAngle(event)) {
|
||||
({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
|
||||
getLockedLinearCursorAlignSize(
|
||||
// actual coordinate of the last committed point
|
||||
lastCommittedX + rx,
|
||||
lastCommittedY + ry,
|
||||
// cursor-grid coordinate
|
||||
gridX,
|
||||
gridY,
|
||||
));
|
||||
}
|
||||
|
||||
if (
|
||||
isArrowElement(multiElement) &&
|
||||
@ -6172,38 +6191,31 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
||||
multiElement.points.length - 1,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
shouldRotateWithDiscreteAngle(event)
|
||||
? lineSegment<GlobalPoint>(
|
||||
otherPoint,
|
||||
pointFrom<GlobalPoint>(
|
||||
multiElement.x + lastCommittedX + dxFromLastCommitted,
|
||||
multiElement.y + lastCommittedY + dyFromLastCommitted,
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
);
|
||||
gridX = avoidancePoint
|
||||
const x = avoidancePoint
|
||||
? avoidancePoint[0]
|
||||
: hoveredElement
|
||||
? scenePointerX
|
||||
: gridX;
|
||||
gridY = avoidancePoint
|
||||
const y = avoidancePoint
|
||||
? avoidancePoint[1]
|
||||
: hoveredElement
|
||||
? scenePointerY
|
||||
: gridY;
|
||||
dxFromLastCommitted = x - rx - lastCommittedX;
|
||||
dyFromLastCommitted = y - ry - lastCommittedY;
|
||||
}
|
||||
}
|
||||
|
||||
const [lastCommittedX, lastCommittedY] =
|
||||
multiElement?.lastCommittedPoint ?? [0, 0];
|
||||
|
||||
let dxFromLastCommitted = gridX - rx - lastCommittedX;
|
||||
let dyFromLastCommitted = gridY - ry - lastCommittedY;
|
||||
|
||||
if (shouldRotateWithDiscreteAngle(event)) {
|
||||
({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
|
||||
getLockedLinearCursorAlignSize(
|
||||
// actual coordinate of the last committed point
|
||||
lastCommittedX + rx,
|
||||
lastCommittedY + ry,
|
||||
// cursor-grid coordinate
|
||||
gridX,
|
||||
gridY,
|
||||
));
|
||||
}
|
||||
|
||||
if (isPathALoop(points, this.state.zoom.value)) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
}
|
||||
@ -9089,6 +9101,15 @@ class App extends React.Component<AppProps, AppState> {
|
||||
let dx = gridX - newElement.x;
|
||||
let dy = gridY - newElement.y;
|
||||
|
||||
if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
|
||||
({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
|
||||
newElement.x,
|
||||
newElement.y,
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
));
|
||||
}
|
||||
|
||||
if (
|
||||
!isElbowArrow(newElement) &&
|
||||
this.state.editingLinearElement &&
|
||||
@ -9119,6 +9140,20 @@ class App extends React.Component<AppProps, AppState> {
|
||||
: pointFrom(gridX, gridY),
|
||||
newElement.points.length - 1,
|
||||
elementsMap,
|
||||
shouldRotateWithDiscreteAngle(event) &&
|
||||
points.length === 2
|
||||
? lineSegment(
|
||||
LinearElementEditor.getPointGlobalCoordinates(
|
||||
newElement,
|
||||
points[0],
|
||||
elementsMap,
|
||||
),
|
||||
pointFrom<GlobalPoint>(
|
||||
newElement.x + dx,
|
||||
newElement.y + dy,
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
)
|
||||
: pointFrom(gridX, gridY);
|
||||
|
||||
@ -9176,15 +9211,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
|
||||
({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
|
||||
newElement.x,
|
||||
newElement.y,
|
||||
pointerCoords.x,
|
||||
pointerCoords.y,
|
||||
));
|
||||
}
|
||||
|
||||
if (points.length === 1) {
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
|
Reference in New Issue
Block a user