mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: resize elements from the sides (#7855)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
6e5aeb112d
commit
88812e0386
19 changed files with 3913 additions and 3741 deletions
|
@ -6,15 +6,24 @@ import {
|
|||
} from "./types";
|
||||
|
||||
import {
|
||||
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||
getTransformHandlesFromCoords,
|
||||
getTransformHandles,
|
||||
TransformHandleType,
|
||||
TransformHandle,
|
||||
MaybeTransformHandleType,
|
||||
getOmitSidesForDevice,
|
||||
canResizeFromSides,
|
||||
} from "./transformHandles";
|
||||
import { AppState, Zoom } from "../types";
|
||||
import { Bounds } from "./bounds";
|
||||
import { AppState, Device, Zoom } from "../types";
|
||||
import { Bounds, getElementAbsoluteCoords } from "./bounds";
|
||||
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
||||
import {
|
||||
angleToDegrees,
|
||||
pointOnLine,
|
||||
pointRotate,
|
||||
} from "../../utils/geometry/geometry";
|
||||
import { Line, Point } from "../../utils/geometry/shape";
|
||||
import { isLinearElement } from "./typeChecks";
|
||||
|
||||
const isInsideTransformHandle = (
|
||||
transformHandle: TransformHandle,
|
||||
|
@ -34,13 +43,20 @@ export const resizeTest = (
|
|||
y: number,
|
||||
zoom: Zoom,
|
||||
pointerType: PointerType,
|
||||
device: Device,
|
||||
): MaybeTransformHandleType => {
|
||||
if (!appState.selectedElementIds[element.id]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { rotation: rotationTransformHandle, ...transformHandles } =
|
||||
getTransformHandles(element, zoom, elementsMap, pointerType);
|
||||
getTransformHandles(
|
||||
element,
|
||||
zoom,
|
||||
elementsMap,
|
||||
pointerType,
|
||||
getOmitSidesForDevice(device),
|
||||
);
|
||||
|
||||
if (
|
||||
rotationTransformHandle &&
|
||||
|
@ -62,6 +78,35 @@ export const resizeTest = (
|
|||
return filter[0] as TransformHandleType;
|
||||
}
|
||||
|
||||
if (canResizeFromSides(device)) {
|
||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
// Note that for a text element, when "resized" from the side
|
||||
// we should make it wrap/unwrap
|
||||
if (
|
||||
element.type !== "text" &&
|
||||
!(isLinearElement(element) && element.points.length <= 2)
|
||||
) {
|
||||
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||
const sides = getSelectionBorders(
|
||||
[x1 - SPACING, y1 - SPACING],
|
||||
[x2 + SPACING, y2 + SPACING],
|
||||
[cx, cy],
|
||||
angleToDegrees(element.angle),
|
||||
);
|
||||
|
||||
for (const [dir, side] of Object.entries(sides)) {
|
||||
// test to see if x, y are on the line segment
|
||||
if (pointOnLine([x, y], side as Line, SPACING)) {
|
||||
return dir as TransformHandleType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -73,6 +118,7 @@ export const getElementWithTransformHandleType = (
|
|||
zoom: Zoom,
|
||||
pointerType: PointerType,
|
||||
elementsMap: ElementsMap,
|
||||
device: Device,
|
||||
) => {
|
||||
return elements.reduce((result, element) => {
|
||||
if (result) {
|
||||
|
@ -86,6 +132,7 @@ export const getElementWithTransformHandleType = (
|
|||
scenePointerY,
|
||||
zoom,
|
||||
pointerType,
|
||||
device,
|
||||
);
|
||||
return transformHandleType ? { element, transformHandleType } : null;
|
||||
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
||||
|
@ -97,13 +144,14 @@ export const getTransformHandleTypeFromCoords = (
|
|||
scenePointerY: number,
|
||||
zoom: Zoom,
|
||||
pointerType: PointerType,
|
||||
device: Device,
|
||||
): MaybeTransformHandleType => {
|
||||
const transformHandles = getTransformHandlesFromCoords(
|
||||
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
|
||||
0,
|
||||
zoom,
|
||||
pointerType,
|
||||
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||
getOmitSidesForDevice(device),
|
||||
);
|
||||
|
||||
const found = Object.keys(transformHandles).find((key) => {
|
||||
|
@ -114,7 +162,33 @@ export const getTransformHandleTypeFromCoords = (
|
|||
isInsideTransformHandle(transformHandle, scenePointerX, scenePointerY)
|
||||
);
|
||||
});
|
||||
return (found || false) as MaybeTransformHandleType;
|
||||
|
||||
if (found) {
|
||||
return found as MaybeTransformHandleType;
|
||||
}
|
||||
|
||||
if (canResizeFromSides(device)) {
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
|
||||
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||
|
||||
const sides = getSelectionBorders(
|
||||
[x1 - SPACING, y1 - SPACING],
|
||||
[x2 + SPACING, y2 + SPACING],
|
||||
[cx, cy],
|
||||
angleToDegrees(0),
|
||||
);
|
||||
|
||||
for (const [dir, side] of Object.entries(sides)) {
|
||||
// test to see if x, y are on the line segment
|
||||
if (pointOnLine([scenePointerX, scenePointerY], side as Line, SPACING)) {
|
||||
return dir as TransformHandleType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"];
|
||||
|
@ -174,3 +248,22 @@ export const getCursorForResizingElement = (resizingElement: {
|
|||
|
||||
return cursor ? `${cursor}-resize` : "";
|
||||
};
|
||||
|
||||
const getSelectionBorders = (
|
||||
[x1, y1]: Point,
|
||||
[x2, y2]: Point,
|
||||
center: Point,
|
||||
angleInDegrees: number,
|
||||
) => {
|
||||
const topLeft = pointRotate([x1, y1], angleInDegrees, center);
|
||||
const topRight = pointRotate([x2, y1], angleInDegrees, center);
|
||||
const bottomLeft = pointRotate([x1, y2], angleInDegrees, center);
|
||||
const bottomRight = pointRotate([x2, y2], angleInDegrees, center);
|
||||
|
||||
return {
|
||||
n: [topLeft, topRight],
|
||||
e: [topRight, bottomRight],
|
||||
s: [bottomRight, bottomLeft],
|
||||
w: [bottomLeft, topLeft],
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue