@canvas-harness/core 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +211 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +211 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1454,9 +1454,33 @@ type InteractionState = {
|
|
|
1454
1454
|
resizeLockAspect: boolean;
|
|
1455
1455
|
/** Whether the user is holding Alt during a resize (resize from center). */
|
|
1456
1456
|
resizeFromCenter: boolean;
|
|
1457
|
+
/**
|
|
1458
|
+
* Live in-progress geometry of the resized node — written every
|
|
1459
|
+
* pointermove, committed to the store once on pointer-up. While
|
|
1460
|
+
* present, `store.getNode(id)` still returns the original geometry;
|
|
1461
|
+
* the renderer overlays this draft via `mapDragPositions` for the
|
|
1462
|
+
* interactive layer paint. Mirrors how `dragDelta` works for drag.
|
|
1463
|
+
*/
|
|
1464
|
+
resizeDraft: {
|
|
1465
|
+
x: number;
|
|
1466
|
+
y: number;
|
|
1467
|
+
w: number;
|
|
1468
|
+
h: number;
|
|
1469
|
+
angle: number;
|
|
1470
|
+
} | null;
|
|
1457
1471
|
marqueeRect: WorldRect | null;
|
|
1458
1472
|
/** Whether the marquee should add to (true, shift held) or replace selection. */
|
|
1459
1473
|
marqueeAdditive: boolean;
|
|
1474
|
+
/**
|
|
1475
|
+
* Live in-progress cubic controls of an edge being mid-point-dragged.
|
|
1476
|
+
* Written every pointermove, committed to the store once on
|
|
1477
|
+
* pointer-up. Same draft+commit model as `resizeDraft` and
|
|
1478
|
+
* `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
|
|
1479
|
+
*/
|
|
1480
|
+
midpointDraft: {
|
|
1481
|
+
edgeId: EdgeId;
|
|
1482
|
+
control: [Vec2, Vec2];
|
|
1483
|
+
} | null;
|
|
1460
1484
|
draftEdge: {
|
|
1461
1485
|
source: EdgeEnd;
|
|
1462
1486
|
target: EdgeEnd;
|
package/dist/index.d.ts
CHANGED
|
@@ -1454,9 +1454,33 @@ type InteractionState = {
|
|
|
1454
1454
|
resizeLockAspect: boolean;
|
|
1455
1455
|
/** Whether the user is holding Alt during a resize (resize from center). */
|
|
1456
1456
|
resizeFromCenter: boolean;
|
|
1457
|
+
/**
|
|
1458
|
+
* Live in-progress geometry of the resized node — written every
|
|
1459
|
+
* pointermove, committed to the store once on pointer-up. While
|
|
1460
|
+
* present, `store.getNode(id)` still returns the original geometry;
|
|
1461
|
+
* the renderer overlays this draft via `mapDragPositions` for the
|
|
1462
|
+
* interactive layer paint. Mirrors how `dragDelta` works for drag.
|
|
1463
|
+
*/
|
|
1464
|
+
resizeDraft: {
|
|
1465
|
+
x: number;
|
|
1466
|
+
y: number;
|
|
1467
|
+
w: number;
|
|
1468
|
+
h: number;
|
|
1469
|
+
angle: number;
|
|
1470
|
+
} | null;
|
|
1457
1471
|
marqueeRect: WorldRect | null;
|
|
1458
1472
|
/** Whether the marquee should add to (true, shift held) or replace selection. */
|
|
1459
1473
|
marqueeAdditive: boolean;
|
|
1474
|
+
/**
|
|
1475
|
+
* Live in-progress cubic controls of an edge being mid-point-dragged.
|
|
1476
|
+
* Written every pointermove, committed to the store once on
|
|
1477
|
+
* pointer-up. Same draft+commit model as `resizeDraft` and
|
|
1478
|
+
* `dragDelta` — keeps mid-gesture mutations off the 'change' bus.
|
|
1479
|
+
*/
|
|
1480
|
+
midpointDraft: {
|
|
1481
|
+
edgeId: EdgeId;
|
|
1482
|
+
control: [Vec2, Vec2];
|
|
1483
|
+
} | null;
|
|
1460
1484
|
draftEdge: {
|
|
1461
1485
|
source: EdgeEnd;
|
|
1462
1486
|
target: EdgeEnd;
|
package/dist/index.js
CHANGED
|
@@ -1048,7 +1048,6 @@ var COMPOSITE = /* @__PURE__ */ new Set([
|
|
|
1048
1048
|
var isCompositePrimitive = (type) => COMPOSITE.has(type);
|
|
1049
1049
|
var isDrawablePrimitive = (type) => ATOMIC.has(type) || COMPOSITE.has(type);
|
|
1050
1050
|
var PLAIN_RECT_CORNER_THRESHOLD_PX = 1.5;
|
|
1051
|
-
var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
|
|
1052
1051
|
var LAYERED_OFFSET = 12;
|
|
1053
1052
|
var drawShape = (ctx, node, scale, theme, opts) => {
|
|
1054
1053
|
if (!isDrawablePrimitive(node.type)) return;
|
|
@@ -1067,7 +1066,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
|
|
|
1067
1066
|
const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
|
|
1068
1067
|
const stroke = resolveColor(style, "strokeColor", DEFAULT_STYLE.strokeColor, theme);
|
|
1069
1068
|
const fillVisible = !isFullyTransparent(fill);
|
|
1070
|
-
const strokeVisible = strokeWidth > 0 &&
|
|
1069
|
+
const strokeVisible = strokeWidth > 0 && !isFullyTransparent(stroke);
|
|
1071
1070
|
if (!fillVisible && !strokeVisible) return;
|
|
1072
1071
|
const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
|
|
1073
1072
|
switch (type) {
|
|
@@ -1104,7 +1103,7 @@ var drawAtomic = (ctx, type, w, h, style, scale, theme, opts) => {
|
|
|
1104
1103
|
}
|
|
1105
1104
|
if (strokeVisible && !opts?.skipStroke) {
|
|
1106
1105
|
ctx.strokeStyle = stroke;
|
|
1107
|
-
ctx.lineWidth = strokeWidth;
|
|
1106
|
+
ctx.lineWidth = Math.max(strokeWidth, 1 / scale);
|
|
1108
1107
|
ctx.setLineDash(dashPatternFor(style?.strokeStyle, strokeWidth));
|
|
1109
1108
|
ctx.stroke();
|
|
1110
1109
|
}
|
|
@@ -1480,13 +1479,16 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
|
|
|
1480
1479
|
const isDark = theme?.("mode") === "dark";
|
|
1481
1480
|
const fill = resolveColor(style, "backgroundColor", DEFAULT_STYLE.backgroundColor, theme);
|
|
1482
1481
|
const strokeColor = deriveRoughStrokeColor(rawStroke, fill, isDark);
|
|
1483
|
-
const
|
|
1484
|
-
if (
|
|
1482
|
+
const rawStrokeWidth = resolveStrokeWidth(style, theme);
|
|
1483
|
+
if (rawStrokeWidth <= 0) return;
|
|
1485
1484
|
const roughness = style?.roughness ?? 0;
|
|
1486
1485
|
if (roughness <= 0) return;
|
|
1486
|
+
const isNoBorderIntent = isFullyTransparent(rawStroke);
|
|
1487
|
+
const effectiveStrokeStyle = isNoBorderIntent ? "solid" : style?.strokeStyle ?? "solid";
|
|
1488
|
+
const strokeWidth = isNoBorderIntent ? DEFAULT_STYLE.strokeWidth : rawStrokeWidth;
|
|
1487
1489
|
const cornerRadius = (style?.roundness ?? DEFAULT_STYLE.roundness) * 4;
|
|
1488
1490
|
const radius = Math.max(0, Math.min(cornerRadius, w / 2, h / 2));
|
|
1489
|
-
const dash = dashPatternFor(
|
|
1491
|
+
const dash = dashPatternFor(effectiveStrokeStyle, strokeWidth);
|
|
1490
1492
|
const detail = apparentDetail(Math.max(w, h), scale);
|
|
1491
1493
|
const cacheKey = [
|
|
1492
1494
|
type,
|
|
@@ -1495,7 +1497,7 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
|
|
|
1495
1497
|
radius.toFixed(1),
|
|
1496
1498
|
strokeColor,
|
|
1497
1499
|
strokeWidth.toFixed(2),
|
|
1498
|
-
|
|
1500
|
+
effectiveStrokeStyle,
|
|
1499
1501
|
roughness.toFixed(2),
|
|
1500
1502
|
seed,
|
|
1501
1503
|
detail.curveStepCount,
|
|
@@ -2646,7 +2648,7 @@ var DEFAULT_EDGE_STYLE = {
|
|
|
2646
2648
|
sourceArrowhead: "none",
|
|
2647
2649
|
targetArrowhead: "arrow-filled"
|
|
2648
2650
|
};
|
|
2649
|
-
var
|
|
2651
|
+
var STROKE_VISIBILITY_THRESHOLD_PX = 0.5;
|
|
2650
2652
|
var ARROWHEAD_VISIBILITY_THRESHOLD_PX = 2;
|
|
2651
2653
|
var samplePaintStride = (scale) => {
|
|
2652
2654
|
if (scale < 0.15) return 8;
|
|
@@ -2660,7 +2662,7 @@ var drawEdge = (ctx, edge, geom, sourceNode, targetNode, scale, theme, opts) =>
|
|
|
2660
2662
|
if (samples.length < 2) return;
|
|
2661
2663
|
const style = edge.style;
|
|
2662
2664
|
const strokeWidth = typeof style?.strokeWidth === "number" ? style.strokeWidth : theme?.("strokeWidth") ?? DEFAULT_EDGE_STYLE.strokeWidth;
|
|
2663
|
-
if (strokeWidth * scale <
|
|
2665
|
+
if (strokeWidth * scale < STROKE_VISIBILITY_THRESHOLD_PX) return;
|
|
2664
2666
|
const strokeColor = typeof style?.strokeColor === "string" ? style.strokeColor : theme?.("edge.strokeColor") ?? DEFAULT_EDGE_STYLE.strokeColor;
|
|
2665
2667
|
const sourceArrowhead = style?.sourceArrowhead ?? DEFAULT_EDGE_STYLE.sourceArrowhead;
|
|
2666
2668
|
const targetArrowhead = style?.targetArrowhead ?? DEFAULT_EDGE_STYLE.targetArrowhead;
|
|
@@ -3376,6 +3378,8 @@ var idleInteractionState = () => ({
|
|
|
3376
3378
|
resizeHandle: null,
|
|
3377
3379
|
resizeLockAspect: false,
|
|
3378
3380
|
resizeFromCenter: false,
|
|
3381
|
+
resizeDraft: null,
|
|
3382
|
+
midpointDraft: null,
|
|
3379
3383
|
marqueeRect: null,
|
|
3380
3384
|
marqueeAdditive: false,
|
|
3381
3385
|
draftEdge: null,
|
|
@@ -4884,7 +4888,7 @@ var drawWithNodeTransform = (ctx, node, fn) => {
|
|
|
4884
4888
|
};
|
|
4885
4889
|
|
|
4886
4890
|
// src/render/renderer.ts
|
|
4887
|
-
var
|
|
4891
|
+
var SCENE_CACHE_MARGIN_PX = 256;
|
|
4888
4892
|
var MIN_ON_SCREEN_SIZE_PX = 1.5;
|
|
4889
4893
|
var MIN_READABLE_FONT_PX = 3;
|
|
4890
4894
|
var createRenderer = (opts) => {
|
|
@@ -4901,6 +4905,30 @@ var createRenderer = (opts) => {
|
|
|
4901
4905
|
let interactiveDirty = false;
|
|
4902
4906
|
let overlaySet = /* @__PURE__ */ new Set();
|
|
4903
4907
|
let lastDrawn = 0;
|
|
4908
|
+
let cacheSurface = null;
|
|
4909
|
+
let cacheCamX = 0;
|
|
4910
|
+
let cacheCamY = 0;
|
|
4911
|
+
let cacheCamZ = 1;
|
|
4912
|
+
let cacheStale = true;
|
|
4913
|
+
const ensureCacheSurface = () => {
|
|
4914
|
+
const dpr = staticSurface.dpr;
|
|
4915
|
+
const cssW = staticSurface.cssWidth + 2 * SCENE_CACHE_MARGIN_PX;
|
|
4916
|
+
const cssH = staticSurface.cssHeight + 2 * SCENE_CACHE_MARGIN_PX;
|
|
4917
|
+
if (!cacheSurface) {
|
|
4918
|
+
const canvas = document.createElement("canvas");
|
|
4919
|
+
const ctx = canvas.getContext("2d");
|
|
4920
|
+
if (!ctx) throw new Error("Canvas 2d context unavailable");
|
|
4921
|
+
cacheSurface = { canvas, ctx, cssWidth: 0, cssHeight: 0, dpr: 1 };
|
|
4922
|
+
}
|
|
4923
|
+
if (cacheSurface.cssWidth !== cssW || cacheSurface.cssHeight !== cssH || cacheSurface.dpr !== dpr) {
|
|
4924
|
+
cacheSurface.cssWidth = cssW;
|
|
4925
|
+
cacheSurface.cssHeight = cssH;
|
|
4926
|
+
cacheSurface.dpr = dpr;
|
|
4927
|
+
cacheSurface.canvas.width = Math.max(1, Math.round(cssW * dpr));
|
|
4928
|
+
cacheSurface.canvas.height = Math.max(1, Math.round(cssH * dpr));
|
|
4929
|
+
}
|
|
4930
|
+
return cacheSurface;
|
|
4931
|
+
};
|
|
4904
4932
|
let sortedNodeIdsCache = null;
|
|
4905
4933
|
let sortedEdgeIdsCache = null;
|
|
4906
4934
|
const invalidateSortedCaches = () => {
|
|
@@ -4909,6 +4937,7 @@ var createRenderer = (opts) => {
|
|
|
4909
4937
|
};
|
|
4910
4938
|
const requestRepaint = () => {
|
|
4911
4939
|
staticDirty = true;
|
|
4940
|
+
cacheStale = true;
|
|
4912
4941
|
loop.requestFrame();
|
|
4913
4942
|
};
|
|
4914
4943
|
const assetCache = createAssetCache({ onReady: requestRepaint });
|
|
@@ -4923,16 +4952,14 @@ var createRenderer = (opts) => {
|
|
|
4923
4952
|
interactiveDirty = false;
|
|
4924
4953
|
}
|
|
4925
4954
|
};
|
|
4926
|
-
const
|
|
4927
|
-
const
|
|
4928
|
-
clearSurface(staticSurface);
|
|
4929
|
-
applyCameraTransform(staticSurface, camera);
|
|
4930
|
-
const scale = camera.z * staticSurface.dpr;
|
|
4955
|
+
const paintSceneBody = (surface, camera, viewport, fullRender = true) => {
|
|
4956
|
+
const scale = camera.z * surface.dpr;
|
|
4931
4957
|
const interaction = store.getInteractionState();
|
|
4932
4958
|
const excludedNodes = interaction.mode === "dragging" || interaction.mode === "resizing" ? new Set(interaction.draggedIds) : null;
|
|
4933
|
-
const
|
|
4934
|
-
const
|
|
4935
|
-
|
|
4959
|
+
const baseExcludedEdges = excludedNodes ? incidentEdgeIds(excludedNodes) : null;
|
|
4960
|
+
const midpointEdgeId = interaction.midpointDraft?.edgeId ?? null;
|
|
4961
|
+
const excludedEdges = midpointEdgeId !== null ? /* @__PURE__ */ new Set([...baseExcludedEdges ?? [], midpointEdgeId]) : baseExcludedEdges;
|
|
4962
|
+
paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
|
|
4936
4963
|
const visible = visibleNodes(camera, viewport);
|
|
4937
4964
|
const isMoving2 = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "dragging" || interaction.mode === "resizing" || interaction.mode === "rotating";
|
|
4938
4965
|
const minOnScreen = MIN_ON_SCREEN_SIZE_PX;
|
|
@@ -4954,8 +4981,8 @@ var createRenderer = (opts) => {
|
|
|
4954
4981
|
for (const node of visible) {
|
|
4955
4982
|
if (node.type !== "frame") continue;
|
|
4956
4983
|
if (excludedNodes?.has(node.id)) continue;
|
|
4957
|
-
drawWithNodeTransform(
|
|
4958
|
-
paintFrameNode(
|
|
4984
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
4985
|
+
paintFrameNode(surface.ctx, node, scale, theme);
|
|
4959
4986
|
});
|
|
4960
4987
|
drawn++;
|
|
4961
4988
|
}
|
|
@@ -4968,52 +4995,53 @@ var createRenderer = (opts) => {
|
|
|
4968
4995
|
const useRough = roughEnabled && (node.style?.roughness ?? 0) > 0;
|
|
4969
4996
|
const roughReady = useRough ? getRoughCanvasCtor() !== null : false;
|
|
4970
4997
|
const composite = isCompositePrimitive(node.type);
|
|
4971
|
-
drawWithNodeTransform(
|
|
4998
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
4972
4999
|
if (useRough && roughReady) {
|
|
4973
5000
|
if (composite) {
|
|
4974
|
-
drawCompositeRough(
|
|
5001
|
+
drawCompositeRough(surface.ctx, node, camera.z, theme);
|
|
4975
5002
|
} else {
|
|
4976
|
-
|
|
4977
|
-
drawShape(
|
|
4978
|
-
|
|
4979
|
-
drawRoughShape(
|
|
5003
|
+
surface.ctx.translate(ROUGH_FILL_MISREGISTER_X, ROUGH_FILL_MISREGISTER_Y);
|
|
5004
|
+
drawShape(surface.ctx, node, scale, theme, { skipStroke: true });
|
|
5005
|
+
surface.ctx.translate(-ROUGH_FILL_MISREGISTER_X, -ROUGH_FILL_MISREGISTER_Y);
|
|
5006
|
+
drawRoughShape(surface.ctx, node, camera.z, theme);
|
|
4980
5007
|
}
|
|
4981
5008
|
} else {
|
|
4982
|
-
drawShape(
|
|
5009
|
+
drawShape(surface.ctx, node, scale, theme);
|
|
4983
5010
|
if (useRough && !roughReady) {
|
|
4984
5011
|
onRoughReady(() => {
|
|
4985
5012
|
staticDirty = true;
|
|
5013
|
+
cacheStale = true;
|
|
4986
5014
|
loop.requestFrame();
|
|
4987
5015
|
});
|
|
4988
5016
|
}
|
|
4989
5017
|
}
|
|
4990
|
-
if (!isEditingThis) paintNodeContent(
|
|
5018
|
+
if (!isEditingThis) paintNodeContent(surface.ctx, node, renderEnv);
|
|
4991
5019
|
});
|
|
4992
5020
|
drawn++;
|
|
4993
5021
|
continue;
|
|
4994
5022
|
}
|
|
4995
5023
|
if (node.type === "image") {
|
|
4996
|
-
drawWithNodeTransform(
|
|
4997
|
-
paintImageNode(
|
|
5024
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5025
|
+
paintImageNode(surface.ctx, node, assetCache, theme);
|
|
4998
5026
|
});
|
|
4999
5027
|
drawn++;
|
|
5000
5028
|
continue;
|
|
5001
5029
|
}
|
|
5002
5030
|
if (node.type === "icon") {
|
|
5003
|
-
drawWithNodeTransform(
|
|
5004
|
-
paintIconNode(
|
|
5031
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5032
|
+
paintIconNode(surface.ctx, node, assetCache, scale, theme);
|
|
5005
5033
|
});
|
|
5006
5034
|
drawn++;
|
|
5007
5035
|
continue;
|
|
5008
5036
|
}
|
|
5009
5037
|
if (node.type === "text") {
|
|
5010
|
-
drawWithNodeTransform(
|
|
5038
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5011
5039
|
if (isEditingThis) return;
|
|
5012
5040
|
const hasContent = node.content && node.content.trim().length > 0;
|
|
5013
5041
|
if (hasContent) {
|
|
5014
|
-
paintNodeContent(
|
|
5042
|
+
paintNodeContent(surface.ctx, node, renderEnv);
|
|
5015
5043
|
} else {
|
|
5016
|
-
paintEmptyTextPlaceholder(
|
|
5044
|
+
paintEmptyTextPlaceholder(surface.ctx, node, camera.z);
|
|
5017
5045
|
}
|
|
5018
5046
|
});
|
|
5019
5047
|
drawn++;
|
|
@@ -5025,7 +5053,7 @@ var createRenderer = (opts) => {
|
|
|
5025
5053
|
if (camera.z < def.lod.minZoomForPlaceholder) continue;
|
|
5026
5054
|
const preferCanvas = camera.z < def.lod.minZoomForReact || isMoving2;
|
|
5027
5055
|
if (preferCanvas) {
|
|
5028
|
-
if (paintCustomCanvasFallback(
|
|
5056
|
+
if (paintCustomCanvasFallback(surface.ctx, node, def, scale, renderEnv)) {
|
|
5029
5057
|
drawn++;
|
|
5030
5058
|
}
|
|
5031
5059
|
continue;
|
|
@@ -5035,10 +5063,10 @@ var createRenderer = (opts) => {
|
|
|
5035
5063
|
continue;
|
|
5036
5064
|
}
|
|
5037
5065
|
if (def.renderCanvas) {
|
|
5038
|
-
drawWithNodeTransform(
|
|
5039
|
-
|
|
5040
|
-
def.renderCanvas(
|
|
5041
|
-
|
|
5066
|
+
drawWithNodeTransform(surface.ctx, node, () => {
|
|
5067
|
+
surface.ctx.save();
|
|
5068
|
+
def.renderCanvas(surface.ctx, node, renderEnv);
|
|
5069
|
+
surface.ctx.restore();
|
|
5042
5070
|
});
|
|
5043
5071
|
drawn++;
|
|
5044
5072
|
}
|
|
@@ -5047,15 +5075,120 @@ var createRenderer = (opts) => {
|
|
|
5047
5075
|
const edgeRoughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
|
|
5048
5076
|
for (const edge of visEdges) {
|
|
5049
5077
|
if (excludedEdges?.has(edge.id)) continue;
|
|
5050
|
-
paintOneEdge(
|
|
5078
|
+
paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
|
|
5051
5079
|
drawn++;
|
|
5052
5080
|
}
|
|
5081
|
+
if (!fullRender) return;
|
|
5053
5082
|
lastDrawn = drawn;
|
|
5054
5083
|
if (!setsEqual(nextOverlaySet, overlaySet)) {
|
|
5055
5084
|
overlaySet = nextOverlaySet;
|
|
5056
5085
|
onOverlayChange?.([...overlaySet]);
|
|
5057
5086
|
}
|
|
5058
5087
|
};
|
|
5088
|
+
const applyCacheTransform = (cache5, centerX, centerY, z) => {
|
|
5089
|
+
const s = z * cache5.dpr;
|
|
5090
|
+
const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
|
|
5091
|
+
cache5.ctx.setTransform(s, 0, 0, s, -centerX * s + m, -centerY * s + m);
|
|
5092
|
+
};
|
|
5093
|
+
const renderFullCache = (camera) => {
|
|
5094
|
+
const cache5 = ensureCacheSurface();
|
|
5095
|
+
clearSurface(cache5);
|
|
5096
|
+
applyCacheTransform(cache5, camera.x, camera.y, camera.z);
|
|
5097
|
+
const marginWorld = SCENE_CACHE_MARGIN_PX / camera.z;
|
|
5098
|
+
const viewport = inflateRect(worldViewport(staticSurface, camera), marginWorld);
|
|
5099
|
+
paintSceneBody(cache5, camera, viewport);
|
|
5100
|
+
cacheCamX = camera.x;
|
|
5101
|
+
cacheCamY = camera.y;
|
|
5102
|
+
cacheCamZ = camera.z;
|
|
5103
|
+
cacheStale = false;
|
|
5104
|
+
};
|
|
5105
|
+
const canExtend = (camera) => {
|
|
5106
|
+
if (!cacheSurface) return false;
|
|
5107
|
+
const s = camera.z * staticSurface.dpr;
|
|
5108
|
+
const dx = Math.abs((cacheCamX - camera.x) * s);
|
|
5109
|
+
const dy = Math.abs((cacheCamY - camera.y) * s);
|
|
5110
|
+
return dx < cacheSurface.canvas.width && dy < cacheSurface.canvas.height;
|
|
5111
|
+
};
|
|
5112
|
+
const renderCacheStrip = (cache5, centerX, centerY, z, px, py, pw, ph) => {
|
|
5113
|
+
const ctx = cache5.ctx;
|
|
5114
|
+
const s = z * cache5.dpr;
|
|
5115
|
+
const m = SCENE_CACHE_MARGIN_PX * cache5.dpr;
|
|
5116
|
+
ctx.save();
|
|
5117
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5118
|
+
ctx.beginPath();
|
|
5119
|
+
ctx.rect(px, py, pw, ph);
|
|
5120
|
+
ctx.clip();
|
|
5121
|
+
ctx.clearRect(px, py, pw, ph);
|
|
5122
|
+
applyCacheTransform(cache5, centerX, centerY, z);
|
|
5123
|
+
const viewport = {
|
|
5124
|
+
x: (px - m) / s + centerX,
|
|
5125
|
+
y: (py - m) / s + centerY,
|
|
5126
|
+
w: pw / s,
|
|
5127
|
+
h: ph / s
|
|
5128
|
+
};
|
|
5129
|
+
paintSceneBody(cache5, { z }, viewport, false);
|
|
5130
|
+
ctx.restore();
|
|
5131
|
+
};
|
|
5132
|
+
const extendCache = (camera) => {
|
|
5133
|
+
const cache5 = ensureCacheSurface();
|
|
5134
|
+
const s = camera.z * cache5.dpr;
|
|
5135
|
+
const cacheW = cache5.canvas.width;
|
|
5136
|
+
const cacheH = cache5.canvas.height;
|
|
5137
|
+
const dx = Math.round((cacheCamX - camera.x) * s);
|
|
5138
|
+
const dy = Math.round((cacheCamY - camera.y) * s);
|
|
5139
|
+
const newCamX = cacheCamX - dx / s;
|
|
5140
|
+
const newCamY = cacheCamY - dy / s;
|
|
5141
|
+
cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5142
|
+
cache5.ctx.drawImage(cache5.canvas, 0, 0, cacheW, cacheH, dx, dy, cacheW, cacheH);
|
|
5143
|
+
cacheCamX = newCamX;
|
|
5144
|
+
cacheCamY = newCamY;
|
|
5145
|
+
cacheCamZ = camera.z;
|
|
5146
|
+
const hw = Math.abs(dx);
|
|
5147
|
+
const vh = Math.abs(dy);
|
|
5148
|
+
const hx = dx > 0 ? 0 : cacheW - hw;
|
|
5149
|
+
const vy = dy > 0 ? 0 : cacheH - vh;
|
|
5150
|
+
const vx = dx > 0 ? hw : 0;
|
|
5151
|
+
const vw = cacheW - hw;
|
|
5152
|
+
if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
|
|
5153
|
+
if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
|
|
5154
|
+
};
|
|
5155
|
+
const cacheSourceOffset = (camera) => {
|
|
5156
|
+
const dpr = staticSurface.dpr;
|
|
5157
|
+
return {
|
|
5158
|
+
x: Math.round(((camera.x - cacheCamX) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr),
|
|
5159
|
+
y: Math.round(((camera.y - cacheCamY) * cacheCamZ + SCENE_CACHE_MARGIN_PX) * dpr)
|
|
5160
|
+
};
|
|
5161
|
+
};
|
|
5162
|
+
const viewportFitsInCache = (camera) => {
|
|
5163
|
+
if (!cacheSurface) return false;
|
|
5164
|
+
const { x, y } = cacheSourceOffset(camera);
|
|
5165
|
+
return x >= 0 && y >= 0 && x + staticSurface.canvas.width <= cacheSurface.canvas.width && y + staticSurface.canvas.height <= cacheSurface.canvas.height;
|
|
5166
|
+
};
|
|
5167
|
+
const presentStatic = (camera) => {
|
|
5168
|
+
const cache5 = ensureCacheSurface();
|
|
5169
|
+
const w = staticSurface.canvas.width;
|
|
5170
|
+
const h = staticSurface.canvas.height;
|
|
5171
|
+
const { x: srcX, y: srcY } = cacheSourceOffset(camera);
|
|
5172
|
+
staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5173
|
+
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5174
|
+
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
|
|
5175
|
+
};
|
|
5176
|
+
const paintStatic = () => {
|
|
5177
|
+
const camera = store.getCamera();
|
|
5178
|
+
if (!cacheStale && camera.z === cacheCamZ) {
|
|
5179
|
+
if (viewportFitsInCache(camera)) {
|
|
5180
|
+
presentStatic(camera);
|
|
5181
|
+
return;
|
|
5182
|
+
}
|
|
5183
|
+
if (canExtend(camera)) {
|
|
5184
|
+
extendCache(camera);
|
|
5185
|
+
presentStatic(camera);
|
|
5186
|
+
return;
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
renderFullCache(camera);
|
|
5190
|
+
presentStatic(camera);
|
|
5191
|
+
};
|
|
5059
5192
|
const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
|
|
5060
5193
|
if (def.getSnapshot) {
|
|
5061
5194
|
const snap = def.getSnapshot(node, {
|
|
@@ -5237,6 +5370,23 @@ var createRenderer = (opts) => {
|
|
|
5237
5370
|
}
|
|
5238
5371
|
}
|
|
5239
5372
|
}
|
|
5373
|
+
if (interaction.midpointDraft) {
|
|
5374
|
+
const { edgeId, control } = interaction.midpointDraft;
|
|
5375
|
+
const edge = store.getEdge(edgeId);
|
|
5376
|
+
if (edge) {
|
|
5377
|
+
const draftEdge = { ...edge, control };
|
|
5378
|
+
const geom = computeEdgeGeometry(draftEdge, (id) => store.getNode(id));
|
|
5379
|
+
if (geom) {
|
|
5380
|
+
const sourceNode = geom.sourceNodeId ? store.getNode(geom.sourceNodeId) ?? null : null;
|
|
5381
|
+
const targetNode = geom.targetNodeId ? store.getNode(geom.targetNodeId) ?? null : null;
|
|
5382
|
+
drawEdge(ctx, draftEdge, geom, sourceNode, targetNode, scale, theme, {
|
|
5383
|
+
zoom: camera.z,
|
|
5384
|
+
dpr: interactiveSurface.dpr,
|
|
5385
|
+
isMoving: true
|
|
5386
|
+
});
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5240
5390
|
const selection = store.getSelection();
|
|
5241
5391
|
const selectedNodeIds = [];
|
|
5242
5392
|
const selectedEdgeIds = [];
|
|
@@ -5309,7 +5459,12 @@ var createRenderer = (opts) => {
|
|
|
5309
5459
|
y: orig.y + interaction.dragDelta.y
|
|
5310
5460
|
});
|
|
5311
5461
|
} else {
|
|
5312
|
-
|
|
5462
|
+
const d = interaction.resizeDraft;
|
|
5463
|
+
if (d) {
|
|
5464
|
+
m.set(orig.id, { ...live, x: d.x, y: d.y, w: d.w, h: d.h, angle: d.angle });
|
|
5465
|
+
} else {
|
|
5466
|
+
m.set(orig.id, live);
|
|
5467
|
+
}
|
|
5313
5468
|
}
|
|
5314
5469
|
}
|
|
5315
5470
|
return m;
|
|
@@ -5340,6 +5495,7 @@ var createRenderer = (opts) => {
|
|
|
5340
5495
|
const onStoreChange = () => {
|
|
5341
5496
|
invalidateSortedCaches();
|
|
5342
5497
|
staticDirty = true;
|
|
5498
|
+
cacheStale = true;
|
|
5343
5499
|
interactiveDirty = true;
|
|
5344
5500
|
loop.requestFrame();
|
|
5345
5501
|
};
|
|
@@ -5356,6 +5512,7 @@ var createRenderer = (opts) => {
|
|
|
5356
5512
|
interactiveDirty = true;
|
|
5357
5513
|
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "zooming" || state.mode === "idle") {
|
|
5358
5514
|
staticDirty = true;
|
|
5515
|
+
cacheStale = true;
|
|
5359
5516
|
}
|
|
5360
5517
|
loop.requestFrame();
|
|
5361
5518
|
};
|
|
@@ -5365,16 +5522,19 @@ var createRenderer = (opts) => {
|
|
|
5365
5522
|
const unsubInteraction = store.subscribe("interaction", onInteractionChange);
|
|
5366
5523
|
const unsubFontEpoch = subscribeFontEpoch(() => {
|
|
5367
5524
|
staticDirty = true;
|
|
5525
|
+
cacheStale = true;
|
|
5368
5526
|
loop.requestFrame();
|
|
5369
5527
|
});
|
|
5370
5528
|
const unsubMathEpoch = subscribeMathEpoch(() => {
|
|
5371
5529
|
staticDirty = true;
|
|
5530
|
+
cacheStale = true;
|
|
5372
5531
|
loop.requestFrame();
|
|
5373
5532
|
});
|
|
5374
5533
|
return {
|
|
5375
5534
|
start() {
|
|
5376
5535
|
loop.start();
|
|
5377
5536
|
staticDirty = true;
|
|
5537
|
+
cacheStale = true;
|
|
5378
5538
|
interactiveDirty = isInteractive(store.getInteractionState());
|
|
5379
5539
|
loop.requestFrame();
|
|
5380
5540
|
},
|
|
@@ -5383,6 +5543,7 @@ var createRenderer = (opts) => {
|
|
|
5383
5543
|
},
|
|
5384
5544
|
invalidate() {
|
|
5385
5545
|
staticDirty = true;
|
|
5546
|
+
cacheStale = true;
|
|
5386
5547
|
interactiveDirty = true;
|
|
5387
5548
|
loop.requestFrame();
|
|
5388
5549
|
},
|
|
@@ -5391,6 +5552,7 @@ var createRenderer = (opts) => {
|
|
|
5391
5552
|
const b = sizeSurface(interactiveSurface, cssW, cssH, maxDpr);
|
|
5392
5553
|
if (a || b) {
|
|
5393
5554
|
staticDirty = true;
|
|
5555
|
+
cacheStale = true;
|
|
5394
5556
|
interactiveDirty = true;
|
|
5395
5557
|
loop.requestFrame();
|
|
5396
5558
|
}
|
|
@@ -5398,6 +5560,7 @@ var createRenderer = (opts) => {
|
|
|
5398
5560
|
setBackground(bg) {
|
|
5399
5561
|
background = bg;
|
|
5400
5562
|
staticDirty = true;
|
|
5563
|
+
cacheStale = true;
|
|
5401
5564
|
loop.requestFrame();
|
|
5402
5565
|
},
|
|
5403
5566
|
setSelectionColor(color) {
|
|
@@ -5408,6 +5571,7 @@ var createRenderer = (opts) => {
|
|
|
5408
5571
|
setHideFrames(hidden) {
|
|
5409
5572
|
hideFrames = hidden;
|
|
5410
5573
|
staticDirty = true;
|
|
5574
|
+
cacheStale = true;
|
|
5411
5575
|
loop.requestFrame();
|
|
5412
5576
|
},
|
|
5413
5577
|
stats: () => loop.stats(),
|
|
@@ -5422,6 +5586,11 @@ var createRenderer = (opts) => {
|
|
|
5422
5586
|
unsubFontEpoch();
|
|
5423
5587
|
unsubMathEpoch();
|
|
5424
5588
|
assetCache.dispose();
|
|
5589
|
+
if (cacheSurface) {
|
|
5590
|
+
cacheSurface.canvas.width = 0;
|
|
5591
|
+
cacheSurface.canvas.height = 0;
|
|
5592
|
+
cacheSurface = null;
|
|
5593
|
+
}
|
|
5425
5594
|
}
|
|
5426
5595
|
};
|
|
5427
5596
|
};
|