Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(draw): support rotate resize #WIK-14735 #773

Merged
merged 14 commits into from
Mar 21, 2024
5 changes: 5 additions & 0 deletions .changeset/purple-weeks-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plait/draw': patch
---

feat(draw): support rotate resize
46 changes: 41 additions & 5 deletions packages/common/src/utils/resize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PlaitBoard, Point, RectangleClient, ResizeCursorClass, setDragging } from '@plait/core';
import { PlaitBoard, Point, RectangleClient, ResizeCursorClass, setDragging, RESIZE_CURSORS, rotatePoints } from '@plait/core';
import { ResizeHandle } from '../constants/resize';
import { PlaitElementOrArray, ResizeRef } from '../types/resize';

Expand Down Expand Up @@ -38,7 +38,16 @@ const getResizeCursorClassByIndex = (index: number) => {
}
};

export const getRectangleResizeHandleRefs = (rectangle: RectangleClient, diameter: number) => {
export const getRotatedResizeCursorClassByAngle = (cursor: ResizeCursorClass, angle: number) => {
const index = RESIZE_CURSORS.indexOf(cursor);
if (index >= 0) {
const temp = Math.round(angle / (Math.PI / 4));
cursor = RESIZE_CURSORS[(index + temp) % RESIZE_CURSORS.length] as ResizeCursorClass;
}
return cursor;
};

export const getRectangleResizeHandleRefs = (rectangle: RectangleClient, diameter: number, angle: number = 0) => {
const corners = RectangleClient.getCornerPoints(rectangle);
const refs = corners.map((corner, index: number) => {
return {
Expand All @@ -49,7 +58,7 @@ export const getRectangleResizeHandleRefs = (rectangle: RectangleClient, diamete
height: diameter
},
handle: getResizeHandleByIndex(index) as ResizeHandle,
cursorClass: getResizeCursorClassByIndex(index) as ResizeCursorClass
cursorClass: getRotatedResizeCursorClassByAngle(getResizeCursorClassByIndex(index) as ResizeCursorClass, angle)
};
});
const rectangles = getResizeSideRectangles(corners, diameter / 2);
Expand All @@ -58,7 +67,7 @@ export const getRectangleResizeHandleRefs = (rectangle: RectangleClient, diamete
return {
rectangle,
handle: getResizeHandleByIndex(index + 4) as ResizeHandle,
cursorClass: getResizeCursorClassByIndex(index + 4) as ResizeCursorClass
cursorClass: getRotatedResizeCursorClassByAngle(getResizeCursorClassByIndex(index + 4) as ResizeCursorClass, angle)
};
})
);
Expand Down Expand Up @@ -93,7 +102,10 @@ export const isResizing = (board: PlaitBoard) => {
return !!IS_RESIZING.get(board);
};

