Skip to content

Commit

Permalink
feat(core): refactor getHitElementByPoint value #WIK-15721
Browse files Browse the repository at this point in the history
  • Loading branch information
huanhuanwa committed Jun 27, 2024
1 parent 02b4a00 commit ff77e9e
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 84 deletions.
5 changes: 0 additions & 5 deletions .changeset/friendly-ears-switch.md

This file was deleted.

12 changes: 12 additions & 0 deletions .changeset/large-planes-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@plait/common': minor
'@plait/core': minor
'@plait/draw': minor
'@plait/mind': minor
---

refactor getHitElementByPoint return value

add getHitElement to board, the hit element is determined by the plugin


10 changes: 9 additions & 1 deletion packages/common/src/utils/math.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Point, distanceBetweenPointAndPoint } from '@plait/core';
import { PlaitBoard, PlaitElement, Point, distanceBetweenPointAndPoint } from '@plait/core';
import { getPointByVectorDirectionComponent, getUnitVectorByPointAndPoint } from './vector';

export function isPointOnSegment(point: Point, startPoint: Point, endPoint: Point) {
Expand All @@ -22,3 +22,11 @@ export const getCrossingPointsBetweenPointAndSegment = (point: Point, startPoint
}
return result;
};

export const getElementArea = (board: PlaitBoard, element: PlaitElement) => {
const rectangle = board.getRectangle(element);
if (rectangle) {
return rectangle.width * rectangle.height;
}
return 0;
};
1 change: 1 addition & 0 deletions packages/core/src/interfaces/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface PlaitBoard {
// When the element has no fill color, it is considered a hit only if it hits the border.
isHit: (element: PlaitElement, point: Point) => boolean;
isInsidePoint: (element: PlaitElement, point: Point) => boolean;
getHitElement: (hitElements: PlaitElement[]) => PlaitElement;
isRecursion: (element: PlaitElement) => boolean;
isMovable: (element: PlaitElement) => boolean;
getRectangle: (element: PlaitElement) => RectangleClient | null;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/plugins/create-board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export function createBoard(children: PlaitElement[], options?: PlaitBoardOption
isRectangleHit: element => false,
isHit: element => false,
isInsidePoint: element => false,
getHitElement: (data: PlaitElement[]) => data[0],
isRecursion: element => true,
isMovable: element => false,
getRectangle: element => null,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/plugins/with-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ export function withSelection(board: PlaitBoard) {
if (isHitElementWithGroup) {
setSelectedElementsWithGroup(board, elements, isShift);
} else {
if (board.selection && Selection.isCollapsed(board.selection)) {
const element = board.getHitElement(elements);
if (element) {
elements = [element];
}
}
if (isShift) {
const newElements = [...selectedElements];
if (board.selection && Selection.isCollapsed(board.selection)) {
Expand Down
42 changes: 18 additions & 24 deletions packages/core/src/utils/selected-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export const getHitElementsBySelection = (
}
const isCollapsed = Selection.isCollapsed(newSelection);
if (isCollapsed) {
const hitElement = getHitElementByPoint(board, newSelection.anchor, match);
if (hitElement) {
return [hitElement];
const hitElements = getHitElementsByPoint(board, newSelection.anchor, match);
if (hitElements?.length) {
return hitElements;
} else {
return [];
}
Expand All @@ -44,43 +44,37 @@ export const getHitElementsBySelection = (
return rectangleHitElements;
};

export const getHitElementByPoint = (
export const getHitElementsByPoint = (
board: PlaitBoard,
point: Point,
match: (element: PlaitElement) => boolean = () => true
): undefined | PlaitElement => {
let hitElement: PlaitElement | undefined = undefined;
let hitInsideElement: PlaitElement | undefined = undefined;
): PlaitElement[] => {
let hitElements: PlaitElement[] = [];
depthFirstRecursion<Ancestor>(
board,
node => {
if (hitElement) {
return;
}
if (PlaitBoard.isBoard(node) || !match(node) || !PlaitElement.hasMounted(node)) {
return;
}
if (board.isHit(node, point)) {
hitElement = node;
hitElements.push(node);
return;
}

/**
* 需要增加场景测试
* hitInsideElement 存的是第一个符合 isInsidePoint 的元素
* 当有元素符合 isHit 时结束遍历,并返回 hitElement
* 当所有元素都不符合 isHit ,则返回第一个符合 isInsidePoint 的元素
* 这样保证最上面的元素优先被探测到;
*/

if (!hitInsideElement && board.isInsidePoint(node, point)) {
hitInsideElement = node;
}
},
getIsRecursionFunc(board),
true
);
return hitElement || hitInsideElement;
return hitElements;
};

export const getHitElementByPoint = (
board: PlaitBoard,
point: Point,
match: (element: PlaitElement) => boolean = () => true
): undefined | PlaitElement => {
const pointHitElements = getHitElementsByPoint(board, point, match);
const hitElement = board.getHitElement(pointHitElements);
return hitElement;
};

export const getHitSelectedElements = (board: PlaitBoard, point: Point) => {
Expand Down
9 changes: 7 additions & 2 deletions packages/draw/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ export const PlaitDrawElement = {
}
},
isShapeElement: (value: any): value is PlaitShapeElement => {
return PlaitDrawElement.isImage(value) || PlaitDrawElement.isGeometry(value) || PlaitDrawElement.isTable(value) || PlaitDrawElement.isSwimlane(value);
return (
PlaitDrawElement.isImage(value) ||
PlaitDrawElement.isGeometry(value) ||
PlaitDrawElement.isTable(value) ||
PlaitDrawElement.isSwimlane(value)
);
},
isBasicShape: (value: any) => {
return Object.keys(BasicShapes).includes(value.shape);
Expand Down Expand Up @@ -77,5 +82,5 @@ export const PlaitDrawElement = {
},
isElementByTable: (value: any): value is PlaitBaseTable => {
return PlaitDrawElement.isTable(value) || PlaitDrawElement.isSwimlane(value) || PlaitDrawElement.isGeometryByTable(value);
},
}
};
22 changes: 20 additions & 2 deletions packages/draw/src/plugins/with-draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@ import { withLineAutoCompleteReaction } from './with-line-auto-complete-reaction
import { withLineAutoComplete } from './with-line-auto-complete';
import { withLineTextMove } from './with-line-text-move';
import { withDrawResize } from './with-draw-resize';
import { isHitDrawElement, isHitElementInside, isRectangleHitDrawElement } from '../utils/hit';
import { getDrawHitElement, isHitDrawElement, isHitElementInside, isRectangleHitDrawElement } from '../utils/hit';
import { getLinePoints, getLineTextRectangle } from '../utils/line/line-basic';
import { withDrawRotate } from './with-draw-rotate';
import { withTable } from './with-table';
import { withSwimlane } from './with-swimlane';

export const withDraw = (board: PlaitBoard) => {
const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment } = board;
const {
drawElement,
getRectangle,
isRectangleHit,
isHit,
isInsidePoint,
isMovable,
isAlign,
getRelatedFragment,
getHitElement
} = board;

board.drawElement = (context: PlaitPluginElementContext) => {
if (PlaitDrawElement.isGeometry(context.element)) {
Expand Down Expand Up @@ -73,6 +83,14 @@ export const withDraw = (board: PlaitBoard) => {
return isHit(element, point);
};

board.getHitElement = elements => {
const isDrawElements = elements.every(item => PlaitDrawElement.isDrawElement(item));
if (isDrawElements) {
return getDrawHitElement(board, elements as PlaitDrawElement[]);
}
return getHitElement(elements);
};

board.isInsidePoint = (element: PlaitElement, point: Point) => {
const result = isHitElementInside(board, element, point);
if (result !== null) {
Expand Down
91 changes: 44 additions & 47 deletions packages/draw/src/utils/hit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import {
rotateAntiPointsByElement
} from '@plait/core';
import { PlaitCommonGeometry, PlaitDrawElement, PlaitGeometry, PlaitLine, PlaitShapeElement } from '../interfaces';
import { getElementsText, TRANSPARENT } from '@plait/common';
import { getNearestPoint } from './geometry';
import { getLinePoints } from './line/line-basic';
import { getFillByElement } from './style/stroke';
import { DefaultDrawStyle } from '../constants/geometry';
import { getEngine } from '../engines';
import { getElementShape } from './shape';
import { getHitLineTextIndex } from './position/line';
import { getTextRectangle } from './common';
import { isMultipleTextGeometry } from './multi-text-geometry';
import { getElementArea, TRANSPARENT } from '@plait/common';
import { DefaultDrawStyle } from '../constants';

export const isTextExceedingBounds = (geometry: PlaitGeometry) => {
const client = RectangleClient.getRectangleByPoints(geometry.points);
Expand All @@ -46,24 +46,6 @@ export const isHitLine = (board: PlaitBoard, element: PlaitLine, point: Point) =
return isHitText || isHitPolyLine(points, point);
};

export const isHitElementText = (element: PlaitCommonGeometry, point: Point) => {
const engine = getEngine<PlaitCommonGeometry>(element.shape);
const text = getElementsText([element]);
if (!text) {
return false;
}
if (isMultipleTextGeometry(element)) {
const texts = element.texts;
return texts.some(item => {
const textClient = engine.getTextRectangle!(element, { key: item.key });
return RectangleClient.isPointInRectangle(textClient, point);
});
} else {
const textClient = engine.getTextRectangle ? engine.getTextRectangle(element) : getTextRectangle(element);
return RectangleClient.isPointInRectangle(textClient, point);
}
};

export const isRectangleHitElementText = (element: PlaitCommonGeometry, rectangle: RectangleClient) => {
const engine = getEngine<PlaitCommonGeometry>(element.shape);
if (isMultipleTextGeometry(element)) {
Expand Down Expand Up @@ -109,35 +91,57 @@ export const isRectangleHitDrawElement = (board: PlaitBoard, element: PlaitEleme
return null;
};

export const getDrawHitElement = (board: PlaitBoard, elements: PlaitDrawElement[]) => {
let firstFilledElement = getFirstFilledDrawElement(board, elements);
let endIndex = elements.length;
if (firstFilledElement) {
endIndex = elements.indexOf(firstFilledElement) + 1;
}
const newElements = elements.slice(0, endIndex);
const texts = newElements.filter(item => PlaitDrawElement.isText(item));
if (texts.length) {
return texts[0];
}
const lines = newElements.filter(item => PlaitDrawElement.isLine(item));
if (lines.length) {
return lines[0];
}
const shapeElements = newElements.filter(item => PlaitDrawElement.isShapeElement(item)) as PlaitShapeElement[];
const sortElements = shapeElements.sort((a, b) => {
return getElementArea(board, a) - getElementArea(board, b);
});
return sortElements[0];
};

export const getFirstFilledDrawElement = (board: PlaitBoard, elements: PlaitDrawElement[]) => {
let filledElement: PlaitGeometry | null = null;
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (PlaitDrawElement.isGeometry(element) && !PlaitDrawElement.isText(element)) {
const fill = getFillByElement(board, element);
if (fill !== DefaultDrawStyle.fill && fill !== TRANSPARENT) {
filledElement = element as PlaitGeometry;
break;
}
}
}
return filledElement;
};

export const isHitDrawElement = (board: PlaitBoard, element: PlaitElement, point: Point) => {
const rectangle = board.getRectangle(element);
point = rotateAntiPointsByElement(point, element) || point;
if (PlaitDrawElement.isGeometry(element)) {
const fill = getFillByElement(board, element);
if (isHitEdgeOfShape(board, element, point, HIT_DISTANCE_BUFFER)) {
return true;
}
const engine = getEngine(getElementShape(element));
// when shape equals text, fill is not allowed
if (fill !== DefaultDrawStyle.fill && fill !== TRANSPARENT && !PlaitDrawElement.isText(element)) {
const isHitInside = engine.isInsidePoint(rectangle!, point);
if (isHitInside) {
return isHitInside;
}
} else {
// if shape equals text, only check text rectangle
if (PlaitDrawElement.isText(element)) {
const textClient = getTextRectangle(element);
let isHitText = RectangleClient.isPointInRectangle(textClient, point);
return isHitText;
}

// check textRectangle of element
const isHitText = isHitElementText(element, point);
if (isHitText) {
return isHitText;
}
if (PlaitDrawElement.isText(element)) {
const textClient = getTextRectangle(element);
return RectangleClient.isPointInRectangle(textClient, point);
}

return engine.isInsidePoint(rectangle!, point);
}
if (PlaitDrawElement.isImage(element)) {
const client = RectangleClient.getRectangleByPoints(element.points);
Expand Down Expand Up @@ -169,13 +173,6 @@ export const isHitElementInside = (board: PlaitBoard, element: PlaitElement, poi
if (isHitInside) {
return isHitInside;
}

if (engine.getTextRectangle) {
const isHitText = isHitElementText(element, point);
if (isHitText) {
return isHitText;
}
}
}
if (PlaitDrawElement.isImage(element)) {
const client = RectangleClient.getRectangleByPoints(element.points);
Expand Down
11 changes: 10 additions & 1 deletion packages/mind/src/plugins/with-mind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export const withMind = (baseBoard: PlaitBoard) => {
isImageBindingAllowed,
canAddToGroup,
canSetZIndex,
isExpanded
isExpanded,
getHitElement
} = board;

board.drawElement = (context: PlaitPluginElementContext) => {
Expand Down Expand Up @@ -114,6 +115,14 @@ export const withMind = (baseBoard: PlaitBoard) => {
return isHit(element, point);
};

board.getHitElement = elements => {
const isMindElements = elements.every(item => MindElement.isMindElement(board, item));
if (isMindElements) {
return elements[0];
}
return getHitElement(elements);
};

board.isMovable = element => {
if (PlaitMind.isMind(element) && element.isRoot) {
return true;
Expand Down
Loading

0 comments on commit ff77e9e

Please sign in to comment.