|
|
@ -305,38 +305,42 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type ElementSelectionBorder = {
|
|
|
|
|
|
|
|
angle: number;
|
|
|
|
|
|
|
|
x1: number;
|
|
|
|
|
|
|
|
y1: number;
|
|
|
|
|
|
|
|
x2: number;
|
|
|
|
|
|
|
|
y2: number;
|
|
|
|
|
|
|
|
selectionColors: string[];
|
|
|
|
|
|
|
|
dashed?: boolean;
|
|
|
|
|
|
|
|
cx: number;
|
|
|
|
|
|
|
|
cy: number;
|
|
|
|
|
|
|
|
activeEmbeddable: boolean;
|
|
|
|
|
|
|
|
padding?: number;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderSelectionBorder = (
|
|
|
|
const renderSelectionBorder = (
|
|
|
|
context: CanvasRenderingContext2D,
|
|
|
|
context: CanvasRenderingContext2D,
|
|
|
|
appState: InteractiveCanvasAppState,
|
|
|
|
appState: InteractiveCanvasAppState,
|
|
|
|
elementProperties: {
|
|
|
|
elementProperties: ElementSelectionBorder,
|
|
|
|
angle: number;
|
|
|
|
|
|
|
|
elementX1: number;
|
|
|
|
|
|
|
|
elementY1: number;
|
|
|
|
|
|
|
|
elementX2: number;
|
|
|
|
|
|
|
|
elementY2: number;
|
|
|
|
|
|
|
|
selectionColors: string[];
|
|
|
|
|
|
|
|
dashed?: boolean;
|
|
|
|
|
|
|
|
cx: number;
|
|
|
|
|
|
|
|
cy: number;
|
|
|
|
|
|
|
|
activeEmbeddable: boolean;
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
) => {
|
|
|
|
) => {
|
|
|
|
const {
|
|
|
|
const {
|
|
|
|
angle,
|
|
|
|
angle,
|
|
|
|
elementX1,
|
|
|
|
x1,
|
|
|
|
elementY1,
|
|
|
|
y1,
|
|
|
|
elementX2,
|
|
|
|
x2,
|
|
|
|
elementY2,
|
|
|
|
y2,
|
|
|
|
selectionColors,
|
|
|
|
selectionColors,
|
|
|
|
cx,
|
|
|
|
cx,
|
|
|
|
cy,
|
|
|
|
cy,
|
|
|
|
dashed,
|
|
|
|
dashed,
|
|
|
|
activeEmbeddable,
|
|
|
|
activeEmbeddable,
|
|
|
|
} = elementProperties;
|
|
|
|
} = elementProperties;
|
|
|
|
const elementWidth = elementX2 - elementX1;
|
|
|
|
const elementWidth = x2 - x1;
|
|
|
|
const elementHeight = elementY2 - elementY1;
|
|
|
|
const elementHeight = y2 - y1;
|
|
|
|
|
|
|
|
|
|
|
|
const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
|
|
|
const padding =
|
|
|
|
|
|
|
|
elementProperties.padding ?? DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
|
|
|
|
|
|
|
|
|
|
|
const linePadding = padding / appState.zoom.value;
|
|
|
|
const linePadding = padding / appState.zoom.value;
|
|
|
|
const lineWidth = 8 / appState.zoom.value;
|
|
|
|
const lineWidth = 8 / appState.zoom.value;
|
|
|
@ -358,8 +362,8 @@ const renderSelectionBorder = (
|
|
|
|
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
|
|
|
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
|
|
|
strokeRectWithRotation(
|
|
|
|
strokeRectWithRotation(
|
|
|
|
context,
|
|
|
|
context,
|
|
|
|
elementX1 - linePadding,
|
|
|
|
x1 - linePadding,
|
|
|
|
elementY1 - linePadding,
|
|
|
|
y1 - linePadding,
|
|
|
|
elementWidth + linePadding * 2,
|
|
|
|
elementWidth + linePadding * 2,
|
|
|
|
elementHeight + linePadding * 2,
|
|
|
|
elementHeight + linePadding * 2,
|
|
|
|
cx,
|
|
|
|
cx,
|
|
|
@ -431,18 +435,17 @@ const renderElementsBoxHighlight = (
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
|
|
|
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
|
|
|
const [elementX1, elementY1, elementX2, elementY2] =
|
|
|
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
|
|
|
getCommonBounds(elements);
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
angle: 0,
|
|
|
|
angle: 0,
|
|
|
|
elementX1,
|
|
|
|
x1,
|
|
|
|
elementX2,
|
|
|
|
x2,
|
|
|
|
elementY1,
|
|
|
|
y1,
|
|
|
|
elementY2,
|
|
|
|
y2,
|
|
|
|
selectionColors: ["rgb(0,118,255)"],
|
|
|
|
selectionColors: ["rgb(0,118,255)"],
|
|
|
|
dashed: false,
|
|
|
|
dashed: false,
|
|
|
|
cx: elementX1 + (elementX2 - elementX1) / 2,
|
|
|
|
cx: x1 + (x2 - x1) / 2,
|
|
|
|
cy: elementY1 + (elementY2 - elementY1) / 2,
|
|
|
|
cy: y1 + (y2 - y1) / 2,
|
|
|
|
activeEmbeddable: false,
|
|
|
|
activeEmbeddable: false,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -599,24 +602,33 @@ const renderCropHandles = (
|
|
|
|
croppingElement: ExcalidrawImageElement,
|
|
|
|
croppingElement: ExcalidrawImageElement,
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
): void => {
|
|
|
|
): void => {
|
|
|
|
const lineWidth = 3 / appState.zoom.value;
|
|
|
|
|
|
|
|
const length = 15 / appState.zoom.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
|
|
|
|
const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
|
|
|
|
croppingElement,
|
|
|
|
croppingElement,
|
|
|
|
elementsMap,
|
|
|
|
elementsMap,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
const halfWidth =
|
|
|
|
|
|
|
|
cx - x1 + (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
|
|
|
const LINE_WIDTH = 3;
|
|
|
|
const halfHeight =
|
|
|
|
const LINE_LENGTH = 20;
|
|
|
|
cy - y1 + (DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
|
|
|
|
|
|
|
|
|
|
|
const ZOOMED_LINE_WIDTH = LINE_WIDTH / appState.zoom.value;
|
|
|
|
|
|
|
|
const ZOOMED_HALF_LINE_WIDTH = ZOOMED_LINE_WIDTH / 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const HALF_WIDTH = cx - x1 + ZOOMED_LINE_WIDTH;
|
|
|
|
|
|
|
|
const HALF_HEIGHT = cy - y1 + ZOOMED_LINE_WIDTH;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const HORIZONTAL_LINE_LENGTH = Math.min(
|
|
|
|
|
|
|
|
LINE_LENGTH / appState.zoom.value,
|
|
|
|
|
|
|
|
HALF_WIDTH,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
const VERTICAL_LINE_LENGTH = Math.min(
|
|
|
|
|
|
|
|
LINE_LENGTH / appState.zoom.value,
|
|
|
|
|
|
|
|
HALF_HEIGHT,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
context.save();
|
|
|
|
context.save();
|
|
|
|
context.fillStyle = renderConfig.selectionColor;
|
|
|
|
context.fillStyle = renderConfig.selectionColor;
|
|
|
|
context.strokeStyle = renderConfig.selectionColor;
|
|
|
|
context.strokeStyle = renderConfig.selectionColor;
|
|
|
|
context.lineWidth = lineWidth;
|
|
|
|
context.lineWidth = ZOOMED_LINE_WIDTH;
|
|
|
|
|
|
|
|
|
|
|
|
const halfLineWidth = lineWidth / 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handles: Array<
|
|
|
|
const handles: Array<
|
|
|
|
[
|
|
|
|
[
|
|
|
@ -629,34 +641,40 @@ const renderCropHandles = (
|
|
|
|
> = [
|
|
|
|
> = [
|
|
|
|
[
|
|
|
|
[
|
|
|
|
// x, y
|
|
|
|
// x, y
|
|
|
|
[-halfWidth, -halfHeight],
|
|
|
|
[-HALF_WIDTH, -HALF_HEIGHT],
|
|
|
|
// first start and t0
|
|
|
|
// horizontal line: first start and to
|
|
|
|
[0, halfLineWidth],
|
|
|
|
[0, ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
[length, halfLineWidth],
|
|
|
|
[HORIZONTAL_LINE_LENGTH, ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
// second start and to
|
|
|
|
// vertical line: second start and to
|
|
|
|
[halfLineWidth, 0],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
|
|
|
[halfLineWidth, length - halfLineWidth],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, VERTICAL_LINE_LENGTH],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[halfWidth - halfLineWidth, -halfHeight + halfLineWidth],
|
|
|
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, -HALF_HEIGHT],
|
|
|
|
[halfLineWidth, 0],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
[-length + halfLineWidth, 0],
|
|
|
|
[
|
|
|
|
[0, -halfLineWidth],
|
|
|
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
|
|
|
[0, length - lineWidth],
|
|
|
|
ZOOMED_HALF_LINE_WIDTH,
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
[0, 0],
|
|
|
|
|
|
|
|
[0, VERTICAL_LINE_LENGTH],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[-halfWidth, halfHeight],
|
|
|
|
[-HALF_WIDTH, HALF_HEIGHT],
|
|
|
|
[0, -halfLineWidth],
|
|
|
|
[0, -ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
[length, -halfLineWidth],
|
|
|
|
[HORIZONTAL_LINE_LENGTH, -ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
[halfLineWidth, 0],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
|
|
|
[halfLineWidth, -length + halfLineWidth],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, -VERTICAL_LINE_LENGTH],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
[halfWidth - halfLineWidth, halfHeight - halfLineWidth],
|
|
|
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, HALF_HEIGHT],
|
|
|
|
[halfLineWidth, 0],
|
|
|
|
[ZOOMED_HALF_LINE_WIDTH, -ZOOMED_HALF_LINE_WIDTH],
|
|
|
|
[-length + halfLineWidth, 0],
|
|
|
|
[
|
|
|
|
[0, halfLineWidth],
|
|
|
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
|
|
|
[0, -length + lineWidth],
|
|
|
|
-ZOOMED_HALF_LINE_WIDTH,
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
[0, 0],
|
|
|
|
|
|
|
|
[0, -VERTICAL_LINE_LENGTH],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
];
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
@ -871,18 +889,7 @@ const _renderInteractiveScene = ({
|
|
|
|
// Optimisation for finding quickly relevant element ids
|
|
|
|
// Optimisation for finding quickly relevant element ids
|
|
|
|
const locallySelectedIds = arrayToMap(selectedElements);
|
|
|
|
const locallySelectedIds = arrayToMap(selectedElements);
|
|
|
|
|
|
|
|
|
|
|
|
const selections: {
|
|
|
|
const selections: ElementSelectionBorder[] = [];
|
|
|
|
angle: number;
|
|
|
|
|
|
|
|
elementX1: number;
|
|
|
|
|
|
|
|
elementY1: number;
|
|
|
|
|
|
|
|
elementX2: number;
|
|
|
|
|
|
|
|
elementY2: number;
|
|
|
|
|
|
|
|
selectionColors: string[];
|
|
|
|
|
|
|
|
dashed?: boolean;
|
|
|
|
|
|
|
|
cx: number;
|
|
|
|
|
|
|
|
cy: number;
|
|
|
|
|
|
|
|
activeEmbeddable: boolean;
|
|
|
|
|
|
|
|
}[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const element of elementsMap.values()) {
|
|
|
|
for (const element of elementsMap.values()) {
|
|
|
|
const selectionColors = [];
|
|
|
|
const selectionColors = [];
|
|
|
@ -922,14 +929,17 @@ const _renderInteractiveScene = ({
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (selectionColors.length) {
|
|
|
|
if (selectionColors.length) {
|
|
|
|
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
|
|
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
|
|
getElementAbsoluteCoords(element, elementsMap, true);
|
|
|
|
element,
|
|
|
|
|
|
|
|
elementsMap,
|
|
|
|
|
|
|
|
true,
|
|
|
|
|
|
|
|
);
|
|
|
|
selections.push({
|
|
|
|
selections.push({
|
|
|
|
angle: element.angle,
|
|
|
|
angle: element.angle,
|
|
|
|
elementX1,
|
|
|
|
x1,
|
|
|
|
elementY1,
|
|
|
|
y1,
|
|
|
|
elementX2,
|
|
|
|
x2,
|
|
|
|
elementY2,
|
|
|
|
y2,
|
|
|
|
selectionColors,
|
|
|
|
selectionColors,
|
|
|
|
dashed: !!remoteClients,
|
|
|
|
dashed: !!remoteClients,
|
|
|
|
cx,
|
|
|
|
cx,
|
|
|
@ -937,24 +947,25 @@ const _renderInteractiveScene = ({
|
|
|
|
activeEmbeddable:
|
|
|
|
activeEmbeddable:
|
|
|
|
appState.activeEmbeddable?.element === element &&
|
|
|
|
appState.activeEmbeddable?.element === element &&
|
|
|
|
appState.activeEmbeddable.state === "active",
|
|
|
|
appState.activeEmbeddable.state === "active",
|
|
|
|
|
|
|
|
padding:
|
|
|
|
|
|
|
|
element.id === appState.croppingElement?.id ? 0 : undefined,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const addSelectionForGroupId = (groupId: GroupId) => {
|
|
|
|
const addSelectionForGroupId = (groupId: GroupId) => {
|
|
|
|
const groupElements = getElementsInGroup(elementsMap, groupId);
|
|
|
|
const groupElements = getElementsInGroup(elementsMap, groupId);
|
|
|
|
const [elementX1, elementY1, elementX2, elementY2] =
|
|
|
|
const [x1, y1, x2, y2] = getCommonBounds(groupElements);
|
|
|
|
getCommonBounds(groupElements);
|
|
|
|
|
|
|
|
selections.push({
|
|
|
|
selections.push({
|
|
|
|
angle: 0,
|
|
|
|
angle: 0,
|
|
|
|
elementX1,
|
|
|
|
x1,
|
|
|
|
elementX2,
|
|
|
|
x2,
|
|
|
|
elementY1,
|
|
|
|
y1,
|
|
|
|
elementY2,
|
|
|
|
y2,
|
|
|
|
selectionColors: [oc.black],
|
|
|
|
selectionColors: [oc.black],
|
|
|
|
dashed: true,
|
|
|
|
dashed: true,
|
|
|
|
cx: elementX1 + (elementX2 - elementX1) / 2,
|
|
|
|
cx: x1 + (x2 - x1) / 2,
|
|
|
|
cy: elementY1 + (elementY2 - elementY1) / 2,
|
|
|
|
cy: y1 + (y2 - y1) / 2,
|
|
|
|
activeEmbeddable: false,
|
|
|
|
activeEmbeddable: false,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|