mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Move path related function into the math package
This commit is contained in:
parent
1b56cf90fb
commit
9eb08df3ea
9 changed files with 72 additions and 46 deletions
|
@ -15,8 +15,7 @@ import { isBindingElement, isLinearElement } from "../element/typeChecks";
|
|||
import type { AppState } from "../types";
|
||||
import { resetCursor } from "../cursor";
|
||||
import { StoreAction } from "../store";
|
||||
import { point } from "../../math";
|
||||
import { isPathALoop } from "../shapes";
|
||||
import { pathIsALoop, point } from "../../math";
|
||||
|
||||
export const actionFinalize = register({
|
||||
name: "finalize",
|
||||
|
@ -104,7 +103,7 @@ export const actionFinalize = register({
|
|||
// If the multi point line closes the loop,
|
||||
// set the last point to first point.
|
||||
// This ensures that loop remains closed at different scales.
|
||||
const isLoop = isPathALoop(multiPointElement.points, appState.zoom.value);
|
||||
const isLoop = pathIsALoop(multiPointElement.points, appState.zoom.value);
|
||||
if (
|
||||
multiPointElement.type === "line" ||
|
||||
multiPointElement.type === "freedraw"
|
||||
|
|
|
@ -229,7 +229,6 @@ import {
|
|||
getBoundTextShape,
|
||||
getCornerRadius,
|
||||
getElementShape,
|
||||
isPathALoop,
|
||||
} from "../shapes";
|
||||
import { getSelectionBoxShape } from "../../utils/geometry/shape";
|
||||
import { isPointInShape } from "../../utils/collision";
|
||||
|
@ -449,6 +448,7 @@ import type {
|
|||
ViewportPoint,
|
||||
} from "../../math";
|
||||
import {
|
||||
pathIsALoop,
|
||||
point,
|
||||
pointCenter,
|
||||
pointDistance,
|
||||
|
@ -5605,7 +5605,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
));
|
||||
}
|
||||
|
||||
if (isPathALoop(points, this.state.zoom.value)) {
|
||||
if (
|
||||
pathIsALoop(points, LINE_CONFIRM_THRESHOLD / this.state.zoom.value)
|
||||
) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
}
|
||||
if (isElbowArrow(multiElement)) {
|
||||
|
@ -7206,7 +7208,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// finalize if completing a loop
|
||||
if (
|
||||
multiElement.type === "line" &&
|
||||
isPathALoop(multiElement.points, this.state.zoom.value)
|
||||
pathIsALoop(
|
||||
multiElement.points,
|
||||
LINE_CONFIRM_THRESHOLD / this.state.zoom.value,
|
||||
)
|
||||
) {
|
||||
mutateElement(multiElement, {
|
||||
lastCommittedPoint:
|
||||
|
|
|
@ -15,9 +15,10 @@ import {
|
|||
isImageElement,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { getBoundTextShape, isPathALoop } from "../shapes";
|
||||
import { getBoundTextShape } from "../shapes";
|
||||
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
|
||||
import { isPointWithinBounds, point } from "../../math";
|
||||
import { pathIsALoop, isPointWithinBounds, point } from "../../math";
|
||||
import { LINE_CONFIRM_THRESHOLD } from "../constants";
|
||||
|
||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||
if (element.type === "arrow") {
|
||||
|
@ -31,11 +32,17 @@ export const shouldTestInside = (element: ExcalidrawElement) => {
|
|||
isTextElement(element);
|
||||
|
||||
if (element.type === "line") {
|
||||
return isDraggableFromInside && isPathALoop(element.points);
|
||||
return (
|
||||
isDraggableFromInside &&
|
||||
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)
|
||||
);
|
||||
}
|
||||
|
||||
if (element.type === "freedraw") {
|
||||
return isDraggableFromInside && isPathALoop(element.points);
|
||||
return (
|
||||
isDraggableFromInside &&
|
||||
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)
|
||||
);
|
||||
}
|
||||
|
||||
return isDraggableFromInside || isImageElement(element);
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
} from "./typeChecks";
|
||||
import { KEYS, shouldRotateWithDiscreteAngle } from "../keys";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import { DRAGGING_THRESHOLD } from "../constants";
|
||||
import { DRAGGING_THRESHOLD, LINE_CONFIRM_THRESHOLD } from "../constants";
|
||||
import type { Mutable } from "../utility-types";
|
||||
import { ShapeCache } from "../scene/ShapeCache";
|
||||
import type { Store } from "../store";
|
||||
|
@ -57,12 +57,12 @@ import {
|
|||
pointDistance,
|
||||
pointSubtract,
|
||||
pointFromPair,
|
||||
pathIsALoop,
|
||||
} from "../../math";
|
||||
import {
|
||||
getBezierCurveLength,
|
||||
getBezierXY,
|
||||
getControlPointsForBezierCurve,
|
||||
isPathALoop,
|
||||
mapIntervalToBezierT,
|
||||
} from "../shapes";
|
||||
import { getGridPoint } from "../snapping";
|
||||
|
@ -418,7 +418,12 @@ export class LinearElementEditor {
|
|||
selectedPoint === 0 ||
|
||||
selectedPoint === element.points.length - 1
|
||||
) {
|
||||
if (isPathALoop(element.points, appState.zoom.value)) {
|
||||
if (
|
||||
pathIsALoop(
|
||||
element.points,
|
||||
LINE_CONFIRM_THRESHOLD / appState.zoom.value,
|
||||
)
|
||||
) {
|
||||
LinearElementEditor.movePoints(
|
||||
element,
|
||||
[
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { Drawable } from "roughjs/bin/core";
|
|||
import type { RoughSVG } from "roughjs/bin/svg";
|
||||
import {
|
||||
FRAME_STYLE,
|
||||
LINE_CONFIRM_THRESHOLD,
|
||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||
MIME_TYPES,
|
||||
SVG_NS,
|
||||
|
@ -36,7 +37,8 @@ import type { AppState, BinaryFiles } from "../types";
|
|||
import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
||||
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
||||
import { getVerticalOffset } from "../fonts";
|
||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||
import { getCornerRadius } from "../shapes";
|
||||
import { pathIsALoop } from "../../math";
|
||||
|
||||
const roughSVGDrawWithPrecision = (
|
||||
rsvg: RoughSVG,
|
||||
|
@ -341,7 +343,7 @@ const renderElementToSvg = (
|
|||
);
|
||||
if (
|
||||
element.type === "line" &&
|
||||
isPathALoop(element.points) &&
|
||||
pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD) &&
|
||||
element.backgroundColor !== "transparent"
|
||||
) {
|
||||
node.setAttribute("fill-rule", "evenodd");
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
import { generateFreeDrawShape } from "../renderer/renderElement";
|
||||
import { isTransparent, assertNever } from "../utils";
|
||||
import { simplify } from "points-on-curve";
|
||||
import { ROUGHNESS } from "../constants";
|
||||
import { LINE_CONFIRM_THRESHOLD, ROUGHNESS } from "../constants";
|
||||
import {
|
||||
isElbowArrow,
|
||||
isEmbeddableElement,
|
||||
|
@ -24,12 +24,13 @@ import {
|
|||
import { canChangeRoundness } from "./comparisons";
|
||||
import type { EmbedsValidationStatus } from "../types";
|
||||
import {
|
||||
pathIsALoop,
|
||||
point,
|
||||
pointDistance,
|
||||
type GlobalPoint,
|
||||
type LocalPoint,
|
||||
} from "../../math";
|
||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||
import { getCornerRadius } from "../shapes";
|
||||
|
||||
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
||||
|
||||
|
@ -107,7 +108,7 @@ export const generateRoughOptions = (
|
|||
}
|
||||
case "line":
|
||||
case "freedraw": {
|
||||
if (isPathALoop(element.points)) {
|
||||
if (pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)) {
|
||||
options.fillStyle = element.fillStyle;
|
||||
options.fill =
|
||||
element.backgroundColor === "transparent"
|
||||
|
@ -473,7 +474,7 @@ export const _generateElementShape = (
|
|||
let shape: ElementShapes[typeof element.type];
|
||||
generateFreeDrawShape(element);
|
||||
|
||||
if (isPathALoop(element.points)) {
|
||||
if (pathIsALoop(element.points, LINE_CONFIRM_THRESHOLD)) {
|
||||
// generate rough polygon to fill freedraw shape
|
||||
const simplifiedPoints = simplify(element.points, 0.75);
|
||||
shape = generator.curve(simplifiedPoints as [number, number][], {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ViewportPoint } from "../math";
|
||||
import type { GenericPoint, ViewportPoint } from "../math";
|
||||
import {
|
||||
isPoint,
|
||||
point,
|
||||
|
@ -33,7 +33,6 @@ import {
|
|||
import {
|
||||
DEFAULT_ADAPTIVE_RADIUS,
|
||||
DEFAULT_PROPORTIONAL_RADIUS,
|
||||
LINE_CONFIRM_THRESHOLD,
|
||||
ROUNDNESS,
|
||||
} from "./constants";
|
||||
import { getElementAbsoluteCoords } from "./element";
|
||||
|
@ -49,7 +48,6 @@ import type {
|
|||
} from "./element/types";
|
||||
import { KEYS } from "./keys";
|
||||
import { ShapeCache } from "./scene/ShapeCache";
|
||||
import type { NormalizedZoomValue, Zoom } from "./types";
|
||||
import { invariant } from "./utils";
|
||||
|
||||
export const SHAPES = [
|
||||
|
@ -222,9 +220,7 @@ export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
|||
return null;
|
||||
};
|
||||
|
||||
export const getControlPointsForBezierCurve = <
|
||||
P extends GlobalPoint | LocalPoint,
|
||||
>(
|
||||
export const getControlPointsForBezierCurve = <P extends GenericPoint>(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
endPoint: P,
|
||||
) => {
|
||||
|
@ -266,7 +262,7 @@ export const getControlPointsForBezierCurve = <
|
|||
return controlPoints;
|
||||
};
|
||||
|
||||
export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
|
||||
export const getBezierXY = <P extends GenericPoint>(
|
||||
p0: P,
|
||||
p1: P,
|
||||
p2: P,
|
||||
|
@ -283,7 +279,7 @@ export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
|
|||
return point(tx, ty);
|
||||
};
|
||||
|
||||
const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
||||
const getPointsInBezierCurve = <P extends GenericPoint>(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
endPoint: P,
|
||||
) => {
|
||||
|
@ -313,7 +309,7 @@ const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
|||
return pointsOnCurve;
|
||||
};
|
||||
|
||||
const getBezierCurveArcLengths = <P extends GlobalPoint | LocalPoint>(
|
||||
const getBezierCurveArcLengths = <P extends GenericPoint>(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
endPoint: P,
|
||||
) => {
|
||||
|
@ -476,21 +472,3 @@ export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
|
|||
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Checks if the first and last point are close enough
|
||||
// to be considered a loop
|
||||
export const isPathALoop = (
|
||||
points: ExcalidrawLinearElement["points"],
|
||||
/** supply if you want the loop detection to account for current zoom */
|
||||
zoomValue: Zoom["value"] = 1 as NormalizedZoomValue,
|
||||
): boolean => {
|
||||
if (points.length >= 3) {
|
||||
const [first, last] = [points[0], points[points.length - 1]];
|
||||
const distance = pointDistance(first, last);
|
||||
|
||||
// Adjusting LINE_CONFIRM_THRESHOLD to current zoom so that when zoomed in
|
||||
// really close we make the threshold smaller, and vice versa.
|
||||
return distance <= LINE_CONFIRM_THRESHOLD / zoomValue;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
@ -2,9 +2,11 @@ export * from "./arc";
|
|||
export * from "./angle";
|
||||
export * from "./curve";
|
||||
export * from "./line";
|
||||
export * from "./path";
|
||||
export * from "./point";
|
||||
export * from "./polygon";
|
||||
export * from "./range";
|
||||
export * from "./rectangle";
|
||||
export * from "./segment";
|
||||
export * from "./triangle";
|
||||
export * from "./types";
|
||||
|
|
27
packages/math/path.ts
Normal file
27
packages/math/path.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { pointDistance } from "./point";
|
||||
import type { LocalPoint } from "./types";
|
||||
|
||||
/**
|
||||
* Checks if the first and last point are close enough to be considered a loop
|
||||
*
|
||||
* @param points
|
||||
* @param threshold
|
||||
* @returns
|
||||
*/
|
||||
export const pathIsALoop = (
|
||||
points: readonly LocalPoint[],
|
||||
/** supply if you want the loop detection to account for current zoom */
|
||||
threshold: number,
|
||||
//zoomValue: Zoom["value"] = 1 as NormalizedZoomValue,
|
||||
): boolean => {
|
||||
if (points.length >= 3) {
|
||||
const [first, last] = [points[0], points[points.length - 1]];
|
||||
const distance = pointDistance(first, last);
|
||||
|
||||
// Adjusting LINE_CONFIRM_THRESHOLD to current zoom so that when zoomed in
|
||||
// really close we make the threshold smaller, and vice versa.
|
||||
|
||||
return distance <= threshold; // LINE_CONFIRM_THRESHOLD / zoomValue;
|
||||
}
|
||||
return false;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue