Resize multiple elements (rectangles, diamonds and ellipses onl… (#1193)
* experiment resizing multiple elements * hack common component * calculate scale properly (still se only)fg * prioritize multi selection * take handle offset into calculation * fix master merge * refactor resizeElements out from App * wip: handlerRectanglesFromCoords * fix test with type assertion * properly show handles wip * revert previous one and do a tweak * remove unnecessary assignments * replace hack code with good one * refactor coords in arg * resize NW * resize from sw,ne * fix with setResizeHandle * do not show hint while resizing multiple elements * empty commit * fix format
This commit is contained in:
parent
b60f5fcf06
commit
2cc1105ff5
@ -4,14 +4,12 @@ import socketIOClient from "socket.io-client";
|
|||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import { FlooredNumber } from "../types";
|
import { FlooredNumber } from "../types";
|
||||||
import { getElementAbsoluteCoords } from "../element/bounds";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
newElement,
|
newElement,
|
||||||
newTextElement,
|
newTextElement,
|
||||||
duplicateElement,
|
duplicateElement,
|
||||||
resizeTest,
|
resizeTest,
|
||||||
normalizeResizeHandle,
|
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
textWysiwyg,
|
textWysiwyg,
|
||||||
@ -24,6 +22,11 @@ import {
|
|||||||
getSyncableElements,
|
getSyncableElements,
|
||||||
hasNonDeletedElements,
|
hasNonDeletedElements,
|
||||||
newLinearElement,
|
newLinearElement,
|
||||||
|
ResizeArrowFnType,
|
||||||
|
resizeElements,
|
||||||
|
getElementWithResizeHandler,
|
||||||
|
canResizeMutlipleElements,
|
||||||
|
getResizeHandlerFromCoords,
|
||||||
} from "../element";
|
} from "../element";
|
||||||
import {
|
import {
|
||||||
deleteSelectedElements,
|
deleteSelectedElements,
|
||||||
@ -50,12 +53,7 @@ import {
|
|||||||
|
|
||||||
import { renderScene } from "../renderer";
|
import { renderScene } from "../renderer";
|
||||||
import { AppState, GestureEvent, Gesture } from "../types";
|
import { AppState, GestureEvent, Gesture } from "../types";
|
||||||
import {
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawLinearElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
} from "../element/types";
|
|
||||||
import { rotate, adjustXYWithRotation } from "../math";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isWritableElement,
|
isWritableElement,
|
||||||
@ -76,7 +74,6 @@ import { createHistory, SceneHistory } from "../history";
|
|||||||
|
|
||||||
import ContextMenu from "./ContextMenu";
|
import ContextMenu from "./ContextMenu";
|
||||||
|
|
||||||
import { getElementWithResizeHandler } from "../element/resizeTest";
|
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import "../actions";
|
import "../actions";
|
||||||
import { actions } from "../actions/register";
|
import { actions } from "../actions/register";
|
||||||
@ -102,7 +99,6 @@ import {
|
|||||||
DRAGGING_THRESHOLD,
|
DRAGGING_THRESHOLD,
|
||||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||||
ARROW_CONFIRM_THRESHOLD,
|
ARROW_CONFIRM_THRESHOLD,
|
||||||
SHIFT_LOCKING_ANGLE,
|
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { LayerUI } from "./LayerUI";
|
import { LayerUI } from "./LayerUI";
|
||||||
import { ScrollBars, SceneState } from "../scene/types";
|
import { ScrollBars, SceneState } from "../scene/types";
|
||||||
@ -112,7 +108,6 @@ import { invalidateShapeForElement } from "../renderer/renderElement";
|
|||||||
import { unstable_batchedUpdates } from "react-dom";
|
import { unstable_batchedUpdates } from "react-dom";
|
||||||
import { SceneStateCallbackRemover } from "../scene/globalScene";
|
import { SceneStateCallbackRemover } from "../scene/globalScene";
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
import { rescalePoints } from "../points";
|
|
||||||
import { actionFinalize } from "../actions";
|
import { actionFinalize } from "../actions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -929,7 +924,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.socket.on("new-user", async (socketID: string) => {
|
this.socket.on("new-user", async (_socketID: string) => {
|
||||||
this.broadcastScene("SCENE_INIT");
|
this.broadcastScene("SCENE_INIT");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1485,19 +1480,34 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
if (selectedElements.length === 1 && !isOverScrollBar) {
|
if (selectedElements.length === 1 && !isOverScrollBar) {
|
||||||
const resizeElement = getElementWithResizeHandler(
|
const elementWithResizeHandler = getElementWithResizeHandler(
|
||||||
globalSceneState.getAllElements(),
|
globalSceneState.getAllElements(),
|
||||||
this.state,
|
this.state,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
if (resizeElement && resizeElement.resizeHandle) {
|
if (elementWithResizeHandler && elementWithResizeHandler.resizeHandle) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
resizeElement,
|
elementWithResizeHandler,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (selectedElements.length > 1 && !isOverScrollBar) {
|
||||||
|
if (canResizeMutlipleElements(selectedElements)) {
|
||||||
|
const resizeHandle = getResizeHandlerFromCoords(
|
||||||
|
getCommonBounds(selectedElements),
|
||||||
|
{ x, y },
|
||||||
|
this.state.zoom,
|
||||||
|
event.pointerType,
|
||||||
|
);
|
||||||
|
if (resizeHandle) {
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement({
|
||||||
|
resizeHandle,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const hitElement = getElementAtPosition(
|
const hitElement = getElementAtPosition(
|
||||||
globalSceneState.getAllElements(),
|
globalSceneState.getAllElements(),
|
||||||
@ -1691,34 +1701,57 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
let resizeHandle: ResizeTestType = false;
|
let resizeHandle: ResizeTestType = false;
|
||||||
|
const setResizeHandle = (nextResizeHandle: ResizeTestType) => {
|
||||||
|
resizeHandle = nextResizeHandle;
|
||||||
|
};
|
||||||
let isResizingElements = false;
|
let isResizingElements = false;
|
||||||
let draggingOccurred = false;
|
let draggingOccurred = false;
|
||||||
let hitElement: ExcalidrawElement | null = null;
|
let hitElement: ExcalidrawElement | null = null;
|
||||||
let hitElementWasAddedToSelection = false;
|
let hitElementWasAddedToSelection = false;
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
const resizeElement = getElementWithResizeHandler(
|
|
||||||
globalSceneState.getAllElements(),
|
|
||||||
this.state,
|
|
||||||
{ x, y },
|
|
||||||
this.state.zoom,
|
|
||||||
event.pointerType,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(
|
||||||
globalSceneState.getAllElements(),
|
globalSceneState.getAllElements(),
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
if (selectedElements.length === 1 && resizeElement) {
|
if (selectedElements.length === 1) {
|
||||||
this.setState({
|
const elementWithResizeHandler = getElementWithResizeHandler(
|
||||||
resizingElement: resizeElement ? resizeElement.element : null,
|
globalSceneState.getAllElements(),
|
||||||
});
|
this.state,
|
||||||
|
{ x, y },
|
||||||
resizeHandle = resizeElement.resizeHandle;
|
this.state.zoom,
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
event.pointerType,
|
||||||
resizeElement,
|
|
||||||
);
|
);
|
||||||
isResizingElements = true;
|
if (elementWithResizeHandler) {
|
||||||
} else {
|
this.setState({
|
||||||
|
resizingElement: elementWithResizeHandler
|
||||||
|
? elementWithResizeHandler.element
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
resizeHandle = elementWithResizeHandler.resizeHandle;
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
|
elementWithResizeHandler,
|
||||||
|
);
|
||||||
|
isResizingElements = true;
|
||||||
|
}
|
||||||
|
} else if (selectedElements.length > 1) {
|
||||||
|
if (canResizeMutlipleElements(selectedElements)) {
|
||||||
|
resizeHandle = getResizeHandlerFromCoords(
|
||||||
|
getCommonBounds(selectedElements),
|
||||||
|
{ x, y },
|
||||||
|
this.state.zoom,
|
||||||
|
event.pointerType,
|
||||||
|
);
|
||||||
|
if (resizeHandle) {
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
|
{
|
||||||
|
resizeHandle,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
isResizingElements = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isResizingElements) {
|
||||||
hitElement = getElementAtPosition(
|
hitElement = getElementAtPosition(
|
||||||
globalSceneState.getAllElements(),
|
globalSceneState.getAllElements(),
|
||||||
this.state,
|
this.state,
|
||||||
@ -1908,82 +1941,9 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resizeArrowFn:
|
let resizeArrowFn: ResizeArrowFnType | null = null;
|
||||||
| ((
|
const setResizeArrrowFn = (fn: ResizeArrowFnType) => {
|
||||||
element: ExcalidrawLinearElement,
|
resizeArrowFn = fn;
|
||||||
pointIndex: number,
|
|
||||||
deltaX: number,
|
|
||||||
deltaY: number,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
perfect: boolean,
|
|
||||||
) => void)
|
|
||||||
| null = null;
|
|
||||||
|
|
||||||
const arrowResizeOrigin = (
|
|
||||||
element: ExcalidrawLinearElement,
|
|
||||||
pointIndex: number,
|
|
||||||
deltaX: number,
|
|
||||||
deltaY: number,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
perfect: boolean,
|
|
||||||
) => {
|
|
||||||
const [px, py] = element.points[pointIndex];
|
|
||||||
let x = element.x + deltaX;
|
|
||||||
let y = element.y + deltaY;
|
|
||||||
let pointX = px - deltaX;
|
|
||||||
let pointY = py - deltaY;
|
|
||||||
|
|
||||||
if (perfect) {
|
|
||||||
const { width, height } = getPerfectElementSize(
|
|
||||||
element.type,
|
|
||||||
px + element.x - pointerX,
|
|
||||||
py + element.y - pointerY,
|
|
||||||
);
|
|
||||||
x = px + element.x - width;
|
|
||||||
y = py + element.y - height;
|
|
||||||
pointX = width;
|
|
||||||
pointY = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutateElement(element, {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([pointX, pointY] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const arrowResizeEnd = (
|
|
||||||
element: ExcalidrawLinearElement,
|
|
||||||
pointIndex: number,
|
|
||||||
deltaX: number,
|
|
||||||
deltaY: number,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
perfect: boolean,
|
|
||||||
) => {
|
|
||||||
const [px, py] = element.points[pointIndex];
|
|
||||||
if (perfect) {
|
|
||||||
const { width, height } = getPerfectElementSize(
|
|
||||||
element.type,
|
|
||||||
pointerX - element.x,
|
|
||||||
pointerY - element.y,
|
|
||||||
);
|
|
||||||
mutateElement(element, {
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([width, height] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([px + deltaX, py + deltaY] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
@ -2012,6 +1972,13 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
|
event,
|
||||||
|
this.state,
|
||||||
|
this.canvas,
|
||||||
|
window.devicePixelRatio,
|
||||||
|
);
|
||||||
|
|
||||||
// for arrows, don't start dragging until a given threshold
|
// for arrows, don't start dragging until a given threshold
|
||||||
// to ensure we don't create a 2-point arrow by mistake when
|
// to ensure we don't create a 2-point arrow by mistake when
|
||||||
// user clicks mouse in a way that it moves a tiny bit (thus
|
// user clicks mouse in a way that it moves a tiny bit (thus
|
||||||
@ -2021,289 +1988,30 @@ export class App extends React.Component<any, AppState> {
|
|||||||
(this.state.elementType === "arrow" ||
|
(this.state.elementType === "arrow" ||
|
||||||
this.state.elementType === "line")
|
this.state.elementType === "line")
|
||||||
) {
|
) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
|
||||||
event,
|
|
||||||
this.state,
|
|
||||||
this.canvas,
|
|
||||||
window.devicePixelRatio,
|
|
||||||
);
|
|
||||||
if (distance2d(x, y, originX, originY) < DRAGGING_THRESHOLD) {
|
if (distance2d(x, y, originX, originY) < DRAGGING_THRESHOLD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResizingElements && this.state.resizingElement) {
|
const resized =
|
||||||
this.setState({
|
isResizingElements &&
|
||||||
isResizing: resizeHandle !== "rotation",
|
resizeElements(
|
||||||
isRotating: resizeHandle === "rotation",
|
resizeHandle,
|
||||||
});
|
setResizeHandle,
|
||||||
const el = this.state.resizingElement;
|
|
||||||
const selectedElements = getSelectedElements(
|
|
||||||
globalSceneState.getAllElements(),
|
|
||||||
this.state,
|
this.state,
|
||||||
|
this.setAppState,
|
||||||
|
resizeArrowFn,
|
||||||
|
setResizeArrrowFn,
|
||||||
|
event,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
lastX,
|
||||||
|
lastY,
|
||||||
);
|
);
|
||||||
if (selectedElements.length === 1) {
|
if (resized) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
lastX = x;
|
||||||
event,
|
lastY = y;
|
||||||
this.state,
|
return;
|
||||||
this.canvas,
|
|
||||||
window.devicePixelRatio,
|
|
||||||
);
|
|
||||||
const element = selectedElements[0];
|
|
||||||
const angle = element.angle;
|
|
||||||
// reverse rotate delta
|
|
||||||
const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
|
|
||||||
switch (resizeHandle) {
|
|
||||||
case "nw":
|
|
||||||
if (isLinearElement(element) && element.points.length === 2) {
|
|
||||||
const [, p1] = element.points;
|
|
||||||
|
|
||||||
if (!resizeArrowFn) {
|
|
||||||
if (p1[0] < 0 || p1[1] < 0) {
|
|
||||||
resizeArrowFn = arrowResizeEnd;
|
|
||||||
} else {
|
|
||||||
resizeArrowFn = arrowResizeOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
||||||
} else {
|
|
||||||
const width = element.width - deltaX;
|
|
||||||
const height = event.shiftKey ? width : element.height - deltaY;
|
|
||||||
const dY = element.height - height;
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("nw", element, deltaX, dY, angle),
|
|
||||||
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
||||||
? {
|
|
||||||
points: rescalePoints(
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
rescalePoints(1, height, element.points),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "ne":
|
|
||||||
if (isLinearElement(element) && element.points.length === 2) {
|
|
||||||
const [, p1] = element.points;
|
|
||||||
if (!resizeArrowFn) {
|
|
||||||
if (p1[0] >= 0) {
|
|
||||||
resizeArrowFn = arrowResizeEnd;
|
|
||||||
} else {
|
|
||||||
resizeArrowFn = arrowResizeOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
||||||
} else {
|
|
||||||
const width = element.width + deltaX;
|
|
||||||
const height = event.shiftKey ? width : element.height - deltaY;
|
|
||||||
const dY = element.height - height;
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("ne", element, deltaX, dY, angle),
|
|
||||||
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
||||||
? {
|
|
||||||
points: rescalePoints(
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
rescalePoints(1, height, element.points),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "sw":
|
|
||||||
if (isLinearElement(element) && element.points.length === 2) {
|
|
||||||
const [, p1] = element.points;
|
|
||||||
if (!resizeArrowFn) {
|
|
||||||
if (p1[0] <= 0) {
|
|
||||||
resizeArrowFn = arrowResizeEnd;
|
|
||||||
} else {
|
|
||||||
resizeArrowFn = arrowResizeOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
||||||
} else {
|
|
||||||
const width = element.width - deltaX;
|
|
||||||
const height = event.shiftKey ? width : element.height + deltaY;
|
|
||||||
const dY = height - element.height;
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("sw", element, deltaX, dY, angle),
|
|
||||||
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
||||||
? {
|
|
||||||
points: rescalePoints(
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
rescalePoints(1, height, element.points),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "se":
|
|
||||||
if (isLinearElement(element) && element.points.length === 2) {
|
|
||||||
const [, p1] = element.points;
|
|
||||||
if (!resizeArrowFn) {
|
|
||||||
if (p1[0] > 0 || p1[1] > 0) {
|
|
||||||
resizeArrowFn = arrowResizeEnd;
|
|
||||||
} else {
|
|
||||||
resizeArrowFn = arrowResizeOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
||||||
} else {
|
|
||||||
const width = element.width + deltaX;
|
|
||||||
const height = event.shiftKey ? width : element.height + deltaY;
|
|
||||||
const dY = height - element.height;
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("se", element, deltaX, dY, angle),
|
|
||||||
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
||||||
? {
|
|
||||||
points: rescalePoints(
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
rescalePoints(1, height, element.points),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "n": {
|
|
||||||
const height = element.height - deltaY;
|
|
||||||
|
|
||||||
if (isLinearElement(element)) {
|
|
||||||
if (element.points.length > 2 && height <= 0) {
|
|
||||||
// Someday we should implement logic to flip the shape.
|
|
||||||
// But for now, just stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mutateElement(element, {
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
||||||
points: rescalePoints(1, height, element.points),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "w": {
|
|
||||||
const width = element.width - deltaX;
|
|
||||||
|
|
||||||
if (isLinearElement(element)) {
|
|
||||||
if (element.points.length > 2 && width <= 0) {
|
|
||||||
// Someday we should implement logic to flip the shape.
|
|
||||||
// But for now, just stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
||||||
points: rescalePoints(0, width, element.points),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "s": {
|
|
||||||
const height = element.height + deltaY;
|
|
||||||
|
|
||||||
if (isLinearElement(element)) {
|
|
||||||
if (element.points.length > 2 && height <= 0) {
|
|
||||||
// Someday we should implement logic to flip the shape.
|
|
||||||
// But for now, just stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mutateElement(element, {
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
|
||||||
points: rescalePoints(1, height, element.points),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
height,
|
|
||||||
...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "e": {
|
|
||||||
const width = element.width + deltaX;
|
|
||||||
|
|
||||||
if (isLinearElement(element)) {
|
|
||||||
if (element.points.length > 2 && width <= 0) {
|
|
||||||
// Someday we should implement logic to flip the shape.
|
|
||||||
// But for now, just stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
|
||||||
points: rescalePoints(0, width, element.points),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "rotation": {
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
||||||
const cx = (x1 + x2) / 2;
|
|
||||||
const cy = (y1 + y2) / 2;
|
|
||||||
let angle = (5 * Math.PI) / 2 + Math.atan2(y - cy, x - cx);
|
|
||||||
if (event.shiftKey) {
|
|
||||||
angle += SHIFT_LOCKING_ANGLE / 2;
|
|
||||||
angle -= angle % SHIFT_LOCKING_ANGLE;
|
|
||||||
}
|
|
||||||
if (angle >= 2 * Math.PI) {
|
|
||||||
angle -= 2 * Math.PI;
|
|
||||||
}
|
|
||||||
mutateElement(element, { angle });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resizeHandle) {
|
|
||||||
resizeHandle = normalizeResizeHandle(element, resizeHandle);
|
|
||||||
}
|
|
||||||
normalizeDimensions(element);
|
|
||||||
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement({
|
|
||||||
element,
|
|
||||||
resizeHandle,
|
|
||||||
});
|
|
||||||
mutateElement(el, {
|
|
||||||
x: element.x,
|
|
||||||
y: element.y,
|
|
||||||
});
|
|
||||||
|
|
||||||
lastX = x;
|
|
||||||
lastY = y;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hitElement && this.state.selectedElementIds[hitElement.id]) {
|
if (hitElement && this.state.selectedElementIds[hitElement.id]) {
|
||||||
@ -2341,13 +2049,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
|
||||||
event,
|
|
||||||
this.state,
|
|
||||||
this.canvas,
|
|
||||||
window.devicePixelRatio,
|
|
||||||
);
|
|
||||||
|
|
||||||
let width = distance(originX, x);
|
let width = distance(originX, x);
|
||||||
let height = distance(originY, y);
|
let height = distance(originY, y);
|
||||||
|
|
||||||
@ -2533,7 +2234,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
this.setState((prevState) => ({
|
this.setState((_prevState) => ({
|
||||||
selectedElementIds: { [hitElement!.id]: true },
|
selectedElementIds: { [hitElement!.id]: true },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,12 @@ const getHints = ({ appState, elements }: Hint) => {
|
|||||||
return t("hints.linearElementMulti");
|
return t("hints.linearElementMulti");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResizing && lastPointerDownWith === "mouse") {
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
const selectedElements = getSelectedElements(elements, appState);
|
if (
|
||||||
|
isResizing &&
|
||||||
|
lastPointerDownWith === "mouse" &&
|
||||||
|
selectedElements.length === 1
|
||||||
|
) {
|
||||||
const targetElement = selectedElements[0];
|
const targetElement = selectedElements[0];
|
||||||
if (isLinearElement(targetElement) && targetElement.points.length > 2) {
|
if (isLinearElement(targetElement) && targetElement.points.length > 2) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -7,7 +7,9 @@ import { isLinearElement } from "./typeChecks";
|
|||||||
|
|
||||||
// If the element is created from right to left, the width is going to be negative
|
// If the element is created from right to left, the width is going to be negative
|
||||||
// This set of functions retrieves the absolute position of the 4 points.
|
// This set of functions retrieves the absolute position of the 4 points.
|
||||||
export function getElementAbsoluteCoords(element: ExcalidrawElement) {
|
export function getElementAbsoluteCoords(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
): [number, number, number, number] {
|
||||||
if (isLinearElement(element)) {
|
if (isLinearElement(element)) {
|
||||||
return getLinearElementAbsoluteBounds(element);
|
return getLinearElementAbsoluteBounds(element);
|
||||||
}
|
}
|
||||||
@ -36,7 +38,7 @@ export function getDiamondPoints(element: ExcalidrawElement) {
|
|||||||
|
|
||||||
export function getLinearElementAbsoluteBounds(
|
export function getLinearElementAbsoluteBounds(
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
) {
|
): [number, number, number, number] {
|
||||||
if (element.points.length < 2 || !getShapeForElement(element)) {
|
if (element.points.length < 2 || !getShapeForElement(element)) {
|
||||||
const { minX, minY, maxX, maxY } = element.points.reduce(
|
const { minX, minY, maxX, maxY } = element.points.reduce(
|
||||||
(limits, [x, y]) => {
|
(limits, [x, y]) => {
|
||||||
@ -186,7 +188,9 @@ export function getArrowPoints(
|
|||||||
return [x2, y2, x3, y3, x4, y4];
|
return [x2, y2, x3, y3, x4, y4];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCommonBounds(elements: readonly ExcalidrawElement[]) {
|
export function getCommonBounds(
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
): [number, number, number, number] {
|
||||||
if (!elements.length) {
|
if (!elements.length) {
|
||||||
return [0, 0, 0, 0];
|
return [0, 0, 0, 0];
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,14 @@ const handleSizes: { [k in PointerType]: number } = {
|
|||||||
|
|
||||||
const ROTATION_HANDLER_GAP = 16;
|
const ROTATION_HANDLER_GAP = 16;
|
||||||
|
|
||||||
|
export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
|
||||||
|
e: true,
|
||||||
|
s: true,
|
||||||
|
n: true,
|
||||||
|
w: true,
|
||||||
|
rotation: true,
|
||||||
|
};
|
||||||
|
|
||||||
function generateHandler(
|
function generateHandler(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -26,11 +34,13 @@ function generateHandler(
|
|||||||
return [xx - width / 2, yy - height / 2, width, height];
|
return [xx - width / 2, yy - height / 2, width, height];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handlerRectangles(
|
export function handlerRectanglesFromCoords(
|
||||||
element: ExcalidrawElement,
|
[x1, y1, x2, y2]: [number, number, number, number],
|
||||||
|
angle: number,
|
||||||
zoom: number,
|
zoom: number,
|
||||||
pointerType: PointerType = "mouse",
|
pointerType: PointerType = "mouse",
|
||||||
) {
|
omitSides: { [T in Sides]?: boolean } = {},
|
||||||
|
): Partial<{ [T in Sides]: [number, number, number, number] }> {
|
||||||
const size = handleSizes[pointerType];
|
const size = handleSizes[pointerType];
|
||||||
const handlerWidth = size / zoom;
|
const handlerWidth = size / zoom;
|
||||||
const handlerHeight = size / zoom;
|
const handlerHeight = size / zoom;
|
||||||
@ -38,116 +48,145 @@ export function handlerRectangles(
|
|||||||
const handlerMarginX = size / zoom;
|
const handlerMarginX = size / zoom;
|
||||||
const handlerMarginY = size / zoom;
|
const handlerMarginY = size / zoom;
|
||||||
|
|
||||||
const [elementX1, elementY1, elementX2, elementY2] = getElementAbsoluteCoords(
|
const width = x2 - x1;
|
||||||
element,
|
const height = y2 - y1;
|
||||||
);
|
const cx = (x1 + x2) / 2;
|
||||||
|
const cy = (y1 + y2) / 2;
|
||||||
const elementWidth = elementX2 - elementX1;
|
|
||||||
const elementHeight = elementY2 - elementY1;
|
|
||||||
const cx = (elementX1 + elementX2) / 2;
|
|
||||||
const cy = (elementY1 + elementY2) / 2;
|
|
||||||
const angle = element.angle;
|
|
||||||
|
|
||||||
const dashedLineMargin = 4 / zoom;
|
const dashedLineMargin = 4 / zoom;
|
||||||
|
|
||||||
const centeringOffset = (size - 8) / (2 * zoom);
|
const centeringOffset = (size - 8) / (2 * zoom);
|
||||||
|
|
||||||
const handlers =
|
const handlers: Partial<
|
||||||
{
|
{ [T in Sides]: [number, number, number, number] }
|
||||||
nw: generateHandler(
|
> = {
|
||||||
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
nw: omitSides["nw"]
|
||||||
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
? undefined
|
||||||
handlerWidth,
|
: generateHandler(
|
||||||
handlerHeight,
|
x1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
||||||
cx,
|
y1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
||||||
cy,
|
handlerWidth,
|
||||||
angle,
|
handlerHeight,
|
||||||
),
|
cx,
|
||||||
ne: generateHandler(
|
cy,
|
||||||
elementX2 + dashedLineMargin - centeringOffset,
|
angle,
|
||||||
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
),
|
||||||
handlerWidth,
|
ne: omitSides["ne"]
|
||||||
handlerHeight,
|
? undefined
|
||||||
cx,
|
: generateHandler(
|
||||||
cy,
|
x2 + dashedLineMargin - centeringOffset,
|
||||||
angle,
|
y1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
||||||
),
|
handlerWidth,
|
||||||
sw: generateHandler(
|
handlerHeight,
|
||||||
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
cx,
|
||||||
elementY2 + dashedLineMargin - centeringOffset,
|
cy,
|
||||||
handlerWidth,
|
angle,
|
||||||
handlerHeight,
|
),
|
||||||
cx,
|
sw: omitSides["sw"]
|
||||||
cy,
|
? undefined
|
||||||
angle,
|
: generateHandler(
|
||||||
),
|
x1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
||||||
se: generateHandler(
|
y2 + dashedLineMargin - centeringOffset,
|
||||||
elementX2 + dashedLineMargin - centeringOffset,
|
handlerWidth,
|
||||||
elementY2 + dashedLineMargin - centeringOffset,
|
handlerHeight,
|
||||||
handlerWidth,
|
cx,
|
||||||
handlerHeight,
|
cy,
|
||||||
cx,
|
angle,
|
||||||
cy,
|
),
|
||||||
angle,
|
se: omitSides["se"]
|
||||||
),
|
? undefined
|
||||||
rotation: generateHandler(
|
: generateHandler(
|
||||||
elementX1 + elementWidth / 2 - handlerWidth / 2,
|
x2 + dashedLineMargin - centeringOffset,
|
||||||
elementY1 -
|
y2 + dashedLineMargin - centeringOffset,
|
||||||
dashedLineMargin -
|
handlerWidth,
|
||||||
handlerMarginY +
|
handlerHeight,
|
||||||
centeringOffset -
|
cx,
|
||||||
ROTATION_HANDLER_GAP / zoom,
|
cy,
|
||||||
handlerWidth,
|
angle,
|
||||||
handlerHeight,
|
),
|
||||||
cx,
|
rotation: omitSides["rotation"]
|
||||||
cy,
|
? undefined
|
||||||
angle,
|
: generateHandler(
|
||||||
),
|
x1 + width / 2 - handlerWidth / 2,
|
||||||
} as { [T in Sides]: [number, number, number, number] };
|
y1 -
|
||||||
|
dashedLineMargin -
|
||||||
|
handlerMarginY +
|
||||||
|
centeringOffset -
|
||||||
|
ROTATION_HANDLER_GAP / zoom,
|
||||||
|
handlerWidth,
|
||||||
|
handlerHeight,
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
angle,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
// We only want to show height handlers (all cardinal directions) above a certain size
|
// We only want to show height handlers (all cardinal directions) above a certain size
|
||||||
const minimumSizeForEightHandlers = (5 * size) / zoom;
|
const minimumSizeForEightHandlers = (5 * size) / zoom;
|
||||||
if (Math.abs(elementWidth) > minimumSizeForEightHandlers) {
|
if (Math.abs(width) > minimumSizeForEightHandlers) {
|
||||||
handlers["n"] = generateHandler(
|
if (!omitSides["n"]) {
|
||||||
elementX1 + elementWidth / 2 - handlerWidth / 2,
|
handlers["n"] = generateHandler(
|
||||||
elementY1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
x1 + width / 2 - handlerWidth / 2,
|
||||||
handlerWidth,
|
y1 - dashedLineMargin - handlerMarginY + centeringOffset,
|
||||||
handlerHeight,
|
handlerWidth,
|
||||||
cx,
|
handlerHeight,
|
||||||
cy,
|
cx,
|
||||||
angle,
|
cy,
|
||||||
);
|
angle,
|
||||||
handlers["s"] = generateHandler(
|
);
|
||||||
elementX1 + elementWidth / 2 - handlerWidth / 2,
|
}
|
||||||
elementY2 + dashedLineMargin - centeringOffset,
|
if (!omitSides["s"]) {
|
||||||
handlerWidth,
|
handlers["s"] = generateHandler(
|
||||||
handlerHeight,
|
x1 + width / 2 - handlerWidth / 2,
|
||||||
cx,
|
y2 + dashedLineMargin - centeringOffset,
|
||||||
cy,
|
handlerWidth,
|
||||||
angle,
|
handlerHeight,
|
||||||
);
|
cx,
|
||||||
|
cy,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Math.abs(elementHeight) > minimumSizeForEightHandlers) {
|
if (Math.abs(height) > minimumSizeForEightHandlers) {
|
||||||
handlers["w"] = generateHandler(
|
if (!omitSides["w"]) {
|
||||||
elementX1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
handlers["w"] = generateHandler(
|
||||||
elementY1 + elementHeight / 2 - handlerHeight / 2,
|
x1 - dashedLineMargin - handlerMarginX + centeringOffset,
|
||||||
handlerWidth,
|
y1 + height / 2 - handlerHeight / 2,
|
||||||
handlerHeight,
|
handlerWidth,
|
||||||
cx,
|
handlerHeight,
|
||||||
cy,
|
cx,
|
||||||
angle,
|
cy,
|
||||||
);
|
angle,
|
||||||
handlers["e"] = generateHandler(
|
);
|
||||||
elementX2 + dashedLineMargin - centeringOffset,
|
}
|
||||||
elementY1 + elementHeight / 2 - handlerHeight / 2,
|
if (!omitSides["e"]) {
|
||||||
handlerWidth,
|
handlers["e"] = generateHandler(
|
||||||
handlerHeight,
|
x2 + dashedLineMargin - centeringOffset,
|
||||||
cx,
|
y1 + height / 2 - handlerHeight / 2,
|
||||||
cy,
|
handlerWidth,
|
||||||
angle,
|
handlerHeight,
|
||||||
);
|
cx,
|
||||||
|
cy,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handlerRectangles(
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
zoom: number,
|
||||||
|
pointerType: PointerType = "mouse",
|
||||||
|
) {
|
||||||
|
const handlers = handlerRectanglesFromCoords(
|
||||||
|
getElementAbsoluteCoords(element),
|
||||||
|
element.angle,
|
||||||
|
zoom,
|
||||||
|
pointerType,
|
||||||
|
);
|
||||||
|
|
||||||
if (element.type === "arrow" || element.type === "line") {
|
if (element.type === "arrow" || element.type === "line") {
|
||||||
if (element.points.length === 2) {
|
if (element.points.length === 2) {
|
||||||
// only check the last point because starting point is always (0,0)
|
// only check the last point because starting point is always (0,0)
|
||||||
|
@ -15,13 +15,21 @@ export {
|
|||||||
getLinearElementAbsoluteBounds,
|
getLinearElementAbsoluteBounds,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
|
|
||||||
export { handlerRectangles } from "./handlerRectangles";
|
export {
|
||||||
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||||
|
handlerRectanglesFromCoords,
|
||||||
|
handlerRectangles,
|
||||||
|
} from "./handlerRectangles";
|
||||||
export { hitTest } from "./collision";
|
export { hitTest } from "./collision";
|
||||||
export {
|
export {
|
||||||
resizeTest,
|
resizeTest,
|
||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
normalizeResizeHandle,
|
normalizeResizeHandle,
|
||||||
|
getElementWithResizeHandler,
|
||||||
|
getResizeHandlerFromCoords,
|
||||||
} from "./resizeTest";
|
} from "./resizeTest";
|
||||||
|
export type { ResizeArrowFnType } from "./resizeElements";
|
||||||
|
export { resizeElements, canResizeMutlipleElements } from "./resizeElements";
|
||||||
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
||||||
export { textWysiwyg } from "./textWysiwyg";
|
export { textWysiwyg } from "./textWysiwyg";
|
||||||
export { redrawTextBoundingBox } from "./textElement";
|
export { redrawTextBoundingBox } from "./textElement";
|
||||||
|
459
src/element/resizeElements.ts
Normal file
459
src/element/resizeElements.ts
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
import { AppState } from "../types";
|
||||||
|
import { SHIFT_LOCKING_ANGLE } from "../constants";
|
||||||
|
import { getSelectedElements, globalSceneState } from "../scene";
|
||||||
|
import { rescalePoints } from "../points";
|
||||||
|
import { rotate, adjustXYWithRotation } from "../math";
|
||||||
|
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
||||||
|
import { getElementAbsoluteCoords, getCommonBounds } from "./bounds";
|
||||||
|
import { isLinearElement } from "./typeChecks";
|
||||||
|
import { mutateElement } from "./mutateElement";
|
||||||
|
import { getPerfectElementSize, normalizeDimensions } from "./sizeHelpers";
|
||||||
|
import {
|
||||||
|
resizeTest,
|
||||||
|
getCursorForResizingElement,
|
||||||
|
normalizeResizeHandle,
|
||||||
|
} from "./resizeTest";
|
||||||
|
|
||||||
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
|
|
||||||
|
export type ResizeArrowFnType = (
|
||||||
|
element: ExcalidrawLinearElement,
|
||||||
|
pointIndex: number,
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
perfect: boolean,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
const arrowResizeOrigin: ResizeArrowFnType = (
|
||||||
|
element: ExcalidrawLinearElement,
|
||||||
|
pointIndex: number,
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
perfect: boolean,
|
||||||
|
) => {
|
||||||
|
const [px, py] = element.points[pointIndex];
|
||||||
|
let x = element.x + deltaX;
|
||||||
|
let y = element.y + deltaY;
|
||||||
|
let pointX = px - deltaX;
|
||||||
|
let pointY = py - deltaY;
|
||||||
|
|
||||||
|
if (perfect) {
|
||||||
|
const { width, height } = getPerfectElementSize(
|
||||||
|
element.type,
|
||||||
|
px + element.x - pointerX,
|
||||||
|
py + element.y - pointerY,
|
||||||
|
);
|
||||||
|
x = px + element.x - width;
|
||||||
|
y = py + element.y - height;
|
||||||
|
pointX = width;
|
||||||
|
pointY = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
points: element.points.map((point, i) =>
|
||||||
|
i === pointIndex ? ([pointX, pointY] as const) : point,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrowResizeEnd: ResizeArrowFnType = (
|
||||||
|
element: ExcalidrawLinearElement,
|
||||||
|
pointIndex: number,
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
perfect: boolean,
|
||||||
|
) => {
|
||||||
|
const [px, py] = element.points[pointIndex];
|
||||||
|
if (perfect) {
|
||||||
|
const { width, height } = getPerfectElementSize(
|
||||||
|
element.type,
|
||||||
|
pointerX - element.x,
|
||||||
|
pointerY - element.y,
|
||||||
|
);
|
||||||
|
mutateElement(element, {
|
||||||
|
points: element.points.map((point, i) =>
|
||||||
|
i === pointIndex ? ([width, height] as const) : point,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
points: element.points.map((point, i) =>
|
||||||
|
i === pointIndex ? ([px + deltaX, py + deltaY] as const) : point,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resizeElements(
|
||||||
|
resizeHandle: ResizeTestType,
|
||||||
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
||||||
|
appState: AppState,
|
||||||
|
setAppState: (obj: any) => void,
|
||||||
|
resizeArrowFn: ResizeArrowFnType | null,
|
||||||
|
setResizeArrowFn: (fn: ResizeArrowFnType) => void,
|
||||||
|
event: PointerEvent,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
lastX: number,
|
||||||
|
lastY: number,
|
||||||
|
) {
|
||||||
|
setAppState({
|
||||||
|
isResizing: resizeHandle !== "rotation",
|
||||||
|
isRotating: resizeHandle === "rotation",
|
||||||
|
});
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
globalSceneState.getAllElements(),
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
if (selectedElements.length === 1) {
|
||||||
|
const element = selectedElements[0];
|
||||||
|
const angle = element.angle;
|
||||||
|
// reverse rotate delta
|
||||||
|
const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "nw":
|
||||||
|
if (isLinearElement(element) && element.points.length === 2) {
|
||||||
|
const [, p1] = element.points;
|
||||||
|
|
||||||
|
if (!resizeArrowFn) {
|
||||||
|
if (p1[0] < 0 || p1[1] < 0) {
|
||||||
|
resizeArrowFn = arrowResizeEnd;
|
||||||
|
} else {
|
||||||
|
resizeArrowFn = arrowResizeOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
||||||
|
setResizeArrowFn(resizeArrowFn);
|
||||||
|
} else {
|
||||||
|
const width = element.width - deltaX;
|
||||||
|
const height = event.shiftKey ? width : element.height - deltaY;
|
||||||
|
const dY = element.height - height;
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("nw", element, deltaX, dY, angle),
|
||||||
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
||||||
|
? {
|
||||||
|
points: rescalePoints(
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
rescalePoints(1, height, element.points),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ne":
|
||||||
|
if (isLinearElement(element) && element.points.length === 2) {
|
||||||
|
const [, p1] = element.points;
|
||||||
|
if (!resizeArrowFn) {
|
||||||
|
if (p1[0] >= 0) {
|
||||||
|
resizeArrowFn = arrowResizeEnd;
|
||||||
|
} else {
|
||||||
|
resizeArrowFn = arrowResizeOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
||||||
|
setResizeArrowFn(resizeArrowFn);
|
||||||
|
} else {
|
||||||
|
const width = element.width + deltaX;
|
||||||
|
const height = event.shiftKey ? width : element.height - deltaY;
|
||||||
|
const dY = element.height - height;
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("ne", element, deltaX, dY, angle),
|
||||||
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
||||||
|
? {
|
||||||
|
points: rescalePoints(
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
rescalePoints(1, height, element.points),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "sw":
|
||||||
|
if (isLinearElement(element) && element.points.length === 2) {
|
||||||
|
const [, p1] = element.points;
|
||||||
|
if (!resizeArrowFn) {
|
||||||
|
if (p1[0] <= 0) {
|
||||||
|
resizeArrowFn = arrowResizeEnd;
|
||||||
|
} else {
|
||||||
|
resizeArrowFn = arrowResizeOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
||||||
|
setResizeArrowFn(resizeArrowFn);
|
||||||
|
} else {
|
||||||
|
const width = element.width - deltaX;
|
||||||
|
const height = event.shiftKey ? width : element.height + deltaY;
|
||||||
|
const dY = height - element.height;
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("sw", element, deltaX, dY, angle),
|
||||||
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
||||||
|
? {
|
||||||
|
points: rescalePoints(
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
rescalePoints(1, height, element.points),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "se":
|
||||||
|
if (isLinearElement(element) && element.points.length === 2) {
|
||||||
|
const [, p1] = element.points;
|
||||||
|
if (!resizeArrowFn) {
|
||||||
|
if (p1[0] > 0 || p1[1] > 0) {
|
||||||
|
resizeArrowFn = arrowResizeEnd;
|
||||||
|
} else {
|
||||||
|
resizeArrowFn = arrowResizeOrigin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
||||||
|
setResizeArrowFn(resizeArrowFn);
|
||||||
|
} else {
|
||||||
|
const width = element.width + deltaX;
|
||||||
|
const height = event.shiftKey ? width : element.height + deltaY;
|
||||||
|
const dY = height - element.height;
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("se", element, deltaX, dY, angle),
|
||||||
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
||||||
|
? {
|
||||||
|
points: rescalePoints(
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
rescalePoints(1, height, element.points),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "n": {
|
||||||
|
const height = element.height - deltaY;
|
||||||
|
|
||||||
|
if (isLinearElement(element)) {
|
||||||
|
if (element.points.length > 2 && height <= 0) {
|
||||||
|
// Someday we should implement logic to flip the shape.
|
||||||
|
// But for now, just stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutateElement(element, {
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
||||||
|
points: rescalePoints(1, height, element.points),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "w": {
|
||||||
|
const width = element.width - deltaX;
|
||||||
|
|
||||||
|
if (isLinearElement(element)) {
|
||||||
|
if (element.points.length > 2 && width <= 0) {
|
||||||
|
// Someday we should implement logic to flip the shape.
|
||||||
|
// But for now, just stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
||||||
|
points: rescalePoints(0, width, element.points),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "s": {
|
||||||
|
const height = element.height + deltaY;
|
||||||
|
|
||||||
|
if (isLinearElement(element)) {
|
||||||
|
if (element.points.length > 2 && height <= 0) {
|
||||||
|
// Someday we should implement logic to flip the shape.
|
||||||
|
// But for now, just stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutateElement(element, {
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
||||||
|
points: rescalePoints(1, height, element.points),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
height,
|
||||||
|
...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "e": {
|
||||||
|
const width = element.width + deltaX;
|
||||||
|
|
||||||
|
if (isLinearElement(element)) {
|
||||||
|
if (element.points.length > 2 && width <= 0) {
|
||||||
|
// Someday we should implement logic to flip the shape.
|
||||||
|
// But for now, just stop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
||||||
|
points: rescalePoints(0, width, element.points),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
width,
|
||||||
|
...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rotation": {
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
const cx = (x1 + x2) / 2;
|
||||||
|
const cy = (y1 + y2) / 2;
|
||||||
|
let angle = (5 * Math.PI) / 2 + Math.atan2(y - cy, x - cx);
|
||||||
|
if (event.shiftKey) {
|
||||||
|
angle += SHIFT_LOCKING_ANGLE / 2;
|
||||||
|
angle -= angle % SHIFT_LOCKING_ANGLE;
|
||||||
|
}
|
||||||
|
if (angle >= 2 * Math.PI) {
|
||||||
|
angle -= 2 * Math.PI;
|
||||||
|
}
|
||||||
|
mutateElement(element, { angle });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeHandle) {
|
||||||
|
setResizeHandle(normalizeResizeHandle(element, resizeHandle));
|
||||||
|
}
|
||||||
|
normalizeDimensions(element);
|
||||||
|
|
||||||
|
// do we need this?
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement({
|
||||||
|
element,
|
||||||
|
resizeHandle,
|
||||||
|
});
|
||||||
|
// why do we need this?
|
||||||
|
if (appState.resizingElement) {
|
||||||
|
mutateElement(appState.resizingElement, {
|
||||||
|
x: element.x,
|
||||||
|
y: element.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (selectedElements.length > 1) {
|
||||||
|
const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
|
||||||
|
const handleOffset = 4 / appState.zoom; // XXX import constant
|
||||||
|
const dashedLinePadding = 4 / appState.zoom; // XXX import constant
|
||||||
|
const minSize = handleOffset * 4;
|
||||||
|
const minScale = Math.max(minSize / (x2 - x1), minSize / (y2 - y1));
|
||||||
|
switch (resizeHandle) {
|
||||||
|
case "se": {
|
||||||
|
const scale = Math.max(
|
||||||
|
(x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
|
||||||
|
(y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
|
||||||
|
);
|
||||||
|
if (scale > minScale) {
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
const width = element.width * scale;
|
||||||
|
const height = element.height * scale;
|
||||||
|
const x = element.x + (element.x - x1) * (scale - 1);
|
||||||
|
const y = element.y + (element.y - y1) * (scale - 1);
|
||||||
|
mutateElement(element, { width, height, x, y });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case "nw": {
|
||||||
|
const scale = Math.max(
|
||||||
|
(x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
|
||||||
|
(y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
|
||||||
|
);
|
||||||
|
if (scale > minScale) {
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
const width = element.width * scale;
|
||||||
|
const height = element.height * scale;
|
||||||
|
const x = element.x - (x2 - element.x) * (scale - 1);
|
||||||
|
const y = element.y - (y2 - element.y) * (scale - 1);
|
||||||
|
mutateElement(element, { width, height, x, y });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case "ne": {
|
||||||
|
const scale = Math.max(
|
||||||
|
(x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
|
||||||
|
(y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
|
||||||
|
);
|
||||||
|
if (scale > minScale) {
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
const width = element.width * scale;
|
||||||
|
const height = element.height * scale;
|
||||||
|
const x = element.x + (element.x - x1) * (scale - 1);
|
||||||
|
const y = element.y - (y2 - element.y) * (scale - 1);
|
||||||
|
mutateElement(element, { width, height, x, y });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case "sw": {
|
||||||
|
const scale = Math.max(
|
||||||
|
(x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
|
||||||
|
(y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
|
||||||
|
);
|
||||||
|
if (scale > minScale) {
|
||||||
|
selectedElements.forEach((element) => {
|
||||||
|
const width = element.width * scale;
|
||||||
|
const height = element.height * scale;
|
||||||
|
const x = element.x - (x2 - element.x) * (scale - 1);
|
||||||
|
const y = element.y + (element.y - y1) * (scale - 1);
|
||||||
|
mutateElement(element, { width, height, x, y });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canResizeMutlipleElements(
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
) {
|
||||||
|
return elements.every((element) =>
|
||||||
|
["rectangle", "diamond", "ellipse"].includes(element.type),
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,10 @@
|
|||||||
import { ExcalidrawElement, PointerType } from "./types";
|
import { ExcalidrawElement, PointerType } from "./types";
|
||||||
|
|
||||||
import { handlerRectangles } from "./handlerRectangles";
|
import {
|
||||||
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||||
|
handlerRectanglesFromCoords,
|
||||||
|
handlerRectangles,
|
||||||
|
} from "./handlerRectangles";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { isLinearElement } from "./typeChecks";
|
import { isLinearElement } from "./typeChecks";
|
||||||
|
|
||||||
@ -77,16 +81,37 @@ export function getElementWithResizeHandler(
|
|||||||
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getResizeHandlerFromCoords(
|
||||||
|
[x1, y1, x2, y2]: readonly [number, number, number, number],
|
||||||
|
{ x, y }: { x: number; y: number },
|
||||||
|
zoom: number,
|
||||||
|
pointerType: PointerType,
|
||||||
|
) {
|
||||||
|
const handlers = handlerRectanglesFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
0,
|
||||||
|
zoom,
|
||||||
|
pointerType,
|
||||||
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const found = Object.keys(handlers).find((key) => {
|
||||||
|
const handler = handlers[key as Exclude<HandlerRectanglesRet, "rotation">]!;
|
||||||
|
return handler && isInHandlerRect(handler, x, y);
|
||||||
|
});
|
||||||
|
return (found || false) as HandlerRectanglesRet;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns bi-directional cursor for the element being resized
|
* Returns bi-directional cursor for the element being resized
|
||||||
*/
|
*/
|
||||||
export function getCursorForResizingElement(resizingElement: {
|
export function getCursorForResizingElement(resizingElement: {
|
||||||
element: ExcalidrawElement;
|
element?: ExcalidrawElement;
|
||||||
resizeHandle: ReturnType<typeof resizeTest>;
|
resizeHandle: ReturnType<typeof resizeTest>;
|
||||||
}): string {
|
}): string {
|
||||||
const { element, resizeHandle } = resizingElement;
|
const { element, resizeHandle } = resizingElement;
|
||||||
const shouldSwapCursors =
|
const shouldSwapCursors =
|
||||||
Math.sign(element.height) * Math.sign(element.width) === -1;
|
element && Math.sign(element.height) * Math.sign(element.width) === -1;
|
||||||
let cursor = null;
|
let cursor = null;
|
||||||
|
|
||||||
switch (resizeHandle) {
|
switch (resizeHandle) {
|
||||||
|
@ -3,7 +3,14 @@ import { RoughSVG } from "roughjs/bin/svg";
|
|||||||
|
|
||||||
import { FlooredNumber, AppState } from "../types";
|
import { FlooredNumber, AppState } from "../types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { getElementAbsoluteCoords, handlerRectangles } from "../element";
|
import {
|
||||||
|
getElementAbsoluteCoords,
|
||||||
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||||
|
handlerRectanglesFromCoords,
|
||||||
|
handlerRectangles,
|
||||||
|
getCommonBounds,
|
||||||
|
canResizeMutlipleElements,
|
||||||
|
} from "../element";
|
||||||
|
|
||||||
import { roundRect } from "./roundRect";
|
import { roundRect } from "./roundRect";
|
||||||
import { SceneState } from "../scene/types";
|
import { SceneState } from "../scene/types";
|
||||||
@ -263,6 +270,56 @@ export function renderScene(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
context.translate(-sceneState.scrollX, -sceneState.scrollY);
|
context.translate(-sceneState.scrollX, -sceneState.scrollY);
|
||||||
|
} else if (locallySelectedElements.length > 1) {
|
||||||
|
if (canResizeMutlipleElements(locallySelectedElements)) {
|
||||||
|
const dashedLinePadding = 4 / sceneState.zoom;
|
||||||
|
context.translate(sceneState.scrollX, sceneState.scrollY);
|
||||||
|
context.fillStyle = "#fff";
|
||||||
|
const [x1, y1, x2, y2] = getCommonBounds(locallySelectedElements);
|
||||||
|
const initialLineDash = context.getLineDash();
|
||||||
|
context.setLineDash([2 / sceneState.zoom]);
|
||||||
|
const lineWidth = context.lineWidth;
|
||||||
|
context.lineWidth = 1 / sceneState.zoom;
|
||||||
|
strokeRectWithRotation(
|
||||||
|
context,
|
||||||
|
x1 - dashedLinePadding,
|
||||||
|
y1 - dashedLinePadding,
|
||||||
|
x2 - x1 + dashedLinePadding * 2,
|
||||||
|
y2 - y1 + dashedLinePadding * 2,
|
||||||
|
(x1 + x2) / 2,
|
||||||
|
(y1 + y2) / 2,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
context.lineWidth = lineWidth;
|
||||||
|
context.setLineDash(initialLineDash);
|
||||||
|
const handlers = handlerRectanglesFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
0,
|
||||||
|
sceneState.zoom,
|
||||||
|
undefined,
|
||||||
|
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||||
|
);
|
||||||
|
Object.keys(handlers).forEach((key) => {
|
||||||
|
const handler = handlers[key as HandlerRectanglesRet];
|
||||||
|
if (handler !== undefined) {
|
||||||
|
const lineWidth = context.lineWidth;
|
||||||
|
context.lineWidth = 1 / sceneState.zoom;
|
||||||
|
strokeRectWithRotation(
|
||||||
|
context,
|
||||||
|
handler[0],
|
||||||
|
handler[1],
|
||||||
|
handler[2],
|
||||||
|
handler[3],
|
||||||
|
handler[0] + handler[2] / 2,
|
||||||
|
handler[1] + handler[3] / 2,
|
||||||
|
0,
|
||||||
|
true, // fill before stroke
|
||||||
|
);
|
||||||
|
context.lineWidth = lineWidth;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.translate(-sceneState.scrollX, -sceneState.scrollY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,12 +162,13 @@ function getSelectedElement(): ExcalidrawElement {
|
|||||||
return selectedElements[0];
|
return selectedElements[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
|
||||||
function getResizeHandles() {
|
function getResizeHandles() {
|
||||||
const rects = handlerRectangles(
|
const rects =
|
||||||
getSelectedElement(),
|
handlerRectangles(getSelectedElement(), h.state.zoom, pointerType) as
|
||||||
h.state.zoom,
|
{
|
||||||
pointerType,
|
[T in HandlerRectanglesRet]: [number, number, number, number];
|
||||||
);
|
};
|
||||||
|
|
||||||
const rv: { [K in keyof typeof rects]: [number, number] } = {} as any;
|
const rv: { [K in keyof typeof rects]: [number, number] } = {} as any;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user