This commit is contained in:
sunub 2025-04-20 21:00:59 +09:00 committed by GitHub
commit 741ddd4e16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 860 additions and 851 deletions

View file

@ -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,

View file

@ -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);
}; };

View file

@ -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 */

View file

@ -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,

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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,
) => ) =>

View file

@ -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),
); );
}; };

View file

@ -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));

View file

@ -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,10 +1534,11 @@ 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] =
LinearElementEditor.getPointGlobalCoordinates(
element, element,
referencePoint, referencePoint,
elementsMap, elementsMap,
@ -1539,20 +1546,16 @@ export class LinearElementEditor {
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,
); );

View file

@ -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,

View file

@ -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 =>

View file

@ -37,14 +37,16 @@ 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, clientX: viewTransformations.offsetLeft,
clientY: viewTransformations.offsetTop, clientY: viewTransformations.offsetTop,
}, },
viewTransformations, viewTransformations,
); );
const bottomRightSceneCoords = viewportCoordsToSceneCoords( const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] =
viewportCoordsToSceneCoords(
{ {
clientX: viewTransformations.offsetLeft + width, clientX: viewTransformations.offsetLeft + width,
clientY: viewTransformations.offsetTop + height, clientY: viewTransformations.offsetTop + height,
@ -53,10 +55,10 @@ export const isElementInViewport = (
); );
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), clientX: viewTransformations.offsetLeft + (padding?.left || 0),
clientY: viewTransformations.offsetTop + (padding?.top || 0), clientY: viewTransformations.offsetTop + (padding?.top || 0),
}, },
viewTransformations, viewTransformations,
); );
const bottomRightSceneCoords = viewportCoordsToSceneCoords( const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] =
viewportCoordsToSceneCoords(
{ {
clientX: viewTransformations.offsetLeft + width - (padding?.right || 0), clientX: viewTransformations.offsetLeft + width - (padding?.right || 0),
clientY: viewTransformations.offsetTop + height - (padding?.bottom || 0), clientY:
viewTransformations.offsetTop + height - (padding?.bottom || 0),
}, },
viewTransformations, viewTransformations,
); );
return ( return (
x1 >= topLeftSceneCoords.x && x1 >= topLeftSceneCoordsX &&
y1 >= topLeftSceneCoords.y && y1 >= topLeftSceneCoordsY &&
x2 <= bottomRightSceneCoords.x && x2 <= bottomRightSceneCoordsX &&
y2 <= bottomRightSceneCoords.y y2 <= bottomRightSceneCoordsY
); );
}; };

View file

@ -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,

View file

@ -143,7 +143,7 @@ export const actionFinalize = register({
maybeBindLinearElement( maybeBindLinearElement(
multiPointElement, multiPointElement,
appState, appState,
{ x, y }, pointFrom(x, y),
elementsMap, elementsMap,
elements, elements,
); );

View file

@ -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

View file

@ -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(),

View file

@ -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

View file

@ -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,
); );

View file

@ -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,
}); });

View file

@ -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,
); );

View file

@ -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);

View file

@ -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,

View file

@ -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",

View file

@ -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,
}); });

View file

@ -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>;

View file

@ -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,
y:
horizontalSnapLines.length > 0 horizontalSnapLines.length > 0
? horizontalSnapLines[0].points[0][1] - pointer.y ? horizontalSnapLines[0].points[0][1] - pointer.y
: 0, : 0,
}, ),
snapLines: [...verticalSnapLines, ...horizontalSnapLines], snapLines: [...verticalSnapLines, ...horizontalSnapLines],
}; };
}; };

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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: {

View file

@ -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 => [

View file

@ -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]];

View file

@ -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[] {

View file

@ -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 {

View file

@ -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,

View file

@ -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]);
} }

View file

@ -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]),

View file

@ -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,

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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>,
) { ) {

View file

@ -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,

View file

@ -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;

View file

@ -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,