mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Unify bounds calculation
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
parent
c0915c6b98
commit
0c47bd0004
9 changed files with 80 additions and 94 deletions
|
@ -1,6 +1,6 @@
|
|||
import { register } from "./register";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||
import type {
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawElbowArrowElement,
|
||||
|
@ -12,7 +12,6 @@ import { resizeMultipleElements } from "../element/resizeElements";
|
|||
import type { AppClassProperties, AppState } from "../types";
|
||||
import { arrayToMap } from "../utils";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import {
|
||||
bindOrUnbindLinearElements,
|
||||
isBindingEnabled,
|
||||
|
@ -132,8 +131,9 @@ const flipElements = (
|
|||
});
|
||||
}
|
||||
|
||||
const { minX, minY, maxX, maxY, midX, midY } =
|
||||
getCommonBoundingBox(selectedElements);
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(selectedElements);
|
||||
const midX = (minX + maxX) / 2;
|
||||
const midY = (minY + maxY) / 2;
|
||||
|
||||
resizeMultipleElements(
|
||||
elementsMap,
|
||||
|
@ -175,8 +175,11 @@ const flipElements = (
|
|||
{ elbowArrows: [], otherElements: [] },
|
||||
);
|
||||
|
||||
const { midX: newMidX, midY: newMidY } =
|
||||
getCommonBoundingBox(selectedElements);
|
||||
const [newMinX, newMinY, newMaxX, newMaxY] =
|
||||
getCommonBounds(selectedElements);
|
||||
const newMidX = (newMinX + newMaxX) / 2;
|
||||
const newMidY = (newMinY + newMaxY) / 2;
|
||||
|
||||
const [diffX, diffY] = [midX - newMidX, midY - newMidY];
|
||||
otherElements.forEach((element) =>
|
||||
mutateElement(element, {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||
import { newElementWith } from "./element/mutateElement";
|
||||
import type { BoundingBox } from "./element/bounds";
|
||||
import { getCommonBoundingBox } from "./element/bounds";
|
||||
import { getCommonBounds, type Bounds } from "./element/bounds";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
|
||||
export interface Alignment {
|
||||
|
@ -18,14 +17,10 @@ export const alignElements = (
|
|||
selectedElements,
|
||||
elementsMap,
|
||||
);
|
||||
const selectionBoundingBox = getCommonBoundingBox(selectedElements);
|
||||
const selectionBounds = getCommonBounds(selectedElements);
|
||||
|
||||
return groups.flatMap((group) => {
|
||||
const translation = calculateTranslation(
|
||||
group,
|
||||
selectionBoundingBox,
|
||||
alignment,
|
||||
);
|
||||
const translation = calculateTranslation(group, selectionBounds, alignment);
|
||||
return group.map((element) =>
|
||||
newElementWith(element, {
|
||||
x: element.x + translation.x,
|
||||
|
@ -37,10 +32,30 @@ export const alignElements = (
|
|||
|
||||
const calculateTranslation = (
|
||||
group: ExcalidrawElement[],
|
||||
selectionBoundingBox: BoundingBox,
|
||||
selectionBounds: Bounds,
|
||||
{ axis, position }: Alignment,
|
||||
): { x: number; y: number } => {
|
||||
const groupBoundingBox = getCommonBoundingBox(group);
|
||||
const selectionBoundingBox = {
|
||||
minX: selectionBounds[0],
|
||||
minY: selectionBounds[1],
|
||||
maxX: selectionBounds[2],
|
||||
maxY: selectionBounds[3],
|
||||
midX: (selectionBounds[0] + selectionBounds[2]) / 2,
|
||||
midY: (selectionBounds[1] + selectionBounds[3]) / 2,
|
||||
width: selectionBounds[2] - selectionBounds[0],
|
||||
height: selectionBounds[3] - selectionBounds[1],
|
||||
};
|
||||
const groupBounds = getCommonBounds(group);
|
||||
const groupBoundingBox = {
|
||||
minX: groupBounds[0],
|
||||
minY: groupBounds[1],
|
||||
maxX: groupBounds[2],
|
||||
maxY: groupBounds[3],
|
||||
midX: (groupBounds[0] + groupBounds[2]) / 2,
|
||||
midY: (groupBounds[1] + groupBounds[3]) / 2,
|
||||
width: groupBounds[2] - groupBounds[0],
|
||||
height: groupBounds[3] - groupBounds[1],
|
||||
};
|
||||
|
||||
const [min, max]: ["minX" | "minY", "maxX" | "maxY"] =
|
||||
axis === "x" ? ["minX", "maxX"] : ["minY", "maxY"];
|
||||
|
|
|
@ -11,7 +11,6 @@ import type App from "../components/App";
|
|||
import { atom } from "jotai";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import { AbortError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
@ -34,7 +33,7 @@ import {
|
|||
import type { MaybePromise } from "../utility-types";
|
||||
import { Emitter } from "../emitter";
|
||||
import { Queue } from "../queue";
|
||||
import { hashElementsVersion, hashString } from "../element";
|
||||
import { getCommonBounds, hashElementsVersion, hashString } from "../element";
|
||||
|
||||
type LibraryUpdate = {
|
||||
/** deleted library items since last onLibraryChange event */
|
||||
|
@ -387,7 +386,8 @@ export const distributeLibraryItemsOnSquareGrid = (
|
|||
const maxHeight = libraryItems
|
||||
.slice(row * ITEMS_PER_ROW, row * ITEMS_PER_ROW + ITEMS_PER_ROW)
|
||||
.reduce((acc, item) => {
|
||||
const { height } = getCommonBoundingBox(item.elements);
|
||||
const bounds = getCommonBounds(item.elements);
|
||||
const height = bounds[3] - bounds[1];
|
||||
return Math.max(acc, height);
|
||||
}, 0);
|
||||
return maxHeight;
|
||||
|
@ -402,7 +402,9 @@ export const distributeLibraryItemsOnSquareGrid = (
|
|||
currCol = 0;
|
||||
}
|
||||
if (currCol === targetCol) {
|
||||
const { width } = getCommonBoundingBox(item.elements);
|
||||
const bounds = getCommonBounds(item.elements);
|
||||
const width = bounds[2] - bounds[0];
|
||||
|
||||
maxWidth = Math.max(maxWidth, width);
|
||||
}
|
||||
index++;
|
||||
|
@ -434,7 +436,10 @@ export const distributeLibraryItemsOnSquareGrid = (
|
|||
}
|
||||
maxWidthCurrCol = getMaxWidthPerCol(col);
|
||||
|
||||
const { minX, minY, width, height } = getCommonBoundingBox(item.elements);
|
||||
const bounds = getCommonBounds(item.elements);
|
||||
const [minX, minY, maxX, maxY] = bounds;
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
const offsetCenterX = (maxWidthCurrCol - width) / 2;
|
||||
const offsetCenterY = (maxHeightCurrRow - height) / 2;
|
||||
resElements.push(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { newElementWith } from "./element/mutateElement";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
import { getCommonBoundingBox } from "./element/bounds";
|
||||
import type { ElementsMap, ExcalidrawElement } from "./element/types";
|
||||
import { getCommonBounds } from "./element/bounds";
|
||||
|
||||
export interface Distribution {
|
||||
space: "between";
|
||||
|
@ -18,9 +18,35 @@ export const distributeElements = (
|
|||
? (["minX", "midX", "maxX", "width"] as const)
|
||||
: (["minY", "midY", "maxY", "height"] as const);
|
||||
|
||||
const bounds = getCommonBoundingBox(selectedElements);
|
||||
const bb = getCommonBounds(selectedElements);
|
||||
const bounds = {
|
||||
minX: bb[0],
|
||||
minY: bb[1],
|
||||
maxX: bb[2],
|
||||
maxY: bb[3],
|
||||
midX: (bb[0] + bb[2]) / 2,
|
||||
midY: (bb[1] + bb[3]) / 2,
|
||||
width: bb[2] - bb[0],
|
||||
height: bb[3] - bb[1],
|
||||
};
|
||||
const groups = getMaximumGroups(selectedElements, elementsMap)
|
||||
.map((group) => [group, getCommonBoundingBox(group)] as const)
|
||||
.map((group) => {
|
||||
const bounds = getCommonBounds(group);
|
||||
|
||||
return [
|
||||
group,
|
||||
{
|
||||
minX: bounds[0],
|
||||
minY: bounds[1],
|
||||
maxX: bounds[2],
|
||||
maxY: bounds[3],
|
||||
midX: (bounds[0] + bounds[2]) / 2,
|
||||
midY: (bounds[1] + bounds[3]) / 2,
|
||||
width: bounds[2] - bounds[0],
|
||||
height: bounds[3] - bounds[1],
|
||||
},
|
||||
] as const;
|
||||
})
|
||||
.sort((a, b) => a[1][mid] - b[1][mid]);
|
||||
|
||||
let span = 0;
|
||||
|
|
|
@ -2,7 +2,6 @@ import type {
|
|||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
NonDeleted,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ElementsMap,
|
||||
} from "./types";
|
||||
|
@ -643,33 +642,6 @@ export const getClosestElementBounds = (
|
|||
return getElementBounds(closestElement, elementsMap);
|
||||
};
|
||||
|
||||
export interface BoundingBox {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const getCommonBoundingBox = (
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
): BoundingBox => {
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
return {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
midX: (minX + maxX) / 2,
|
||||
midY: (minY + maxY) / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* returns scene coords of user's editor viewport (visible canvas area) bounds
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
getElementAbsoluteCoords,
|
||||
getCommonBounds,
|
||||
getResizedElementAbsoluteCoords,
|
||||
getCommonBoundingBox,
|
||||
} from "./bounds";
|
||||
import {
|
||||
isArrowElement,
|
||||
|
@ -808,9 +807,11 @@ export const resizeMultipleElements = (
|
|||
return [...acc, { ...text, ...xy }];
|
||||
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||
|
||||
const { minX, minY, maxX, maxY, midX, midY } = getCommonBoundingBox(
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(
|
||||
targetElements.map(({ orig }) => orig).concat(boundTextElements),
|
||||
);
|
||||
const midX = (minX + maxX) / 2;
|
||||
const midY = (minY + maxY) / 2;
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { vi } from "vitest";
|
|||
import { fireEvent, render, waitFor } from "./test-utils";
|
||||
import { act, queryByTestId } from "@testing-library/react";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { Excalidraw, getCommonBounds } from "../index";
|
||||
import { API } from "./helpers/api";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import type { LibraryItem, LibraryItems } from "../types";
|
||||
|
@ -11,7 +11,6 @@ import { UI } from "./helpers/ui";
|
|||
import { serializeLibraryAsJSON } from "../data/json";
|
||||
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||
import type { ExcalidrawGenericElement } from "../element/types";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import { parseLibraryJSON } from "../data/blob";
|
||||
|
||||
const { h } = window;
|
||||
|
@ -294,6 +293,7 @@ describe("distributeLibraryItemsOnSquareGrid()", () => {
|
|||
expect(distributed.length).toEqual(
|
||||
libraryItems.map((x) => x.elements).flat().length,
|
||||
);
|
||||
const el4_el5_bounds = getCommonBounds([el4, el5]);
|
||||
expect(distributed).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
|
@ -306,7 +306,7 @@ describe("distributeLibraryItemsOnSquareGrid()", () => {
|
|||
x:
|
||||
el1.width +
|
||||
PADDING +
|
||||
(getCommonBoundingBox([el4, el5]).width - el2.width) / 2,
|
||||
(el4_el5_bounds[2] - el4_el5_bounds[0] - el2.width) / 2,
|
||||
y: Math.abs(el1.height - el2.height) / 2,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { LineSegment } from "../math";
|
||||
import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math";
|
||||
import type { BoundingBox, Bounds } from "./element/bounds";
|
||||
import type { Bounds } from "./element/bounds";
|
||||
import { isBounds } from "./element/typeChecks";
|
||||
|
||||
// The global data holder to collect the debug operations
|
||||
|
@ -72,41 +72,6 @@ export const debugDrawPoint = (
|
|||
);
|
||||
};
|
||||
|
||||
export const debugDrawBoundingBox = (
|
||||
box: BoundingBox | BoundingBox[],
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
(Array.isArray(box) ? box : [box]).forEach((bbox) =>
|
||||
debugDrawLine(
|
||||
[
|
||||
lineSegment(
|
||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||
),
|
||||
lineSegment(
|
||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||
),
|
||||
lineSegment(
|
||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||
),
|
||||
lineSegment(
|
||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
||||
),
|
||||
],
|
||||
{
|
||||
color: opts?.color ?? "cyan",
|
||||
permanent: opts?.permanent,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const debugDrawBounds = (
|
||||
box: Bounds | Bounds[],
|
||||
opts?: {
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./export";
|
||||
export * from "./withinBounds";
|
||||
export { getCommonBounds } from "../excalidraw/element/bounds";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue