import type { KonvaEventObject } from "konva/lib/Node";
|
import type { Box, Transformer } from "konva/lib/shapes/Transformer";
|
import type { WorkingArea } from "./types";
|
|
// define several math function
|
export const getCorner = (pivotX: number, pivotY: number, diffX: number, diffY: number, angle: number) => {
|
const distance = Math.sqrt(diffX * diffX + diffY * diffY);
|
|
/// find angle from pivot to corner
|
angle += Math.atan2(diffY, diffX);
|
|
/// get new x and y and round it off to integer
|
const x = pivotX + distance * Math.cos(angle);
|
const y = pivotY + distance * Math.sin(angle);
|
|
return { x, y };
|
};
|
|
export const getClientRect = (rotatedBox: Box) => {
|
const { x, y, width, height } = rotatedBox;
|
const rad = rotatedBox.rotation;
|
|
const p1 = getCorner(x, y, 0, 0, rad);
|
const p2 = getCorner(x, y, width, 0, rad);
|
const p3 = getCorner(x, y, width, height, rad);
|
const p4 = getCorner(x, y, 0, height, rad);
|
|
const minX = Math.min(p1.x, p2.x, p3.x, p4.x);
|
const minY = Math.min(p1.y, p2.y, p3.y, p4.y);
|
const maxX = Math.max(p1.x, p2.x, p3.x, p4.x);
|
const maxY = Math.max(p1.y, p2.y, p3.y, p4.y);
|
|
return {
|
x: minX,
|
y: minY,
|
width: maxX - minX,
|
height: maxY - minY,
|
};
|
};
|
|
export const getCommonBBox = (
|
boxes: {
|
x: number;
|
y: number;
|
width: number;
|
height: number;
|
}[],
|
) => {
|
let minX = Number.POSITIVE_INFINITY;
|
let minY = Number.POSITIVE_INFINITY;
|
let maxX = Number.NEGATIVE_INFINITY;
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
boxes.forEach((box) => {
|
minX = Math.min(minX, box.x);
|
minY = Math.min(minY, box.y);
|
maxX = Math.max(maxX, box.x + box.width);
|
maxY = Math.max(maxY, box.y + box.height);
|
});
|
|
return {
|
x: minX,
|
y: minY,
|
width: maxX - minX,
|
height: maxY - minY,
|
};
|
};
|
|
export const createBoundingBoxGetter =
|
(workingArea: WorkingArea, enabled = true) =>
|
(oldBox: Box, newBox: Box) => {
|
if (!enabled) return newBox;
|
|
const box = getClientRect(newBox);
|
const result = { ...newBox };
|
|
const edgeReached = [
|
box.x <= workingArea.x, // x0
|
box.y <= workingArea.y, // y0
|
box.x + box.width >= workingArea.x + workingArea.width, // x1
|
box.y + box.height >= workingArea.y + workingArea.height, // y1
|
];
|
|
// If any edge is caught, stop the movement
|
if (edgeReached.some(Boolean)) {
|
return oldBox;
|
}
|
|
return result;
|
};
|
|
export const createOnDragMoveHandler = (workingArea: WorkingArea, enabled = true) =>
|
function (this: Transformer, e: KonvaEventObject<DragEvent>) {
|
if (!enabled) return;
|
|
const nodes = this?.nodes ? this.nodes() : [e.target];
|
const boxes = nodes.map((node) => node.getClientRect());
|
const box = getCommonBBox(boxes);
|
|
nodes.forEach((shape) => {
|
const absPos = shape.getAbsolutePosition();
|
// where are shapes inside bounding box of all shapes?
|
const offsetX = box.x - workingArea.x - absPos.x;
|
const offsetY = box.y - workingArea.y - absPos.y;
|
|
// we total box goes outside of viewport, we need to move absolute position of shape
|
const newAbsPos = { ...absPos };
|
|
if (box.x - workingArea.x < 0) {
|
newAbsPos.x = -offsetX;
|
}
|
if (box.y - workingArea.y < 0) {
|
newAbsPos.y = -offsetY;
|
}
|
if (box.x - workingArea.x + box.width > workingArea.width) {
|
newAbsPos.x = workingArea.width - box.width - offsetX;
|
}
|
if (box.y - workingArea.y + box.height > workingArea.height) {
|
newAbsPos.y = workingArea.height - box.height - offsetY;
|
}
|
shape.setAbsolutePosition(newAbsPos);
|
});
|
};
|