mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-23 16:04:33 +02:00
feat: Add binding visual debug
This commit is contained in:
@@ -8,9 +8,16 @@ import {
|
|||||||
getNormalizedCanvasDimensions,
|
getNormalizedCanvasDimensions,
|
||||||
} from "@excalidraw/excalidraw/renderer/helpers";
|
} from "@excalidraw/excalidraw/renderer/helpers";
|
||||||
import { type AppState } from "@excalidraw/excalidraw/types";
|
import { type AppState } from "@excalidraw/excalidraw/types";
|
||||||
import { throttleRAF } from "@excalidraw/common";
|
import { arrayToMap, throttleRAF } from "@excalidraw/common";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getGlobalFixedPointForBindableElement,
|
||||||
|
isArrowElement,
|
||||||
|
isBindableElement,
|
||||||
|
isFixedPointBinding,
|
||||||
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isLineSegment,
|
isLineSegment,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
@@ -21,8 +28,15 @@ import { isCurve } from "@excalidraw/math/curve";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import type { Curve } from "@excalidraw/math";
|
import type { Curve } from "@excalidraw/math";
|
||||||
|
import type { DebugElement } from "@excalidraw/common";
|
||||||
import type { DebugElement } from "@excalidraw/utils/visualdebug";
|
import type {
|
||||||
|
ElementsMap,
|
||||||
|
ExcalidrawArrowElement,
|
||||||
|
ExcalidrawBindableElement,
|
||||||
|
FixedPointBinding,
|
||||||
|
OrderedExcalidrawElement,
|
||||||
|
PointBinding,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
@@ -75,6 +89,180 @@ const renderOrigin = (context: CanvasRenderingContext2D, zoom: number) => {
|
|||||||
context.save();
|
context.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _renderBinding = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
binding: FixedPointBinding | PointBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
zoom: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
color: string,
|
||||||
|
) => {
|
||||||
|
if (isFixedPointBinding(binding)) {
|
||||||
|
if (!binding.fixedPoint) {
|
||||||
|
console.warn("Binding must have a fixedPoint");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindable = elementsMap.get(
|
||||||
|
binding.elementId,
|
||||||
|
) as ExcalidrawBindableElement;
|
||||||
|
const [x, y] = getGlobalFixedPointForBindableElement(
|
||||||
|
binding.fixedPoint,
|
||||||
|
bindable,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.strokeStyle = color;
|
||||||
|
context.lineWidth = 1;
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x * zoom, y * zoom);
|
||||||
|
context.bezierCurveTo(
|
||||||
|
x * zoom - width,
|
||||||
|
y * zoom - height,
|
||||||
|
x * zoom - width,
|
||||||
|
y * zoom + height,
|
||||||
|
x * zoom,
|
||||||
|
y * zoom,
|
||||||
|
);
|
||||||
|
context.stroke();
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _renderBindableBinding = (
|
||||||
|
binding: FixedPointBinding | PointBinding,
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
zoom: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
color: string,
|
||||||
|
) => {
|
||||||
|
if (isFixedPointBinding(binding)) {
|
||||||
|
const bindable = elementsMap.get(
|
||||||
|
binding.elementId,
|
||||||
|
) as ExcalidrawBindableElement;
|
||||||
|
if (!binding.fixedPoint) {
|
||||||
|
console.warn("Binding must have a fixedPoint");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [x, y] = getGlobalFixedPointForBindableElement(
|
||||||
|
binding.fixedPoint,
|
||||||
|
bindable,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.strokeStyle = color;
|
||||||
|
context.lineWidth = 1;
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x * zoom, y * zoom);
|
||||||
|
context.bezierCurveTo(
|
||||||
|
x * zoom + width,
|
||||||
|
y * zoom + height,
|
||||||
|
x * zoom + width,
|
||||||
|
y * zoom - height,
|
||||||
|
x * zoom,
|
||||||
|
y * zoom,
|
||||||
|
);
|
||||||
|
context.stroke();
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderBindings = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
elements: readonly OrderedExcalidrawElement[],
|
||||||
|
zoom: number,
|
||||||
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
const dim = 16;
|
||||||
|
elements.forEach((element) => {
|
||||||
|
if (element.isDeleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArrowElement(element)) {
|
||||||
|
if (element.startBinding) {
|
||||||
|
if (
|
||||||
|
!elementsMap
|
||||||
|
.get(element.startBinding.elementId)
|
||||||
|
?.boundElements?.find((e) => e.id === element.id)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderBinding(
|
||||||
|
context,
|
||||||
|
element.startBinding as FixedPointBinding,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
dim,
|
||||||
|
dim,
|
||||||
|
"red",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.endBinding) {
|
||||||
|
if (
|
||||||
|
!elementsMap
|
||||||
|
.get(element.endBinding.elementId)
|
||||||
|
?.boundElements?.find((e) => e.id === element.id)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_renderBinding(
|
||||||
|
context,
|
||||||
|
element.endBinding,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
dim,
|
||||||
|
dim,
|
||||||
|
"red",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBindableElement(element) && element.boundElements?.length) {
|
||||||
|
element.boundElements.forEach((boundElement) => {
|
||||||
|
if (boundElement.type !== "arrow") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrow = elementsMap.get(
|
||||||
|
boundElement.id,
|
||||||
|
) as ExcalidrawArrowElement;
|
||||||
|
|
||||||
|
if (arrow && arrow.startBinding?.elementId === element.id) {
|
||||||
|
_renderBindableBinding(
|
||||||
|
arrow.startBinding,
|
||||||
|
context,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
dim,
|
||||||
|
dim,
|
||||||
|
"green",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (arrow && arrow.endBinding?.elementId === element.id) {
|
||||||
|
_renderBindableBinding(
|
||||||
|
arrow.endBinding,
|
||||||
|
context,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
dim,
|
||||||
|
dim,
|
||||||
|
"green",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const render = (
|
const render = (
|
||||||
frame: DebugElement[],
|
frame: DebugElement[],
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@@ -107,8 +295,8 @@ const render = (
|
|||||||
const _debugRenderer = (
|
const _debugRenderer = (
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elements: readonly OrderedExcalidrawElement[],
|
||||||
scale: number,
|
scale: number,
|
||||||
refresh: () => void,
|
|
||||||
) => {
|
) => {
|
||||||
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
||||||
canvas,
|
canvas,
|
||||||
@@ -131,6 +319,7 @@ const _debugRenderer = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
renderOrigin(context, appState.zoom.value);
|
renderOrigin(context, appState.zoom.value);
|
||||||
|
renderBindings(context, elements, appState.zoom.value);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
window.visualDebug?.currentFrame &&
|
window.visualDebug?.currentFrame &&
|
||||||
@@ -182,10 +371,10 @@ export const debugRenderer = throttleRAF(
|
|||||||
(
|
(
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elements: readonly OrderedExcalidrawElement[],
|
||||||
scale: number,
|
scale: number,
|
||||||
refresh: () => void,
|
|
||||||
) => {
|
) => {
|
||||||
_debugRenderer(canvas, appState, scale, refresh);
|
_debugRenderer(canvas, appState, elements, scale);
|
||||||
},
|
},
|
||||||
{ trailing: true },
|
{ trailing: true },
|
||||||
);
|
);
|
||||||
|
@@ -10,3 +10,4 @@ export * from "./random";
|
|||||||
export * from "./url";
|
export * from "./url";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * from "./emitter";
|
export * from "./emitter";
|
||||||
|
export * from "./visualdebug";
|
||||||
|
Reference in New Issue
Block a user