You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.8 KiB
TypeScript
207 lines
5.8 KiB
TypeScript
5 years ago
|
import { updateBoundElements } from "./binding";
|
||
1 year ago
|
import { Bounds, getCommonBounds } from "./bounds";
|
||
5 years ago
|
import { mutateElement } from "./mutateElement";
|
||
|
import { getPerfectElementSize } from "./sizeHelpers";
|
||
5 years ago
|
import { NonDeletedExcalidrawElement } from "./types";
|
||
3 years ago
|
import { AppState, PointerDownState } from "../types";
|
||
3 years ago
|
import { getBoundTextElement } from "./textElement";
|
||
3 years ago
|
import { isSelectedViaGroup } from "../groups";
|
||
1 year ago
|
import { getGridPoint } from "../math";
|
||
2 years ago
|
import Scene from "../scene/Scene";
|
||
1 year ago
|
import {
|
||
|
isArrowElement,
|
||
|
isBoundToContainer,
|
||
1 year ago
|
isFrameLikeElement,
|
||
1 year ago
|
} from "./typeChecks";
|
||
5 years ago
|
|
||
|
export const dragSelectedElements = (
|
||
4 years ago
|
pointerDownState: PointerDownState,
|
||
5 years ago
|
selectedElements: NonDeletedExcalidrawElement[],
|
||
1 year ago
|
offset: { x: number; y: number },
|
||
3 years ago
|
appState: AppState,
|
||
2 years ago
|
scene: Scene,
|
||
1 year ago
|
snapOffset: {
|
||
|
x: number;
|
||
|
y: number;
|
||
|
},
|
||
|
gridSize: AppState["gridSize"],
|
||
5 years ago
|
) => {
|
||
2 years ago
|
// we do not want a frame and its elements to be selected at the same time
|
||
|
// but when it happens (due to some bug), we want to avoid updating element
|
||
|
// in the frame twice, hence the use of set
|
||
|
const elementsToUpdate = new Set<NonDeletedExcalidrawElement>(
|
||
|
selectedElements,
|
||
|
);
|
||
|
const frames = selectedElements
|
||
1 year ago
|
.filter((e) => isFrameLikeElement(e))
|
||
2 years ago
|
.map((f) => f.id);
|
||
|
|
||
|
if (frames.length > 0) {
|
||
|
const elementsInFrames = scene
|
||
|
.getNonDeletedElements()
|
||
1 year ago
|
.filter((e) => !isBoundToContainer(e))
|
||
2 years ago
|
.filter((e) => e.frameId !== null)
|
||
|
.filter((e) => frames.includes(e.frameId!));
|
||
|
|
||
|
elementsInFrames.forEach((element) => elementsToUpdate.add(element));
|
||
|
}
|
||
|
|
||
1 year ago
|
const commonBounds = getCommonBounds(
|
||
|
Array.from(elementsToUpdate).map(
|
||
|
(el) => pointerDownState.originalElements.get(el.id) ?? el,
|
||
|
),
|
||
|
);
|
||
|
const adjustedOffset = calculateOffset(
|
||
|
commonBounds,
|
||
|
offset,
|
||
|
snapOffset,
|
||
|
gridSize,
|
||
|
);
|
||
|
|
||
2 years ago
|
elementsToUpdate.forEach((element) => {
|
||
1 year ago
|
updateElementCoords(pointerDownState, element, adjustedOffset);
|
||
3 years ago
|
// update coords of bound text only if we're dragging the container directly
|
||
|
// (we don't drag the group that it's part of)
|
||
|
if (
|
||
1 year ago
|
// Don't update coords of arrow label since we calculate its position during render
|
||
|
!isArrowElement(element) &&
|
||
3 years ago
|
// container isn't part of any group
|
||
|
// (perf optim so we don't check `isSelectedViaGroup()` in every case)
|
||
1 year ago
|
(!element.groupIds.length ||
|
||
|
// container is part of a group, but we're dragging the container directly
|
||
|
(appState.editingGroupId && !isSelectedViaGroup(appState, element)))
|
||
3 years ago
|
) {
|
||
3 years ago
|
const textElement = getBoundTextElement(element);
|
||
1 year ago
|
if (textElement) {
|
||
1 year ago
|
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||
3 years ago
|
}
|
||
4 years ago
|
}
|
||
|
updateBoundElements(element, {
|
||
2 years ago
|
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
||
5 years ago
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
1 year ago
|
const calculateOffset = (
|
||
|
commonBounds: Bounds,
|
||
1 year ago
|
dragOffset: { x: number; y: number },
|
||
|
snapOffset: { x: number; y: number },
|
||
|
gridSize: AppState["gridSize"],
|
||
1 year ago
|
): { x: number; y: number } => {
|
||
|
const [x, y] = commonBounds;
|
||
|
let nextX = x + dragOffset.x + snapOffset.x;
|
||
|
let nextY = y + dragOffset.y + snapOffset.y;
|
||
1 year ago
|
|
||
|
if (snapOffset.x === 0 || snapOffset.y === 0) {
|
||
|
const [nextGridX, nextGridY] = getGridPoint(
|
||
1 year ago
|
x + dragOffset.x,
|
||
|
y + dragOffset.y,
|
||
1 year ago
|
gridSize,
|
||
|
);
|
||
|
|
||
|
if (snapOffset.x === 0) {
|
||
|
nextX = nextGridX;
|
||
|
}
|
||
|
|
||
|
if (snapOffset.y === 0) {
|
||
|
nextY = nextGridY;
|
||
|
}
|
||
3 years ago
|
}
|
||
1 year ago
|
return {
|
||
|
x: nextX - x,
|
||
|
y: nextY - y,
|
||
|
};
|
||
|
};
|
||
|
|
||
|
const updateElementCoords = (
|
||
|
pointerDownState: PointerDownState,
|
||
|
element: NonDeletedExcalidrawElement,
|
||
|
dragOffset: { x: number; y: number },
|
||
|
) => {
|
||
|
const originalElement =
|
||
|
pointerDownState.originalElements.get(element.id) ?? element;
|
||
|
|
||
|
const nextX = originalElement.x + dragOffset.x;
|
||
|
const nextY = originalElement.y + dragOffset.y;
|
||
3 years ago
|
|
||
|
mutateElement(element, {
|
||
1 year ago
|
x: nextX,
|
||
|
y: nextY,
|
||
3 years ago
|
});
|
||
|
};
|
||
1 year ago
|
|
||
5 years ago
|
export const getDragOffsetXY = (
|
||
|
selectedElements: NonDeletedExcalidrawElement[],
|
||
|
x: number,
|
||
|
y: number,
|
||
|
): [number, number] => {
|
||
|
const [x1, y1] = getCommonBounds(selectedElements);
|
||
|
return [x - x1, y - y1];
|
||
|
};
|
||
|
|
||
|
export const dragNewElement = (
|
||
|
draggingElement: NonDeletedExcalidrawElement,
|
||
3 years ago
|
elementType: AppState["activeTool"]["type"],
|
||
5 years ago
|
originX: number,
|
||
|
originY: number,
|
||
|
x: number,
|
||
|
y: number,
|
||
|
width: number,
|
||
|
height: number,
|
||
3 years ago
|
shouldMaintainAspectRatio: boolean,
|
||
|
shouldResizeFromCenter: boolean,
|
||
|
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
||
|
true */
|
||
|
widthAspectRatio?: number | null,
|
||
1 year ago
|
originOffset: {
|
||
|
x: number;
|
||
|
y: number;
|
||
|
} | null = null,
|
||
5 years ago
|
) => {
|
||
3 years ago
|
if (shouldMaintainAspectRatio && draggingElement.type !== "selection") {
|
||
3 years ago
|
if (widthAspectRatio) {
|
||
|
height = width / widthAspectRatio;
|
||
|
} else {
|
||
3 years ago
|
// Depending on where the cursor is at (x, y) relative to where the starting point is
|
||
|
// (originX, originY), we use ONLY width or height to control size increase.
|
||
|
// This allows the cursor to always "stick" to one of the sides of the bounding box.
|
||
|
if (Math.abs(y - originY) > Math.abs(x - originX)) {
|
||
|
({ width, height } = getPerfectElementSize(
|
||
|
elementType,
|
||
|
height,
|
||
|
x < originX ? -width : width,
|
||
|
));
|
||
|
} else {
|
||
|
({ width, height } = getPerfectElementSize(
|
||
|
elementType,
|
||
|
width,
|
||
|
y < originY ? -height : height,
|
||
|
));
|
||
|
}
|
||
5 years ago
|
|
||
3 years ago
|
if (height < 0) {
|
||
|
height = -height;
|
||
|
}
|
||
5 years ago
|
}
|
||
|
}
|
||
|
|
||
|
let newX = x < originX ? originX - width : originX;
|
||
|
let newY = y < originY ? originY - height : originY;
|
||
|
|
||
3 years ago
|
if (shouldResizeFromCenter) {
|
||
5 years ago
|
width += width;
|
||
|
height += height;
|
||
|
newX = originX - width / 2;
|
||
|
newY = originY - height / 2;
|
||
|
}
|
||
|
|
||
|
if (width !== 0 && height !== 0) {
|
||
|
mutateElement(draggingElement, {
|
||
1 year ago
|
x: newX + (originOffset?.x ?? 0),
|
||
|
y: newY + (originOffset?.y ?? 0),
|
||
4 years ago
|
width,
|
||
|
height,
|
||
5 years ago
|
});
|
||
|
}
|
||
|
};
|