123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- import { ExcalidrawElement, PointerType } from "./types";
- import { getElementAbsoluteCoords, Bounds } from "./bounds";
- import { rotate } from "../math";
- import { Zoom } from "../types";
- export type TransformHandleDirection =
- | "n"
- | "s"
- | "w"
- | "e"
- | "nw"
- | "ne"
- | "sw"
- | "se";
- export type TransformHandleType = TransformHandleDirection | "rotation";
- export type TransformHandle = [number, number, number, number];
- export type TransformHandles = Partial<
- { [T in TransformHandleType]: TransformHandle }
- >;
- export type MaybeTransformHandleType = TransformHandleType | false;
- const transformHandleSizes: { [k in PointerType]: number } = {
- mouse: 8,
- pen: 16,
- touch: 28,
- };
- const ROTATION_RESIZE_HANDLE_GAP = 16;
- export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
- e: true,
- s: true,
- n: true,
- w: true,
- };
- const OMIT_SIDES_FOR_TEXT_ELEMENT = {
- e: true,
- s: true,
- n: true,
- w: true,
- };
- const OMIT_SIDES_FOR_LINE_SLASH = {
- e: true,
- s: true,
- n: true,
- w: true,
- nw: true,
- se: true,
- };
- const OMIT_SIDES_FOR_LINE_BACKSLASH = {
- e: true,
- s: true,
- n: true,
- w: true,
- ne: true,
- sw: true,
- };
- const generateTransformHandle = (
- x: number,
- y: number,
- width: number,
- height: number,
- cx: number,
- cy: number,
- angle: number,
- ): TransformHandle => {
- const [xx, yy] = rotate(x + width / 2, y + height / 2, cx, cy, angle);
- return [xx - width / 2, yy - height / 2, width, height];
- };
- export const getTransformHandlesFromCoords = (
- [x1, y1, x2, y2]: Bounds,
- angle: number,
- zoom: Zoom,
- pointerType: PointerType,
- omitSides: { [T in TransformHandleType]?: boolean } = {},
- ): TransformHandles => {
- const size = transformHandleSizes[pointerType];
- const handleWidth = size / zoom.value;
- const handleHeight = size / zoom.value;
- const handleMarginX = size / zoom.value;
- const handleMarginY = size / zoom.value;
- const width = x2 - x1;
- const height = y2 - y1;
- const cx = (x1 + x2) / 2;
- const cy = (y1 + y2) / 2;
- const dashedLineMargin = 4 / zoom.value;
- const centeringOffset = (size - 8) / (2 * zoom.value);
- const transformHandles: TransformHandles = {
- nw: omitSides.nw
- ? undefined
- : generateTransformHandle(
- x1 - dashedLineMargin - handleMarginX + centeringOffset,
- y1 - dashedLineMargin - handleMarginY + centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- ),
- ne: omitSides.ne
- ? undefined
- : generateTransformHandle(
- x2 + dashedLineMargin - centeringOffset,
- y1 - dashedLineMargin - handleMarginY + centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- ),
- sw: omitSides.sw
- ? undefined
- : generateTransformHandle(
- x1 - dashedLineMargin - handleMarginX + centeringOffset,
- y2 + dashedLineMargin - centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- ),
- se: omitSides.se
- ? undefined
- : generateTransformHandle(
- x2 + dashedLineMargin - centeringOffset,
- y2 + dashedLineMargin - centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- ),
- rotation: omitSides.rotation
- ? undefined
- : generateTransformHandle(
- x1 + width / 2 - handleWidth / 2,
- y1 -
- dashedLineMargin -
- handleMarginY +
- centeringOffset -
- ROTATION_RESIZE_HANDLE_GAP / zoom.value,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- ),
- };
- // We only want to show height handles (all cardinal directions) above a certain size
- // Note: we render using "mouse" size so we should also use "mouse" size for this check
- const minimumSizeForEightHandles =
- (5 * transformHandleSizes.mouse) / zoom.value;
- if (Math.abs(width) > minimumSizeForEightHandles) {
- if (!omitSides.n) {
- transformHandles.n = generateTransformHandle(
- x1 + width / 2 - handleWidth / 2,
- y1 - dashedLineMargin - handleMarginY + centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- );
- }
- if (!omitSides.s) {
- transformHandles.s = generateTransformHandle(
- x1 + width / 2 - handleWidth / 2,
- y2 + dashedLineMargin - centeringOffset,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- );
- }
- }
- if (Math.abs(height) > minimumSizeForEightHandles) {
- if (!omitSides.w) {
- transformHandles.w = generateTransformHandle(
- x1 - dashedLineMargin - handleMarginX + centeringOffset,
- y1 + height / 2 - handleHeight / 2,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- );
- }
- if (!omitSides.e) {
- transformHandles.e = generateTransformHandle(
- x2 + dashedLineMargin - centeringOffset,
- y1 + height / 2 - handleHeight / 2,
- handleWidth,
- handleHeight,
- cx,
- cy,
- angle,
- );
- }
- }
- return transformHandles;
- };
- export const getTransformHandles = (
- element: ExcalidrawElement,
- zoom: Zoom,
- pointerType: PointerType = "mouse",
- ): TransformHandles => {
- let omitSides: { [T in TransformHandleType]?: boolean } = {};
- if (
- element.type === "arrow" ||
- element.type === "line" ||
- element.type === "draw"
- ) {
- if (element.points.length === 2) {
- // only check the last point because starting point is always (0,0)
- const [, p1] = element.points;
- if (p1[0] === 0 || p1[1] === 0) {
- omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
- } else if (p1[0] > 0 && p1[1] < 0) {
- omitSides = OMIT_SIDES_FOR_LINE_SLASH;
- } else if (p1[0] > 0 && p1[1] > 0) {
- omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
- } else if (p1[0] < 0 && p1[1] > 0) {
- omitSides = OMIT_SIDES_FOR_LINE_SLASH;
- } else if (p1[0] < 0 && p1[1] < 0) {
- omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH;
- }
- }
- } else if (element.type === "text") {
- omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
- }
- return getTransformHandlesFromCoords(
- getElementAbsoluteCoords(element),
- element.angle,
- zoom,
- pointerType,
- omitSides,
- );
- };
|