|
@@ -4,6 +4,7 @@ import socketIOClient from "socket.io-client";
|
|
|
import rough from "roughjs/bin/rough";
|
|
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
|
|
import { FlooredNumber } from "../types";
|
|
|
+import { getElementAbsoluteCoords } from "../element/bounds";
|
|
|
|
|
|
import {
|
|
|
newElement,
|
|
@@ -50,6 +51,7 @@ import { restore } from "../data/restore";
|
|
|
import { renderScene } from "../renderer";
|
|
|
import { AppState, GestureEvent, Gesture } from "../types";
|
|
|
import { ExcalidrawElement, ExcalidrawLinearElement } from "../element/types";
|
|
|
+import { rotate, adjustXYWithRotation } from "../math";
|
|
|
|
|
|
import {
|
|
|
isWritableElement,
|
|
@@ -1208,6 +1210,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
font: element.font,
|
|
|
opacity: this.state.currentItemOpacity,
|
|
|
zoom: this.state.zoom,
|
|
|
+ angle: element.angle,
|
|
|
onSubmit: (text) => {
|
|
|
if (text) {
|
|
|
globalSceneState.replaceAllElements([
|
|
@@ -1703,6 +1706,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
opacity: this.state.currentItemOpacity,
|
|
|
font: this.state.currentItemFont,
|
|
|
zoom: this.state.zoom,
|
|
|
+ angle: 0,
|
|
|
onSubmit: (text) => {
|
|
|
if (text) {
|
|
|
globalSceneState.replaceAllElements([
|
|
@@ -1974,7 +1978,10 @@ export class App extends React.Component<any, AppState> {
|
|
|
}
|
|
|
|
|
|
if (isResizingElements && this.state.resizingElement) {
|
|
|
- this.setState({ isResizing: true });
|
|
|
+ this.setState({
|
|
|
+ isResizing: resizeHandle !== "rotation",
|
|
|
+ isRotating: resizeHandle === "rotation",
|
|
|
+ });
|
|
|
const el = this.state.resizingElement;
|
|
|
const selectedElements = getSelectedElements(
|
|
|
globalSceneState.getAllElements(),
|
|
@@ -1987,9 +1994,10 @@ export class App extends React.Component<any, AppState> {
|
|
|
this.canvas,
|
|
|
window.devicePixelRatio,
|
|
|
);
|
|
|
- const deltaX = x - lastX;
|
|
|
- const deltaY = y - lastY;
|
|
|
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) {
|
|
@@ -2005,16 +2013,12 @@ export class App extends React.Component<any, AppState> {
|
|
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
} else {
|
|
|
const width = element.width - deltaX;
|
|
|
- const height = event.shiftKey
|
|
|
- ? element.width
|
|
|
- : element.height - deltaY;
|
|
|
+ const height = event.shiftKey ? width : element.height - deltaY;
|
|
|
+ const dY = element.height - height;
|
|
|
mutateElement(element, {
|
|
|
- x: element.x + deltaX,
|
|
|
- y: event.shiftKey
|
|
|
- ? element.y + element.height - element.width
|
|
|
- : element.y + deltaY,
|
|
|
width,
|
|
|
height,
|
|
|
+ ...adjustXYWithRotation("nw", element, deltaX, dY, angle),
|
|
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
|
? {
|
|
|
points: rescalePoints(
|
|
@@ -2041,12 +2045,11 @@ export class App extends React.Component<any, AppState> {
|
|
|
} else {
|
|
|
const width = element.width + deltaX;
|
|
|
const height = event.shiftKey ? width : element.height - deltaY;
|
|
|
+ const dY = element.height - height;
|
|
|
mutateElement(element, {
|
|
|
- y: event.shiftKey
|
|
|
- ? element.y + element.height - width
|
|
|
- : element.y + deltaY,
|
|
|
width,
|
|
|
height,
|
|
|
+ ...adjustXYWithRotation("ne", element, deltaX, dY, angle),
|
|
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
|
? {
|
|
|
points: rescalePoints(
|
|
@@ -2072,13 +2075,12 @@ export class App extends React.Component<any, AppState> {
|
|
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
} else {
|
|
|
const width = element.width - deltaX;
|
|
|
- const height = event.shiftKey
|
|
|
- ? element.width
|
|
|
- : element.height + deltaY;
|
|
|
+ const height = event.shiftKey ? width : element.height + deltaY;
|
|
|
+ const dY = height - element.height;
|
|
|
mutateElement(element, {
|
|
|
- x: element.x + deltaX,
|
|
|
width,
|
|
|
height,
|
|
|
+ ...adjustXYWithRotation("sw", element, deltaX, dY, angle),
|
|
|
...(isLinearElement(element) && width >= 0 && height >= 0
|
|
|
? {
|
|
|
points: rescalePoints(
|
|
@@ -2104,12 +2106,12 @@ export class App extends React.Component<any, AppState> {
|
|
|
resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
} else {
|
|
|
const width = element.width + deltaX;
|
|
|
- const height = event.shiftKey
|
|
|
- ? element.width
|
|
|
- : element.height + deltaY;
|
|
|
+ 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(
|
|
@@ -2133,13 +2135,13 @@ export class App extends React.Component<any, AppState> {
|
|
|
}
|
|
|
mutateElement(element, {
|
|
|
height,
|
|
|
- y: element.y + deltaY,
|
|
|
+ ...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
|
points: rescalePoints(1, height, element.points),
|
|
|
});
|
|
|
} else {
|
|
|
mutateElement(element, {
|
|
|
height,
|
|
|
- y: element.y + deltaY,
|
|
|
+ ...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -2157,13 +2159,13 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
mutateElement(element, {
|
|
|
width,
|
|
|
- x: element.x + deltaX,
|
|
|
+ ...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
|
points: rescalePoints(0, width, element.points),
|
|
|
});
|
|
|
} else {
|
|
|
mutateElement(element, {
|
|
|
width,
|
|
|
- x: element.x + deltaX,
|
|
|
+ ...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
|
});
|
|
|
}
|
|
|
break;
|
|
@@ -2179,11 +2181,13 @@ export class App extends React.Component<any, AppState> {
|
|
|
}
|
|
|
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;
|
|
@@ -2199,15 +2203,32 @@ export class App extends React.Component<any, AppState> {
|
|
|
}
|
|
|
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 += Math.PI / 16;
|
|
|
+ angle -= angle % (Math.PI / 8);
|
|
|
+ }
|
|
|
+ if (angle >= 2 * Math.PI) {
|
|
|
+ angle -= 2 * Math.PI;
|
|
|
+ }
|
|
|
+ mutateElement(element, { angle });
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (resizeHandle) {
|
|
@@ -2351,6 +2372,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
|
|
|
this.setState({
|
|
|
isResizing: false,
|
|
|
+ isRotating: false,
|
|
|
resizingElement: null,
|
|
|
selectionElement: null,
|
|
|
editingElement: multiElement ? this.state.editingElement : null,
|