diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 707e7292e3..39e2e9149c 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,10 +1,12 @@ import { average, pointFrom, type GlobalPoint } from "@excalidraw/math"; +import { getCenterForBounds, getElementBounds } from "@excalidraw/element"; import type { ExcalidrawBindableElement, FontFamilyValues, FontString, ExcalidrawElement, + ElementsMap, } from "@excalidraw/element/types"; import type { @@ -1240,16 +1242,13 @@ export const castArray = (value: T | T[]): T[] => export const elementCenterPoint = ( element: ExcalidrawElement, + elementsMap: ElementsMap, xOffset: number = 0, yOffset: number = 0, ) => { - const { x, y, width, height } = element; + const [x, y] = getCenterForBounds(getElementBounds(element, elementsMap)); - const centerXPoint = x + width / 2 + xOffset; - - const centerYPoint = y + height / 2 + yOffset; - - return pointFrom(centerXPoint, centerYPoint); + return pointFrom(x + xOffset, y + yOffset); }; /** hack for Array.isArray type guard not working with readonly value[] */ diff --git a/packages/element/src/Shape.ts b/packages/element/src/Shape.ts index 4def419574..317dfbacb8 100644 --- a/packages/element/src/Shape.ts +++ b/packages/element/src/Shape.ts @@ -1,8 +1,17 @@ import { simplify } from "points-on-curve"; -import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math"; +import { + pointFrom, + pointDistance, + type LocalPoint, + pointRotateRads, +} from "@excalidraw/math"; import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common"; +import { RoughGenerator } from "roughjs/bin/generator"; + +import type { GlobalPoint } from "@excalidraw/math"; + import type { Mutable } from "@excalidraw/common/utility-types"; import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types"; @@ -20,7 +29,12 @@ import { headingForPointIsHorizontal } from "./heading"; import { canChangeRoundness } from "./comparisons"; import { generateFreeDrawShape } from "./renderElement"; -import { getArrowheadPoints, getDiamondPoints } from "./bounds"; +import { + getArrowheadPoints, + getCenterForBounds, + getDiamondPoints, + getElementBounds, +} from "./bounds"; import type { ExcalidrawElement, @@ -28,10 +42,11 @@ import type { ExcalidrawSelectionElement, ExcalidrawLinearElement, Arrowhead, + ExcalidrawFreeDrawElement, + ElementsMap, } from "./types"; import type { Drawable, Options } from "roughjs/bin/core"; -import type { RoughGenerator } from "roughjs/bin/generator"; import type { Point as RoughPoint } from "roughjs/bin/geometry"; const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth]; @@ -303,6 +318,172 @@ const getArrowheadShapes = ( } }; +export const generateLinearCollisionShape = ( + element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, +) => { + const generator = new RoughGenerator(); + const options: Options = { + seed: element.seed, + disableMultiStroke: true, + disableMultiStrokeFill: true, + roughness: 0, + preserveVertices: true, + }; + const center = getCenterForBounds( + getElementBounds(element, elementsMap, true), + ); + + switch (element.type) { + case "line": + case "arrow": { + // points array can be empty in the beginning, so it is important to add + // initial position to it + const points = element.points.length + ? element.points + : [pointFrom(0, 0)]; + + if (isElbowArrow(element)) { + return generator.path(generateElbowArrowShape(points, 16), options) + .sets[0].ops; + } else if (!element.roundness) { + return points.map((point, idx) => { + const p = pointRotateRads( + pointFrom(element.x + point[0], element.y + point[1]), + center, + element.angle, + ); + + return { + op: idx === 0 ? "move" : "lineTo", + data: pointFrom(p[0] - element.x, p[1] - element.y), + }; + }); + } + + return generator + .curve(points as unknown as RoughPoint[], options) + .sets[0].ops.slice(0, element.points.length) + .map((op, i) => { + if (i === 0) { + const p = pointRotateRads( + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + center, + element.angle, + ); + + return { + op: "move", + data: pointFrom(p[0] - element.x, p[1] - element.y), + }; + } + + return { + op: "bcurveTo", + data: [ + pointRotateRads( + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + center, + element.angle, + ), + pointRotateRads( + pointFrom( + element.x + op.data[2], + element.y + op.data[3], + ), + center, + element.angle, + ), + pointRotateRads( + pointFrom( + element.x + op.data[4], + element.y + op.data[5], + ), + center, + element.angle, + ), + ] + .map((p) => + pointFrom(p[0] - element.x, p[1] - element.y), + ) + .flat(), + }; + }); + } + case "freedraw": { + if (element.points.length < 2) { + return []; + } + + const simplifiedPoints = simplify( + element.points as Mutable, + 0.75, + ); + + return generator + .curve(simplifiedPoints as [number, number][], options) + .sets[0].ops.slice(0, element.points.length) + .map((op, i) => { + if (i === 0) { + const p = pointRotateRads( + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + center, + element.angle, + ); + + return { + op: "move", + data: pointFrom(p[0] - element.x, p[1] - element.y), + }; + } + + return { + op: "bcurveTo", + data: [ + pointRotateRads( + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + center, + element.angle, + ), + pointRotateRads( + pointFrom( + element.x + op.data[2], + element.y + op.data[3], + ), + center, + element.angle, + ), + pointRotateRads( + pointFrom( + element.x + op.data[4], + element.y + op.data[5], + ), + center, + element.angle, + ), + ] + .map((p) => + pointFrom(p[0] - element.x, p[1] - element.y), + ) + .flat(), + }; + }); + } + } +}; + /** * Generates the roughjs shape for given element. * diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 2ea05510b4..0a7a4a68ae 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -27,8 +27,6 @@ import { PRECISION, } from "@excalidraw/math"; -import { isPointOnShape } from "@excalidraw/utils/collision"; - import type { LocalPoint, Radians } from "@excalidraw/math"; import type { AppState } from "@excalidraw/excalidraw/types"; @@ -41,7 +39,7 @@ import { doBoundsIntersect, } from "./bounds"; import { intersectElementWithLineSegment } from "./collision"; -import { distanceToBindableElement } from "./distance"; +import { distanceToElement } from "./distance"; import { headingForPointFromElement, headingIsHorizontal, @@ -63,7 +61,7 @@ import { isTextElement, } from "./typeChecks"; -import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes"; +import { aabbForElement } from "./shapes"; import { updateElbowArrowPoints } from "./elbowArrow"; import type { Scene } from "./Scene"; @@ -109,7 +107,6 @@ export const isBindingEnabled = (appState: AppState): boolean => { export const FIXED_BINDING_DISTANCE = 5; export const BINDING_HIGHLIGHT_THICKNESS = 10; -export const BINDING_HIGHLIGHT_OFFSET = 4; const getNonDeletedElements = ( scene: Scene, @@ -131,6 +128,7 @@ export const bindOrUnbindLinearElement = ( endBindingElement: ExcalidrawBindableElement | null | "keep", scene: Scene, ): void => { + const elementsMap = scene.getNonDeletedElementsMap(); const boundToElementIds: Set = new Set(); const unboundFromElementIds: Set = new Set(); bindOrUnbindLinearElementEdge( @@ -141,6 +139,7 @@ export const bindOrUnbindLinearElement = ( boundToElementIds, unboundFromElementIds, scene, + elementsMap, ); bindOrUnbindLinearElementEdge( linearElement, @@ -150,6 +149,7 @@ export const bindOrUnbindLinearElement = ( boundToElementIds, unboundFromElementIds, scene, + elementsMap, ); const onlyUnbound = Array.from(unboundFromElementIds).filter( @@ -176,6 +176,7 @@ const bindOrUnbindLinearElementEdge = ( // Is mutated unboundFromElementIds: Set, scene: Scene, + elementsMap: ElementsMap, ): void => { // "keep" is for method chaining convenience, a "no-op", so just bail out if (bindableElement === "keep") { @@ -216,43 +217,29 @@ const bindOrUnbindLinearElementEdge = ( } }; -const getOriginalBindingIfStillCloseOfLinearElementEdge = ( - linearElement: NonDeleted, - edge: "start" | "end", - elementsMap: NonDeletedSceneElementsMap, - zoom?: AppState["zoom"], -): NonDeleted | null => { - const coors = getLinearElementEdgeCoors(linearElement, edge, elementsMap); - const elementId = - edge === "start" - ? linearElement.startBinding?.elementId - : linearElement.endBinding?.elementId; - if (elementId) { - const element = elementsMap.get(elementId); - if ( - isBindableElement(element) && - bindingBorderTest(element, coors, elementsMap, zoom) - ) { - return element; - } - } - - return null; -}; - const getOriginalBindingsIfStillCloseToArrowEnds = ( linearElement: NonDeleted, elementsMap: NonDeletedSceneElementsMap, zoom?: AppState["zoom"], ): (NonDeleted | null)[] => - ["start", "end"].map((edge) => - getOriginalBindingIfStillCloseOfLinearElementEdge( - linearElement, - edge as "start" | "end", - elementsMap, - zoom, - ), - ); + (["start", "end"] as const).map((edge) => { + const coors = getLinearElementEdgeCoors(linearElement, edge, elementsMap); + const elementId = + edge === "start" + ? linearElement.startBinding?.elementId + : linearElement.endBinding?.elementId; + if (elementId) { + const element = elementsMap.get(elementId); + if ( + isBindableElement(element) && + bindingBorderTest(element, coors, elementsMap, zoom) + ) { + return element; + } + } + + return null; + }); const getBindingStrategyForDraggingArrowEndpoints = ( selectedElement: NonDeleted, @@ -268,7 +255,7 @@ const getBindingStrategyForDraggingArrowEndpoints = ( const endDragged = draggingPoints.findIndex((i) => i === endIdx) > -1; const start = startDragged ? isBindingEnabled - ? getElligibleElementForBindingElement( + ? getEligibleElementForBindingElement( selectedElement, "start", elementsMap, @@ -279,7 +266,7 @@ const getBindingStrategyForDraggingArrowEndpoints = ( : "keep"; const end = endDragged ? isBindingEnabled - ? getElligibleElementForBindingElement( + ? getEligibleElementForBindingElement( selectedElement, "end", elementsMap, @@ -311,7 +298,7 @@ const getBindingStrategyForDraggingArrowOrJoints = ( ); const start = startIsClose ? isBindingEnabled - ? getElligibleElementForBindingElement( + ? getEligibleElementForBindingElement( selectedElement, "start", elementsMap, @@ -322,7 +309,7 @@ const getBindingStrategyForDraggingArrowOrJoints = ( : null; const end = endIsClose ? isBindingEnabled - ? getElligibleElementForBindingElement( + ? getEligibleElementForBindingElement( selectedElement, "end", elementsMap, @@ -441,22 +428,13 @@ export const maybeBindLinearElement = ( const normalizePointBinding = ( binding: { focus: number; gap: number }, hoveredElement: ExcalidrawBindableElement, -) => { - let gap = binding.gap; - const maxGap = maxBindingGap( - hoveredElement, - hoveredElement.width, - hoveredElement.height, - ); - - if (gap > maxGap) { - gap = BINDING_HIGHLIGHT_THICKNESS + BINDING_HIGHLIGHT_OFFSET; - } - return { - ...binding, - gap, - }; -}; +) => ({ + ...binding, + gap: Math.min( + binding.gap, + maxBindingGap(hoveredElement, hoveredElement.width, hoveredElement.height), + ), +}); export const bindLinearElement = ( linearElement: NonDeleted, @@ -488,6 +466,7 @@ export const bindLinearElement = ( linearElement, hoveredElement, startOrEnd, + scene.getNonDeletedElementsMap(), ), }; } @@ -703,8 +682,13 @@ const calculateFocusAndGap = ( ); return { - focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint), - gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)), + focus: determineFocusDistance( + hoveredElement, + elementsMap, + adjacentPoint, + edgePoint, + ), + gap: Math.max(1, distanceToElement(hoveredElement, elementsMap, edgePoint)), }; }; @@ -874,6 +858,7 @@ export const getHeadingForElbowArrowSnap = ( bindableElement: ExcalidrawBindableElement | undefined | null, aabb: Bounds | undefined | null, origPoint: GlobalPoint, + elementsMap: ElementsMap, zoom?: AppState["zoom"], ): Heading => { const otherPointHeading = vectorToHeading(vectorFromPoint(otherPoint, p)); @@ -882,11 +867,16 @@ export const getHeadingForElbowArrowSnap = ( return otherPointHeading; } - const distance = getDistanceForBinding(origPoint, bindableElement, zoom); + const distance = getDistanceForBinding( + origPoint, + bindableElement, + elementsMap, + zoom, + ); if (!distance) { return vectorToHeading( - vectorFromPoint(p, elementCenterPoint(bindableElement)), + vectorFromPoint(p, elementCenterPoint(bindableElement, elementsMap)), ); } @@ -896,9 +886,10 @@ export const getHeadingForElbowArrowSnap = ( const getDistanceForBinding = ( point: Readonly, bindableElement: ExcalidrawBindableElement, + elementsMap: ElementsMap, zoom?: AppState["zoom"], ) => { - const distance = distanceToBindableElement(bindableElement, point); + const distance = distanceToElement(bindableElement, elementsMap, point); const bindDistance = maxBindingGap( bindableElement, bindableElement.width, @@ -913,12 +904,13 @@ export const bindPointToSnapToElementOutline = ( arrow: ExcalidrawElbowArrowElement, bindableElement: ExcalidrawBindableElement, startOrEnd: "start" | "end", + elementsMap: ElementsMap, ): GlobalPoint => { if (isDevEnv() || isTestEnv()) { invariant(arrow.points.length > 1, "Arrow should have at least 2 points"); } - const aabb = aabbForElement(bindableElement); + const aabb = aabbForElement(bindableElement, elementsMap); const localP = arrow.points[startOrEnd === "start" ? 0 : arrow.points.length - 1]; const globalP = pointFrom( @@ -926,7 +918,7 @@ export const bindPointToSnapToElementOutline = ( arrow.y + localP[1], ); const edgePoint = isRectanguloidElement(bindableElement) - ? avoidRectangularCorner(bindableElement, globalP) + ? avoidRectangularCorner(bindableElement, elementsMap, globalP) : globalP; const elbowed = isElbowArrow(arrow); const center = getCenterForBounds(aabb); @@ -945,26 +937,31 @@ export const bindPointToSnapToElementOutline = ( const isHorizontal = headingIsHorizontal( headingForPointFromElement(bindableElement, aabb, globalP), ); + const snapPoint = snapToMid(bindableElement, elementsMap, edgePoint); const otherPoint = pointFrom( - isHorizontal ? center[0] : edgePoint[0], - !isHorizontal ? center[1] : edgePoint[1], + isHorizontal ? center[0] : snapPoint[0], + !isHorizontal ? center[1] : snapPoint[1], + ); + const intersector = lineSegment( + otherPoint, + pointFromVector( + vectorScale( + vectorNormalize(vectorFromPoint(snapPoint, otherPoint)), + Math.max(bindableElement.width, bindableElement.height) * 2, + ), + otherPoint, + ), ); intersection = intersectElementWithLineSegment( bindableElement, - lineSegment( - otherPoint, - pointFromVector( - vectorScale( - vectorNormalize(vectorFromPoint(edgePoint, otherPoint)), - Math.max(bindableElement.width, bindableElement.height) * 2, - ), - otherPoint, - ), - ), - )[0]; + elementsMap, + intersector, + FIXED_BINDING_DISTANCE, + ).sort(pointDistanceSq)[0]; } else { intersection = intersectElementWithLineSegment( bindableElement, + elementsMap, lineSegment( adjacentPoint, pointFromVector( @@ -991,31 +988,15 @@ export const bindPointToSnapToElementOutline = ( return edgePoint; } - if (elbowed) { - const scalar = - pointDistanceSq(edgePoint, center) - - pointDistanceSq(intersection, center) > - 0 - ? FIXED_BINDING_DISTANCE - : -FIXED_BINDING_DISTANCE; - - return pointFromVector( - vectorScale( - vectorNormalize(vectorFromPoint(edgePoint, intersection)), - scalar, - ), - intersection, - ); - } - - return edgePoint; + return elbowed ? intersection : edgePoint; }; export const avoidRectangularCorner = ( element: ExcalidrawBindableElement, + elementsMap: ElementsMap, p: GlobalPoint, ): GlobalPoint => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); const nonRotatedPoint = pointRotateRads(p, center, -element.angle as Radians); if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) { @@ -1108,35 +1089,34 @@ export const avoidRectangularCorner = ( export const snapToMid = ( element: ExcalidrawBindableElement, + elementsMap: ElementsMap, p: GlobalPoint, tolerance: number = 0.05, ): GlobalPoint => { const { x, y, width, height, angle } = element; - - const center = elementCenterPoint(element, -0.1, -0.1); - + const center = elementCenterPoint(element, elementsMap, -0.1, -0.1); const nonRotated = pointRotateRads(p, center, -angle as Radians); // snap-to-center point is adaptive to element size, but we don't want to go // above and below certain px distance - const verticalThrehsold = clamp(tolerance * height, 5, 80); - const horizontalThrehsold = clamp(tolerance * width, 5, 80); + const verticalThreshold = clamp(tolerance * height, 5, 80); + const horizontalThreshold = clamp(tolerance * width, 5, 80); if ( nonRotated[0] <= x + width / 2 && - nonRotated[1] > center[1] - verticalThrehsold && - nonRotated[1] < center[1] + verticalThrehsold + nonRotated[1] > center[1] - verticalThreshold && + nonRotated[1] < center[1] + verticalThreshold ) { // LEFT - return pointRotateRads( + return pointRotateRads( pointFrom(x - FIXED_BINDING_DISTANCE, center[1]), center, angle, ); } else if ( nonRotated[1] <= y + height / 2 && - nonRotated[0] > center[0] - horizontalThrehsold && - nonRotated[0] < center[0] + horizontalThrehsold + nonRotated[0] > center[0] - horizontalThreshold && + nonRotated[0] < center[0] + horizontalThreshold ) { // TOP return pointRotateRads( @@ -1146,8 +1126,8 @@ export const snapToMid = ( ); } else if ( nonRotated[0] >= x + width / 2 && - nonRotated[1] > center[1] - verticalThrehsold && - nonRotated[1] < center[1] + verticalThrehsold + nonRotated[1] > center[1] - verticalThreshold && + nonRotated[1] < center[1] + verticalThreshold ) { // RIGHT return pointRotateRads( @@ -1157,8 +1137,8 @@ export const snapToMid = ( ); } else if ( nonRotated[1] >= y + height / 2 && - nonRotated[0] > center[0] - horizontalThrehsold && - nonRotated[0] < center[0] + horizontalThrehsold + nonRotated[0] > center[0] - horizontalThreshold && + nonRotated[0] < center[0] + horizontalThreshold ) { // DOWN return pointRotateRads( @@ -1167,7 +1147,7 @@ export const snapToMid = ( angle, ); } else if (element.type === "diamond") { - const distance = FIXED_BINDING_DISTANCE - 1; + const distance = FIXED_BINDING_DISTANCE; const topLeft = pointFrom( x + width / 4 - distance, y + height / 4 - distance, @@ -1184,27 +1164,28 @@ export const snapToMid = ( x + (3 * width) / 4 + distance, y + (3 * height) / 4 + distance, ); + if ( pointDistance(topLeft, nonRotated) < - Math.max(horizontalThrehsold, verticalThrehsold) + Math.max(horizontalThreshold, verticalThreshold) ) { return pointRotateRads(topLeft, center, angle); } if ( pointDistance(topRight, nonRotated) < - Math.max(horizontalThrehsold, verticalThrehsold) + Math.max(horizontalThreshold, verticalThreshold) ) { return pointRotateRads(topRight, center, angle); } if ( pointDistance(bottomLeft, nonRotated) < - Math.max(horizontalThrehsold, verticalThrehsold) + Math.max(horizontalThreshold, verticalThreshold) ) { return pointRotateRads(bottomLeft, center, angle); } if ( pointDistance(bottomRight, nonRotated) < - Math.max(horizontalThrehsold, verticalThrehsold) + Math.max(horizontalThreshold, verticalThreshold) ) { return pointRotateRads(bottomRight, center, angle); } @@ -1239,8 +1220,9 @@ const updateBoundPoint = ( linearElement, bindableElement, startOrEnd === "startBinding" ? "start" : "end", + elementsMap, ).fixedPoint; - const globalMidPoint = elementCenterPoint(bindableElement); + const globalMidPoint = elementCenterPoint(bindableElement, elementsMap); const global = pointFrom( bindableElement.x + fixedPoint[0] * bindableElement.width, bindableElement.y + fixedPoint[1] * bindableElement.height, @@ -1266,6 +1248,7 @@ const updateBoundPoint = ( ); const focusPointAbsolute = determineFocusPoint( bindableElement, + elementsMap, binding.focus, adjacentPoint, ); @@ -1284,7 +1267,7 @@ const updateBoundPoint = ( elementsMap, ); - const center = elementCenterPoint(bindableElement); + const center = elementCenterPoint(bindableElement, elementsMap); const interceptorLength = pointDistance(adjacentPoint, edgePointAbsolute) + pointDistance(adjacentPoint, center) + @@ -1292,6 +1275,7 @@ const updateBoundPoint = ( const intersections = [ ...intersectElementWithLineSegment( bindableElement, + elementsMap, lineSegment( adjacentPoint, pointFromVector( @@ -1342,6 +1326,7 @@ export const calculateFixedPointForElbowArrowBinding = ( linearElement: NonDeleted, hoveredElement: ExcalidrawBindableElement, startOrEnd: "start" | "end", + elementsMap: ElementsMap, ): { fixedPoint: FixedPoint } => { const bounds = [ hoveredElement.x, @@ -1353,6 +1338,7 @@ export const calculateFixedPointForElbowArrowBinding = ( linearElement, hoveredElement, startOrEnd, + elementsMap, ); const globalMidPoint = pointFrom( bounds[0] + (bounds[2] - bounds[0]) / 2, @@ -1396,7 +1382,7 @@ const maybeCalculateNewGapWhenScaling = ( return { ...currentBinding, gap: newGap }; }; -const getElligibleElementForBindingElement = ( +const getEligibleElementForBindingElement = ( linearElement: NonDeleted, startOrEnd: "start" | "end", elementsMap: NonDeletedSceneElementsMap, @@ -1548,14 +1534,38 @@ export const bindingBorderTest = ( zoom?: AppState["zoom"], fullShape?: boolean, ): boolean => { + const p = pointFrom(x, y); const threshold = maxBindingGap(element, element.width, element.height, zoom); + const shouldTestInside = + // disable fullshape snapping for frame elements so we + // can bind to frame children + (fullShape || !isBindingFallthroughEnabled(element)) && + !isFrameLikeElement(element); - const shape = getElementShape(element, elementsMap); - return ( - isPointOnShape(pointFrom(x, y), shape, threshold) || - (fullShape === true && - pointInsideBounds(pointFrom(x, y), aabbForElement(element))) + // PERF: Run a cheap test to see if the binding element + // is even close to the element + const bounds = [ + x - threshold, + y - threshold, + x + threshold, + y + threshold, + ] as Bounds; + const elementBounds = getElementBounds(element, elementsMap); + if (!doBoundsIntersect(bounds, elementBounds)) { + return false; + } + + // Do the intersection test against the element since it's close enough + const intersections = intersectElementWithLineSegment( + element, + elementsMap, + lineSegment(elementCenterPoint(element, elementsMap), p), ); + const distance = distanceToElement(element, elementsMap, p); + + return shouldTestInside + ? intersections.length === 0 || distance <= threshold + : intersections.length > 0 && distance <= threshold; }; export const maxBindingGap = ( @@ -1575,7 +1585,7 @@ export const maxBindingGap = ( // bigger bindable boundary for bigger elements Math.min(0.25 * smallerDimension, 32), // keep in sync with the zoomed highlight - BINDING_HIGHLIGHT_THICKNESS / zoomValue + BINDING_HIGHLIGHT_OFFSET, + BINDING_HIGHLIGHT_THICKNESS / zoomValue + FIXED_BINDING_DISTANCE, ); }; @@ -1586,12 +1596,13 @@ export const maxBindingGap = ( // of the element. const determineFocusDistance = ( element: ExcalidrawBindableElement, + elementsMap: ElementsMap, // Point on the line, in absolute coordinates a: GlobalPoint, // Another point on the line, in absolute coordinates (closer to element) b: GlobalPoint, ): number => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); if (pointsEqual(a, b)) { return 0; @@ -1716,12 +1727,13 @@ const determineFocusDistance = ( const determineFocusPoint = ( element: ExcalidrawBindableElement, + elementsMap: ElementsMap, // The oriented, relative distance from the center of `element` of the // returned focusPoint focus: number, adjacentPoint: GlobalPoint, ): GlobalPoint => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); if (focus === 0) { return center; @@ -2144,6 +2156,7 @@ export class BindableElement { export const getGlobalFixedPointForBindableElement = ( fixedPointRatio: [number, number], element: ExcalidrawBindableElement, + elementsMap: ElementsMap, ): GlobalPoint => { const [fixedX, fixedY] = normalizeFixedPoint(fixedPointRatio); @@ -2152,7 +2165,7 @@ export const getGlobalFixedPointForBindableElement = ( element.x + element.width * fixedX, element.y + element.height * fixedY, ), - elementCenterPoint(element), + elementCenterPoint(element, elementsMap), element.angle, ); }; @@ -2176,6 +2189,7 @@ export const getGlobalFixedPoints = ( ? getGlobalFixedPointForBindableElement( arrow.startBinding.fixedPoint, startElement as ExcalidrawBindableElement, + elementsMap, ) : pointFrom( arrow.x + arrow.points[0][0], @@ -2186,6 +2200,7 @@ export const getGlobalFixedPoints = ( ? getGlobalFixedPointForBindableElement( arrow.endBinding.fixedPoint, endElement as ExcalidrawBindableElement, + elementsMap, ) : pointFrom( arrow.x + arrow.points[arrow.points.length - 1][0], diff --git a/packages/element/src/bounds.ts b/packages/element/src/bounds.ts index a5b91922b4..1bfb441585 100644 --- a/packages/element/src/bounds.ts +++ b/packages/element/src/bounds.ts @@ -102,9 +102,23 @@ export class ElementBounds { version: ExcalidrawElement["version"]; } >(); + private static nonRotatedBoundsCache = new WeakMap< + ExcalidrawElement, + { + bounds: Bounds; + version: ExcalidrawElement["version"]; + } + >(); - static getBounds(element: ExcalidrawElement, elementsMap: ElementsMap) { - const cachedBounds = ElementBounds.boundsCache.get(element); + static getBounds( + element: ExcalidrawElement, + elementsMap: ElementsMap, + nonRotated: boolean = false, + ) { + const cachedBounds = + nonRotated && element.angle !== 0 + ? ElementBounds.nonRotatedBoundsCache.get(element) + : ElementBounds.boundsCache.get(element); if ( cachedBounds?.version && @@ -115,6 +129,23 @@ export class ElementBounds { ) { return cachedBounds.bounds; } + + if (nonRotated && element.angle !== 0) { + const nonRotatedBounds = ElementBounds.calculateBounds( + { + ...element, + angle: 0 as Radians, + }, + elementsMap, + ); + ElementBounds.nonRotatedBoundsCache.set(element, { + version: element.version, + bounds: nonRotatedBounds, + }); + + return nonRotatedBounds; + } + const bounds = ElementBounds.calculateBounds(element, elementsMap); ElementBounds.boundsCache.set(element, { @@ -939,8 +970,9 @@ const getLinearElementRotatedBounds = ( export const getElementBounds = ( element: ExcalidrawElement, elementsMap: ElementsMap, + nonRotated: boolean = false, ): Bounds => { - return ElementBounds.getBounds(element, elementsMap); + return ElementBounds.getBounds(element, elementsMap, nonRotated); }; export const getCommonBounds = ( diff --git a/packages/element/src/collision.ts b/packages/element/src/collision.ts index 07b17bfde5..af81ff99ce 100644 --- a/packages/element/src/collision.ts +++ b/packages/element/src/collision.ts @@ -2,51 +2,60 @@ import { isTransparent, elementCenterPoint } from "@excalidraw/common"; import { curveIntersectLineSegment, isPointWithinBounds, - line, lineSegment, lineSegmentIntersectionPoints, pointFrom, + pointFromVector, pointRotateRads, pointsEqual, + vectorFromPoint, + vectorNormalize, + vectorScale, } from "@excalidraw/math"; import { ellipse, - ellipseLineIntersectionPoints, + ellipseSegmentInterceptPoints, } from "@excalidraw/math/ellipse"; -import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision"; -import { type GeometricShape, getPolygonShape } from "@excalidraw/utils/shape"; - -import type { - GlobalPoint, - LineSegment, - LocalPoint, - Polygon, - Radians, -} from "@excalidraw/math"; +import type { GlobalPoint, LineSegment, Radians } from "@excalidraw/math"; import type { FrameNameBounds } from "@excalidraw/excalidraw/types"; -import { getBoundTextShape, isPathALoop } from "./shapes"; -import { getElementBounds } from "./bounds"; +import { isPathALoop } from "./shapes"; +import { + type Bounds, + doBoundsIntersect, + getCenterForBounds, + getElementBounds, +} from "./bounds"; import { hasBoundTextElement, + isFreeDrawElement, isIframeLikeElement, isImageElement, + isLinearElement, isTextElement, } from "./typeChecks"; import { deconstructDiamondElement, + deconstructLinearOrFreeDrawElement, deconstructRectanguloidElement, } from "./utils"; +import { getBoundTextElement } from "./textElement"; + +import { LinearElementEditor } from "./linearElementEditor"; + +import { distanceToElement } from "./distance"; + import type { ElementsMap, ExcalidrawDiamondElement, ExcalidrawElement, ExcalidrawEllipseElement, - ExcalidrawRectangleElement, + ExcalidrawFreeDrawElement, + ExcalidrawLinearElement, ExcalidrawRectanguloidElement, } from "./types"; @@ -72,45 +81,64 @@ export const shouldTestInside = (element: ExcalidrawElement) => { return isDraggableFromInside || isImageElement(element); }; -export type HitTestArgs = { - x: number; - y: number; +export type HitTestArgs = { + point: GlobalPoint; element: ExcalidrawElement; - shape: GeometricShape; - threshold?: number; + threshold: number; + elementsMap: ElementsMap; frameNameBound?: FrameNameBounds | null; }; -export const hitElementItself = ({ - x, - y, +export const hitElementItself = ({ + point, element, - shape, - threshold = 10, + threshold, + elementsMap, frameNameBound = null, -}: HitTestArgs) => { - let hit = shouldTestInside(element) - ? // Since `inShape` tests STRICTLY againt the insides of a shape - // we would need `onShape` as well to include the "borders" - isPointInShape(pointFrom(x, y), shape) || - isPointOnShape(pointFrom(x, y), shape, threshold) - : isPointOnShape(pointFrom(x, y), shape, threshold); +}: HitTestArgs) => { + // Hit test against a frame's name + const hitFrameName = frameNameBound + ? isPointWithinBounds( + pointFrom(frameNameBound.x - threshold, frameNameBound.y - threshold), + point, + pointFrom( + frameNameBound.x + frameNameBound.width + threshold, + frameNameBound.y + frameNameBound.height + threshold, + ), + ) + : false; - // hit test against a frame's name - if (!hit && frameNameBound) { - hit = isPointInShape(pointFrom(x, y), { - type: "polygon", - data: getPolygonShape(frameNameBound as ExcalidrawRectangleElement) - .data as Polygon, - }); + // Hit test against the extended, rotated bounding box of the element first + const bounds = getElementBounds(element, elementsMap, true); + const hitBounds = isPointWithinBounds( + pointFrom(bounds[0] - threshold, bounds[1] - threshold), + pointRotateRads( + point, + getCenterForBounds(bounds), + -element.angle as Radians, + ), + pointFrom(bounds[2] + threshold, bounds[3] + threshold), + ); + + // PERF: Bail out early if the point is not even in the + // rotated bounding box or not hitting the frame name (saves 99%) + if (!hitBounds && !hitFrameName) { + return false; } - return hit; + // Do the precise (and relatively costly) hit test + const hitElement = shouldTestInside(element) + ? // Since `inShape` tests STRICTLY againt the insides of a shape + // we would need `onShape` as well to include the "borders" + isPointInElement(point, element, elementsMap) || + isPointOnElementOutline(point, element, elementsMap, threshold) + : isPointOnElementOutline(point, element, elementsMap, threshold); + + return hitElement || hitFrameName; }; export const hitElementBoundingBox = ( - x: number, - y: number, + point: GlobalPoint, element: ExcalidrawElement, elementsMap: ElementsMap, tolerance = 0, @@ -120,37 +148,42 @@ export const hitElementBoundingBox = ( y1 -= tolerance; x2 += tolerance; y2 += tolerance; - return isPointWithinBounds( - pointFrom(x1, y1), - pointFrom(x, y), - pointFrom(x2, y2), - ); + return isPointWithinBounds(pointFrom(x1, y1), point, pointFrom(x2, y2)); }; -export const hitElementBoundingBoxOnly = < - Point extends GlobalPoint | LocalPoint, ->( - hitArgs: HitTestArgs, +export const hitElementBoundingBoxOnly = ( + hitArgs: HitTestArgs, elementsMap: ElementsMap, -) => { - return ( - !hitElementItself(hitArgs) && - // bound text is considered part of the element (even if it's outside the bounding box) - !hitElementBoundText( - hitArgs.x, - hitArgs.y, - getBoundTextShape(hitArgs.element, elementsMap), - ) && - hitElementBoundingBox(hitArgs.x, hitArgs.y, hitArgs.element, elementsMap) - ); -}; +) => + !hitElementItself(hitArgs) && + // bound text is considered part of the element (even if it's outside the bounding box) + !hitElementBoundText(hitArgs.point, hitArgs.element, elementsMap) && + hitElementBoundingBox(hitArgs.point, hitArgs.element, elementsMap); -export const hitElementBoundText = ( - x: number, - y: number, - textShape: GeometricShape | null, +export const hitElementBoundText = ( + point: GlobalPoint, + element: ExcalidrawElement, + elementsMap: ElementsMap, ): boolean => { - return !!textShape && isPointInShape(pointFrom(x, y), textShape); + const boundTextElementCandidate = getBoundTextElement(element, elementsMap); + + if (!boundTextElementCandidate) { + return false; + } + const boundTextElement = isLinearElement(element) + ? { + ...boundTextElementCandidate, + // arrow's bound text accurate position is not stored in the element's property + // but rather calculated and returned from the following static method + ...LinearElementEditor.getBoundTextElementPosition( + element, + boundTextElementCandidate, + elementsMap, + ), + } + : boundTextElementCandidate; + + return isPointInElement(point, boundTextElement, elementsMap); }; /** @@ -163,9 +196,26 @@ export const hitElementBoundText = ( */ export const intersectElementWithLineSegment = ( element: ExcalidrawElement, + elementsMap: ElementsMap, line: LineSegment, offset: number = 0, + onlyFirst = false, ): GlobalPoint[] => { + // First check if the line intersects the element's axis-aligned bounding box + // as it is much faster than checking intersection against the element's shape + const intersectorBounds = [ + Math.min(line[0][0] - offset, line[1][0] - offset), + Math.min(line[0][1] - offset, line[1][1] - offset), + Math.max(line[0][0] + offset, line[1][0] + offset), + Math.max(line[0][1] + offset, line[1][1] + offset), + ] as Bounds; + const elementBounds = getElementBounds(element, elementsMap); + + if (!doBoundsIntersect(intersectorBounds, elementBounds)) { + return []; + } + + // Do the actual intersection test against the element's shape switch (element.type) { case "rectangle": case "image": @@ -173,23 +223,88 @@ export const intersectElementWithLineSegment = ( case "iframe": case "embeddable": case "frame": + case "selection": case "magicframe": - return intersectRectanguloidWithLineSegment(element, line, offset); + return intersectRectanguloidWithLineSegment( + element, + elementsMap, + line, + offset, + onlyFirst, + ); case "diamond": - return intersectDiamondWithLineSegment(element, line, offset); + return intersectDiamondWithLineSegment( + element, + elementsMap, + line, + offset, + onlyFirst, + ); case "ellipse": - return intersectEllipseWithLineSegment(element, line, offset); - default: - throw new Error(`Unimplemented element type '${element.type}'`); + return intersectEllipseWithLineSegment( + element, + elementsMap, + line, + offset, + ); + case "line": + case "freedraw": + case "arrow": + return intersectLinearOrFreeDrawWithLineSegment( + element, + elementsMap, + line, + onlyFirst, + ); } }; +const intersectLinearOrFreeDrawWithLineSegment = ( + element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, + segment: LineSegment, + onlyFirst = false, +): GlobalPoint[] => { + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); + const intersections = []; + + for (const l of lines) { + const intersection = lineSegmentIntersectionPoints(l, segment); + if (intersection) { + intersections.push(intersection); + + if (onlyFirst) { + return intersections; + } + } + } + + for (const c of curves) { + const hits = curveIntersectLineSegment(c, segment); + + if (hits.length > 0) { + intersections.push(...hits); + + if (onlyFirst) { + return intersections; + } + } + } + + return intersections; +}; + const intersectRectanguloidWithLineSegment = ( element: ExcalidrawRectanguloidElement, + elementsMap: ElementsMap, l: LineSegment, offset: number = 0, + onlyFirst = false, ): GlobalPoint[] => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); // To emulate a rotated rectangle we rotate the point in the inverse angle // instead. It's all the same distance-wise. const rotatedA = pointRotateRads( @@ -206,34 +321,37 @@ const intersectRectanguloidWithLineSegment = ( // Get the element's building components we can test against const [sides, corners] = deconstructRectanguloidElement(element, offset); - return ( - // Test intersection against the sides, keep only the valid - // intersection points and rotate them back to scene space - sides - .map((s) => - lineSegmentIntersectionPoints( - lineSegment(rotatedA, rotatedB), - s, - ), - ) - .filter((x) => x != null) - .map((j) => pointRotateRads(j!, center, element.angle)) - // Test intersection against the corners which are cubic bezier curves, - // keep only the valid intersection points and rotate them back to scene - // space - .concat( - corners - .flatMap((t) => - curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)), - ) - .filter((i) => i != null) - .map((j) => pointRotateRads(j, center, element.angle)), - ) - // Remove duplicates - .filter( - (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, - ) - ); + const intersections: GlobalPoint[] = []; + + for (const s of sides) { + const intersection = lineSegmentIntersectionPoints( + lineSegment(rotatedA, rotatedB), + s, + ); + if (intersection) { + intersections.push(pointRotateRads(intersection, center, element.angle)); + + if (onlyFirst) { + return intersections; + } + } + } + + for (const t of corners) { + const hits = curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)); + + if (hits.length > 0) { + for (const j of hits) { + intersections.push(pointRotateRads(j, center, element.angle)); + } + + if (onlyFirst) { + return intersections; + } + } + } + + return intersections; }; /** @@ -245,43 +363,51 @@ const intersectRectanguloidWithLineSegment = ( */ const intersectDiamondWithLineSegment = ( element: ExcalidrawDiamondElement, + elementsMap: ElementsMap, l: LineSegment, offset: number = 0, + onlyFirst = false, ): GlobalPoint[] => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); // Rotate the point to the inverse direction to simulate the rotated diamond // points. It's all the same distance-wise. const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians); const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians); - const [sides, curves] = deconstructDiamondElement(element, offset); + const [sides, corners] = deconstructDiamondElement(element, offset); - return ( - sides - .map((s) => - lineSegmentIntersectionPoints( - lineSegment(rotatedA, rotatedB), - s, - ), - ) - .filter((p): p is GlobalPoint => p != null) - // Rotate back intersection points - .map((p) => pointRotateRads(p!, center, element.angle)) - .concat( - curves - .flatMap((p) => - curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)), - ) - .filter((p) => p != null) - // Rotate back intersection points - .map((p) => pointRotateRads(p, center, element.angle)), - ) - // Remove duplicates - .filter( - (p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx, - ) - ); + const intersections: GlobalPoint[] = []; + + for (const s of sides) { + const intersection = lineSegmentIntersectionPoints( + lineSegment(rotatedA, rotatedB), + s, + ); + if (intersection) { + intersections.push(pointRotateRads(intersection, center, element.angle)); + + if (onlyFirst) { + return intersections; + } + } + } + + for (const t of corners) { + const hits = curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)); + + if (hits.length > 0) { + for (const j of hits) { + intersections.push(pointRotateRads(j, center, element.angle)); + } + + if (onlyFirst) { + return intersections; + } + } + } + + return intersections; }; /** @@ -293,16 +419,76 @@ const intersectDiamondWithLineSegment = ( */ const intersectEllipseWithLineSegment = ( element: ExcalidrawEllipseElement, + elementsMap: ElementsMap, l: LineSegment, offset: number = 0, ): GlobalPoint[] => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians); const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians); - return ellipseLineIntersectionPoints( + return ellipseSegmentInterceptPoints( ellipse(center, element.width / 2 + offset, element.height / 2 + offset), - line(rotatedA, rotatedB), + lineSegment(rotatedA, rotatedB), ).map((p) => pointRotateRads(p, center, element.angle)); }; + +/** + * Check if the given point is considered on the given shape's border + * + * @param point + * @param element + * @param tolerance + * @returns + */ +const isPointOnElementOutline = ( + point: GlobalPoint, + element: ExcalidrawElement, + elementsMap: ElementsMap, + tolerance = 1, +) => distanceToElement(element, elementsMap, point) <= tolerance; + +/** + * Check if the given point is considered inside the element's border + * + * @param point + * @param element + * @returns + */ +export const isPointInElement = ( + point: GlobalPoint, + element: ExcalidrawElement, + elementsMap: ElementsMap, +) => { + if ( + (isLinearElement(element) || isFreeDrawElement(element)) && + !isPathALoop(element.points) + ) { + // There isn't any "inside" for a non-looping path + return false; + } + + const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); + + if (!isPointWithinBounds(pointFrom(x1, y1), point, pointFrom(x2, y2))) { + return false; + } + + const center = pointFrom((x1 + x2) / 2, (y1 + y2) / 2); + const otherPoint = pointFromVector( + vectorScale( + vectorNormalize(vectorFromPoint(point, center, 0.1)), + Math.max(element.width, element.height) * 2, + ), + center, + ); + const intersector = lineSegment(point, otherPoint); + const intersections = intersectElementWithLineSegment( + element, + elementsMap, + intersector, + ).filter((p, pos, arr) => arr.findIndex((q) => pointsEqual(q, p)) === pos); + + return intersections.length % 2 === 1; +}; diff --git a/packages/element/src/cropElement.ts b/packages/element/src/cropElement.ts index 2bc930d668..c2a9f91fdb 100644 --- a/packages/element/src/cropElement.ts +++ b/packages/element/src/cropElement.ts @@ -34,6 +34,7 @@ export const MINIMAL_CROP_SIZE = 10; export const cropElement = ( element: ExcalidrawImageElement, + elementsMap: ElementsMap, transformHandle: TransformHandleType, naturalWidth: number, naturalHeight: number, @@ -63,7 +64,7 @@ export const cropElement = ( const rotatedPointer = pointRotateRads( pointFrom(pointerX, pointerY), - elementCenterPoint(element), + elementCenterPoint(element, elementsMap), -element.angle as Radians, ); diff --git a/packages/element/src/distance.ts b/packages/element/src/distance.ts index d261faf7df..55e9ed2bdb 100644 --- a/packages/element/src/distance.ts +++ b/packages/element/src/distance.ts @@ -12,21 +12,27 @@ import type { GlobalPoint, Radians } from "@excalidraw/math"; import { deconstructDiamondElement, + deconstructLinearOrFreeDrawElement, deconstructRectanguloidElement, } from "./utils"; import type { - ExcalidrawBindableElement, + ElementsMap, ExcalidrawDiamondElement, + ExcalidrawElement, ExcalidrawEllipseElement, + ExcalidrawFreeDrawElement, + ExcalidrawLinearElement, ExcalidrawRectanguloidElement, } from "./types"; -export const distanceToBindableElement = ( - element: ExcalidrawBindableElement, +export const distanceToElement = ( + element: ExcalidrawElement, + elementsMap: ElementsMap, p: GlobalPoint, ): number => { switch (element.type) { + case "selection": case "rectangle": case "image": case "text": @@ -34,11 +40,15 @@ export const distanceToBindableElement = ( case "embeddable": case "frame": case "magicframe": - return distanceToRectanguloidElement(element, p); + return distanceToRectanguloidElement(element, elementsMap, p); case "diamond": - return distanceToDiamondElement(element, p); + return distanceToDiamondElement(element, elementsMap, p); case "ellipse": - return distanceToEllipseElement(element, p); + return distanceToEllipseElement(element, elementsMap, p); + case "line": + case "arrow": + case "freedraw": + return distanceToLinearOrFreeDraElement(element, elementsMap, p); } }; @@ -52,9 +62,10 @@ export const distanceToBindableElement = ( */ const distanceToRectanguloidElement = ( element: ExcalidrawRectanguloidElement, + elementsMap: ElementsMap, p: GlobalPoint, ) => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); // To emulate a rotated rectangle we rotate the point in the inverse angle // instead. It's all the same distance-wise. const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians); @@ -80,9 +91,10 @@ const distanceToRectanguloidElement = ( */ const distanceToDiamondElement = ( element: ExcalidrawDiamondElement, + elementsMap: ElementsMap, p: GlobalPoint, ): number => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); // Rotate the point to the inverse direction to simulate the rotated diamond // points. It's all the same distance-wise. @@ -108,12 +120,28 @@ const distanceToDiamondElement = ( */ const distanceToEllipseElement = ( element: ExcalidrawEllipseElement, + elementsMap: ElementsMap, p: GlobalPoint, ): number => { - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); return ellipseDistanceFromPoint( // Instead of rotating the ellipse, rotate the point to the inverse angle pointRotateRads(p, center, -element.angle as Radians), ellipse(center, element.width / 2, element.height / 2), ); }; + +const distanceToLinearOrFreeDraElement = ( + element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, + p: GlobalPoint, +) => { + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); + return Math.min( + ...lines.map((s) => distanceToLineSegment(p, s)), + ...curves.map((a) => curvePointDistance(a, p)), + ); +}; diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 73c82a8980..fb60c10db7 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -29,10 +29,9 @@ import { FIXED_BINDING_DISTANCE, getHeadingForElbowArrowSnap, getGlobalFixedPointForBindableElement, - snapToMid, getHoveredElementForBinding, } from "./binding"; -import { distanceToBindableElement } from "./distance"; +import { distanceToElement } from "./distance"; import { compareHeading, flipHeading, @@ -898,50 +897,6 @@ export const updateElbowArrowPoints = ( return { points: updates.points ?? arrow.points }; } - // NOTE (mtolmacs): This is a temporary check to ensure that the incoming elbow - // arrow size is valid. This check will be removed once the issue is identified - if ( - arrow.x < -MAX_POS || - arrow.x > MAX_POS || - arrow.y < -MAX_POS || - arrow.y > MAX_POS || - arrow.x + (updates?.points?.[updates?.points?.length - 1]?.[0] ?? 0) < - -MAX_POS || - arrow.x + (updates?.points?.[updates?.points?.length - 1]?.[0] ?? 0) > - MAX_POS || - arrow.y + (updates?.points?.[updates?.points?.length - 1]?.[1] ?? 0) < - -MAX_POS || - arrow.y + (updates?.points?.[updates?.points?.length - 1]?.[1] ?? 0) > - MAX_POS || - arrow.x + (arrow?.points?.[arrow?.points?.length - 1]?.[0] ?? 0) < - -MAX_POS || - arrow.x + (arrow?.points?.[arrow?.points?.length - 1]?.[0] ?? 0) > - MAX_POS || - arrow.y + (arrow?.points?.[arrow?.points?.length - 1]?.[1] ?? 0) < - -MAX_POS || - arrow.y + (arrow?.points?.[arrow?.points?.length - 1]?.[1] ?? 0) > MAX_POS - ) { - console.error( - "Elbow arrow (or update) is outside reasonable bounds (> 1e6)", - { - arrow, - updates, - }, - ); - } - // @ts-ignore See above note - arrow.x = clamp(arrow.x, -MAX_POS, MAX_POS); - // @ts-ignore See above note - arrow.y = clamp(arrow.y, -MAX_POS, MAX_POS); - if (updates.points) { - updates.points = updates.points.map(([x, y]) => - pointFrom( - clamp(x, -MAX_POS, MAX_POS), - clamp(y, -MAX_POS, MAX_POS), - ), - ); - } - if (!import.meta.env.PROD) { invariant( !updates.points || updates.points.length >= 2, @@ -1273,6 +1228,7 @@ const getElbowArrowData = ( arrow.startBinding?.fixedPoint, origStartGlobalPoint, hoveredStartElement, + elementsMap, options?.isDragging, ); const endGlobalPoint = getGlobalPoint( @@ -1286,6 +1242,7 @@ const getElbowArrowData = ( arrow.endBinding?.fixedPoint, origEndGlobalPoint, hoveredEndElement, + elementsMap, options?.isDragging, ); const startHeading = getBindPointHeading( @@ -1293,12 +1250,14 @@ const getElbowArrowData = ( endGlobalPoint, hoveredStartElement, origStartGlobalPoint, + elementsMap, ); const endHeading = getBindPointHeading( endGlobalPoint, startGlobalPoint, hoveredEndElement, origEndGlobalPoint, + elementsMap, ); const startPointBounds = [ startGlobalPoint[0] - 2, @@ -1315,6 +1274,7 @@ const getElbowArrowData = ( const startElementBounds = hoveredStartElement ? aabbForElement( hoveredStartElement, + elementsMap, offsetFromHeading( startHeading, arrow.startArrowhead @@ -1327,6 +1287,7 @@ const getElbowArrowData = ( const endElementBounds = hoveredEndElement ? aabbForElement( hoveredEndElement, + elementsMap, offsetFromHeading( endHeading, arrow.endArrowhead @@ -1342,6 +1303,7 @@ const getElbowArrowData = ( hoveredEndElement ? aabbForElement( hoveredEndElement, + elementsMap, offsetFromHeading(endHeading, BASE_PADDING, BASE_PADDING), ) : endPointBounds, @@ -1351,6 +1313,7 @@ const getElbowArrowData = ( hoveredStartElement ? aabbForElement( hoveredStartElement, + elementsMap, offsetFromHeading(startHeading, BASE_PADDING, BASE_PADDING), ) : startPointBounds, @@ -1397,8 +1360,8 @@ const getElbowArrowData = ( BASE_PADDING, ), boundsOverlap, - hoveredStartElement && aabbForElement(hoveredStartElement), - hoveredEndElement && aabbForElement(hoveredEndElement), + hoveredStartElement && aabbForElement(hoveredStartElement, elementsMap), + hoveredEndElement && aabbForElement(hoveredEndElement, elementsMap), ); const startDonglePosition = getDonglePosition( dynamicAABBs[0], @@ -2229,34 +2192,35 @@ const getGlobalPoint = ( fixedPointRatio: [number, number] | undefined | null, initialPoint: GlobalPoint, element?: ExcalidrawBindableElement | null, + elementsMap?: ElementsMap, isDragging?: boolean, ): GlobalPoint => { if (isDragging) { - if (element) { - const snapPoint = bindPointToSnapToElementOutline( + if (element && elementsMap) { + return bindPointToSnapToElementOutline( arrow, element, startOrEnd, + elementsMap, ); - - return snapToMid(element, snapPoint); } return initialPoint; } - if (element) { + if (element && elementsMap) { const fixedGlobalPoint = getGlobalFixedPointForBindableElement( fixedPointRatio || [0, 0], element, + elementsMap, ); // NOTE: Resize scales the binding position point too, so we need to update it return Math.abs( - distanceToBindableElement(element, fixedGlobalPoint) - + distanceToElement(element, elementsMap, fixedGlobalPoint) - FIXED_BINDING_DISTANCE, ) > 0.01 - ? bindPointToSnapToElementOutline(arrow, element, startOrEnd) + ? bindPointToSnapToElementOutline(arrow, element, startOrEnd, elementsMap) : fixedGlobalPoint; } @@ -2268,6 +2232,7 @@ const getBindPointHeading = ( otherPoint: GlobalPoint, hoveredElement: ExcalidrawBindableElement | null | undefined, origPoint: GlobalPoint, + elementsMap: ElementsMap, ): Heading => getHeadingForElbowArrowSnap( p, @@ -2276,7 +2241,8 @@ const getBindPointHeading = ( hoveredElement && aabbForElement( hoveredElement, - Array(4).fill(distanceToBindableElement(hoveredElement, p)) as [ + elementsMap, + Array(4).fill(distanceToElement(hoveredElement, elementsMap, p)) as [ number, number, number, @@ -2284,6 +2250,7 @@ const getBindPointHeading = ( ], ), origPoint, + elementsMap, ); const getHoveredElement = ( diff --git a/packages/element/src/flowchart.ts b/packages/element/src/flowchart.ts index 5194e54259..9e5af4216e 100644 --- a/packages/element/src/flowchart.ts +++ b/packages/element/src/flowchart.ts @@ -95,10 +95,11 @@ const getNodeRelatives = ( type === "predecessors" ? el.points[el.points.length - 1] : [0, 0] ) as Readonly; - const heading = headingForPointFromElement(node, aabbForElement(node), [ - edgePoint[0] + el.x, - edgePoint[1] + el.y, - ] as Readonly); + const heading = headingForPointFromElement( + node, + aabbForElement(node, elementsMap), + [edgePoint[0] + el.x, edgePoint[1] + el.y] as Readonly, + ); acc.push({ relative, diff --git a/packages/element/src/shapes.ts b/packages/element/src/shapes.ts index 20041ce1bc..3abf7f5905 100644 --- a/packages/element/src/shapes.ts +++ b/packages/element/src/shapes.ts @@ -291,6 +291,7 @@ export const mapIntervalToBezierT =

( */ export const aabbForElement = ( element: Readonly, + elementsMap: ElementsMap, offset?: [number, number, number, number], ) => { const bbox = { @@ -302,7 +303,7 @@ export const aabbForElement = ( midY: element.y + element.height / 2, }; - const center = elementCenterPoint(element); + const center = elementCenterPoint(element, elementsMap); const [topLeftX, topLeftY] = pointRotateRads( pointFrom(bbox.minX, bbox.minY), center, diff --git a/packages/element/src/types.ts b/packages/element/src/types.ts index 23e4f99290..c2becd3e6c 100644 --- a/packages/element/src/types.ts +++ b/packages/element/src/types.ts @@ -195,7 +195,8 @@ export type ExcalidrawRectanguloidElement = | ExcalidrawFreeDrawElement | ExcalidrawIframeLikeElement | ExcalidrawFrameLikeElement - | ExcalidrawEmbeddableElement; + | ExcalidrawEmbeddableElement + | ExcalidrawSelectionElement; /** * ExcalidrawElement should be JSON serializable and (eventually) contain diff --git a/packages/element/src/utils.ts b/packages/element/src/utils.ts index 57b1e4346c..673b9ef1b0 100644 --- a/packages/element/src/utils.ts +++ b/packages/element/src/utils.ts @@ -1,28 +1,168 @@ import { curve, + curveCatmullRomCubicApproxPoints, + curveOffsetPoints, lineSegment, pointFrom, - pointFromVector, + pointFromArray, rectangle, - vectorFromPoint, - vectorNormalize, - vectorScale, type GlobalPoint, } from "@excalidraw/math"; -import { elementCenterPoint } from "@excalidraw/common"; - -import type { Curve, LineSegment } from "@excalidraw/math"; +import type { Curve, LineSegment, LocalPoint } from "@excalidraw/math"; import { getCornerRadius } from "./shapes"; import { getDiamondPoints } from "./bounds"; +import { generateLinearCollisionShape } from "./Shape"; + import type { + ElementsMap, ExcalidrawDiamondElement, + ExcalidrawElement, + ExcalidrawFreeDrawElement, + ExcalidrawLinearElement, ExcalidrawRectanguloidElement, } from "./types"; +type ElementShape = [LineSegment[], Curve[]]; + +const ElementShapesCache = new WeakMap< + ExcalidrawElement, + { version: ExcalidrawElement["version"]; shapes: Map } +>(); + +const getElementShapesCacheEntry = ( + element: T, + offset: number, +): ElementShape | undefined => { + const record = ElementShapesCache.get(element); + + if (!record) { + return undefined; + } + + const { version, shapes } = record; + + if (version !== element.version) { + ElementShapesCache.delete(element); + return undefined; + } + + return shapes.get(offset); +}; + +const setElementShapesCacheEntry = ( + element: T, + shape: ElementShape, + offset: number, +) => { + const record = ElementShapesCache.get(element); + + if (!record) { + ElementShapesCache.set(element, { + version: element.version, + shapes: new Map([[offset, shape]]), + }); + + return; + } + + const { version, shapes } = record; + + if (version !== element.version) { + ElementShapesCache.set(element, { + version: element.version, + shapes: new Map([[offset, shape]]), + }); + + return; + } + + shapes.set(offset, shape); +}; + +export function deconstructLinearOrFreeDrawElement( + element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, +): [LineSegment[], Curve[]] { + const cachedShape = getElementShapesCacheEntry(element, 0); + + if (cachedShape) { + return cachedShape; + } + + const ops = generateLinearCollisionShape(element, elementsMap) as { + op: string; + data: number[]; + }[]; + const lines = []; + const curves = []; + + for (let idx = 0; idx < ops.length; idx += 1) { + const op = ops[idx]; + const prevPoint = + ops[idx - 1] && pointFromArray(ops[idx - 1].data.slice(-2)); + switch (op.op) { + case "move": + continue; + case "lineTo": + if (!prevPoint) { + throw new Error("prevPoint is undefined"); + } + + lines.push( + lineSegment( + pointFrom( + element.x + prevPoint[0], + element.y + prevPoint[1], + ), + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + ), + ); + continue; + case "bcurveTo": + if (!prevPoint) { + throw new Error("prevPoint is undefined"); + } + + curves.push( + curve( + pointFrom( + element.x + prevPoint[0], + element.y + prevPoint[1], + ), + pointFrom( + element.x + op.data[0], + element.y + op.data[1], + ), + pointFrom( + element.x + op.data[2], + element.y + op.data[3], + ), + pointFrom( + element.x + op.data[4], + element.y + op.data[5], + ), + ), + ); + continue; + default: { + console.error("Unknown op type", op.op); + } + } + } + + const shape = [lines, curves] as ElementShape; + setElementShapesCacheEntry(element, shape, 0); + + return shape; +} + /** * Get the building components of a rectanguloid element in the form of * line segments and curves. @@ -35,175 +175,132 @@ export function deconstructRectanguloidElement( element: ExcalidrawRectanguloidElement, offset: number = 0, ): [LineSegment[], Curve[]] { - const roundness = getCornerRadius( + const cachedShape = getElementShapesCacheEntry(element, offset); + + if (cachedShape) { + return cachedShape; + } + + let radius = getCornerRadius( Math.min(element.width, element.height), element, ); - if (roundness <= 0) { - const r = rectangle( - pointFrom(element.x - offset, element.y - offset), - pointFrom( - element.x + element.width + offset, - element.y + element.height + offset, - ), - ); - - const top = lineSegment( - pointFrom(r[0][0] + roundness, r[0][1]), - pointFrom(r[1][0] - roundness, r[0][1]), - ); - const right = lineSegment( - pointFrom(r[1][0], r[0][1] + roundness), - pointFrom(r[1][0], r[1][1] - roundness), - ); - const bottom = lineSegment( - pointFrom(r[0][0] + roundness, r[1][1]), - pointFrom(r[1][0] - roundness, r[1][1]), - ); - const left = lineSegment( - pointFrom(r[0][0], r[1][1] - roundness), - pointFrom(r[0][0], r[0][1] + roundness), - ); - const sides = [top, right, bottom, left]; - - return [sides, []]; + if (radius === 0) { + radius = 0.01; } - const center = elementCenterPoint(element); - const r = rectangle( pointFrom(element.x, element.y), pointFrom(element.x + element.width, element.y + element.height), ); const top = lineSegment( - pointFrom(r[0][0] + roundness, r[0][1]), - pointFrom(r[1][0] - roundness, r[0][1]), + pointFrom(r[0][0] + radius, r[0][1]), + pointFrom(r[1][0] - radius, r[0][1]), ); const right = lineSegment( - pointFrom(r[1][0], r[0][1] + roundness), - pointFrom(r[1][0], r[1][1] - roundness), + pointFrom(r[1][0], r[0][1] + radius), + pointFrom(r[1][0], r[1][1] - radius), ); const bottom = lineSegment( - pointFrom(r[0][0] + roundness, r[1][1]), - pointFrom(r[1][0] - roundness, r[1][1]), + pointFrom(r[0][0] + radius, r[1][1]), + pointFrom(r[1][0] - radius, r[1][1]), ); const left = lineSegment( - pointFrom(r[0][0], r[1][1] - roundness), - pointFrom(r[0][0], r[0][1] + roundness), + pointFrom(r[0][0], r[1][1] - radius), + pointFrom(r[0][0], r[0][1] + radius), ); - const offsets = [ - vectorScale( - vectorNormalize( - vectorFromPoint(pointFrom(r[0][0] - offset, r[0][1] - offset), center), - ), - offset, - ), // TOP LEFT - vectorScale( - vectorNormalize( - vectorFromPoint(pointFrom(r[1][0] + offset, r[0][1] - offset), center), - ), - offset, - ), //TOP RIGHT - vectorScale( - vectorNormalize( - vectorFromPoint(pointFrom(r[1][0] + offset, r[1][1] + offset), center), - ), - offset, - ), // BOTTOM RIGHT - vectorScale( - vectorNormalize( - vectorFromPoint(pointFrom(r[0][0] - offset, r[1][1] + offset), center), - ), - offset, - ), // BOTTOM LEFT - ]; - - const corners = [ + const baseCorners = [ curve( - pointFromVector(offsets[0], left[1]), - pointFromVector( - offsets[0], - pointFrom( - left[1][0] + (2 / 3) * (r[0][0] - left[1][0]), - left[1][1] + (2 / 3) * (r[0][1] - left[1][1]), - ), + left[1], + pointFrom( + left[1][0] + (2 / 3) * (r[0][0] - left[1][0]), + left[1][1] + (2 / 3) * (r[0][1] - left[1][1]), ), - pointFromVector( - offsets[0], - pointFrom( - top[0][0] + (2 / 3) * (r[0][0] - top[0][0]), - top[0][1] + (2 / 3) * (r[0][1] - top[0][1]), - ), + pointFrom( + top[0][0] + (2 / 3) * (r[0][0] - top[0][0]), + top[0][1] + (2 / 3) * (r[0][1] - top[0][1]), ), - pointFromVector(offsets[0], top[0]), + top[0], ), // TOP LEFT curve( - pointFromVector(offsets[1], top[1]), - pointFromVector( - offsets[1], - pointFrom( - top[1][0] + (2 / 3) * (r[1][0] - top[1][0]), - top[1][1] + (2 / 3) * (r[0][1] - top[1][1]), - ), + top[1], + pointFrom( + top[1][0] + (2 / 3) * (r[1][0] - top[1][0]), + top[1][1] + (2 / 3) * (r[0][1] - top[1][1]), ), - pointFromVector( - offsets[1], - pointFrom( - right[0][0] + (2 / 3) * (r[1][0] - right[0][0]), - right[0][1] + (2 / 3) * (r[0][1] - right[0][1]), - ), + pointFrom( + right[0][0] + (2 / 3) * (r[1][0] - right[0][0]), + right[0][1] + (2 / 3) * (r[0][1] - right[0][1]), ), - pointFromVector(offsets[1], right[0]), + right[0], ), // TOP RIGHT curve( - pointFromVector(offsets[2], right[1]), - pointFromVector( - offsets[2], - pointFrom( - right[1][0] + (2 / 3) * (r[1][0] - right[1][0]), - right[1][1] + (2 / 3) * (r[1][1] - right[1][1]), - ), + right[1], + pointFrom( + right[1][0] + (2 / 3) * (r[1][0] - right[1][0]), + right[1][1] + (2 / 3) * (r[1][1] - right[1][1]), ), - pointFromVector( - offsets[2], - pointFrom( - bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]), - bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]), - ), + pointFrom( + bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]), + bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]), ), - pointFromVector(offsets[2], bottom[1]), + bottom[1], ), // BOTTOM RIGHT curve( - pointFromVector(offsets[3], bottom[0]), - pointFromVector( - offsets[3], - pointFrom( - bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]), - bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]), - ), + bottom[0], + pointFrom( + bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]), + bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]), ), - pointFromVector( - offsets[3], - pointFrom( - left[0][0] + (2 / 3) * (r[0][0] - left[0][0]), - left[0][1] + (2 / 3) * (r[1][1] - left[0][1]), - ), + pointFrom( + left[0][0] + (2 / 3) * (r[0][0] - left[0][0]), + left[0][1] + (2 / 3) * (r[1][1] - left[0][1]), ), - pointFromVector(offsets[3], left[0]), + left[0], ), // BOTTOM LEFT ]; - const sides = [ - lineSegment(corners[0][3], corners[1][0]), - lineSegment(corners[1][3], corners[2][0]), - lineSegment(corners[2][3], corners[3][0]), - lineSegment(corners[3][3], corners[0][0]), - ]; + const corners = + offset > 0 + ? baseCorners.map( + (corner) => + curveCatmullRomCubicApproxPoints( + curveOffsetPoints(corner, offset), + )!, + ) + : [ + [baseCorners[0]], + [baseCorners[1]], + [baseCorners[2]], + [baseCorners[3]], + ]; - return [sides, corners]; + const sides = [ + lineSegment( + corners[0][corners[0].length - 1][3], + corners[1][0][0], + ), + lineSegment( + corners[1][corners[1].length - 1][3], + corners[2][0][0], + ), + lineSegment( + corners[2][corners[2].length - 1][3], + corners[3][0][0], + ), + lineSegment( + corners[3][corners[3].length - 1][3], + corners[0][0][0], + ), + ]; + const shape = [sides, corners.flat()] as ElementShape; + + setElementShapesCacheEntry(element, shape, offset); + + return shape; } /** @@ -218,42 +315,20 @@ export function deconstructDiamondElement( element: ExcalidrawDiamondElement, offset: number = 0, ): [LineSegment[], Curve[]] { - const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = - getDiamondPoints(element); - const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element); - const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element); + const cachedShape = getElementShapesCacheEntry(element, offset); - if (element.roundness?.type == null) { - const [top, right, bottom, left]: GlobalPoint[] = [ - pointFrom(element.x + topX, element.y + topY - offset), - pointFrom(element.x + rightX + offset, element.y + rightY), - pointFrom(element.x + bottomX, element.y + bottomY + offset), - pointFrom(element.x + leftX - offset, element.y + leftY), - ]; - - // Create the line segment parts of the diamond - // NOTE: Horizontal and vertical seems to be flipped here - const topRight = lineSegment( - pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius), - pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius), - ); - const bottomRight = lineSegment( - pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius), - pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius), - ); - const bottomLeft = lineSegment( - pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius), - pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius), - ); - const topLeft = lineSegment( - pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius), - pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius), - ); - - return [[topRight, bottomRight, bottomLeft, topLeft], []]; + if (cachedShape) { + return cachedShape; } - const center = elementCenterPoint(element); + const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] = + getDiamondPoints(element); + const verticalRadius = element.roundness + ? getCornerRadius(Math.abs(topX - leftX), element) + : (topX - leftX) * 0.01; + const horizontalRadius = element.roundness + ? getCornerRadius(Math.abs(rightY - topY), element) + : (rightY - topY) * 0.01; const [top, right, bottom, left]: GlobalPoint[] = [ pointFrom(element.x + topX, element.y + topY), @@ -262,94 +337,94 @@ export function deconstructDiamondElement( pointFrom(element.x + leftX, element.y + leftY), ]; - const offsets = [ - vectorScale(vectorNormalize(vectorFromPoint(right, center)), offset), // RIGHT - vectorScale(vectorNormalize(vectorFromPoint(bottom, center)), offset), // BOTTOM - vectorScale(vectorNormalize(vectorFromPoint(left, center)), offset), // LEFT - vectorScale(vectorNormalize(vectorFromPoint(top, center)), offset), // TOP - ]; - - const corners = [ + const baseCorners = [ curve( - pointFromVector( - offsets[0], - pointFrom( - right[0] - verticalRadius, - right[1] - horizontalRadius, - ), + pointFrom( + right[0] - verticalRadius, + right[1] - horizontalRadius, ), - pointFromVector(offsets[0], right), - pointFromVector(offsets[0], right), - pointFromVector( - offsets[0], - pointFrom( - right[0] - verticalRadius, - right[1] + horizontalRadius, - ), + right, + right, + pointFrom( + right[0] - verticalRadius, + right[1] + horizontalRadius, ), ), // RIGHT curve( - pointFromVector( - offsets[1], - pointFrom( - bottom[0] + verticalRadius, - bottom[1] - horizontalRadius, - ), + pointFrom( + bottom[0] + verticalRadius, + bottom[1] - horizontalRadius, ), - pointFromVector(offsets[1], bottom), - pointFromVector(offsets[1], bottom), - pointFromVector( - offsets[1], - pointFrom( - bottom[0] - verticalRadius, - bottom[1] - horizontalRadius, - ), + bottom, + bottom, + pointFrom( + bottom[0] - verticalRadius, + bottom[1] - horizontalRadius, ), ), // BOTTOM curve( - pointFromVector( - offsets[2], - pointFrom( - left[0] + verticalRadius, - left[1] + horizontalRadius, - ), + pointFrom( + left[0] + verticalRadius, + left[1] + horizontalRadius, ), - pointFromVector(offsets[2], left), - pointFromVector(offsets[2], left), - pointFromVector( - offsets[2], - pointFrom( - left[0] + verticalRadius, - left[1] - horizontalRadius, - ), + left, + left, + pointFrom( + left[0] + verticalRadius, + left[1] - horizontalRadius, ), ), // LEFT curve( - pointFromVector( - offsets[3], - pointFrom( - top[0] - verticalRadius, - top[1] + horizontalRadius, - ), + pointFrom( + top[0] - verticalRadius, + top[1] + horizontalRadius, ), - pointFromVector(offsets[3], top), - pointFromVector(offsets[3], top), - pointFromVector( - offsets[3], - pointFrom( - top[0] + verticalRadius, - top[1] + horizontalRadius, - ), + top, + top, + pointFrom( + top[0] + verticalRadius, + top[1] + horizontalRadius, ), ), // TOP ]; + const corners = + offset > 0 + ? baseCorners.map( + (corner) => + curveCatmullRomCubicApproxPoints( + curveOffsetPoints(corner, offset), + )!, + ) + : [ + [baseCorners[0]], + [baseCorners[1]], + [baseCorners[2]], + [baseCorners[3]], + ]; + const sides = [ - lineSegment(corners[0][3], corners[1][0]), - lineSegment(corners[1][3], corners[2][0]), - lineSegment(corners[2][3], corners[3][0]), - lineSegment(corners[3][3], corners[0][0]), + lineSegment( + corners[0][corners[0].length - 1][3], + corners[1][0][0], + ), + lineSegment( + corners[1][corners[1].length - 1][3], + corners[2][0][0], + ), + lineSegment( + corners[2][corners[2].length - 1][3], + corners[3][0][0], + ), + lineSegment( + corners[3][corners[3].length - 1][3], + corners[0][0][0], + ), ]; - return [sides, corners]; + const shape = [sides, corners.flat()] as ElementShape; + + setElementShapesCacheEntry(element, shape, offset); + + return shape; } diff --git a/packages/element/tests/align.test.tsx b/packages/element/tests/align.test.tsx index 2dcafc65b8..afffb72cb4 100644 --- a/packages/element/tests/align.test.tsx +++ b/packages/element/tests/align.test.tsx @@ -35,6 +35,7 @@ const createAndSelectTwoRectangles = () => { // The second rectangle is already reselected because it was the last element created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); }; @@ -52,6 +53,7 @@ const createAndSelectTwoRectanglesWithDifferentSizes = () => { // The second rectangle is already reselected because it was the last element created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); }; @@ -202,6 +204,7 @@ describe("aligning", () => { // The second rectangle is already reselected because it was the last element created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); @@ -215,6 +218,7 @@ describe("aligning", () => { // Add the created group to the current selection mouse.restorePosition(0, 0); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); }; @@ -316,6 +320,7 @@ describe("aligning", () => { // The second rectangle is already selected because it was the last element created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); @@ -330,7 +335,7 @@ describe("aligning", () => { mouse.down(); mouse.up(100, 100); - mouse.restorePosition(200, 200); + mouse.restorePosition(210, 200); Keyboard.withModifierKeys({ shift: true }, () => { mouse.click(); }); @@ -341,6 +346,7 @@ describe("aligning", () => { // The second group is already selected because it was the last group created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); }; @@ -454,6 +460,7 @@ describe("aligning", () => { // The second rectangle is already reselected because it was the last element created mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); @@ -466,7 +473,7 @@ describe("aligning", () => { mouse.up(100, 100); // Add group to current selection - mouse.restorePosition(0, 0); + mouse.restorePosition(10, 0); Keyboard.withModifierKeys({ shift: true }, () => { mouse.click(); }); @@ -482,6 +489,7 @@ describe("aligning", () => { // Select the nested group, the rectangle is already selected mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); }; diff --git a/packages/element/tests/binding.test.tsx b/packages/element/tests/binding.test.tsx index f57d7793ae..bfc34af28c 100644 --- a/packages/element/tests/binding.test.tsx +++ b/packages/element/tests/binding.test.tsx @@ -172,12 +172,12 @@ describe("element binding", () => { const arrow = UI.createElement("arrow", { x: 0, y: 0, - size: 50, + size: 49, }); expect(arrow.endBinding).toBe(null); - mouse.downAt(50, 50); + mouse.downAt(49, 49); mouse.moveTo(51, 0); mouse.up(0, 0); diff --git a/packages/element/tests/collision.test.tsx b/packages/element/tests/collision.test.tsx new file mode 100644 index 0000000000..bcbf114f5e --- /dev/null +++ b/packages/element/tests/collision.test.tsx @@ -0,0 +1,38 @@ +import { type GlobalPoint, type LocalPoint, pointFrom } from "@excalidraw/math"; +import { Excalidraw } from "@excalidraw/excalidraw"; +import { UI } from "@excalidraw/excalidraw/tests/helpers/ui"; +import "@excalidraw/utils/test-utils"; +import { render } from "@excalidraw/excalidraw/tests/test-utils"; + +import { hitElementItself } from "../src/collision"; + +describe("check rotated elements can be hit:", () => { + beforeEach(async () => { + localStorage.clear(); + await render(); + }); + + it("arrow", () => { + UI.createElement("arrow", { + x: 0, + y: 0, + width: 124, + height: 302, + angle: 1.8700426423973724, + points: [ + [0, 0], + [120, -198], + [-4, -302], + ] as LocalPoint[], + }); + //const p = [120, -211]; + //const p = [0, 13]; + const hit = hitElementItself({ + point: pointFrom(87, -68), + element: window.h.elements[0], + threshold: 10, + elementsMap: window.h.scene.getNonDeletedElementsMap(), + }); + expect(hit).toBe(true); + }); +}); diff --git a/packages/element/tests/linearElementEditor.test.tsx b/packages/element/tests/linearElementEditor.test.tsx index b8e49c6339..35b1447801 100644 --- a/packages/element/tests/linearElementEditor.test.tsx +++ b/packages/element/tests/linearElementEditor.test.tsx @@ -1262,7 +1262,7 @@ describe("Test Linear Elements", () => { mouse.downAt(rect.x, rect.y); mouse.moveTo(200, 0); mouse.upAt(200, 0); - expect(arrow.width).toBeCloseTo(204, 0); + expect(arrow.width).toBeCloseTo(200, 0); expect(rect.x).toBe(200); expect(rect.y).toBe(0); expect(handleBindTextResizeSpy).toHaveBeenCalledWith( diff --git a/packages/element/tests/resize.test.tsx b/packages/element/tests/resize.test.tsx index 98fbf2a9a0..1d0b6ac0b2 100644 --- a/packages/element/tests/resize.test.tsx +++ b/packages/element/tests/resize.test.tsx @@ -510,12 +510,12 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); UI.resize(rectangle, "se", [-200, -150]); - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); }); @@ -538,11 +538,11 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); UI.resize([rectangle, arrow], "nw", [300, 350]); - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(0); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.25); }); }); @@ -819,7 +819,7 @@ describe("image element", () => { UI.resize(image, "ne", [40, 0]); - expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(31, 0); + expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30, 0); const imageWidth = image.width; const scale = 20 / image.height; @@ -1033,7 +1033,7 @@ describe("multiple selection", () => { expect(leftBoundArrow.x).toBeCloseTo(-110); expect(leftBoundArrow.y).toBeCloseTo(50); - expect(leftBoundArrow.width).toBeCloseTo(143, 0); + expect(leftBoundArrow.width).toBeCloseTo(140, 0); expect(leftBoundArrow.height).toBeCloseTo(7, 0); expect(leftBoundArrow.angle).toEqual(0); expect(leftBoundArrow.startBinding).toBeNull(); diff --git a/packages/element/tests/sizeHelpers.test.ts b/packages/element/tests/sizeHelpers.test.ts index 168a9a2adf..4e589fee7f 100644 --- a/packages/element/tests/sizeHelpers.test.ts +++ b/packages/element/tests/sizeHelpers.test.ts @@ -1,7 +1,5 @@ import { vi } from "vitest"; -import * as constants from "@excalidraw/common"; - import { getPerfectElementSize } from "../src/sizeHelpers"; const EPSILON_DIGITS = 3; @@ -57,13 +55,4 @@ describe("getPerfectElementSize", () => { expect(width).toBeCloseTo(0, EPSILON_DIGITS); expect(height).toBeCloseTo(0, EPSILON_DIGITS); }); - - describe("should respond to SHIFT_LOCKING_ANGLE constant", () => { - it("should have only 2 locking angles per section if SHIFT_LOCKING_ANGLE = 45 deg (Math.PI/4)", () => { - (constants as any).SHIFT_LOCKING_ANGLE = Math.PI / 4; - const { height, width } = getPerfectElementSize("arrow", 120, 185); - expect(width).toBeCloseTo(120, EPSILON_DIGITS); - expect(height).toBeCloseTo(120, EPSILON_DIGITS); - }); - }); }); diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 30c9e74343..62c6bae91b 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -18,7 +18,6 @@ import { arrayToMap, getFontFamilyString, getShortcutKey, - tupleToCoors, getLineHeight, isTransparent, reduceToCommonValue, @@ -28,9 +27,7 @@ import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element"; import { bindLinearElement, - bindPointToSnapToElementOutline, calculateFixedPointForElbowArrowBinding, - getHoveredElementForBinding, updateBoundElements, } from "@excalidraw/element"; @@ -1661,63 +1658,16 @@ export const actionChangeArrowType = register({ -1, elementsMap, ); - const startHoveredElement = - !newElement.startBinding && - getHoveredElementForBinding( - tupleToCoors(startGlobalPoint), - elements, - elementsMap, - appState.zoom, - false, - true, - ); - const endHoveredElement = - !newElement.endBinding && - getHoveredElementForBinding( - tupleToCoors(endGlobalPoint), - elements, - elementsMap, - appState.zoom, - false, - true, - ); - const startElement = startHoveredElement - ? startHoveredElement - : newElement.startBinding && - (elementsMap.get( - newElement.startBinding.elementId, - ) as ExcalidrawBindableElement); - const endElement = endHoveredElement - ? endHoveredElement - : newElement.endBinding && - (elementsMap.get( - newElement.endBinding.elementId, - ) as ExcalidrawBindableElement); - - const finalStartPoint = startHoveredElement - ? bindPointToSnapToElementOutline( - newElement, - startHoveredElement, - "start", - ) - : startGlobalPoint; - const finalEndPoint = endHoveredElement - ? bindPointToSnapToElementOutline( - newElement, - endHoveredElement, - "end", - ) - : endGlobalPoint; - - startHoveredElement && - bindLinearElement( - newElement, - startHoveredElement, - "start", - app.scene, - ); - endHoveredElement && - bindLinearElement(newElement, endHoveredElement, "end", app.scene); + const startElement = + newElement.startBinding && + (elementsMap.get( + newElement.startBinding.elementId, + ) as ExcalidrawBindableElement); + const endElement = + newElement.endBinding && + (elementsMap.get( + newElement.endBinding.elementId, + ) as ExcalidrawBindableElement); const startBinding = startElement && newElement.startBinding @@ -1728,6 +1678,7 @@ export const actionChangeArrowType = register({ newElement, startElement, "start", + elementsMap, ), } : null; @@ -1740,6 +1691,7 @@ export const actionChangeArrowType = register({ newElement, endElement, "end", + elementsMap, ), } : null; @@ -1749,7 +1701,7 @@ export const actionChangeArrowType = register({ startBinding, endBinding, ...updateElbowArrowPoints(newElement, elementsMap, { - points: [finalStartPoint, finalEndPoint].map( + points: [startGlobalPoint, endGlobalPoint].map( (p): LocalPoint => pointFrom(p[0] - newElement.x, p[1] - newElement.y), ), diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index b45a6f7d30..75e99768c6 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -10,6 +10,7 @@ import { STATS_PANELS, THEME, DEFAULT_GRID_STEP, + isTestEnv, } from "@excalidraw/common"; import type { AppState, NormalizedZoomValue } from "./types"; @@ -36,7 +37,7 @@ export const getDefaultAppState = (): Omit< currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness, currentItemStartArrowhead: null, currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor, - currentItemRoundness: "round", + currentItemRoundness: isTestEnv() ? "sharp" : "round", currentItemArrowType: ARROW_TYPE.round, currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle, currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 15b5e5b6fd..b1ec1aebff 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -17,8 +17,6 @@ import { vectorDot, vectorNormalize, } from "@excalidraw/math"; -import { isPointInShape } from "@excalidraw/utils/collision"; -import { getSelectionBoxShape } from "@excalidraw/utils/shape"; import { COLOR_PALETTE, @@ -104,9 +102,9 @@ import { Emitter, } from "@excalidraw/common"; -import { getCommonBounds, getElementAbsoluteCoords } from "@excalidraw/element"; - import { + getCommonBounds, + getElementAbsoluteCoords, bindOrUnbindLinearElements, fixBindingsAfterDeletion, getHoveredElementForBinding, @@ -115,13 +113,8 @@ import { shouldEnableBindingForPointerEvent, updateBoundElements, getSuggestedBindingsForArrows, -} from "@excalidraw/element"; - -import { LinearElementEditor } from "@excalidraw/element"; - -import { newElementWith } from "@excalidraw/element"; - -import { + LinearElementEditor, + newElementWith, newFrameElement, newFreeDrawElement, newEmbeddableElement, @@ -133,11 +126,8 @@ import { newLinearElement, newTextElement, refreshTextDimensions, -} from "@excalidraw/element"; - -import { deepCopyElement, duplicateElements } from "@excalidraw/element"; - -import { + deepCopyElement, + duplicateElements, hasBoundTextElement, isArrowElement, isBindingElement, @@ -158,48 +148,27 @@ import { isFlowchartNodeElement, isBindableElement, isTextElement, -} from "@excalidraw/element"; - -import { getLockedLinearCursorAlignSize, getNormalizedDimensions, isElementCompletelyInViewport, isElementInViewport, isInvisiblySmallElement, -} from "@excalidraw/element"; - -import { - getBoundTextShape, getCornerRadius, - getElementShape, isPathALoop, -} from "@excalidraw/element"; - -import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, -} from "@excalidraw/element"; - -import { getInitializedImageElements, loadHTMLImageElement, normalizeSVG, updateImageCache as _updateImageCache, -} from "@excalidraw/element"; - -import { getBoundTextElement, getContainerCenter, getContainerElement, isValidTextContainer, redrawTextBoundingBox, -} from "@excalidraw/element"; - -import { shouldShowBoundingBox } from "@excalidraw/element"; - -import { + shouldShowBoundingBox, getFrameChildren, isCursorInFrame, addElementsToFrame, @@ -214,29 +183,17 @@ import { getFrameLikeTitle, getElementsOverlappingFrame, filterElementsEligibleAsFrameChildren, -} from "@excalidraw/element"; - -import { hitElementBoundText, hitElementBoundingBoxOnly, hitElementItself, -} from "@excalidraw/element"; - -import { getVisibleSceneBounds } from "@excalidraw/element"; - -import { + getVisibleSceneBounds, FlowChartCreator, FlowChartNavigator, getLinkDirectionFromKey, -} from "@excalidraw/element"; - -import { cropElement } from "@excalidraw/element"; - -import { wrapText } from "@excalidraw/element"; - -import { isElementLink, parseElementLinkFromURL } from "@excalidraw/element"; - -import { + cropElement, + wrapText, + isElementLink, + parseElementLinkFromURL, isMeasureTextSupported, normalizeText, measureText, @@ -244,13 +201,8 @@ import { getApproxMinLineWidth, getApproxMinLineHeight, getMinTextElementWidth, -} from "@excalidraw/element"; - -import { ShapeCache } from "@excalidraw/element"; - -import { getRenderOpacity } from "@excalidraw/element"; - -import { + ShapeCache, + getRenderOpacity, editGroupForSelectedElement, getElementsInGroup, getSelectedGroupIdForElement, @@ -258,42 +210,28 @@ import { isElementInGroup, isSelectedViaGroup, selectGroupsForSelectedElements, -} from "@excalidraw/element"; - -import { syncInvalidIndices, syncMovedIndices } from "@excalidraw/element"; - -import { + syncInvalidIndices, + syncMovedIndices, excludeElementsInFramesFromSelection, getSelectionStateForElements, makeNextSelectedElementIds, -} from "@excalidraw/element"; - -import { getResizeOffsetXY, getResizeArrowDirection, transformElements, -} from "@excalidraw/element"; - -import { getCursorForResizingElement, getElementWithTransformHandleType, getTransformHandleTypeFromCoords, -} from "@excalidraw/element"; - -import { dragNewElement, dragSelectedElements, getDragOffsetXY, + isNonDeletedElement, + Scene, + Store, + CaptureUpdateAction, + type ElementUpdate, + hitElementBoundingBox, } from "@excalidraw/element"; -import { isNonDeletedElement } from "@excalidraw/element"; - -import { Scene } from "@excalidraw/element"; - -import { Store, CaptureUpdateAction } from "@excalidraw/element"; - -import type { ElementUpdate } from "@excalidraw/element"; - import type { LocalPoint, Radians } from "@excalidraw/math"; import type { @@ -5095,6 +5033,7 @@ class App extends React.Component { return null; } + // NOTE: Hot path for hit testing, so avoid unnecessary computations private getElementAtPosition( x: number, y: number, @@ -5134,16 +5073,12 @@ class App extends React.Component { // If we're hitting element with highest z-index only on its bounding box // while also hitting other element figure, the latter should be considered. return hitElementItself({ - x, - y, + point: pointFrom(x, y), element: elementWithHighestZIndex, - shape: getElementShape( - elementWithHighestZIndex, - this.scene.getNonDeletedElementsMap(), - ), // when overlapping, we would like to be more precise // this also avoids the need to update past tests - threshold: this.getElementHitThreshold() / 2, + threshold: this.getElementHitThreshold(elementWithHighestZIndex) / 2, + elementsMap: this.scene.getNonDeletedElementsMap(), frameNameBound: isFrameLikeElement(elementWithHighestZIndex) ? this.frameNameBoundsCache.get(elementWithHighestZIndex) : null, @@ -5158,6 +5093,7 @@ class App extends React.Component { return null; } + // NOTE: Hot path for hit testing, so avoid unnecessary computations private getElementsAtPosition( x: number, y: number, @@ -5208,8 +5144,14 @@ class App extends React.Component { return elements; } - getElementHitThreshold() { - return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value; + getElementHitThreshold(element: ExcalidrawElement) { + return Math.max( + element.strokeWidth / 2 + 0.1, + // NOTE: Here be dragons. Do not go under the 0.63 multiplier unless you're + // willing to test extensively. The hit testing starts to become unreliable + // due to FP imprecision under 0.63 in high zoom levels. + 0.85 * (DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value), + ); } private hitElement( @@ -5224,35 +5166,35 @@ class App extends React.Component { this.state.selectedElementIds[element.id] && shouldShowBoundingBox([element], this.state) ) { - const selectionShape = getSelectionBoxShape( - element, - this.scene.getNonDeletedElementsMap(), - isImageElement(element) ? 0 : this.getElementHitThreshold(), - ); - // if hitting the bounding box, return early // but if not, we should check for other cases as well (e.g. frame name) - if (isPointInShape(pointFrom(x, y), selectionShape)) { + if ( + hitElementBoundingBox( + pointFrom(x, y), + element, + this.scene.getNonDeletedElementsMap(), + this.getElementHitThreshold(element), + ) + ) { return true; } } // take bound text element into consideration for hit collision as well const hitBoundTextOfElement = hitElementBoundText( - x, - y, - getBoundTextShape(element, this.scene.getNonDeletedElementsMap()), + pointFrom(x, y), + element, + this.scene.getNonDeletedElementsMap(), ); if (hitBoundTextOfElement) { return true; } return hitElementItself({ - x, - y, + point: pointFrom(x, y), element, - shape: getElementShape(element, this.scene.getNonDeletedElementsMap()), - threshold: this.getElementHitThreshold(), + threshold: this.getElementHitThreshold(element), + elementsMap: this.scene.getNonDeletedElementsMap(), frameNameBound: isFrameLikeElement(element) ? this.frameNameBoundsCache.get(element) : null, @@ -5280,14 +5222,10 @@ class App extends React.Component { if ( isArrowElement(elements[index]) && hitElementItself({ - x, - y, + point: pointFrom(x, y), element: elements[index], - shape: getElementShape( - elements[index], - this.scene.getNonDeletedElementsMap(), - ), - threshold: this.getElementHitThreshold(), + elementsMap: this.scene.getNonDeletedElementsMap(), + threshold: this.getElementHitThreshold(elements[index]), }) ) { hitElement = elements[index]; @@ -5632,14 +5570,10 @@ class App extends React.Component { hasBoundTextElement(container) || !isTransparent(container.backgroundColor) || hitElementItself({ - x: sceneX, - y: sceneY, + point: pointFrom(sceneX, sceneY), element: container, - shape: getElementShape( - container, - this.scene.getNonDeletedElementsMap(), - ), - threshold: this.getElementHitThreshold(), + elementsMap: this.scene.getNonDeletedElementsMap(), + threshold: this.getElementHitThreshold(container), }) ) { const midPoint = getContainerCenter( @@ -6329,13 +6263,10 @@ class App extends React.Component { let segmentMidPointHoveredCoords = null; if ( hitElementItself({ - x: scenePointerX, - y: scenePointerY, + point: pointFrom(scenePointerX, scenePointerY), element, - shape: getElementShape( - element, - this.scene.getNonDeletedElementsMap(), - ), + elementsMap, + threshold: this.getElementHitThreshold(element), }) ) { hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor( @@ -7505,7 +7436,10 @@ class App extends React.Component { } // How many pixels off the shape boundary we still consider a hit - const threshold = this.getElementHitThreshold(); + const threshold = Math.max( + DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value, + 1, + ); const [x1, y1, x2, y2] = getCommonBounds(selectedElements); return ( point.x > x1 - threshold && @@ -9768,14 +9702,13 @@ class App extends React.Component { ((hitElement && hitElementBoundingBoxOnly( { - x: pointerDownState.origin.x, - y: pointerDownState.origin.y, - element: hitElement, - shape: getElementShape( - hitElement, - this.scene.getNonDeletedElementsMap(), + point: pointFrom( + pointerDownState.origin.x, + pointerDownState.origin.y, ), - threshold: this.getElementHitThreshold(), + element: hitElement, + elementsMap, + threshold: this.getElementHitThreshold(hitElement), frameNameBound: isFrameLikeElement(hitElement) ? this.frameNameBoundsCache.get(hitElement) : null, @@ -10882,6 +10815,7 @@ class App extends React.Component { croppingElement, cropElement( croppingElement, + this.scene.getNonDeletedElementsMap(), transformHandleType, image.naturalWidth, image.naturalHeight, diff --git a/packages/excalidraw/components/Stats/stats.test.tsx b/packages/excalidraw/components/Stats/stats.test.tsx index cc1cfce984..05163a32f3 100644 --- a/packages/excalidraw/components/Stats/stats.test.tsx +++ b/packages/excalidraw/components/Stats/stats.test.tsx @@ -133,7 +133,6 @@ describe("binding with linear elements", () => { const inputX = UI.queryStatsProperty("X")?.querySelector( ".drag-input", ) as HTMLInputElement; - expect(linear.startBinding).not.toBe(null); expect(inputX).not.toBeNull(); UI.updateInput(inputX, String("204")); @@ -657,6 +656,7 @@ describe("stats for multiple elements", () => { mouse.reset(); Keyboard.withModifierKeys({ shift: true }, () => { + mouse.moveTo(10, 0); mouse.click(); }); diff --git a/packages/excalidraw/components/hyperlink/Hyperlink.tsx b/packages/excalidraw/components/hyperlink/Hyperlink.tsx index 292659bdb6..5e380e4e6e 100644 --- a/packages/excalidraw/components/hyperlink/Hyperlink.tsx +++ b/packages/excalidraw/components/hyperlink/Hyperlink.tsx @@ -463,7 +463,7 @@ const shouldHideLinkPopup = ( const threshold = 15 / appState.zoom.value; // hitbox to prevent hiding when hovered in element bounding box - if (hitElementBoundingBox(sceneX, sceneY, element, elementsMap)) { + if (hitElementBoundingBox(pointFrom(sceneX, sceneY), element, elementsMap)) { return false; } const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap); diff --git a/packages/excalidraw/components/hyperlink/helpers.ts b/packages/excalidraw/components/hyperlink/helpers.ts index 7d39b7ff74..e89ecdc554 100644 --- a/packages/excalidraw/components/hyperlink/helpers.ts +++ b/packages/excalidraw/components/hyperlink/helpers.ts @@ -92,7 +92,7 @@ export const isPointHittingLink = ( if ( !isMobile && appState.viewModeEnabled && - hitElementBoundingBox(x, y, element, elementsMap) + hitElementBoundingBox(pointFrom(x, y), element, elementsMap) ) { return true; } diff --git a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap index 48337288a9..1bc3ce9253 100644 --- a/packages/excalidraw/data/__snapshots__/transform.test.ts.snap +++ b/packages/excalidraw/data/__snapshots__/transform.test.ts.snap @@ -175,7 +175,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s "startBinding": { "elementId": "diamond-1", "focus": 0, - "gap": 4.545343408287929, + "gap": 4.535423522449215, }, "strokeColor": "#e67700", "strokeStyle": "solid", @@ -335,7 +335,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t "endBinding": { "elementId": "text-2", "focus": 0, - "gap": 14, + "gap": 16, }, "fillStyle": "solid", "frameId": null, @@ -1540,7 +1540,7 @@ exports[`Test Transform > should transform the elements correctly when linear el "endBinding": { "elementId": "B", "focus": 0, - "gap": 14, + "gap": 32, }, "fillStyle": "solid", "frameId": null, diff --git a/packages/excalidraw/data/transform.test.ts b/packages/excalidraw/data/transform.test.ts index 0b0718e8e3..0d9fcf3161 100644 --- a/packages/excalidraw/data/transform.test.ts +++ b/packages/excalidraw/data/transform.test.ts @@ -781,7 +781,7 @@ describe("Test Transform", () => { expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({ elementId: "rect-1", focus: -0, - gap: 14, + gap: 25, }); expect(rect.boundElements).toStrictEqual([ { diff --git a/packages/excalidraw/eraser/index.ts b/packages/excalidraw/eraser/index.ts index 5e6c4e5171..6fbe2bd4ca 100644 --- a/packages/excalidraw/eraser/index.ts +++ b/packages/excalidraw/eraser/index.ts @@ -1,25 +1,19 @@ import { arrayToMap, easeOut, THEME } from "@excalidraw/common"; -import { getElementLineSegments } from "@excalidraw/element"; import { - lineSegment, - lineSegmentIntersectionPoints, - pointFrom, -} from "@excalidraw/math"; + computeBoundTextPosition, + getBoundTextElement, + intersectElementWithLineSegment, + isPointInElement, +} from "@excalidraw/element"; +import { lineSegment, pointFrom } from "@excalidraw/math"; import { getElementsInGroup } from "@excalidraw/element"; -import { getElementShape } from "@excalidraw/element"; import { shouldTestInside } from "@excalidraw/element"; -import { isPointInShape } from "@excalidraw/utils/collision"; import { hasBoundTextElement, isBoundToContainer } from "@excalidraw/element"; import { getBoundTextElementId } from "@excalidraw/element"; -import type { GeometricShape } from "@excalidraw/utils/shape"; -import type { - ElementsSegmentsMap, - GlobalPoint, - LineSegment, -} from "@excalidraw/math/types"; +import type { GlobalPoint, LineSegment } from "@excalidraw/math/types"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; import { AnimatedTrail } from "../animated-trail"; @@ -28,15 +22,9 @@ import type { AnimationFrameHandler } from "../animation-frame-handler"; import type App from "../components/App"; -// just enough to form a segment; this is sufficient for eraser -const POINTS_ON_TRAIL = 2; - export class EraserTrail extends AnimatedTrail { private elementsToErase: Set = new Set(); private groupsToErase: Set = new Set(); - private segmentsCache: Map[]> = new Map(); - private geometricShapesCache: Map> = - new Map(); constructor(animationFrameHandler: AnimationFrameHandler, app: App) { super(animationFrameHandler, app, { @@ -79,14 +67,21 @@ export class EraserTrail extends AnimatedTrail { } private updateElementsToBeErased(restoreToErase?: boolean) { - let eraserPath: GlobalPoint[] = + const eraserPath: GlobalPoint[] = super .getCurrentTrail() ?.originalPoints?.map((p) => pointFrom(p[0], p[1])) || []; + if (eraserPath.length < 2) { + return []; + } + // for efficiency and avoid unnecessary calculations, // take only POINTS_ON_TRAIL points to form some number of segments - eraserPath = eraserPath?.slice(eraserPath.length - POINTS_ON_TRAIL); + const pathSegment = lineSegment( + eraserPath[eraserPath.length - 1], + eraserPath[eraserPath.length - 2], + ); const candidateElements = this.app.visibleElements.filter( (el) => !el.locked, @@ -94,28 +89,13 @@ export class EraserTrail extends AnimatedTrail { const candidateElementsMap = arrayToMap(candidateElements); - const pathSegments = eraserPath.reduce((acc, point, index) => { - if (index === 0) { - return acc; - } - acc.push(lineSegment(eraserPath[index - 1], point)); - return acc; - }, [] as LineSegment[]); - - if (pathSegments.length === 0) { - return []; - } - for (const element of candidateElements) { // restore only if already added to the to-be-erased set if (restoreToErase && this.elementsToErase.has(element.id)) { const intersects = eraserTest( - pathSegments, + pathSegment, element, - this.segmentsCache, - this.geometricShapesCache, candidateElementsMap, - this.app, ); if (intersects) { @@ -148,12 +128,9 @@ export class EraserTrail extends AnimatedTrail { } } else if (!restoreToErase && !this.elementsToErase.has(element.id)) { const intersects = eraserTest( - pathSegments, + pathSegment, element, - this.segmentsCache, - this.geometricShapesCache, candidateElementsMap, - this.app, ); if (intersects) { @@ -196,45 +173,37 @@ export class EraserTrail extends AnimatedTrail { super.clearTrails(); this.elementsToErase.clear(); this.groupsToErase.clear(); - this.segmentsCache.clear(); } } const eraserTest = ( - pathSegments: LineSegment[], + pathSegment: LineSegment, element: ExcalidrawElement, - elementsSegments: ElementsSegmentsMap, - shapesCache: Map>, elementsMap: ElementsMap, - app: App, ): boolean => { - let shape = shapesCache.get(element.id); - - if (!shape) { - shape = getElementShape(element, elementsMap); - shapesCache.set(element.id, shape); - } - - const lastPoint = pathSegments[pathSegments.length - 1][1]; - if (shouldTestInside(element) && isPointInShape(lastPoint, shape)) { + const lastPoint = pathSegment[1]; + if ( + shouldTestInside(element) && + isPointInElement(lastPoint, element, elementsMap) + ) { return true; } - let elementSegments = elementsSegments.get(element.id); + const boundTextElement = getBoundTextElement(element, elementsMap); - if (!elementSegments) { - elementSegments = getElementLineSegments(element, elementsMap); - elementsSegments.set(element.id, elementSegments); - } - - return pathSegments.some((pathSegment) => - elementSegments?.some( - (elementSegment) => - lineSegmentIntersectionPoints( - pathSegment, - elementSegment, - app.getElementHitThreshold(), - ) !== null, - ), + return ( + intersectElementWithLineSegment(element, elementsMap, pathSegment, 0, true) + .length > 0 || + (!!boundTextElement && + intersectElementWithLineSegment( + { + ...boundTextElement, + ...computeBoundTextPosition(element, boundTextElement, elementsMap), + }, + elementsMap, + pathSegment, + 0, + true, + ).length > 0) ); }; diff --git a/packages/excalidraw/lasso/index.ts b/packages/excalidraw/lasso/index.ts index 163a8b7a98..5d9f704583 100644 --- a/packages/excalidraw/lasso/index.ts +++ b/packages/excalidraw/lasso/index.ts @@ -199,6 +199,7 @@ export class LassoTrail extends AnimatedTrail { const { selectedElementIds } = getLassoSelectedElementIds({ lassoPath, elements: this.app.visibleElements, + elementsMap: this.app.scene.getNonDeletedElementsMap(), elementsSegments: this.elementsSegments, intersectedElements: this.intersectedElements, enclosedElements: this.enclosedElements, diff --git a/packages/excalidraw/lasso/utils.ts b/packages/excalidraw/lasso/utils.ts index 0721ccf0ef..2cab64662b 100644 --- a/packages/excalidraw/lasso/utils.ts +++ b/packages/excalidraw/lasso/utils.ts @@ -3,20 +3,25 @@ import { simplify } from "points-on-curve"; import { polygonFromPoints, lineSegment, - lineSegmentIntersectionPoints, polygonIncludesPointNonZero, } from "@excalidraw/math"; -import type { - ElementsSegmentsMap, - GlobalPoint, - LineSegment, -} from "@excalidraw/math/types"; -import type { ExcalidrawElement } from "@excalidraw/element/types"; +import { + type Bounds, + computeBoundTextPosition, + doBoundsIntersect, + getBoundTextElement, + getElementBounds, + intersectElementWithLineSegment, +} from "@excalidraw/element"; + +import type { ElementsSegmentsMap, GlobalPoint } from "@excalidraw/math/types"; +import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; export const getLassoSelectedElementIds = (input: { lassoPath: GlobalPoint[]; elements: readonly ExcalidrawElement[]; + elementsMap: ElementsMap; elementsSegments: ElementsSegmentsMap; intersectedElements: Set; enclosedElements: Set; @@ -27,6 +32,7 @@ export const getLassoSelectedElementIds = (input: { const { lassoPath, elements, + elementsMap, elementsSegments, intersectedElements, enclosedElements, @@ -40,8 +46,26 @@ export const getLassoSelectedElementIds = (input: { const unlockedElements = elements.filter((el) => !el.locked); // as the path might not enclose a shape anymore, clear before checking enclosedElements.clear(); + intersectedElements.clear(); + const lassoBounds = lassoPath.reduce( + (acc, item) => { + return [ + Math.min(acc[0], item[0]), + Math.min(acc[1], item[1]), + Math.max(acc[2], item[0]), + Math.max(acc[3], item[1]), + ]; + }, + [Infinity, Infinity, -Infinity, -Infinity], + ) as Bounds; for (const element of unlockedElements) { + // First check if the lasso segment intersects the element's axis-aligned + // bounding box as it is much faster than checking intersection against + // the element's shape + const elementBounds = getElementBounds(element, elementsMap); + if ( + doBoundsIntersect(lassoBounds, elementBounds) && !intersectedElements.has(element.id) && !enclosedElements.has(element.id) ) { @@ -49,7 +73,7 @@ export const getLassoSelectedElementIds = (input: { if (enclosed) { enclosedElements.add(element.id); } else { - const intersects = intersectionTest(path, element, elementsSegments); + const intersects = intersectionTest(path, element, elementsMap); if (intersects) { intersectedElements.add(element.id); } @@ -85,26 +109,34 @@ const enclosureTest = ( const intersectionTest = ( lassoPath: GlobalPoint[], element: ExcalidrawElement, - elementsSegments: ElementsSegmentsMap, + elementsMap: ElementsMap, ): boolean => { - const elementSegments = elementsSegments.get(element.id); - if (!elementSegments) { - return false; - } + const lassoSegments = lassoPath + .slice(1) + .map((point: GlobalPoint, index) => lineSegment(lassoPath[index], point)) + .concat([lineSegment(lassoPath[lassoPath.length - 1], lassoPath[0])]); - const lassoSegments = lassoPath.reduce((acc, point, index) => { - if (index === 0) { - return acc; - } - acc.push(lineSegment(lassoPath[index - 1], point)); - return acc; - }, [] as LineSegment[]); + const boundTextElement = getBoundTextElement(element, elementsMap); - return lassoSegments.some((lassoSegment) => - elementSegments.some( - (elementSegment) => - // introduce a bit of tolerance to account for roughness and simplification of paths - lineSegmentIntersectionPoints(lassoSegment, elementSegment, 1) !== null, - ), + return lassoSegments.some( + (lassoSegment) => + intersectElementWithLineSegment( + element, + elementsMap, + lassoSegment, + 0, + true, + ).length > 0 || + (!!boundTextElement && + intersectElementWithLineSegment( + { + ...boundTextElement, + ...computeBoundTextPosition(element, boundTextElement, elementsMap), + }, + elementsMap, + lassoSegment, + 0, + true, + ).length > 0), ); }; diff --git a/packages/excalidraw/renderer/helpers.ts b/packages/excalidraw/renderer/helpers.ts index e42f4166b0..9dd26df646 100644 --- a/packages/excalidraw/renderer/helpers.ts +++ b/packages/excalidraw/renderer/helpers.ts @@ -5,17 +5,14 @@ import { getDiamondPoints } from "@excalidraw/element"; import { getCornerRadius } from "@excalidraw/element"; import { - bezierEquation, curve, - curveTangent, + curveCatmullRomCubicApproxPoints, + curveCatmullRomQuadraticApproxPoints, + curveOffsetPoints, type GlobalPoint, + offsetPointsForQuadraticBezier, pointFrom, - pointFromVector, pointRotateRads, - vector, - vectorNormal, - vectorNormalize, - vectorScale, } from "@excalidraw/math"; import type { @@ -102,25 +99,14 @@ export const bootstrapCanvas = ({ function drawCatmullRomQuadraticApprox( ctx: CanvasRenderingContext2D, points: GlobalPoint[], - segments = 20, + tension = 0.5, ) { - ctx.lineTo(points[0][0], points[0][1]); + const pointSets = curveCatmullRomQuadraticApproxPoints(points, tension); + if (pointSets) { + for (let i = 0; i < pointSets.length - 1; i++) { + const [[cpX, cpY], [p2X, p2Y]] = pointSets[i]; - for (let i = 0; i < points.length - 1; i++) { - const p0 = points[i - 1 < 0 ? 0 : i - 1]; - const p1 = points[i]; - const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1]; - - for (let t = 0; t <= 1; t += 1 / segments) { - const t2 = t * t; - - const x = - (1 - t) * (1 - t) * p0[0] + 2 * (1 - t) * t * p1[0] + t2 * p2[0]; - - const y = - (1 - t) * (1 - t) * p0[1] + 2 * (1 - t) * t * p1[1] + t2 * p2[1]; - - ctx.lineTo(x, y); + ctx.quadraticCurveTo(cpX, cpY, p2X, p2Y); } } } @@ -128,35 +114,13 @@ function drawCatmullRomQuadraticApprox( function drawCatmullRomCubicApprox( ctx: CanvasRenderingContext2D, points: GlobalPoint[], - segments = 20, + tension = 0.5, ) { - ctx.lineTo(points[0][0], points[0][1]); - - for (let i = 0; i < points.length - 1; i++) { - const p0 = points[i - 1 < 0 ? 0 : i - 1]; - const p1 = points[i]; - const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1]; - const p3 = points[i + 2 >= points.length ? points.length - 1 : i + 2]; - - for (let t = 0; t <= 1; t += 1 / segments) { - const t2 = t * t; - const t3 = t2 * t; - - const x = - 0.5 * - (2 * p1[0] + - (-p0[0] + p2[0]) * t + - (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + - (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3); - - const y = - 0.5 * - (2 * p1[1] + - (-p0[1] + p2[1]) * t + - (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + - (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3); - - ctx.lineTo(x, y); + const pointSets = curveCatmullRomCubicApproxPoints(points, tension); + if (pointSets) { + for (let i = 0; i < pointSets.length; i++) { + const [[cp1x, cp1y], [cp2x, cp2y], [x, y]] = pointSets[i]; + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); } } } @@ -168,7 +132,10 @@ export const drawHighlightForRectWithRotation = ( ) => { const [x, y] = pointRotateRads( pointFrom(element.x, element.y), - elementCenterPoint(element), + elementCenterPoint( + element, + window.h.app.scene.getElementsMapIncludingDeleted(), + ), element.angle, ); @@ -187,25 +154,25 @@ export const drawHighlightForRectWithRotation = ( context.beginPath(); { - const topLeftApprox = offsetQuadraticBezier( + const topLeftApprox = offsetPointsForQuadraticBezier( pointFrom(0, 0 + radius), pointFrom(0, 0), pointFrom(0 + radius, 0), padding, ); - const topRightApprox = offsetQuadraticBezier( + const topRightApprox = offsetPointsForQuadraticBezier( pointFrom(element.width - radius, 0), pointFrom(element.width, 0), pointFrom(element.width, radius), padding, ); - const bottomRightApprox = offsetQuadraticBezier( + const bottomRightApprox = offsetPointsForQuadraticBezier( pointFrom(element.width, element.height - radius), pointFrom(element.width, element.height), pointFrom(element.width - radius, element.height), padding, ); - const bottomLeftApprox = offsetQuadraticBezier( + const bottomLeftApprox = offsetPointsForQuadraticBezier( pointFrom(radius, element.height), pointFrom(0, element.height), pointFrom(0, element.height - radius), @@ -230,25 +197,25 @@ export const drawHighlightForRectWithRotation = ( // mask" on a filled shape for the diamond highlight, because stroking creates // sharp inset edges on line joins < 90 degrees. { - const topLeftApprox = offsetQuadraticBezier( + const topLeftApprox = offsetPointsForQuadraticBezier( pointFrom(0 + radius, 0), pointFrom(0, 0), pointFrom(0, 0 + radius), -FIXED_BINDING_DISTANCE, ); - const topRightApprox = offsetQuadraticBezier( + const topRightApprox = offsetPointsForQuadraticBezier( pointFrom(element.width, radius), pointFrom(element.width, 0), pointFrom(element.width - radius, 0), -FIXED_BINDING_DISTANCE, ); - const bottomRightApprox = offsetQuadraticBezier( + const bottomRightApprox = offsetPointsForQuadraticBezier( pointFrom(element.width - radius, element.height), pointFrom(element.width, element.height), pointFrom(element.width, element.height - radius), -FIXED_BINDING_DISTANCE, ); - const bottomLeftApprox = offsetQuadraticBezier( + const bottomLeftApprox = offsetPointsForQuadraticBezier( pointFrom(0, element.height - radius), pointFrom(0, element.height), pointFrom(radius, element.height), @@ -325,7 +292,10 @@ export const drawHighlightForDiamondWithRotation = ( ) => { const [x, y] = pointRotateRads( pointFrom(element.x, element.y), - elementCenterPoint(element), + elementCenterPoint( + element, + window.h.app.scene.getElementsMapIncludingDeleted(), + ), element.angle, ); context.save(); @@ -343,32 +313,40 @@ export const drawHighlightForDiamondWithRotation = ( const horizontalRadius = element.roundness ? getCornerRadius(Math.abs(rightY - topY), element) : (rightY - topY) * 0.01; - const topApprox = offsetCubicBezier( - pointFrom(topX - verticalRadius, topY + horizontalRadius), - pointFrom(topX, topY), - pointFrom(topX, topY), - pointFrom(topX + verticalRadius, topY + horizontalRadius), + const topApprox = curveOffsetPoints( + curve( + pointFrom(topX - verticalRadius, topY + horizontalRadius), + pointFrom(topX, topY), + pointFrom(topX, topY), + pointFrom(topX + verticalRadius, topY + horizontalRadius), + ), padding, ); - const rightApprox = offsetCubicBezier( - pointFrom(rightX - verticalRadius, rightY - horizontalRadius), - pointFrom(rightX, rightY), - pointFrom(rightX, rightY), - pointFrom(rightX - verticalRadius, rightY + horizontalRadius), + const rightApprox = curveOffsetPoints( + curve( + pointFrom(rightX - verticalRadius, rightY - horizontalRadius), + pointFrom(rightX, rightY), + pointFrom(rightX, rightY), + pointFrom(rightX - verticalRadius, rightY + horizontalRadius), + ), padding, ); - const bottomApprox = offsetCubicBezier( - pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), - pointFrom(bottomX, bottomY), - pointFrom(bottomX, bottomY), - pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), + const bottomApprox = curveOffsetPoints( + curve( + pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), + pointFrom(bottomX, bottomY), + pointFrom(bottomX, bottomY), + pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), + ), padding, ); - const leftApprox = offsetCubicBezier( - pointFrom(leftX + verticalRadius, leftY + horizontalRadius), - pointFrom(leftX, leftY), - pointFrom(leftX, leftY), - pointFrom(leftX + verticalRadius, leftY - horizontalRadius), + const leftApprox = curveOffsetPoints( + curve( + pointFrom(leftX + verticalRadius, leftY + horizontalRadius), + pointFrom(leftX, leftY), + pointFrom(leftX, leftY), + pointFrom(leftX + verticalRadius, leftY - horizontalRadius), + ), padding, ); @@ -376,13 +354,13 @@ export const drawHighlightForDiamondWithRotation = ( topApprox[topApprox.length - 1][0], topApprox[topApprox.length - 1][1], ); - context.lineTo(rightApprox[0][0], rightApprox[0][1]); + context.lineTo(rightApprox[1][0], rightApprox[1][1]); drawCatmullRomCubicApprox(context, rightApprox); - context.lineTo(bottomApprox[0][0], bottomApprox[0][1]); + context.lineTo(bottomApprox[1][0], bottomApprox[1][1]); drawCatmullRomCubicApprox(context, bottomApprox); - context.lineTo(leftApprox[0][0], leftApprox[0][1]); + context.lineTo(leftApprox[1][0], leftApprox[1][1]); drawCatmullRomCubicApprox(context, leftApprox); - context.lineTo(topApprox[0][0], topApprox[0][1]); + context.lineTo(topApprox[1][0], topApprox[1][1]); drawCatmullRomCubicApprox(context, topApprox); } @@ -398,32 +376,40 @@ export const drawHighlightForDiamondWithRotation = ( const horizontalRadius = element.roundness ? getCornerRadius(Math.abs(rightY - topY), element) : (rightY - topY) * 0.01; - const topApprox = offsetCubicBezier( - pointFrom(topX + verticalRadius, topY + horizontalRadius), - pointFrom(topX, topY), - pointFrom(topX, topY), - pointFrom(topX - verticalRadius, topY + horizontalRadius), + const topApprox = curveOffsetPoints( + curve( + pointFrom(topX + verticalRadius, topY + horizontalRadius), + pointFrom(topX, topY), + pointFrom(topX, topY), + pointFrom(topX - verticalRadius, topY + horizontalRadius), + ), -FIXED_BINDING_DISTANCE, ); - const rightApprox = offsetCubicBezier( - pointFrom(rightX - verticalRadius, rightY + horizontalRadius), - pointFrom(rightX, rightY), - pointFrom(rightX, rightY), - pointFrom(rightX - verticalRadius, rightY - horizontalRadius), + const rightApprox = curveOffsetPoints( + curve( + pointFrom(rightX - verticalRadius, rightY + horizontalRadius), + pointFrom(rightX, rightY), + pointFrom(rightX, rightY), + pointFrom(rightX - verticalRadius, rightY - horizontalRadius), + ), -FIXED_BINDING_DISTANCE, ); - const bottomApprox = offsetCubicBezier( - pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), - pointFrom(bottomX, bottomY), - pointFrom(bottomX, bottomY), - pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), + const bottomApprox = curveOffsetPoints( + curve( + pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), + pointFrom(bottomX, bottomY), + pointFrom(bottomX, bottomY), + pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), + ), -FIXED_BINDING_DISTANCE, ); - const leftApprox = offsetCubicBezier( - pointFrom(leftX + verticalRadius, leftY - horizontalRadius), - pointFrom(leftX, leftY), - pointFrom(leftX, leftY), - pointFrom(leftX + verticalRadius, leftY + horizontalRadius), + const leftApprox = curveOffsetPoints( + curve( + pointFrom(leftX + verticalRadius, leftY - horizontalRadius), + pointFrom(leftX, leftY), + pointFrom(leftX, leftY), + pointFrom(leftX + verticalRadius, leftY + horizontalRadius), + ), -FIXED_BINDING_DISTANCE, ); @@ -431,66 +417,16 @@ export const drawHighlightForDiamondWithRotation = ( topApprox[topApprox.length - 1][0], topApprox[topApprox.length - 1][1], ); - context.lineTo(leftApprox[0][0], leftApprox[0][1]); + context.lineTo(leftApprox[1][0], leftApprox[1][1]); drawCatmullRomCubicApprox(context, leftApprox); - context.lineTo(bottomApprox[0][0], bottomApprox[0][1]); + context.lineTo(bottomApprox[1][0], bottomApprox[1][1]); drawCatmullRomCubicApprox(context, bottomApprox); - context.lineTo(rightApprox[0][0], rightApprox[0][1]); + context.lineTo(rightApprox[1][0], rightApprox[1][1]); drawCatmullRomCubicApprox(context, rightApprox); - context.lineTo(topApprox[0][0], topApprox[0][1]); + context.lineTo(topApprox[1][0], topApprox[1][1]); drawCatmullRomCubicApprox(context, topApprox); } context.closePath(); context.fill(); context.restore(); }; - -function offsetCubicBezier( - p0: GlobalPoint, - p1: GlobalPoint, - p2: GlobalPoint, - p3: GlobalPoint, - offsetDist: number, - steps = 20, -) { - const offsetPoints = []; - - for (let i = 0; i <= steps; i++) { - const t = i / steps; - const c = curve(p0, p1, p2, p3); - const point = bezierEquation(c, t); - const tangent = vectorNormalize(curveTangent(c, t)); - const normal = vectorNormal(tangent); - - offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point)); - } - - return offsetPoints; -} - -function offsetQuadraticBezier( - p0: GlobalPoint, - p1: GlobalPoint, - p2: GlobalPoint, - offsetDist: number, - steps = 20, -) { - const offsetPoints = []; - - for (let i = 0; i <= steps; i++) { - const t = i / steps; - const t1 = 1 - t; - const point = pointFrom( - t1 * t1 * p0[0] + 2 * t1 * t * p1[0] + t * t * p2[0], - t1 * t1 * p0[1] + 2 * t1 * t * p1[1] + t * t * p2[1], - ); - const tangentX = 2 * (1 - t) * (p1[0] - p0[0]) + 2 * t * (p2[0] - p1[0]); - const tangentY = 2 * (1 - t) * (p1[1] - p0[1]) + 2 * t * (p2[1] - p1[1]); - const tangent = vectorNormalize(vector(tangentX, tangentY)); - const normal = vectorNormal(tangent); - - offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point)); - } - - return offsetPoints; -} diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index 7d8b474858..40bce1c7d0 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -193,16 +193,10 @@ const renderBindingHighlightForBindableElement = ( elementsMap: ElementsMap, zoom: InteractiveCanvasAppState["zoom"], ) => { - const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap); - const width = x2 - x1; - const height = y2 - y1; - - context.strokeStyle = "rgba(0,0,0,.05)"; - context.fillStyle = "rgba(0,0,0,.05)"; - - // To ensure the binding highlight doesn't overlap the element itself const padding = maxBindingGap(element, element.width, element.height, zoom); + context.fillStyle = "rgba(0,0,0,.05)"; + switch (element.type) { case "rectangle": case "text": @@ -216,10 +210,13 @@ const renderBindingHighlightForBindableElement = ( case "diamond": drawHighlightForDiamondWithRotation(context, padding, element); break; - case "ellipse": - context.lineWidth = - maxBindingGap(element, element.width, element.height, zoom) - - FIXED_BINDING_DISTANCE; + case "ellipse": { + const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap); + const width = x2 - x1; + const height = y2 - y1; + + context.strokeStyle = "rgba(0,0,0,.05)"; + context.lineWidth = padding - FIXED_BINDING_DISTANCE; strokeEllipseWithRotation( context, @@ -230,6 +227,7 @@ const renderBindingHighlightForBindableElement = ( element.angle, ); break; + } } }; diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 23f4ccb4f2..54fce16156 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -898,7 +898,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1016,9 +1016,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1052,9 +1050,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1101,7 +1097,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1162,7 +1158,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "resizingElement": null, "scrollX": 0, "scrollY": 0, - "scrolledOutside": false, + "scrolledOutside": true, "searchMatches": null, "selectedElementIds": { "id0": true, @@ -1205,7 +1201,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id0", "index": "a0", "isDeleted": false, @@ -1213,20 +1209,18 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, - "width": 20, - "x": -10, - "y": 0, + "versionNonce": 2019559783, + "width": 10, + "x": -20, + "y": -10, } `; @@ -1263,23 +1257,21 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a0", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, + "width": 10, + "x": -20, + "y": -10, }, "inserted": { "isDeleted": true, @@ -1317,7 +1309,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1427,9 +1419,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1461,9 +1451,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1518,9 +1506,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1572,9 +1558,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1650,7 +1634,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1760,9 +1744,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1794,9 +1776,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -1851,9 +1831,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1905,9 +1883,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1983,7 +1959,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2044,7 +2020,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "resizingElement": null, "scrollX": 0, "scrollY": 0, - "scrolledOutside": false, + "scrolledOutside": true, "searchMatches": null, "selectedElementIds": { "id0": true, @@ -2087,7 +2063,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id0", "index": "a0", "isDeleted": false, @@ -2095,20 +2071,18 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, - "width": 20, - "x": -10, - "y": 0, + "versionNonce": 2019559783, + "width": 10, + "x": -20, + "y": -10, } `; @@ -2145,23 +2119,21 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a0", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, + "width": 10, + "x": -20, + "y": -10, }, "inserted": { "isDeleted": true, @@ -2199,7 +2171,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2299,7 +2271,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id0", "index": "a0", "isDeleted": true, @@ -2307,20 +2279,18 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1014066025, - "width": 20, - "x": -10, - "y": 0, + "versionNonce": 1116226695, + "width": 10, + "x": -20, + "y": -10, } `; @@ -2357,23 +2327,21 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a0", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, + "width": 10, + "x": -20, + "y": -10, }, "inserted": { "isDeleted": true, @@ -2440,7 +2408,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2542,7 +2510,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id0", "index": "a0", "isDeleted": false, @@ -2550,20 +2518,18 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, - "width": 20, - "x": -10, - "y": 0, + "versionNonce": 2019559783, + "width": 10, + "x": -20, + "y": -10, } `; @@ -2576,7 +2542,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id3", "index": "a1", "isDeleted": false, @@ -2584,9 +2550,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -2595,9 +2559,9 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "updated": 1, "version": 5, "versionNonce": 400692809, - "width": 20, - "x": 0, - "y": 10, + "width": 10, + "x": -10, + "y": 0, } `; @@ -2634,23 +2598,21 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a0", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, + "width": 10, + "x": -20, + "y": -10, }, "inserted": { "isDeleted": true, @@ -2688,23 +2650,21 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a1", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": 0, - "y": 10, + "width": 10, + "x": -10, + "y": 0, }, "inserted": { "isDeleted": true, @@ -2742,7 +2702,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2859,9 +2819,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -2895,9 +2853,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -2952,9 +2908,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3006,9 +2960,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3114,7 +3066,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "currentItemFontSize": 20, "currentItemOpacity": 60, "currentItemRoughness": 2, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#e03131", "currentItemStrokeStyle": "dotted", @@ -3226,9 +3178,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "locked": false, "opacity": 60, "roughness": 2, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#e03131", "strokeStyle": "dotted", @@ -3236,7 +3186,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 941653321, + "versionNonce": 908564423, "width": 20, "x": -10, "y": 0, @@ -3260,17 +3210,15 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "locked": false, "opacity": 60, "roughness": 2, - "roundness": { - "type": 3, - }, - "seed": 289600103, + "roundness": null, + "seed": 1315507081, "strokeColor": "#e03131", "strokeStyle": "dotted", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 9, - "versionNonce": 640725609, + "versionNonce": 406373543, "width": 20, "x": 20, "y": 30, @@ -3317,9 +3265,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3371,9 +3317,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3597,7 +3541,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3707,17 +3651,15 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 1014066025, + "roundness": null, + "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 23633383, + "versionNonce": 1604849351, "width": 20, "x": 20, "y": 30, @@ -3741,17 +3683,15 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, + "versionNonce": 401146281, "width": 20, "x": -10, "y": 0, @@ -3798,9 +3738,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3852,9 +3790,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3922,7 +3858,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4032,9 +3968,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1014066025, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -4066,9 +4000,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -4123,9 +4055,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4177,9 +4107,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4247,7 +4175,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4360,9 +4288,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -4394,9 +4320,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -4451,9 +4375,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4505,9 +4427,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5528,7 +5448,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5641,9 +5561,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 453191, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -5675,17 +5593,15 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 400692809, + "roundness": null, + "seed": 1604849351, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 10, "x": 12, "y": 0, @@ -5732,9 +5648,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5786,9 +5700,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6749,7 +6661,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -6866,9 +6778,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 449462985, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -6902,9 +6812,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 238820263, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -6959,9 +6867,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7013,9 +6919,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7684,7 +7588,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8684,7 +8588,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8745,7 +8649,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "resizingElement": null, "scrollX": 0, "scrollY": 0, - "scrolledOutside": false, + "scrolledOutside": true, "searchMatches": null, "selectedElementIds": { "id0": true, @@ -9675,7 +9579,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9780,7 +9684,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "id": "id0", "index": "a0", "isDeleted": false, @@ -9788,20 +9692,18 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, - "seed": 449462985, + "roundness": null, + "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 3, - "versionNonce": 1150084233, - "width": 20, - "x": -10, - "y": 0, + "versionNonce": 2019559783, + "width": 10, + "x": -20, + "y": -10, } `; @@ -9822,9 +9724,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -9856,9 +9756,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -9912,23 +9810,21 @@ exports[`contextMenu element > shows context menu for element > [end of test] un "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": 20, + "height": 10, "index": "a0", "isDeleted": false, "link": null, "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", - "width": 20, - "x": -10, - "y": 0, + "width": 10, + "x": -20, + "y": -10, }, "inserted": { "isDeleted": true, diff --git a/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap index 62beb7f7cf..c25b269f4b 100644 --- a/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/dragCreate.test.tsx.snap @@ -71,9 +71,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -107,9 +105,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -155,9 +151,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "startArrowhead": null, "startBinding": null, @@ -193,9 +187,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", diff --git a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap index 3105273934..59ee0a3f85 100644 --- a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap @@ -1,11 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = ` -"eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTTW/TQFx1MDAxML3zKyz3iqiTqlx1MDAxY3IrUERcdTAwMGblQJA4IFx1MDAwZVvvxFx1MDAxZWWyu9pcdTAwMWQ3XHRVJH5cdTAwMDY3/lwiP4HZjeuNnWJLlvbtfLx58/z0qihK3jsoXHUwMDE3RVx0u1pcdTAwMTFqr7bl64g/glx1MDAwZmiNXFzN0znYztcpsmV2i8tLspLQ2sCLq6qqjklAsFx1MDAwMcNBwr7LuSie0lduUMfUm1x1MDAxNJaA575cZjvO6E6gajjtR6ctam5cdTAwMDWZvVx1MDAxZKBcdTAwMTawaXmMKdNcdTAwMTCMXHUwMDEyXHUwMDAze7uG95asj1x1MDAxZC9mXHUwMDEw39z0QdXrxtvO6CGGvTLBKS/D5LhcdTAwMTVcdTAwMTIteZ+qi1x1MDAxZaJWOenxrac4n+D/y5KmTWsgRMFmXHUwMDAzap2qkePwsypPXHUwMDExXHUwMDE5ujudtP2ROXm1gbsorumIXHUwMDA2XHUwMDE4jYbdXHUwMDE0TCP23Z5cdTAwMTeTN3HVI4fMXHUwMDFkQI+IZU+cYZ+tqceqY/ggduBUYqUoQNY78rjNVlx1MDAxOZHsnFY86Uto1tM4sd/6hdrJTlwi9N8/v3+dbM5cdTAwMWFe4s9IcF6N0I9qg1x1MDAxNKW+XHUwMDFllbghbOKcJcHqxFx1MDAwMTIso9h+uGbr8m0t9Vx1MDAxNFx1MDAxYfDn+7BcdTAwMWVcdTAwMWI0ir6+SE91bL9AOFx1MDAxMmTfwenk8Gkw+Zv5dbo4yDdZoFTOLVn0XHUwMDFhNio2QT1cdTAwMTn1iDG4PGaC7q2GW6NcdTAwMWVoqmP5iLB9d/5XXFys0tNcdTAwMTPvV3DfXHUwMDEx41JWXbP4IHkr8ks2ir9cZvTQ4Vx1MDAxZvRzK/4ifQ==" `; diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 080d8fbf05..0dc6e525ba 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -24,7 +24,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -133,9 +133,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -165,9 +163,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -200,7 +196,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "102.45605", + "height": "99.19972", "id": "id691", "index": "a2", "isDeleted": false, @@ -214,8 +210,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "102.80179", - "102.45605", + "98.40611", + "99.19972", ], ], "roughness": 1, @@ -230,8 +226,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 37, - "width": "102.80179", - "x": "-0.42182", + "width": "98.40611", + "x": 1, "y": 0, } `; @@ -258,9 +254,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -314,15 +308,15 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "focus": 0, "gap": 1, }, - "height": "70.45017", + "height": "68.55969", "points": [ [ 0, 0, ], [ - "100.70774", - "70.45017", + 98, + "68.55969", ], ], "startBinding": { @@ -337,15 +331,15 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "focus": "-0.02000", "gap": 1, }, - "height": "0.09250", + "height": "0.00656", "points": [ [ 0, 0, ], [ - "98.58579", - "0.09250", + "98.00000", + "-0.00656", ], ], "startBinding": { @@ -398,30 +392,30 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "id691": { "deleted": { - "height": "102.45584", + "height": "99.19972", "points": [ [ 0, 0, ], [ - "102.79971", - "102.45584", + "98.40611", + "99.19972", ], ], "startBinding": null, "y": 0, }, "inserted": { - "height": "70.33521", + "height": "68.58402", "points": [ [ 0, 0, ], [ - "100.78887", - "70.33521", + 98, + "68.58402", ], ], "startBinding": { @@ -429,7 +423,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "focus": "0.02970", "gap": 1, }, - "y": "35.20327", + "y": "35.82151", }, }, }, @@ -467,9 +461,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -498,9 +490,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -615,7 +605,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -724,9 +714,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -756,9 +744,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -814,7 +800,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "updated": 1, "version": 33, "width": 100, - "x": "149.29289", + "x": 149, "y": 0, } `; @@ -976,9 +962,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1007,9 +991,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1124,7 +1106,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1235,7 +1217,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "1.30038", + "height": "1.36342", "id": "id715", "index": "Zz", "isDeleted": false, @@ -1249,14 +1231,12 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "98.58579", - "1.30038", + 98, + "1.36342", ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": { "elementId": "id711", @@ -1273,8 +1253,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -1301,9 +1281,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1338,9 +1316,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1387,9 +1363,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1418,9 +1392,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1492,7 +1464,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1603,7 +1575,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "1.30038", + "height": "1.36342", "id": "id725", "index": "a0", "isDeleted": false, @@ -1617,14 +1589,12 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "98.58579", - "1.30038", + 98, + "1.36342", ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": { "elementId": "id720", @@ -1641,8 +1611,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -1669,9 +1639,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1706,9 +1674,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1759,7 +1725,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "11.27227", + "height": "11.63758", "index": "a0", "isDeleted": false, "lastCommittedPoint": null, @@ -1772,14 +1738,12 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "98.58579", - "11.27227", + 98, + "11.63758", ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": { "elementId": "id720", @@ -1794,8 +1758,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, }, "inserted": { @@ -1861,7 +1825,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1969,9 +1933,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2001,9 +1963,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2050,9 +2010,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2081,9 +2039,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2128,7 +2084,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2240,9 +2196,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2277,9 +2231,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2308,7 +2260,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "374.05754", + "height": "370.26975", "id": "id740", "index": "a2", "isDeleted": false, @@ -2322,8 +2274,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "502.78936", - "-374.05754", + "498.00000", + "-370.26975", ], ], "roughness": 1, @@ -2342,9 +2294,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 10, - "width": "502.78936", - "x": "-0.83465", - "y": "-36.58211", + "width": "498.00000", + "x": 1, + "y": "-37.92697", } `; @@ -2382,9 +2334,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2413,9 +2363,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2565,7 +2513,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2678,9 +2626,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2716,9 +2662,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2757,9 +2701,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2811,9 +2753,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2864,7 +2804,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2977,9 +2917,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3015,9 +2953,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3056,9 +2992,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3148,7 +3082,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3261,9 +3195,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3299,9 +3231,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3340,9 +3270,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3442,7 +3370,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3550,9 +3478,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3587,9 +3513,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3625,9 +3549,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3728,7 +3650,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3836,9 +3758,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3874,9 +3794,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3963,7 +3881,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4076,9 +3994,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4114,9 +4030,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4166,9 +4080,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4222,7 +4134,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4335,9 +4247,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4373,9 +4283,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4431,9 +4339,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4495,7 +4401,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4608,9 +4514,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4646,9 +4550,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4726,7 +4628,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4839,9 +4741,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4877,9 +4777,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4957,7 +4855,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5065,9 +4963,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5103,9 +4999,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5186,7 +5080,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5299,9 +5193,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5337,9 +5229,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "opacity": 100, "originalText": "que pasa", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5415,7 +5305,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5523,9 +5413,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5556,9 +5444,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "name": null, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5605,9 +5491,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5675,7 +5559,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5784,9 +5668,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5818,9 +5700,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5881,9 +5761,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5914,9 +5792,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6008,7 +5884,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -6115,9 +5991,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6147,9 +6021,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6179,9 +6051,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6234,9 +6104,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6289,9 +6157,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6366,9 +6232,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6437,7 +6301,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -6547,9 +6411,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6579,9 +6441,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6611,9 +6471,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6666,9 +6524,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6697,9 +6553,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6728,9 +6582,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6817,7 +6669,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -6935,9 +6787,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6969,9 +6819,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7003,9 +6851,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7037,9 +6883,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7137,7 +6981,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7438,7 +7282,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7543,9 +7387,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7599,9 +7441,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7668,7 +7508,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7773,9 +7613,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7805,9 +7643,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7837,9 +7673,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7915,9 +7749,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7946,9 +7778,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7977,9 +7807,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8024,7 +7852,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8129,9 +7957,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8161,9 +7987,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8193,9 +8017,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8271,9 +8093,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8302,9 +8122,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8333,9 +8151,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8380,7 +8196,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8491,9 +8307,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8523,9 +8337,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8555,9 +8367,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#a5d8ff", "strokeStyle": "solid", "strokeWidth": 2, @@ -8610,9 +8420,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8664,9 +8472,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8788,7 +8594,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8952,9 +8758,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#a5d8ff", "strokeStyle": "solid", "strokeWidth": 2, @@ -9075,7 +8879,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9182,9 +8986,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9214,9 +9016,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#a5d8ff", "strokeStyle": "solid", "strokeWidth": 2, @@ -9269,9 +9069,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9341,7 +9139,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9448,9 +9246,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9480,9 +9276,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#a5d8ff", "strokeStyle": "solid", "strokeWidth": 2, @@ -9561,9 +9355,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9608,7 +9400,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9715,9 +9507,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#ffec99", "strokeStyle": "solid", "strokeWidth": 2, @@ -9770,9 +9560,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9840,7 +9628,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9951,9 +9739,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9986,9 +9772,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10020,9 +9804,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10054,9 +9836,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10141,7 +9921,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10483,7 +10263,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10588,9 +10368,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10643,9 +10421,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10719,7 +10495,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10827,9 +10603,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10859,9 +10633,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10919,9 +10691,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": { "elementId": "KPrBI4g_v9qUB1XxYLgSz", @@ -11007,9 +10777,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": { "elementId": "KPrBI4g_v9qUB1XxYLgSz", @@ -11091,9 +10859,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11122,9 +10888,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11169,7 +10933,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11276,9 +11040,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11380,9 +11142,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11427,7 +11187,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11532,9 +11292,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11564,9 +11322,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11619,9 +11375,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11666,7 +11420,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11773,9 +11527,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11805,9 +11557,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11860,9 +11610,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11907,7 +11655,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#e03131", "currentItemStrokeStyle": "solid", @@ -12012,9 +11760,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12175,9 +11921,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12310,7 +12054,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -12418,9 +12162,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12449,9 +12191,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12510,9 +12250,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12557,7 +12295,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -12664,9 +12402,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12696,9 +12432,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12751,9 +12485,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12798,7 +12530,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -12905,9 +12637,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12937,9 +12667,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12992,9 +12720,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13039,7 +12765,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13146,9 +12872,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13241,9 +12965,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13288,7 +13010,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13395,9 +13117,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13427,9 +13147,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13482,9 +13200,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13576,9 +13292,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13623,7 +13337,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13731,9 +13445,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13795,7 +13507,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13907,9 +13619,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13941,9 +13651,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14003,9 +13711,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14036,9 +13742,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14083,7 +13787,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14191,9 +13895,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14223,9 +13925,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14272,9 +13972,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14303,9 +14001,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14350,7 +14046,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14459,9 +14155,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14583,9 +14277,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14630,7 +14322,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14792,7 +14484,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14910,9 +14602,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14948,9 +14638,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14988,9 +14676,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15033,7 +14719,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + 98, 0, ], ], @@ -15053,8 +14739,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -15174,9 +14860,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15211,9 +14895,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15245,9 +14927,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15493,7 +15173,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -15611,9 +15291,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15649,9 +15327,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15689,9 +15365,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15734,7 +15408,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + 98, 0, ], ], @@ -15754,8 +15428,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -15794,9 +15468,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15831,9 +15503,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -15865,9 +15535,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16113,7 +15781,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -16231,9 +15899,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16269,9 +15935,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16309,9 +15973,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16354,7 +16016,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + 98, 0, ], ], @@ -16374,8 +16036,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -16414,9 +16076,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16451,9 +16111,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16485,9 +16143,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16733,7 +16389,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -16849,9 +16505,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16887,9 +16541,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16927,9 +16579,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -16972,7 +16622,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + 98, 0, ], ], @@ -16992,8 +16642,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 10, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -17105,9 +16755,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17142,9 +16790,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17176,9 +16822,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17448,7 +17092,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -17567,9 +17211,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17605,9 +17247,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17645,9 +17285,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17690,7 +17328,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding 0, ], [ - "98.58579", + 98, 0, ], ], @@ -17710,8 +17348,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "type": "arrow", "updated": 1, "version": 11, - "width": "98.58579", - "x": "0.70711", + "width": 98, + "x": 1, "y": 0, } `; @@ -17838,9 +17476,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17875,9 +17511,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "opacity": 100, "originalText": "ola", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -17909,9 +17543,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18201,7 +17833,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -18311,9 +17943,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18343,9 +17973,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18375,9 +18003,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18430,9 +18056,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18484,9 +18108,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18538,9 +18160,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18681,7 +18301,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -18795,9 +18415,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18829,9 +18447,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18863,9 +18479,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18897,9 +18511,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18931,9 +18543,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -18965,9 +18575,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19027,9 +18635,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19060,9 +18666,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19124,9 +18728,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19157,9 +18759,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19204,7 +18804,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -19311,9 +18911,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19343,9 +18941,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19375,9 +18971,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19430,9 +19024,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19484,9 +19076,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19538,9 +19128,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -19665,7 +19253,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index 729e53d225..ddbb76c273 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -17,9 +17,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -51,9 +49,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1604849351, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -85,9 +81,7 @@ exports[`move element > rectangle 5`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -124,9 +118,7 @@ exports[`move element > rectangles with binding arrow 5`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -163,9 +155,7 @@ exports[`move element > rectangles with binding arrow 6`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -196,7 +186,7 @@ exports[`move element > rectangles with binding arrow 7`] = ` "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "87.29887", + "height": "81.40630", "id": "id6", "index": "a2", "isDeleted": false, @@ -210,8 +200,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` 0, ], [ - "86.85786", - "87.29887", + "81.00000", + "81.40630", ], ], "roughness": 1, @@ -232,8 +222,8 @@ exports[`move element > rectangles with binding arrow 7`] = ` "updated": 1, "version": 11, "versionNonce": 1996028265, - "width": "86.85786", - "x": "107.07107", - "y": "47.07107", + "width": "81.00000", + "x": "110.00000", + "y": 50, } `; diff --git a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap index 5f2a82e4e8..1b0092757a 100644 --- a/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/multiPointCreate.test.tsx.snap @@ -95,9 +95,7 @@ exports[`multi point mode in linear elements > line 3`] = ` ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "startArrowhead": null, "startBinding": null, diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index a81ed30352..60fe9249fc 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -24,7 +24,7 @@ exports[`given element A and group of elements B and given both are selected whe "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -164,9 +164,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -218,9 +216,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -272,9 +268,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -446,7 +440,7 @@ exports[`given element A and group of elements B and given both are selected whe "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -588,9 +582,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -642,9 +634,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -696,9 +686,7 @@ exports[`given element A and group of elements B and given both are selected whe "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -858,7 +846,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -991,9 +979,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1045,9 +1031,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1210,9 +1194,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1414,7 +1396,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1547,9 +1529,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1619,7 +1599,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -1757,9 +1737,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1811,9 +1789,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1865,9 +1841,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -1999,7 +1973,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2134,9 +2108,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] undo "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2188,9 +2160,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] undo "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2235,7 +2205,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = ` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2368,9 +2338,7 @@ exports[`regression tests > arrow keys > [end of test] undo stack 1`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2415,7 +2383,7 @@ exports[`regression tests > can drag element that covers another element, while "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -2550,9 +2518,7 @@ exports[`regression tests > can drag element that covers another element, while "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2604,9 +2570,7 @@ exports[`regression tests > can drag element that covers another element, while "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2658,9 +2622,7 @@ exports[`regression tests > can drag element that covers another element, while "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2738,7 +2700,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1971c2", "currentItemStrokeStyle": "solid", @@ -2871,9 +2833,7 @@ exports[`regression tests > change the properties of a shape > [end of test] und "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -2987,7 +2947,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3099,9 +3059,7 @@ exports[`regression tests > click on an element and drag it > [dragged] element "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -3156,9 +3114,7 @@ exports[`regression tests > click on an element and drag it > [dragged] undo sta "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3228,7 +3184,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3363,9 +3319,7 @@ exports[`regression tests > click on an element and drag it > [end of test] undo "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3460,7 +3414,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`] "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3595,9 +3549,7 @@ exports[`regression tests > click to select a shape > [end of test] undo stack 1 "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3649,9 +3601,7 @@ exports[`regression tests > click to select a shape > [end of test] undo stack 1 "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3718,7 +3668,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -3854,9 +3804,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3908,9 +3856,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -3962,9 +3908,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4032,7 +3976,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4167,9 +4111,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4221,9 +4163,7 @@ exports[`regression tests > deleting last but one element in editing group shoul "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4462,7 +4402,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4548,9 +4488,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1505387817, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -4626,9 +4564,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4680,9 +4616,7 @@ exports[`regression tests > deselects group of selected elements on pointer down "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4747,7 +4681,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -4881,9 +4815,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -4935,9 +4867,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5023,7 +4953,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5108,9 +5038,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1150084233, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -5186,9 +5114,7 @@ exports[`regression tests > deselects selected element on pointer down when poin "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5233,7 +5159,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5366,9 +5292,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5433,7 +5357,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5566,9 +5490,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5620,9 +5542,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5674,9 +5594,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -5820,7 +5738,7 @@ exports[`regression tests > drags selected elements from point inside common bou "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -5957,9 +5875,7 @@ exports[`regression tests > drags selected elements from point inside common bou "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6011,9 +5927,7 @@ exports[`regression tests > drags selected elements from point inside common bou "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6113,7 +6027,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -6244,9 +6158,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6298,9 +6210,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6352,9 +6262,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -6494,9 +6402,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -6719,9 +6625,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -6935,7 +6839,7 @@ exports[`regression tests > given a group of selected elements with an element t "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7071,9 +6975,7 @@ exports[`regression tests > given a group of selected elements with an element t "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7125,9 +7027,7 @@ exports[`regression tests > given a group of selected elements with an element t "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7179,9 +7079,7 @@ exports[`regression tests > given a group of selected elements with an element t "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7269,7 +7167,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7405,9 +7303,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7459,9 +7355,7 @@ exports[`regression tests > given a selected element A and a not selected elemen "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7548,7 +7442,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7683,9 +7577,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7714,9 +7606,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7783,7 +7673,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -7918,9 +7808,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -7949,9 +7837,7 @@ exports[`regression tests > given selected element A with lower z-index than uns "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8021,7 +7907,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8154,9 +8040,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8201,7 +8085,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8334,9 +8218,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] undo stac "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8381,7 +8263,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8514,9 +8396,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] undo stac "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -8561,7 +8441,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8785,7 +8665,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`] "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -8960,9 +8840,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] undo stack 1 ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -9009,7 +8887,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9203,7 +9081,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9427,7 +9305,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9560,9 +9438,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] undo stac "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -9607,7 +9483,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`] "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9782,9 +9658,7 @@ exports[`regression tests > key l selects line tool > [end of test] undo stack 1 ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "startArrowhead": null, "startBinding": null, "strokeColor": "#1e1e1e", @@ -9831,7 +9705,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -9964,9 +9838,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] undo stac "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10011,7 +9883,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10205,7 +10077,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10338,9 +10210,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10385,7 +10255,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -10526,9 +10396,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10580,9 +10448,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10634,9 +10500,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10772,9 +10636,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10805,9 +10667,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10838,9 +10698,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -10885,7 +10743,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11020,9 +10878,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11074,9 +10930,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11165,7 +11019,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = ` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11288,7 +11142,7 @@ exports[`regression tests > shift click on selected element should deselect it o "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11421,9 +11275,7 @@ exports[`regression tests > shift click on selected element should deselect it o "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11488,7 +11340,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11625,9 +11477,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11679,9 +11529,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11803,7 +11651,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -11942,9 +11790,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -11996,9 +11842,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12050,9 +11894,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12220,7 +12062,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -12363,9 +12205,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12417,9 +12257,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12559,9 +12397,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12613,9 +12449,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -12844,7 +12678,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -12970,7 +12804,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`] "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13105,9 +12939,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13159,9 +12991,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13213,9 +13043,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13591,7 +13419,7 @@ exports[`regression tests > switches from group of selected elements to another "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -13679,9 +13507,7 @@ exports[`regression tests > switches from group of selected elements to another "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1723083209, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -13757,9 +13583,7 @@ exports[`regression tests > switches from group of selected elements to another "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13811,9 +13635,7 @@ exports[`regression tests > switches from group of selected elements to another "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13865,9 +13687,7 @@ exports[`regression tests > switches from group of selected elements to another "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -13932,7 +13752,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14019,9 +13839,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1604849351, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -14097,9 +13915,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14151,9 +13967,7 @@ exports[`regression tests > switches selected element on pointer down > [end of "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14198,7 +14012,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`] "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14321,7 +14135,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14605,9 +14419,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14659,9 +14471,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] undo st "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, @@ -14706,7 +14516,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", @@ -14829,7 +14639,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = ` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", diff --git a/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap index c134a23eda..f47b89813f 100644 --- a/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/selection.test.tsx.snap @@ -81,9 +81,7 @@ exports[`select single element on the scene > arrow escape 1`] = ` ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "startArrowhead": null, "startBinding": null, @@ -117,9 +115,7 @@ exports[`select single element on the scene > diamond 1`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -151,9 +147,7 @@ exports[`select single element on the scene > ellipse 1`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -185,9 +179,7 @@ exports[`select single element on the scene > rectangle 1`] = ` "locked": false, "opacity": 100, "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": 1278240551, "strokeColor": "#1e1e1e", "strokeStyle": "solid", diff --git a/packages/excalidraw/tests/contextmenu.test.tsx b/packages/excalidraw/tests/contextmenu.test.tsx index 75de2717f8..5bb7fee8e1 100644 --- a/packages/excalidraw/tests/contextmenu.test.tsx +++ b/packages/excalidraw/tests/contextmenu.test.tsx @@ -110,8 +110,8 @@ describe("contextMenu element", () => { it("shows context menu for element", () => { UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + mouse.down(0, 0); + mouse.up(10, 10); fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, @@ -304,8 +304,8 @@ describe("contextMenu element", () => { it("selecting 'Copy styles' in context menu copies styles", () => { UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + mouse.down(0, 0); + mouse.up(10, 10); fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, @@ -389,8 +389,8 @@ describe("contextMenu element", () => { it("selecting 'Delete' in context menu deletes element", () => { UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + mouse.down(0, 0); + mouse.up(10, 10); fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, @@ -405,8 +405,8 @@ describe("contextMenu element", () => { it("selecting 'Add to library' in context menu adds element to library", async () => { UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + mouse.down(0, 0); + mouse.up(10, 10); fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, @@ -424,8 +424,8 @@ describe("contextMenu element", () => { it("selecting 'Duplicate' in context menu duplicates element", () => { UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + mouse.down(0, 0); + mouse.up(10, 10); fireEvent.contextMenu(GlobalTestState.interactiveCanvas, { button: 2, diff --git a/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap b/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap index dfb0f8d2da..d59a829a0f 100644 --- a/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap +++ b/packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap @@ -31,9 +31,7 @@ exports[`restoreElements > should restore arrow element correctly 1`] = ` ], ], "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": Any, "startArrowhead": null, "startBinding": null, @@ -193,9 +191,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = ` ], "pressures": [], "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": Any, "simulatePressure": true, "strokeColor": "#1e1e1e", @@ -242,9 +238,7 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] = ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": Any, "startArrowhead": null, "startBinding": null, @@ -292,9 +286,7 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] = ], "polygon": false, "roughness": 1, - "roundness": { - "type": 2, - }, + "roundness": null, "seed": Any, "startArrowhead": null, "startBinding": null, @@ -334,9 +326,7 @@ exports[`restoreElements > should restore text element correctly passing value f "opacity": 100, "originalText": "text", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": Any, "strokeColor": "#1e1e1e", "strokeStyle": "solid", @@ -378,9 +368,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo "opacity": 100, "originalText": "", "roughness": 1, - "roundness": { - "type": 3, - }, + "roundness": null, "seed": Any, "strokeColor": "#1e1e1e", "strokeStyle": "solid", diff --git a/packages/excalidraw/tests/helpers/ui.ts b/packages/excalidraw/tests/helpers/ui.ts index abfadf3316..9a37bf6a0b 100644 --- a/packages/excalidraw/tests/helpers/ui.ts +++ b/packages/excalidraw/tests/helpers/ui.ts @@ -32,6 +32,7 @@ import type { ExcalidrawTextContainer, ExcalidrawTextElementWithContainer, ExcalidrawImageElement, + ElementsMap, } from "@excalidraw/element/types"; import { createTestHook } from "../../components/App"; @@ -146,6 +147,7 @@ export class Keyboard { const getElementPointForSelection = ( element: ExcalidrawElement, + elementsMap: ElementsMap, ): GlobalPoint => { const { x, y, width, angle } = element; const target = pointFrom( @@ -162,7 +164,7 @@ const getElementPointForSelection = ( (bounds[1] + bounds[3]) / 2, ); } else { - center = elementCenterPoint(element); + center = elementCenterPoint(element, elementsMap); } if (isTextElement(element)) { @@ -299,7 +301,12 @@ export class Pointer { elements = Array.isArray(elements) ? elements : [elements]; elements.forEach((element) => { this.reset(); - this.click(...getElementPointForSelection(element)); + this.click( + ...getElementPointForSelection( + element, + h.app.scene.getElementsMapIncludingDeleted(), + ), + ); }); }); @@ -308,13 +315,23 @@ export class Pointer { clickOn(element: ExcalidrawElement) { this.reset(); - this.click(...getElementPointForSelection(element)); + this.click( + ...getElementPointForSelection( + element, + h.app.scene.getElementsMapIncludingDeleted(), + ), + ); this.reset(); } doubleClickOn(element: ExcalidrawElement) { this.reset(); - this.doubleClick(...getElementPointForSelection(element)); + this.doubleClick( + ...getElementPointForSelection( + element, + h.app.scene.getElementsMapIncludingDeleted(), + ), + ); this.reset(); } } @@ -598,6 +615,7 @@ export class UI { const mutations = cropElement( element, + h.scene.getNonDeletedElementsMap(), handle, naturalWidth, naturalHeight, diff --git a/packages/excalidraw/tests/lasso.test.tsx b/packages/excalidraw/tests/lasso.test.tsx index 7e67d9b5b5..d84ce1ffb9 100644 --- a/packages/excalidraw/tests/lasso.test.tsx +++ b/packages/excalidraw/tests/lasso.test.tsx @@ -70,6 +70,7 @@ const updatePath = (startPoint: GlobalPoint, points: LocalPoint[]) => { ?.originalPoints?.map((p) => pointFrom(p[0], p[1])) ?? [], elements: h.elements, + elementsMap: h.scene.getNonDeletedElementsMap(), elementsSegments, intersectedElements: new Set(), enclosedElements: new Set(), diff --git a/packages/excalidraw/tests/move.test.tsx b/packages/excalidraw/tests/move.test.tsx index 1a02ba1db0..095db38a0c 100644 --- a/packages/excalidraw/tests/move.test.tsx +++ b/packages/excalidraw/tests/move.test.tsx @@ -124,8 +124,8 @@ describe("move element", () => { expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectB.x, rectB.y]).toEqual([201, 2]); - expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[107.07, 47.07]]); - expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([[86.86, 87.3]]); + expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[110, 50]]); + expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([[81, 81.4]]); h.elements.forEach((element) => expect(element).toMatchSnapshot()); }); diff --git a/packages/excalidraw/tests/rotate.test.tsx b/packages/excalidraw/tests/rotate.test.tsx index 9687b08f25..38079db8f3 100644 --- a/packages/excalidraw/tests/rotate.test.tsx +++ b/packages/excalidraw/tests/rotate.test.tsx @@ -35,7 +35,7 @@ test("unselected bound arrow updates when rotating its target element", async () expect(arrow.endBinding?.elementId).toEqual(rectangle.id); expect(arrow.x).toBeCloseTo(-80); expect(arrow.y).toBeCloseTo(50); - expect(arrow.width).toBeCloseTo(116.7, 1); + expect(arrow.width).toBeCloseTo(110.7, 1); expect(arrow.height).toBeCloseTo(0); }); diff --git a/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx b/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx index e7cd975092..d3ec968efc 100644 --- a/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx +++ b/packages/excalidraw/wysiwyg/textWysiwyg.test.tsx @@ -682,7 +682,7 @@ describe("textWysiwyg", () => { expect(diamond.height).toBe(70); }); - it("should bind text to container when double clicked on center of transparent container", async () => { + it("should bind text to container when double clicked inside of the transparent container", async () => { const rectangle = API.createElement({ type: "rectangle", x: 10, @@ -1500,9 +1500,7 @@ describe("textWysiwyg", () => { locked: false, opacity: 100, roughness: 1, - roundness: { - type: 3, - }, + roundness: null, strokeColor: "#1e1e1e", strokeStyle: "solid", strokeWidth: 2, diff --git a/packages/math/src/curve.ts b/packages/math/src/curve.ts index 359caee09d..26ab690e9c 100644 --- a/packages/math/src/curve.ts +++ b/packages/math/src/curve.ts @@ -1,8 +1,7 @@ -import type { Bounds } from "@excalidraw/element"; +import { doBoundsIntersect, type Bounds } from "@excalidraw/element"; -import { isPoint, pointDistance, pointFrom } from "./point"; -import { rectangle, rectangleIntersectLineSegment } from "./rectangle"; -import { vector } from "./vector"; +import { isPoint, pointDistance, pointFrom, pointFromVector } from "./point"; +import { vector, vectorNormal, vectorNormalize, vectorScale } from "./vector"; import type { Curve, GlobalPoint, LineSegment, LocalPoint } from "./types"; @@ -105,16 +104,15 @@ export function curveIntersectLineSegment< Point extends GlobalPoint | LocalPoint, >(c: Curve, l: LineSegment): Point[] { // Optimize by doing a cheap bounding box check first - const bounds = curveBounds(c); - if ( - rectangleIntersectLineSegment( - rectangle( - pointFrom(bounds[0], bounds[1]), - pointFrom(bounds[2], bounds[3]), - ), - l, - ).length === 0 - ) { + const b1 = curveBounds(c); + const b2 = [ + Math.min(l[0][0], l[1][0]), + Math.min(l[0][1], l[1][1]), + Math.max(l[0][0], l[1][0]), + Math.max(l[0][1], l[1][1]), + ] as Bounds; + + if (!doBoundsIntersect(b1, b2)) { return []; } @@ -303,3 +301,108 @@ function curveBounds( const y = [P0[1], P1[1], P2[1], P3[1]]; return [Math.min(...x), Math.min(...y), Math.max(...x), Math.max(...y)]; } + +export function curveCatmullRomQuadraticApproxPoints( + points: GlobalPoint[], + tension = 0.5, +) { + if (points.length < 2) { + return; + } + + const pointSets: [GlobalPoint, GlobalPoint][] = []; + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i - 1 < 0 ? 0 : i - 1]; + const p1 = points[i]; + const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1]; + const cpX = p1[0] + ((p2[0] - p0[0]) * tension) / 2; + const cpY = p1[1] + ((p2[1] - p0[1]) * tension) / 2; + + pointSets.push([ + pointFrom(cpX, cpY), + pointFrom(p2[0], p2[1]), + ]); + } + + return pointSets; +} + +export function curveCatmullRomCubicApproxPoints< + Point extends GlobalPoint | LocalPoint, +>(points: Point[], tension = 0.5) { + if (points.length < 2) { + return; + } + + const pointSets: Curve[] = []; + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i - 1 < 0 ? 0 : i - 1]; + const p1 = points[i]; + const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1]; + const p3 = points[i + 2 >= points.length ? points.length - 1 : i + 2]; + const tangent1 = [(p2[0] - p0[0]) * tension, (p2[1] - p0[1]) * tension]; + const tangent2 = [(p3[0] - p1[0]) * tension, (p3[1] - p1[1]) * tension]; + const cp1x = p1[0] + tangent1[0] / 3; + const cp1y = p1[1] + tangent1[1] / 3; + const cp2x = p2[0] - tangent2[0] / 3; + const cp2y = p2[1] - tangent2[1] / 3; + + pointSets.push( + curve( + pointFrom(p1[0], p1[1]), + pointFrom(cp1x, cp1y), + pointFrom(cp2x, cp2y), + pointFrom(p2[0], p2[1]), + ), + ); + } + + return pointSets; +} + +export function curveOffsetPoints( + [p0, p1, p2, p3]: Curve, + offset: number, + steps = 50, +) { + const offsetPoints = []; + + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const c = curve(p0, p1, p2, p3); + const point = bezierEquation(c, t); + const tangent = vectorNormalize(curveTangent(c, t)); + const normal = vectorNormal(tangent); + + offsetPoints.push(pointFromVector(vectorScale(normal, offset), point)); + } + + return offsetPoints; +} + +export function offsetPointsForQuadraticBezier( + p0: GlobalPoint, + p1: GlobalPoint, + p2: GlobalPoint, + offsetDist: number, + steps = 50, +) { + const offsetPoints = []; + + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const t1 = 1 - t; + const point = pointFrom( + t1 * t1 * p0[0] + 2 * t1 * t * p1[0] + t * t * p2[0], + t1 * t1 * p0[1] + 2 * t1 * t * p1[1] + t * t * p2[1], + ); + const tangentX = 2 * (1 - t) * (p1[0] - p0[0]) + 2 * t * (p2[0] - p1[0]); + const tangentY = 2 * (1 - t) * (p1[1] - p0[1]) + 2 * t * (p2[1] - p1[1]); + const tangent = vectorNormalize(vector(tangentX, tangentY)); + const normal = vectorNormal(tangent); + + offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point)); + } + + return offsetPoints; +} diff --git a/packages/math/src/index.ts b/packages/math/src/index.ts index d00ab469d7..e487ac3336 100644 --- a/packages/math/src/index.ts +++ b/packages/math/src/index.ts @@ -1,5 +1,6 @@ export * from "./angle"; export * from "./curve"; +export * from "./ellipse"; export * from "./line"; export * from "./point"; export * from "./polygon"; diff --git a/packages/math/src/vector.ts b/packages/math/src/vector.ts index 12682fcd9f..c520fce244 100644 --- a/packages/math/src/vector.ts +++ b/packages/math/src/vector.ts @@ -21,13 +21,23 @@ export function vector( * * @param p The point to turn into a vector * @param origin The origin point in a given coordiante system - * @returns The created vector from the point and the origin + * @param threshold The threshold to consider the vector as 'undefined' + * @param defaultValue The default value to return if the vector is 'undefined' + * @returns The created vector from the point and the origin or default */ export function vectorFromPoint( p: Point, origin: Point = [0, 0] as Point, + threshold?: number, + defaultValue: Vector = [0, 1] as Vector, ): Vector { - return vector(p[0] - origin[0], p[1] - origin[1]); + const vec = vector(p[0] - origin[0], p[1] - origin[1]); + + if (threshold && vectorMagnitudeSq(vec) < threshold * threshold) { + return defaultValue; + } + + return vec; } /** diff --git a/packages/utils/src/collision.ts b/packages/utils/src/collision.ts deleted file mode 100644 index b7c155f663..0000000000 --- a/packages/utils/src/collision.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - lineSegment, - pointFrom, - polygonIncludesPoint, - pointOnLineSegment, - pointOnPolygon, - polygonFromPoints, - type GlobalPoint, - type LocalPoint, - type Polygon, -} from "@excalidraw/math"; - -import type { Curve } from "@excalidraw/math"; - -import { pointInEllipse, pointOnEllipse } from "./shape"; - -import type { Polycurve, Polyline, GeometricShape } from "./shape"; - -// check if the given point is considered on the given shape's border -export const isPointOnShape = ( - point: Point, - shape: GeometricShape, - tolerance = 0, -) => { - // get the distance from the given point to the given element - // check if the distance is within the given epsilon range - switch (shape.type) { - case "polygon": - return pointOnPolygon(point, shape.data, tolerance); - case "ellipse": - return pointOnEllipse(point, shape.data, tolerance); - case "line": - return pointOnLineSegment(point, shape.data, tolerance); - case "polyline": - return pointOnPolyline(point, shape.data, tolerance); - case "curve": - return pointOnCurve(point, shape.data, tolerance); - case "polycurve": - return pointOnPolycurve(point, shape.data, tolerance); - default: - throw Error(`shape ${shape} is not implemented`); - } -}; - -// check if the given point is considered inside the element's border -export const isPointInShape = ( - point: Point, - shape: GeometricShape, -) => { - switch (shape.type) { - case "polygon": - return polygonIncludesPoint(point, shape.data); - case "line": - return false; - case "curve": - return false; - case "ellipse": - return pointInEllipse(point, shape.data); - case "polyline": { - const polygon = polygonFromPoints(shape.data.flat()); - return polygonIncludesPoint(point, polygon); - } - case "polycurve": { - return false; - } - default: - throw Error(`shape ${shape} is not implemented`); - } -}; - -// check if the given element is in the given bounds -export const isPointInBounds = ( - point: Point, - bounds: Polygon, -) => { - return polygonIncludesPoint(point, bounds); -}; - -const pointOnPolycurve = ( - point: Point, - polycurve: Polycurve, - tolerance: number, -) => { - return polycurve.some((curve) => pointOnCurve(point, curve, tolerance)); -}; - -const cubicBezierEquation = ( - curve: Curve, -) => { - const [p0, p1, p2, p3] = curve; - // B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3 - return (t: number, idx: number) => - Math.pow(1 - t, 3) * p3[idx] + - 3 * t * Math.pow(1 - t, 2) * p2[idx] + - 3 * Math.pow(t, 2) * (1 - t) * p1[idx] + - p0[idx] * Math.pow(t, 3); -}; - -const polyLineFromCurve = ( - curve: Curve, - segments = 10, -): Polyline => { - const equation = cubicBezierEquation(curve); - let startingPoint = [equation(0, 0), equation(0, 1)] as Point; - const lineSegments: Polyline = []; - let t = 0; - const increment = 1 / segments; - - for (let i = 0; i < segments; i++) { - t += increment; - if (t <= 1) { - const nextPoint: Point = pointFrom(equation(t, 0), equation(t, 1)); - lineSegments.push(lineSegment(startingPoint, nextPoint)); - startingPoint = nextPoint; - } - } - - return lineSegments; -}; - -export const pointOnCurve = ( - point: Point, - curve: Curve, - threshold: number, -) => { - return pointOnPolyline(point, polyLineFromCurve(curve), threshold); -}; - -export const pointOnPolyline = ( - point: Point, - polyline: Polyline, - threshold = 10e-5, -) => { - return polyline.some((line) => pointOnLineSegment(point, line, threshold)); -}; diff --git a/packages/utils/tests/__snapshots__/export.test.ts.snap b/packages/utils/tests/__snapshots__/export.test.ts.snap index 2c22874a9b..8307e5a543 100644 --- a/packages/utils/tests/__snapshots__/export.test.ts.snap +++ b/packages/utils/tests/__snapshots__/export.test.ts.snap @@ -24,7 +24,7 @@ exports[`exportToSvg > with default arguments 1`] = ` "currentItemFontSize": 20, "currentItemOpacity": 100, "currentItemRoughness": 1, - "currentItemRoundness": "round", + "currentItemRoundness": "sharp", "currentItemStartArrowhead": null, "currentItemStrokeColor": "#1e1e1e", "currentItemStrokeStyle": "solid", diff --git a/packages/utils/tests/collision.test.ts b/packages/utils/tests/collision.test.ts deleted file mode 100644 index 35bc28b34e..0000000000 --- a/packages/utils/tests/collision.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - curve, - degreesToRadians, - lineSegment, - lineSegmentRotate, - pointFrom, - pointRotateDegs, -} from "@excalidraw/math"; - -import type { Curve, Degrees, GlobalPoint } from "@excalidraw/math"; - -import { pointOnCurve, pointOnPolyline } from "../src/collision"; - -import type { Polyline } from "../src/shape"; - -describe("point and curve", () => { - const c: Curve = curve( - pointFrom(1.4, 1.65), - pointFrom(1.9, 7.9), - pointFrom(5.9, 1.65), - pointFrom(6.44, 4.84), - ); - - it("point on curve", () => { - expect(pointOnCurve(c[0], c, 10e-5)).toBe(true); - expect(pointOnCurve(c[3], c, 10e-5)).toBe(true); - - expect(pointOnCurve(pointFrom(2, 4), c, 0.1)).toBe(true); - expect(pointOnCurve(pointFrom(4, 4.4), c, 0.1)).toBe(true); - expect(pointOnCurve(pointFrom(5.6, 3.85), c, 0.1)).toBe(true); - - expect(pointOnCurve(pointFrom(5.6, 4), c, 0.1)).toBe(false); - expect(pointOnCurve(c[1], c, 0.1)).toBe(false); - expect(pointOnCurve(c[2], c, 0.1)).toBe(false); - }); -}); - -describe("point and polylines", () => { - const polyline: Polyline = [ - lineSegment(pointFrom(1, 0), pointFrom(1, 2)), - lineSegment(pointFrom(1, 2), pointFrom(2, 2)), - lineSegment(pointFrom(2, 2), pointFrom(2, 1)), - lineSegment(pointFrom(2, 1), pointFrom(3, 1)), - ]; - - it("point on the line", () => { - expect(pointOnPolyline(pointFrom(1, 0), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(1, 2), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(2, 2), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(2, 1), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(3, 1), polyline)).toBe(true); - - expect(pointOnPolyline(pointFrom(1, 1), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(2, 1.5), polyline)).toBe(true); - expect(pointOnPolyline(pointFrom(2.5, 1), polyline)).toBe(true); - - expect(pointOnPolyline(pointFrom(0, 1), polyline)).toBe(false); - expect(pointOnPolyline(pointFrom(2.1, 1.5), polyline)).toBe(false); - }); - - it("point on the line with rotation", () => { - const truePoints = [ - pointFrom(1, 0), - pointFrom(1, 2), - pointFrom(2, 2), - pointFrom(2, 1), - pointFrom(3, 1), - ]; - - truePoints.forEach((p) => { - const rotation = (Math.random() * 360) as Degrees; - const rotatedPoint = pointRotateDegs(p, pointFrom(0, 0), rotation); - const rotatedPolyline = polyline.map((line) => - lineSegmentRotate(line, degreesToRadians(rotation), pointFrom(0, 0)), - ); - expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(true); - }); - - const falsePoints = [pointFrom(0, 1), pointFrom(2.1, 1.5)]; - - falsePoints.forEach((p) => { - const rotation = (Math.random() * 360) as Degrees; - const rotatedPoint = pointRotateDegs(p, pointFrom(0, 0), rotation); - const rotatedPolyline = polyline.map((line) => - lineSegmentRotate(line, degreesToRadians(rotation), pointFrom(0, 0)), - ); - expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(false); - }); - }); -});