@glissade/scene 0.10.0-pre.1 → 0.10.1-pre.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.
package/dist/index.d.ts CHANGED
@@ -440,6 +440,10 @@ declare class ShaderEffect extends Group {
440
440
  }
441
441
  //#endregion
442
442
  //#region src/raster2d.d.ts
443
+ /** A backend gradient handle (DOM CanvasGradient and @napi-rs CanvasGradient both satisfy it). */
444
+ interface CanvasGradientLike {
445
+ addColorStop(offset: number, color: string): void;
446
+ }
443
447
  /** The structural path surface buildPath drives — DOM Path2D and @napi-rs Path2D both satisfy it. */
444
448
  interface PathLike {
445
449
  moveTo(x: number, y: number): void;
@@ -468,6 +472,8 @@ interface Ctx2DLike<TPath, TDrawable> {
468
472
  drawImage(image: TDrawable, x: number, y: number, w?: number, h?: number): void;
469
473
  drawImage(image: TDrawable, sx: number, sy: number, sw: number, sh: number, x: number, y: number, w: number, h: number): void;
470
474
  setLineDash(segments: number[]): void;
475
+ createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradientLike;
476
+ createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradientLike;
471
477
  lineDashOffset: number;
472
478
  fillStyle: unknown;
473
479
  strokeStyle: unknown;
package/dist/index.js CHANGED
@@ -967,6 +967,24 @@ var ShaderEffect = class extends Group {
967
967
  * four-line adapters, so the twin rasterizers structurally cannot drift —
968
968
  * the golden + SSIM suites verify the refactor preserved every byte.
969
969
  */
970
+ function resolveFill(ctx, paint, bounds) {
971
+ if (paint.kind === "color") return paint.color;
972
+ let g;
973
+ if (paint.kind === "radial") {
974
+ const cx = paint.center ? paint.center[0] : bounds ? (bounds.minX + bounds.maxX) / 2 : 0;
975
+ const cy = paint.center ? paint.center[1] : bounds ? (bounds.minY + bounds.maxY) / 2 : 0;
976
+ const r = paint.radius !== void 0 ? paint.radius : bounds ? Math.hypot(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2 : 0;
977
+ g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
978
+ } else {
979
+ const fx = paint.from ? paint.from[0] : bounds ? (bounds.minX + bounds.maxX) / 2 : 0;
980
+ const fy = paint.from ? paint.from[1] : bounds ? bounds.minY : 0;
981
+ const tx = paint.to ? paint.to[0] : bounds ? (bounds.minX + bounds.maxX) / 2 : 0;
982
+ const ty = paint.to ? paint.to[1] : bounds ? bounds.maxY : 0;
983
+ g = ctx.createLinearGradient(fx, fy, tx, ty);
984
+ }
985
+ for (const s of paint.stops) g.addColorStop(s.offset, s.color);
986
+ return g;
987
+ }
970
988
  function fontString(font) {
971
989
  return `${font.style === "italic" ? "italic " : ""}${font.weight !== void 0 && font.weight !== 400 ? `${font.weight} ` : ""}${font.size}px ${font.family}`;
972
990
  }
@@ -1260,15 +1278,16 @@ var Raster2D = class {
1260
1278
  break;
1261
1279
  case "fillPath": {
1262
1280
  const ctx = ctxOf();
1263
- ctx.fillStyle = cmd.paint.color;
1264
- ctx.fill(this.path(list.resources, cmd.path));
1265
1281
  const b = this.pathBounds(list.resources, cmd.path);
1282
+ ctx.fillStyle = resolveFill(ctx, cmd.paint, b);
1283
+ ctx.fill(this.path(list.resources, cmd.path));
1266
1284
  if (b) accumulateRect(top(), mat, b.minX, b.minY, b.maxX, b.maxY);
1267
1285
  break;
1268
1286
  }
1269
1287
  case "strokePath": {
1270
1288
  const ctx = ctxOf();
1271
- ctx.strokeStyle = cmd.paint.color;
1289
+ const sb = this.pathBounds(list.resources, cmd.path);
1290
+ ctx.strokeStyle = resolveFill(ctx, cmd.paint, sb);
1272
1291
  ctx.lineWidth = cmd.stroke.width;
1273
1292
  ctx.lineCap = cmd.stroke.cap ?? "butt";
1274
1293
  ctx.lineJoin = cmd.stroke.join ?? "miter";
@@ -1281,17 +1300,16 @@ var Raster2D = class {
1281
1300
  ctx.setLineDash([]);
1282
1301
  ctx.lineDashOffset = 0;
1283
1302
  }
1284
- const b = this.pathBounds(list.resources, cmd.path);
1285
- if (b) {
1303
+ if (sb) {
1286
1304
  const o = cmd.stroke.width * ((cmd.stroke.join ?? "miter") === "miter" ? 5 : 1);
1287
- accumulateRect(top(), mat, b.minX - o, b.minY - o, b.maxX + o, b.maxY + o);
1305
+ accumulateRect(top(), mat, sb.minX - o, sb.minY - o, sb.maxX + o, sb.maxY + o);
1288
1306
  }
1289
1307
  break;
1290
1308
  }
1291
1309
  case "fillText": {
1292
1310
  const ctx = ctxOf();
1293
1311
  ctx.font = fontString(cmd.font);
1294
- ctx.fillStyle = cmd.paint.color;
1312
+ ctx.fillStyle = resolveFill(ctx, cmd.paint, null);
1295
1313
  ctx.textBaseline = "alphabetic";
1296
1314
  ctx.textAlign = cmd.align ?? "left";
1297
1315
  ctx.fillText(cmd.text, cmd.x, cmd.y);
@@ -1,4 +1,4 @@
1
- import { BindableSignal, PathValue, ReadonlySignal, Rng, Track, Vec2, Vec2Signal } from "@glissade/core";
1
+ import { BindableSignal, Paint, PathValue, ReadonlySignal, Rng, Track, Vec2, Vec2Signal } from "@glissade/core";
2
2
 
3
3
  //#region src/matrix.d.ts
4
4
 
@@ -33,11 +33,6 @@ type Resource = {
33
33
  mediaT: number;
34
34
  };
35
35
  type BlendMode = 'source-over' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten';
36
- /** M1: solid colors. Gradients/patterns are additive later — backends switch on kind. */
37
- type Paint = {
38
- kind: 'color';
39
- color: string;
40
- };
41
36
  interface StrokeStyle {
42
37
  width: number;
43
38
  cap?: 'butt' | 'round' | 'square';
@@ -287,6 +282,15 @@ interface NodeProps {
287
282
  * (the cache key folds in the inherited device transform, so a stale CTM can
288
283
  * never blit). OFF by default: a scene that never sets it emits ZERO extra
289
284
  * groups and is byte-identical to before. Best for expensive STATIC subtrees.
285
+ *
286
+ * CAVEAT (when it does NOT help): the key folds in the inherited device
287
+ * transform, so a subtree that itself DRIFTS — e.g. animated on sub-pixel
288
+ * float positions — misses the cache every frame; cache a static subtree
289
+ * under a *moving parent*, not a subtree that moves itself. And a `filter`
290
+ * is a LIVE composite parameter applied on the blit, never baked into the
291
+ * cached bitmap, so `cache:true` on a filter-declaring (e.g. blurred) group
292
+ * does not cache the filter cost. For per-frame-cheap drift, prefer
293
+ * eliminating the work (a cheaper Paint/effect) over caching it.
290
294
  */
291
295
  cache?: boolean;
292
296
  }
@@ -489,8 +493,12 @@ declare class Group extends Node {
489
493
  remove(child: Node): this;
490
494
  protected draw(out: DisplayListBuilder, ctx: EvalContext): void;
491
495
  }
496
+ /** A color string is sugar for a solid `color` Paint; a Paint passes through. */
497
+
492
498
  interface ShapeProps extends NodeProps {
493
- fill?: PropInit<string>;
499
+ /** A CSS color string, or a `Paint` (e.g. a `radial` gradient — soft-light
500
+ * fills with no blur filter; center/radius default to the shape bounds). */
501
+ fill?: PropInit<string | Paint>;
494
502
  stroke?: PropInit<string>;
495
503
  strokeWidth?: PropInit<number>;
496
504
  /** hand-drawn look: the outline is geometrically roughened (see sketch.ts) */
@@ -506,7 +514,7 @@ interface ShapeProps extends NodeProps {
506
514
  sketchFill?: HachureSpec;
507
515
  }
508
516
  declare abstract class Shape extends Node {
509
- readonly fill: BindableSignal<string>;
517
+ readonly fill: BindableSignal<string | Paint>;
510
518
  readonly stroke: BindableSignal<string>;
511
519
  readonly strokeWidth: BindableSignal<number>;
512
520
  readonly sketch: SketchStyle | undefined;
@@ -948,6 +948,13 @@ var Group = class extends Node {
948
948
  for (const child of sorted) child.emit(out, ctx);
949
949
  }
950
950
  };
951
+ /** A color string is sugar for a solid `color` Paint; a Paint passes through. */
952
+ function toPaint(fill) {
953
+ return typeof fill === "string" ? {
954
+ kind: "color",
955
+ color: fill
956
+ } : fill;
957
+ }
951
958
  var Shape = class extends Node {
952
959
  fill;
953
960
  stroke;
@@ -984,10 +991,7 @@ var Shape = class extends Node {
984
991
  if (fill) out.push({
985
992
  op: "fillPath",
986
993
  path,
987
- paint: {
988
- kind: "color",
989
- color: fill
990
- }
994
+ paint: toPaint(fill)
991
995
  });
992
996
  const stroke = this.stroke();
993
997
  const width = this.strokeWidth();
@@ -1021,14 +1025,11 @@ var Shape = class extends Node {
1021
1025
  out.push({
1022
1026
  op: "fillPath",
1023
1027
  path,
1024
- paint: {
1025
- kind: "color",
1026
- color: fill
1027
- }
1028
+ paint: toPaint(fill)
1028
1029
  });
1029
1030
  }
1030
1031
  const { strokes, resolved } = roughen(segs, this.sketch, rng);
1031
- const ink = this.stroke() || fill || "#000000";
1032
+ const ink = this.stroke() || (typeof fill === "string" ? fill : "") || "#000000";
1032
1033
  if (this.sketchFill) {
1033
1034
  const clipPath = out.resource({
1034
1035
  kind: "path",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/scene",
3
- "version": "0.10.0-pre.1",
3
+ "version": "0.10.1-pre.0",
4
4
  "description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "yoga-layout": "^3.2.1",
23
- "@glissade/core": "0.10.0-pre.1"
23
+ "@glissade/core": "0.10.1-pre.0"
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",