@canvas-harness/core 0.1.19 → 0.1.21

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 CHANGED
@@ -4754,6 +4754,59 @@ var buildPath = (type, x, y, w, h, radius) => {
4754
4754
  }
4755
4755
  };
4756
4756
 
4757
+ // src/render/scene-cache-math.ts
4758
+ var computeCacheSourceRect = (cache5, view) => {
4759
+ const ratio = cache5.camZ / view.camZ;
4760
+ const srcX = Math.round(((view.camX - cache5.camX) * cache5.camZ + cache5.marginCssPx) * cache5.dpr);
4761
+ const srcY = Math.round(((view.camY - cache5.camY) * cache5.camZ + cache5.marginCssPx) * cache5.dpr);
4762
+ const srcW = view.widthCssPx * ratio * cache5.dpr;
4763
+ const srcH = view.heightCssPx * ratio * cache5.dpr;
4764
+ return { srcX, srcY, srcW, srcH };
4765
+ };
4766
+ var cacheCoversViewport = (cache5, view) => {
4767
+ const { srcX, srcY, srcW, srcH } = computeCacheSourceRect(cache5, view);
4768
+ return srcX >= 0 && srcY >= 0 && srcX + srcW <= cache5.widthDevicePx && srcY + srcH <= cache5.heightDevicePx;
4769
+ };
4770
+ var scaleRatioInBounds = (cacheCamZ, viewCamZ, maxRatio) => {
4771
+ if (viewCamZ <= 0 || cacheCamZ <= 0 || maxRatio <= 0) return false;
4772
+ const ratio = viewCamZ >= cacheCamZ ? viewCamZ / cacheCamZ : cacheCamZ / viewCamZ;
4773
+ return ratio <= maxRatio;
4774
+ };
4775
+ var cacheReuseLayout = (cache5, view) => {
4776
+ const ratio = view.camZ / cache5.camZ;
4777
+ const cacheW = cache5.widthDevicePx;
4778
+ const cacheH = cache5.heightDevicePx;
4779
+ const marginDev = cache5.marginCssPx * cache5.dpr;
4780
+ const destW = cacheW * ratio;
4781
+ const destH = cacheH * ratio;
4782
+ const destX = (cache5.camX - view.camX) * view.camZ * cache5.dpr + marginDev * (1 - ratio);
4783
+ const destY = (cache5.camY - view.camY) * view.camZ * cache5.dpr + marginDev * (1 - ratio);
4784
+ const dest = { x: destX, y: destY, w: destW, h: destH };
4785
+ const strips = {
4786
+ top: { x: 0, y: 0, w: cacheW, h: Math.max(0, destY) },
4787
+ bottom: {
4788
+ x: 0,
4789
+ y: destY + destH,
4790
+ w: cacheW,
4791
+ h: Math.max(0, cacheH - destY - destH)
4792
+ },
4793
+ left: { x: 0, y: destY, w: Math.max(0, destX), h: destH },
4794
+ right: {
4795
+ x: destX + destW,
4796
+ y: destY,
4797
+ w: Math.max(0, cacheW - destX - destW),
4798
+ h: destH
4799
+ }
4800
+ };
4801
+ const valid = destX >= 0 && destY >= 0 && destX + destW <= cacheW && destY + destH <= cacheH;
4802
+ return { dest, strips, valid };
4803
+ };
4804
+ var zoomExtendRatioInBounds = (cacheCamZ, viewCamZ, minRatio) => {
4805
+ if (viewCamZ <= 0 || cacheCamZ <= 0 || minRatio <= 0 || minRatio >= 1) return false;
4806
+ const ratio = viewCamZ / cacheCamZ;
4807
+ return ratio >= minRatio && ratio < 1;
4808
+ };
4809
+
4757
4810
  // src/render/shapes/content-bounds.ts
4758
4811
  var SQRT2_INV = 1 / Math.SQRT2;
