Rotation support (#1099)

* rotate rectanble with fixed angle

* rotate dashed rectangle with fixed angle

* fix rotate handler rect

* fix canvas size with rotation

* angle in element base

* fix bug in calculating canvas size

* trial only for rectangle

* hitTest for rectangle rotation

* properly resize rotated rectangle

* fix canvas size calculation

* giving up... workaround for now

* **experimental** handler to rotate rectangle

* remove rotation on copy for debugging

* update snapshots

* better rotation handler with atan2

* rotate when drawImage

* add rotation handler

* hitTest for any shapes

* fix hitTest for curved lines

* rotate text element

* rotation locking

* hint messaage for rotating

* show proper handlers on mobile (a workaround, there should be a better way)

* refactor hitTest

* support exporting png

* support exporting svg

* fix rotating curved line

* refactor drawElementFromCanvas with getElementAbsoluteCoords

* fix export png and svg

* adjust resize positions for lines (N, E, S, W)

* do not make handlers big on mobile

* Update src/locales/en.json

Alright!

Co-Authored-By: Lipis <lipiridis@gmail.com>

* do not show rotation/resizing hints on mobile

* proper calculation for N and W positions

* simplify calculation

* use "rotation" as property name for clarification (may increase bundle size)

* update snapshots excluding rotation handle

* refactor with adjustPositionWithRotation

* refactor with adjustXYWithRotation

* forgot to rename rotation

* rename internal function

* initialize element angle on restore

* rotate wysiwyg editor

* fix shift-rotate around 270deg

* improve rotation locking

* refactor adjustXYWithRotation

* avoid rotation degree becomes >=360

* refactor with generateHandler

Co-authored-by: Lipis <lipiridis@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Daishi Kato 2020-04-02 17:40:26 +09:00 committed by GitHub
parent 3e3ce18755
commit 65be7973be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 664 additions and 108 deletions

View file

@ -17,6 +17,8 @@ import { getSelectedElements } from "../scene/selection";
import { renderElement, renderElementToSvg } from "./renderElement";
import colors from "../colors";
type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
function colorsForClientId(clientId: string) {
// Naive way of getting an integer out of the clientId
const sum = clientId.split("").reduce((a, str) => a + str.charCodeAt(0), 0);
@ -26,6 +28,40 @@ function colorsForClientId(clientId: string) {
};
}
function strokeRectWithRotation(
context: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
cx: number,
cy: number,
angle: number,
fill?: boolean,
) {
context.translate(cx, cy);
context.rotate(angle);
if (fill) {
context.fillRect(x - cx, y - cy, width, height);
}
context.strokeRect(x - cx, y - cy, width, height);
context.rotate(-angle);
context.translate(-cx, -cy);
}
function strokeCircle(
context: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
) {
context.beginPath();
context.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2);
context.fill();
context.stroke();
}
export function renderScene(
allElements: readonly ExcalidrawElement[],
appState: AppState,
@ -113,7 +149,7 @@ export function renderScene(
// Pain selected elements
if (renderSelection) {
const selectedElements = getSelectedElements(elements, appState);
const dashledLinePadding = 4 / sceneState.zoom;
const dashedLinePadding = 4 / sceneState.zoom;
context.translate(sceneState.scrollX, sceneState.scrollY);
selectedElements.forEach((element) => {
@ -131,11 +167,15 @@ export function renderScene(
context.setLineDash([8 / sceneState.zoom, 4 / sceneState.zoom]);
const lineWidth = context.lineWidth;
context.lineWidth = 1 / sceneState.zoom;
context.strokeRect(
elementX1 - dashledLinePadding,
elementY1 - dashledLinePadding,
elementWidth + dashledLinePadding * 2,
elementHeight + dashledLinePadding * 2,
strokeRectWithRotation(
context,
elementX1 - dashedLinePadding,
elementY1 - dashedLinePadding,
elementWidth + dashedLinePadding * 2,
elementHeight + dashedLinePadding * 2,
elementX1 + elementWidth / 2,
elementY1 + elementHeight / 2,
element.angle,
);
context.lineWidth = lineWidth;
context.setLineDash(initialLineDash);
@ -143,19 +183,39 @@ export function renderScene(
context.translate(-sceneState.scrollX, -sceneState.scrollY);
// Paint resize handlers
if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
if (selectedElements.length === 1) {
context.translate(sceneState.scrollX, sceneState.scrollY);
context.fillStyle = "#fff";
const handlers = handlerRectangles(selectedElements[0], sceneState.zoom);
Object.values(handlers)
.filter((handler) => handler !== undefined)
.forEach((handler) => {
Object.keys(handlers).forEach((key) => {
const handler = handlers[key as HandlerRectanglesRet];
if (handler !== undefined) {
const lineWidth = context.lineWidth;
context.lineWidth = 1 / sceneState.zoom;
context.fillRect(handler[0], handler[1], handler[2], handler[3]);
context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
if (key === "rotation") {
strokeCircle(
context,
handler[0],
handler[1],
handler[2],
handler[3],
);
} else if (selectedElements[0].type !== "text") {
strokeRectWithRotation(
context,
handler[0],
handler[1],
handler[2],
handler[3],
handler[0] + handler[2] / 2,
handler[1] + handler[3] / 2,
selectedElements[0].angle,
true, // fill before stroke
);
}
context.lineWidth = lineWidth;
});
}
});
context.translate(-sceneState.scrollX, -sceneState.scrollY);
}
}