export const isResizingByCondition = <T extends PlaitElementOrArray, K>(board: PlaitBoard, match: (resizeRef: ResizeRef<T, K>) => boolean) => {
export const isResizingByCondition = <T extends PlaitElementOrArray, K>(
board: PlaitBoard,
match: (resizeRef: ResizeRef<T, K>) => boolean
) => {
return isResizing(board) && match(IS_RESIZING.get(board)!);
};

Expand Down Expand Up @@ -121,3 +133,27 @@ export const isEdgeHandle = (board: PlaitBoard, handle: ResizeHandle) => {
export const isCornerHandle = (board: PlaitBoard, handle: ResizeHandle) => {
return !isEdgeHandle(board, handle);
};

// 处理元素先旋转后resize导致的位置偏移
export const resetPointsAfterResize = (
originRectangle: RectangleClient,
currentRectangle: RectangleClient,
originSelectionCenterPoint: Point,
currentSelectionCenterPoint: Point,
angle: number
): [Point, Point] => {
const correctSelectionCenterPoint = rotatePoints([currentSelectionCenterPoint], originSelectionCenterPoint, angle)[0];
const rotatedElementCenterPoint = rotatePoints(
[RectangleClient.getCenterPoint(currentRectangle) as Point],
originSelectionCenterPoint,
angle
)[0];

const currentPoints = RectangleClient.getPoints(currentRectangle);
const originRectangleCenterPoint = RectangleClient.getCenterPoint(originRectangle);

const correctElementCenterPoint = rotatePoints([rotatedElementCenterPoint], correctSelectionCenterPoint, -angle)[0];

const rotatedPoints = rotatePoints(currentPoints, originRectangleCenterPoint, angle);
return rotatePoints(rotatedPoints, correctElementCenterPoint, -angle) as [Point, Point];
};
4 changes: 3 additions & 1 deletion packages/core/src/constants/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export enum ResizeCursorClass {

export enum CursorClass {
crosshair = 'crosshair'
}
}

export const RESIZE_CURSORS = [ResizeCursorClass.ns, ResizeCursorClass.nesw, ResizeCursorClass.ew, ResizeCursorClass.nwse];
13 changes: 13 additions & 0 deletions packages/core/src/utils/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ export const getRotatedBoundingRectangle = (rectanglesCornerPoints: [Point, Poin
const centerPoint = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(cornerPointsFromOrigin));
return RectangleClient.getRectangleByPoints(rotatePoints(cornerPointsFromOrigin, centerPoint, -angle));
};

export const getOffsetAfterRotate = (rectangle: RectangleClient, rotateCenterPoint: Point, angle: number) => {
const targetCenterPoint = RectangleClient.getCenterPoint(rectangle);
const [rotatedCenterPoint] = rotatePoints([targetCenterPoint], rotateCenterPoint, angle);
const offsetX = rotatedCenterPoint[0] - targetCenterPoint[0];
const offsetY = rotatedCenterPoint[1] - targetCenterPoint[1];
return { offsetX, offsetY };
};

export const rotatedDataPoints = (points: Point[], rotateCenterPoint: Point, angle: number): Point[] => {
const { offsetX, offsetY } = getOffsetAfterRotate(RectangleClient.getRectangleByPoints(points), rotateCenterPoint, angle);
return points.map(p => [p[0] + offsetX, p[1] + offsetY]) as Point[];
};
73 changes: 69 additions & 4 deletions packages/draw/src/plugins/with-draw-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
getResizeHandlePointByIndex,
getSymmetricHandleIndex,
isCornerHandle,
withResize
withResize,
resetPointsAfterResize
} from '@plait/common';
import {
PlaitBoard,
Expand All @@ -20,13 +21,21 @@ import {
getSelectedElements,
isSelectionMoving,
getSelectionAngle,
rotatePoints
rotatePoints,
rotatedDataPoints
} from '@plait/core';
import { PlaitDrawElement } from '../interfaces';
import { DrawTransforms } from '../transforms';
import { getHitRectangleResizeHandleRef } from '../utils/position/geometry';
import { getResizeAlignRef } from '../utils/resize-align';

export interface BulkRotationRef {
angle: number;
offsetX: number;
offsetY: number;
newCenterPoint: Point;
}

export function withDrawResize(board: PlaitBoard) {
const { afterChange } = board;
let alignG: SVGGElement | null;
Expand All @@ -43,7 +52,8 @@ export function withDrawResize(board: PlaitBoard) {
hitTest: (point: Point) => {
const elements = getSelectedElements(board) as PlaitDrawElement[];
const boundingRectangle = getRectangleByElements(board, elements, false);
const handleRef = getHitRectangleResizeHandleRef(board, boundingRectangle, point);
const angle = getSelectionAngle(elements);
const handleRef = getHitRectangleResizeHandleRef(board, boundingRectangle, point, angle);
if (handleRef) {
return {
element: elements,
Expand All @@ -58,7 +68,28 @@ export function withDrawResize(board: PlaitBoard) {
alignG?.remove();
const isFromCorner = isCornerHandle(board, resizeRef.handle);
const isAspectRatio = resizeState.isShift || isFromCorner;
const centerPoint = RectangleClient.getCenterPoint(resizeRef.rectangle!);
const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, resizeRef);
const angle = getSelectionAngle(resizeRef.element);
let bulkRotationRef: BulkRotationRef | undefined;
if (angle) {
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的三个判断可以写在一起

bulkRotationRef = {
angle: angle,
offsetX: 0,
offsetY: 0,
newCenterPoint: [0, 0]
};
}

if (bulkRotationRef) {
const [rotatedStartPoint, rotateEndPoint] = rotatePoints(
[resizeState.startPoint, resizeState.endPoint],
centerPoint,
-bulkRotationRef.angle
);
resizeState.startPoint = rotatedStartPoint;
resizeState.endPoint = rotateEndPoint;
}

const resizeAlignRef = getResizeAlignRef(
board,
Expand All @@ -73,12 +104,47 @@ export function withDrawResize(board: PlaitBoard) {
);
alignG = resizeAlignRef.alignG;
PlaitBoard.getElementActiveHost(board).append(alignG);

if (bulkRotationRef) {
const boundingBoxCornerPoints = RectangleClient.getPoints(resizeRef.rectangle!);
const resizedBoundingBoxCornerPoints = boundingBoxCornerPoints.map(p => {
return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
});
const newBoundingBox = RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints);
const newBoundingBoxCenter = RectangleClient.getCenterPoint(newBoundingBox);
const adjustedNewBoundingBoxPoints = resetPointsAfterResize(
RectangleClient.getRectangleByPoints(boundingBoxCornerPoints),
RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints),
centerPoint,
newBoundingBoxCenter,
bulkRotationRef.angle
);
const newCenter = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(adjustedNewBoundingBoxPoints));
bulkRotationRef = Object.assign(bulkRotationRef, {
offsetX: newCenter[0] - newBoundingBoxCenter[0],
offsetY: newCenter[1] - newBoundingBoxCenter[1],
newCenterPoint: newCenter
});
}

resizeRef.element.forEach(target => {
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
const path = PlaitBoard.findPath(board, target);
let points = target.points.map(p => {
return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
});

if (bulkRotationRef) {
MissLixf marked this conversation as resolved.
Show resolved Hide resolved
const reversedPoints = rotatedDataPoints(target.points, centerPoint, -bulkRotationRef.angle);
points = reversedPoints.map((p: Point) => {
return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
}) as [Point, Point];
const adjustTargetPoints = points.map(p => [
p[0] + bulkRotationRef!.offsetX,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不需要加 ! 号,前面有 if 判断

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我也这么觉得,但是不加就会报错
image

p[1] + bulkRotationRef!.offsetY
]) as Point[];
points = rotatedDataPoints(adjustTargetPoints, bulkRotationRef.newCenterPoint, bulkRotationRef.angle) as [Point, Point];
}

if (PlaitDrawElement.isGeometry(target)) {
const { height: textHeight } = getFirstTextManage(target).getSize();
DrawTransforms.resizeGeometry(board, points as [Point, Point], textHeight, path);
Expand Down Expand Up @@ -138,7 +204,6 @@ export function withDrawResize(board: PlaitBoard) {
PlaitBoard.getElementActiveHost(board).append(handleG);
}
};

return board;
}

Expand Down
32 changes: 27 additions & 5 deletions packages/draw/src/plugins/with-geometry-resize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Path, PlaitBoard, PlaitElement, Point, RectangleClient, Transforms, getSelectedElements } from '@plait/core';
import { Path, PlaitBoard, PlaitElement, Point, RectangleClient, Transforms, getSelectedElements, rotate, rotatePoints } from '@plait/core';
import { PlaitGeometry } from '../interfaces/geometry';
import {
ResizeRef,
Expand All @@ -7,15 +7,16 @@ import {
getFirstTextManage,
isCornerHandle,
normalizeShapePoints,
withResize
withResize,
resetPointsAfterResize
} from '@plait/common';
import { getSelectedGeometryElements, getSelectedImageElements } from '../utils/selected';
import { DrawTransforms } from '../transforms';
import { GeometryComponent } from '../geometry.component';
import { PlaitImage } from '../interfaces/image';
import { PlaitDrawElement } from '../interfaces';
import { getHitRectangleResizeHandleRef } from '../utils/position/geometry';
import { getResizeOriginPointAndHandlePoint, getResizeZoom, movePointByZoomAndOriginPoint } from './with-draw-resize';
import { getResizeOriginPointAndHandlePoint } from './with-draw-resize';
import { getResizeAlignRef } from '../utils/resize-align';

export const withGeometryResize = (board: PlaitBoard) => {
Expand All @@ -34,7 +35,7 @@ export const withGeometryResize = (board: PlaitBoard) => {
const targetComponent = PlaitElement.getComponent(selectedElements[0]) as GeometryComponent;
if (targetComponent.activeGenerator.hasResizeHandle) {
const rectangle = board.getRectangle(target) as RectangleClient;
const handleRef = getHitRectangleResizeHandleRef(board, rectangle, point);
const handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, target.angle);
if (handleRef) {
return {
element: target,
Expand All @@ -47,11 +48,22 @@ export const withGeometryResize = (board: PlaitBoard) => {
return null;
},
onResize: (resizeRef: ResizeRef<PlaitGeometry | PlaitImage>, resizeState: ResizeState) => {
const centerPoint = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(resizeRef.element.points));
const angle = resizeRef.element.angle;
if (angle) {
const [rotatedStartPoint, rotateEndPoint] = rotatePoints(
[resizeState.startPoint, resizeState.endPoint],
centerPoint,
-resizeRef.element.angle
);
resizeState.startPoint = rotatedStartPoint;
resizeState.endPoint = rotateEndPoint;
}

alignG?.remove();
const isFromCorner = isCornerHandle(board, resizeRef.handle);
const isAspectRatio = resizeState.isShift || PlaitDrawElement.isImage(resizeRef.element);
const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, resizeRef);

const resizeAlignRef = getResizeAlignRef(
board,
resizeRef,
Expand All @@ -66,6 +78,16 @@ export const withGeometryResize = (board: PlaitBoard) => {
alignG = resizeAlignRef.alignG;
PlaitBoard.getElementActiveHost(board).append(alignG);
let points = resizeAlignRef.activePoints as [Point, Point];
if (angle) {
points = resetPointsAfterResize(
resizeRef.rectangle!,
RectangleClient.getRectangleByPoints(points),
centerPoint,
RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(points)),
angle
);
}

if (PlaitDrawElement.isGeometry(resizeRef.element)) {
const { height: textHeight } = getFirstTextManage(resizeRef.element).getSize();
DrawTransforms.resizeGeometry(board, points, textHeight, resizeRef.path as Path);
Expand Down
12 changes: 7 additions & 5 deletions packages/draw/src/utils/position/geometry.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Ancestor, PlaitBoard, Point, RectangleClient, depthFirstRecursion, getIsRecursionFunc } from '@plait/core';
import { Ancestor, PlaitBoard, Point, RectangleClient, depthFirstRecursion, getIsRecursionFunc, rotatePoints } from '@plait/core';
import { PlaitDrawElement, PlaitGeometry } from '../../interfaces';
import { RESIZE_HANDLE_DIAMETER, getRectangleResizeHandleRefs, ResizeHandle } from '@plait/common';
import { RESIZE_HANDLE_DIAMETER, getRectangleResizeHandleRefs } from '@plait/common';
import { getEngine } from '../../engines';
import { PlaitImage } from '../../interfaces/image';
import { getShape } from '../shape';

export const getHitRectangleResizeHandleRef = (board: PlaitBoard, rectangle: RectangleClient, point: Point) => {
const resizeHandleRefs = getRectangleResizeHandleRefs(rectangle, RESIZE_HANDLE_DIAMETER);
export const getHitRectangleResizeHandleRef = (board: PlaitBoard, rectangle: RectangleClient, point: Point, angle: number = 0) => {
const centerPoint = RectangleClient.getCenterPoint(rectangle);
const rotatedPoint = rotatePoints([point], centerPoint, -angle)[0];
const resizeHandleRefs = getRectangleResizeHandleRefs(rectangle, RESIZE_HANDLE_DIAMETER, angle);
const result = resizeHandleRefs.find(resizeHandleRef => {
return RectangleClient.isHit(RectangleClient.getRectangleByPoints([point, point]), resizeHandleRef.rectangle);
return RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), resizeHandleRef.rectangle);
});
return result;
};
Expand Down