mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge 60907a5a0e
into debf2ad608
This commit is contained in:
commit
741ddd4e16
48 changed files with 860 additions and 851 deletions
|
@ -424,7 +424,7 @@ export default function ExampleApp({
|
||||||
if (!excalidrawAPI) {
|
if (!excalidrawAPI) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const [x, y] = viewportCoordsToSceneCoords(
|
||||||
{
|
{
|
||||||
clientX: event.clientX - pointerDownState.hitElementOffsets.x,
|
clientX: event.clientX - pointerDownState.hitElementOffsets.x,
|
||||||
clientY: event.clientY - pointerDownState.hitElementOffsets.y,
|
clientY: event.clientY - pointerDownState.hitElementOffsets.y,
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
import {
|
import { type GenericPoint, pointFromPair } from "@excalidraw/math";
|
||||||
pointFromPair,
|
import { pointFrom } from "@excalidraw/math";
|
||||||
type GlobalPoint,
|
|
||||||
type LocalPoint,
|
|
||||||
} from "@excalidraw/math";
|
|
||||||
|
|
||||||
import type { NullableGridSize } from "@excalidraw/excalidraw/types";
|
import type { NullableGridSize } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
export const getSizeFromPoints = (
|
export const getSizeFromPoints = (points: readonly GenericPoint[]) => {
|
||||||
points: readonly (GlobalPoint | LocalPoint)[],
|
|
||||||
) => {
|
|
||||||
const xs = points.map((point) => point[0]);
|
const xs = points.map((point) => point[0]);
|
||||||
const ys = points.map((point) => point[1]);
|
const ys = points.map((point) => point[1]);
|
||||||
return {
|
return {
|
||||||
|
@ -18,7 +13,7 @@ export const getSizeFromPoints = (
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @arg dimension, 0 for rescaling only x, 1 for y */
|
/** @arg dimension, 0 for rescaling only x, 1 for y */
|
||||||
export const rescalePoints = <Point extends GlobalPoint | LocalPoint>(
|
export const rescalePoints = <Point extends GenericPoint>(
|
||||||
dimension: 0 | 1,
|
dimension: 0 | 1,
|
||||||
newSize: number,
|
newSize: number,
|
||||||
points: readonly Point[],
|
points: readonly Point[],
|
||||||
|
@ -65,16 +60,16 @@ export const rescalePoints = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Rounding this point causes some shake when free drawing
|
// TODO: Rounding this point causes some shake when free drawing
|
||||||
export const getGridPoint = (
|
export const getGridPoint = <Point extends GenericPoint>(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
gridSize: NullableGridSize,
|
gridSize: NullableGridSize,
|
||||||
): [number, number] => {
|
): Point => {
|
||||||
if (gridSize) {
|
if (gridSize) {
|
||||||
return [
|
return pointFrom(
|
||||||
Math.round(x / gridSize) * gridSize,
|
Math.round(x / gridSize) * gridSize,
|
||||||
Math.round(y / gridSize) * gridSize,
|
Math.round(y / gridSize) * gridSize,
|
||||||
];
|
);
|
||||||
}
|
}
|
||||||
return [x, y];
|
return pointFrom(x, y);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { average, pointFrom, type GlobalPoint } from "@excalidraw/math";
|
import {
|
||||||
|
average,
|
||||||
|
pointFrom,
|
||||||
|
type ViewportPoint,
|
||||||
|
type GlobalPoint,
|
||||||
|
type GenericPoint,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
|
@ -448,11 +454,11 @@ export const viewportCoordsToSceneCoords = (
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
},
|
},
|
||||||
) => {
|
): ViewportPoint => {
|
||||||
const x = (clientX - offsetLeft) / zoom.value - scrollX;
|
const x = (clientX - offsetLeft) / zoom.value - scrollX;
|
||||||
const y = (clientY - offsetTop) / zoom.value - scrollY;
|
const y = (clientY - offsetTop) / zoom.value - scrollY;
|
||||||
|
|
||||||
return { x, y };
|
return pointFrom<ViewportPoint>(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sceneCoordsToViewportCoords = (
|
export const sceneCoordsToViewportCoords = (
|
||||||
|
@ -470,10 +476,10 @@ export const sceneCoordsToViewportCoords = (
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
},
|
},
|
||||||
) => {
|
): ViewportPoint => {
|
||||||
const x = (sceneX + scrollX) * zoom.value + offsetLeft;
|
const x = (sceneX + scrollX) * zoom.value + offsetLeft;
|
||||||
const y = (sceneY + scrollY) * zoom.value + offsetTop;
|
const y = (sceneY + scrollY) * zoom.value + offsetTop;
|
||||||
return { x, y };
|
return pointFrom<ViewportPoint>(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGlobalCSSVariable = (name: string) =>
|
export const getGlobalCSSVariable = (name: string) =>
|
||||||
|
@ -492,11 +498,11 @@ const RE_RTL_CHECK = new RegExp(`^[^${RS_LTR_CHARS}]*[${RS_RTL_CHARS}]`);
|
||||||
*/
|
*/
|
||||||
export const isRTL = (text: string) => RE_RTL_CHECK.test(text);
|
export const isRTL = (text: string) => RE_RTL_CHECK.test(text);
|
||||||
|
|
||||||
export const tupleToCoors = (
|
export const tupleToCoors = <Point extends GenericPoint>(
|
||||||
xyTuple: readonly [number, number],
|
xyTuple: readonly [number, number],
|
||||||
): { x: number; y: number } => {
|
): Point => {
|
||||||
const [x, y] = xyTuple;
|
const [x, y] = xyTuple;
|
||||||
return { x, y };
|
return pointFrom(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** use as a rejectionHandler to mute filesystem Abort errors */
|
/** use as a rejectionHandler to mute filesystem Abort errors */
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
|
|
||||||
import { isPointOnShape } from "@excalidraw/utils/collision";
|
import { isPointOnShape } from "@excalidraw/utils/collision";
|
||||||
|
|
||||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
import type { GenericPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||||
|
|
||||||
|
@ -425,10 +425,10 @@ export const getSuggestedBindingsForArrows = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const maybeBindLinearElement = (
|
export const maybeBindLinearElement = <Point extends GenericPoint>(
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
pointerCoords: { x: number; y: number },
|
pointerCoords: Point,
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
): void => {
|
): void => {
|
||||||
|
@ -576,11 +576,8 @@ const unbindLinearElement = (
|
||||||
return binding.elementId;
|
return binding.elementId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHoveredElementForBinding = (
|
export const getHoveredElementForBinding = <Point extends GenericPoint>(
|
||||||
pointerCoords: {
|
pointerCoords: Point,
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
},
|
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
zoom?: AppState["zoom"],
|
zoom?: AppState["zoom"],
|
||||||
|
@ -1392,11 +1389,11 @@ const getElligibleElementForBindingElement = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLinearElementEdgeCoors = (
|
const getLinearElementEdgeCoors = <Point extends GenericPoint>(
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): { x: number; y: number } => {
|
): Point => {
|
||||||
const index = startOrEnd === "start" ? 0 : -1;
|
const index = startOrEnd === "start" ? 0 : -1;
|
||||||
return tupleToCoors(
|
return tupleToCoors(
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
@ -1516,9 +1513,9 @@ const newBoundElements = (
|
||||||
return nextBoundElements;
|
return nextBoundElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bindingBorderTest = (
|
export const bindingBorderTest = <Point extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawBindableElement>,
|
element: NonDeleted<ExcalidrawBindableElement>,
|
||||||
{ x, y }: { x: number; y: number },
|
[x, y]: Point,
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
zoom?: AppState["zoom"],
|
zoom?: AppState["zoom"],
|
||||||
fullShape?: boolean,
|
fullShape?: boolean,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { pointsOnBezierCurves } from "points-on-curve";
|
||||||
import type {
|
import type {
|
||||||
Curve,
|
Curve,
|
||||||
Degrees,
|
Degrees,
|
||||||
|
GenericPoint,
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
LineSegment,
|
LineSegment,
|
||||||
LocalPoint,
|
LocalPoint,
|
||||||
|
@ -1057,7 +1058,7 @@ export const getElementPointsCoords = (
|
||||||
|
|
||||||
export const getClosestElementBounds = (
|
export const getClosestElementBounds = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
from: { x: number; y: number },
|
from: GenericPoint,
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
if (!elements.length) {
|
if (!elements.length) {
|
||||||
return [0, 0, 0, 0];
|
return [0, 0, 0, 0];
|
||||||
|
@ -1070,7 +1071,7 @@ export const getClosestElementBounds = (
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
pointFrom((x1 + x2) / 2, (y1 + y2) / 2),
|
pointFrom((x1 + x2) / 2, (y1 + y2) / 2),
|
||||||
pointFrom(from.x, from.y),
|
from,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
|
|
|
@ -19,9 +19,9 @@ import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision";
|
||||||
import { type GeometricShape, getPolygonShape } from "@excalidraw/utils/shape";
|
import { type GeometricShape, getPolygonShape } from "@excalidraw/utils/shape";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
GenericPoint,
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
LineSegment,
|
LineSegment,
|
||||||
LocalPoint,
|
|
||||||
Polygon,
|
Polygon,
|
||||||
Radians,
|
Radians,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
@ -72,7 +72,7 @@ export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||||
return isDraggableFromInside || isImageElement(element);
|
return isDraggableFromInside || isImageElement(element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HitTestArgs<Point extends GlobalPoint | LocalPoint> = {
|
export type HitTestArgs<Point extends GenericPoint> = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
element: ExcalidrawElement;
|
element: ExcalidrawElement;
|
||||||
|
@ -81,7 +81,7 @@ export type HitTestArgs<Point extends GlobalPoint | LocalPoint> = {
|
||||||
frameNameBound?: FrameNameBounds | null;
|
frameNameBound?: FrameNameBounds | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hitElementItself = <Point extends GlobalPoint | LocalPoint>({
|
export const hitElementItself = <Point extends GenericPoint>({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
element,
|
element,
|
||||||
|
@ -127,9 +127,7 @@ export const hitElementBoundingBox = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hitElementBoundingBoxOnly = <
|
export const hitElementBoundingBoxOnly = <Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
hitArgs: HitTestArgs<Point>,
|
hitArgs: HitTestArgs<Point>,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
|
@ -145,7 +143,7 @@ export const hitElementBoundingBoxOnly = <
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hitElementBoundText = <Point extends GlobalPoint | LocalPoint>(
|
export const hitElementBoundText = <Point extends GenericPoint>(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
textShape: GeometricShape<Point> | null,
|
textShape: GeometricShape<Point> | null,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||||
|
import type { GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
|
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
|
@ -208,10 +209,7 @@ export const dragNewElement = ({
|
||||||
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
||||||
true */
|
true */
|
||||||
widthAspectRatio?: number | null;
|
widthAspectRatio?: number | null;
|
||||||
originOffset?: {
|
originOffset?: GenericPoint | null;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
} | null;
|
|
||||||
informMutation?: boolean;
|
informMutation?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (shouldMaintainAspectRatio && newElement.type !== "selection") {
|
if (shouldMaintainAspectRatio && newElement.type !== "selection") {
|
||||||
|
@ -285,11 +283,12 @@ export const dragNewElement = ({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [originOffsetX, originOffsetY] = originOffset ?? [0, 0];
|
||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
x: newX + (originOffset?.x ?? 0),
|
x: newX + originOffsetX,
|
||||||
y: newY + (originOffset?.y ?? 0),
|
y: newY + originOffsetY,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...textAutoResize,
|
...textAutoResize,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
vectorCross,
|
vectorCross,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
vectorScale,
|
vectorScale,
|
||||||
|
type GenericPoint,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
@ -1642,7 +1643,7 @@ const pathTo = (start: Node, node: Node) => {
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const m_dist = (a: GlobalPoint | LocalPoint, b: GlobalPoint | LocalPoint) =>
|
const m_dist = (a: GenericPoint, b: GenericPoint) =>
|
||||||
Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
|
Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2291,7 +2292,7 @@ const getHoveredElement = (
|
||||||
const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean =>
|
const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean =>
|
||||||
a[0] === b[0] && a[1] === b[1];
|
a[0] === b[0] && a[1] === b[1];
|
||||||
|
|
||||||
export const validateElbowPoints = <P extends GlobalPoint | LocalPoint>(
|
export const validateElbowPoints = <P extends GenericPoint>(
|
||||||
points: readonly P[],
|
points: readonly P[],
|
||||||
tolerance: number = DEDUP_TRESHOLD,
|
tolerance: number = DEDUP_TRESHOLD,
|
||||||
) =>
|
) =>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { arrayToMap } from "@excalidraw/common";
|
import { arrayToMap } from "@excalidraw/common";
|
||||||
import { isPointWithinBounds, pointFrom } from "@excalidraw/math";
|
import {
|
||||||
|
type GenericPoint,
|
||||||
|
isPointWithinBounds,
|
||||||
|
pointFrom,
|
||||||
|
} from "@excalidraw/math";
|
||||||
import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox";
|
import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox";
|
||||||
import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds";
|
import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds";
|
||||||
|
|
||||||
|
@ -152,11 +156,8 @@ export const elementOverlapsWithFrame = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isCursorInFrame = (
|
export const isCursorInFrame = <Point extends GenericPoint>(
|
||||||
cursorCoords: {
|
cursorCoords: Point,
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
},
|
|
||||||
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
|
@ -164,7 +165,7 @@ export const isCursorInFrame = (
|
||||||
|
|
||||||
return isPointWithinBounds(
|
return isPointWithinBounds(
|
||||||
pointFrom(fx1, fy1),
|
pointFrom(fx1, fy1),
|
||||||
pointFrom(cursorCoords.x, cursorCoords.y),
|
cursorCoords,
|
||||||
pointFrom(fx2, fy2),
|
pointFrom(fx2, fy2),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,10 +13,10 @@ import {
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
LocalPoint,
|
|
||||||
GlobalPoint,
|
GlobalPoint,
|
||||||
Triangle,
|
Triangle,
|
||||||
Vector,
|
Vector,
|
||||||
|
GenericPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { getCenterForBounds, type Bounds } from "./bounds";
|
import { getCenterForBounds, type Bounds } from "./bounds";
|
||||||
|
@ -43,12 +43,10 @@ export const vectorToHeading = (vec: Vector): Heading => {
|
||||||
return HEADING_UP;
|
return HEADING_UP;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const headingForPoint = <P extends GlobalPoint | LocalPoint>(
|
export const headingForPoint = <P extends GenericPoint>(p: P, o: P) =>
|
||||||
p: P,
|
vectorToHeading(vectorFromPoint<P>(p, o));
|
||||||
o: P,
|
|
||||||
) => vectorToHeading(vectorFromPoint<P>(p, o));
|
|
||||||
|
|
||||||
export const headingForPointIsHorizontal = <P extends GlobalPoint | LocalPoint>(
|
export const headingForPointIsHorizontal = <P extends GenericPoint>(
|
||||||
p: P,
|
p: P,
|
||||||
o: P,
|
o: P,
|
||||||
) => headingIsHorizontal(headingForPoint<P>(p, o));
|
) => headingIsHorizontal(headingForPoint<P>(p, o));
|
||||||
|
|
|
@ -3,8 +3,6 @@ import {
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
pointsEqual,
|
pointsEqual,
|
||||||
type GlobalPoint,
|
|
||||||
type LocalPoint,
|
|
||||||
pointDistance,
|
pointDistance,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
@ -26,7 +24,12 @@ import Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||||
|
|
||||||
import type { Store } from "@excalidraw/excalidraw/store";
|
import type { Store } from "@excalidraw/excalidraw/store";
|
||||||
|
|
||||||
import type { Radians } from "@excalidraw/math";
|
import type {
|
||||||
|
GlobalPoint,
|
||||||
|
LocalPoint,
|
||||||
|
GenericPoint,
|
||||||
|
Radians,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -106,7 +109,7 @@ export class LinearElementEditor {
|
||||||
/** index */
|
/** index */
|
||||||
lastClickedPoint: number;
|
lastClickedPoint: number;
|
||||||
lastClickedIsEndPoint: boolean;
|
lastClickedIsEndPoint: boolean;
|
||||||
origin: Readonly<{ x: number; y: number }> | null;
|
origin: Readonly<GenericPoint> | null;
|
||||||
segmentMidpoint: {
|
segmentMidpoint: {
|
||||||
value: GlobalPoint | null;
|
value: GlobalPoint | null;
|
||||||
index: number | null;
|
index: number | null;
|
||||||
|
@ -117,7 +120,7 @@ export class LinearElementEditor {
|
||||||
/** whether you're dragging a point */
|
/** whether you're dragging a point */
|
||||||
public readonly isDragging: boolean;
|
public readonly isDragging: boolean;
|
||||||
public readonly lastUncommittedPoint: LocalPoint | null;
|
public readonly lastUncommittedPoint: LocalPoint | null;
|
||||||
public readonly pointerOffset: Readonly<{ x: number; y: number }>;
|
public readonly pointerOffset: Readonly<GenericPoint>;
|
||||||
public readonly startBindingElement:
|
public readonly startBindingElement:
|
||||||
| ExcalidrawBindableElement
|
| ExcalidrawBindableElement
|
||||||
| null
|
| null
|
||||||
|
@ -139,7 +142,7 @@ export class LinearElementEditor {
|
||||||
this.selectedPointsIndices = null;
|
this.selectedPointsIndices = null;
|
||||||
this.lastUncommittedPoint = null;
|
this.lastUncommittedPoint = null;
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.pointerOffset = { x: 0, y: 0 };
|
this.pointerOffset = pointFrom(0, 0);
|
||||||
this.startBindingElement = "keep";
|
this.startBindingElement = "keep";
|
||||||
this.endBindingElement = "keep";
|
this.endBindingElement = "keep";
|
||||||
this.pointerDownState = {
|
this.pointerDownState = {
|
||||||
|
@ -242,14 +245,14 @@ export class LinearElementEditor {
|
||||||
/**
|
/**
|
||||||
* @returns whether point was dragged
|
* @returns whether point was dragged
|
||||||
*/
|
*/
|
||||||
static handlePointDragging(
|
static handlePointDragging<Point extends GenericPoint>(
|
||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
maybeSuggestBinding: (
|
maybeSuggestBinding: (
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
pointSceneCoords: { x: number; y: number }[],
|
pointSceneCoords: Point[],
|
||||||
) => void,
|
) => void,
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
|
@ -320,11 +323,13 @@ export class LinearElementEditor {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
const [pointerOffsetX, pointerOffsetY] =
|
||||||
|
linearElementEditor.pointerOffset;
|
||||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - pointerOffsetX,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - pointerOffsetY,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -339,8 +344,8 @@ export class LinearElementEditor {
|
||||||
? LinearElementEditor.createPointAt(
|
? LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - pointerOffsetX,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - pointerOffsetY,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
)
|
)
|
||||||
: pointFrom(
|
: pointFrom(
|
||||||
|
@ -363,7 +368,7 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
// suggest bindings for first and last point if selected
|
// suggest bindings for first and last point if selected
|
||||||
if (isBindingElement(element, false)) {
|
if (isBindingElement(element, false)) {
|
||||||
const coords: { x: number; y: number }[] = [];
|
const coords: Point[] = [];
|
||||||
|
|
||||||
const firstSelectedIndex = selectedPointsIndices[0];
|
const firstSelectedIndex = selectedPointsIndices[0];
|
||||||
if (firstSelectedIndex === 0) {
|
if (firstSelectedIndex === 0) {
|
||||||
|
@ -511,7 +516,7 @@ export class LinearElementEditor {
|
||||||
? [pointerDownState.lastClickedPoint]
|
? [pointerDownState.lastClickedPoint]
|
||||||
: selectedPointsIndices,
|
: selectedPointsIndices,
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
pointerOffset: { x: 0, y: 0 },
|
pointerOffset: pointFrom(0, 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,9 +591,9 @@ export class LinearElementEditor {
|
||||||
editorMidPointsCache.zoom = appState.zoom.value;
|
editorMidPointsCache.zoom = appState.zoom.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
static getSegmentMidpointHitCoords = (
|
static getSegmentMidpointHitCoords = <Point extends GenericPoint>(
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
scenePointer: { x: number; y: number },
|
[scenePointerX, scenePointerY]: Point,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): GlobalPoint | null => {
|
): GlobalPoint | null => {
|
||||||
|
@ -601,8 +606,8 @@ export class LinearElementEditor {
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointerX,
|
||||||
scenePointer.y,
|
scenePointerY,
|
||||||
);
|
);
|
||||||
if (!isElbowArrow(element) && clickedPointIndex >= 0) {
|
if (!isElbowArrow(element) && clickedPointIndex >= 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -630,7 +635,7 @@ export class LinearElementEditor {
|
||||||
existingSegmentMidpointHitCoords[0],
|
existingSegmentMidpointHitCoords[0],
|
||||||
existingSegmentMidpointHitCoords[1],
|
existingSegmentMidpointHitCoords[1],
|
||||||
),
|
),
|
||||||
pointFrom(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointerX, scenePointerY),
|
||||||
);
|
);
|
||||||
if (distance <= threshold) {
|
if (distance <= threshold) {
|
||||||
return existingSegmentMidpointHitCoords;
|
return existingSegmentMidpointHitCoords;
|
||||||
|
@ -644,7 +649,7 @@ export class LinearElementEditor {
|
||||||
if (midPoints[index] !== null) {
|
if (midPoints[index] !== null) {
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
midPoints[index]!,
|
midPoints[index]!,
|
||||||
pointFrom(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointerX, scenePointerY),
|
||||||
);
|
);
|
||||||
if (distance <= threshold) {
|
if (distance <= threshold) {
|
||||||
return midPoints[index];
|
return midPoints[index];
|
||||||
|
@ -656,7 +661,7 @@ export class LinearElementEditor {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
static isSegmentTooShort<P extends GlobalPoint | LocalPoint>(
|
static isSegmentTooShort<P extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startPoint: P,
|
startPoint: P,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
|
@ -747,11 +752,11 @@ export class LinearElementEditor {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static handlePointerDown(
|
static handlePointerDown<Point extends GenericPoint>(
|
||||||
event: React.PointerEvent<HTMLElement>,
|
event: React.PointerEvent<HTMLElement>,
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
store: Store,
|
store: Store,
|
||||||
scenePointer: { x: number; y: number },
|
scenePointer: Point,
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
): {
|
): {
|
||||||
|
@ -762,6 +767,7 @@ export class LinearElementEditor {
|
||||||
const appState = app.state;
|
const appState = app.state;
|
||||||
const elementsMap = scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
const elements = scene.getNonDeletedElements();
|
const elements = scene.getNonDeletedElements();
|
||||||
|
const [scenePointerX, scenePointerY] = scenePointer;
|
||||||
|
|
||||||
const ret: ReturnType<typeof LinearElementEditor["handlePointerDown"]> = {
|
const ret: ReturnType<typeof LinearElementEditor["handlePointerDown"]> = {
|
||||||
didAddPoint: false,
|
didAddPoint: false,
|
||||||
|
@ -801,8 +807,8 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.createPointAt(
|
LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
scenePointer.x,
|
scenePointerX,
|
||||||
scenePointer.y,
|
scenePointerY,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -816,7 +822,7 @@ export class LinearElementEditor {
|
||||||
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
||||||
lastClickedPoint: -1,
|
lastClickedPoint: -1,
|
||||||
lastClickedIsEndPoint: false,
|
lastClickedIsEndPoint: false,
|
||||||
origin: { x: scenePointer.x, y: scenePointer.y },
|
origin: pointFrom(scenePointerX, scenePointerY),
|
||||||
segmentMidpoint: {
|
segmentMidpoint: {
|
||||||
value: segmentMidpoint,
|
value: segmentMidpoint,
|
||||||
index: segmentMidpointIndex,
|
index: segmentMidpointIndex,
|
||||||
|
@ -842,8 +848,8 @@ export class LinearElementEditor {
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointerX,
|
||||||
scenePointer.y,
|
scenePointerY,
|
||||||
);
|
);
|
||||||
// if we clicked on a point, set the element as hitElement otherwise
|
// if we clicked on a point, set the element as hitElement otherwise
|
||||||
// it would get deselected if the point is outside the hitbox area
|
// it would get deselected if the point is outside the hitbox area
|
||||||
|
@ -897,7 +903,7 @@ export class LinearElementEditor {
|
||||||
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
||||||
lastClickedPoint: clickedPointIndex,
|
lastClickedPoint: clickedPointIndex,
|
||||||
lastClickedIsEndPoint: clickedPointIndex === element.points.length - 1,
|
lastClickedIsEndPoint: clickedPointIndex === element.points.length - 1,
|
||||||
origin: { x: scenePointer.x, y: scenePointer.y },
|
origin: pointFrom(scenePointerX, scenePointerY),
|
||||||
segmentMidpoint: {
|
segmentMidpoint: {
|
||||||
value: segmentMidpoint,
|
value: segmentMidpoint,
|
||||||
index: segmentMidpointIndex,
|
index: segmentMidpointIndex,
|
||||||
|
@ -906,17 +912,17 @@ export class LinearElementEditor {
|
||||||
},
|
},
|
||||||
selectedPointsIndices: nextSelectedPointsIndices,
|
selectedPointsIndices: nextSelectedPointsIndices,
|
||||||
pointerOffset: targetPoint
|
pointerOffset: targetPoint
|
||||||
? {
|
? pointFrom(
|
||||||
x: scenePointer.x - targetPoint[0],
|
scenePointerX - targetPoint[0],
|
||||||
y: scenePointer.y - targetPoint[1],
|
scenePointerY - targetPoint[1],
|
||||||
}
|
)
|
||||||
: { x: 0, y: 0 },
|
: pointFrom(0, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static arePointsEqual<Point extends LocalPoint | GlobalPoint>(
|
static arePointsEqual<Point extends GenericPoint>(
|
||||||
point1: Point | null,
|
point1: Point | null,
|
||||||
point2: Point | null,
|
point2: Point | null,
|
||||||
) {
|
) {
|
||||||
|
@ -977,11 +983,13 @@ export class LinearElementEditor {
|
||||||
height + lastCommittedPoint[1],
|
height + lastCommittedPoint[1],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const [pointerOffsetX, pointerOffsetY] =
|
||||||
|
appState.editingLinearElement.pointerOffset;
|
||||||
newPoint = LinearElementEditor.createPointAt(
|
newPoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
scenePointerX - pointerOffsetX,
|
||||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
scenePointerY - pointerOffsetY,
|
||||||
event[KEYS.CTRL_OR_CMD] || isElbowArrow(element)
|
event[KEYS.CTRL_OR_CMD] || isElbowArrow(element)
|
||||||
? null
|
? null
|
||||||
: app.getEffectiveGridSize(),
|
: app.getEffectiveGridSize(),
|
||||||
|
@ -1376,10 +1384,7 @@ export class LinearElementEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const origin = linearElementEditor.pointerDownState.origin!;
|
const origin = linearElementEditor.pointerDownState.origin!;
|
||||||
const dist = pointDistance(
|
const dist = pointDistance(origin, pointerCoords);
|
||||||
pointFrom(origin.x, origin.y),
|
|
||||||
pointFrom(pointerCoords.x, pointerCoords.y),
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
!appState.editingLinearElement &&
|
!appState.editingLinearElement &&
|
||||||
dist < DRAGGING_THRESHOLD / appState.zoom.value
|
dist < DRAGGING_THRESHOLD / appState.zoom.value
|
||||||
|
@ -1412,11 +1417,12 @@ export class LinearElementEditor {
|
||||||
selectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
selectedPointsIndices: linearElementEditor.selectedPointsIndices,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [pointerX, pointerY] = pointerCoords;
|
||||||
const midpoint = LinearElementEditor.createPointAt(
|
const midpoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
pointerCoords.x,
|
pointerX,
|
||||||
pointerCoords.y,
|
pointerY,
|
||||||
snapToGrid && !isElbowArrow(element) ? app.getEffectiveGridSize() : null,
|
snapToGrid && !isElbowArrow(element) ? app.getEffectiveGridSize() : null,
|
||||||
);
|
);
|
||||||
const points = [
|
const points = [
|
||||||
|
@ -1528,31 +1534,28 @@ export class LinearElementEditor {
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
referencePoint: LocalPoint,
|
referencePoint: LocalPoint,
|
||||||
scenePointer: GlobalPoint,
|
[scenePointerX, scenePointerY]: GlobalPoint,
|
||||||
gridSize: NullableGridSize,
|
gridSize: NullableGridSize,
|
||||||
) {
|
) {
|
||||||
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
const [referencePointCoordsX, referencePointCoordsY] =
|
||||||
element,
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
referencePoint,
|
element,
|
||||||
elementsMap,
|
referencePoint,
|
||||||
);
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
if (isElbowArrow(element)) {
|
||||||
return [
|
return [
|
||||||
scenePointer[0] - referencePointCoords[0],
|
scenePointerX - referencePointCoordsX,
|
||||||
scenePointer[1] - referencePointCoords[1],
|
scenePointerY - referencePointCoordsY,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
||||||
scenePointer[0],
|
|
||||||
scenePointer[1],
|
|
||||||
gridSize,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { width, height } = getLockedLinearCursorAlignSize(
|
const { width, height } = getLockedLinearCursorAlignSize(
|
||||||
referencePointCoords[0],
|
referencePointCoordsX,
|
||||||
referencePointCoords[1],
|
referencePointCoordsY,
|
||||||
gridX,
|
gridX,
|
||||||
gridY,
|
gridY,
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
|
|
||||||
import { SIDE_RESIZING_THRESHOLD } from "@excalidraw/common";
|
import { SIDE_RESIZING_THRESHOLD } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
|
import type { GenericPoint, LineSegment } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState, Device, Zoom } from "@excalidraw/excalidraw/types";
|
import type { AppState, Device, Zoom } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ const isInsideTransformHandle = (
|
||||||
y >= transformHandle[1] &&
|
y >= transformHandle[1] &&
|
||||||
y <= transformHandle[1] + transformHandle[3];
|
y <= transformHandle[1] + transformHandle[3];
|
||||||
|
|
||||||
export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
export const resizeTest = <Point extends GenericPoint>(
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
@ -152,9 +152,7 @@ export const getElementWithTransformHandleType = (
|
||||||
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
}, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTransformHandleTypeFromCoords = <
|
export const getTransformHandleTypeFromCoords = <Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
[x1, y1, x2, y2]: Bounds,
|
[x1, y1, x2, y2]: Bounds,
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
|
@ -271,7 +269,7 @@ export const getCursorForResizingElement = (resizingElement: {
|
||||||
return cursor ? `${cursor}-resize` : "";
|
return cursor ? `${cursor}-resize` : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelectionBorders = <Point extends LocalPoint | GlobalPoint>(
|
const getSelectionBorders = <Point extends GenericPoint>(
|
||||||
[x1, y1]: Point,
|
[x1, y1]: Point,
|
||||||
[x2, y2]: Point,
|
[x2, y2]: Point,
|
||||||
center: Point,
|
center: Point,
|
||||||
|
|
|
@ -13,8 +13,7 @@ import {
|
||||||
pointFromPair,
|
pointFromPair,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
pointsEqual,
|
pointsEqual,
|
||||||
type GlobalPoint,
|
type GenericPoint,
|
||||||
type LocalPoint,
|
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
import {
|
import {
|
||||||
getClosedCurveShape,
|
getClosedCurveShape,
|
||||||
|
@ -46,7 +45,7 @@ import type {
|
||||||
* get the pure geometric shape of an excalidraw elementw
|
* get the pure geometric shape of an excalidraw elementw
|
||||||
* which is then used for hit detection
|
* which is then used for hit detection
|
||||||
*/
|
*/
|
||||||
export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getElementShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): GeometricShape<Point> => {
|
): GeometricShape<Point> => {
|
||||||
|
@ -98,7 +97,7 @@ export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getBoundTextShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): GeometricShape<Point> | null => {
|
): GeometricShape<Point> | null => {
|
||||||
|
@ -126,9 +125,7 @@ export const getBoundTextShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getControlPointsForBezierCurve = <
|
export const getControlPointsForBezierCurve = <P extends GenericPoint>(
|
||||||
P extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
) => {
|
) => {
|
||||||
|
@ -170,7 +167,7 @@ export const getControlPointsForBezierCurve = <
|
||||||
return controlPoints;
|
return controlPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
|
export const getBezierXY = <P extends GenericPoint>(
|
||||||
p0: P,
|
p0: P,
|
||||||
p1: P,
|
p1: P,
|
||||||
p2: P,
|
p2: P,
|
||||||
|
@ -187,7 +184,7 @@ export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
|
||||||
return pointFrom(tx, ty);
|
return pointFrom(tx, ty);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
const getPointsInBezierCurve = <P extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
) => {
|
) => {
|
||||||
|
@ -217,7 +214,7 @@ const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
||||||
return pointsOnCurve;
|
return pointsOnCurve;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBezierCurveArcLengths = <P extends GlobalPoint | LocalPoint>(
|
const getBezierCurveArcLengths = <P extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
) => {
|
) => {
|
||||||
|
@ -236,7 +233,7 @@ const getBezierCurveArcLengths = <P extends GlobalPoint | LocalPoint>(
|
||||||
return arcLengths;
|
return arcLengths;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBezierCurveLength = <P extends GlobalPoint | LocalPoint>(
|
export const getBezierCurveLength = <P extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
) => {
|
) => {
|
||||||
|
@ -245,7 +242,7 @@ export const getBezierCurveLength = <P extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// This maps interval to actual interval t on the curve so that when t = 0.5, its actually the point at 50% of the length
|
// This maps interval to actual interval t on the curve so that when t = 0.5, its actually the point at 50% of the length
|
||||||
export const mapIntervalToBezierT = <P extends GlobalPoint | LocalPoint>(
|
export const mapIntervalToBezierT = <P extends GenericPoint>(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
endPoint: P,
|
endPoint: P,
|
||||||
interval: number, // The interval between 0 to 1 for which you want to find the point on the curve,
|
interval: number, // The interval between 0 to 1 for which you want to find the point on the curve,
|
||||||
|
@ -340,7 +337,7 @@ export const aabbForElement = (
|
||||||
return bounds;
|
return bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
export const pointInsideBounds = <P extends GenericPoint>(
|
||||||
p: P,
|
p: P,
|
||||||
bounds: Bounds,
|
bounds: Bounds,
|
||||||
): boolean =>
|
): boolean =>
|
||||||
|
|
|
@ -37,26 +37,28 @@ export const isElementInViewport = (
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates
|
||||||
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
const [topLeftSceneCoordsX, topLeftSceneCoordsY] =
|
||||||
{
|
viewportCoordsToSceneCoords(
|
||||||
clientX: viewTransformations.offsetLeft,
|
{
|
||||||
clientY: viewTransformations.offsetTop,
|
clientX: viewTransformations.offsetLeft,
|
||||||
},
|
clientY: viewTransformations.offsetTop,
|
||||||
viewTransformations,
|
},
|
||||||
);
|
viewTransformations,
|
||||||
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
);
|
||||||
{
|
const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] =
|
||||||
clientX: viewTransformations.offsetLeft + width,
|
viewportCoordsToSceneCoords(
|
||||||
clientY: viewTransformations.offsetTop + height,
|
{
|
||||||
},
|
clientX: viewTransformations.offsetLeft + width,
|
||||||
viewTransformations,
|
clientY: viewTransformations.offsetTop + height,
|
||||||
);
|
},
|
||||||
|
viewTransformations,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
topLeftSceneCoords.x <= x2 &&
|
topLeftSceneCoordsX <= x2 &&
|
||||||
topLeftSceneCoords.y <= y2 &&
|
topLeftSceneCoordsY <= y2 &&
|
||||||
bottomRightSceneCoords.x >= x1 &&
|
bottomRightSceneCoordsX >= x1 &&
|
||||||
bottomRightSceneCoords.y >= y1
|
bottomRightSceneCoordsY >= y1
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,26 +77,29 @@ export const isElementCompletelyInViewport = (
|
||||||
padding?: Offsets,
|
padding?: Offsets,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getCommonBounds(elements, elementsMap); // scene coordinates
|
const [x1, y1, x2, y2] = getCommonBounds(elements, elementsMap); // scene coordinates
|
||||||
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
const [topLeftSceneCoordsX, topLeftSceneCoordsY] =
|
||||||
{
|
viewportCoordsToSceneCoords(
|
||||||
clientX: viewTransformations.offsetLeft + (padding?.left || 0),
|
{
|
||||||
clientY: viewTransformations.offsetTop + (padding?.top || 0),
|
clientX: viewTransformations.offsetLeft + (padding?.left || 0),
|
||||||
},
|
clientY: viewTransformations.offsetTop + (padding?.top || 0),
|
||||||
viewTransformations,
|
},
|
||||||
);
|
viewTransformations,
|
||||||
const bottomRightSceneCoords = viewportCoordsToSceneCoords(
|
);
|
||||||
{
|
const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] =
|
||||||
clientX: viewTransformations.offsetLeft + width - (padding?.right || 0),
|
viewportCoordsToSceneCoords(
|
||||||
clientY: viewTransformations.offsetTop + height - (padding?.bottom || 0),
|
{
|
||||||
},
|
clientX: viewTransformations.offsetLeft + width - (padding?.right || 0),
|
||||||
viewTransformations,
|
clientY:
|
||||||
);
|
viewTransformations.offsetTop + height - (padding?.bottom || 0),
|
||||||
|
},
|
||||||
|
viewTransformations,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
x1 >= topLeftSceneCoords.x &&
|
x1 >= topLeftSceneCoordsX &&
|
||||||
y1 >= topLeftSceneCoords.y &&
|
y1 >= topLeftSceneCoordsY &&
|
||||||
x2 <= bottomRightSceneCoords.x &&
|
x2 <= bottomRightSceneCoordsX &&
|
||||||
y2 <= bottomRightSceneCoords.y
|
y2 <= bottomRightSceneCoordsY
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { clamp, roundToStep } from "@excalidraw/math";
|
import { clamp, pointFrom, roundToStep } from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_CANVAS_BACKGROUND_PICKS,
|
DEFAULT_CANVAS_BACKGROUND_PICKS,
|
||||||
|
@ -333,7 +333,7 @@ export const zoomToFitBounds = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const centerScroll = centerScrollOn({
|
const centerScroll = centerScrollOn({
|
||||||
scenePoint: { x: centerX, y: centerY },
|
scenePoint: pointFrom(centerX, centerY),
|
||||||
viewportDimensions: {
|
viewportDimensions: {
|
||||||
width: appState.width,
|
width: appState.width,
|
||||||
height: appState.height,
|
height: appState.height,
|
||||||
|
|
|
@ -143,7 +143,7 @@ export const actionFinalize = register({
|
||||||
maybeBindLinearElement(
|
maybeBindLinearElement(
|
||||||
multiPointElement,
|
multiPointElement,
|
||||||
appState,
|
appState,
|
||||||
{ x, y },
|
pointFrom(x, y),
|
||||||
elementsMap,
|
elementsMap,
|
||||||
elements,
|
elements,
|
||||||
);
|
);
|
||||||
|
|
|
@ -182,12 +182,12 @@ export class AnimatedTrail implements Trail {
|
||||||
const _stroke = trail
|
const _stroke = trail
|
||||||
.getStrokeOutline(trail.options.size / state.zoom.value)
|
.getStrokeOutline(trail.options.size / state.zoom.value)
|
||||||
.map(([x, y]) => {
|
.map(([x, y]) => {
|
||||||
const result = sceneCoordsToViewportCoords(
|
const [resultX, resultY] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x, sceneY: y },
|
{ sceneX: x, sceneY: y },
|
||||||
state,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
return [result.x, result.y];
|
return [resultX, resultY];
|
||||||
});
|
});
|
||||||
|
|
||||||
const stroke = this.trailAnimation
|
const stroke = this.trailAnimation
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
DEFAULT_GRID_STEP,
|
DEFAULT_GRID_STEP,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { pointFrom } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState, NormalizedZoomValue } from "./types";
|
import type { AppState, NormalizedZoomValue } from "./types";
|
||||||
|
|
||||||
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
||||||
|
@ -112,10 +114,7 @@ export const getDefaultAppState = (): Omit<
|
||||||
showHyperlinkPopup: false,
|
showHyperlinkPopup: false,
|
||||||
selectedLinearElement: null,
|
selectedLinearElement: null,
|
||||||
snapLines: [],
|
snapLines: [],
|
||||||
originSnapOffset: {
|
originSnapOffset: pointFrom(0, 0),
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
objectsSnapModeEnabled: false,
|
objectsSnapModeEnabled: false,
|
||||||
userToFollow: null,
|
userToFollow: null,
|
||||||
followedBy: new Set(),
|
followedBy: new Set(),
|
||||||
|
|
|
@ -69,7 +69,7 @@ export const renderRemoteCursors = ({
|
||||||
}) => {
|
}) => {
|
||||||
// Paint remote pointers
|
// Paint remote pointers
|
||||||
for (const [socketId, pointer] of renderConfig.remotePointerViewportCoords) {
|
for (const [socketId, pointer] of renderConfig.remotePointerViewportCoords) {
|
||||||
let { x, y } = pointer;
|
let [x, y] = pointer;
|
||||||
|
|
||||||
const collaborator = appState.collaborators.get(socketId);
|
const collaborator = appState.collaborators.get(socketId);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,7 @@ const getContainerCoords = (
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const [viewportX, viewportY] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width, sceneY: y1 },
|
{ sceneX: x1 + element.width, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
|
@ -166,9 +166,14 @@ export const EyeDropper: React.FC<{
|
||||||
eyeDropperContainer.focus();
|
eyeDropperContainer.focus();
|
||||||
|
|
||||||
// init color preview else it would show only after the first mouse move
|
// init color preview else it would show only after the first mouse move
|
||||||
|
const [
|
||||||
|
stablePropsAppLastViewportPositionX,
|
||||||
|
stablePropsAppLastViewportPositionY,
|
||||||
|
] = stableProps.app.lastViewportPosition;
|
||||||
|
|
||||||
mouseMoveListener({
|
mouseMoveListener({
|
||||||
clientX: stableProps.app.lastViewportPosition.x,
|
clientX: stablePropsAppLastViewportPositionX,
|
||||||
clientY: stableProps.app.lastViewportPosition.y,
|
clientY: stablePropsAppLastViewportPositionY,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -360,7 +360,7 @@ const getCoordsForPopover = (
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const [viewportX, viewportY] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
@ -422,7 +422,7 @@ const renderTooltip = (
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const linkViewportCoords = sceneCoordsToViewportCoords(
|
const [linkViewportCoordX, linkViewportCoordY] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: linkX, sceneY: linkY },
|
{ sceneX: linkX, sceneY: linkY },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
@ -430,8 +430,8 @@ const renderTooltip = (
|
||||||
updateTooltipPosition(
|
updateTooltipPosition(
|
||||||
tooltipDiv,
|
tooltipDiv,
|
||||||
{
|
{
|
||||||
left: linkViewportCoords.x,
|
left: linkViewportCoordX,
|
||||||
top: linkViewportCoords.y,
|
top: linkViewportCoordY,
|
||||||
width: linkWidth,
|
width: linkWidth,
|
||||||
height: linkHeight,
|
height: linkHeight,
|
||||||
},
|
},
|
||||||
|
@ -457,7 +457,7 @@ const shouldHideLinkPopup = (
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
[clientX, clientY]: GlobalPoint,
|
[clientX, clientY]: GlobalPoint,
|
||||||
): Boolean => {
|
): Boolean => {
|
||||||
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
const [sceneX, sceneY] = viewportCoordsToSceneCoords(
|
||||||
{ clientX, clientY },
|
{ clientX, clientY },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
import type { PointerCoords } from "./types";
|
import { pointFrom, type GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
export const getCenter = (pointers: Map<number, PointerCoords>) => {
|
export const getCenter = <Point extends GenericPoint>(
|
||||||
|
pointers: Map<number, Point>,
|
||||||
|
): Point => {
|
||||||
const allCoords = Array.from(pointers.values());
|
const allCoords = Array.from(pointers.values());
|
||||||
return {
|
return pointFrom(
|
||||||
x: sum(allCoords, (coords) => coords.x) / allCoords.length,
|
sum(allCoords, ([coordsX, _]) => coordsX) / allCoords.length,
|
||||||
y: sum(allCoords, (coords) => coords.y) / allCoords.length,
|
sum(allCoords, ([_, coordsY]) => coordsY) / allCoords.length,
|
||||||
};
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDistance = ([a, b]: readonly PointerCoords[]) =>
|
export const isTwoPointerCoords = <Point extends GenericPoint>(
|
||||||
Math.hypot(a.x - b.x, a.y - b.y);
|
arr: Point[],
|
||||||
|
): arr is [Point, Point] => {
|
||||||
|
return arr.length === 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDistance = <Point extends GenericPoint>([
|
||||||
|
[x1, y1],
|
||||||
|
[x2, y2],
|
||||||
|
]: readonly [Point, Point]) => Math.hypot(x1 - x2, y1 - y2);
|
||||||
|
|
||||||
const sum = <T>(array: readonly T[], mapper: (item: T) => number): number =>
|
const sum = <T>(array: readonly T[], mapper: (item: T) => number): number =>
|
||||||
array.reduce((acc, item) => acc + mapper(item), 0);
|
array.reduce((acc, item) => acc + mapper(item), 0);
|
||||||
|
|
|
@ -2,8 +2,8 @@ import oc from "open-color";
|
||||||
import {
|
import {
|
||||||
pointFrom,
|
pointFrom,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LocalPoint,
|
|
||||||
type Radians,
|
type Radians,
|
||||||
|
type GenericPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -144,7 +144,7 @@ const renderLinearElementPointHighlight = (
|
||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlightPoint = <Point extends LocalPoint | GlobalPoint>(
|
const highlightPoint = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
|
@ -211,7 +211,7 @@ const strokeDiamondWithRotation = (
|
||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSingleLinearPoint = <Point extends GlobalPoint | LocalPoint>(
|
const renderSingleLinearPoint = <Point extends GenericPoint>(
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { pointFrom, type GlobalPoint, type LocalPoint } from "@excalidraw/math";
|
import { pointFrom, type GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import { THEME } from "@excalidraw/common";
|
import { THEME } from "@excalidraw/common";
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ const drawPointerSnapLine = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawCross = <Point extends LocalPoint | GlobalPoint>(
|
const drawCross = <Point extends GenericPoint>(
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
|
@ -109,7 +109,7 @@ const drawCross = <Point extends LocalPoint | GlobalPoint>(
|
||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawLine = <Point extends LocalPoint | GlobalPoint>(
|
const drawLine = <Point extends GenericPoint>(
|
||||||
from: Point,
|
from: Point,
|
||||||
to: Point,
|
to: Point,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
|
@ -120,7 +120,7 @@ const drawLine = <Point extends LocalPoint | GlobalPoint>(
|
||||||
context.stroke();
|
context.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawGapLine = <Point extends LocalPoint | GlobalPoint>(
|
const drawGapLine = <Point extends GenericPoint>(
|
||||||
from: Point,
|
from: Point,
|
||||||
to: Point,
|
to: Point,
|
||||||
direction: "horizontal" | "vertical",
|
direction: "horizontal" | "vertical",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
import { pointFrom } from "@excalidraw/math";
|
||||||
|
|
||||||
import { getClosestElementBounds } from "@excalidraw/element/bounds";
|
import { getClosestElementBounds } from "@excalidraw/element/bounds";
|
||||||
|
|
||||||
|
@ -14,11 +15,11 @@ import type { AppState, Offsets, PointerCoords, Zoom } from "../types";
|
||||||
|
|
||||||
const isOutsideViewPort = (appState: AppState, cords: Array<number>) => {
|
const isOutsideViewPort = (appState: AppState, cords: Array<number>) => {
|
||||||
const [x1, y1, x2, y2] = cords;
|
const [x1, y1, x2, y2] = cords;
|
||||||
const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords(
|
const [viewportX1, viewportY1] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1, sceneY: y1 },
|
{ sceneX: x1, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
const { x: viewportX2, y: viewportY2 } = sceneCoordsToViewportCoords(
|
const [viewportX2, viewportY2] = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x2, sceneY: y2 },
|
{ sceneX: x2, sceneY: y2 },
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
|
@ -39,15 +40,16 @@ export const centerScrollOn = ({
|
||||||
zoom: Zoom;
|
zoom: Zoom;
|
||||||
offsets?: Offsets;
|
offsets?: Offsets;
|
||||||
}) => {
|
}) => {
|
||||||
|
const [scenePointX, scenePointY] = scenePoint;
|
||||||
let scrollX =
|
let scrollX =
|
||||||
(viewportDimensions.width - (offsets?.right ?? 0)) / 2 / zoom.value -
|
(viewportDimensions.width - (offsets?.right ?? 0)) / 2 / zoom.value -
|
||||||
scenePoint.x;
|
scenePointX;
|
||||||
|
|
||||||
scrollX += (offsets?.left ?? 0) / 2 / zoom.value;
|
scrollX += (offsets?.left ?? 0) / 2 / zoom.value;
|
||||||
|
|
||||||
let scrollY =
|
let scrollY =
|
||||||
(viewportDimensions.height - (offsets?.bottom ?? 0)) / 2 / zoom.value -
|
(viewportDimensions.height - (offsets?.bottom ?? 0)) / 2 / zoom.value -
|
||||||
scenePoint.y;
|
scenePointY;
|
||||||
|
|
||||||
scrollY += (offsets?.top ?? 0) / 2 / zoom.value;
|
scrollY += (offsets?.top ?? 0) / 2 / zoom.value;
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ export const calculateScrollCenter = (
|
||||||
const centerY = (y1 + y2) / 2;
|
const centerY = (y1 + y2) / 2;
|
||||||
|
|
||||||
return centerScrollOn({
|
return centerScrollOn({
|
||||||
scenePoint: { x: centerX, y: centerY },
|
scenePoint: pointFrom(centerX, centerY),
|
||||||
viewportDimensions: { width: appState.width, height: appState.height },
|
viewportDimensions: { width: appState.width, height: appState.height },
|
||||||
zoom: appState.zoom,
|
zoom: appState.zoom,
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type {
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
||||||
|
import type { GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppClassProperties,
|
AppClassProperties,
|
||||||
|
@ -61,7 +62,7 @@ export type InteractiveCanvasRenderConfig = {
|
||||||
// collab-related state
|
// collab-related state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
remoteSelectedElementIds: Map<ExcalidrawElement["id"], SocketId[]>;
|
remoteSelectedElementIds: Map<ExcalidrawElement["id"], SocketId[]>;
|
||||||
remotePointerViewportCoords: Map<SocketId, { x: number; y: number }>;
|
remotePointerViewportCoords: Map<SocketId, GenericPoint>;
|
||||||
remotePointerUserStates: Map<SocketId, UserIdleState>;
|
remotePointerUserStates: Map<SocketId, UserIdleState>;
|
||||||
remotePointerUsernames: Map<SocketId, string>;
|
remotePointerUsernames: Map<SocketId, string>;
|
||||||
remotePointerButton: Map<SocketId, string | undefined>;
|
remotePointerButton: Map<SocketId, string | undefined>;
|
||||||
|
|
|
@ -1327,7 +1327,7 @@ export const getSnapLinesAtPointer = (
|
||||||
) => {
|
) => {
|
||||||
if (!isSnappingEnabled({ event, selectedElements: [], app })) {
|
if (!isSnappingEnabled({ event, selectedElements: [], app })) {
|
||||||
return {
|
return {
|
||||||
originOffset: { x: 0, y: 0 },
|
originOffset: pointFrom(0, 0),
|
||||||
snapLines: [],
|
snapLines: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1388,16 +1388,14 @@ export const getSnapLinesAtPointer = (
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
originOffset: {
|
originOffset: pointFrom(
|
||||||
x:
|
verticalSnapLines.length > 0
|
||||||
verticalSnapLines.length > 0
|
? verticalSnapLines[0].points[0][0] - pointer.x
|
||||||
? verticalSnapLines[0].points[0][0] - pointer.x
|
: 0,
|
||||||
: 0,
|
horizontalSnapLines.length > 0
|
||||||
y:
|
? horizontalSnapLines[0].points[0][1] - pointer.y
|
||||||
horizontalSnapLines.length > 0
|
: 0,
|
||||||
? horizontalSnapLines[0].points[0][1] - pointer.y
|
),
|
||||||
: 0,
|
|
||||||
},
|
|
||||||
snapLines: [...verticalSnapLines, ...horizontalSnapLines],
|
snapLines: [...verticalSnapLines, ...horizontalSnapLines],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -945,10 +945,10 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -7758,10 +7758,10 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -9754,10 +9754,10 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
|
|
@ -1178,10 +1178,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -1550,10 +1550,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -1923,10 +1923,10 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -2634,10 +2634,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -2937,10 +2937,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -3225,10 +3225,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -3523,10 +3523,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -3813,10 +3813,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -4052,10 +4052,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -4315,10 +4315,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -4592,10 +4592,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -4827,10 +4827,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -5062,10 +5062,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -5295,10 +5295,10 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -5528,10 +5528,10 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -6937,10 +6937,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -9984,10 +9984,10 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -10872,10 +10872,10 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -12476,10 +12476,10 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -13804,10 +13804,10 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -14272,10 +14272,10 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -14822,10 +14822,10 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
|
|
@ -8566,10 +8566,10 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
||||||
"value": null,
|
"value": null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"pointerOffset": {
|
"pointerOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"segmentMidPointHoveredCoords": null,
|
"segmentMidPointHoveredCoords": null,
|
||||||
"selectedPointsIndices": null,
|
"selectedPointsIndices": null,
|
||||||
"startBindingElement": "keep",
|
"startBindingElement": "keep",
|
||||||
|
@ -8790,10 +8790,10 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
||||||
"value": null,
|
"value": null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"pointerOffset": {
|
"pointerOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"segmentMidPointHoveredCoords": null,
|
"segmentMidPointHoveredCoords": null,
|
||||||
"selectedPointsIndices": null,
|
"selectedPointsIndices": null,
|
||||||
"startBindingElement": "keep",
|
"startBindingElement": "keep",
|
||||||
|
@ -9208,10 +9208,10 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
||||||
"value": null,
|
"value": null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"pointerOffset": {
|
"pointerOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"segmentMidPointHoveredCoords": null,
|
"segmentMidPointHoveredCoords": null,
|
||||||
"selectedPointsIndices": null,
|
"selectedPointsIndices": null,
|
||||||
"startBindingElement": "keep",
|
"startBindingElement": "keep",
|
||||||
|
@ -9613,10 +9613,10 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
||||||
"value": null,
|
"value": null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"pointerOffset": {
|
"pointerOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"segmentMidPointHoveredCoords": null,
|
"segmentMidPointHoveredCoords": null,
|
||||||
"selectedPointsIndices": null,
|
"selectedPointsIndices": null,
|
||||||
"startBindingElement": "keep",
|
"startBindingElement": "keep",
|
||||||
|
@ -12796,10 +12796,10 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
@ -14750,10 +14750,10 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
|
|
@ -42,6 +42,7 @@ import type {
|
||||||
ValueOf,
|
ValueOf,
|
||||||
MakeBrand,
|
MakeBrand,
|
||||||
} from "@excalidraw/common/utility-types";
|
} from "@excalidraw/common/utility-types";
|
||||||
|
import type { GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Action } from "./actions/types";
|
import type { Action } from "./actions/types";
|
||||||
import type { Spreadsheet } from "./charts";
|
import type { Spreadsheet } from "./charts";
|
||||||
|
@ -413,10 +414,7 @@ export interface AppState {
|
||||||
showHyperlinkPopup: false | "info" | "editor";
|
showHyperlinkPopup: false | "info" | "editor";
|
||||||
selectedLinearElement: LinearElementEditor | null;
|
selectedLinearElement: LinearElementEditor | null;
|
||||||
snapLines: readonly SnapLine[];
|
snapLines: readonly SnapLine[];
|
||||||
originSnapOffset: {
|
originSnapOffset: GenericPoint | null;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
} | null;
|
|
||||||
objectsSnapModeEnabled: boolean;
|
objectsSnapModeEnabled: boolean;
|
||||||
/** the user's socket id & username who is being followed on the canvas */
|
/** the user's socket id & username who is being followed on the canvas */
|
||||||
userToFollow: UserToFollow | null;
|
userToFollow: UserToFollow | null;
|
||||||
|
@ -456,14 +454,11 @@ export type Zoom = Readonly<{
|
||||||
value: NormalizedZoomValue;
|
value: NormalizedZoomValue;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type PointerCoords = Readonly<{
|
export type PointerCoords = Readonly<GenericPoint>;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type Gesture = {
|
export type Gesture = {
|
||||||
pointers: Map<number, PointerCoords>;
|
pointers: Map<number, PointerCoords>;
|
||||||
lastCenter: { x: number; y: number } | null;
|
lastCenter: PointerCoords | null;
|
||||||
initialDistance: number | null;
|
initialDistance: number | null;
|
||||||
initialScale: number | null;
|
initialScale: number | null;
|
||||||
};
|
};
|
||||||
|
@ -718,13 +713,13 @@ export type AppClassProperties = {
|
||||||
|
|
||||||
export type PointerDownState = Readonly<{
|
export type PointerDownState = Readonly<{
|
||||||
// The first position at which pointerDown happened
|
// The first position at which pointerDown happened
|
||||||
origin: Readonly<{ x: number; y: number }>;
|
origin: Readonly<GenericPoint>;
|
||||||
// Same as "origin" but snapped to the grid, if grid is on
|
// Same as "origin" but snapped to the grid, if grid is on
|
||||||
originInGrid: Readonly<{ x: number; y: number }>;
|
originInGrid: Readonly<GenericPoint>;
|
||||||
// Scrollbar checks
|
// Scrollbar checks
|
||||||
scrollbars: ReturnType<typeof isOverScrollBars>;
|
scrollbars: ReturnType<typeof isOverScrollBars>;
|
||||||
// The previous pointer position
|
// The previous pointer position
|
||||||
lastCoords: { x: number; y: number };
|
lastCoords: GenericPoint;
|
||||||
// original element frozen snapshots so we can access the original
|
// original element frozen snapshots so we can access the original
|
||||||
// element attribute values at time of pointerdown
|
// element attribute values at time of pointerdown
|
||||||
originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
|
originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
|
||||||
|
@ -734,11 +729,11 @@ export type PointerDownState = Readonly<{
|
||||||
// This is determined on the initial pointer down event
|
// This is determined on the initial pointer down event
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
// This is determined on the initial pointer down event
|
// This is determined on the initial pointer down event
|
||||||
offset: { x: number; y: number };
|
offset: GenericPoint;
|
||||||
// This is determined on the initial pointer down event
|
// This is determined on the initial pointer down event
|
||||||
arrowDirection: "origin" | "end";
|
arrowDirection: "origin" | "end";
|
||||||
// This is a center point of selected elements determined on the initial pointer down event (for rotation only)
|
// This is a center point of selected elements determined on the initial pointer down event (for rotation only)
|
||||||
center: { x: number; y: number };
|
center: GenericPoint;
|
||||||
};
|
};
|
||||||
hit: {
|
hit: {
|
||||||
// The element the pointer is "hitting", is determined on the initial
|
// The element the pointer is "hitting", is determined on the initial
|
||||||
|
@ -759,10 +754,10 @@ export type PointerDownState = Readonly<{
|
||||||
// Might change during the pointer interaction
|
// Might change during the pointer interaction
|
||||||
hasOccurred: boolean;
|
hasOccurred: boolean;
|
||||||
// Might change during the pointer interaction
|
// Might change during the pointer interaction
|
||||||
offset: { x: number; y: number } | null;
|
offset: GenericPoint | null;
|
||||||
// by default same as PointerDownState.origin. On alt-duplication, reset
|
// by default same as PointerDownState.origin. On alt-duplication, reset
|
||||||
// to current pointer position at time of duplication.
|
// to current pointer position at time of duplication.
|
||||||
origin: { x: number; y: number };
|
origin: GenericPoint;
|
||||||
};
|
};
|
||||||
// We need to have these in the state so that we can unsubscribe them
|
// We need to have these in the state so that we can unsubscribe them
|
||||||
eventListeners: {
|
eventListeners: {
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
import type {
|
import type { Degrees, GenericPoint, PolarCoords, Radians } from "./types";
|
||||||
Degrees,
|
|
||||||
GlobalPoint,
|
|
||||||
LocalPoint,
|
|
||||||
PolarCoords,
|
|
||||||
Radians,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
// TODO: Simplify with modulo and fix for angles beyond 4*Math.PI and - 4*Math.PI
|
// TODO: Simplify with modulo and fix for angles beyond 4*Math.PI and - 4*Math.PI
|
||||||
export const normalizeRadians = (angle: Radians): Radians => {
|
export const normalizeRadians = (angle: Radians): Radians => {
|
||||||
|
@ -24,7 +18,7 @@ export const normalizeRadians = (angle: Radians): Radians => {
|
||||||
* (x, y) for the center point 0,0 where the first number returned is the radius,
|
* (x, y) for the center point 0,0 where the first number returned is the radius,
|
||||||
* the second is the angle in radians.
|
* the second is the angle in radians.
|
||||||
*/
|
*/
|
||||||
export const cartesian2Polar = <P extends GlobalPoint | LocalPoint>([
|
export const cartesian2Polar = <P extends GenericPoint>([
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
]: P): PolarCoords => [
|
]: P): PolarCoords => [
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { Bounds } from "@excalidraw/element/bounds";
|
||||||
import { isPoint, pointDistance, pointFrom } from "./point";
|
import { isPoint, pointDistance, pointFrom } from "./point";
|
||||||
import { rectangle, rectangleIntersectLineSegment } from "./rectangle";
|
import { rectangle, rectangleIntersectLineSegment } from "./rectangle";
|
||||||
|
|
||||||
import type { Curve, GlobalPoint, LineSegment, LocalPoint } from "./types";
|
import type { Curve, GenericPoint, LineSegment } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -13,7 +13,7 @@ import type { Curve, GlobalPoint, LineSegment, LocalPoint } from "./types";
|
||||||
* @param d
|
* @param d
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function curve<Point extends GlobalPoint | LocalPoint>(
|
export function curve<Point extends GenericPoint>(
|
||||||
a: Point,
|
a: Point,
|
||||||
b: Point,
|
b: Point,
|
||||||
c: Point,
|
c: Point,
|
||||||
|
@ -82,7 +82,7 @@ function solve(
|
||||||
return [t0, s0];
|
return [t0, s0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const bezierEquation = <Point extends GlobalPoint | LocalPoint>(
|
const bezierEquation = <Point extends GenericPoint>(
|
||||||
c: Curve<Point>,
|
c: Curve<Point>,
|
||||||
t: number,
|
t: number,
|
||||||
) =>
|
) =>
|
||||||
|
@ -100,9 +100,10 @@ const bezierEquation = <Point extends GlobalPoint | LocalPoint>(
|
||||||
/**
|
/**
|
||||||
* Computes the intersection between a cubic spline and a line segment.
|
* Computes the intersection between a cubic spline and a line segment.
|
||||||
*/
|
*/
|
||||||
export function curveIntersectLineSegment<
|
export function curveIntersectLineSegment<Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
c: Curve<Point>,
|
||||||
>(c: Curve<Point>, l: LineSegment<Point>): Point[] {
|
l: LineSegment<Point>,
|
||||||
|
): Point[] {
|
||||||
// Optimize by doing a cheap bounding box check first
|
// Optimize by doing a cheap bounding box check first
|
||||||
const bounds = curveBounds(c);
|
const bounds = curveBounds(c);
|
||||||
if (
|
if (
|
||||||
|
@ -188,7 +189,7 @@ export function curveIntersectLineSegment<
|
||||||
* @param maxLevel
|
* @param maxLevel
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function curveClosestPoint<Point extends GlobalPoint | LocalPoint>(
|
export function curveClosestPoint<Point extends GenericPoint>(
|
||||||
c: Curve<Point>,
|
c: Curve<Point>,
|
||||||
p: Point,
|
p: Point,
|
||||||
tolerance: number = 1e-3,
|
tolerance: number = 1e-3,
|
||||||
|
@ -245,7 +246,7 @@ export function curveClosestPoint<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param c The curve to test
|
* @param c The curve to test
|
||||||
* @param p The point to measure from
|
* @param p The point to measure from
|
||||||
*/
|
*/
|
||||||
export function curvePointDistance<Point extends GlobalPoint | LocalPoint>(
|
export function curvePointDistance<Point extends GenericPoint>(
|
||||||
c: Curve<Point>,
|
c: Curve<Point>,
|
||||||
p: Point,
|
p: Point,
|
||||||
) {
|
) {
|
||||||
|
@ -261,9 +262,7 @@ export function curvePointDistance<Point extends GlobalPoint | LocalPoint>(
|
||||||
/**
|
/**
|
||||||
* Determines if the parameter is a Curve
|
* Determines if the parameter is a Curve
|
||||||
*/
|
*/
|
||||||
export function isCurve<P extends GlobalPoint | LocalPoint>(
|
export function isCurve<P extends GenericPoint>(v: unknown): v is Curve<P> {
|
||||||
v: unknown,
|
|
||||||
): v is Curve<P> {
|
|
||||||
return (
|
return (
|
||||||
Array.isArray(v) &&
|
Array.isArray(v) &&
|
||||||
v.length === 4 &&
|
v.length === 4 &&
|
||||||
|
@ -274,9 +273,7 @@ export function isCurve<P extends GlobalPoint | LocalPoint>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function curveBounds<Point extends GlobalPoint | LocalPoint>(
|
function curveBounds<Point extends GenericPoint>(c: Curve<Point>): Bounds {
|
||||||
c: Curve<Point>,
|
|
||||||
): Bounds {
|
|
||||||
const [P0, P1, P2, P3] = c;
|
const [P0, P1, P2, P3] = c;
|
||||||
const x = [P0[0], P1[0], P2[0], P3[0]];
|
const x = [P0[0], P1[0], P2[0], P3[0]];
|
||||||
const y = [P0[1], P1[1], P2[1], P3[1]];
|
const y = [P0[1], P1[1], P2[1], P3[1]];
|
||||||
|
|
|
@ -13,13 +13,7 @@ import {
|
||||||
vectorScale,
|
vectorScale,
|
||||||
} from "./vector";
|
} from "./vector";
|
||||||
|
|
||||||
import type {
|
import type { Ellipse, GenericPoint, Line, LineSegment } from "./types";
|
||||||
Ellipse,
|
|
||||||
GlobalPoint,
|
|
||||||
Line,
|
|
||||||
LineSegment,
|
|
||||||
LocalPoint,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an Ellipse object from the parameters
|
* Construct an Ellipse object from the parameters
|
||||||
|
@ -30,7 +24,7 @@ import type {
|
||||||
* @param halfHeight Half of the height of a non-slanted version of the ellipse
|
* @param halfHeight Half of the height of a non-slanted version of the ellipse
|
||||||
* @returns The constructed Ellipse object
|
* @returns The constructed Ellipse object
|
||||||
*/
|
*/
|
||||||
export function ellipse<Point extends GlobalPoint | LocalPoint>(
|
export function ellipse<Point extends GenericPoint>(
|
||||||
center: Point,
|
center: Point,
|
||||||
halfWidth: number,
|
halfWidth: number,
|
||||||
halfHeight: number,
|
halfHeight: number,
|
||||||
|
@ -49,7 +43,7 @@ export function ellipse<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param ellipse The ellipse to compare against
|
* @param ellipse The ellipse to compare against
|
||||||
* @returns TRUE if the point is inside or on the outline of the ellipse
|
* @returns TRUE if the point is inside or on the outline of the ellipse
|
||||||
*/
|
*/
|
||||||
export const ellipseIncludesPoint = <Point extends GlobalPoint | LocalPoint>(
|
export const ellipseIncludesPoint = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -69,7 +63,7 @@ export const ellipseIncludesPoint = <Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param threshold The distance to consider a point close enough to be "on" the outline
|
* @param threshold The distance to consider a point close enough to be "on" the outline
|
||||||
* @returns TRUE if the point is on the ellise outline
|
* @returns TRUE if the point is on the ellise outline
|
||||||
*/
|
*/
|
||||||
export const ellipseTouchesPoint = <Point extends GlobalPoint | LocalPoint>(
|
export const ellipseTouchesPoint = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
|
@ -85,9 +79,7 @@ export const ellipseTouchesPoint = <Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param ellipse The ellipse to calculate the distance to
|
* @param ellipse The ellipse to calculate the distance to
|
||||||
* @returns The eucledian distance
|
* @returns The eucledian distance
|
||||||
*/
|
*/
|
||||||
export const ellipseDistanceFromPoint = <
|
export const ellipseDistanceFromPoint = <Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
): number => {
|
): number => {
|
||||||
|
@ -140,9 +132,10 @@ export const ellipseDistanceFromPoint = <
|
||||||
* Calculate a maximum of two intercept points for a line going throug an
|
* Calculate a maximum of two intercept points for a line going throug an
|
||||||
* ellipse.
|
* ellipse.
|
||||||
*/
|
*/
|
||||||
export function ellipseSegmentInterceptPoints<
|
export function ellipseSegmentInterceptPoints<Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
e: Readonly<Ellipse<Point>>,
|
||||||
>(e: Readonly<Ellipse<Point>>, s: Readonly<LineSegment<Point>>): Point[] {
|
s: Readonly<LineSegment<Point>>,
|
||||||
|
): Point[] {
|
||||||
const rx = e.halfWidth;
|
const rx = e.halfWidth;
|
||||||
const ry = e.halfHeight;
|
const ry = e.halfHeight;
|
||||||
|
|
||||||
|
@ -194,9 +187,7 @@ export function ellipseSegmentInterceptPoints<
|
||||||
return intersections;
|
return intersections;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ellipseLineIntersectionPoints<
|
export function ellipseLineIntersectionPoints<Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
{ center, halfWidth, halfHeight }: Ellipse<Point>,
|
{ center, halfWidth, halfHeight }: Ellipse<Point>,
|
||||||
[g, h]: Line<Point>,
|
[g, h]: Line<Point>,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { pointFrom } from "./point";
|
import { pointFrom } from "./point";
|
||||||
|
|
||||||
import type { GlobalPoint, Line, LocalPoint } from "./types";
|
import type { GenericPoint, Line } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a line from two points.
|
* Create a line from two points.
|
||||||
|
@ -8,7 +8,7 @@ import type { GlobalPoint, Line, LocalPoint } from "./types";
|
||||||
* @param points The two points lying on the line
|
* @param points The two points lying on the line
|
||||||
* @returns The line on which the points lie
|
* @returns The line on which the points lie
|
||||||
*/
|
*/
|
||||||
export function line<P extends GlobalPoint | LocalPoint>(a: P, b: P): Line<P> {
|
export function line<P extends GenericPoint>(a: P, b: P): Line<P> {
|
||||||
return [a, b] as Line<P>;
|
return [a, b] as Line<P>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ export function line<P extends GlobalPoint | LocalPoint>(a: P, b: P): Line<P> {
|
||||||
* @param b
|
* @param b
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function linesIntersectAt<Point extends GlobalPoint | LocalPoint>(
|
export function linesIntersectAt<Point extends GenericPoint>(
|
||||||
a: Line<Point>,
|
a: Line<Point>,
|
||||||
b: Line<Point>,
|
b: Line<Point>,
|
||||||
): Point | null {
|
): Point | null {
|
||||||
|
|
|
@ -2,13 +2,7 @@ import { degreesToRadians } from "./angle";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
import { vectorFromPoint, vectorScale } from "./vector";
|
import { vectorFromPoint, vectorScale } from "./vector";
|
||||||
|
|
||||||
import type {
|
import type { Radians, Degrees, Vector, GenericPoint } from "./types";
|
||||||
LocalPoint,
|
|
||||||
GlobalPoint,
|
|
||||||
Radians,
|
|
||||||
Degrees,
|
|
||||||
Vector,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a properly typed Point instance from the X and Y coordinates.
|
* Create a properly typed Point instance from the X and Y coordinates.
|
||||||
|
@ -17,7 +11,7 @@ import type {
|
||||||
* @param y The Y coordinate
|
* @param y The Y coordinate
|
||||||
* @returns The branded and created point
|
* @returns The branded and created point
|
||||||
*/
|
*/
|
||||||
export function pointFrom<Point extends GlobalPoint | LocalPoint>(
|
export function pointFrom<Point extends GenericPoint>(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
): Point {
|
): Point {
|
||||||
|
@ -30,7 +24,7 @@ export function pointFrom<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param numberArray The number array to check and to convert to Point
|
* @param numberArray The number array to check and to convert to Point
|
||||||
* @returns The point instance
|
* @returns The point instance
|
||||||
*/
|
*/
|
||||||
export function pointFromArray<Point extends GlobalPoint | LocalPoint>(
|
export function pointFromArray<Point extends GenericPoint>(
|
||||||
numberArray: number[],
|
numberArray: number[],
|
||||||
): Point | undefined {
|
): Point | undefined {
|
||||||
return numberArray.length === 2
|
return numberArray.length === 2
|
||||||
|
@ -44,7 +38,7 @@ export function pointFromArray<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param pair A number pair to convert to Point
|
* @param pair A number pair to convert to Point
|
||||||
* @returns The point instance
|
* @returns The point instance
|
||||||
*/
|
*/
|
||||||
export function pointFromPair<Point extends GlobalPoint | LocalPoint>(
|
export function pointFromPair<Point extends GenericPoint>(
|
||||||
pair: [number, number],
|
pair: [number, number],
|
||||||
): Point {
|
): Point {
|
||||||
return pair as Point;
|
return pair as Point;
|
||||||
|
@ -56,7 +50,7 @@ export function pointFromPair<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param v The vector to convert
|
* @param v The vector to convert
|
||||||
* @returns The point the vector points at with origin 0,0
|
* @returns The point the vector points at with origin 0,0
|
||||||
*/
|
*/
|
||||||
export function pointFromVector<P extends GlobalPoint | LocalPoint>(
|
export function pointFromVector<P extends GenericPoint>(
|
||||||
v: Vector,
|
v: Vector,
|
||||||
offset: P = pointFrom(0, 0),
|
offset: P = pointFrom(0, 0),
|
||||||
): P {
|
): P {
|
||||||
|
@ -69,7 +63,7 @@ export function pointFromVector<P extends GlobalPoint | LocalPoint>(
|
||||||
* @param p The value to attempt verification on
|
* @param p The value to attempt verification on
|
||||||
* @returns TRUE if the provided value has the shape of a local or global point
|
* @returns TRUE if the provided value has the shape of a local or global point
|
||||||
*/
|
*/
|
||||||
export function isPoint(p: unknown): p is LocalPoint | GlobalPoint {
|
export function isPoint(p: unknown): p is GenericPoint {
|
||||||
return (
|
return (
|
||||||
Array.isArray(p) &&
|
Array.isArray(p) &&
|
||||||
p.length === 2 &&
|
p.length === 2 &&
|
||||||
|
@ -88,7 +82,7 @@ export function isPoint(p: unknown): p is LocalPoint | GlobalPoint {
|
||||||
* @param b Point The second point to compare
|
* @param b Point The second point to compare
|
||||||
* @returns TRUE if the points are sufficiently close to each other
|
* @returns TRUE if the points are sufficiently close to each other
|
||||||
*/
|
*/
|
||||||
export function pointsEqual<Point extends GlobalPoint | LocalPoint>(
|
export function pointsEqual<Point extends GenericPoint>(
|
||||||
a: Point,
|
a: Point,
|
||||||
b: Point,
|
b: Point,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
@ -104,7 +98,7 @@ export function pointsEqual<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param angle The radians to rotate the point by
|
* @param angle The radians to rotate the point by
|
||||||
* @returns The rotated point
|
* @returns The rotated point
|
||||||
*/
|
*/
|
||||||
export function pointRotateRads<Point extends GlobalPoint | LocalPoint>(
|
export function pointRotateRads<Point extends GenericPoint>(
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
[cx, cy]: Point,
|
[cx, cy]: Point,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
|
@ -123,7 +117,7 @@ export function pointRotateRads<Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param angle The degree to rotate the point by
|
* @param angle The degree to rotate the point by
|
||||||
* @returns The rotated point
|
* @returns The rotated point
|
||||||
*/
|
*/
|
||||||
export function pointRotateDegs<Point extends GlobalPoint | LocalPoint>(
|
export function pointRotateDegs<Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
center: Point,
|
center: Point,
|
||||||
angle: Degrees,
|
angle: Degrees,
|
||||||
|
@ -145,8 +139,8 @@ export function pointRotateDegs<Point extends GlobalPoint | LocalPoint>(
|
||||||
*/
|
*/
|
||||||
// TODO 99% of use is translating between global and local coords, which need to be formalized
|
// TODO 99% of use is translating between global and local coords, which need to be formalized
|
||||||
export function pointTranslate<
|
export function pointTranslate<
|
||||||
From extends GlobalPoint | LocalPoint,
|
From extends GenericPoint,
|
||||||
To extends GlobalPoint | LocalPoint,
|
To extends GenericPoint,
|
||||||
>(p: From, v: Vector = [0, 0] as Vector): To {
|
>(p: From, v: Vector = [0, 0] as Vector): To {
|
||||||
return pointFrom(p[0] + v[0], p[1] + v[1]);
|
return pointFrom(p[0] + v[0], p[1] + v[1]);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +152,7 @@ export function pointTranslate<
|
||||||
* @param b The other point to create the middle point for
|
* @param b The other point to create the middle point for
|
||||||
* @returns The middle point
|
* @returns The middle point
|
||||||
*/
|
*/
|
||||||
export function pointCenter<P extends LocalPoint | GlobalPoint>(a: P, b: P): P {
|
export function pointCenter<P extends GenericPoint>(a: P, b: P): P {
|
||||||
return pointFrom((a[0] + b[0]) / 2, (a[1] + b[1]) / 2);
|
return pointFrom((a[0] + b[0]) / 2, (a[1] + b[1]) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,10 +163,7 @@ export function pointCenter<P extends LocalPoint | GlobalPoint>(a: P, b: P): P {
|
||||||
* @param b Second point
|
* @param b Second point
|
||||||
* @returns The euclidean distance between the two points.
|
* @returns The euclidean distance between the two points.
|
||||||
*/
|
*/
|
||||||
export function pointDistance<P extends LocalPoint | GlobalPoint>(
|
export function pointDistance<P extends GenericPoint>(a: P, b: P): number {
|
||||||
a: P,
|
|
||||||
b: P,
|
|
||||||
): number {
|
|
||||||
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,10 +176,7 @@ export function pointDistance<P extends LocalPoint | GlobalPoint>(
|
||||||
* @param b Second point
|
* @param b Second point
|
||||||
* @returns The euclidean distance between the two points.
|
* @returns The euclidean distance between the two points.
|
||||||
*/
|
*/
|
||||||
export function pointDistanceSq<P extends LocalPoint | GlobalPoint>(
|
export function pointDistanceSq<P extends GenericPoint>(a: P, b: P): number {
|
||||||
a: P,
|
|
||||||
b: P,
|
|
||||||
): number {
|
|
||||||
const xDiff = b[0] - a[0];
|
const xDiff = b[0] - a[0];
|
||||||
const yDiff = b[1] - a[1];
|
const yDiff = b[1] - a[1];
|
||||||
|
|
||||||
|
@ -203,7 +191,7 @@ export function pointDistanceSq<P extends LocalPoint | GlobalPoint>(
|
||||||
* @param multiplier The scaling factor
|
* @param multiplier The scaling factor
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pointScaleFromOrigin = <P extends GlobalPoint | LocalPoint>(
|
export const pointScaleFromOrigin = <P extends GenericPoint>(
|
||||||
p: P,
|
p: P,
|
||||||
mid: P,
|
mid: P,
|
||||||
multiplier: number,
|
multiplier: number,
|
||||||
|
@ -218,7 +206,7 @@ export const pointScaleFromOrigin = <P extends GlobalPoint | LocalPoint>(
|
||||||
* @param r The other point to compare against
|
* @param r The other point to compare against
|
||||||
* @returns TRUE if q is indeed between p and r
|
* @returns TRUE if q is indeed between p and r
|
||||||
*/
|
*/
|
||||||
export const isPointWithinBounds = <P extends GlobalPoint | LocalPoint>(
|
export const isPointWithinBounds = <P extends GenericPoint>(
|
||||||
p: P,
|
p: P,
|
||||||
q: P,
|
q: P,
|
||||||
r: P,
|
r: P,
|
||||||
|
|
|
@ -2,21 +2,17 @@ import { pointsEqual } from "./point";
|
||||||
import { lineSegment, pointOnLineSegment } from "./segment";
|
import { lineSegment, pointOnLineSegment } from "./segment";
|
||||||
import { PRECISION } from "./utils";
|
import { PRECISION } from "./utils";
|
||||||
|
|
||||||
import type { GlobalPoint, LocalPoint, Polygon } from "./types";
|
import type { GenericPoint, Polygon } from "./types";
|
||||||
|
|
||||||
export function polygon<Point extends GlobalPoint | LocalPoint>(
|
export function polygon<Point extends GenericPoint>(...points: Point[]) {
|
||||||
...points: Point[]
|
|
||||||
) {
|
|
||||||
return polygonClose(points) as Polygon<Point>;
|
return polygonClose(points) as Polygon<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function polygonFromPoints<Point extends GlobalPoint | LocalPoint>(
|
export function polygonFromPoints<Point extends GenericPoint>(points: Point[]) {
|
||||||
points: Point[],
|
|
||||||
) {
|
|
||||||
return polygonClose(points) as Polygon<Point>;
|
return polygonClose(points) as Polygon<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const polygonIncludesPoint = <Point extends LocalPoint | GlobalPoint>(
|
export const polygonIncludesPoint = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
polygon: Polygon<Point>,
|
polygon: Polygon<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -69,7 +65,7 @@ export const polygonIncludesPointNonZero = <Point extends [number, number]>(
|
||||||
return windingNumber !== 0;
|
return windingNumber !== 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnPolygon = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnPolygon = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
poly: Polygon<Point>,
|
poly: Polygon<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
|
@ -86,16 +82,12 @@ export const pointOnPolygon = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return on;
|
return on;
|
||||||
};
|
};
|
||||||
|
|
||||||
function polygonClose<Point extends LocalPoint | GlobalPoint>(
|
function polygonClose<Point extends GenericPoint>(polygon: Point[]) {
|
||||||
polygon: Point[],
|
|
||||||
) {
|
|
||||||
return polygonIsClosed(polygon)
|
return polygonIsClosed(polygon)
|
||||||
? polygon
|
? polygon
|
||||||
: ([...polygon, polygon[0]] as Polygon<Point>);
|
: ([...polygon, polygon[0]] as Polygon<Point>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function polygonIsClosed<Point extends LocalPoint | GlobalPoint>(
|
function polygonIsClosed<Point extends GenericPoint>(polygon: Point[]) {
|
||||||
polygon: Point[],
|
|
||||||
) {
|
|
||||||
return pointsEqual(polygon[0], polygon[polygon.length - 1]);
|
return pointsEqual(polygon[0], polygon[polygon.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { pointFrom } from "./point";
|
import { pointFrom } from "./point";
|
||||||
import { lineSegment, lineSegmentIntersectionPoints } from "./segment";
|
import { lineSegment, lineSegmentIntersectionPoints } from "./segment";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint, Rectangle } from "./types";
|
import type { GenericPoint, LineSegment, Rectangle } from "./types";
|
||||||
|
|
||||||
export function rectangle<P extends GlobalPoint | LocalPoint>(
|
export function rectangle<P extends GenericPoint>(
|
||||||
topLeft: P,
|
topLeft: P,
|
||||||
bottomRight: P,
|
bottomRight: P,
|
||||||
): Rectangle<P> {
|
): Rectangle<P> {
|
||||||
return [topLeft, bottomRight] as Rectangle<P>;
|
return [topLeft, bottomRight] as Rectangle<P>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rectangleIntersectLineSegment<
|
export function rectangleIntersectLineSegment<Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint,
|
r: Rectangle<Point>,
|
||||||
>(r: Rectangle<Point>, l: LineSegment<Point>): Point[] {
|
l: LineSegment<Point>,
|
||||||
|
): Point[] {
|
||||||
return [
|
return [
|
||||||
lineSegment(r[0], pointFrom(r[1][0], r[0][1])),
|
lineSegment(r[0], pointFrom(r[1][0], r[0][1])),
|
||||||
lineSegment(pointFrom(r[1][0], r[0][1]), r[1]),
|
lineSegment(pointFrom(r[1][0], r[0][1]), r[1]),
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
vectorSubtract,
|
vectorSubtract,
|
||||||
} from "./vector";
|
} from "./vector";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint, Radians } from "./types";
|
import type { GenericPoint, LineSegment, Radians } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a line segment from two points.
|
* Create a line segment from two points.
|
||||||
|
@ -22,7 +22,7 @@ import type { GlobalPoint, LineSegment, LocalPoint, Radians } from "./types";
|
||||||
* @param points The two points delimiting the line segment on each end
|
* @param points The two points delimiting the line segment on each end
|
||||||
* @returns The line segment delineated by the points
|
* @returns The line segment delineated by the points
|
||||||
*/
|
*/
|
||||||
export function lineSegment<P extends GlobalPoint | LocalPoint>(
|
export function lineSegment<P extends GenericPoint>(
|
||||||
a: P,
|
a: P,
|
||||||
b: P,
|
b: P,
|
||||||
): LineSegment<P> {
|
): LineSegment<P> {
|
||||||
|
@ -34,7 +34,7 @@ export function lineSegment<P extends GlobalPoint | LocalPoint>(
|
||||||
* @param segment
|
* @param segment
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const isLineSegment = <Point extends GlobalPoint | LocalPoint>(
|
export const isLineSegment = <Point extends GenericPoint>(
|
||||||
segment: unknown,
|
segment: unknown,
|
||||||
): segment is LineSegment<Point> =>
|
): segment is LineSegment<Point> =>
|
||||||
Array.isArray(segment) &&
|
Array.isArray(segment) &&
|
||||||
|
@ -51,7 +51,7 @@ export const isLineSegment = <Point extends GlobalPoint | LocalPoint>(
|
||||||
* @param origin
|
* @param origin
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const lineSegmentRotate = <Point extends LocalPoint | GlobalPoint>(
|
export const lineSegmentRotate = <Point extends GenericPoint>(
|
||||||
l: LineSegment<Point>,
|
l: LineSegment<Point>,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
origin?: Point,
|
origin?: Point,
|
||||||
|
@ -66,7 +66,7 @@ export const lineSegmentRotate = <Point extends LocalPoint | GlobalPoint>(
|
||||||
* Calculates the point two line segments with a definite start and end point
|
* Calculates the point two line segments with a definite start and end point
|
||||||
* intersect at.
|
* intersect at.
|
||||||
*/
|
*/
|
||||||
export const segmentsIntersectAt = <Point extends GlobalPoint | LocalPoint>(
|
export const segmentsIntersectAt = <Point extends GenericPoint>(
|
||||||
a: Readonly<LineSegment<Point>>,
|
a: Readonly<LineSegment<Point>>,
|
||||||
b: Readonly<LineSegment<Point>>,
|
b: Readonly<LineSegment<Point>>,
|
||||||
): Point | null => {
|
): Point | null => {
|
||||||
|
@ -99,7 +99,7 @@ export const segmentsIntersectAt = <Point extends GlobalPoint | LocalPoint>(
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnLineSegment = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnLineSegment = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
line: LineSegment<Point>,
|
line: LineSegment<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
|
@ -113,7 +113,7 @@ export const pointOnLineSegment = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return distance < threshold;
|
return distance < threshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const distanceToLineSegment = <Point extends LocalPoint | GlobalPoint>(
|
export const distanceToLineSegment = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
line: LineSegment<Point>,
|
line: LineSegment<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -158,9 +158,7 @@ export const distanceToLineSegment = <Point extends LocalPoint | GlobalPoint>(
|
||||||
* @param s
|
* @param s
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function lineSegmentIntersectionPoints<
|
export function lineSegmentIntersectionPoints<Point extends GenericPoint>(
|
||||||
Point extends GlobalPoint | LocalPoint,
|
|
||||||
>(
|
|
||||||
l: LineSegment<Point>,
|
l: LineSegment<Point>,
|
||||||
s: LineSegment<Point>,
|
s: LineSegment<Point>,
|
||||||
threshold?: number,
|
threshold?: number,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { GlobalPoint, LocalPoint, Triangle } from "./types";
|
import type { GenericPoint, Triangle } from "./types";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import type { GlobalPoint, LocalPoint, Triangle } from "./types";
|
||||||
* @param p The point to test whether is in the triangle
|
* @param p The point to test whether is in the triangle
|
||||||
* @returns TRUE if the point is inside of the triangle
|
* @returns TRUE if the point is inside of the triangle
|
||||||
*/
|
*/
|
||||||
export function triangleIncludesPoint<P extends GlobalPoint | LocalPoint>(
|
export function triangleIncludesPoint<P extends GenericPoint>(
|
||||||
[a, b, c]: Triangle<P>,
|
[a, b, c]: Triangle<P>,
|
||||||
p: P,
|
p: P,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|
|
@ -27,6 +27,12 @@ export type InclusiveRange = [number, number] & { _brand: "excalimath_degree" };
|
||||||
// Point
|
// Point
|
||||||
//
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a 2D position in world or canvas space. A
|
||||||
|
* unintified glboal, viewport, or local coordinate.
|
||||||
|
*/
|
||||||
|
export type GenericPoint = GlobalPoint | ViewportPoint | LocalPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a 2D position in world or canvas space. A
|
* Represents a 2D position in world or canvas space. A
|
||||||
* global coordinate.
|
* global coordinate.
|
||||||
|
@ -35,6 +41,14 @@ export type GlobalPoint = [x: number, y: number] & {
|
||||||
_brand: "excalimath__globalpoint";
|
_brand: "excalimath__globalpoint";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a 2D position in world or viewport space. A
|
||||||
|
* viewport coordinate.
|
||||||
|
*/
|
||||||
|
export type ViewportPoint = [x: number, y: number] & {
|
||||||
|
_brand: "excalimath__viewportpoint";
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a 2D position in whatever local space it's
|
* Represents a 2D position in whatever local space it's
|
||||||
* needed. A local coordinate.
|
* needed. A local coordinate.
|
||||||
|
@ -48,7 +62,7 @@ export type LocalPoint = [x: number, y: number] & {
|
||||||
/**
|
/**
|
||||||
* A line is an infinitely long object with no width, depth, or curvature.
|
* A line is an infinitely long object with no width, depth, or curvature.
|
||||||
*/
|
*/
|
||||||
export type Line<P extends GlobalPoint | LocalPoint> = [p: P, q: P] & {
|
export type Line<P extends GenericPoint> = [p: P, q: P] & {
|
||||||
_brand: "excalimath_line";
|
_brand: "excalimath_line";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,7 +71,7 @@ export type Line<P extends GlobalPoint | LocalPoint> = [p: P, q: P] & {
|
||||||
* line that is bounded by two distinct end points, and
|
* line that is bounded by two distinct end points, and
|
||||||
* contains every point on the line that is between its endpoints.
|
* contains every point on the line that is between its endpoints.
|
||||||
*/
|
*/
|
||||||
export type LineSegment<P extends GlobalPoint | LocalPoint> = [a: P, b: P] & {
|
export type LineSegment<P extends GenericPoint> = [a: P, b: P] & {
|
||||||
_brand: "excalimath_linesegment";
|
_brand: "excalimath_linesegment";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,18 +91,14 @@ export type Vector = [u: number, v: number] & {
|
||||||
/**
|
/**
|
||||||
* A triangle represented by 3 points
|
* A triangle represented by 3 points
|
||||||
*/
|
*/
|
||||||
export type Triangle<P extends GlobalPoint | LocalPoint> = [
|
export type Triangle<P extends GenericPoint> = [a: P, b: P, c: P] & {
|
||||||
a: P,
|
|
||||||
b: P,
|
|
||||||
c: P,
|
|
||||||
] & {
|
|
||||||
_brand: "excalimath__triangle";
|
_brand: "excalimath__triangle";
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A rectangular shape represented by 4 points at its corners
|
* A rectangular shape represented by 4 points at its corners
|
||||||
*/
|
*/
|
||||||
export type Rectangle<P extends GlobalPoint | LocalPoint> = [a: P, b: P] & {
|
export type Rectangle<P extends GenericPoint> = [a: P, b: P] & {
|
||||||
_brand: "excalimath__rectangle";
|
_brand: "excalimath__rectangle";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,7 +110,7 @@ export type Rectangle<P extends GlobalPoint | LocalPoint> = [a: P, b: P] & {
|
||||||
* A polygon is a closed shape by connecting the given points
|
* A polygon is a closed shape by connecting the given points
|
||||||
* rectangles and diamonds are modelled by polygons
|
* rectangles and diamonds are modelled by polygons
|
||||||
*/
|
*/
|
||||||
export type Polygon<Point extends GlobalPoint | LocalPoint> = Point[] & {
|
export type Polygon<Point extends GenericPoint> = Point[] & {
|
||||||
_brand: "excalimath_polygon";
|
_brand: "excalimath_polygon";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,12 +121,7 @@ export type Polygon<Point extends GlobalPoint | LocalPoint> = Point[] & {
|
||||||
/**
|
/**
|
||||||
* Cubic bezier curve with four control points
|
* Cubic bezier curve with four control points
|
||||||
*/
|
*/
|
||||||
export type Curve<Point extends GlobalPoint | LocalPoint> = [
|
export type Curve<Point extends GenericPoint> = [Point, Point, Point, Point] & {
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
Point,
|
|
||||||
] & {
|
|
||||||
_brand: "excalimath_curve";
|
_brand: "excalimath_curve";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -131,7 +136,7 @@ export type PolarCoords = [
|
||||||
but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
||||||
in replace of semi major and semi minor axes
|
in replace of semi major and semi minor axes
|
||||||
*/
|
*/
|
||||||
export type Ellipse<Point extends GlobalPoint | LocalPoint> = {
|
export type Ellipse<Point extends GenericPoint> = {
|
||||||
center: Point;
|
center: Point;
|
||||||
halfWidth: number;
|
halfWidth: number;
|
||||||
halfHeight: number;
|
halfHeight: number;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { GlobalPoint, LocalPoint, Vector } from "./types";
|
import type { GenericPoint, Vector } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a vector from the x and y coordiante elements.
|
* Create a vector from the x and y coordiante elements.
|
||||||
|
@ -23,7 +23,7 @@ export function vector(
|
||||||
* @param origin The origin point in a given coordiante system
|
* @param origin The origin point in a given coordiante system
|
||||||
* @returns The created vector from the point and the origin
|
* @returns The created vector from the point and the origin
|
||||||
*/
|
*/
|
||||||
export function vectorFromPoint<Point extends GlobalPoint | LocalPoint>(
|
export function vectorFromPoint<Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
origin: Point = [0, 0] as Point,
|
origin: Point = [0, 0] as Point,
|
||||||
): Vector {
|
): Vector {
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
import {
|
import {
|
||||||
vectorCross,
|
vectorCross,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
type GlobalPoint,
|
type GenericPoint,
|
||||||
type LocalPoint,
|
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element/bounds";
|
import type { Bounds } from "@excalidraw/element/bounds";
|
||||||
|
|
||||||
export type LineSegment<P extends LocalPoint | GlobalPoint> = [P, P];
|
export type LineSegment<P extends GenericPoint> = [P, P];
|
||||||
|
|
||||||
export function getBBox<P extends LocalPoint | GlobalPoint>(
|
export function getBBox<P extends GenericPoint>(line: LineSegment<P>): Bounds {
|
||||||
line: LineSegment<P>,
|
|
||||||
): Bounds {
|
|
||||||
return [
|
return [
|
||||||
Math.min(line[0][0], line[1][0]),
|
Math.min(line[0][0], line[1][0]),
|
||||||
Math.min(line[0][1], line[1][1]),
|
Math.min(line[0][1], line[1][1]),
|
||||||
|
@ -26,10 +23,7 @@ export function doBBoxesIntersect(a: Bounds, b: Bounds) {
|
||||||
|
|
||||||
const EPSILON = 0.000001;
|
const EPSILON = 0.000001;
|
||||||
|
|
||||||
export function isPointOnLine<P extends GlobalPoint | LocalPoint>(
|
export function isPointOnLine<P extends GenericPoint>(l: LineSegment<P>, p: P) {
|
||||||
l: LineSegment<P>,
|
|
||||||
p: P,
|
|
||||||
) {
|
|
||||||
const p1 = vectorFromPoint(l[1], l[0]);
|
const p1 = vectorFromPoint(l[1], l[0]);
|
||||||
const p2 = vectorFromPoint(p, l[0]);
|
const p2 = vectorFromPoint(p, l[0]);
|
||||||
|
|
||||||
|
@ -38,7 +32,7 @@ export function isPointOnLine<P extends GlobalPoint | LocalPoint>(
|
||||||
return Math.abs(r) < EPSILON;
|
return Math.abs(r) < EPSILON;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPointRightOfLine<P extends GlobalPoint | LocalPoint>(
|
export function isPointRightOfLine<P extends GenericPoint>(
|
||||||
l: LineSegment<P>,
|
l: LineSegment<P>,
|
||||||
p: P,
|
p: P,
|
||||||
) {
|
) {
|
||||||
|
@ -48,9 +42,10 @@ export function isPointRightOfLine<P extends GlobalPoint | LocalPoint>(
|
||||||
return vectorCross(p1, p2) < 0;
|
return vectorCross(p1, p2) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLineSegmentTouchingOrCrossingLine<
|
export function isLineSegmentTouchingOrCrossingLine<P extends GenericPoint>(
|
||||||
P extends GlobalPoint | LocalPoint,
|
a: LineSegment<P>,
|
||||||
>(a: LineSegment<P>, b: LineSegment<P>) {
|
b: LineSegment<P>,
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
isPointOnLine(a, b[0]) ||
|
isPointOnLine(a, b[0]) ||
|
||||||
isPointOnLine(a, b[1]) ||
|
isPointOnLine(a, b[1]) ||
|
||||||
|
@ -61,7 +56,7 @@ export function isLineSegmentTouchingOrCrossingLine<
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://martin-thoma.com/how-to-check-if-two-line-segments-intersect/
|
// https://martin-thoma.com/how-to-check-if-two-line-segments-intersect/
|
||||||
export function doLineSegmentsIntersect<P extends GlobalPoint | LocalPoint>(
|
export function doLineSegmentsIntersect<P extends GenericPoint>(
|
||||||
a: LineSegment<P>,
|
a: LineSegment<P>,
|
||||||
b: LineSegment<P>,
|
b: LineSegment<P>,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -5,19 +5,17 @@ import {
|
||||||
pointOnLineSegment,
|
pointOnLineSegment,
|
||||||
pointOnPolygon,
|
pointOnPolygon,
|
||||||
polygonFromPoints,
|
polygonFromPoints,
|
||||||
type GlobalPoint,
|
|
||||||
type LocalPoint,
|
|
||||||
type Polygon,
|
type Polygon,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Curve } from "@excalidraw/math";
|
import type { Curve, GenericPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import { pointInEllipse, pointOnEllipse } from "./shape";
|
import { pointInEllipse, pointOnEllipse } from "./shape";
|
||||||
|
|
||||||
import type { Polycurve, Polyline, GeometricShape } from "./shape";
|
import type { Polycurve, Polyline, GeometricShape } from "./shape";
|
||||||
|
|
||||||
// check if the given point is considered on the given shape's border
|
// check if the given point is considered on the given shape's border
|
||||||
export const isPointOnShape = <Point extends GlobalPoint | LocalPoint>(
|
export const isPointOnShape = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
shape: GeometricShape<Point>,
|
shape: GeometricShape<Point>,
|
||||||
tolerance = 0,
|
tolerance = 0,
|
||||||
|
@ -43,7 +41,7 @@ export const isPointOnShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if the given point is considered inside the element's border
|
// check if the given point is considered inside the element's border
|
||||||
export const isPointInShape = <Point extends GlobalPoint | LocalPoint>(
|
export const isPointInShape = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
shape: GeometricShape<Point>,
|
shape: GeometricShape<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -69,14 +67,14 @@ export const isPointInShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if the given element is in the given bounds
|
// check if the given element is in the given bounds
|
||||||
export const isPointInBounds = <Point extends GlobalPoint | LocalPoint>(
|
export const isPointInBounds = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
bounds: Polygon<Point>,
|
bounds: Polygon<Point>,
|
||||||
) => {
|
) => {
|
||||||
return polygonIncludesPoint(point, bounds);
|
return polygonIncludesPoint(point, bounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pointOnPolycurve = <Point extends LocalPoint | GlobalPoint>(
|
const pointOnPolycurve = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
polycurve: Polycurve<Point>,
|
polycurve: Polycurve<Point>,
|
||||||
tolerance: number,
|
tolerance: number,
|
||||||
|
@ -84,7 +82,7 @@ const pointOnPolycurve = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return polycurve.some((curve) => pointOnCurve(point, curve, tolerance));
|
return polycurve.some((curve) => pointOnCurve(point, curve, tolerance));
|
||||||
};
|
};
|
||||||
|
|
||||||
const cubicBezierEquation = <Point extends LocalPoint | GlobalPoint>(
|
const cubicBezierEquation = <Point extends GenericPoint>(
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
) => {
|
) => {
|
||||||
const [p0, p1, p2, p3] = curve;
|
const [p0, p1, p2, p3] = curve;
|
||||||
|
@ -96,7 +94,7 @@ const cubicBezierEquation = <Point extends LocalPoint | GlobalPoint>(
|
||||||
p0[idx] * Math.pow(t, 3);
|
p0[idx] * Math.pow(t, 3);
|
||||||
};
|
};
|
||||||
|
|
||||||
const polyLineFromCurve = <Point extends LocalPoint | GlobalPoint>(
|
const polyLineFromCurve = <Point extends GenericPoint>(
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
segments = 10,
|
segments = 10,
|
||||||
): Polyline<Point> => {
|
): Polyline<Point> => {
|
||||||
|
@ -118,7 +116,7 @@ const polyLineFromCurve = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return lineSegments;
|
return lineSegments;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnCurve = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnCurve = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
curve: Curve<Point>,
|
curve: Curve<Point>,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
|
@ -126,7 +124,7 @@ export const pointOnCurve = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
|
return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnPolyline = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnPolyline = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
polyline: Polyline<Point>,
|
polyline: Polyline<Point>,
|
||||||
threshold = 10e-5,
|
threshold = 10e-5,
|
||||||
|
|
|
@ -30,8 +30,6 @@ import {
|
||||||
vectorAdd,
|
vectorAdd,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
vectorScale,
|
vectorScale,
|
||||||
type GlobalPoint,
|
|
||||||
type LocalPoint,
|
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { getElementAbsoluteCoords } from "@excalidraw/element/bounds";
|
import { getElementAbsoluteCoords } from "@excalidraw/element/bounds";
|
||||||
|
@ -52,31 +50,36 @@ import type {
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
import type { Curve, LineSegment, Polygon, Radians } from "@excalidraw/math";
|
import type {
|
||||||
|
Curve,
|
||||||
|
GenericPoint,
|
||||||
|
LineSegment,
|
||||||
|
Polygon,
|
||||||
|
Radians,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Drawable, Op } from "roughjs/bin/core";
|
import type { Drawable, Op } from "roughjs/bin/core";
|
||||||
|
|
||||||
// a polyline (made up term here) is a line consisting of other line segments
|
// a polyline (made up term here) is a line consisting of other line segments
|
||||||
// this corresponds to a straight line element in the editor but it could also
|
// this corresponds to a straight line element in the editor but it could also
|
||||||
// be used to model other elements
|
// be used to model other elements
|
||||||
export type Polyline<Point extends GlobalPoint | LocalPoint> =
|
export type Polyline<Point extends GenericPoint> = LineSegment<Point>[];
|
||||||
LineSegment<Point>[];
|
|
||||||
|
|
||||||
// a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
// a polycurve is a curve consisting of ther curves, this corresponds to a complex
|
||||||
// curve on the canvas
|
// curve on the canvas
|
||||||
export type Polycurve<Point extends GlobalPoint | LocalPoint> = Curve<Point>[];
|
export type Polycurve<Point extends GenericPoint> = Curve<Point>[];
|
||||||
|
|
||||||
// an ellipse is specified by its center, angle, and its major and minor axes
|
// an ellipse is specified by its center, angle, and its major and minor axes
|
||||||
// but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
// but for the sake of simplicity, we've used halfWidth and halfHeight instead
|
||||||
// in replace of semi major and semi minor axes
|
// in replace of semi major and semi minor axes
|
||||||
export type Ellipse<Point extends GlobalPoint | LocalPoint> = {
|
export type Ellipse<Point extends GenericPoint> = {
|
||||||
center: Point;
|
center: Point;
|
||||||
angle: Radians;
|
angle: Radians;
|
||||||
halfWidth: number;
|
halfWidth: number;
|
||||||
halfHeight: number;
|
halfHeight: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GeometricShape<Point extends GlobalPoint | LocalPoint> =
|
export type GeometricShape<Point extends GenericPoint> =
|
||||||
| {
|
| {
|
||||||
type: "line";
|
type: "line";
|
||||||
data: LineSegment<Point>;
|
data: LineSegment<Point>;
|
||||||
|
@ -113,7 +116,7 @@ type RectangularElement =
|
||||||
| ExcalidrawSelectionElement;
|
| ExcalidrawSelectionElement;
|
||||||
|
|
||||||
// polygon
|
// polygon
|
||||||
export const getPolygonShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getPolygonShape = <Point extends GenericPoint>(
|
||||||
element: RectangularElement,
|
element: RectangularElement,
|
||||||
): GeometricShape<Point> => {
|
): GeometricShape<Point> => {
|
||||||
const { angle, width, height, x, y } = element;
|
const { angle, width, height, x, y } = element;
|
||||||
|
@ -148,7 +151,7 @@ export const getPolygonShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// return the selection box for an element, possibly rotated as well
|
// return the selection box for an element, possibly rotated as well
|
||||||
export const getSelectionBoxShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getSelectionBoxShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
padding = 10,
|
padding = 10,
|
||||||
|
@ -178,7 +181,7 @@ export const getSelectionBoxShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// ellipse
|
// ellipse
|
||||||
export const getEllipseShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getEllipseShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
): GeometricShape<Point> => {
|
): GeometricShape<Point> => {
|
||||||
const { width, height, angle, x, y } = element;
|
const { width, height, angle, x, y } = element;
|
||||||
|
@ -209,7 +212,7 @@ export const getCurvePathOps = (shape: Drawable): Op[] => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// linear
|
// linear
|
||||||
export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getCurveShape = <Point extends GenericPoint>(
|
||||||
roughShape: Drawable,
|
roughShape: Drawable,
|
||||||
startingPoint: Point = pointFrom(0, 0),
|
startingPoint: Point = pointFrom(0, 0),
|
||||||
angleInRadian: Radians,
|
angleInRadian: Radians,
|
||||||
|
@ -247,7 +250,7 @@ export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const polylineFromPoints = <Point extends GlobalPoint | LocalPoint>(
|
const polylineFromPoints = <Point extends GenericPoint>(
|
||||||
points: Point[],
|
points: Point[],
|
||||||
): Polyline<Point> => {
|
): Polyline<Point> => {
|
||||||
let previousPoint: Point = points[0];
|
let previousPoint: Point = points[0];
|
||||||
|
@ -262,7 +265,7 @@ const polylineFromPoints = <Point extends GlobalPoint | LocalPoint>(
|
||||||
return polyline;
|
return polyline;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getFreedrawShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawFreeDrawElement,
|
element: ExcalidrawFreeDrawElement,
|
||||||
center: Point,
|
center: Point,
|
||||||
isClosed: boolean = false,
|
isClosed: boolean = false,
|
||||||
|
@ -293,7 +296,7 @@ export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
) as GeometricShape<Point>;
|
) as GeometricShape<Point>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getClosedCurveShape = <Point extends GenericPoint>(
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
roughShape: Drawable,
|
roughShape: Drawable,
|
||||||
startingPoint: Point = pointFrom<Point>(0, 0),
|
startingPoint: Point = pointFrom<Point>(0, 0),
|
||||||
|
@ -359,9 +362,7 @@ export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
* @returns An array of intersections
|
* @returns An array of intersections
|
||||||
*/
|
*/
|
||||||
// TODO: Replace with final rounded rectangle code
|
// TODO: Replace with final rounded rectangle code
|
||||||
export const segmentIntersectRectangleElement = <
|
export const segmentIntersectRectangleElement = <Point extends GenericPoint>(
|
||||||
Point extends LocalPoint | GlobalPoint,
|
|
||||||
>(
|
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
segment: LineSegment<Point>,
|
segment: LineSegment<Point>,
|
||||||
gap: number = 0,
|
gap: number = 0,
|
||||||
|
@ -399,7 +400,7 @@ export const segmentIntersectRectangleElement = <
|
||||||
.filter((i): i is Point => !!i);
|
.filter((i): i is Point => !!i);
|
||||||
};
|
};
|
||||||
|
|
||||||
const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
const distanceToEllipse = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -456,7 +457,7 @@ const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnEllipse = <Point extends GenericPoint>(
|
||||||
point: Point,
|
point: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
threshold = PRECISION,
|
threshold = PRECISION,
|
||||||
|
@ -464,7 +465,7 @@ export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return distanceToEllipse(point, ellipse) <= threshold;
|
return distanceToEllipse(point, ellipse) <= threshold;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointInEllipse = <Point extends LocalPoint | GlobalPoint>(
|
export const pointInEllipse = <Point extends GenericPoint>(
|
||||||
p: Point,
|
p: Point,
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -486,7 +487,7 @@ export const pointInEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ellipseAxes = <Point extends LocalPoint | GlobalPoint>(
|
export const ellipseAxes = <Point extends GenericPoint>(
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
const widthGreaterThanHeight = ellipse.halfWidth > ellipse.halfHeight;
|
||||||
|
@ -504,7 +505,7 @@ export const ellipseAxes = <Point extends LocalPoint | GlobalPoint>(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ellipseFocusToCenter = <Point extends LocalPoint | GlobalPoint>(
|
export const ellipseFocusToCenter = <Point extends GenericPoint>(
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
const { majorAxis, minorAxis } = ellipseAxes(ellipse);
|
||||||
|
@ -512,7 +513,7 @@ export const ellipseFocusToCenter = <Point extends LocalPoint | GlobalPoint>(
|
||||||
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
return Math.sqrt(majorAxis ** 2 - minorAxis ** 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ellipseExtremes = <Point extends LocalPoint | GlobalPoint>(
|
export const ellipseExtremes = <Point extends GenericPoint>(
|
||||||
ellipse: Ellipse<Point>,
|
ellipse: Ellipse<Point>,
|
||||||
) => {
|
) => {
|
||||||
const { center, angle } = ellipse;
|
const { center, angle } = ellipse;
|
||||||
|
|
|
@ -69,10 +69,10 @@ exports[`exportToSvg > with default arguments 1`] = `
|
||||||
"openMenu": null,
|
"openMenu": null,
|
||||||
"openPopup": null,
|
"openPopup": null,
|
||||||
"openSidebar": null,
|
"openSidebar": null,
|
||||||
"originSnapOffset": {
|
"originSnapOffset": [
|
||||||
"x": 0,
|
0,
|
||||||
"y": 0,
|
0,
|
||||||
},
|
],
|
||||||
"pasteDialog": {
|
"pasteDialog": {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue