feat: Orthogonal (elbow) arrows for diagramming (#8299)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Márk Tolmács 2024-08-01 18:39:03 +02:00 committed by GitHub
parent a133a70e87
commit 15e019706d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
69 changed files with 5415 additions and 1144 deletions

View file

@ -22,6 +22,7 @@ import {
import {
isArrowElement,
isBoundToContainer,
isElbowArrow,
isFrameLikeElement,
isFreeDrawElement,
isImageElement,
@ -30,7 +31,7 @@ import {
} from "./typeChecks";
import { mutateElement } from "./mutateElement";
import { getFontString } from "../utils";
import { updateBoundElements } from "./binding";
import { getArrowLocalFixedPoints, updateBoundElements } from "./binding";
import type {
MaybeTransformHandleType,
TransformHandleDirection,
@ -51,6 +52,7 @@ import {
} from "./textElement";
import { LinearElementEditor } from "./linearElementEditor";
import { isInGroup } from "../groups";
import { mutateElbowArrow } from "./routing";
export const normalizeAngle = (angle: number): number => {
if (angle < 0) {
@ -75,18 +77,21 @@ export const transformElements = (
pointerY: number,
centerX: number,
centerY: number,
scene: Scene,
) => {
if (selectedElements.length === 1) {
const [element] = selectedElements;
if (transformHandleType === "rotation") {
rotateSingleElement(
element,
elementsMap,
pointerX,
pointerY,
shouldRotateWithDiscreteAngle,
);
updateBoundElements(element, elementsMap);
if (!isElbowArrow(element)) {
rotateSingleElement(
element,
elementsMap,
pointerX,
pointerY,
shouldRotateWithDiscreteAngle,
);
updateBoundElements(element, elementsMap, scene);
}
} else if (isTextElement(element) && transformHandleType) {
resizeSingleTextElement(
originalElements,
@ -97,7 +102,7 @@ export const transformElements = (
pointerX,
pointerY,
);
updateBoundElements(element, elementsMap);
updateBoundElements(element, elementsMap, scene);
} else if (transformHandleType) {
resizeSingleElement(
originalElements,
@ -108,6 +113,7 @@ export const transformElements = (
shouldResizeFromCenter,
pointerX,
pointerY,
scene,
);
}
@ -123,6 +129,7 @@ export const transformElements = (
shouldRotateWithDiscreteAngle,
centerX,
centerY,
scene,
);
return true;
} else if (transformHandleType) {
@ -135,6 +142,7 @@ export const transformElements = (
shouldMaintainAspectRatio,
pointerX,
pointerY,
scene,
);
return true;
}
@ -431,7 +439,17 @@ export const resizeSingleElement = (
shouldResizeFromCenter: boolean,
pointerX: number,
pointerY: number,
scene: Scene,
) => {
// Elbow arrows cannot be resized when bound on either end
if (
isArrowElement(element) &&
isElbowArrow(element) &&
(element.startBinding || element.endBinding)
) {
return;
}
const stateAtResizeStart = originalElements.get(element.id)!;
// Gets bounds corners
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
@ -701,8 +719,11 @@ export const resizeSingleElement = (
) {
mutateElement(element, resizedElement);
updateBoundElements(element, elementsMap, {
newSize: { width: resizedElement.width, height: resizedElement.height },
updateBoundElements(element, elementsMap, scene, {
oldSize: {
width: stateAtResizeStart.width,
height: stateAtResizeStart.height,
},
});
if (boundTextElement && boundTextFont != null) {
@ -728,6 +749,7 @@ export const resizeMultipleElements = (
shouldMaintainAspectRatio: boolean,
pointerX: number,
pointerY: number,
scene: Scene,
) => {
// map selected elements to the original elements. While it never should
// happen that pointerDownState.originalElements won't contain the selected
@ -955,13 +977,20 @@ export const resizeMultipleElements = (
element,
update: { boundTextFontSize, ...update },
} of elementsAndUpdates) {
const { width, height, angle } = update;
const { angle } = update;
const { width: oldWidth, height: oldHeight } = element;
mutateElement(element, update, false);
updateBoundElements(element, elementsMap, {
if (isArrowElement(element) && isElbowArrow(element)) {
mutateElbowArrow(element, scene, element.points, undefined, undefined, {
informMutation: false,
});
}
updateBoundElements(element, elementsMap, scene, {
simultaneouslyUpdated: elementsToUpdate,
newSize: { width, height },
oldSize: { width: oldWidth, height: oldHeight },
});
const boundTextElement = getBoundTextElement(element, elementsMap);
@ -990,6 +1019,7 @@ const rotateMultipleElements = (
shouldRotateWithDiscreteAngle: boolean,
centerX: number,
centerY: number,
scene: Scene,
) => {
let centerAngle =
(5 * Math.PI) / 2 + Math.atan2(pointerY - centerY, pointerX - centerX);
@ -1013,16 +1043,23 @@ const rotateMultipleElements = (
centerY,
centerAngle + origAngle - element.angle,
);
mutateElement(
element,
{
x: element.x + (rotatedCX - cx),
y: element.y + (rotatedCY - cy),
angle: normalizeAngle(centerAngle + origAngle),
},
false,
);
updateBoundElements(element, elementsMap, {
if (isArrowElement(element) && isElbowArrow(element)) {
const points = getArrowLocalFixedPoints(element, elementsMap);
mutateElbowArrow(element, scene, points);
} else {
mutateElement(
element,
{
x: element.x + (rotatedCX - cx),
y: element.y + (rotatedCY - cy),
angle: normalizeAngle(centerAngle + origAngle),
},
false,
);
}
updateBoundElements(element, elementsMap, scene, {
simultaneouslyUpdated: elements,
});