2024-07-01 06:53:53 +08:00
|
|
|
import { useTransform } from "../hooks";
|
2024-07-18 03:45:51 +08:00
|
|
|
import { createContext, useCallback, useMemo, useRef, useState } from "react";
|
2024-07-01 06:53:53 +08:00
|
|
|
import { useEventListener, useResizeObserver } from "usehooks-ts";
|
|
|
|
|
|
|
|
export const CanvasContext = createContext({
|
|
|
|
canvas: {
|
|
|
|
screenSize: {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
},
|
|
|
|
viewBox: new DOMRect(),
|
|
|
|
},
|
|
|
|
coords: {
|
|
|
|
toDiagramSpace(coords) {
|
|
|
|
return coords;
|
|
|
|
},
|
|
|
|
toScreenSpace(coords) {
|
|
|
|
return coords;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pointer: {
|
|
|
|
spaces: {
|
|
|
|
screen: {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
},
|
|
|
|
diagram: {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
style: "default",
|
|
|
|
setStyle() {},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2024-07-18 03:45:51 +08:00
|
|
|
export function CanvasContextProvider({ children, ...attrs }) {
|
|
|
|
const canvasWrapRef = useRef(null);
|
2024-07-01 06:53:53 +08:00
|
|
|
const { transform } = useTransform();
|
|
|
|
const canvasSize = useResizeObserver({
|
2024-07-18 03:45:51 +08:00
|
|
|
ref: canvasWrapRef,
|
2024-07-01 06:53:53 +08:00
|
|
|
box: "content-box",
|
|
|
|
});
|
|
|
|
const screenSize = useMemo(
|
|
|
|
() => ({
|
|
|
|
x: canvasSize.width ?? 0,
|
|
|
|
y: canvasSize.height ?? 0,
|
|
|
|
}),
|
|
|
|
[canvasSize.height, canvasSize.width],
|
|
|
|
);
|
|
|
|
const viewBoxSize = useMemo(
|
|
|
|
() => ({
|
|
|
|
x: screenSize.x / transform.zoom,
|
|
|
|
y: screenSize.y / transform.zoom,
|
|
|
|
}),
|
|
|
|
[screenSize.x, screenSize.y, transform.zoom],
|
|
|
|
);
|
|
|
|
const viewBox = useMemo(
|
|
|
|
() =>
|
|
|
|
new DOMRect(
|
|
|
|
transform.pan.x - viewBoxSize.x / 2,
|
|
|
|
transform.pan.y - viewBoxSize.y / 2,
|
|
|
|
viewBoxSize.x,
|
|
|
|
viewBoxSize.y,
|
|
|
|
),
|
|
|
|
[transform.pan.x, transform.pan.y, viewBoxSize.x, viewBoxSize.y],
|
|
|
|
);
|
|
|
|
|
|
|
|
const toDiagramSpace = useCallback(
|
|
|
|
(coord) => ({
|
|
|
|
x:
|
|
|
|
typeof coord.x === "number"
|
|
|
|
? (coord.x / screenSize.x) * viewBox.width + viewBox.left
|
|
|
|
: undefined,
|
|
|
|
y:
|
|
|
|
typeof coord.y === "number"
|
|
|
|
? (coord.y / screenSize.y) * viewBox.height + viewBox.top
|
|
|
|
: undefined,
|
|
|
|
}),
|
|
|
|
[
|
|
|
|
screenSize.x,
|
|
|
|
screenSize.y,
|
|
|
|
viewBox.height,
|
|
|
|
viewBox.left,
|
|
|
|
viewBox.top,
|
|
|
|
viewBox.width,
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
const toScreenSpace = useCallback(
|
|
|
|
(coord) => ({
|
|
|
|
x:
|
|
|
|
typeof coord.x === "number"
|
|
|
|
? ((coord.x - viewBox.left) / viewBox.width) * screenSize.x
|
|
|
|
: undefined,
|
|
|
|
y:
|
|
|
|
typeof coord.y === "number"
|
|
|
|
? ((coord.y - viewBox.top) / viewBox.height) * screenSize.y
|
|
|
|
: undefined,
|
|
|
|
}),
|
|
|
|
[
|
|
|
|
screenSize.x,
|
|
|
|
screenSize.y,
|
|
|
|
viewBox.height,
|
|
|
|
viewBox.left,
|
|
|
|
viewBox.top,
|
|
|
|
viewBox.width,
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
const [pointerScreenCoords, setPointerScreenCoords] = useState({
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
});
|
|
|
|
const pointerDiagramCoords = useMemo(
|
|
|
|
() => toDiagramSpace(pointerScreenCoords),
|
|
|
|
[pointerScreenCoords, toDiagramSpace],
|
|
|
|
);
|
|
|
|
const [pointerStyle, setPointerStyle] = useState("default");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {PointerEvent} e
|
|
|
|
*/
|
|
|
|
function detectPointerMovement(e) {
|
|
|
|
const targetElm = /** @type {HTMLElement | null} */ (e.currentTarget);
|
|
|
|
if (!e.isPrimary || !targetElm) return;
|
|
|
|
|
|
|
|
const canvasBounds = targetElm.getBoundingClientRect();
|
|
|
|
|
|
|
|
setPointerScreenCoords({
|
|
|
|
x: e.clientX - canvasBounds.left,
|
|
|
|
y: e.clientY - canvasBounds.top,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Important for touch screen devices!
|
2024-07-18 03:45:51 +08:00
|
|
|
useEventListener("pointerdown", detectPointerMovement, canvasWrapRef);
|
2024-07-01 06:53:53 +08:00
|
|
|
|
2024-07-18 03:45:51 +08:00
|
|
|
useEventListener("pointermove", detectPointerMovement, canvasWrapRef);
|
2024-07-01 06:53:53 +08:00
|
|
|
|
2024-07-18 03:45:51 +08:00
|
|
|
const contextValue = {
|
2024-07-01 06:53:53 +08:00
|
|
|
canvas: {
|
|
|
|
screenSize,
|
|
|
|
viewBox,
|
|
|
|
},
|
|
|
|
coords: {
|
|
|
|
toDiagramSpace,
|
|
|
|
toScreenSpace,
|
|
|
|
},
|
|
|
|
pointer: {
|
|
|
|
spaces: {
|
|
|
|
screen: pointerScreenCoords,
|
|
|
|
diagram: pointerDiagramCoords,
|
|
|
|
},
|
|
|
|
style: pointerStyle,
|
|
|
|
setStyle: setPointerStyle,
|
|
|
|
},
|
|
|
|
};
|
2024-07-18 03:45:51 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<CanvasContext.Provider value={contextValue}>
|
|
|
|
<div {...attrs} ref={canvasWrapRef}>
|
|
|
|
{children}
|
|
|
|
</div>
|
|
|
|
</CanvasContext.Provider>
|
|
|
|
)
|
2024-07-01 06:53:53 +08:00
|
|
|
}
|