4759
4812
  var contentBounds = (node) => {
@@ -4838,6 +4891,7 @@ var createRenderer = (opts) => {
4838
4891
  let interactiveDirty = false;
4839
4892
  let overlaySet = /* @__PURE__ */ new Set();
4840
4893
  let lastDrawn = 0;
4894
+ let lastDrawPath = "idle";
4841
4895
  let cacheSurface = null;
4842
4896
  let cacheCamX = 0;
4843
4897
  let cacheCamY = 0;
@@ -4907,9 +4961,8 @@ var createRenderer = (opts) => {
4907
4961
  theme: (token) => theme ? theme(token) : void 0
4908
4962
  };
4909
4963
  const editingNodeId = interaction.editingTarget?.kind === "node" ? interaction.editingTarget.id : null;
4910
- const cameraIsMoving = interaction.mode === "panning" || interaction.mode === "zooming";
4911
4964
  const movingNodeCount = excludedNodes?.size ?? 0;
4912
- const roughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visible.length <= ROUGH_MAX_NODES;
4965
+ const roughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visible.length <= ROUGH_MAX_NODES;
4913
4966
  if (!hideFrames) {
4914
4967
  for (const node of visible) {
4915
4968
  if (node.type !== "frame") continue;
@@ -5005,7 +5058,7 @@ var createRenderer = (opts) => {
5005
5058
  }
5006
5059
  }
5007
5060
  const visEdges = visibleEdges(viewport);
5008
- const edgeRoughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
5061
+ const edgeRoughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
5009
5062
  for (const edge of visEdges) {
5010
5063
  if (excludedEdges?.has(edge.id)) continue;
5011
5064
  paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
@@ -5085,6 +5138,44 @@ var createRenderer = (opts) => {
5085
5138
  if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
5086
5139
  if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
5087
5140
  };
5141
+ let scaledExtendScratch = null;
5142
+ const extendCacheScaled = (camera, layout) => {
5143
+ const cache5 = ensureCacheSurface();
5144
+ const cacheW = cache5.canvas.width;
5145
+ const cacheH = cache5.canvas.height;
5146
+ if (!scaledExtendScratch || scaledExtendScratch.width !== cacheW || scaledExtendScratch.height !== cacheH) {
5147
+ scaledExtendScratch = document.createElement("canvas");
5148
+ scaledExtendScratch.width = cacheW;
5149
+ scaledExtendScratch.height = cacheH;
5150
+ }
5151
+ const sctx = scaledExtendScratch.getContext("2d");
5152
+ sctx.setTransform(1, 0, 0, 1, 0, 0);
5153
+ sctx.clearRect(0, 0, cacheW, cacheH);
5154
+ sctx.drawImage(cache5.canvas, 0, 0);
5155
+ cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
5156
+ cache5.ctx.clearRect(0, 0, cacheW, cacheH);
5157
+ cache5.ctx.drawImage(
5158
+ scaledExtendScratch,
5159
+ layout.dest.x,
5160
+ layout.dest.y,
5161
+ layout.dest.w,
5162
+ layout.dest.h
5163
+ );
5164
+ cacheCamX = camera.x;
5165
+ cacheCamY = camera.y;
5166
+ cacheCamZ = camera.z;
5167
+ const strips = [
5168
+ layout.strips.top,
5169
+ layout.strips.bottom,
5170
+ layout.strips.left,
5171
+ layout.strips.right
5172
+ ];
5173
+ for (const s of strips) {
5174
+ if (s.w > 0 && s.h > 0) {
5175
+ renderCacheStrip(cache5, camera.x, camera.y, camera.z, s.x, s.y, s.w, s.h);
5176
+ }
5177
+ }
5178
+ };
5088
5179
  const cacheSourceOffset = (camera) => {
5089
5180
  const dpr = staticSurface.dpr;
5090
5181
  return {
@@ -5106,21 +5197,72 @@ var createRenderer = (opts) => {
5106
5197
  staticSurface.ctx.clearRect(0, 0, w, h);
5107
5198
  staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
5108
5199
  };
5200
+ const snapshotCacheCamera = (cache5) => ({
5201
+ camX: cacheCamX,
5202
+ camY: cacheCamY,
5203
+ camZ: cacheCamZ,
5204
+ widthDevicePx: cache5.canvas.width,
5205
+ heightDevicePx: cache5.canvas.height,
5206
+ dpr: cache5.dpr,
5207
+ marginCssPx: SCENE_CACHE_MARGIN_PX
5208
+ });
5209
+ const snapshotView = (camera) => ({
5210
+ camX: camera.x,
5211
+ camY: camera.y,
5212
+ camZ: camera.z,
5213
+ widthCssPx: staticSurface.cssWidth,
5214
+ heightCssPx: staticSurface.cssHeight
5215
+ });
5216
+ const presentStaticScaled = (camera) => {
5217
+ const cache5 = ensureCacheSurface();
5218
+ const w = staticSurface.canvas.width;
5219
+ const h = staticSurface.canvas.height;
5220
+ const { srcX, srcY, srcW, srcH } = computeCacheSourceRect(
5221
+ snapshotCacheCamera(cache5),
5222
+ snapshotView(camera)
5223
+ );
5224
+ staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
5225
+ staticSurface.ctx.clearRect(0, 0, w, h);
5226
+ staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, srcW, srcH, 0, 0, w, h);
5227
+ };
5228
+ const SCALED_BLIT_MAX_RATIO = 4;
5229
+ const SCALED_EXTEND_MIN_RATIO = 0.5;
5109
5230
  const paintStatic = () => {
5110
5231
  const camera = store.getCamera();
5111
5232
  if (!cacheStale && camera.z === cacheCamZ) {
5112
5233
  if (viewportFitsInCache(camera)) {
5113
5234
  presentStatic(camera);
5235
+ lastDrawPath = "present";
5114
5236
  return;
5115
5237
  }
5116
5238
  if (canExtend(camera)) {
5117
5239
  extendCache(camera);
5118
5240
  presentStatic(camera);
5241
+ lastDrawPath = "extend";
5242
+ return;
5243
+ }
5244
+ }
5245
+ if (!cacheStale && camera.z !== cacheCamZ && store.getInteractionState().mode === "zooming" && cacheSurface) {
5246
+ const cacheCam = snapshotCacheCamera(cacheSurface);
5247
+ const view = snapshotView(camera);
5248
+ if (scaleRatioInBounds(cacheCam.camZ, view.camZ, SCALED_BLIT_MAX_RATIO) && cacheCoversViewport(cacheCam, view)) {
5249
+ presentStaticScaled(camera);
5250
+ lastDrawPath = "scaled";
5119
5251
  return;
5120
5252
  }
5253
+ if (zoomExtendRatioInBounds(cacheCam.camZ, view.camZ, SCALED_EXTEND_MIN_RATIO)) {
5254
+ const layout = cacheReuseLayout(cacheCam, view);
5255
+ if (layout.valid) {
5256
+ extendCacheScaled(camera, layout);
5257
+ presentStatic(camera);
5258
+ lastDrawPath = "scaled-extend";
5259
+ return;
5260
+ }
5261
+ }
5121
5262
  }
5122
5263
  renderFullCache(camera);
5123
5264
  presentStatic(camera);
5265
+ lastDrawPath = "full";
5124
5266
  };
5125
5267
  const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
5126
5268
  if (def.getSnapshot) {
@@ -5443,9 +5585,11 @@ var createRenderer = (opts) => {
5443
5585
  };
5444
5586
  const onInteractionChange = (state) => {
5445
5587
  interactiveDirty = true;
5446
- if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "zooming" || state.mode === "idle") {
5588
+ if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "idle") {
5447
5589
  staticDirty = true;
5448
5590
  cacheStale = true;
5591
+ } else if (state.mode === "zooming") {
5592
+ staticDirty = true;
5449
5593
  }
5450
5594
  loop.requestFrame();
5451
5595
  };
@@ -5509,6 +5653,7 @@ var createRenderer = (opts) => {
5509
5653
  },
5510
5654
  stats: () => loop.stats(),
5511
5655
  lastDrawCount: () => lastDrawn,
5656
+ getLastDrawPath: () => lastDrawPath,
5512
5657
  getOverlaySet: () => [...overlaySet],
5513
5658
  getAssetCache: () => assetCache,
5514
5659
  dispose() {