@canvas-harness/core 0.1.1 → 0.1.2
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 +50 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +50 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -817,7 +817,28 @@ type SnapshotEnv = {
|
|
|
817
817
|
type NodeTypeDefOptions = {
|
|
818
818
|
/** Unique type id, e.g. 'chart-card'. */
|
|
819
819
|
type: string;
|
|
820
|
+
/**
|
|
821
|
+
* Canvas paint for the node body. Caller has already applied the
|
|
822
|
+
* camera + node transform, so paint at `(0, 0, node.w, node.h)`.
|
|
823
|
+
*
|
|
824
|
+
* Context state contract:
|
|
825
|
+
* - The renderer wraps this call in `ctx.save()` / `ctx.restore()`
|
|
826
|
+
* so any state you change (fillStyle, strokeStyle, lineWidth,
|
|
827
|
+
* setLineDash, globalAlpha, font, …) is automatically rolled
|
|
828
|
+
* back before the next node draws — set whatever you need
|
|
829
|
+
* without worrying about cleanup.
|
|
830
|
+
* - Conversely, **do NOT assume default state on entry.** Always
|
|
831
|
+
* set the styles you depend on; the previous node's values
|
|
832
|
+
* may still be in effect.
|
|
833
|
+
* - The transform is NOT save/restore-protected at this level
|
|
834
|
+
* (it's managed one frame up by the renderer). Don't leave
|
|
835
|
+
* `translate` / `rotate` / `scale` calls un-paired.
|
|
836
|
+
*/
|
|
820
837
|
renderCanvas?: (ctx: CanvasRenderingContext2D, node: Node, env: RenderEnv) => void;
|
|
838
|
+
/**
|
|
839
|
+
* Low-zoom / motion fallback paint — see ARCHITECTURE.md §5.3 LOD.
|
|
840
|
+
* Same context-state contract as `renderCanvas`.
|
|
841
|
+
*/
|
|
821
842
|
drawPlaceholder?: (ctx: CanvasRenderingContext2D, node: Node, env: RenderEnv) => void;
|
|
822
843
|
/**
|
|
823
844
|
* The React view component reference. Stored as `unknown` here because the
|
|
@@ -2328,6 +2349,15 @@ declare const worldViewport: (surface: CanvasSurface, camera: CameraState) => Wo
|
|
|
2328
2349
|
* Wraps a draw callback in the local-frame transform for one node:
|
|
2329
2350
|
* translates to the node's center, rotates by node.angle, then translates
|
|
2330
2351
|
* back to the node's top-left so the drawer can build paths in (0..w, 0..h).
|
|
2352
|
+
*
|
|
2353
|
+
* Fast path: when `node.angle === 0` (the common case) we skip the
|
|
2354
|
+
* canvas2d save/restore pair entirely and manually un-translate after
|
|
2355
|
+
* the callback. `save()`/`restore()` allocate + swap a full graphics
|
|
2356
|
+
* state record; at 10k+ nodes/frame that's ~1ms of the paint budget.
|
|
2357
|
+
* The callback is responsible for not leaking transform state — all
|
|
2358
|
+
* built-in drawers (drawShape, drawCompositeRough, paintFrameNode,
|
|
2359
|
+
* paintImageNode, paintIconNode) honor this contract by either
|
|
2360
|
+
* leaving the transform untouched or restoring their own inner pushes.
|
|
2331
2361
|
*/
|
|
2332
2362
|
declare const drawWithNodeTransform: (ctx: CanvasRenderingContext2D, node: Node, fn: () => void) => void;
|
|
2333
2363
|
|
package/dist/index.d.ts
CHANGED
|
@@ -817,7 +817,28 @@ type SnapshotEnv = {
|
|
|
817
817
|
type NodeTypeDefOptions = {
|
|
818
818
|
/** Unique type id, e.g. 'chart-card'. */
|
|
819
819
|
type: string;
|
|
820
|
+
/**
|
|
821
|
+
* Canvas paint for the node body. Caller has already applied the
|
|
822
|
+
* camera + node transform, so paint at `(0, 0, node.w, node.h)`.
|
|
823
|
+
*
|
|
824
|
+
* Context state contract:
|
|
825
|
+
* - The renderer wraps this call in `ctx.save()` / `ctx.restore()`
|
|
826
|
+
* so any state you change (fillStyle, strokeStyle, lineWidth,
|
|
827
|
+
* setLineDash, globalAlpha, font, …) is automatically rolled
|
|
828
|
+
* back before the next node draws — set whatever you need
|
|
829
|
+
* without worrying about cleanup.
|
|
830
|
+
* - Conversely, **do NOT assume default state on entry.** Always
|
|
831
|
+
* set the styles you depend on; the previous node's values
|
|
832
|
+
* may still be in effect.
|
|
833
|
+
* - The transform is NOT save/restore-protected at this level
|
|
834
|
+
* (it's managed one frame up by the renderer). Don't leave
|
|
835
|
+
* `translate` / `rotate` / `scale` calls un-paired.
|
|
836
|
+
*/
|
|
820
837
|
renderCanvas?: (ctx: CanvasRenderingContext2D, node: Node, env: RenderEnv) => void;
|
|
838
|
+
/**
|
|
839
|
+
* Low-zoom / motion fallback paint — see ARCHITECTURE.md §5.3 LOD.
|
|
840
|
+
* Same context-state contract as `renderCanvas`.
|
|
841
|
+
*/
|
|
821
842
|
drawPlaceholder?: (ctx: CanvasRenderingContext2D, node: Node, env: RenderEnv) => void;
|
|
822
843
|
/**
|
|
823
844
|
* The React view component reference. Stored as `unknown` here because the
|
|
@@ -2328,6 +2349,15 @@ declare const worldViewport: (surface: CanvasSurface, camera: CameraState) => Wo
|
|
|
2328
2349
|
* Wraps a draw callback in the local-frame transform for one node:
|
|
2329
2350
|
* translates to the node's center, rotates by node.angle, then translates
|
|
2330
2351
|
* back to the node's top-left so the drawer can build paths in (0..w, 0..h).
|
|
2352
|
+
*
|
|
2353
|
+
* Fast path: when `node.angle === 0` (the common case) we skip the
|
|
2354
|
+
* canvas2d save/restore pair entirely and manually un-translate after
|
|
2355
|
+
* the callback. `save()`/`restore()` allocate + swap a full graphics
|
|
2356
|
+
* state record; at 10k+ nodes/frame that's ~1ms of the paint budget.
|
|
2357
|
+
* The callback is responsible for not leaking transform state — all
|
|
2358
|
+
* built-in drawers (drawShape, drawCompositeRough, paintFrameNode,
|
|
2359
|
+
* paintImageNode, paintIconNode) honor this contract by either
|
|
2360
|
+
* leaving the transform untouched or restoring their own inner pushes.
|
|
2331
2361
|
*/
|
|
2332
2362
|
declare const drawWithNodeTransform: (ctx: CanvasRenderingContext2D, node: Node, fn: () => void) => void;
|
|
2333
2363
|
|
package/dist/index.js
CHANGED
|
@@ -4869,16 +4869,18 @@ var applyCameraTransform = (surface, camera) => {
|
|
|
4869
4869
|
};
|
|
4870
4870
|
var worldViewport = (surface, camera) => viewportWorldRect(camera, surface.cssWidth, surface.cssHeight);
|
|
4871
4871
|
var drawWithNodeTransform = (ctx, node, fn) => {
|
|
4872
|
-
ctx.save();
|
|
4873
4872
|
if (node.angle === 0) {
|
|
4874
4873
|
ctx.translate(node.x, node.y);
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
ctx.translate(cx, cy);
|
|
4879
|
-
ctx.rotate(node.angle);
|
|
4880
|
-
ctx.translate(-node.w / 2, -node.h / 2);
|
|
4874
|
+
fn();
|
|
4875
|
+
ctx.translate(-node.x, -node.y);
|
|
4876
|
+
return;
|
|
4881
4877
|
}
|
|
4878
|
+
ctx.save();
|
|
4879
|
+
const cx = node.x + node.w / 2;
|
|
4880
|
+
const cy = node.y + node.h / 2;
|
|
4881
|
+
ctx.translate(cx, cy);
|
|
4882
|
+
ctx.rotate(node.angle);
|
|
4883
|
+
ctx.translate(-node.w / 2, -node.h / 2);
|
|
4882
4884
|
fn();
|
|
4883
4885
|
ctx.restore();
|
|
4884
4886
|
};
|
|
@@ -4900,6 +4902,12 @@ var createRenderer = (opts) => {
|
|
|
4900
4902
|
let interactiveDirty = false;
|
|
4901
4903
|
let overlaySet = /* @__PURE__ */ new Set();
|
|
4902
4904
|
let lastDrawn = 0;
|
|
4905
|
+
let sortedNodeIdsCache = null;
|
|
4906
|
+
let sortedEdgeIdsCache = null;
|
|
4907
|
+
const invalidateSortedCaches = () => {
|
|
4908
|
+
sortedNodeIdsCache = null;
|
|
4909
|
+
sortedEdgeIdsCache = null;
|
|
4910
|
+
};
|
|
4903
4911
|
const requestRepaint = () => {
|
|
4904
4912
|
staticDirty = true;
|
|
4905
4913
|
loop.requestFrame();
|
|
@@ -5029,7 +5037,9 @@ var createRenderer = (opts) => {
|
|
|
5029
5037
|
}
|
|
5030
5038
|
if (def.renderCanvas) {
|
|
5031
5039
|
drawWithNodeTransform(staticSurface.ctx, node, () => {
|
|
5040
|
+
staticSurface.ctx.save();
|
|
5032
5041
|
def.renderCanvas(staticSurface.ctx, node, renderEnv);
|
|
5042
|
+
staticSurface.ctx.restore();
|
|
5033
5043
|
});
|
|
5034
5044
|
drawn++;
|
|
5035
5045
|
}
|
|
@@ -5062,11 +5072,19 @@ var createRenderer = (opts) => {
|
|
|
5062
5072
|
}
|
|
5063
5073
|
}
|
|
5064
5074
|
if (def.drawPlaceholder) {
|
|
5065
|
-
drawWithNodeTransform(ctx, node, () =>
|
|
5075
|
+
drawWithNodeTransform(ctx, node, () => {
|
|
5076
|
+
ctx.save();
|
|
5077
|
+
def.drawPlaceholder(ctx, node, env);
|
|
5078
|
+
ctx.restore();
|
|
5079
|
+
});
|
|
5066
5080
|
return true;
|
|
5067
5081
|
}
|
|
5068
5082
|
if (def.renderCanvas) {
|
|
5069
|
-
drawWithNodeTransform(ctx, node, () =>
|
|
5083
|
+
drawWithNodeTransform(ctx, node, () => {
|
|
5084
|
+
ctx.save();
|
|
5085
|
+
def.renderCanvas(ctx, node, env);
|
|
5086
|
+
ctx.restore();
|
|
5087
|
+
});
|
|
5070
5088
|
return true;
|
|
5071
5089
|
}
|
|
5072
5090
|
return false;
|
|
@@ -5133,14 +5151,23 @@ var createRenderer = (opts) => {
|
|
|
5133
5151
|
isMoving: isMoving2
|
|
5134
5152
|
});
|
|
5135
5153
|
};
|
|
5154
|
+
const getSortedEdgeIds = () => {
|
|
5155
|
+
if (sortedEdgeIdsCache) return sortedEdgeIdsCache;
|
|
5156
|
+
const all = store.getAllEdges();
|
|
5157
|
+
sortedEdgeIdsCache = all.slice().sort((a, b) => a.z - b.z || (a.id < b.id ? -1 : 1)).map((e) => e.id);
|
|
5158
|
+
return sortedEdgeIdsCache;
|
|
5159
|
+
};
|
|
5136
5160
|
const visibleEdges = (viewport) => {
|
|
5137
5161
|
const ids = store.querySpatial({ rect: viewport }).edges;
|
|
5162
|
+
if (ids.length === 0) return [];
|
|
5163
|
+
const visibleSet = new Set(ids);
|
|
5164
|
+
const sorted = getSortedEdgeIds();
|
|
5138
5165
|
const result = [];
|
|
5139
|
-
for (const id of
|
|
5166
|
+
for (const id of sorted) {
|
|
5167
|
+
if (!visibleSet.has(id)) continue;
|
|
5140
5168
|
const e = store.getEdge(id);
|
|
5141
5169
|
if (e) result.push(e);
|
|
5142
5170
|
}
|
|
5143
|
-
result.sort((a, b) => a.z - b.z || (a.id < b.id ? -1 : 1));
|
|
5144
5171
|
return result;
|
|
5145
5172
|
};
|
|
5146
5173
|
const paintInteractive = () => {
|
|
@@ -5288,21 +5315,31 @@ var createRenderer = (opts) => {
|
|
|
5288
5315
|
}
|
|
5289
5316
|
return m;
|
|
5290
5317
|
};
|
|
5318
|
+
const getSortedNodeIds = () => {
|
|
5319
|
+
if (sortedNodeIdsCache) return sortedNodeIdsCache;
|
|
5320
|
+
const all = store.getAllNodes();
|
|
5321
|
+
sortedNodeIdsCache = all.slice().sort((a, b) => a.z - b.z || (a.id < b.id ? -1 : 1)).map((n) => n.id);
|
|
5322
|
+
return sortedNodeIdsCache;
|
|
5323
|
+
};
|
|
5291
5324
|
const visibleNodes = (camera, viewport) => {
|
|
5292
5325
|
const ids = store.querySpatial({ rect: viewport }).nodes;
|
|
5326
|
+
if (ids.length === 0) return [];
|
|
5327
|
+
const visibleSet = new Set(ids);
|
|
5328
|
+
const sorted = getSortedNodeIds();
|
|
5293
5329
|
const result = [];
|
|
5294
5330
|
const minWorldSize = MIN_ON_SCREEN_SIZE_PX / camera.z;
|
|
5295
|
-
for (const id of
|
|
5331
|
+
for (const id of sorted) {
|
|
5332
|
+
if (!visibleSet.has(id)) continue;
|
|
5296
5333
|
const n = store.getNode(id);
|
|
5297
5334
|
if (!n) continue;
|
|
5298
5335
|
if (n.w < minWorldSize && n.h < minWorldSize) continue;
|
|
5299
5336
|
if (intersectsViewport(n, viewport)) result.push(n);
|
|
5300
5337
|
}
|
|
5301
|
-
result.sort((a, b) => a.z - b.z || (a.id < b.id ? -1 : 1));
|
|
5302
5338
|
return result;
|
|
5303
5339
|
};
|
|
5304
5340
|
const loop = createFrameLoop({ draw: drawFrame });
|
|
5305
5341
|
const onStoreChange = () => {
|
|
5342
|
+
invalidateSortedCaches();
|
|
5306
5343
|
staticDirty = true;
|
|
5307
5344
|
interactiveDirty = true;
|
|
5308
5345
|
loop.requestFrame();
|