@almadar/ui 5.139.0 → 5.141.0

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.
@@ -9167,9 +9167,26 @@ var init_imageCache = __esm({
9167
9167
  });
9168
9168
 
9169
9169
  // lib/webPainter2d.ts
9170
+ function makeNoiseTile(alpha, color) {
9171
+ const tile = document.createElement("canvas");
9172
+ tile.width = NOISE_CELLS;
9173
+ tile.height = NOISE_CELLS;
9174
+ const t = tile.getContext("2d");
9175
+ if (t) {
9176
+ t.fillStyle = color;
9177
+ for (let y = 0; y < NOISE_CELLS; y++) {
9178
+ for (let x = 0; x < NOISE_CELLS; x++) {
9179
+ t.globalAlpha = noiseHash(x, y) * alpha;
9180
+ t.fillRect(x, y, 1, 1);
9181
+ }
9182
+ }
9183
+ }
9184
+ return tile;
9185
+ }
9170
9186
  function createWebPainter(ctx, onAssetLoad) {
9171
9187
  let vw = 0;
9172
9188
  let vh = 0;
9189
+ const patternCache = /* @__PURE__ */ new Map();
9173
9190
  const tracePoly = (points, closed) => {
9174
9191
  if (points.length === 0) return;
9175
9192
  ctx.beginPath();
@@ -9177,6 +9194,31 @@ function createWebPainter(ctx, onAssetLoad) {
9177
9194
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
9178
9195
  if (closed) ctx.closePath();
9179
9196
  };
9197
+ const toCanvasPattern = (style) => {
9198
+ const key = JSON.stringify(style);
9199
+ let pattern = patternCache.get(key);
9200
+ if (pattern === void 0) {
9201
+ if (style.kind === "noise") {
9202
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
9203
+ } else {
9204
+ const img = getOrLoadImage(style.url, onAssetLoad);
9205
+ if (!img) return "rgba(0,0,0,0)";
9206
+ pattern = ctx.createPattern(img, "repeat");
9207
+ }
9208
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
9209
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
9210
+ }
9211
+ patternCache.set(key, pattern);
9212
+ }
9213
+ return pattern ?? "rgba(0,0,0,0)";
9214
+ };
9215
+ const toCanvasStyle = (style) => {
9216
+ if (typeof style === "string") return style;
9217
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
9218
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : style.kind === "conic" ? ctx.createConicGradient(style.angle, style.cx, style.cy) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
9219
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
9220
+ return g;
9221
+ };
9180
9222
  return {
9181
9223
  setViewport(width, height, dpr) {
9182
9224
  vw = width;
@@ -9208,6 +9250,19 @@ function createWebPainter(ctx, onAssetLoad) {
9208
9250
  ctx.shadowColor = shadow ? shadow.color : "transparent";
9209
9251
  ctx.shadowBlur = shadow ? shadow.blur : 0;
9210
9252
  },
9253
+ setBlend(mode) {
9254
+ ctx.globalCompositeOperation = mode ?? "source-over";
9255
+ },
9256
+ setLineDash(pattern, offset = 0) {
9257
+ ctx.setLineDash(pattern ? [...pattern] : []);
9258
+ ctx.lineDashOffset = offset;
9259
+ },
9260
+ setBlur(px) {
9261
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
9262
+ },
9263
+ clipPath(d) {
9264
+ ctx.clip(new Path2D(d));
9265
+ },
9211
9266
  resolveTexture(url) {
9212
9267
  const img = getOrLoadImage(url, onAssetLoad);
9213
9268
  if (!img) return null;
@@ -9230,47 +9285,47 @@ function createWebPainter(ctx, onAssetLoad) {
9230
9285
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
9231
9286
  }
9232
9287
  },
9233
- fillRect(x, y, w, h, color) {
9234
- ctx.fillStyle = color;
9288
+ fillRect(x, y, w, h, style) {
9289
+ ctx.fillStyle = toCanvasStyle(style);
9235
9290
  ctx.fillRect(x, y, w, h);
9236
9291
  },
9237
- strokeRect(x, y, w, h, color, lineWidth = 1) {
9238
- ctx.strokeStyle = color;
9292
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
9293
+ ctx.strokeStyle = toCanvasStyle(style);
9239
9294
  ctx.lineWidth = lineWidth;
9240
9295
  ctx.strokeRect(x, y, w, h);
9241
9296
  },
9242
- fillPoly(points, color) {
9297
+ fillPoly(points, style) {
9243
9298
  if (points.length === 0) return;
9244
9299
  tracePoly(points, true);
9245
- ctx.fillStyle = color;
9300
+ ctx.fillStyle = toCanvasStyle(style);
9246
9301
  ctx.fill();
9247
9302
  },
9248
- strokePoly(points, color, lineWidth = 1, closed = false) {
9303
+ strokePoly(points, style, lineWidth = 1, closed = false) {
9249
9304
  if (points.length === 0) return;
9250
9305
  tracePoly(points, closed);
9251
- ctx.strokeStyle = color;
9306
+ ctx.strokeStyle = toCanvasStyle(style);
9252
9307
  ctx.lineWidth = lineWidth;
9253
9308
  ctx.stroke();
9254
9309
  },
9255
- fillEllipse(cx, cy, rx, ry, color) {
9310
+ fillEllipse(cx, cy, rx, ry, style) {
9256
9311
  ctx.beginPath();
9257
9312
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9258
- ctx.fillStyle = color;
9313
+ ctx.fillStyle = toCanvasStyle(style);
9259
9314
  ctx.fill();
9260
9315
  },
9261
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
9316
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
9262
9317
  ctx.beginPath();
9263
9318
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
9264
- ctx.strokeStyle = color;
9319
+ ctx.strokeStyle = toCanvasStyle(style);
9265
9320
  ctx.lineWidth = lineWidth;
9266
9321
  ctx.stroke();
9267
9322
  },
9268
- fillPath(d, color) {
9269
- ctx.fillStyle = color;
9323
+ fillPath(d, style) {
9324
+ ctx.fillStyle = toCanvasStyle(style);
9270
9325
  ctx.fill(new Path2D(d));
9271
9326
  },
9272
- strokePath(d, color, lineWidth = 1) {
9273
- ctx.strokeStyle = color;
9327
+ strokePath(d, style, lineWidth = 1) {
9328
+ ctx.strokeStyle = toCanvasStyle(style);
9274
9329
  ctx.lineWidth = lineWidth;
9275
9330
  ctx.stroke(new Path2D(d));
9276
9331
  },
@@ -9283,13 +9338,18 @@ function createWebPainter(ctx, onAssetLoad) {
9283
9338
  }
9284
9339
  };
9285
9340
  }
9286
- var handleByImage, imageByHandle;
9341
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
9287
9342
  var init_webPainter2d = __esm({
9288
9343
  "lib/webPainter2d.ts"() {
9289
9344
  "use client";
9290
9345
  init_imageCache();
9291
9346
  handleByImage = /* @__PURE__ */ new WeakMap();
9292
9347
  imageByHandle = /* @__PURE__ */ new WeakMap();
9348
+ noiseHash = (x, y) => {
9349
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
9350
+ return s - Math.floor(s);
9351
+ };
9352
+ NOISE_CELLS = 32;
9293
9353
  }
9294
9354
  });
9295
9355
 
@@ -9445,6 +9505,101 @@ var init_registry = __esm({
9445
9505
  DrawableRegistryContext = createContext(null);
9446
9506
  }
9447
9507
  });
9508
+ function isAnimatedShape(node) {
9509
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
9510
+ }
9511
+ function applyShapeAnimation(node, timeMs) {
9512
+ const anim = node.animation;
9513
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
9514
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
9515
+ const cycle = timeMs / anim.durationMs;
9516
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
9517
+ const out = { ...node };
9518
+ const trackValue = (key) => {
9519
+ const defined = frames.filter((f3) => f3[key] !== void 0);
9520
+ if (defined.length === 0) return void 0;
9521
+ let prev;
9522
+ let next;
9523
+ for (const f3 of defined) {
9524
+ if (f3.at <= t) prev = f3;
9525
+ else if (!next) next = f3;
9526
+ }
9527
+ if (!prev) return defined[0][key];
9528
+ if (!next) return prev[key];
9529
+ const span = next.at - prev.at;
9530
+ const k = span > 0 ? (t - prev.at) / span : 1;
9531
+ const a = prev[key];
9532
+ const b = next[key];
9533
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
9534
+ return a;
9535
+ };
9536
+ for (const key of NUMERIC_TRACKS) {
9537
+ const v = trackValue(key);
9538
+ if (v !== void 0) out[key] = v;
9539
+ }
9540
+ const fill = trackValue("fill");
9541
+ if (fill !== void 0) out.fill = fill;
9542
+ const stroke = trackValue("stroke");
9543
+ if (stroke !== void 0) out.stroke = stroke;
9544
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
9545
+ if (shadowFrames.length > 0) {
9546
+ const sh = trackValue("shadow");
9547
+ if (sh !== void 0) {
9548
+ let prevSh;
9549
+ let nextSh;
9550
+ for (const f3 of shadowFrames) {
9551
+ if (f3.at <= t) prevSh = f3;
9552
+ else if (!nextSh) nextSh = f3;
9553
+ }
9554
+ if (prevSh?.shadow && nextSh?.shadow) {
9555
+ const span = nextSh.at - prevSh.at;
9556
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
9557
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
9558
+ } else {
9559
+ out.shadow = sh;
9560
+ }
9561
+ }
9562
+ }
9563
+ return out;
9564
+ }
9565
+ function gradientStyle(g, ox, oy, scale) {
9566
+ if (g.kind === "linear") {
9567
+ if (!g.from || !g.to) return void 0;
9568
+ return {
9569
+ kind: "linear",
9570
+ x1: ox + g.from.x * scale,
9571
+ y1: oy + g.from.y * scale,
9572
+ x2: ox + g.to.x * scale,
9573
+ y2: oy + g.to.y * scale,
9574
+ stops: g.stops
9575
+ };
9576
+ }
9577
+ if (g.kind === "conic") {
9578
+ if (!g.center) return void 0;
9579
+ return {
9580
+ kind: "conic",
9581
+ cx: ox + g.center.x * scale,
9582
+ cy: oy + g.center.y * scale,
9583
+ angle: g.angle ?? 0,
9584
+ stops: g.stops
9585
+ };
9586
+ }
9587
+ if (!g.center || g.radius === void 0) return void 0;
9588
+ return {
9589
+ kind: "radial",
9590
+ cx: ox + g.center.x * scale,
9591
+ cy: oy + g.center.y * scale,
9592
+ r: g.radius * scale,
9593
+ stops: g.stops
9594
+ };
9595
+ }
9596
+ function patternStyle(p, unit) {
9597
+ if (p.kind === "image") {
9598
+ if (!p.url) return void 0;
9599
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9600
+ }
9601
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9602
+ }
9448
9603
  function DrawShape(props) {
9449
9604
  const register = useContext(DrawableRegistryContext);
9450
9605
  if (register) {
@@ -9455,25 +9610,66 @@ function DrawShape(props) {
9455
9610
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
9456
9611
  };
9457
9612
  register(node);
9458
- return /* @__PURE__ */ jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
9459
9613
  }
9460
9614
  return null;
9461
9615
  }
9462
- var paintShape;
9616
+ var NUMERIC_TRACKS, lerp, paintShape;
9463
9617
  var init_DrawShape = __esm({
9464
9618
  "components/game/atoms/DrawShape.tsx"() {
9465
9619
  "use client";
9466
9620
  init_contract();
9467
9621
  init_registry();
9468
- paintShape = (painter, node, dctx) => {
9622
+ NUMERIC_TRACKS = [
9623
+ "offsetX",
9624
+ "offsetY",
9625
+ "rotate",
9626
+ "opacity",
9627
+ "radiusX",
9628
+ "radiusY",
9629
+ "width",
9630
+ "height",
9631
+ "strokeWidth",
9632
+ "strokeDashOffset",
9633
+ "blur"
9634
+ ];
9635
+ lerp = (a, b, k) => a + (b - a) * k;
9636
+ paintShape = (painter, rawNode, dctx) => {
9637
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
9469
9638
  if (!isValidScenePos(node.position)) return;
9470
9639
  painter.save();
9471
9640
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9641
+ const origin = dctx.projector.project(node.position);
9642
+ const tileWidth = dctx.projector.tileWidth;
9643
+ const groupScale = dctx.groupScale ?? 1;
9644
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9645
+ if (node.blendMode) painter.setBlend(node.blendMode);
9646
+ if (node.strokeDash && node.strokeDash.length > 0) {
9647
+ painter.setLineDash(
9648
+ node.strokeDash.map((v) => v / groupScale),
9649
+ (node.strokeDashOffset ?? 0) / groupScale
9650
+ );
9651
+ }
9652
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9653
+ if (node.rotate) {
9654
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9655
+ const px = origin.x + pivot.x * tileWidth;
9656
+ const py = origin.y + pivot.y * tileWidth;
9657
+ painter.translate(px, py);
9658
+ painter.rotate(node.rotate);
9659
+ painter.translate(-px, -py);
9660
+ }
9661
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9662
+ const fill = node.fill === "none" ? void 0 : node.fill;
9663
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
9664
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9665
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9666
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9472
9667
  switch (node.shape) {
9473
9668
  case "cell": {
9474
9669
  const pts = dctx.projector.cellPath(node.position);
9475
- if (node.fill) painter.fillPoly(pts, node.fill);
9476
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9670
+ if (pxFill) painter.fillPoly(pts, pxFill);
9671
+ if (patFill) painter.fillPoly(pts, patFill);
9672
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9477
9673
  break;
9478
9674
  }
9479
9675
  case "rect": {
@@ -9483,8 +9679,9 @@ var init_DrawShape = __esm({
9483
9679
  const y = p.y + (node.offsetY ?? 0) * tw;
9484
9680
  const w = (node.width ?? 0) * tw;
9485
9681
  const h = (node.height ?? 0) * tw;
9486
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
9487
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
9682
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9683
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9684
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9488
9685
  break;
9489
9686
  }
9490
9687
  case "ellipse": {
@@ -9494,26 +9691,38 @@ var init_DrawShape = __esm({
9494
9691
  const cy = p.y + (node.offsetY ?? 0) * tw;
9495
9692
  const rx = (node.radiusX ?? 0) * tw;
9496
9693
  const ry = (node.radiusY ?? rx) * tw;
9497
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
9498
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
9694
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9695
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9696
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9499
9697
  break;
9500
9698
  }
9501
9699
  case "poly": {
9502
- const base = dctx.projector.project(node.position);
9503
- const tw = dctx.projector.tileWidth;
9504
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
9505
- if (node.fill) painter.fillPoly(pts, node.fill);
9506
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9700
+ const pts = (node.points ?? []).map((pt) => ({
9701
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
9702
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9703
+ }));
9704
+ if (pxFill) painter.fillPoly(pts, pxFill);
9705
+ if (patFill) painter.fillPoly(pts, patFill);
9706
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9507
9707
  break;
9508
9708
  }
9509
9709
  case "path": {
9510
9710
  if (!node.d) break;
9511
- const base = dctx.projector.project(node.position);
9512
- const tw = dctx.projector.tileWidth;
9513
- painter.translate(base.x, base.y);
9514
- painter.scale(tw, tw);
9515
- if (node.fill) painter.fillPath(node.d, node.fill);
9516
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9711
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9712
+ painter.scale(tileWidth, tileWidth);
9713
+ if (node.strokeDash && node.strokeDash.length > 0) {
9714
+ painter.setLineDash(
9715
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9716
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9717
+ );
9718
+ }
9719
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9720
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9721
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9722
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9723
+ if (localFill) painter.fillPath(node.d, localFill);
9724
+ if (localPat) painter.fillPath(node.d, localPat);
9725
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9517
9726
  break;
9518
9727
  }
9519
9728
  }
@@ -9594,8 +9803,6 @@ var init_DrawTextLayer = __esm({
9594
9803
  };
9595
9804
  }
9596
9805
  });
9597
-
9598
- // lib/drawable/paintDispatch.ts
9599
9806
  function paintDrawable(painter, node, dctx) {
9600
9807
  switch (node.type) {
9601
9808
  case "draw-sprite":
@@ -9616,10 +9823,20 @@ function paintDrawable(painter, node, dctx) {
9616
9823
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9617
9824
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9618
9825
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9619
- for (const item of node.items) paintDrawable(painter, item, dctx);
9826
+ if (node.clip) {
9827
+ const tw = dctx.projector.tileWidth;
9828
+ painter.scale(tw, tw);
9829
+ painter.clipPath(node.clip);
9830
+ painter.scale(1 / tw, 1 / tw);
9831
+ }
9832
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9833
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9620
9834
  painter.restore();
9621
9835
  break;
9622
9836
  }
9837
+ case "draw-mesh":
9838
+ warnUnsupported2d("draw-mesh");
9839
+ break;
9623
9840
  case "draw-sprite-layer":
9624
9841
  paintSpriteLayer(painter, node, dctx);
9625
9842
  break;
@@ -9631,6 +9848,7 @@ function paintDrawable(painter, node, dctx) {
9631
9848
  break;
9632
9849
  }
9633
9850
  }
9851
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9634
9852
  var init_paintDispatch = __esm({
9635
9853
  "lib/drawable/paintDispatch.ts"() {
9636
9854
  init_contract();
@@ -9640,6 +9858,13 @@ var init_paintDispatch = __esm({
9640
9858
  init_DrawSpriteLayer();
9641
9859
  init_DrawShapeLayer();
9642
9860
  init_DrawTextLayer();
9861
+ paint2dLog = createLogger("almadar:ui:drawable-2d");
9862
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9863
+ warnUnsupported2d = (kind) => {
9864
+ if (warnedUnsupported2d.has(kind)) return;
9865
+ warnedUnsupported2d.add(kind);
9866
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9867
+ };
9643
9868
  }
9644
9869
  });
9645
9870
 
@@ -9654,6 +9879,7 @@ function collectDrawnItems(nodes) {
9654
9879
  case "draw-shape":
9655
9880
  case "draw-text":
9656
9881
  case "draw-group":
9882
+ case "draw-mesh":
9657
9883
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9658
9884
  break;
9659
9885
  case "draw-sprite-layer":
@@ -9725,7 +9951,6 @@ function Canvas2D({
9725
9951
  childDrawablesRef.current.push(node);
9726
9952
  }, []);
9727
9953
  const hasJsxChildren = React84.Children.count(children) > 0;
9728
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
9729
9954
  function isDrawableLayer(node) {
9730
9955
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
9731
9956
  }
@@ -9874,11 +10099,24 @@ function Canvas2D({
9874
10099
  }, [showMinimap, scenePositions]);
9875
10100
  const miniMapWidth = gridExtent.width || 10;
9876
10101
  const miniMapHeight = gridExtent.height || 10;
9877
- const draw = useCallback(() => {
10102
+ const drawableIsAnimated = (node) => {
10103
+ if (node.type === "draw-shape") return isAnimatedShape(node);
10104
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10105
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10106
+ return false;
10107
+ };
10108
+ const animRafRef = useRef(0);
10109
+ const drawTimeRef = useRef(() => void 0);
10110
+ const draw = useCallback((timeMs = 0) => {
9878
10111
  const canvas = canvasRef.current;
9879
10112
  if (!canvas) return;
9880
10113
  const ctx = canvas.getContext("2d");
9881
10114
  if (!ctx) return;
10115
+ const scheduleAnimation = (nodes) => {
10116
+ if (!nodes.some(drawableIsAnimated)) return;
10117
+ cancelAnimationFrame(animRafRef.current);
10118
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
10119
+ };
9882
10120
  const dpr = window.devicePixelRatio || 1;
9883
10121
  canvas.width = viewportSize.width * dpr;
9884
10122
  canvas.height = viewportSize.height * dpr;
@@ -9911,14 +10149,24 @@ function Canvas2D({
9911
10149
  if (!drawables || drawables.length === 0) {
9912
10150
  const childDrawables = childDrawablesRef.current;
9913
10151
  if (childDrawables.length === 0) return;
10152
+ const cam0 = cameraRef.current;
10153
+ if (camera !== "follow" && dragDistance() === 0) {
10154
+ const focus = cameraPos ?? defaultGridFocus;
10155
+ if (focus) {
10156
+ const p = projector.anchorPoint(focus, "center");
10157
+ cam0.x = p.x - viewportSize.width / 2;
10158
+ cam0.y = p.y - viewportSize.height / 2;
10159
+ }
10160
+ }
9914
10161
  const painter0 = createWebPainter(ctx, bumpAtlas);
9915
10162
  painter0.save();
9916
10163
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
9917
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
9918
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
9919
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
10164
+ painter0.scale(cam0.zoom, cam0.zoom);
10165
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
10166
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
9920
10167
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
9921
10168
  painter0.restore();
10169
+ scheduleAnimation(childDrawables);
9922
10170
  return;
9923
10171
  }
9924
10172
  const cam = cameraRef.current;
@@ -9938,10 +10186,15 @@ function Canvas2D({
9938
10186
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
9939
10187
  painter.scale(cam.zoom, cam.zoom);
9940
10188
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
9941
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
10189
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
9942
10190
  for (const node of drawables) paintDrawable(painter, node, dctx);
9943
10191
  painter.restore();
10192
+ scheduleAnimation(drawables);
9944
10193
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
10194
+ useEffect(() => {
10195
+ drawTimeRef.current = draw;
10196
+ }, [draw]);
10197
+ useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
9945
10198
  useEffect(() => {
9946
10199
  if (camera !== "follow" || !followTarget) return;
9947
10200
  const p = projector.anchorPoint(followTarget, "center");
@@ -10164,15 +10417,7 @@ function Canvas2D({
10164
10417
  mapHeight: miniMapHeight
10165
10418
  }
10166
10419
  ) }),
10167
- hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
10168
- /* @__PURE__ */ jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
10169
- "C=",
10170
- React84.Children.count(children),
10171
- " J=",
10172
- String(hasJsxChildren),
10173
- " D=",
10174
- drawables?.length ?? -1
10175
- ] })
10420
+ hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
10176
10421
  ]
10177
10422
  }
10178
10423
  ) });
@@ -10197,6 +10442,7 @@ var init_Canvas2D = __esm({
10197
10442
  init_webPainter2d();
10198
10443
  init_projector();
10199
10444
  init_paintDispatch();
10445
+ init_DrawShape();
10200
10446
  init_registry();
10201
10447
  init_hitTest();
10202
10448
  init_isometric();
@@ -10251,6 +10497,7 @@ function Canvas({
10251
10497
  isLoading,
10252
10498
  cameraMode: to3DCameraMode(camera?.mode),
10253
10499
  ...zoom !== void 0 ? { scale: zoom } : {},
10500
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10254
10501
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10255
10502
  unitScale,
10256
10503
  backgroundColor,
@@ -38075,14 +38322,26 @@ var init_DetailPanel = __esm({
38075
38322
  DetailPanel.displayName = "DetailPanel";
38076
38323
  }
38077
38324
  });
38078
-
38079
- // components/game/atoms/DrawGroup.tsx
38080
- function DrawGroup(_props) {
38325
+ function DrawGroup(props) {
38326
+ const register = useContext(DrawableRegistryContext);
38327
+ if (register) register({ ...props, type: "draw-group" });
38081
38328
  return null;
38082
38329
  }
38083
38330
  var init_DrawGroup = __esm({
38084
38331
  "components/game/atoms/DrawGroup.tsx"() {
38085
38332
  "use client";
38333
+ init_registry();
38334
+ }
38335
+ });
38336
+ function DrawMesh(props) {
38337
+ const register = useContext(DrawableRegistryContext);
38338
+ if (register) register({ ...props, type: "draw-mesh" });
38339
+ return null;
38340
+ }
38341
+ var init_DrawMesh = __esm({
38342
+ "components/game/atoms/DrawMesh.tsx"() {
38343
+ "use client";
38344
+ init_registry();
38086
38345
  }
38087
38346
  });
38088
38347
  function extractTitle(children) {
@@ -43808,6 +44067,7 @@ var init_component_registry_generated = __esm({
43808
44067
  init_DocTOC();
43809
44068
  init_DocumentViewer();
43810
44069
  init_DrawGroup();
44070
+ init_DrawMesh();
43811
44071
  init_DrawShape();
43812
44072
  init_DrawShapeLayer();
43813
44073
  init_DrawSprite();
@@ -44076,6 +44336,7 @@ var init_component_registry_generated = __esm({
44076
44336
  "DocTOC": DocTOC,
44077
44337
  "DocumentViewer": DocumentViewer,
44078
44338
  "DrawGroup": DrawGroup,
44339
+ "DrawMesh": DrawMesh,
44079
44340
  "DrawShape": DrawShape,
44080
44341
  "DrawShapeLayer": DrawShapeLayer,
44081
44342
  "DrawSprite": DrawSprite,
@@ -44984,8 +45245,13 @@ function SlotContentRenderer({
44984
45245
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
44985
45246
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
44986
45247
  const isDrawHost = isDrawHostPattern(content.pattern);
45248
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
45249
+ const hasTraitChildren = arr.some(
45250
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
45251
+ );
45252
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
44987
45253
  const myPath = patternPath ?? "root";
44988
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45254
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
44989
45255
  slot: content.slot,
44990
45256
  transitionEvent: content.transitionEvent,
44991
45257
  fromState: content.fromState,
@@ -45046,7 +45312,7 @@ function SlotContentRenderer({
45046
45312
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
45047
45313
  finalProps[k] = v;
45048
45314
  }
45049
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45315
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45050
45316
  finalProps.drawables = toDrawableNodes(childrenConfig);
45051
45317
  }
45052
45318
  const entityVal = finalProps.entity;
@@ -45225,6 +45491,8 @@ var init_UISlotRenderer = __esm({
45225
45491
  "vstack",
45226
45492
  "hstack",
45227
45493
  "box",
45494
+ "canvas",
45495
+ "canvas-2d",
45228
45496
  "grid",
45229
45497
  "center",
45230
45498
  "card",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.139.0",
3
+ "version": "5.141.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.45.0",
121
+ "@almadar/core": "^10.47.0",
122
122
  "@almadar/evaluator": "^2.38.0",
123
123
  "@almadar/logger": "^1.10.0",
124
- "@almadar/runtime": "^6.47.0",
125
- "@almadar/std": "^16.155.0",
124
+ "@almadar/runtime": "^6.48.0",
125
+ "@almadar/std": "^16.157.0",
126
126
  "@almadar/syntax": "^1.13.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",