@weasel-js/core 0.8.0 → 1.0.1
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/CHANGELOG.md +748 -0
- package/dist/{DrawCommand-BKJ5JPkC.d.ts → DrawCommand-DCmiNkBj.d.ts} +14 -169
- package/dist/{chunk-GLZLSPGJ.js → chunk-6SHZHXXR.js} +46 -9
- package/dist/chunk-6SHZHXXR.js.map +1 -0
- package/dist/chunk-DCIU3SRK.js +505 -0
- package/dist/chunk-DCIU3SRK.js.map +1 -0
- package/dist/{chunk-DLV5F4NY.js → chunk-FVNWK7U3.js} +1525 -763
- package/dist/chunk-FVNWK7U3.js.map +1 -0
- package/dist/chunk-IO5Z75X4.js +167 -0
- package/dist/chunk-IO5Z75X4.js.map +1 -0
- package/dist/{chunk-BPRHGTLQ.js → chunk-UB6A6L77.js} +3 -3
- package/dist/{chunk-BPRHGTLQ.js.map → chunk-UB6A6L77.js.map} +1 -1
- package/dist/{chunk-CMBE45MT.js → chunk-Z3KNFTTS.js} +6 -6
- package/dist/chunk-Z3KNFTTS.js.map +1 -0
- package/dist/clipboard.d.ts +1 -1
- package/dist/{index-Clf_koyR.d.ts → index-DPI8DV0A.d.ts} +104 -91
- package/dist/index.d.ts +352 -43
- package/dist/index.js +6 -6
- package/dist/insert.js +5 -5
- package/dist/insert.js.map +1 -1
- package/dist/move.js +2 -2
- package/dist/paint-types-CnLIzqq1.d.ts +261 -0
- package/dist/patterns-builtin.d.ts +57 -2
- package/dist/patterns-builtin.js +1 -97
- package/dist/patterns-builtin.js.map +1 -1
- package/dist/renderer.d.ts +76 -15
- package/dist/renderer.js +6 -6
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +5 -5
- package/dist/routing.js +1 -1
- package/dist/{types-DUpI7Apc.d.ts → types-Kh_osAq9.d.ts} +8 -1
- package/dist/{viewToMat3-DFMdZvVl.d.ts → viewToMat3-BvaWCN99.d.ts} +1 -1
- package/package.json +6 -6
- package/dist/chunk-6DAUIMTO.js +0 -233
- package/dist/chunk-6DAUIMTO.js.map +0 -1
- package/dist/chunk-CMBE45MT.js.map +0 -1
- package/dist/chunk-DLV5F4NY.js.map +0 -1
- package/dist/chunk-GLZLSPGJ.js.map +0 -1
- package/dist/chunk-Y52N27PF.js +0 -39
- package/dist/chunk-Y52N27PF.js.map +0 -1
- package/dist/registerTexture-BzHTLhD9.d.ts +0 -25
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { defineViewportTool,
|
|
2
|
-
import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-
|
|
3
|
-
import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-
|
|
4
|
-
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit,
|
|
1
|
+
import { ActionDisabledReason, defineViewportTool, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, defineTool, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-DCIU3SRK.js';
|
|
2
|
+
import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-UB6A6L77.js';
|
|
3
|
+
import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-Z3KNFTTS.js';
|
|
4
|
+
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, withinPxRadius, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, translatePath, pxExtent, remapRotatedLeaf, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-6SHZHXXR.js';
|
|
5
5
|
import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-775XXAHR.js';
|
|
6
6
|
import { createSetSelectionOp } from './chunk-BHVYVFGV.js';
|
|
7
7
|
import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-GVCNT7UH.js';
|
|
8
|
-
import { getTexture } from './chunk-
|
|
8
|
+
import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-IO5Z75X4.js';
|
|
9
9
|
import { createContext, createElement, forwardRef, useMemo, useRef, useState, useCallback, useImperativeHandle, useEffect, useSyncExternalStore, useContext, useReducer, useLayoutEffect } from 'react';
|
|
10
10
|
import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget, glyphGeneration, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, resolveFontVariant, resolveGlyphFallback, glyphOutline } from '@weasel-js/font';
|
|
11
11
|
export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
|
|
12
12
|
import earcut from 'earcut';
|
|
13
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
13
|
import { applyToPoint, rotateAboutPoint, boxToBox, pointInPolygon, segmentsCross } from '@weasel-js/geom';
|
|
14
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
15
15
|
import polygonClipping from 'polygon-clipping';
|
|
16
|
-
import { matchSpec, mimeMatchesGlob } from '@weasel-js/gestures';
|
|
16
|
+
import { matchSpec, parseTargetSpec, mimeMatchesGlob } from '@weasel-js/gestures';
|
|
17
17
|
import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
|
|
18
18
|
import { createHistory } from '@weasel-js/history';
|
|
19
19
|
export * from '@weasel-js/history';
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var VERSION = "0.
|
|
22
|
+
var VERSION = "1.0.1" ;
|
|
23
23
|
|
|
24
24
|
// src/features/grid/roundToCell.ts
|
|
25
25
|
function roundToCell(value, cellSize) {
|
|
@@ -333,6 +333,60 @@ var GRAD_FILL_UNIFORMS = [
|
|
|
333
333
|
];
|
|
334
334
|
var GRAD_FILL_ATTRIBUTES = ["a_position"];
|
|
335
335
|
|
|
336
|
+
// src/renderer/shaders/patternFill.ts
|
|
337
|
+
var PATTERN_VERT_SRC = (
|
|
338
|
+
/* glsl */
|
|
339
|
+
`#version 300 es
|
|
340
|
+
in vec2 a_position;
|
|
341
|
+
uniform mat3 u_proj;
|
|
342
|
+
uniform mat3 u_model;
|
|
343
|
+
uniform mat3 u_worldInv;
|
|
344
|
+
out vec2 v_world;
|
|
345
|
+
void main() {
|
|
346
|
+
vec3 screen = u_model * vec3(a_position, 1.0);
|
|
347
|
+
vec3 clip = u_proj * vec3(screen.xy, 1.0);
|
|
348
|
+
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
|
349
|
+
vec3 world = u_worldInv * vec3(screen.xy, 1.0);
|
|
350
|
+
v_world = world.xy;
|
|
351
|
+
}
|
|
352
|
+
`
|
|
353
|
+
);
|
|
354
|
+
var PATTERN_FRAG_SRC = (
|
|
355
|
+
/* glsl */
|
|
356
|
+
`#version 300 es
|
|
357
|
+
precision highp float;
|
|
358
|
+
in vec2 v_world;
|
|
359
|
+
uniform sampler2D u_sampler;
|
|
360
|
+
uniform vec2 u_tileOrigin;
|
|
361
|
+
uniform vec2 u_tileSize;
|
|
362
|
+
uniform float u_opacity;
|
|
363
|
+
uniform float u_alpha;
|
|
364
|
+
uniform mat4 u_colorMatrix;
|
|
365
|
+
uniform vec4 u_colorBias;
|
|
366
|
+
out vec4 outColor;
|
|
367
|
+
void main() {
|
|
368
|
+
vec2 uv = (v_world - u_tileOrigin) / max(u_tileSize, vec2(0.0001));
|
|
369
|
+
vec4 texel = texture(u_sampler, uv);
|
|
370
|
+
vec4 mapped = clamp(u_colorMatrix * texel + u_colorBias, 0.0, 1.0);
|
|
371
|
+
float a = mapped.a * u_opacity * u_alpha;
|
|
372
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
373
|
+
}
|
|
374
|
+
`
|
|
375
|
+
);
|
|
376
|
+
var PATTERN_FILL_UNIFORMS = [
|
|
377
|
+
"u_proj",
|
|
378
|
+
"u_model",
|
|
379
|
+
"u_worldInv",
|
|
380
|
+
"u_sampler",
|
|
381
|
+
"u_tileOrigin",
|
|
382
|
+
"u_tileSize",
|
|
383
|
+
"u_opacity",
|
|
384
|
+
"u_alpha",
|
|
385
|
+
"u_colorMatrix",
|
|
386
|
+
"u_colorBias"
|
|
387
|
+
];
|
|
388
|
+
var PATTERN_FILL_ATTRIBUTES = ["a_position"];
|
|
389
|
+
|
|
336
390
|
// src/renderer/cache/GLMeshCache.ts
|
|
337
391
|
var GLMeshCache = class {
|
|
338
392
|
constructor(gl, aPositionLoc) {
|
|
@@ -458,17 +512,20 @@ var GLTextureCache = class {
|
|
|
458
512
|
has(id) {
|
|
459
513
|
return this.map.has(id);
|
|
460
514
|
}
|
|
461
|
-
|
|
515
|
+
/** Uploads once per id; `wrap` therefore applies on first upload only.
|
|
516
|
+
* An id is either a pattern tile or a shader texture, never both. */
|
|
517
|
+
upload(id, source, wrap = "clamp") {
|
|
462
518
|
if (this.map.has(id)) return id;
|
|
463
519
|
const gl = this.gl;
|
|
464
520
|
const tex = gl.createTexture();
|
|
465
521
|
if (!tex) throw new Error(`GLTextureCache: createTexture failed for id="${id}"`);
|
|
522
|
+
const wrapMode = wrap === "repeat" ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
466
523
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
467
524
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
468
525
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
469
526
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
470
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
|
|
471
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
|
|
527
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
|
|
528
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
|
|
472
529
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
473
530
|
this.map.set(id, tex);
|
|
474
531
|
return id;
|
|
@@ -1100,6 +1157,18 @@ function scale(m, sx, sy) {
|
|
|
1100
1157
|
const s = create(sx, 0, 0, sy, 0, 0);
|
|
1101
1158
|
return multiply(m, s);
|
|
1102
1159
|
}
|
|
1160
|
+
function invert(m) {
|
|
1161
|
+
const a = m[0], b = m[1];
|
|
1162
|
+
const c = m[3], d = m[4];
|
|
1163
|
+
const tx = m[6], ty = m[7];
|
|
1164
|
+
const det = a * d - b * c;
|
|
1165
|
+
if (det === 0 || !Number.isFinite(det)) return identity();
|
|
1166
|
+
const ia = d / det;
|
|
1167
|
+
const ib = -b / det;
|
|
1168
|
+
const ic = -c / det;
|
|
1169
|
+
const id = a / det;
|
|
1170
|
+
return create(ia, ib, ic, id, -(ia * tx + ic * ty), -(ib * tx + id * ty));
|
|
1171
|
+
}
|
|
1103
1172
|
function apply(m, x, y) {
|
|
1104
1173
|
const a = m[0], b = m[1];
|
|
1105
1174
|
const c = m[3], d = m[4];
|
|
@@ -1127,6 +1196,7 @@ var mat3 = {
|
|
|
1127
1196
|
multiply,
|
|
1128
1197
|
translate,
|
|
1129
1198
|
scale,
|
|
1199
|
+
invert,
|
|
1130
1200
|
apply,
|
|
1131
1201
|
screenToClip
|
|
1132
1202
|
};
|
|
@@ -2154,7 +2224,7 @@ function warnMissingGlyphOnce(family, cp) {
|
|
|
2154
2224
|
`weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
|
|
2155
2225
|
);
|
|
2156
2226
|
}
|
|
2157
|
-
function layoutRuns(runs, opts
|
|
2227
|
+
function layoutRuns(runs, opts) {
|
|
2158
2228
|
const ctx = { groups: /* @__PURE__ */ new Map() };
|
|
2159
2229
|
const entries = [];
|
|
2160
2230
|
let prevCp;
|
|
@@ -2324,7 +2394,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2324
2394
|
}
|
|
2325
2395
|
}
|
|
2326
2396
|
const lineBoxes = [];
|
|
2327
|
-
let penY =
|
|
2397
|
+
let penY = 0;
|
|
2328
2398
|
let maxLineWidth = 0;
|
|
2329
2399
|
const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
|
|
2330
2400
|
for (const line of lines) {
|
|
@@ -2336,7 +2406,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2336
2406
|
const slack = finiteWidth - line.width;
|
|
2337
2407
|
return opts.align === "center" ? slack / 2 : slack;
|
|
2338
2408
|
})();
|
|
2339
|
-
const lineX0 =
|
|
2409
|
+
const lineX0 = alignShift;
|
|
2340
2410
|
const baselineSource = line.entries[0] ?? line.blank;
|
|
2341
2411
|
const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
|
|
2342
2412
|
let penX = lineX0;
|
|
@@ -2410,7 +2480,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2410
2480
|
groups: [...ctx.groups.values()],
|
|
2411
2481
|
decorations,
|
|
2412
2482
|
lines: lineBoxes,
|
|
2413
|
-
bounds: { width: maxLineWidth, height: penY
|
|
2483
|
+
bounds: { width: maxLineWidth, height: penY }
|
|
2414
2484
|
};
|
|
2415
2485
|
}
|
|
2416
2486
|
var LAYOUT_CACHE_VARIANT_LIMIT = 8;
|
|
@@ -2423,10 +2493,10 @@ function outlineBucket(runs, min) {
|
|
|
2423
2493
|
for (const size of sizes) if (size >= min) n++;
|
|
2424
2494
|
return n;
|
|
2425
2495
|
}
|
|
2426
|
-
function variantKey(runs, opts
|
|
2427
|
-
return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}
|
|
2496
|
+
function variantKey(runs, opts) {
|
|
2497
|
+
return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
|
|
2428
2498
|
}
|
|
2429
|
-
function cachedLayoutRuns(runs, opts
|
|
2499
|
+
function cachedLayoutRuns(runs, opts) {
|
|
2430
2500
|
const fonts = glyphGeneration();
|
|
2431
2501
|
let entry = cache2.get(runs);
|
|
2432
2502
|
if (entry === void 0) {
|
|
@@ -2436,10 +2506,10 @@ function cachedLayoutRuns(runs, opts, origin) {
|
|
|
2436
2506
|
entry.generation = fonts;
|
|
2437
2507
|
entry.byVariant.clear();
|
|
2438
2508
|
}
|
|
2439
|
-
const key = variantKey(runs, opts
|
|
2509
|
+
const key = variantKey(runs, opts);
|
|
2440
2510
|
const hit = entry.byVariant.get(key);
|
|
2441
2511
|
if (hit !== void 0) return hit;
|
|
2442
|
-
const laid = layoutRuns(runs, opts
|
|
2512
|
+
const laid = layoutRuns(runs, opts);
|
|
2443
2513
|
if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
|
|
2444
2514
|
entry.byVariant.set(key, laid);
|
|
2445
2515
|
return laid;
|
|
@@ -2757,26 +2827,267 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
|
|
|
2757
2827
|
return mesh.indices.length === 0 ? null : mesh;
|
|
2758
2828
|
}
|
|
2759
2829
|
|
|
2830
|
+
// src/renderer/solidBatch.ts
|
|
2831
|
+
var MAX_VERTICES_PER_BATCH = 32768;
|
|
2832
|
+
var FLOATS_PER_VERTEX = 6;
|
|
2833
|
+
var INITIAL_VERTICES = 256;
|
|
2834
|
+
var INITIAL_INDICES = 384;
|
|
2835
|
+
var SolidBatch = class {
|
|
2836
|
+
gl;
|
|
2837
|
+
vao;
|
|
2838
|
+
vbo;
|
|
2839
|
+
ibo;
|
|
2840
|
+
verts = new Float32Array(INITIAL_VERTICES * FLOATS_PER_VERTEX);
|
|
2841
|
+
idx = new Uint32Array(INITIAL_INDICES);
|
|
2842
|
+
nVerts = 0;
|
|
2843
|
+
nIdx = 0;
|
|
2844
|
+
/** What the GPU buffers are currently sized for. */
|
|
2845
|
+
vboVerts = 0;
|
|
2846
|
+
iboIdx = 0;
|
|
2847
|
+
constructor(gl, prog) {
|
|
2848
|
+
const aPos = prog.attribute("a_position");
|
|
2849
|
+
const aColor = prog.attribute("a_vertexColor");
|
|
2850
|
+
if (aPos === void 0 || aColor === void 0) {
|
|
2851
|
+
throw new Error("SolidBatch: vertex-color program is missing a_position / a_vertexColor");
|
|
2852
|
+
}
|
|
2853
|
+
const vao = gl.createVertexArray();
|
|
2854
|
+
const vbo = gl.createBuffer();
|
|
2855
|
+
const ibo = gl.createBuffer();
|
|
2856
|
+
if (!vao || !vbo || !ibo) throw new Error("SolidBatch: failed to create GL objects");
|
|
2857
|
+
this.gl = gl;
|
|
2858
|
+
this.vao = vao;
|
|
2859
|
+
this.vbo = vbo;
|
|
2860
|
+
this.ibo = ibo;
|
|
2861
|
+
const stride = FLOATS_PER_VERTEX * 4;
|
|
2862
|
+
gl.bindVertexArray(vao);
|
|
2863
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
2864
|
+
gl.enableVertexAttribArray(aPos);
|
|
2865
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);
|
|
2866
|
+
gl.enableVertexAttribArray(aColor);
|
|
2867
|
+
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 8);
|
|
2868
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
|
|
2869
|
+
gl.bindVertexArray(null);
|
|
2870
|
+
this.growGpu(INITIAL_VERTICES, INITIAL_INDICES);
|
|
2871
|
+
}
|
|
2872
|
+
get length() {
|
|
2873
|
+
return this.nIdx;
|
|
2874
|
+
}
|
|
2875
|
+
/** Whether staging `vertices` more would put the run past the per-flush cap. */
|
|
2876
|
+
wouldOverflow(vertices) {
|
|
2877
|
+
return this.nVerts + vertices > MAX_VERTICES_PER_BATCH;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Append one rect's four corners through `m`, all carrying `rgba` (straight
|
|
2881
|
+
* alpha). An affine maps a rect to a parallelogram, so two triangles still
|
|
2882
|
+
* cover it and the batch draws at `u_model` identity.
|
|
2883
|
+
*/
|
|
2884
|
+
pushRect(x, y, w, h, m, r, g, b, a) {
|
|
2885
|
+
this.reserve(4, 6);
|
|
2886
|
+
const v = this.verts;
|
|
2887
|
+
let i = this.nVerts * FLOATS_PER_VERTEX;
|
|
2888
|
+
const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
|
|
2889
|
+
const x1 = x + w;
|
|
2890
|
+
const y1 = y + h;
|
|
2891
|
+
const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;
|
|
2892
|
+
const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;
|
|
2893
|
+
const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;
|
|
2894
|
+
const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;
|
|
2895
|
+
v[i++] = ax;
|
|
2896
|
+
v[i++] = ay;
|
|
2897
|
+
v[i++] = r;
|
|
2898
|
+
v[i++] = g;
|
|
2899
|
+
v[i++] = b;
|
|
2900
|
+
v[i++] = a;
|
|
2901
|
+
v[i++] = bx;
|
|
2902
|
+
v[i++] = by;
|
|
2903
|
+
v[i++] = r;
|
|
2904
|
+
v[i++] = g;
|
|
2905
|
+
v[i++] = b;
|
|
2906
|
+
v[i++] = a;
|
|
2907
|
+
v[i++] = cx;
|
|
2908
|
+
v[i++] = cy;
|
|
2909
|
+
v[i++] = r;
|
|
2910
|
+
v[i++] = g;
|
|
2911
|
+
v[i++] = b;
|
|
2912
|
+
v[i++] = a;
|
|
2913
|
+
v[i++] = dx;
|
|
2914
|
+
v[i++] = dy;
|
|
2915
|
+
v[i++] = r;
|
|
2916
|
+
v[i++] = g;
|
|
2917
|
+
v[i++] = b;
|
|
2918
|
+
v[i++] = a;
|
|
2919
|
+
const base = this.nVerts;
|
|
2920
|
+
let j = this.nIdx;
|
|
2921
|
+
this.idx[j++] = base;
|
|
2922
|
+
this.idx[j++] = base + 1;
|
|
2923
|
+
this.idx[j++] = base + 2;
|
|
2924
|
+
this.idx[j++] = base;
|
|
2925
|
+
this.idx[j++] = base + 2;
|
|
2926
|
+
this.idx[j++] = base + 3;
|
|
2927
|
+
this.nVerts += 4;
|
|
2928
|
+
this.nIdx += 6;
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
|
|
2932
|
+
* mesh's own indices are rebased onto the staged vertices, which is why the
|
|
2933
|
+
* index buffer is uploaded per flush rather than written once.
|
|
2934
|
+
*/
|
|
2935
|
+
pushMesh(mesh, m, r, g, b, a) {
|
|
2936
|
+
const src = mesh.vertices;
|
|
2937
|
+
const srcIdx = mesh.indices;
|
|
2938
|
+
const n = src.length >> 1;
|
|
2939
|
+
this.reserve(n, srcIdx.length);
|
|
2940
|
+
const v = this.verts;
|
|
2941
|
+
let i = this.nVerts * FLOATS_PER_VERTEX;
|
|
2942
|
+
const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
|
|
2943
|
+
for (let k = 0; k < n; k++) {
|
|
2944
|
+
const x = src[k * 2];
|
|
2945
|
+
const y = src[k * 2 + 1];
|
|
2946
|
+
v[i++] = ma * x + mc * y + mtx;
|
|
2947
|
+
v[i++] = mb * x + md * y + mty;
|
|
2948
|
+
v[i++] = r;
|
|
2949
|
+
v[i++] = g;
|
|
2950
|
+
v[i++] = b;
|
|
2951
|
+
v[i++] = a;
|
|
2952
|
+
}
|
|
2953
|
+
const base = this.nVerts;
|
|
2954
|
+
const out = this.idx;
|
|
2955
|
+
let j = this.nIdx;
|
|
2956
|
+
for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];
|
|
2957
|
+
this.nVerts += n;
|
|
2958
|
+
this.nIdx += srcIdx.length;
|
|
2959
|
+
}
|
|
2960
|
+
/** Upload the staged geometry and bind the batch VAO. Returns the index
|
|
2961
|
+
* count for the caller's `drawElements`. */
|
|
2962
|
+
uploadAndBind() {
|
|
2963
|
+
const gl = this.gl;
|
|
2964
|
+
if (this.nVerts > this.vboVerts || this.nIdx > this.iboIdx) {
|
|
2965
|
+
this.growGpu(this.nVerts, this.nIdx);
|
|
2966
|
+
}
|
|
2967
|
+
gl.bindVertexArray(this.vao);
|
|
2968
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
2969
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * FLOATS_PER_VERTEX);
|
|
2970
|
+
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);
|
|
2971
|
+
return this.nIdx;
|
|
2972
|
+
}
|
|
2973
|
+
reset() {
|
|
2974
|
+
this.nVerts = 0;
|
|
2975
|
+
this.nIdx = 0;
|
|
2976
|
+
}
|
|
2977
|
+
dispose() {
|
|
2978
|
+
const gl = this.gl;
|
|
2979
|
+
gl.deleteBuffer(this.vbo);
|
|
2980
|
+
gl.deleteBuffer(this.ibo);
|
|
2981
|
+
gl.deleteVertexArray(this.vao);
|
|
2982
|
+
}
|
|
2983
|
+
/** Grow the CPU arrays so `vertices` / `indices` more fit. */
|
|
2984
|
+
reserve(vertices, indices) {
|
|
2985
|
+
const needVerts = (this.nVerts + vertices) * FLOATS_PER_VERTEX;
|
|
2986
|
+
if (needVerts > this.verts.length) {
|
|
2987
|
+
let len = this.verts.length;
|
|
2988
|
+
while (len < needVerts) len *= 2;
|
|
2989
|
+
const grown = new Float32Array(len);
|
|
2990
|
+
grown.set(this.verts);
|
|
2991
|
+
this.verts = grown;
|
|
2992
|
+
}
|
|
2993
|
+
const needIdx = this.nIdx + indices;
|
|
2994
|
+
if (needIdx > this.idx.length) {
|
|
2995
|
+
let len = this.idx.length;
|
|
2996
|
+
while (len < needIdx) len *= 2;
|
|
2997
|
+
const grown = new Uint32Array(len);
|
|
2998
|
+
grown.set(this.idx);
|
|
2999
|
+
this.idx = grown;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
/** Size both GPU buffers for at least what is staged, doubling to amortize. */
|
|
3003
|
+
growGpu(vertices, indices) {
|
|
3004
|
+
const gl = this.gl;
|
|
3005
|
+
if (vertices > this.vboVerts) {
|
|
3006
|
+
let capacity = Math.max(this.vboVerts, INITIAL_VERTICES);
|
|
3007
|
+
while (capacity < vertices) capacity *= 2;
|
|
3008
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
3009
|
+
gl.bufferData(gl.ARRAY_BUFFER, capacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);
|
|
3010
|
+
this.vboVerts = capacity;
|
|
3011
|
+
}
|
|
3012
|
+
if (indices > this.iboIdx) {
|
|
3013
|
+
let capacity = Math.max(this.iboIdx, INITIAL_INDICES);
|
|
3014
|
+
while (capacity < indices) capacity *= 2;
|
|
3015
|
+
gl.bindVertexArray(this.vao);
|
|
3016
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
|
|
3017
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, capacity * 4, gl.DYNAMIC_DRAW);
|
|
3018
|
+
gl.bindVertexArray(null);
|
|
3019
|
+
this.iboIdx = capacity;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
};
|
|
3023
|
+
|
|
2760
3024
|
// src/renderer/draw.ts
|
|
2761
|
-
|
|
3025
|
+
var FRAME_UPLOADS = /* @__PURE__ */ new WeakMap();
|
|
3026
|
+
var FRAME_PROJ = /* @__PURE__ */ new WeakMap();
|
|
3027
|
+
function uploadedFor(ctx, prog) {
|
|
3028
|
+
let byProgram = FRAME_UPLOADS.get(ctx);
|
|
3029
|
+
if (!byProgram) {
|
|
3030
|
+
byProgram = /* @__PURE__ */ new WeakMap();
|
|
3031
|
+
FRAME_UPLOADS.set(ctx, byProgram);
|
|
3032
|
+
}
|
|
3033
|
+
let uploaded = byProgram.get(prog);
|
|
3034
|
+
if (!uploaded) {
|
|
3035
|
+
uploaded = {};
|
|
3036
|
+
byProgram.set(prog, uploaded);
|
|
3037
|
+
}
|
|
3038
|
+
return uploaded;
|
|
3039
|
+
}
|
|
3040
|
+
function sameValues(prev, next) {
|
|
3041
|
+
if (prev === void 0 || prev.length !== next.length) return false;
|
|
3042
|
+
for (let i = 0; i < prev.length; i++) if (prev[i] !== next[i]) return false;
|
|
3043
|
+
return true;
|
|
3044
|
+
}
|
|
3045
|
+
var BATCH_MODEL = mat3.identity();
|
|
3046
|
+
function projFor(ctx) {
|
|
3047
|
+
let proj = FRAME_PROJ.get(ctx);
|
|
3048
|
+
if (!proj) {
|
|
3049
|
+
proj = mat3.screenToClip(ctx.widthCss, ctx.heightCss);
|
|
3050
|
+
FRAME_PROJ.set(ctx, proj);
|
|
3051
|
+
}
|
|
3052
|
+
return proj;
|
|
3053
|
+
}
|
|
3054
|
+
var COLOR_MATRIX_SCRATCH = new Float32Array(16);
|
|
3055
|
+
function setColorMatrixUniforms(ctx, prog, cm = ctx.state.colorMatrix) {
|
|
2762
3056
|
const gl = ctx.gl;
|
|
2763
|
-
const
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
for (let
|
|
2767
|
-
|
|
3057
|
+
const uploaded = uploadedFor(ctx, prog);
|
|
3058
|
+
const mLoc = prog.uniform("u_colorMatrix");
|
|
3059
|
+
if (mLoc !== void 0) {
|
|
3060
|
+
for (let row = 0; row < 4; row++) {
|
|
3061
|
+
for (let col = 0; col < 4; col++) {
|
|
3062
|
+
COLOR_MATRIX_SCRATCH[col * 4 + row] = cm[row * 5 + col];
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
if (!sameValues(uploaded.colorMatrix, COLOR_MATRIX_SCRATCH)) {
|
|
3066
|
+
gl.uniformMatrix4fv(mLoc, false, COLOR_MATRIX_SCRATCH);
|
|
3067
|
+
uploaded.colorMatrix = Float32Array.from(COLOR_MATRIX_SCRATCH);
|
|
2768
3068
|
}
|
|
2769
3069
|
}
|
|
2770
|
-
const mLoc = prog.uniform("u_colorMatrix");
|
|
2771
3070
|
const bLoc = prog.uniform("u_colorBias");
|
|
2772
|
-
if (
|
|
2773
|
-
|
|
3071
|
+
if (bLoc !== void 0) {
|
|
3072
|
+
const bias = [cm[4], cm[9], cm[14], cm[19]];
|
|
3073
|
+
if (!sameValues(uploaded.colorBias, bias)) {
|
|
3074
|
+
gl.uniform4f(bLoc, bias[0], bias[1], bias[2], bias[3]);
|
|
3075
|
+
uploaded.colorBias = Float32Array.from(bias);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2774
3078
|
}
|
|
2775
|
-
function
|
|
3079
|
+
function fillMesh(ctx, path) {
|
|
2776
3080
|
if (ctx.flattenTolerance !== void 0) {
|
|
2777
|
-
return
|
|
3081
|
+
return tessellate(path, { flattenTolerance: ctx.flattenTolerance });
|
|
2778
3082
|
}
|
|
2779
|
-
return
|
|
3083
|
+
return getMesh(path);
|
|
3084
|
+
}
|
|
3085
|
+
function meshHandle(ctx, mesh) {
|
|
3086
|
+
if (ctx.flattenTolerance !== void 0) return ctx.meshCache.uploadTransient(mesh);
|
|
3087
|
+
return ctx.meshCache.handleFor(mesh);
|
|
3088
|
+
}
|
|
3089
|
+
function fillMeshHandle(ctx, path) {
|
|
3090
|
+
return meshHandle(ctx, fillMesh(ctx, path));
|
|
2780
3091
|
}
|
|
2781
3092
|
function dispatch(ctx, cmd) {
|
|
2782
3093
|
switch (cmd.kind) {
|
|
@@ -2785,10 +3096,13 @@ function dispatch(ctx, cmd) {
|
|
|
2785
3096
|
case "path":
|
|
2786
3097
|
return drawPath(ctx, cmd);
|
|
2787
3098
|
case "text":
|
|
3099
|
+
flushSolids(ctx);
|
|
2788
3100
|
return drawText(ctx, cmd);
|
|
2789
3101
|
case "image":
|
|
3102
|
+
flushSolids(ctx);
|
|
2790
3103
|
return drawImage(ctx, cmd);
|
|
2791
3104
|
case "shader":
|
|
3105
|
+
flushSolids(ctx);
|
|
2792
3106
|
return drawShader(ctx, cmd);
|
|
2793
3107
|
}
|
|
2794
3108
|
}
|
|
@@ -2917,11 +3231,13 @@ function drawGroup(ctx, cmd) {
|
|
|
2917
3231
|
"weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
|
|
2918
3232
|
);
|
|
2919
3233
|
}
|
|
3234
|
+
flushSolids(ctx);
|
|
2920
3235
|
pushClip(ctx, cmd.clip, newDepth);
|
|
2921
3236
|
ctx.clipDepth = newDepth;
|
|
2922
3237
|
}
|
|
2923
3238
|
for (const child of cmd.children) dispatch(ctx, child);
|
|
2924
3239
|
if (cmd.clip) {
|
|
3240
|
+
flushSolids(ctx);
|
|
2925
3241
|
popClip(ctx, cmd.clip, ctx.clipDepth - 1);
|
|
2926
3242
|
ctx.clipDepth -= 1;
|
|
2927
3243
|
}
|
|
@@ -2930,38 +3246,98 @@ function drawGroup(ctx, cmd) {
|
|
|
2930
3246
|
function drawPath(ctx, cmd) {
|
|
2931
3247
|
if (!cmd.fill && !cmd.stroke) return;
|
|
2932
3248
|
if (cmd.fill) {
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
3249
|
+
const batchablePaint = (cmd.fill.fill === void 0 || cmd.fill.fill === "solid") && (!cmd.vertexColors || cmd.vertexColors.length === 0);
|
|
3250
|
+
const solid = cmd.fill;
|
|
3251
|
+
if (batchablePaint && cmd.path.kind === "rect") {
|
|
3252
|
+
pushRect(ctx, cmd.path, solid);
|
|
2936
3253
|
} else {
|
|
2937
|
-
const
|
|
2938
|
-
if (
|
|
2939
|
-
|
|
2940
|
-
} else if (handle.requiresStencil) {
|
|
2941
|
-
drawPathFillStencil(ctx, cmd.fill, handle);
|
|
3254
|
+
const mesh = fillMesh(ctx, cmd.path);
|
|
3255
|
+
if (batchablePaint && canBatchMesh(mesh)) {
|
|
3256
|
+
pushMesh(ctx, mesh, solid);
|
|
2942
3257
|
} else {
|
|
2943
|
-
|
|
3258
|
+
flushSolids(ctx);
|
|
3259
|
+
const handle = meshHandle(ctx, mesh);
|
|
3260
|
+
if (cmd.vertexColors && cmd.vertexColors.length > 0 && (cmd.fill.fill === void 0 || cmd.fill.fill === "solid")) {
|
|
3261
|
+
drawPathFillVColor(ctx, cmd, solid, handle);
|
|
3262
|
+
} else if (handle.requiresStencil) {
|
|
3263
|
+
drawPathFillStencil(ctx, cmd.fill, handle);
|
|
3264
|
+
} else {
|
|
3265
|
+
drawPathFillByKind(ctx, cmd.fill, handle);
|
|
3266
|
+
}
|
|
2944
3267
|
}
|
|
2945
3268
|
}
|
|
2946
3269
|
}
|
|
2947
|
-
if (cmd.stroke)
|
|
2948
|
-
|
|
3270
|
+
if (cmd.stroke) drawPathStroke(ctx, cmd);
|
|
3271
|
+
}
|
|
3272
|
+
function isIdentityColorMatrix(cm) {
|
|
3273
|
+
return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
|
|
3274
|
+
}
|
|
3275
|
+
function stagedStateIsLive(ctx, staged) {
|
|
3276
|
+
if (staged.clipDepth !== ctx.clipDepth) return false;
|
|
3277
|
+
if (!staged.foldsAlpha && staged.alpha !== ctx.state.alpha) return false;
|
|
3278
|
+
const colorMatrix = ctx.state.colorMatrix;
|
|
3279
|
+
return staged.colorMatrix === colorMatrix || sameValues(staged.colorMatrix, colorMatrix);
|
|
3280
|
+
}
|
|
3281
|
+
var MAX_BATCHED_MESH_VERTICES = 256;
|
|
3282
|
+
function canBatchMesh(mesh) {
|
|
3283
|
+
return !mesh.requiresStencil && mesh.vertices.length >> 1 <= MAX_BATCHED_MESH_VERTICES;
|
|
3284
|
+
}
|
|
3285
|
+
function stageSolid(ctx, vertices) {
|
|
3286
|
+
if (ctx.solidState !== void 0 && !stagedStateIsLive(ctx, ctx.solidState)) flushSolids(ctx);
|
|
3287
|
+
if (ctx.solidBatch.wouldOverflow(vertices)) flushSolids(ctx);
|
|
3288
|
+
if (ctx.solidState === void 0) {
|
|
3289
|
+
const colorMatrix = ctx.state.colorMatrix;
|
|
3290
|
+
const foldsAlpha = isIdentityColorMatrix(colorMatrix);
|
|
3291
|
+
ctx.solidState = {
|
|
3292
|
+
alpha: foldsAlpha ? 1 : ctx.state.alpha,
|
|
3293
|
+
colorMatrix,
|
|
3294
|
+
clipDepth: ctx.clipDepth,
|
|
3295
|
+
foldsAlpha
|
|
3296
|
+
};
|
|
2949
3297
|
}
|
|
3298
|
+
return ctx.solidState;
|
|
3299
|
+
}
|
|
3300
|
+
function stagedColor(ctx, staged, paint) {
|
|
3301
|
+
const [r, g, b, a] = resolveColor(paint.color);
|
|
3302
|
+
return [r, g, b, a * (paint.opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1)];
|
|
3303
|
+
}
|
|
3304
|
+
function pushRect(ctx, rect, fill) {
|
|
3305
|
+
const staged = stageSolid(ctx, 4);
|
|
3306
|
+
const [r, g, b, a] = stagedColor(ctx, staged, fill);
|
|
3307
|
+
ctx.solidBatch.pushRect(
|
|
3308
|
+
rect.x,
|
|
3309
|
+
rect.y,
|
|
3310
|
+
rect.width,
|
|
3311
|
+
rect.height,
|
|
3312
|
+
ctx.state.transform,
|
|
3313
|
+
r,
|
|
3314
|
+
g,
|
|
3315
|
+
b,
|
|
3316
|
+
a
|
|
3317
|
+
);
|
|
2950
3318
|
}
|
|
2951
|
-
function
|
|
3319
|
+
function pushMesh(ctx, mesh, paint) {
|
|
3320
|
+
const staged = stageSolid(ctx, mesh.vertices.length >> 1);
|
|
3321
|
+
const [r, g, b, a] = stagedColor(ctx, staged, paint);
|
|
3322
|
+
ctx.solidBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a);
|
|
3323
|
+
}
|
|
3324
|
+
function flushSolids(ctx) {
|
|
3325
|
+
const batch = ctx.solidBatch;
|
|
3326
|
+
const staged = ctx.solidState;
|
|
3327
|
+
if (batch.length === 0 || staged === void 0) return;
|
|
2952
3328
|
const gl = ctx.gl;
|
|
2953
|
-
const
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
gl.
|
|
2958
|
-
gl.
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
applyClipTest(ctx);
|
|
2963
|
-
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
|
|
3329
|
+
const prog = ctx.pathFillVColor;
|
|
3330
|
+
gl.useProgram(prog.handle);
|
|
3331
|
+
const indexCount = batch.uploadAndBind();
|
|
3332
|
+
setProjAndModel(ctx, prog, BATCH_MODEL);
|
|
3333
|
+
gl.uniform4f(prog.uniform("u_color"), 1, 1, 1, 1);
|
|
3334
|
+
gl.uniform1f(prog.uniform("u_alpha"), staged.alpha);
|
|
3335
|
+
setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
|
|
3336
|
+
applyClipTest(ctx, staged.clipDepth);
|
|
3337
|
+
gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_INT, 0);
|
|
2964
3338
|
gl.bindVertexArray(null);
|
|
3339
|
+
batch.reset();
|
|
3340
|
+
ctx.solidState = void 0;
|
|
2965
3341
|
}
|
|
2966
3342
|
function expandAnchorColors(perAnchor, handle) {
|
|
2967
3343
|
const aA = handle.anchorA;
|
|
@@ -3006,11 +3382,18 @@ function drawPathFillVColor(ctx, cmd, fill, handle) {
|
|
|
3006
3382
|
gl.bindVertexArray(null);
|
|
3007
3383
|
gl.deleteBuffer(colorVbo);
|
|
3008
3384
|
}
|
|
3009
|
-
function setProjAndModel(ctx, prog) {
|
|
3385
|
+
function setProjAndModel(ctx, prog, model = ctx.state.transform) {
|
|
3010
3386
|
const gl = ctx.gl;
|
|
3011
|
-
const
|
|
3012
|
-
|
|
3013
|
-
|
|
3387
|
+
const uploaded = uploadedFor(ctx, prog);
|
|
3388
|
+
const proj = projFor(ctx);
|
|
3389
|
+
if (!sameValues(uploaded.proj, proj)) {
|
|
3390
|
+
gl.uniformMatrix3fv(prog.uniform("u_proj"), false, proj);
|
|
3391
|
+
uploaded.proj = Float32Array.from(proj);
|
|
3392
|
+
}
|
|
3393
|
+
if (!sameValues(uploaded.model, model)) {
|
|
3394
|
+
gl.uniformMatrix3fv(prog.uniform("u_model"), false, model);
|
|
3395
|
+
uploaded.model = Float32Array.from(model);
|
|
3396
|
+
}
|
|
3014
3397
|
}
|
|
3015
3398
|
function setSolidPaintUniforms(ctx, prog, color, opacity) {
|
|
3016
3399
|
const gl = ctx.gl;
|
|
@@ -3048,28 +3431,42 @@ function drawPathFillPattern(ctx, fill, handle) {
|
|
|
3048
3431
|
if (isDev3) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
3049
3432
|
return;
|
|
3050
3433
|
}
|
|
3051
|
-
ctx.textureCache.upload(tex.id, entry.source);
|
|
3434
|
+
ctx.textureCache.upload(tex.id, entry.source, "repeat");
|
|
3052
3435
|
const gl = ctx.gl;
|
|
3053
|
-
gl.useProgram(ctx.
|
|
3436
|
+
gl.useProgram(ctx.patternFill.handle);
|
|
3054
3437
|
gl.bindVertexArray(handle.vao);
|
|
3055
|
-
setProjAndModel(ctx, ctx.
|
|
3056
|
-
setColorMatrixUniforms(ctx, ctx.
|
|
3438
|
+
setProjAndModel(ctx, ctx.patternFill);
|
|
3439
|
+
setColorMatrixUniforms(ctx, ctx.patternFill);
|
|
3440
|
+
gl.uniformMatrix3fv(ctx.patternFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
3441
|
+
const [tw, th] = textureSize(entry.source);
|
|
3442
|
+
const origin = fill.origin ?? { x: 0, y: 0 };
|
|
3443
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileOrigin"), origin.x, origin.y);
|
|
3444
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileSize"), tw, th);
|
|
3057
3445
|
ctx.textureCache.bind(tex.id, 0);
|
|
3058
|
-
gl.uniform1i(ctx.
|
|
3059
|
-
gl.uniform1f(ctx.
|
|
3060
|
-
gl.uniform1f(ctx.
|
|
3446
|
+
gl.uniform1i(ctx.patternFill.uniform("u_sampler"), 0);
|
|
3447
|
+
gl.uniform1f(ctx.patternFill.uniform("u_opacity"), fill.opacity ?? 1);
|
|
3448
|
+
gl.uniform1f(ctx.patternFill.uniform("u_alpha"), ctx.state.alpha);
|
|
3061
3449
|
applyClipTest(ctx);
|
|
3062
3450
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
3063
3451
|
gl.bindVertexArray(null);
|
|
3064
3452
|
}
|
|
3453
|
+
function textureSize(source) {
|
|
3454
|
+
const w = "naturalWidth" in source ? source.naturalWidth : source.width;
|
|
3455
|
+
const h = "naturalHeight" in source ? source.naturalHeight : source.height;
|
|
3456
|
+
return [w || 1, h || 1];
|
|
3457
|
+
}
|
|
3458
|
+
function gradientSpaceInverse(ctx, units) {
|
|
3459
|
+
if (units === "local") return mat3.invert(ctx.state.transform);
|
|
3460
|
+
if (units === "world" && ctx.viewMatrix) return mat3.invert(ctx.viewMatrix);
|
|
3461
|
+
return mat3.identity();
|
|
3462
|
+
}
|
|
3065
3463
|
function drawPathFillGradient(ctx, fill, handle) {
|
|
3066
3464
|
const gl = ctx.gl;
|
|
3067
3465
|
const key = ctx.gradRampCache.upload(fill.stops);
|
|
3068
3466
|
gl.useProgram(ctx.gradFill.handle);
|
|
3069
3467
|
gl.bindVertexArray(handle.vao);
|
|
3070
3468
|
setProjAndModel(ctx, ctx.gradFill);
|
|
3071
|
-
|
|
3072
|
-
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, identity3);
|
|
3469
|
+
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
3073
3470
|
ctx.gradRampCache.bind(key, 0);
|
|
3074
3471
|
gl.uniform1i(ctx.gradFill.uniform("u_ramp"), 0);
|
|
3075
3472
|
gl.uniform1f(ctx.gradFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -3103,9 +3500,8 @@ function drawPathFillGradient(ctx, fill, handle) {
|
|
|
3103
3500
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
3104
3501
|
gl.bindVertexArray(null);
|
|
3105
3502
|
}
|
|
3106
|
-
function applyClipTest(ctx) {
|
|
3503
|
+
function applyClipTest(ctx, depth = ctx.clipDepth) {
|
|
3107
3504
|
const gl = ctx.gl;
|
|
3108
|
-
const depth = ctx.clipDepth;
|
|
3109
3505
|
if (depth === 0) {
|
|
3110
3506
|
gl.disable(gl.STENCIL_TEST);
|
|
3111
3507
|
return;
|
|
@@ -3189,6 +3585,7 @@ function drawPathStroke(ctx, cmd) {
|
|
|
3189
3585
|
}
|
|
3190
3586
|
const align = stroke.align ?? "center";
|
|
3191
3587
|
if (cmd.path.kind === "polygon" && align !== "center") {
|
|
3588
|
+
flushSolids(ctx);
|
|
3192
3589
|
drawPathStrokeStenciled(ctx, cmd, align);
|
|
3193
3590
|
return;
|
|
3194
3591
|
}
|
|
@@ -3199,6 +3596,12 @@ function drawPathStrokeUnclipped(ctx, cmd) {
|
|
|
3199
3596
|
const solid = stroke.paint;
|
|
3200
3597
|
const mesh = tessellateStroke(cmd.path, stroke, { flattenTolerance: ctx.flattenTolerance });
|
|
3201
3598
|
if (mesh.indices.length === 0) return;
|
|
3599
|
+
const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
|
|
3600
|
+
if (!hasVColors && canBatchMesh(mesh)) {
|
|
3601
|
+
pushMesh(ctx, mesh, solid);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
flushSolids(ctx);
|
|
3202
3605
|
const handle = ctx.meshCache.uploadTransient(mesh);
|
|
3203
3606
|
const gl = ctx.gl;
|
|
3204
3607
|
if (stroke.vertexColors && stroke.vertexColors.length > 0) {
|
|
@@ -3296,20 +3699,17 @@ function drawText(ctx, cmd) {
|
|
|
3296
3699
|
const maxWidth = cmd.maxWidth ?? Infinity;
|
|
3297
3700
|
const minScreen = ctx.textOutlineMinScreenSize ?? OUTLINE_MIN_SCREEN_PX;
|
|
3298
3701
|
const outlineMinSize = Number.isFinite(minScreen) ? minScreen / modelScale(ctx.state.transform) : void 0;
|
|
3299
|
-
const laid = cachedLayoutRuns(
|
|
3300
|
-
cmd.runs,
|
|
3301
|
-
{ maxWidth, lineHeight, align, outlineMinSize },
|
|
3302
|
-
{ x: cmd.x, y: cmd.y }
|
|
3303
|
-
);
|
|
3702
|
+
const laid = cachedLayoutRuns(cmd.runs, { maxWidth, lineHeight, align, outlineMinSize });
|
|
3304
3703
|
if (laid.groups.length === 0 && laid.decorations.length === 0) return;
|
|
3305
|
-
const
|
|
3704
|
+
const dx = cmd.x;
|
|
3705
|
+
const dy = cmd.y + verticalAlignOffset(cmd.verticalAlign, cmd.height, laid.bounds.height);
|
|
3306
3706
|
const gl = ctx.gl;
|
|
3307
3707
|
applyClipTest(ctx);
|
|
3308
3708
|
const preparedPrograms = /* @__PURE__ */ new Set();
|
|
3309
3709
|
let currentProg = null;
|
|
3310
3710
|
for (const group of laid.groups) {
|
|
3311
3711
|
if (group.source === "outline") {
|
|
3312
|
-
drawTextOutlineGroup(ctx, group, dy);
|
|
3712
|
+
drawTextOutlineGroup(ctx, group, dx, dy);
|
|
3313
3713
|
currentProg = null;
|
|
3314
3714
|
continue;
|
|
3315
3715
|
}
|
|
@@ -3324,31 +3724,31 @@ function drawText(ctx, cmd) {
|
|
|
3324
3724
|
setColorMatrixUniforms(ctx, prog);
|
|
3325
3725
|
gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
|
|
3326
3726
|
}
|
|
3327
|
-
drawTextGroup(ctx, group, prog, dy);
|
|
3727
|
+
drawTextGroup(ctx, group, prog, dx, dy);
|
|
3328
3728
|
}
|
|
3329
|
-
drawTextDecorations(ctx, laid.decorations, dy);
|
|
3729
|
+
drawTextDecorations(ctx, laid.decorations, dx, dy);
|
|
3330
3730
|
}
|
|
3331
3731
|
var SYNTHETIC_ITALIC_RADIANS = 0.2094;
|
|
3332
|
-
function drawTextOutlineGroup(ctx, group, dy) {
|
|
3333
|
-
const mesh = outlineGroupMesh(group, dy);
|
|
3732
|
+
function drawTextOutlineGroup(ctx, group, dx, dy) {
|
|
3733
|
+
const mesh = outlineGroupMesh(group, dx, dy);
|
|
3334
3734
|
if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
|
|
3335
3735
|
if (!group.stroke) return;
|
|
3336
|
-
const strokeMesh = outlineGroupStrokeMesh(group, dy);
|
|
3736
|
+
const strokeMesh = outlineGroupStrokeMesh(group, dx, dy);
|
|
3337
3737
|
if (strokeMesh) {
|
|
3338
3738
|
drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
|
|
3339
3739
|
}
|
|
3340
3740
|
}
|
|
3341
|
-
function outlineGroupMesh(group, dy) {
|
|
3342
|
-
return mergeGlyphMeshes(group, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
|
|
3741
|
+
function outlineGroupMesh(group, dx, dy) {
|
|
3742
|
+
return mergeGlyphMeshes(group, dx, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
|
|
3343
3743
|
}
|
|
3344
|
-
function outlineGroupStrokeMesh(group, dy) {
|
|
3744
|
+
function outlineGroupStrokeMesh(group, dx, dy) {
|
|
3345
3745
|
const stroke = group.stroke;
|
|
3346
3746
|
if (!stroke) return null;
|
|
3347
3747
|
const width = stroke.width ?? 1;
|
|
3348
3748
|
if (!(width > 0)) return null;
|
|
3349
|
-
return mergeGlyphMeshes(group, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
|
|
3749
|
+
return mergeGlyphMeshes(group, dx, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
|
|
3350
3750
|
}
|
|
3351
|
-
function mergeGlyphMeshes(group, dy, meshFor) {
|
|
3751
|
+
function mergeGlyphMeshes(group, dx, dy, meshFor) {
|
|
3352
3752
|
const parts = [];
|
|
3353
3753
|
let vertexFloats = 0;
|
|
3354
3754
|
let indexCount = 0;
|
|
@@ -3371,7 +3771,7 @@ function mergeGlyphMeshes(group, dy, meshFor) {
|
|
|
3371
3771
|
for (let k = 0; k < mesh.vertices.length; k += 2) {
|
|
3372
3772
|
const ex = mesh.vertices[k];
|
|
3373
3773
|
const ey = mesh.vertices[k + 1];
|
|
3374
|
-
vertices[vi++] = x + (ex - ey * shear) * scale2;
|
|
3774
|
+
vertices[vi++] = x + dx + (ex - ey * shear) * scale2;
|
|
3375
3775
|
vertices[vi++] = baselineY + dy + ey * scale2;
|
|
3376
3776
|
}
|
|
3377
3777
|
for (let k = 0; k < mesh.indices.length; k++) indices[ii++] = base + mesh.indices[k];
|
|
@@ -3379,7 +3779,7 @@ function mergeGlyphMeshes(group, dy, meshFor) {
|
|
|
3379
3779
|
}
|
|
3380
3780
|
return { vertices, indices };
|
|
3381
3781
|
}
|
|
3382
|
-
function drawTextDecorations(ctx, decorations, dy) {
|
|
3782
|
+
function drawTextDecorations(ctx, decorations, dx, dy) {
|
|
3383
3783
|
if (decorations.length === 0) return;
|
|
3384
3784
|
const batches = /* @__PURE__ */ new Map();
|
|
3385
3785
|
for (const d of decorations) {
|
|
@@ -3402,14 +3802,15 @@ function drawTextDecorations(ctx, decorations, dy) {
|
|
|
3402
3802
|
const vertices = new Float32Array(rects.length * 4 * 2);
|
|
3403
3803
|
let vi = 0;
|
|
3404
3804
|
for (const d of rects) {
|
|
3805
|
+
const dx0 = d.x0 + dx, dx1 = d.x1 + dx;
|
|
3405
3806
|
const dy0 = d.y0 + dy, dy1 = d.y1 + dy;
|
|
3406
|
-
vertices[vi++] =
|
|
3807
|
+
vertices[vi++] = dx0;
|
|
3407
3808
|
vertices[vi++] = dy0;
|
|
3408
|
-
vertices[vi++] =
|
|
3809
|
+
vertices[vi++] = dx1;
|
|
3409
3810
|
vertices[vi++] = dy0;
|
|
3410
|
-
vertices[vi++] =
|
|
3811
|
+
vertices[vi++] = dx0;
|
|
3411
3812
|
vertices[vi++] = dy1;
|
|
3412
|
-
vertices[vi++] =
|
|
3813
|
+
vertices[vi++] = dx1;
|
|
3413
3814
|
vertices[vi++] = dy1;
|
|
3414
3815
|
}
|
|
3415
3816
|
const indices = new Uint32Array(rects.length * 6);
|
|
@@ -3448,7 +3849,7 @@ function drawTextDecorations(ctx, decorations, dy) {
|
|
|
3448
3849
|
gl.deleteBuffer(ibo);
|
|
3449
3850
|
}
|
|
3450
3851
|
}
|
|
3451
|
-
function drawTextGroup(ctx, group, prog, dy) {
|
|
3852
|
+
function drawTextGroup(ctx, group, prog, dx, dy) {
|
|
3452
3853
|
if (group.source === "canvas") {
|
|
3453
3854
|
if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
|
|
3454
3855
|
} else {
|
|
@@ -3459,23 +3860,24 @@ function drawTextGroup(ctx, group, prog, dy) {
|
|
|
3459
3860
|
const vertices = new Float32Array(group.quads.length * 4 * 5);
|
|
3460
3861
|
let vi = 0;
|
|
3461
3862
|
for (const q of group.quads) {
|
|
3863
|
+
const x0 = q.x0 + dx, x1 = q.x1 + dx;
|
|
3462
3864
|
const y0 = q.y0 + dy, y1 = q.y1 + dy, by = q.baselineY + dy;
|
|
3463
|
-
vertices[vi++] =
|
|
3865
|
+
vertices[vi++] = x0;
|
|
3464
3866
|
vertices[vi++] = y0;
|
|
3465
3867
|
vertices[vi++] = q.u0;
|
|
3466
3868
|
vertices[vi++] = q.v0;
|
|
3467
3869
|
vertices[vi++] = by;
|
|
3468
|
-
vertices[vi++] =
|
|
3870
|
+
vertices[vi++] = x1;
|
|
3469
3871
|
vertices[vi++] = y0;
|
|
3470
3872
|
vertices[vi++] = q.u1;
|
|
3471
3873
|
vertices[vi++] = q.v0;
|
|
3472
3874
|
vertices[vi++] = by;
|
|
3473
|
-
vertices[vi++] =
|
|
3875
|
+
vertices[vi++] = x0;
|
|
3474
3876
|
vertices[vi++] = y1;
|
|
3475
3877
|
vertices[vi++] = q.u0;
|
|
3476
3878
|
vertices[vi++] = q.v1;
|
|
3477
3879
|
vertices[vi++] = by;
|
|
3478
|
-
vertices[vi++] =
|
|
3880
|
+
vertices[vi++] = x1;
|
|
3479
3881
|
vertices[vi++] = y1;
|
|
3480
3882
|
vertices[vi++] = q.u1;
|
|
3481
3883
|
vertices[vi++] = q.v1;
|
|
@@ -3589,6 +3991,11 @@ function drawImage(ctx, cmd) {
|
|
|
3589
3991
|
setProjAndModel(ctx, ctx.imageFill);
|
|
3590
3992
|
setColorMatrixUniforms(ctx, ctx.imageFill);
|
|
3591
3993
|
ctx.imageCache.bind(cmd.image, 0);
|
|
3994
|
+
gl.texParameteri(
|
|
3995
|
+
gl.TEXTURE_2D,
|
|
3996
|
+
gl.TEXTURE_MAG_FILTER,
|
|
3997
|
+
cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
|
|
3998
|
+
);
|
|
3592
3999
|
gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
|
|
3593
4000
|
gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
|
|
3594
4001
|
gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -3689,6 +4096,7 @@ var WeaselRenderer = class {
|
|
|
3689
4096
|
textSdfR8;
|
|
3690
4097
|
imageFill;
|
|
3691
4098
|
gradFill;
|
|
4099
|
+
patternFill;
|
|
3692
4100
|
meshCache;
|
|
3693
4101
|
textureCache;
|
|
3694
4102
|
imageCache;
|
|
@@ -3696,10 +4104,7 @@ var WeaselRenderer = class {
|
|
|
3696
4104
|
programRegistry = /* @__PURE__ */ new Map();
|
|
3697
4105
|
quadVbo = null;
|
|
3698
4106
|
quadIbo = null;
|
|
3699
|
-
|
|
3700
|
-
rectVao = null;
|
|
3701
|
-
rectVbo = null;
|
|
3702
|
-
rectIbo = null;
|
|
4107
|
+
solidBatch;
|
|
3703
4108
|
groupState = new GroupState();
|
|
3704
4109
|
widthCss;
|
|
3705
4110
|
heightCss;
|
|
@@ -3764,6 +4169,9 @@ var WeaselRenderer = class {
|
|
|
3764
4169
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
3765
4170
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
3766
4171
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
4172
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
4173
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
4174
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
3767
4175
|
const aPos = this.pathFill.attribute("a_position");
|
|
3768
4176
|
if (aPos === void 0) throw new Error("WeaselRenderer: a_position not found in path-fill shader");
|
|
3769
4177
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -3771,31 +4179,7 @@ var WeaselRenderer = class {
|
|
|
3771
4179
|
this.imageCache = new GLImageCache(this.gl, this.imageMinification);
|
|
3772
4180
|
this.gradRampCache = new GradientRampCache(this.gl);
|
|
3773
4181
|
this.uploadQuadGeometry();
|
|
3774
|
-
this.
|
|
3775
|
-
}
|
|
3776
|
-
/** Allocate the shared rect VAO + dynamic VBO (4 verts × 2 floats) + static
|
|
3777
|
-
* IBO (6 indices). drawRectFast bufferSubData's the 4 corner coords each
|
|
3778
|
-
* draw, avoiding per-rect buffer allocation in animated demos. */
|
|
3779
|
-
uploadRectGeometry(aPositionLoc) {
|
|
3780
|
-
const gl = this.gl;
|
|
3781
|
-
this.rectVao = gl.createVertexArray();
|
|
3782
|
-
this.rectVbo = gl.createBuffer();
|
|
3783
|
-
this.rectIbo = gl.createBuffer();
|
|
3784
|
-
if (!this.rectVao || !this.rectVbo || !this.rectIbo) {
|
|
3785
|
-
throw new Error("WeaselRenderer: failed to create rect-fast geometry");
|
|
3786
|
-
}
|
|
3787
|
-
gl.bindVertexArray(this.rectVao);
|
|
3788
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this.rectVbo);
|
|
3789
|
-
gl.bufferData(gl.ARRAY_BUFFER, 8 * 4, gl.DYNAMIC_DRAW);
|
|
3790
|
-
gl.enableVertexAttribArray(aPositionLoc);
|
|
3791
|
-
gl.vertexAttribPointer(aPositionLoc, 2, gl.FLOAT, false, 0, 0);
|
|
3792
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.rectIbo);
|
|
3793
|
-
gl.bufferData(
|
|
3794
|
-
gl.ELEMENT_ARRAY_BUFFER,
|
|
3795
|
-
new Uint32Array([0, 1, 2, 0, 2, 3]),
|
|
3796
|
-
gl.STATIC_DRAW
|
|
3797
|
-
);
|
|
3798
|
-
gl.bindVertexArray(null);
|
|
4182
|
+
this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
|
|
3799
4183
|
}
|
|
3800
4184
|
uploadQuadGeometry() {
|
|
3801
4185
|
const gl = this.gl;
|
|
@@ -3872,6 +4256,9 @@ var WeaselRenderer = class {
|
|
|
3872
4256
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
3873
4257
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
3874
4258
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
4259
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
4260
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
4261
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
3875
4262
|
const aPos = this.pathFill.attribute("a_position");
|
|
3876
4263
|
if (aPos === void 0) throw new Error("a_position missing after restore");
|
|
3877
4264
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -3880,7 +4267,7 @@ var WeaselRenderer = class {
|
|
|
3880
4267
|
this.gradRampCache = new GradientRampCache(this.gl);
|
|
3881
4268
|
markAllFontsNotUploaded();
|
|
3882
4269
|
this.uploadQuadGeometry();
|
|
3883
|
-
this.
|
|
4270
|
+
this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
|
|
3884
4271
|
for (const id of this.programRegistry.keys()) {
|
|
3885
4272
|
const src = getProgramSource(id);
|
|
3886
4273
|
if (!src) continue;
|
|
@@ -3940,11 +4327,17 @@ var WeaselRenderer = class {
|
|
|
3940
4327
|
this.gradRampCache.free();
|
|
3941
4328
|
if (this.quadVbo) gl.deleteBuffer(this.quadVbo);
|
|
3942
4329
|
if (this.quadIbo) gl.deleteBuffer(this.quadIbo);
|
|
3943
|
-
|
|
3944
|
-
if (this.rectIbo) gl.deleteBuffer(this.rectIbo);
|
|
3945
|
-
if (this.rectVao) gl.deleteVertexArray(this.rectVao);
|
|
4330
|
+
this.solidBatch.dispose();
|
|
3946
4331
|
}
|
|
3947
|
-
|
|
4332
|
+
/**
|
|
4333
|
+
* Draw one frame.
|
|
4334
|
+
*
|
|
4335
|
+
* `viewMatrix` is the frame's world→screen transform. It is only consulted
|
|
4336
|
+
* by `units: 'world'` gradients, which fall back to screen space without
|
|
4337
|
+
* it — every other command carries its own transform in the stream, so
|
|
4338
|
+
* callers with no view concept can keep calling `render(commands)`.
|
|
4339
|
+
*/
|
|
4340
|
+
render(commands, viewMatrix) {
|
|
3948
4341
|
if (this.contextLost || this.disposed) return;
|
|
3949
4342
|
const gl = this.gl;
|
|
3950
4343
|
this.meshCache.drainPendingDeletes();
|
|
@@ -3959,6 +4352,7 @@ var WeaselRenderer = class {
|
|
|
3959
4352
|
textSdfR8: this.textSdfR8,
|
|
3960
4353
|
imageFill: this.imageFill,
|
|
3961
4354
|
gradFill: this.gradFill,
|
|
4355
|
+
patternFill: this.patternFill,
|
|
3962
4356
|
meshCache: this.meshCache,
|
|
3963
4357
|
textureCache: this.textureCache,
|
|
3964
4358
|
imageCache: this.imageCache,
|
|
@@ -3966,16 +4360,17 @@ var WeaselRenderer = class {
|
|
|
3966
4360
|
programRegistry: this.programRegistry,
|
|
3967
4361
|
quadVbo: this.quadVbo,
|
|
3968
4362
|
quadIbo: this.quadIbo,
|
|
3969
|
-
|
|
3970
|
-
rectVbo: this.rectVbo,
|
|
4363
|
+
solidBatch: this.solidBatch,
|
|
3971
4364
|
state: this.groupState,
|
|
3972
4365
|
widthCss: this.widthCss,
|
|
3973
4366
|
heightCss: this.heightCss,
|
|
3974
4367
|
clipDepth: 0,
|
|
3975
4368
|
flattenTolerance: this.flattenTolerance,
|
|
3976
|
-
textOutlineMinScreenSize: this.textOutlineMinScreenSize
|
|
4369
|
+
textOutlineMinScreenSize: this.textOutlineMinScreenSize,
|
|
4370
|
+
viewMatrix
|
|
3977
4371
|
};
|
|
3978
4372
|
for (const cmd of commands) dispatch(ctx, cmd);
|
|
4373
|
+
flushSolids(ctx);
|
|
3979
4374
|
this.meshCache.freeTransient();
|
|
3980
4375
|
}
|
|
3981
4376
|
resize(dims) {
|
|
@@ -4005,6 +4400,10 @@ var WeaselRenderer = class {
|
|
|
4005
4400
|
return this.pathFillVColor;
|
|
4006
4401
|
}
|
|
4007
4402
|
/** @internal */
|
|
4403
|
+
_solidBatch() {
|
|
4404
|
+
return this.solidBatch;
|
|
4405
|
+
}
|
|
4406
|
+
/** @internal */
|
|
4008
4407
|
_textSdf() {
|
|
4009
4408
|
return this.textSdf;
|
|
4010
4409
|
}
|
|
@@ -4021,6 +4420,10 @@ var WeaselRenderer = class {
|
|
|
4021
4420
|
return this.gradFill;
|
|
4022
4421
|
}
|
|
4023
4422
|
/** @internal */
|
|
4423
|
+
_patternFill() {
|
|
4424
|
+
return this.patternFill;
|
|
4425
|
+
}
|
|
4426
|
+
/** @internal */
|
|
4024
4427
|
_meshCache() {
|
|
4025
4428
|
return this.meshCache;
|
|
4026
4429
|
}
|
|
@@ -4086,6 +4489,11 @@ function alignedStrokeRect(rect, align, width = 1) {
|
|
|
4086
4489
|
};
|
|
4087
4490
|
}
|
|
4088
4491
|
|
|
4492
|
+
// src/core/viewport/meanScale.ts
|
|
4493
|
+
function meanScale(s) {
|
|
4494
|
+
return Math.sqrt(s.x * s.y);
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4089
4497
|
// src/features/grid/layer.ts
|
|
4090
4498
|
var DEFAULT_LINE = {
|
|
4091
4499
|
paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
@@ -5224,254 +5632,6 @@ function matchesKeyBinding(e, b) {
|
|
|
5224
5632
|
}
|
|
5225
5633
|
return true;
|
|
5226
5634
|
}
|
|
5227
|
-
var DepRegistryContext = createContext(null);
|
|
5228
|
-
function DepRegistryProvider({ children }) {
|
|
5229
|
-
const sourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
5230
|
-
const registry2 = useMemo(() => ({
|
|
5231
|
-
register: (name, source) => {
|
|
5232
|
-
sourcesRef.current.set(name, source);
|
|
5233
|
-
return () => {
|
|
5234
|
-
sourcesRef.current.delete(name);
|
|
5235
|
-
};
|
|
5236
|
-
},
|
|
5237
|
-
get: (name) => sourcesRef.current.get(name)?.()
|
|
5238
|
-
}), []);
|
|
5239
|
-
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry2, children });
|
|
5240
|
-
}
|
|
5241
|
-
function useDepRegistry() {
|
|
5242
|
-
const r = useContext(DepRegistryContext);
|
|
5243
|
-
if (r === null) {
|
|
5244
|
-
throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
|
|
5245
|
-
}
|
|
5246
|
-
return r;
|
|
5247
|
-
}
|
|
5248
|
-
function useOptionalDepRegistry() {
|
|
5249
|
-
return useContext(DepRegistryContext);
|
|
5250
|
-
}
|
|
5251
|
-
function useDepSource(name, source) {
|
|
5252
|
-
const registry2 = useDepRegistry();
|
|
5253
|
-
const sourceRef = useRef(source);
|
|
5254
|
-
sourceRef.current = source;
|
|
5255
|
-
useEffect(() => {
|
|
5256
|
-
return registry2.register(name, () => sourceRef.current());
|
|
5257
|
-
}, [name, registry2]);
|
|
5258
|
-
}
|
|
5259
|
-
|
|
5260
|
-
// src/interactions/actions/buildDeps.ts
|
|
5261
|
-
var DEV = (() => {
|
|
5262
|
-
try {
|
|
5263
|
-
return Boolean(import.meta.env?.DEV);
|
|
5264
|
-
} catch {
|
|
5265
|
-
return false;
|
|
5266
|
-
}
|
|
5267
|
-
})();
|
|
5268
|
-
function buildDepsFromRequires(action, depRegistry) {
|
|
5269
|
-
const requires = action.requires ?? [];
|
|
5270
|
-
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
5271
|
-
const deps = Object.fromEntries(entries);
|
|
5272
|
-
if (DEV) {
|
|
5273
|
-
const declared = new Set(requires);
|
|
5274
|
-
return new Proxy(deps, {
|
|
5275
|
-
get(target, prop, receiver) {
|
|
5276
|
-
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
5277
|
-
const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
|
|
5278
|
-
console.warn(message);
|
|
5279
|
-
throw new Error(message);
|
|
5280
|
-
}
|
|
5281
|
-
return Reflect.get(target, prop, receiver);
|
|
5282
|
-
}
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
|
-
return deps;
|
|
5286
|
-
}
|
|
5287
|
-
var ActionDisabledReason = {
|
|
5288
|
-
SelectionRequired: "selection-required",
|
|
5289
|
-
SceneEmpty: "scene-empty",
|
|
5290
|
-
NotApplicable: "not-applicable",
|
|
5291
|
-
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
5292
|
-
PredicateThrew: "predicate-threw"
|
|
5293
|
-
};
|
|
5294
|
-
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
5295
|
-
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
5296
|
-
var ENABLED_BUDGET_MS = 4;
|
|
5297
|
-
function evaluateEnabled(action, deps) {
|
|
5298
|
-
if (!action.enabled) return { enabled: true };
|
|
5299
|
-
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
5300
|
-
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
5301
|
-
const start = isDev3 ? now() : 0;
|
|
5302
|
-
try {
|
|
5303
|
-
const result = action.enabled(deps);
|
|
5304
|
-
if (isDev3) {
|
|
5305
|
-
const elapsed = now() - start;
|
|
5306
|
-
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
5307
|
-
enabledSlowWarned.add(action.id);
|
|
5308
|
-
console.warn(
|
|
5309
|
-
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
5310
|
-
);
|
|
5311
|
-
}
|
|
5312
|
-
}
|
|
5313
|
-
if (result === true) return { enabled: true };
|
|
5314
|
-
return { enabled: false, reason: result };
|
|
5315
|
-
} catch (e) {
|
|
5316
|
-
if (isDev3 && !enabledThrewWarned.has(action.id)) {
|
|
5317
|
-
enabledThrewWarned.add(action.id);
|
|
5318
|
-
console.warn(
|
|
5319
|
-
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
5320
|
-
e
|
|
5321
|
-
);
|
|
5322
|
-
}
|
|
5323
|
-
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
5324
|
-
}
|
|
5325
|
-
}
|
|
5326
|
-
function validateActionId(id) {
|
|
5327
|
-
if (id.length === 0) {
|
|
5328
|
-
throw new Error(`weasel: action id may not be empty`);
|
|
5329
|
-
}
|
|
5330
|
-
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
5331
|
-
throw new Error(
|
|
5332
|
-
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
5333
|
-
);
|
|
5334
|
-
}
|
|
5335
|
-
if (RESERVED_ID_NAMES.has(id)) {
|
|
5336
|
-
throw new Error(
|
|
5337
|
-
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
5338
|
-
);
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
function validateActionDefaultBinding(action) {
|
|
5342
|
-
const gs = action.defaultBinding;
|
|
5343
|
-
if (!gs) return;
|
|
5344
|
-
const entries = Array.isArray(gs) ? gs : [gs];
|
|
5345
|
-
for (const entry of entries) {
|
|
5346
|
-
const spec = "kind" in entry ? entry : entry.spec;
|
|
5347
|
-
const phase = spec.phase;
|
|
5348
|
-
if (phase === void 0) continue;
|
|
5349
|
-
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
5350
|
-
for (const atom of atoms) {
|
|
5351
|
-
if (atom.channel === "&") {
|
|
5352
|
-
throw new Error(
|
|
5353
|
-
`weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
|
|
5354
|
-
);
|
|
5355
|
-
}
|
|
5356
|
-
}
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
var ActionsContext = createContext(null);
|
|
5360
|
-
function ActionsProvider({ children }) {
|
|
5361
|
-
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
5362
|
-
const versionRef = useRef(0);
|
|
5363
|
-
const cachedRef = useRef([]);
|
|
5364
|
-
const cachedVerRef = useRef(-1);
|
|
5365
|
-
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
5366
|
-
const depReg = useOptionalDepRegistry();
|
|
5367
|
-
const depRegRef = useRef(depReg);
|
|
5368
|
-
depRegRef.current = depReg;
|
|
5369
|
-
const wiredDepRegRef = useRef(null);
|
|
5370
|
-
const dispatcherRef = useRef(null);
|
|
5371
|
-
const registry2 = useMemo(() => {
|
|
5372
|
-
const snapshot = () => {
|
|
5373
|
-
const v = versionRef.current;
|
|
5374
|
-
if (cachedVerRef.current === v) return cachedRef.current;
|
|
5375
|
-
const out = Object.freeze(Array.from(actionsRef.current.values()));
|
|
5376
|
-
cachedRef.current = out;
|
|
5377
|
-
cachedVerRef.current = v;
|
|
5378
|
-
return out;
|
|
5379
|
-
};
|
|
5380
|
-
const notify2 = () => {
|
|
5381
|
-
for (const l of listenersRef.current) {
|
|
5382
|
-
try {
|
|
5383
|
-
l();
|
|
5384
|
-
} catch (err) {
|
|
5385
|
-
console.error("weasel ActionsRegistry: subscriber threw", err);
|
|
5386
|
-
}
|
|
5387
|
-
}
|
|
5388
|
-
};
|
|
5389
|
-
return {
|
|
5390
|
-
register: (action) => {
|
|
5391
|
-
validateActionId(action.id);
|
|
5392
|
-
validateActionDefaultBinding(action);
|
|
5393
|
-
actionsRef.current.set(action.id, action);
|
|
5394
|
-
versionRef.current++;
|
|
5395
|
-
notify2();
|
|
5396
|
-
return () => {
|
|
5397
|
-
const cur = actionsRef.current.get(action.id);
|
|
5398
|
-
if (cur === action) {
|
|
5399
|
-
actionsRef.current.delete(action.id);
|
|
5400
|
-
versionRef.current++;
|
|
5401
|
-
notify2();
|
|
5402
|
-
}
|
|
5403
|
-
};
|
|
5404
|
-
},
|
|
5405
|
-
unregister: (id) => {
|
|
5406
|
-
if (actionsRef.current.delete(id)) {
|
|
5407
|
-
versionRef.current++;
|
|
5408
|
-
notify2();
|
|
5409
|
-
}
|
|
5410
|
-
},
|
|
5411
|
-
list: () => snapshot(),
|
|
5412
|
-
trigger: (id, params) => {
|
|
5413
|
-
const a = actionsRef.current.get(id);
|
|
5414
|
-
if (!a) return false;
|
|
5415
|
-
try {
|
|
5416
|
-
if (a.invoker && a.invoker.timing === "immediate") {
|
|
5417
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
5418
|
-
const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
|
|
5419
|
-
selection: r.get("selection"),
|
|
5420
|
-
scene: r.get("scene"),
|
|
5421
|
-
history: r.get("history"),
|
|
5422
|
-
view: r.get("view"),
|
|
5423
|
-
pointer: r.get("pointer"),
|
|
5424
|
-
activeTool: r.get("activeTool"),
|
|
5425
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
5426
|
-
};
|
|
5427
|
-
a.invoker.run(deps, params);
|
|
5428
|
-
}
|
|
5429
|
-
} catch (err) {
|
|
5430
|
-
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
5431
|
-
}
|
|
5432
|
-
return true;
|
|
5433
|
-
},
|
|
5434
|
-
subscribe: (listener) => {
|
|
5435
|
-
listenersRef.current.add(listener);
|
|
5436
|
-
return () => {
|
|
5437
|
-
listenersRef.current.delete(listener);
|
|
5438
|
-
};
|
|
5439
|
-
},
|
|
5440
|
-
setDispatcher: (d) => {
|
|
5441
|
-
dispatcherRef.current = d;
|
|
5442
|
-
},
|
|
5443
|
-
setDepRegistry: (r) => {
|
|
5444
|
-
wiredDepRegRef.current = r;
|
|
5445
|
-
},
|
|
5446
|
-
begin: (id, params) => {
|
|
5447
|
-
const disp = dispatcherRef.current;
|
|
5448
|
-
if (!disp) return null;
|
|
5449
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
5450
|
-
const deps = r ? {
|
|
5451
|
-
selection: r.get("selection"),
|
|
5452
|
-
scene: r.get("scene"),
|
|
5453
|
-
history: r.get("history"),
|
|
5454
|
-
view: r.get("view"),
|
|
5455
|
-
pointer: r.get("pointer"),
|
|
5456
|
-
activeTool: r.get("activeTool"),
|
|
5457
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
5458
|
-
} : {};
|
|
5459
|
-
return disp.beginUiOngoing(id, deps, params);
|
|
5460
|
-
}
|
|
5461
|
-
};
|
|
5462
|
-
}, []);
|
|
5463
|
-
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry2, children });
|
|
5464
|
-
}
|
|
5465
|
-
function useActionsRegistry() {
|
|
5466
|
-
return useContext(ActionsContext);
|
|
5467
|
-
}
|
|
5468
|
-
function useAction(action) {
|
|
5469
|
-
const reg = useActionsRegistry();
|
|
5470
|
-
useEffect(() => {
|
|
5471
|
-
if (!reg) return;
|
|
5472
|
-
return reg.register(action);
|
|
5473
|
-
}, [reg, action]);
|
|
5474
|
-
}
|
|
5475
5635
|
|
|
5476
5636
|
// src/interactions/actions/invoker.ts
|
|
5477
5637
|
function resolveParams(params) {
|
|
@@ -5513,12 +5673,7 @@ function moveGestureAdapter(scene) {
|
|
|
5513
5673
|
return {
|
|
5514
5674
|
getNode: (id) => scene.get(asNodeId(id)),
|
|
5515
5675
|
getNodes: () => {
|
|
5516
|
-
|
|
5517
|
-
for (const id of scene.renderOrder()) {
|
|
5518
|
-
const n = scene.get(id);
|
|
5519
|
-
if (n) out.push(n);
|
|
5520
|
-
}
|
|
5521
|
-
return out;
|
|
5676
|
+
return [...scene.renderOrderNodes()];
|
|
5522
5677
|
},
|
|
5523
5678
|
getPose: (id) => scene.get(asNodeId(id)).pose,
|
|
5524
5679
|
getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
|
|
@@ -6044,6 +6199,62 @@ var moveAction = {
|
|
|
6044
6199
|
enabled: () => true
|
|
6045
6200
|
};
|
|
6046
6201
|
|
|
6202
|
+
// src/features/paths/poseRotation.ts
|
|
6203
|
+
function poseRotationOf(pose) {
|
|
6204
|
+
const p = pose;
|
|
6205
|
+
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
6206
|
+
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
6207
|
+
}
|
|
6208
|
+
return null;
|
|
6209
|
+
}
|
|
6210
|
+
function rotatePathAround(path, cx, cy, rotation) {
|
|
6211
|
+
if (path.kind === "rect") {
|
|
6212
|
+
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
6213
|
+
}
|
|
6214
|
+
return rotatePolygon(path, cx, cy, rotation);
|
|
6215
|
+
}
|
|
6216
|
+
function rotatePolygon(path, cx, cy, rotation) {
|
|
6217
|
+
const cos = Math.cos(rotation);
|
|
6218
|
+
const sin = Math.sin(rotation);
|
|
6219
|
+
const { commands, coords } = path;
|
|
6220
|
+
const next = new Float32Array(coords.length);
|
|
6221
|
+
let ci = 0;
|
|
6222
|
+
for (let i = 0; i < commands.length; i++) {
|
|
6223
|
+
const len = COORD_COUNT[commands[i]];
|
|
6224
|
+
for (let k = 0; k < len; k += 2) {
|
|
6225
|
+
const dx = coords[ci + k] - cx;
|
|
6226
|
+
const dy = coords[ci + k + 1] - cy;
|
|
6227
|
+
next[ci + k] = cx + dx * cos - dy * sin;
|
|
6228
|
+
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
6229
|
+
}
|
|
6230
|
+
ci += len;
|
|
6231
|
+
}
|
|
6232
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
6233
|
+
}
|
|
6234
|
+
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
6235
|
+
const cos = Math.cos(rotation);
|
|
6236
|
+
const sin = Math.sin(rotation);
|
|
6237
|
+
const rotate = (px, py) => {
|
|
6238
|
+
const dx = px - cx;
|
|
6239
|
+
const dy = py - cy;
|
|
6240
|
+
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
6241
|
+
};
|
|
6242
|
+
const [x0, y0] = rotate(x, y);
|
|
6243
|
+
const [x1, y1] = rotate(x + w, y);
|
|
6244
|
+
const [x2, y2] = rotate(x + w, y + h);
|
|
6245
|
+
const [x3, y3] = rotate(x, y + h);
|
|
6246
|
+
const commands = new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]);
|
|
6247
|
+
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
6248
|
+
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
6249
|
+
}
|
|
6250
|
+
var COORD_COUNT = {
|
|
6251
|
+
[PATH_M]: 2,
|
|
6252
|
+
[PATH_L]: 2,
|
|
6253
|
+
[PATH_C]: 6,
|
|
6254
|
+
[PATH_Q]: 4,
|
|
6255
|
+
[PATH_Z]: 0
|
|
6256
|
+
};
|
|
6257
|
+
|
|
6047
6258
|
// src/interactions/actions/rotate/geometry.ts
|
|
6048
6259
|
function aabbCenter(bounds) {
|
|
6049
6260
|
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
@@ -6078,12 +6289,7 @@ function defaultCommitAdapter(scene) {
|
|
|
6078
6289
|
return {
|
|
6079
6290
|
getNode: (id) => scene.get(asNodeId(id)),
|
|
6080
6291
|
getNodes: () => {
|
|
6081
|
-
|
|
6082
|
-
for (const id of scene.renderOrder()) {
|
|
6083
|
-
const n = scene.get(id);
|
|
6084
|
-
if (n) out.push(n);
|
|
6085
|
-
}
|
|
6086
|
-
return out;
|
|
6292
|
+
return [...scene.renderOrderNodes()];
|
|
6087
6293
|
},
|
|
6088
6294
|
getPose: (id) => scene.get(asNodeId(id)).pose,
|
|
6089
6295
|
getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
|
|
@@ -6360,6 +6566,13 @@ var resizeAction = {
|
|
|
6360
6566
|
const computePose = (id) => {
|
|
6361
6567
|
const sp = scratch.startPoses.get(id);
|
|
6362
6568
|
if (sp === void 0) return void 0;
|
|
6569
|
+
if (isGroupPath && poseRotationOf(sp) !== null) {
|
|
6570
|
+
return remapRotatedLeaf(
|
|
6571
|
+
sp,
|
|
6572
|
+
scratch.originBounds,
|
|
6573
|
+
proposedBounds
|
|
6574
|
+
);
|
|
6575
|
+
}
|
|
6363
6576
|
return scratch.geometry.remapBounds(sp, scratch.originBounds, proposedBounds);
|
|
6364
6577
|
};
|
|
6365
6578
|
scratch.previews.clear();
|
|
@@ -7217,7 +7430,13 @@ function makeViewportZoomAction(opts = {}) {
|
|
|
7217
7430
|
var viewportZoomAction = makeViewportZoomAction();
|
|
7218
7431
|
|
|
7219
7432
|
// src/interactions/dispatcher/predicates.ts
|
|
7220
|
-
|
|
7433
|
+
function bodyPredicate(fn) {
|
|
7434
|
+
return Object.assign(fn, { readsAffordance: false });
|
|
7435
|
+
}
|
|
7436
|
+
var isBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
|
|
7437
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body");
|
|
7438
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "unselected-body");
|
|
7439
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "empty");
|
|
7221
7440
|
function kindOf(target) {
|
|
7222
7441
|
if (typeof target !== "object" || target === null) return void 0;
|
|
7223
7442
|
const k = target.kind;
|
|
@@ -7258,17 +7477,17 @@ function splitCubicAtT(p0, p1, p2, p3, t) {
|
|
|
7258
7477
|
function fitCubicThroughDeletion(prev, next) {
|
|
7259
7478
|
const dx = next.x - prev.x;
|
|
7260
7479
|
const dy = next.y - prev.y;
|
|
7261
|
-
const
|
|
7480
|
+
const dist3 = Math.sqrt(dx * dx + dy * dy);
|
|
7262
7481
|
const c1DirX = prev.outHandle ? prev.outHandle.x - prev.x : dx;
|
|
7263
7482
|
const c1DirY = prev.outHandle ? prev.outHandle.y - prev.y : dy;
|
|
7264
|
-
const c1 = controlOnSide(prev, c1DirX, c1DirY,
|
|
7483
|
+
const c1 = controlOnSide(prev, c1DirX, c1DirY, dist3, 1 / 3);
|
|
7265
7484
|
const c2DirX = next.inHandle ? next.inHandle.x - next.x : -dx;
|
|
7266
7485
|
const c2DirY = next.inHandle ? next.inHandle.y - next.y : -dy;
|
|
7267
|
-
const c2 = controlOnSide(next, c2DirX, c2DirY,
|
|
7486
|
+
const c2 = controlOnSide(next, c2DirX, c2DirY, dist3, 1 / 3);
|
|
7268
7487
|
return { c1, c2 };
|
|
7269
7488
|
}
|
|
7270
|
-
function controlOnSide(anchor, dirX, dirY,
|
|
7271
|
-
const targetDist =
|
|
7489
|
+
function controlOnSide(anchor, dirX, dirY, dist3, fraction) {
|
|
7490
|
+
const targetDist = dist3 * fraction;
|
|
7272
7491
|
const len = Math.hypot(dirX, dirY) || 1;
|
|
7273
7492
|
const ux = dirX / len, uy = dirY / len;
|
|
7274
7493
|
return { x: anchor.x + ux * targetDist, y: anchor.y + uy * targetDist };
|
|
@@ -7539,64 +7758,6 @@ function anchorsInRect(set, rect) {
|
|
|
7539
7758
|
}
|
|
7540
7759
|
return hits;
|
|
7541
7760
|
}
|
|
7542
|
-
|
|
7543
|
-
// src/features/paths/poseRotation.ts
|
|
7544
|
-
function poseRotationOf(pose) {
|
|
7545
|
-
const p = pose;
|
|
7546
|
-
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
7547
|
-
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
7548
|
-
}
|
|
7549
|
-
return null;
|
|
7550
|
-
}
|
|
7551
|
-
function rotatePathAround(path, cx, cy, rotation) {
|
|
7552
|
-
if (path.kind === "rect") {
|
|
7553
|
-
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
7554
|
-
}
|
|
7555
|
-
return rotatePolygon(path, cx, cy, rotation);
|
|
7556
|
-
}
|
|
7557
|
-
function rotatePolygon(path, cx, cy, rotation) {
|
|
7558
|
-
const cos = Math.cos(rotation);
|
|
7559
|
-
const sin = Math.sin(rotation);
|
|
7560
|
-
const { commands, coords } = path;
|
|
7561
|
-
const next = new Float32Array(coords.length);
|
|
7562
|
-
let ci = 0;
|
|
7563
|
-
for (let i = 0; i < commands.length; i++) {
|
|
7564
|
-
const len = COORD_COUNT[commands[i]];
|
|
7565
|
-
for (let k = 0; k < len; k += 2) {
|
|
7566
|
-
const dx = coords[ci + k] - cx;
|
|
7567
|
-
const dy = coords[ci + k + 1] - cy;
|
|
7568
|
-
next[ci + k] = cx + dx * cos - dy * sin;
|
|
7569
|
-
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
7570
|
-
}
|
|
7571
|
-
ci += len;
|
|
7572
|
-
}
|
|
7573
|
-
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
7574
|
-
}
|
|
7575
|
-
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
7576
|
-
const cos = Math.cos(rotation);
|
|
7577
|
-
const sin = Math.sin(rotation);
|
|
7578
|
-
const rotate = (px, py) => {
|
|
7579
|
-
const dx = px - cx;
|
|
7580
|
-
const dy = py - cy;
|
|
7581
|
-
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
7582
|
-
};
|
|
7583
|
-
const [x0, y0] = rotate(x, y);
|
|
7584
|
-
const [x1, y1] = rotate(x + w, y);
|
|
7585
|
-
const [x2, y2] = rotate(x + w, y + h);
|
|
7586
|
-
const [x3, y3] = rotate(x, y + h);
|
|
7587
|
-
const commands = new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]);
|
|
7588
|
-
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
7589
|
-
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
7590
|
-
}
|
|
7591
|
-
var COORD_COUNT = {
|
|
7592
|
-
[PATH_M]: 2,
|
|
7593
|
-
[PATH_L]: 2,
|
|
7594
|
-
[PATH_C]: 6,
|
|
7595
|
-
[PATH_Q]: 4,
|
|
7596
|
-
[PATH_Z]: 0
|
|
7597
|
-
};
|
|
7598
|
-
|
|
7599
|
-
// src/features/paths/pathInWorld.ts
|
|
7600
7761
|
function pathInPoseFrame(path, pose) {
|
|
7601
7762
|
if (path.kind === "rect") {
|
|
7602
7763
|
if (path.x === pose.x && path.y === pose.y && path.width === pose.width && path.height === pose.height) return path;
|
|
@@ -9281,7 +9442,7 @@ var LABEL = {
|
|
|
9281
9442
|
divide: "Divide",
|
|
9282
9443
|
crop: "Crop"
|
|
9283
9444
|
};
|
|
9284
|
-
function
|
|
9445
|
+
function isEmpty2(p) {
|
|
9285
9446
|
return p.commands.length === 0;
|
|
9286
9447
|
}
|
|
9287
9448
|
function applyBooleanOp(adapter, op) {
|
|
@@ -9319,7 +9480,7 @@ function applyBooleanOp(adapter, op) {
|
|
|
9319
9480
|
break;
|
|
9320
9481
|
}
|
|
9321
9482
|
}
|
|
9322
|
-
results = results.filter((p) => !
|
|
9483
|
+
results = results.filter((p) => !isEmpty2(p));
|
|
9323
9484
|
if (results.length === 0) return { kind: "noop", reason: "empty-result" };
|
|
9324
9485
|
const topmostId = entries[entries.length - 1].id;
|
|
9325
9486
|
const topAnchor = adapter.getZOrder?.(topmostId);
|
|
@@ -9870,14 +10031,24 @@ var DEFAULT_PALETTE = [
|
|
|
9870
10031
|
var GHOST_STROKE = DEFAULT_PALETTE[0];
|
|
9871
10032
|
|
|
9872
10033
|
// src/interactions/actions/defaults/setFill.ts
|
|
10034
|
+
function resolveFill(scratch, prev) {
|
|
10035
|
+
if (scratch.currentPaint) return scratch.currentPaint;
|
|
10036
|
+
const prevColor = typeof prev?.fill === "string" ? prev.fill : DEFAULT_FILL_COLOR;
|
|
10037
|
+
return mergeAlphaFromPrev(scratch.currentColor, prevColor);
|
|
10038
|
+
}
|
|
9873
10039
|
function refreshPreviews(scratch) {
|
|
9874
10040
|
scratch.previews.clear();
|
|
9875
10041
|
for (const id of scratch.ids) {
|
|
9876
10042
|
const prev = scratch.startData.get(id);
|
|
9877
|
-
|
|
9878
|
-
scratch.previews.set(id, { ...prev ?? {}, fill: next });
|
|
10043
|
+
scratch.previews.set(id, { ...prev ?? {}, fill: resolveFill(scratch, prev) });
|
|
9879
10044
|
}
|
|
9880
10045
|
}
|
|
10046
|
+
function readParams(params) {
|
|
10047
|
+
return {
|
|
10048
|
+
color: params?.color,
|
|
10049
|
+
paint: params?.paint
|
|
10050
|
+
};
|
|
10051
|
+
}
|
|
9881
10052
|
var setFillAction = {
|
|
9882
10053
|
id: "setFill",
|
|
9883
10054
|
label: "Set fill",
|
|
@@ -9892,9 +10063,10 @@ var setFillAction = {
|
|
|
9892
10063
|
if (!selection || !scene) return {};
|
|
9893
10064
|
const ids = selection.get();
|
|
9894
10065
|
if (ids.length === 0) return {};
|
|
9895
|
-
const
|
|
9896
|
-
const
|
|
9897
|
-
const
|
|
10066
|
+
const fromCtx = readParams(ctx.params);
|
|
10067
|
+
const fromOpts = readParams(opts?.params);
|
|
10068
|
+
const initialPaint = fromCtx.paint ?? fromOpts.paint;
|
|
10069
|
+
const initialColor = fromCtx.color ?? fromOpts.color ?? DEFAULT_STROKE_COLOR;
|
|
9898
10070
|
const startData = /* @__PURE__ */ new Map();
|
|
9899
10071
|
for (const id of ids) {
|
|
9900
10072
|
const node = scene.get(id);
|
|
@@ -9905,15 +10077,20 @@ var setFillAction = {
|
|
|
9905
10077
|
scene,
|
|
9906
10078
|
startData,
|
|
9907
10079
|
currentColor: initialColor,
|
|
10080
|
+
currentPaint: initialPaint,
|
|
9908
10081
|
previews: /* @__PURE__ */ new Map(),
|
|
9909
10082
|
applyOps
|
|
9910
10083
|
};
|
|
9911
10084
|
refreshPreviews(scratch);
|
|
9912
10085
|
return {
|
|
9913
10086
|
onMove(moveCtx) {
|
|
9914
|
-
const
|
|
9915
|
-
if (
|
|
9916
|
-
scratch.
|
|
10087
|
+
const { color, paint } = readParams(moveCtx.params);
|
|
10088
|
+
if (color === void 0 && paint === void 0) return;
|
|
10089
|
+
if (paint !== void 0) scratch.currentPaint = paint;
|
|
10090
|
+
if (color !== void 0) {
|
|
10091
|
+
scratch.currentColor = color;
|
|
10092
|
+
if (paint === void 0) scratch.currentPaint = void 0;
|
|
10093
|
+
}
|
|
9917
10094
|
refreshPreviews(scratch);
|
|
9918
10095
|
},
|
|
9919
10096
|
onEnd(_endCtx, reason) {
|
|
@@ -9923,8 +10100,7 @@ var setFillAction = {
|
|
|
9923
10100
|
}
|
|
9924
10101
|
const ops = [];
|
|
9925
10102
|
for (const id of scratch.ids) {
|
|
9926
|
-
const
|
|
9927
|
-
const merged = mergeAlphaFromPrev(scratch.currentColor, prev?.fill ?? DEFAULT_FILL_COLOR);
|
|
10103
|
+
const merged = resolveFill(scratch, scratch.startData.get(id));
|
|
9928
10104
|
const nodeNow = scratch.scene.get(id);
|
|
9929
10105
|
if (!nodeNow) continue;
|
|
9930
10106
|
const from = { ...nodeNow.data };
|
|
@@ -10813,13 +10989,82 @@ function modsCount(mods) {
|
|
|
10813
10989
|
}
|
|
10814
10990
|
function targetRank(target) {
|
|
10815
10991
|
if (target === void 0) return 0;
|
|
10816
|
-
|
|
10817
|
-
if (
|
|
10818
|
-
|
|
10819
|
-
|
|
10992
|
+
const form = parseTargetSpec(target);
|
|
10993
|
+
if (form === null) return 1;
|
|
10994
|
+
switch (form.form) {
|
|
10995
|
+
case "kind":
|
|
10996
|
+
return form.requireSelected ? 3 : 2;
|
|
10997
|
+
case "affordance":
|
|
10998
|
+
return 2;
|
|
10999
|
+
case "body":
|
|
11000
|
+
return 1;
|
|
11001
|
+
case "predicate":
|
|
11002
|
+
return 1;
|
|
11003
|
+
default: {
|
|
11004
|
+
return 1;
|
|
11005
|
+
}
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
function targetConsultsAffordance(specTarget) {
|
|
11009
|
+
if (specTarget === void 0) return false;
|
|
11010
|
+
const form = parseTargetSpec(specTarget);
|
|
11011
|
+
if (form === null) return false;
|
|
11012
|
+
switch (form.form) {
|
|
11013
|
+
case "predicate":
|
|
11014
|
+
return form.kindOf.readsAffordance !== false;
|
|
11015
|
+
case "affordance":
|
|
11016
|
+
return true;
|
|
11017
|
+
case "body":
|
|
11018
|
+
return false;
|
|
11019
|
+
case "kind":
|
|
11020
|
+
return false;
|
|
11021
|
+
default: {
|
|
11022
|
+
return false;
|
|
11023
|
+
}
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
function specTargetOf(spec) {
|
|
11027
|
+
return "target" in spec ? spec.target : void 0;
|
|
11028
|
+
}
|
|
11029
|
+
function claimOf(e) {
|
|
11030
|
+
return "affordance" in e ? e.affordance : void 0;
|
|
11031
|
+
}
|
|
11032
|
+
function claimGestureOf(e) {
|
|
11033
|
+
switch (e.kind) {
|
|
11034
|
+
case "pointerdown":
|
|
11035
|
+
case "click":
|
|
11036
|
+
return "pointer";
|
|
11037
|
+
case "doubleclick":
|
|
11038
|
+
return "doubleClick";
|
|
11039
|
+
case "contextmenu":
|
|
11040
|
+
return "contextMenu";
|
|
11041
|
+
case "longpress":
|
|
11042
|
+
return "longPress";
|
|
11043
|
+
case "wheel":
|
|
11044
|
+
return "wheel";
|
|
11045
|
+
default:
|
|
11046
|
+
return null;
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
function isExclusiveClaim(e) {
|
|
11050
|
+
const claim = claimOf(e);
|
|
11051
|
+
if (claim?.strength !== "exclusive") return false;
|
|
11052
|
+
const gesture = claimGestureOf(e);
|
|
11053
|
+
if (gesture === null) return false;
|
|
11054
|
+
return claim.claimedKinds === void 0 || claim.claimedKinds.includes(gesture);
|
|
11055
|
+
}
|
|
11056
|
+
var warnedDeadClaims = /* @__PURE__ */ new Set();
|
|
11057
|
+
function reportDeadClaim(owner, warn) {
|
|
11058
|
+
if (process.env.NODE_ENV === "production") return;
|
|
11059
|
+
const key = String(owner);
|
|
11060
|
+
if (warnedDeadClaims.has(key)) return;
|
|
11061
|
+
warnedDeadClaims.add(key);
|
|
11062
|
+
warn(
|
|
11063
|
+
`[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
|
|
11064
|
+
);
|
|
10820
11065
|
}
|
|
10821
11066
|
function specificity(spec) {
|
|
10822
|
-
const t = targetRank(
|
|
11067
|
+
const t = targetRank(specTargetOf(spec));
|
|
10823
11068
|
const mods = "mods" in spec ? spec.mods : void 0;
|
|
10824
11069
|
const m = modsCount(mods);
|
|
10825
11070
|
const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
|
|
@@ -10834,12 +11079,14 @@ function compareSpecificity(a, b) {
|
|
|
10834
11079
|
}
|
|
10835
11080
|
return 0;
|
|
10836
11081
|
}
|
|
10837
|
-
function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
11082
|
+
function matchSorted(e, bindings, isMac, engagedChannels, warn = (m) => console.warn(m)) {
|
|
10838
11083
|
const out = [];
|
|
10839
11084
|
const engaged = engagedChannels ?? EMPTY_ENGAGED;
|
|
11085
|
+
const exclusive = isExclusiveClaim(e);
|
|
11086
|
+
const pool = exclusive ? bindings.filter((sb) => targetConsultsAffordance(specTargetOf(sb.binding.spec))) : bindings;
|
|
10840
11087
|
for (const scope of SCOPE_PRIORITY) {
|
|
10841
11088
|
const scopeMatches = [];
|
|
10842
|
-
for (const sb of
|
|
11089
|
+
for (const sb of pool) {
|
|
10843
11090
|
if (sb.scope !== scope) continue;
|
|
10844
11091
|
const phaseCtx = { selfChannel: sb.ownerToolId, engagedChannels: engaged };
|
|
10845
11092
|
if (matchSpec(e, sb.binding.spec, isMac, phaseCtx)) {
|
|
@@ -10849,10 +11096,37 @@ function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
|
10849
11096
|
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
10850
11097
|
out.push(...scopeMatches);
|
|
10851
11098
|
}
|
|
11099
|
+
if (exclusive && out.length === 0 && bindings.length > 0) {
|
|
11100
|
+
reportDeadClaim(claimOf(e)?.owner, warn);
|
|
11101
|
+
}
|
|
10852
11102
|
return out;
|
|
10853
11103
|
}
|
|
10854
11104
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
10855
11105
|
|
|
11106
|
+
// src/contributions/eligibility.ts
|
|
11107
|
+
function liveScope(id, eligibility, state) {
|
|
11108
|
+
const tags = eligibility.capabilities;
|
|
11109
|
+
if (tags && tags.length > 0 && state.allows && !state.allows(tags)) return null;
|
|
11110
|
+
if (state.engagedIds?.has(id)) return "hotkey";
|
|
11111
|
+
if (eligibility.offhand && state.heldTriggers.has(eligibility.offhand)) return "hotkey";
|
|
11112
|
+
if (eligibility.focus && state.focusedId === id) return "active";
|
|
11113
|
+
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
11114
|
+
return null;
|
|
11115
|
+
}
|
|
11116
|
+
|
|
11117
|
+
// src/contributions/assemble.ts
|
|
11118
|
+
function scopeBindings(entries, state) {
|
|
11119
|
+
const out = [];
|
|
11120
|
+
for (const entry of entries) {
|
|
11121
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
11122
|
+
if (scope === null) continue;
|
|
11123
|
+
for (const binding of entry.bindings ?? []) {
|
|
11124
|
+
out.push({ binding, scope, ownerToolId: entry.id });
|
|
11125
|
+
}
|
|
11126
|
+
}
|
|
11127
|
+
return out;
|
|
11128
|
+
}
|
|
11129
|
+
|
|
10856
11130
|
// src/features/chrome-caps/rule.ts
|
|
10857
11131
|
var ALWAYS = { all: [] };
|
|
10858
11132
|
var NEVER = { any: [] };
|
|
@@ -11157,7 +11431,7 @@ function useHoverTracking(args) {
|
|
|
11157
11431
|
function pointerGestureId(pointerId) {
|
|
11158
11432
|
return `pointer-${pointerId ?? "mouse"}`;
|
|
11159
11433
|
}
|
|
11160
|
-
var
|
|
11434
|
+
var DEV = (() => {
|
|
11161
11435
|
try {
|
|
11162
11436
|
return Boolean(import.meta.env?.DEV);
|
|
11163
11437
|
} catch {
|
|
@@ -11165,9 +11439,9 @@ var DEV2 = (() => {
|
|
|
11165
11439
|
}
|
|
11166
11440
|
})();
|
|
11167
11441
|
var TRACE_LIMIT = 200;
|
|
11168
|
-
var traceLog =
|
|
11442
|
+
var traceLog = DEV && typeof window !== "undefined" ? window.__weaselDispatchLog__ ??= [] : [];
|
|
11169
11443
|
function recordTrace(entry) {
|
|
11170
|
-
if (!
|
|
11444
|
+
if (!DEV) return;
|
|
11171
11445
|
traceLog.push(entry);
|
|
11172
11446
|
if (traceLog.length > TRACE_LIMIT) traceLog.shift();
|
|
11173
11447
|
}
|
|
@@ -11198,6 +11472,7 @@ function filterEligible(matches, actionLookup, ruleCtx) {
|
|
|
11198
11472
|
return isEligible(action, ruleCtx);
|
|
11199
11473
|
});
|
|
11200
11474
|
}
|
|
11475
|
+
var EMPTY_TRIGGERS = /* @__PURE__ */ new Set();
|
|
11201
11476
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
11202
11477
|
function modifiersOf(e) {
|
|
11203
11478
|
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
@@ -11305,34 +11580,23 @@ function createDispatcher(opts) {
|
|
|
11305
11580
|
return `ongoing-${event.kind}`;
|
|
11306
11581
|
}
|
|
11307
11582
|
function assembleScopedBindings(ctx) {
|
|
11308
|
-
const
|
|
11309
|
-
for (const
|
|
11310
|
-
const tool = ctx.toolsById.get(
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
for (const binding of tool?.bindings ?? []) {
|
|
11322
|
-
result.push({ binding, scope: "ambient", ownerToolId: toolId });
|
|
11323
|
-
}
|
|
11324
|
-
}
|
|
11583
|
+
const ordered = [];
|
|
11584
|
+
for (const id of ctx.hotkeyStack) {
|
|
11585
|
+
const tool = ctx.toolsById.get(id);
|
|
11586
|
+
if (tool && !ordered.includes(tool)) ordered.push(tool);
|
|
11587
|
+
}
|
|
11588
|
+
for (const tool of ctx.toolsById.values()) {
|
|
11589
|
+
if (!ordered.includes(tool)) ordered.push(tool);
|
|
11590
|
+
}
|
|
11591
|
+
const result = scopeBindings(ordered, {
|
|
11592
|
+
focusedId: ctx.activeToolId,
|
|
11593
|
+
heldTriggers: EMPTY_TRIGGERS,
|
|
11594
|
+
engagedIds: new Set(ctx.hotkeyStack)
|
|
11595
|
+
});
|
|
11325
11596
|
for (const action of ctx.actions.list()) {
|
|
11326
|
-
const
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
for (const entry of raw) {
|
|
11330
|
-
const isBoundObj = !("kind" in entry);
|
|
11331
|
-
const spec = isBoundObj ? entry.spec : entry;
|
|
11332
|
-
const opts2 = isBoundObj ? entry.opts : void 0;
|
|
11333
|
-
const defaultBinding = { spec, actionId: action.id, ...opts2 !== void 0 ? { opts: opts2 } : {} };
|
|
11334
|
-
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
11335
|
-
result.push({ binding: defaultBinding, scope: targetScope, ownerToolId: null });
|
|
11597
|
+
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
11598
|
+
for (const binding of actionBindings(action)) {
|
|
11599
|
+
result.push({ binding, scope: targetScope, ownerToolId: null });
|
|
11336
11600
|
}
|
|
11337
11601
|
}
|
|
11338
11602
|
return result;
|
|
@@ -11487,15 +11751,26 @@ function createDispatcher(opts) {
|
|
|
11487
11751
|
deltaY: event.deltaY,
|
|
11488
11752
|
clientX: event.clientX,
|
|
11489
11753
|
clientY: event.clientY,
|
|
11754
|
+
affordance: event.affordance,
|
|
11490
11755
|
...resolved
|
|
11491
11756
|
};
|
|
11492
11757
|
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
11493
11758
|
params = {
|
|
11494
11759
|
worldX: event.worldX,
|
|
11495
11760
|
worldY: event.worldY,
|
|
11761
|
+
affordance: event.affordance,
|
|
11496
11762
|
// Press point as well as release point — an action that places
|
|
11497
11763
|
// geometry at the click wants the former. See `ClickEvent`.
|
|
11498
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY
|
|
11764
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
11765
|
+
mods: modifiersOf(event),
|
|
11766
|
+
...resolved
|
|
11767
|
+
};
|
|
11768
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
11769
|
+
params = {
|
|
11770
|
+
worldX: event.kind === "contextmenu" ? event.worldX : event.x,
|
|
11771
|
+
worldY: event.kind === "contextmenu" ? event.worldY : event.y,
|
|
11772
|
+
affordance: event.affordance,
|
|
11773
|
+
bodyTarget: event.bodyTarget,
|
|
11499
11774
|
mods: modifiersOf(event),
|
|
11500
11775
|
...resolved
|
|
11501
11776
|
};
|
|
@@ -11867,7 +12142,7 @@ function computeMultiTouchGeometry(positions) {
|
|
|
11867
12142
|
return { centroid, spread };
|
|
11868
12143
|
}
|
|
11869
12144
|
function useGestureDispatcher(opts) {
|
|
11870
|
-
const { canvasRef, actions, toolsById,
|
|
12145
|
+
const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, getRuleCtx, onDoubleClick } = opts;
|
|
11871
12146
|
const onDoubleClickRef = useRef(onDoubleClick);
|
|
11872
12147
|
onDoubleClickRef.current = onDoubleClick;
|
|
11873
12148
|
const activeTool = useActiveToolContext();
|
|
@@ -11882,7 +12157,6 @@ function useGestureDispatcher(opts) {
|
|
|
11882
12157
|
activeToolId: activeTool.active,
|
|
11883
12158
|
hotkeyStack: activeTool.hotkeyStack,
|
|
11884
12159
|
toolsById,
|
|
11885
|
-
ambientToolIds,
|
|
11886
12160
|
isMac: IS_MAC,
|
|
11887
12161
|
getRuleCtx
|
|
11888
12162
|
});
|
|
@@ -11892,7 +12166,6 @@ function useGestureDispatcher(opts) {
|
|
|
11892
12166
|
activeToolId: activeTool.active,
|
|
11893
12167
|
hotkeyStack: activeTool.hotkeyStack,
|
|
11894
12168
|
toolsById,
|
|
11895
|
-
ambientToolIds,
|
|
11896
12169
|
isMac: IS_MAC,
|
|
11897
12170
|
getRuleCtx
|
|
11898
12171
|
};
|
|
@@ -11970,6 +12243,7 @@ function useGestureDispatcher(opts) {
|
|
|
11970
12243
|
ctrlKey: down.ctrlKey,
|
|
11971
12244
|
metaKey: down.metaKey,
|
|
11972
12245
|
shiftKey: down.shiftKey,
|
|
12246
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
11973
12247
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
11974
12248
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
11975
12249
|
};
|
|
@@ -11979,11 +12253,15 @@ function useGestureDispatcher(opts) {
|
|
|
11979
12253
|
y: down.worldY,
|
|
11980
12254
|
clientX: down.clientX,
|
|
11981
12255
|
clientY: down.clientY,
|
|
11982
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
11983
12256
|
...shared
|
|
11984
12257
|
});
|
|
11985
12258
|
if (result === "unhandled") {
|
|
11986
|
-
dispatch2({
|
|
12259
|
+
dispatch2({
|
|
12260
|
+
kind: "contextmenu",
|
|
12261
|
+
worldX: down.worldX,
|
|
12262
|
+
worldY: down.worldY,
|
|
12263
|
+
...shared
|
|
12264
|
+
});
|
|
11987
12265
|
}
|
|
11988
12266
|
};
|
|
11989
12267
|
const DOUBLE_CLICK_MAX_MS = 600;
|
|
@@ -12038,6 +12316,9 @@ function useGestureDispatcher(opts) {
|
|
|
12038
12316
|
const onWheel = (e) => {
|
|
12039
12317
|
const rect = canvas?.getBoundingClientRect();
|
|
12040
12318
|
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
12319
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
12320
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
12321
|
+
const body = classifyTargetRef.current?.(screenPoint);
|
|
12041
12322
|
const ev = {
|
|
12042
12323
|
kind: "wheel",
|
|
12043
12324
|
altKey: e.altKey,
|
|
@@ -12047,7 +12328,10 @@ function useGestureDispatcher(opts) {
|
|
|
12047
12328
|
deltaX: e.deltaX,
|
|
12048
12329
|
deltaY: e.deltaY,
|
|
12049
12330
|
clientX: localX,
|
|
12050
|
-
clientY: localY
|
|
12331
|
+
clientY: localY,
|
|
12332
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
12333
|
+
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
12334
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
12051
12335
|
};
|
|
12052
12336
|
const result = dispatch2(ev);
|
|
12053
12337
|
if (result === "handled") {
|
|
@@ -12341,6 +12625,7 @@ function useGestureDispatcher(opts) {
|
|
|
12341
12625
|
shiftKey: e.shiftKey,
|
|
12342
12626
|
worldX: wClick.x,
|
|
12343
12627
|
worldY: wClick.y,
|
|
12628
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
12344
12629
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
12345
12630
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
12346
12631
|
};
|
|
@@ -12382,8 +12667,10 @@ function useGestureDispatcher(opts) {
|
|
|
12382
12667
|
};
|
|
12383
12668
|
const onContextMenu = (e) => {
|
|
12384
12669
|
e.preventDefault();
|
|
12385
|
-
const
|
|
12386
|
-
const
|
|
12670
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
12671
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
12672
|
+
const menuBody = classifyTargetRef.current?.(screenPoint);
|
|
12673
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
12387
12674
|
const ev = {
|
|
12388
12675
|
kind: "contextmenu",
|
|
12389
12676
|
target: e.target,
|
|
@@ -12391,6 +12678,9 @@ function useGestureDispatcher(opts) {
|
|
|
12391
12678
|
ctrlKey: e.ctrlKey,
|
|
12392
12679
|
metaKey: e.metaKey,
|
|
12393
12680
|
shiftKey: e.shiftKey,
|
|
12681
|
+
worldX: w.x,
|
|
12682
|
+
worldY: w.y,
|
|
12683
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
12394
12684
|
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
12395
12685
|
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
12396
12686
|
};
|
|
@@ -12786,51 +13076,213 @@ function useViewAnimation(setView) {
|
|
|
12786
13076
|
if (target === currentView) return;
|
|
12787
13077
|
animateTo(currentView, target, { duration: opts?.duration, easing: opts?.easing });
|
|
12788
13078
|
},
|
|
12789
|
-
[animateTo]
|
|
12790
|
-
);
|
|
12791
|
-
return { animateTo, animateToBounds, cancel };
|
|
13079
|
+
[animateTo]
|
|
13080
|
+
);
|
|
13081
|
+
return { animateTo, animateToBounds, cancel };
|
|
13082
|
+
}
|
|
13083
|
+
|
|
13084
|
+
// src/interactions/actions/defaults/toolOffhand.ts
|
|
13085
|
+
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
13086
|
+
function offhandKeyFor(trigger) {
|
|
13087
|
+
switch (trigger) {
|
|
13088
|
+
case "space":
|
|
13089
|
+
return " ";
|
|
13090
|
+
case "alt":
|
|
13091
|
+
return "Alt";
|
|
13092
|
+
case "ctrl":
|
|
13093
|
+
return "Control";
|
|
13094
|
+
case "meta":
|
|
13095
|
+
return "Meta";
|
|
13096
|
+
case "shift":
|
|
13097
|
+
return "Shift";
|
|
13098
|
+
}
|
|
13099
|
+
}
|
|
13100
|
+
function buildToolOffhandBindings(specs) {
|
|
13101
|
+
return specs.map(({ toolId, key }) => ({
|
|
13102
|
+
spec: { kind: "key-held", key },
|
|
13103
|
+
opts: { params: { toolId } }
|
|
13104
|
+
}));
|
|
13105
|
+
}
|
|
13106
|
+
function makeToolOffhandAction(bindings) {
|
|
13107
|
+
return {
|
|
13108
|
+
id: TOOL_OFFHAND_ID,
|
|
13109
|
+
label: "Engage tool (offhand)",
|
|
13110
|
+
defaultBinding: bindings,
|
|
13111
|
+
scope: "hotkey",
|
|
13112
|
+
invoker: {
|
|
13113
|
+
timing: "ongoing",
|
|
13114
|
+
start: (ctx, opts) => {
|
|
13115
|
+
const activeTool = ctx.deps.activeTool;
|
|
13116
|
+
const toolId = resolveParams(opts?.params)?.toolId;
|
|
13117
|
+
if (!activeTool || !toolId) return {};
|
|
13118
|
+
activeTool.pushHotkey(toolId);
|
|
13119
|
+
return {
|
|
13120
|
+
onEnd: () => {
|
|
13121
|
+
activeTool.popHotkey();
|
|
13122
|
+
}
|
|
13123
|
+
};
|
|
13124
|
+
}
|
|
13125
|
+
},
|
|
13126
|
+
requires: ["activeTool"]
|
|
13127
|
+
};
|
|
12792
13128
|
}
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
13129
|
+
|
|
13130
|
+
// src/contributions/useContributions.ts
|
|
13131
|
+
var NO_TRIGGERS = /* @__PURE__ */ new Set();
|
|
13132
|
+
function useContributions(opts) {
|
|
12797
13133
|
const ctx = useActiveToolContext();
|
|
13134
|
+
const actionsRegistry = useActionsRegistry();
|
|
12798
13135
|
const hasInitializedRef = useRef(false);
|
|
12799
13136
|
const isFirstRender = !hasInitializedRef.current;
|
|
12800
13137
|
const needsFirstMountSyncRef = useRef(false);
|
|
12801
13138
|
if (isFirstRender) {
|
|
12802
13139
|
hasInitializedRef.current = true;
|
|
12803
|
-
needsFirstMountSyncRef.current = ctx.active === "select" && opts.
|
|
13140
|
+
needsFirstMountSyncRef.current = ctx.active === "select" && opts.focused !== "select";
|
|
12804
13141
|
}
|
|
12805
13142
|
useEffect(() => {
|
|
12806
13143
|
if (!needsFirstMountSyncRef.current) return;
|
|
12807
13144
|
needsFirstMountSyncRef.current = false;
|
|
12808
|
-
ctx.setActive(opts.
|
|
13145
|
+
ctx.setActive(opts.focused);
|
|
13146
|
+
}, []);
|
|
13147
|
+
const focused2 = isFirstRender && ctx.active === "select" && opts.focused !== "select" ? opts.focused : ctx.active;
|
|
13148
|
+
const entriesRef = useRef(opts.entries);
|
|
13149
|
+
entriesRef.current = opts.entries;
|
|
13150
|
+
const focusedRef = useRef(focused2);
|
|
13151
|
+
focusedRef.current = focused2;
|
|
13152
|
+
const engagedRef = useRef(ctx.hotkeyStack);
|
|
13153
|
+
engagedRef.current = ctx.hotkeyStack;
|
|
13154
|
+
const setFocused = useCallback((id) => {
|
|
13155
|
+
ctx.setActive(id);
|
|
13156
|
+
}, [ctx]);
|
|
13157
|
+
const snapshot = useCallback(() => {
|
|
13158
|
+
const engaged = engagedRef.current;
|
|
13159
|
+
const engagedIds = new Set(engaged);
|
|
13160
|
+
const ordered = [];
|
|
13161
|
+
for (const id of engaged) {
|
|
13162
|
+
const entry = entriesRef.current.find((e) => e.id === id);
|
|
13163
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
13164
|
+
}
|
|
13165
|
+
for (const entry of entriesRef.current) {
|
|
13166
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
13167
|
+
}
|
|
13168
|
+
return {
|
|
13169
|
+
ordered,
|
|
13170
|
+
state: { focusedId: focusedRef.current, heldTriggers: NO_TRIGGERS, engagedIds }
|
|
13171
|
+
};
|
|
12809
13172
|
}, []);
|
|
12810
|
-
const
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
13173
|
+
const scopedBindings = useCallback(() => {
|
|
13174
|
+
const { ordered, state } = snapshot();
|
|
13175
|
+
return scopeBindings(ordered, state);
|
|
13176
|
+
}, [snapshot]);
|
|
13177
|
+
const overlays = useCallback(() => {
|
|
13178
|
+
const { ordered, state } = snapshot();
|
|
13179
|
+
const active = [];
|
|
13180
|
+
const hotkey = [];
|
|
13181
|
+
const ambient = [];
|
|
13182
|
+
for (const entry of ordered) {
|
|
13183
|
+
if (!entry.overlay) continue;
|
|
13184
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
13185
|
+
if (scope === "active") active.push(entry.overlay);
|
|
13186
|
+
else if (scope === "hotkey") hotkey.push(entry.overlay);
|
|
13187
|
+
else if (scope === "ambient") ambient.push(entry.overlay);
|
|
13188
|
+
}
|
|
13189
|
+
return [...active, ...hotkey, ...ambient];
|
|
13190
|
+
}, [snapshot]);
|
|
13191
|
+
const offhandSig = opts.entries.map((e) => e.eligibility?.offhand ? `${e.id}:${e.eligibility.offhand}` : "").filter(Boolean).join(",");
|
|
13192
|
+
useEffect(() => {
|
|
13193
|
+
if (!actionsRegistry || offhandSig === "") return;
|
|
13194
|
+
const specs = offhandSig.split(",").map((entry) => {
|
|
13195
|
+
const [toolId, trigger] = entry.split(":");
|
|
13196
|
+
return { toolId, key: offhandKeyFor(trigger) };
|
|
13197
|
+
});
|
|
13198
|
+
return actionsRegistry.register(makeToolOffhandAction(buildToolOffhandBindings(specs)));
|
|
13199
|
+
}, [actionsRegistry, offhandSig]);
|
|
13200
|
+
const lastConflictSigRef = useRef(null);
|
|
13201
|
+
const entrySig = opts.entries.map((e) => e.id).join(",");
|
|
13202
|
+
useEffect(() => {
|
|
13203
|
+
if (process.env.NODE_ENV === "production") return;
|
|
13204
|
+
if (lastConflictSigRef.current === entrySig) return;
|
|
13205
|
+
lastConflictSigRef.current = entrySig;
|
|
13206
|
+
const registry2 = [];
|
|
13207
|
+
const ambient = [];
|
|
13208
|
+
for (const entry of entriesRef.current) {
|
|
13209
|
+
(entry.eligibility?.focus ? registry2 : ambient).push(entry);
|
|
13210
|
+
}
|
|
13211
|
+
reportRouteConflicts({
|
|
13212
|
+
registry: registry2,
|
|
13213
|
+
ambient,
|
|
13214
|
+
actions: actionsRegistry?.list() ?? []
|
|
13215
|
+
});
|
|
13216
|
+
}, [entrySig, actionsRegistry]);
|
|
13217
|
+
return useMemo(
|
|
13218
|
+
() => ({
|
|
13219
|
+
entries: entriesRef.current,
|
|
13220
|
+
focused: focused2,
|
|
13221
|
+
setFocused,
|
|
13222
|
+
scopedBindings,
|
|
13223
|
+
overlays
|
|
13224
|
+
}),
|
|
13225
|
+
[focused2, setFocused, scopedBindings, overlays]
|
|
13226
|
+
);
|
|
13227
|
+
}
|
|
13228
|
+
|
|
13229
|
+
// src/tools/useTools.ts
|
|
13230
|
+
function declareSlot(tool, slot) {
|
|
13231
|
+
const hotkey = tool.def?.hotkey;
|
|
13232
|
+
const eligibility = {
|
|
13233
|
+
...tool.eligibility,
|
|
13234
|
+
[slot]: true,
|
|
13235
|
+
...hotkey ? { offhand: hotkey } : {}
|
|
13236
|
+
};
|
|
13237
|
+
return sameEligibility(tool.eligibility, eligibility) ? tool : { ...tool, eligibility };
|
|
13238
|
+
}
|
|
13239
|
+
function sameEligibility(a, b) {
|
|
13240
|
+
if (!a) return false;
|
|
13241
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
13242
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
13243
|
+
return true;
|
|
13244
|
+
}
|
|
13245
|
+
function useTools(opts) {
|
|
13246
|
+
if (!(opts.active in opts.registry)) {
|
|
13247
|
+
throw new Error(`useTools: active "${opts.active}" not in registry`);
|
|
13248
|
+
}
|
|
13249
|
+
const ctx = useActiveToolContext();
|
|
13250
|
+
const slotted = useMemo(() => {
|
|
13251
|
+
const registry2 = {};
|
|
13252
|
+
for (const [id, tool] of Object.entries(opts.registry)) {
|
|
13253
|
+
registry2[id] = declareSlot(tool, "focus");
|
|
13254
|
+
}
|
|
13255
|
+
const ambient = (opts.ambient ?? []).map((t) => declareSlot(t, "always"));
|
|
13256
|
+
const byId = /* @__PURE__ */ new Map();
|
|
13257
|
+
for (const tool of [...Object.values(registry2), ...ambient]) {
|
|
13258
|
+
const prior = byId.get(tool.id);
|
|
13259
|
+
if (prior) byId.set(tool.id, { ...tool, eligibility: { ...prior.eligibility, ...tool.eligibility } });
|
|
13260
|
+
else byId.set(tool.id, tool);
|
|
13261
|
+
}
|
|
13262
|
+
return { registry: registry2, ambient, entries: [...byId.values()] };
|
|
13263
|
+
}, [opts.registry, opts.ambient]);
|
|
13264
|
+
const contributions = useContributions({ entries: slotted.entries, focused: opts.active });
|
|
13265
|
+
const hotkeyEngaged = ctx.hotkeyStack.at(-1) ?? null;
|
|
13266
|
+
const slottedRef = useRef(slotted);
|
|
13267
|
+
slottedRef.current = slotted;
|
|
13268
|
+
const activeRef = useRef(contributions.focused);
|
|
13269
|
+
activeRef.current = contributions.focused;
|
|
12819
13270
|
const hotkeyRef = useRef(hotkeyEngaged);
|
|
12820
13271
|
hotkeyRef.current = hotkeyEngaged;
|
|
13272
|
+
const setFocused = contributions.setFocused;
|
|
12821
13273
|
const setActive = useCallback(
|
|
12822
13274
|
(id) => {
|
|
12823
|
-
if (!(id in
|
|
13275
|
+
if (!(id in slottedRef.current.registry)) {
|
|
12824
13276
|
throw new Error(`setActive: "${id}" not in registry`);
|
|
12825
13277
|
}
|
|
12826
13278
|
dlog("tools", "active:", activeRef.current, "\u2192", id);
|
|
12827
|
-
|
|
13279
|
+
setFocused(id);
|
|
12828
13280
|
},
|
|
12829
|
-
[
|
|
13281
|
+
[setFocused]
|
|
12830
13282
|
);
|
|
12831
13283
|
const engageHotkey = useCallback(
|
|
12832
13284
|
(id) => {
|
|
12833
|
-
if (!(id in
|
|
13285
|
+
if (!(id in slottedRef.current.registry)) {
|
|
12834
13286
|
throw new Error(`engageHotkey: "${id}" not in registry`);
|
|
12835
13287
|
}
|
|
12836
13288
|
dlog("tools", "hotkey engaged:", id);
|
|
@@ -12842,32 +13294,12 @@ function useTools(opts) {
|
|
|
12842
13294
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
12843
13295
|
ctx.popHotkey();
|
|
12844
13296
|
}, [ctx]);
|
|
12845
|
-
const lastConflictSigRef = useRef(null);
|
|
12846
|
-
const toolSetSig = Object.keys(opts.registry).join(",") + "|" + (opts.ambient ?? []).map((t) => t.id).join(",");
|
|
12847
|
-
useEffect(() => {
|
|
12848
|
-
if (process.env.NODE_ENV === "production") return;
|
|
12849
|
-
if (lastConflictSigRef.current === toolSetSig) return;
|
|
12850
|
-
lastConflictSigRef.current = toolSetSig;
|
|
12851
|
-
reportRouteConflicts({
|
|
12852
|
-
registry: registryRef.current,
|
|
12853
|
-
ambient: ambientRef.current
|
|
12854
|
-
});
|
|
12855
|
-
}, [toolSetSig]);
|
|
12856
13297
|
const has = useCallback(
|
|
12857
|
-
(id) => id in
|
|
13298
|
+
(id) => id in slottedRef.current.registry || slottedRef.current.ambient.some((t) => t.id === id),
|
|
12858
13299
|
[]
|
|
12859
13300
|
);
|
|
12860
|
-
const
|
|
12861
|
-
|
|
12862
|
-
const activeTool = registryRef.current[activeRef.current];
|
|
12863
|
-
if (activeTool?.overlay) out.push(activeTool.overlay);
|
|
12864
|
-
const mod = hotkeyRef.current ? registryRef.current[hotkeyRef.current] : null;
|
|
12865
|
-
if (mod?.overlay) out.push(mod.overlay);
|
|
12866
|
-
for (const t of ambientRef.current) {
|
|
12867
|
-
if (t.overlay) out.push(t.overlay);
|
|
12868
|
-
}
|
|
12869
|
-
return out;
|
|
12870
|
-
}, []);
|
|
13301
|
+
const active = contributions.focused;
|
|
13302
|
+
const getActiveOverlays = contributions.overlays;
|
|
12871
13303
|
return useMemo(
|
|
12872
13304
|
() => ({
|
|
12873
13305
|
active,
|
|
@@ -12875,8 +13307,8 @@ function useTools(opts) {
|
|
|
12875
13307
|
hotkeyEngaged,
|
|
12876
13308
|
engageHotkey,
|
|
12877
13309
|
disengageHotkey,
|
|
12878
|
-
ambient:
|
|
12879
|
-
registry:
|
|
13310
|
+
ambient: slottedRef.current.ambient,
|
|
13311
|
+
registry: slottedRef.current.registry,
|
|
12880
13312
|
has,
|
|
12881
13313
|
getActiveOverlays
|
|
12882
13314
|
}),
|
|
@@ -12884,38 +13316,6 @@ function useTools(opts) {
|
|
|
12884
13316
|
);
|
|
12885
13317
|
}
|
|
12886
13318
|
|
|
12887
|
-
// src/interactions/actions/defaults/toolOffhand.ts
|
|
12888
|
-
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
12889
|
-
function buildToolOffhandBindings(specs) {
|
|
12890
|
-
return specs.map(({ toolId, key }) => ({
|
|
12891
|
-
spec: { kind: "key-held", key },
|
|
12892
|
-
opts: { params: { toolId } }
|
|
12893
|
-
}));
|
|
12894
|
-
}
|
|
12895
|
-
function makeToolOffhandAction(bindings) {
|
|
12896
|
-
return {
|
|
12897
|
-
id: TOOL_OFFHAND_ID,
|
|
12898
|
-
label: "Engage tool (offhand)",
|
|
12899
|
-
defaultBinding: bindings,
|
|
12900
|
-
scope: "hotkey",
|
|
12901
|
-
invoker: {
|
|
12902
|
-
timing: "ongoing",
|
|
12903
|
-
start: (ctx, opts) => {
|
|
12904
|
-
const activeTool = ctx.deps.activeTool;
|
|
12905
|
-
const toolId = resolveParams(opts?.params)?.toolId;
|
|
12906
|
-
if (!activeTool || !toolId) return {};
|
|
12907
|
-
activeTool.pushHotkey(toolId);
|
|
12908
|
-
return {
|
|
12909
|
-
onEnd: () => {
|
|
12910
|
-
activeTool.popHotkey();
|
|
12911
|
-
}
|
|
12912
|
-
};
|
|
12913
|
-
}
|
|
12914
|
-
},
|
|
12915
|
-
requires: ["activeTool"]
|
|
12916
|
-
};
|
|
12917
|
-
}
|
|
12918
|
-
|
|
12919
13319
|
// src/interactions/actions/defaults/toolActivate.ts
|
|
12920
13320
|
var TOOL_ACTIVATE_ID = "tool.activate";
|
|
12921
13321
|
function buildToolActivateBindings(specs) {
|
|
@@ -12992,9 +13392,6 @@ var BUILTIN_SELECT_KEYS = {
|
|
|
12992
13392
|
hand: { key: "H" },
|
|
12993
13393
|
pen: { key: "P" }
|
|
12994
13394
|
};
|
|
12995
|
-
var BUILTIN_OFFHAND_ACTIONS = {
|
|
12996
|
-
hand: " "
|
|
12997
|
-
};
|
|
12998
13395
|
function useKeybindings(tools, options = {}) {
|
|
12999
13396
|
const toolsRef = useRef(tools);
|
|
13000
13397
|
toolsRef.current = tools;
|
|
@@ -13002,19 +13399,6 @@ function useKeybindings(tools, options = {}) {
|
|
|
13002
13399
|
optionsRef.current = options;
|
|
13003
13400
|
const initialActiveRef = useRef(tools.active);
|
|
13004
13401
|
const registry2 = useActionsRegistry();
|
|
13005
|
-
useEffect(() => {
|
|
13006
|
-
if (optionsRef.current.disable) return;
|
|
13007
|
-
if (!registry2) return;
|
|
13008
|
-
const specs = [];
|
|
13009
|
-
for (const [toolId, key] of Object.entries(BUILTIN_OFFHAND_ACTIONS)) {
|
|
13010
|
-
if (toolsRef.current.has(toolId)) {
|
|
13011
|
-
specs.push({ toolId, key });
|
|
13012
|
-
}
|
|
13013
|
-
}
|
|
13014
|
-
if (specs.length === 0) return;
|
|
13015
|
-
const bindings = buildToolOffhandBindings(specs);
|
|
13016
|
-
return registry2.register(makeToolOffhandAction(bindings));
|
|
13017
|
-
}, [registry2, tools]);
|
|
13018
13402
|
useEffect(() => {
|
|
13019
13403
|
if (optionsRef.current.disable) return;
|
|
13020
13404
|
if (!registry2) return;
|
|
@@ -13553,21 +13937,18 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
13553
13937
|
const style = resolveTextStyle(pose.style);
|
|
13554
13938
|
const source = pose.runs && pose.runs.length > 0 ? pose.runs : pose.text;
|
|
13555
13939
|
const runs = resolveRuns(toRuns(source), style);
|
|
13556
|
-
const laid = layoutRuns(
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
{ x: pose.x, y: pose.y }
|
|
13564
|
-
);
|
|
13565
|
-
const dy = verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);
|
|
13940
|
+
const laid = layoutRuns(runs, {
|
|
13941
|
+
maxWidth: opts.maxWidth ?? pose.width,
|
|
13942
|
+
lineHeight: style.lineHeight,
|
|
13943
|
+
align: style.align
|
|
13944
|
+
});
|
|
13945
|
+
const dx = pose.x;
|
|
13946
|
+
const dy = pose.y + verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);
|
|
13566
13947
|
const out = [];
|
|
13567
13948
|
for (const line of laid.lines) {
|
|
13568
13949
|
if (!opts.includeEmpty && line.x1 <= line.x0) continue;
|
|
13569
13950
|
out.push({
|
|
13570
|
-
x: line.x0 - padding,
|
|
13951
|
+
x: line.x0 + dx - padding,
|
|
13571
13952
|
y: line.y0 + dy - padding,
|
|
13572
13953
|
width: line.x1 - line.x0 + padding * 2,
|
|
13573
13954
|
height: line.y1 - line.y0 + padding * 2
|
|
@@ -13576,8 +13957,108 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
13576
13957
|
return out;
|
|
13577
13958
|
}
|
|
13578
13959
|
|
|
13579
|
-
// src/
|
|
13960
|
+
// src/core/fillInPoseFrame.ts
|
|
13961
|
+
function fillInPoseFrame(fill, box) {
|
|
13962
|
+
if (isBoundsPattern(fill)) {
|
|
13963
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
13964
|
+
return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: "local" };
|
|
13965
|
+
}
|
|
13966
|
+
if (!isBoundsGradient(fill)) return fill;
|
|
13967
|
+
const toBox = (p) => ({
|
|
13968
|
+
x: box.x + p.x * box.width,
|
|
13969
|
+
y: box.y + p.y * box.height
|
|
13970
|
+
});
|
|
13971
|
+
if (fill.fill === "linear-gradient") {
|
|
13972
|
+
return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: "local" };
|
|
13973
|
+
}
|
|
13974
|
+
if (fill.fill === "radial-gradient") {
|
|
13975
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
13976
|
+
return { ...fill, center: toBox(fill.center), radius: fill.radius * scale2, units: "local" };
|
|
13977
|
+
}
|
|
13978
|
+
return { ...fill, center: toBox(fill.center), units: "local" };
|
|
13979
|
+
}
|
|
13980
|
+
function fillToBoundsFrame(fill, box) {
|
|
13981
|
+
if (fill.fill === "pattern") {
|
|
13982
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
13983
|
+
return { ...fill, origin: { x: o.x - box.x, y: o.y - box.y }, units: "bounds" };
|
|
13984
|
+
}
|
|
13985
|
+
const isGradient = fill.fill === "linear-gradient" || fill.fill === "radial-gradient" || fill.fill === "conic-gradient";
|
|
13986
|
+
if (!isGradient) return fill;
|
|
13987
|
+
if (box.width === 0 || box.height === 0) return fill;
|
|
13988
|
+
const fromBox = (p) => ({
|
|
13989
|
+
x: (p.x - box.x) / box.width,
|
|
13990
|
+
y: (p.y - box.y) / box.height
|
|
13991
|
+
});
|
|
13992
|
+
if (fill.fill === "linear-gradient") {
|
|
13993
|
+
return { ...fill, from: fromBox(fill.from), to: fromBox(fill.to), units: "bounds" };
|
|
13994
|
+
}
|
|
13995
|
+
if (fill.fill === "radial-gradient") {
|
|
13996
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
13997
|
+
return { ...fill, center: fromBox(fill.center), radius: fill.radius / scale2, units: "bounds" };
|
|
13998
|
+
}
|
|
13999
|
+
return { ...fill, center: fromBox(fill.center), units: "bounds" };
|
|
14000
|
+
}
|
|
14001
|
+
function isBoundsPattern(fill) {
|
|
14002
|
+
return fill.fill === "pattern" && fill.units === "bounds";
|
|
14003
|
+
}
|
|
14004
|
+
function isBoundsGradient(fill) {
|
|
14005
|
+
if (fill === void 0) return false;
|
|
14006
|
+
const kind = fill.fill;
|
|
14007
|
+
if (kind !== "linear-gradient" && kind !== "radial-gradient" && kind !== "conic-gradient") {
|
|
14008
|
+
return false;
|
|
14009
|
+
}
|
|
14010
|
+
return fill.units === "bounds";
|
|
14011
|
+
}
|
|
14012
|
+
|
|
14013
|
+
// src/features/patterns/resolveSpec.ts
|
|
13580
14014
|
var cache5 = /* @__PURE__ */ new Map();
|
|
14015
|
+
function specKey(spec) {
|
|
14016
|
+
const keys = Object.keys(spec).sort();
|
|
14017
|
+
const bag = spec;
|
|
14018
|
+
return keys.map((k) => `${k}=${String(bag[k])}`).join("&");
|
|
14019
|
+
}
|
|
14020
|
+
function resolvePatternSpec(spec) {
|
|
14021
|
+
const key = specKey(spec);
|
|
14022
|
+
const hit = cache5.get(key);
|
|
14023
|
+
if (hit !== void 0) return hit;
|
|
14024
|
+
const built = buildTile(spec);
|
|
14025
|
+
cache5.set(key, built);
|
|
14026
|
+
return built;
|
|
14027
|
+
}
|
|
14028
|
+
function buildTile(spec) {
|
|
14029
|
+
const { color, size } = spec;
|
|
14030
|
+
switch (spec.tile) {
|
|
14031
|
+
case "hatch":
|
|
14032
|
+
return hatch({ color, size, lineWidth: spec.lineWidth });
|
|
14033
|
+
case "crosshatch":
|
|
14034
|
+
return crosshatch({ color, size, lineWidth: spec.lineWidth });
|
|
14035
|
+
case "dots":
|
|
14036
|
+
return dots({ color, size, radius: spec.radius });
|
|
14037
|
+
case "chunks":
|
|
14038
|
+
return chunks({
|
|
14039
|
+
color,
|
|
14040
|
+
size,
|
|
14041
|
+
bg: spec.bg,
|
|
14042
|
+
density: spec.density,
|
|
14043
|
+
chunkSize: spec.chunkSize,
|
|
14044
|
+
seed: spec.seed
|
|
14045
|
+
});
|
|
14046
|
+
default:
|
|
14047
|
+
return null;
|
|
14048
|
+
}
|
|
14049
|
+
}
|
|
14050
|
+
function isPatternSpec(payload) {
|
|
14051
|
+
return "tile" in payload;
|
|
14052
|
+
}
|
|
14053
|
+
function resolveFillPattern(fill) {
|
|
14054
|
+
if (fill.fill !== "pattern") return fill;
|
|
14055
|
+
if (!isPatternSpec(fill.pattern)) return fill;
|
|
14056
|
+
const handle = resolvePatternSpec(fill.pattern);
|
|
14057
|
+
return handle ? { ...fill, pattern: handle } : null;
|
|
14058
|
+
}
|
|
14059
|
+
|
|
14060
|
+
// src/features/images/imageCache.ts
|
|
14061
|
+
var cache6 = /* @__PURE__ */ new Map();
|
|
13581
14062
|
var subscribers = /* @__PURE__ */ new Set();
|
|
13582
14063
|
var defaultLoader = async (src) => {
|
|
13583
14064
|
if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
|
|
@@ -13595,28 +14076,28 @@ function notify() {
|
|
|
13595
14076
|
}
|
|
13596
14077
|
function startLoad(src) {
|
|
13597
14078
|
const entry = { status: "loading" };
|
|
13598
|
-
|
|
14079
|
+
cache6.set(src, entry);
|
|
13599
14080
|
loader(src).then(
|
|
13600
14081
|
(bitmap) => {
|
|
13601
|
-
if (
|
|
13602
|
-
|
|
14082
|
+
if (cache6.get(src) !== entry) return;
|
|
14083
|
+
cache6.set(src, { status: "ready", bitmap });
|
|
13603
14084
|
notify();
|
|
13604
14085
|
},
|
|
13605
14086
|
() => {
|
|
13606
|
-
if (
|
|
13607
|
-
|
|
14087
|
+
if (cache6.get(src) !== entry) return;
|
|
14088
|
+
cache6.set(src, { status: "error" });
|
|
13608
14089
|
notify();
|
|
13609
14090
|
}
|
|
13610
14091
|
);
|
|
13611
14092
|
}
|
|
13612
14093
|
function getImageBitmap(src) {
|
|
13613
|
-
const entry =
|
|
14094
|
+
const entry = cache6.get(src);
|
|
13614
14095
|
if (entry) return entry.bitmap;
|
|
13615
14096
|
startLoad(src);
|
|
13616
14097
|
return void 0;
|
|
13617
14098
|
}
|
|
13618
14099
|
function imageStatus(src) {
|
|
13619
|
-
return
|
|
14100
|
+
return cache6.get(src)?.status ?? "idle";
|
|
13620
14101
|
}
|
|
13621
14102
|
function subscribeImageReady(cb) {
|
|
13622
14103
|
subscribers.add(cb);
|
|
@@ -13801,6 +14282,17 @@ function rectsToPath(rects) {
|
|
|
13801
14282
|
}
|
|
13802
14283
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
13803
14284
|
}
|
|
14285
|
+
function isFillStyle(v) {
|
|
14286
|
+
return typeof v === "object" && v !== null;
|
|
14287
|
+
}
|
|
14288
|
+
function resolveNodeFill(fill, color, hasStroke, fallback) {
|
|
14289
|
+
if (isFillStyle(fill)) return fill;
|
|
14290
|
+
const c = fill ?? color;
|
|
14291
|
+
if (c === "none") return null;
|
|
14292
|
+
if (c !== void 0) return { color: c };
|
|
14293
|
+
if (hasStroke && fallback === null) return null;
|
|
14294
|
+
return fallback === null ? null : { color: fallback };
|
|
14295
|
+
}
|
|
13804
14296
|
var PATH_PAINTER = {
|
|
13805
14297
|
id: "kit:path",
|
|
13806
14298
|
matches: (node) => {
|
|
@@ -13814,12 +14306,12 @@ var PATH_PAINTER = {
|
|
|
13814
14306
|
const d = node.data;
|
|
13815
14307
|
const projected = pathInPoseFrame(d.path, pose);
|
|
13816
14308
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
13817
|
-
const
|
|
13818
|
-
const
|
|
14309
|
+
const declared = resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888");
|
|
14310
|
+
const fill = declared && resolveFillPattern(fillInPoseFrame(declared, pose));
|
|
13819
14311
|
const cmd = {
|
|
13820
14312
|
kind: "path",
|
|
13821
14313
|
path: projected,
|
|
13822
|
-
...
|
|
14314
|
+
...fill ? { fill } : {},
|
|
13823
14315
|
...hasStroke ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
13824
14316
|
};
|
|
13825
14317
|
return [cmd];
|
|
@@ -13835,9 +14327,8 @@ var PATH_PAINTER = {
|
|
|
13835
14327
|
ink: (node) => {
|
|
13836
14328
|
const d = node.data;
|
|
13837
14329
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
13838
|
-
const fillColor = d.fill ?? d.color;
|
|
13839
14330
|
return {
|
|
13840
|
-
filled:
|
|
14331
|
+
filled: resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888") !== null,
|
|
13841
14332
|
strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0
|
|
13842
14333
|
};
|
|
13843
14334
|
}
|
|
@@ -13854,10 +14345,12 @@ var SHAPE_PAINTER = {
|
|
|
13854
14345
|
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
13855
14346
|
const d = node.data;
|
|
13856
14347
|
const path = pathForShape(d, pose);
|
|
14348
|
+
const declaredFill = resolveNodeFill(d.fill, d.color, false, "#888");
|
|
14349
|
+
const shapeFill = declaredFill && resolveFillPattern(fillInPoseFrame(declaredFill, pose));
|
|
13857
14350
|
return [{
|
|
13858
14351
|
kind: "path",
|
|
13859
14352
|
path,
|
|
13860
|
-
|
|
14353
|
+
...shapeFill ? { fill: shapeFill } : {},
|
|
13861
14354
|
...d.stroke && (d.strokeWidth ?? 0) > 0 ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
13862
14355
|
}];
|
|
13863
14356
|
}),
|
|
@@ -14258,11 +14751,8 @@ function useSelectTool(adapter, options) {
|
|
|
14258
14751
|
...moveOpts
|
|
14259
14752
|
},
|
|
14260
14753
|
{ spec: { kind: "drag", target: "empty" }, actionId: "areaSelect" },
|
|
14261
|
-
//
|
|
14262
|
-
//
|
|
14263
|
-
// widget that happens to float over empty canvas isn't a click on
|
|
14264
|
-
// empty canvas, and declining in the SPEC lets the chrome's owner
|
|
14265
|
-
// have the click instead of it being consumed here.
|
|
14754
|
+
// resize/rotate bind only drag (no click), so a click on either handle
|
|
14755
|
+
// (body genuinely empty) would hit target:'empty' and wrongly clear selection.
|
|
14266
14756
|
{
|
|
14267
14757
|
spec: {
|
|
14268
14758
|
kind: "click",
|
|
@@ -14348,17 +14838,17 @@ function worldToLocal(xf, wx, wy) {
|
|
|
14348
14838
|
function annulusSemiAxes(shape, view) {
|
|
14349
14839
|
const bandPx = shape.minBandPx ?? 0;
|
|
14350
14840
|
if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };
|
|
14351
|
-
const band = bandPx
|
|
14841
|
+
const band = pxExtent(bandPx, view.scale);
|
|
14352
14842
|
return {
|
|
14353
|
-
rx: Math.max(shape.rx, shape.innerWidth / 2 + band),
|
|
14354
|
-
ry: Math.max(shape.ry, shape.innerHeight / 2 + band)
|
|
14843
|
+
rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),
|
|
14844
|
+
ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y)
|
|
14355
14845
|
};
|
|
14356
14846
|
}
|
|
14357
14847
|
function hitRegion(region, wx, wy, xf, view) {
|
|
14358
14848
|
const local = worldToLocal(xf, wx, wy);
|
|
14359
14849
|
if (region.shape.kind === "point") {
|
|
14360
|
-
const
|
|
14361
|
-
return
|
|
14850
|
+
const anchor = localToWorld(xf, region.shape.x, region.shape.y);
|
|
14851
|
+
return withinPxBox(anchor.x - wx, anchor.y - wy, region.shape.hitRadiusPx, view.scale);
|
|
14362
14852
|
}
|
|
14363
14853
|
if (region.shape.kind === "annulus") {
|
|
14364
14854
|
const s = region.shape;
|
|
@@ -14424,14 +14914,13 @@ function asIsVisible(data) {
|
|
|
14424
14914
|
function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
14425
14915
|
if (region.shape.kind === "point") {
|
|
14426
14916
|
const w2 = localToWorld(xf, region.shape.x, region.shape.y);
|
|
14427
|
-
const
|
|
14917
|
+
const e = pxExtent(region.shape.hitRadiusPx, view.scale);
|
|
14428
14918
|
debug.recordHitbox(affordanceId, "handle", {
|
|
14429
14919
|
kind: "rect",
|
|
14430
|
-
x: w2.x -
|
|
14431
|
-
y: w2.y -
|
|
14432
|
-
width:
|
|
14433
|
-
height:
|
|
14434
|
-
...xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }
|
|
14920
|
+
x: w2.x - e.x,
|
|
14921
|
+
y: w2.y - e.y,
|
|
14922
|
+
width: e.x * 2,
|
|
14923
|
+
height: e.y * 2
|
|
14435
14924
|
});
|
|
14436
14925
|
return;
|
|
14437
14926
|
}
|
|
@@ -14490,7 +14979,7 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
14490
14979
|
if (paint.kind === "annulus") {
|
|
14491
14980
|
if (region.shape.kind !== "annulus") return;
|
|
14492
14981
|
const s = region.shape;
|
|
14493
|
-
const insetLocal = (paint.insetPx ?? 0
|
|
14982
|
+
const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale);
|
|
14494
14983
|
const cmd = annulusCommand(s, annulusSemiAxes(s, view), insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
14495
14984
|
if (cmd) out.push(cmd);
|
|
14496
14985
|
return;
|
|
@@ -14507,13 +14996,13 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
14507
14996
|
}
|
|
14508
14997
|
}
|
|
14509
14998
|
function annulusCommand(shape, semiAxes, insetLocal, xf, viewT, fill, stroke) {
|
|
14510
|
-
const rx = semiAxes.rx - insetLocal;
|
|
14511
|
-
const ry = semiAxes.ry - insetLocal;
|
|
14999
|
+
const rx = semiAxes.rx - insetLocal.x;
|
|
15000
|
+
const ry = semiAxes.ry - insetLocal.y;
|
|
14512
15001
|
if (rx <= 0 || ry <= 0) return null;
|
|
14513
15002
|
const cx = shape.cx;
|
|
14514
15003
|
const cy = shape.cy;
|
|
14515
|
-
const rectHW = shape.innerWidth / 2 + insetLocal;
|
|
14516
|
-
const rectHH = shape.innerHeight / 2 + insetLocal;
|
|
15004
|
+
const rectHW = shape.innerWidth / 2 + insetLocal.x;
|
|
15005
|
+
const rectHH = shape.innerHeight / 2 + insetLocal.y;
|
|
14517
15006
|
const haveTopBottom = rectHH < ry;
|
|
14518
15007
|
const haveLeftRight = rectHW < rx;
|
|
14519
15008
|
if (!haveTopBottom && !haveLeftRight) return null;
|
|
@@ -14774,6 +15263,7 @@ function useHandTool(opts = {}) {
|
|
|
14774
15263
|
() => Object.assign(defineViewportTool({
|
|
14775
15264
|
id: "hand",
|
|
14776
15265
|
capabilities: ["navigation"],
|
|
15266
|
+
hotkey: "space",
|
|
14777
15267
|
hookName: "useHandTool",
|
|
14778
15268
|
presentation: {
|
|
14779
15269
|
label: "Hand",
|
|
@@ -14898,10 +15388,6 @@ function mirrorHandle(anchor, out) {
|
|
|
14898
15388
|
if (!out || anchor.altBroken) return void 0;
|
|
14899
15389
|
return { x: 2 * anchor.x - out.x, y: 2 * anchor.y - out.y };
|
|
14900
15390
|
}
|
|
14901
|
-
function dist2(ax, ay, bx, by) {
|
|
14902
|
-
const dx = ax - bx, dy = ay - by;
|
|
14903
|
-
return Math.hypot(dx, dy);
|
|
14904
|
-
}
|
|
14905
15391
|
function usePenTool(options) {
|
|
14906
15392
|
const { wrapPath, adapter, autoSelect = true, autoCommitOnClose = true, closeHitRadius = 8, snapPoint: snapPoint2 } = options;
|
|
14907
15393
|
const scratchRef = useRef(null);
|
|
@@ -14925,11 +15411,14 @@ function usePenTool(options) {
|
|
|
14925
15411
|
const fn = optsRef.current.snapPoint;
|
|
14926
15412
|
return fn ? fn({ x, y }) : { x, y };
|
|
14927
15413
|
}, []);
|
|
14928
|
-
const
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
15414
|
+
const withinCloseRadius = useCallback(
|
|
15415
|
+
(deps, ax, ay, wx, wy) => {
|
|
15416
|
+
const view = deps.view?.get();
|
|
15417
|
+
const scale2 = view?.scale ?? { x: 1, y: 1 };
|
|
15418
|
+
return withinPxRadius(ax - wx, ay - wy, optsRef.current.closeHitRadius, scale2);
|
|
15419
|
+
},
|
|
15420
|
+
[]
|
|
15421
|
+
);
|
|
14933
15422
|
const actions = useMemo(() => {
|
|
14934
15423
|
const s = () => scratchRef.current;
|
|
14935
15424
|
return [
|
|
@@ -14945,10 +15434,9 @@ function usePenTool(options) {
|
|
|
14945
15434
|
if (p?.pressX === void 0 || p.pressY === void 0) return;
|
|
14946
15435
|
const { x: wx, y: wy } = snap2(p.pressX, p.pressY);
|
|
14947
15436
|
const scratch = s();
|
|
14948
|
-
const radius = closeRadius(deps);
|
|
14949
15437
|
if (scratch.current && scratch.current.anchors.length >= 3) {
|
|
14950
15438
|
const first = scratch.current.anchors[0];
|
|
14951
|
-
if (
|
|
15439
|
+
if (withinCloseRadius(deps, first.x, first.y, wx, wy)) {
|
|
14952
15440
|
scratch.current.closed = true;
|
|
14953
15441
|
scratch.finishedSubpaths.push(scratch.current);
|
|
14954
15442
|
scratch.current = null;
|
|
@@ -15067,7 +15555,7 @@ function usePenTool(options) {
|
|
|
15067
15555
|
}
|
|
15068
15556
|
}
|
|
15069
15557
|
];
|
|
15070
|
-
}, [commit, snap2,
|
|
15558
|
+
}, [commit, snap2, withinCloseRadius]);
|
|
15071
15559
|
return useMemo(() => {
|
|
15072
15560
|
return defineTool({
|
|
15073
15561
|
id: "pen",
|
|
@@ -16443,6 +16931,15 @@ function wrapWithPoseRotation(cmds, pose) {
|
|
|
16443
16931
|
children: cmds
|
|
16444
16932
|
}];
|
|
16445
16933
|
}
|
|
16934
|
+
|
|
16935
|
+
// src/canvas/wrapNodeOutput.ts
|
|
16936
|
+
function wrapNodeOutput(cmds, pose, alpha) {
|
|
16937
|
+
const rotated = wrapWithPoseRotation(cmds, pose);
|
|
16938
|
+
if (alpha !== 1 && rotated.length > 0) {
|
|
16939
|
+
return [{ kind: "group", alpha, children: rotated }];
|
|
16940
|
+
}
|
|
16941
|
+
return rotated;
|
|
16942
|
+
}
|
|
16446
16943
|
function CursorCoordsHud({ canvasRef, viewRef, offset }) {
|
|
16447
16944
|
const [state, setState] = useState({
|
|
16448
16945
|
client: { x: 0, y: 0 },
|
|
@@ -16694,12 +17191,7 @@ function buildSceneLayer(cfg, adapter, debugSink, boundsOfFn, hideIds, slot) {
|
|
|
16694
17191
|
const oy = pose.y ?? (b ? b.y : 0);
|
|
16695
17192
|
debugSink.recordOrigin(obj.id, { x: ox, y: oy });
|
|
16696
17193
|
}
|
|
16697
|
-
|
|
16698
|
-
const alpha = cfg.alphaFor ? cfg.alphaFor(obj.id) : 1;
|
|
16699
|
-
if (alpha !== 1 && rotated.length > 0) {
|
|
16700
|
-
return [{ kind: "group", alpha, children: rotated }];
|
|
16701
|
-
}
|
|
16702
|
-
return rotated;
|
|
17194
|
+
return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);
|
|
16703
17195
|
};
|
|
16704
17196
|
const hierarchicalAdapter = cfg.toPose ? {
|
|
16705
17197
|
...a,
|
|
@@ -16723,13 +17215,8 @@ function buildSceneLayer(cfg, adapter, debugSink, boundsOfFn, hideIds, slot) {
|
|
|
16723
17215
|
const pose = toPose(obj);
|
|
16724
17216
|
if (drawOne) {
|
|
16725
17217
|
const cmds = drawOne(obj, pose, view);
|
|
16726
|
-
const
|
|
16727
|
-
const
|
|
16728
|
-
if (alpha !== 1 && rotated.length > 0) {
|
|
16729
|
-
children.push({ kind: "group", alpha, children: rotated });
|
|
16730
|
-
} else {
|
|
16731
|
-
for (const cmd of rotated) children.push(cmd);
|
|
16732
|
-
}
|
|
17218
|
+
const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);
|
|
17219
|
+
for (const cmd of wrapped) children.push(cmd);
|
|
16733
17220
|
}
|
|
16734
17221
|
if (debugSink) {
|
|
16735
17222
|
const b = boundsOfFn ? boundsOfFn(obj.id) : null;
|
|
@@ -16844,8 +17331,8 @@ function CanvasInner(props, ref) {
|
|
|
16844
17331
|
const isVisible = getIsVisibleRef.current?.() ?? alwaysVisible;
|
|
16845
17332
|
for (const layer of layers2) {
|
|
16846
17333
|
if (!layer.hitTest) continue;
|
|
16847
|
-
const
|
|
16848
|
-
if (
|
|
17334
|
+
const hit = layer.hitTest(worldX, worldY, void 0, view, dims, isVisible);
|
|
17335
|
+
if (hit) return { layerId: layer.id, hit };
|
|
16849
17336
|
}
|
|
16850
17337
|
return null;
|
|
16851
17338
|
}, []);
|
|
@@ -17281,7 +17768,7 @@ function CanvasInner(props, ref) {
|
|
|
17281
17768
|
effectiveView,
|
|
17282
17769
|
{ width, height }
|
|
17283
17770
|
);
|
|
17284
|
-
renderer.render(commands);
|
|
17771
|
+
renderer.render(commands, viewToMat3(effectiveView));
|
|
17285
17772
|
}, [layersWithDebug, width, height, effectiveView, debugSink, redrawNonce, dprProp]);
|
|
17286
17773
|
const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
|
|
17287
17774
|
useEffect(() => {
|
|
@@ -17443,6 +17930,7 @@ function createScene(options) {
|
|
|
17443
17930
|
return i;
|
|
17444
17931
|
}
|
|
17445
17932
|
function rebuildLayerIndex() {
|
|
17933
|
+
invalidateOrder();
|
|
17446
17934
|
state.layerIndex.clear();
|
|
17447
17935
|
for (let i = 0; i < state.layers.length; i++) {
|
|
17448
17936
|
state.layerIndex.set(state.layers[i].id, i);
|
|
@@ -17468,6 +17956,7 @@ function createScene(options) {
|
|
|
17468
17956
|
const idx = sibs.indexOf(id);
|
|
17469
17957
|
if (idx < 0) throw new Error(`Scene: node "${id}" not found in its parent's children`);
|
|
17470
17958
|
sibs.splice(idx, 1);
|
|
17959
|
+
invalidateOrder();
|
|
17471
17960
|
return { parent: node.parent, index: idx };
|
|
17472
17961
|
}
|
|
17473
17962
|
function attach(id, parent, index) {
|
|
@@ -17476,6 +17965,7 @@ function createScene(options) {
|
|
|
17476
17965
|
sibs.splice(i, 0, id);
|
|
17477
17966
|
const node = requireNode(id);
|
|
17478
17967
|
node.parent = parent;
|
|
17968
|
+
invalidateOrder();
|
|
17479
17969
|
}
|
|
17480
17970
|
function descendants(id, out) {
|
|
17481
17971
|
const n = state.nodes.get(id);
|
|
@@ -17555,9 +18045,11 @@ function createScene(options) {
|
|
|
17555
18045
|
apply: (p) => {
|
|
17556
18046
|
requireLayerIndex(p.to);
|
|
17557
18047
|
requireNode(p.id).layer = p.to;
|
|
18048
|
+
invalidateOrder();
|
|
17558
18049
|
},
|
|
17559
18050
|
revert: (p) => {
|
|
17560
18051
|
requireNode(p.id).layer = p.from;
|
|
18052
|
+
invalidateOrder();
|
|
17561
18053
|
}
|
|
17562
18054
|
});
|
|
17563
18055
|
registerKitOp("kit:move", {
|
|
@@ -17696,21 +18188,98 @@ function createScene(options) {
|
|
|
17696
18188
|
registered.set(k, h);
|
|
17697
18189
|
}
|
|
17698
18190
|
}
|
|
17699
|
-
function
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
18191
|
+
function renderOrderInternal() {
|
|
18192
|
+
return state.layers.length === 1 ? renderOrderFlat() : renderOrderBucketed();
|
|
18193
|
+
}
|
|
18194
|
+
function renderOrderNodesInternal() {
|
|
18195
|
+
return state.layers.length === 1 ? renderOrderNodesFlat() : renderOrderNodesBucketed();
|
|
18196
|
+
}
|
|
18197
|
+
let structureGeneration = 0;
|
|
18198
|
+
let orderCache = { gen: 0 };
|
|
18199
|
+
function invalidateOrder() {
|
|
18200
|
+
structureGeneration++;
|
|
18201
|
+
}
|
|
18202
|
+
function currentOrderCache() {
|
|
18203
|
+
if (orderCache.gen !== structureGeneration) {
|
|
18204
|
+
orderCache = { gen: structureGeneration };
|
|
18205
|
+
}
|
|
18206
|
+
return orderCache;
|
|
18207
|
+
}
|
|
18208
|
+
function renderOrderFlat() {
|
|
18209
|
+
const only = state.layers[0].id;
|
|
18210
|
+
const out = [];
|
|
18211
|
+
const stack = [...state.roots].reverse();
|
|
18212
|
+
while (stack.length > 0) {
|
|
18213
|
+
const id = stack.pop();
|
|
18214
|
+
const node = state.nodes.get(id);
|
|
18215
|
+
if (!node) continue;
|
|
18216
|
+
if (node.layer === only) out.push(id);
|
|
18217
|
+
if (node.kind === "container") {
|
|
18218
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
18219
|
+
stack.push(node.children[i]);
|
|
18220
|
+
}
|
|
18221
|
+
}
|
|
18222
|
+
}
|
|
18223
|
+
return out;
|
|
18224
|
+
}
|
|
18225
|
+
function renderOrderNodesFlat() {
|
|
18226
|
+
const only = state.layers[0].id;
|
|
18227
|
+
const out = [];
|
|
18228
|
+
const stack = [...state.roots].reverse();
|
|
18229
|
+
while (stack.length > 0) {
|
|
18230
|
+
const id = stack.pop();
|
|
18231
|
+
const node = state.nodes.get(id);
|
|
18232
|
+
if (!node) continue;
|
|
18233
|
+
if (node.layer === only) out.push(node);
|
|
18234
|
+
if (node.kind === "container") {
|
|
18235
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
18236
|
+
stack.push(node.children[i]);
|
|
18237
|
+
}
|
|
18238
|
+
}
|
|
18239
|
+
}
|
|
18240
|
+
return out;
|
|
18241
|
+
}
|
|
18242
|
+
function renderOrderBucketed() {
|
|
18243
|
+
const buckets = state.layers.map(() => []);
|
|
18244
|
+
const stack = [...state.roots].reverse();
|
|
18245
|
+
while (stack.length > 0) {
|
|
18246
|
+
const id = stack.pop();
|
|
18247
|
+
const node = state.nodes.get(id);
|
|
18248
|
+
if (!node) continue;
|
|
18249
|
+
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
18250
|
+
if (bucket) bucket.push(id);
|
|
18251
|
+
if (node.kind === "container") {
|
|
18252
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
18253
|
+
stack.push(node.children[i]);
|
|
18254
|
+
}
|
|
18255
|
+
}
|
|
18256
|
+
}
|
|
18257
|
+
const out = [];
|
|
18258
|
+
for (const bucket of buckets) {
|
|
18259
|
+
for (const id of bucket) out.push(id);
|
|
18260
|
+
}
|
|
18261
|
+
return out;
|
|
18262
|
+
}
|
|
18263
|
+
function renderOrderNodesBucketed() {
|
|
18264
|
+
const buckets = state.layers.map(() => []);
|
|
18265
|
+
const stack = [...state.roots].reverse();
|
|
18266
|
+
while (stack.length > 0) {
|
|
18267
|
+
const id = stack.pop();
|
|
18268
|
+
const node = state.nodes.get(id);
|
|
18269
|
+
if (!node) continue;
|
|
18270
|
+
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
18271
|
+
if (bucket) bucket.push(node);
|
|
18272
|
+
if (node.kind === "container") {
|
|
18273
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
18274
|
+
stack.push(node.children[i]);
|
|
17711
18275
|
}
|
|
17712
18276
|
}
|
|
17713
18277
|
}
|
|
18278
|
+
const out = [];
|
|
18279
|
+
for (const bucket of buckets) {
|
|
18280
|
+
for (const node of bucket) out.push(node);
|
|
18281
|
+
}
|
|
18282
|
+
return out;
|
|
17714
18283
|
}
|
|
17715
18284
|
const scene = {
|
|
17716
18285
|
get nodes() {
|
|
@@ -17739,7 +18308,12 @@ function createScene(options) {
|
|
|
17739
18308
|
return out;
|
|
17740
18309
|
},
|
|
17741
18310
|
renderOrder() {
|
|
17742
|
-
|
|
18311
|
+
const c = currentOrderCache();
|
|
18312
|
+
return c.ids ??= renderOrderInternal();
|
|
18313
|
+
},
|
|
18314
|
+
renderOrderNodes() {
|
|
18315
|
+
const c = currentOrderCache();
|
|
18316
|
+
return c.nodes ??= renderOrderNodesInternal();
|
|
17743
18317
|
},
|
|
17744
18318
|
add(spec) {
|
|
17745
18319
|
const id = spec.id ?? generateId();
|
|
@@ -18047,9 +18621,8 @@ function createScene(options) {
|
|
|
18047
18621
|
},
|
|
18048
18622
|
toJSON() {
|
|
18049
18623
|
const nodes = [];
|
|
18050
|
-
for (const
|
|
18051
|
-
const
|
|
18052
|
-
if (!n) continue;
|
|
18624
|
+
for (const n of renderOrderNodesInternal()) {
|
|
18625
|
+
const id = n.id;
|
|
18053
18626
|
const out = {
|
|
18054
18627
|
id,
|
|
18055
18628
|
kind: n.kind,
|
|
@@ -18083,6 +18656,7 @@ function createScene(options) {
|
|
|
18083
18656
|
state.roots.length = 0;
|
|
18084
18657
|
state.layers.length = 0;
|
|
18085
18658
|
state.layerIndex.clear();
|
|
18659
|
+
invalidateOrder();
|
|
18086
18660
|
for (let i = 0; i < json.systemLayers.length; i++) {
|
|
18087
18661
|
const spec = json.systemLayers[i];
|
|
18088
18662
|
if (state.layerIndex.has(spec.id)) {
|
|
@@ -18380,9 +18954,8 @@ function sceneToAdapter(scene, options = {}) {
|
|
|
18380
18954
|
getNodes() {
|
|
18381
18955
|
const visible = visibleLayers();
|
|
18382
18956
|
const out = [];
|
|
18383
|
-
for (const
|
|
18384
|
-
|
|
18385
|
-
if (n && visible.has(n.layer)) out.push(n);
|
|
18957
|
+
for (const n of scene.renderOrderNodes()) {
|
|
18958
|
+
if (visible.has(n.layer)) out.push(n);
|
|
18386
18959
|
}
|
|
18387
18960
|
return out;
|
|
18388
18961
|
},
|
|
@@ -18722,9 +19295,7 @@ function useSceneSelectTool(args) {
|
|
|
18722
19295
|
// path descriptor for a tighter test.
|
|
18723
19296
|
hitTestArea: (rect) => {
|
|
18724
19297
|
const hits = [];
|
|
18725
|
-
for (const
|
|
18726
|
-
const n = scene.get(nid);
|
|
18727
|
-
if (!n) continue;
|
|
19298
|
+
for (const n of scene.renderOrderNodes()) {
|
|
18728
19299
|
if (isPathLike(n.pose) && pathPoseDescriptor.intersectsRect) {
|
|
18729
19300
|
if (pathPoseDescriptor.intersectsRect(n.pose, rect)) hits.push(n.id);
|
|
18730
19301
|
continue;
|
|
@@ -18762,9 +19333,8 @@ function useSceneSelectTool(args) {
|
|
|
18762
19333
|
}
|
|
18763
19334
|
const tolerance = pickTolerancePx / meanScale(getView?.()?.scale ?? { x: 1, y: 1 });
|
|
18764
19335
|
const out = [];
|
|
18765
|
-
for (const
|
|
18766
|
-
|
|
18767
|
-
if (!n || !poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
|
|
19336
|
+
for (const n of scene.renderOrderNodes()) {
|
|
19337
|
+
if (!poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
|
|
18768
19338
|
if (shapePicking && !shapeCoversPoint(n, n.pose, wx, wy, { tolerance })) continue;
|
|
18769
19339
|
out.push(n.id);
|
|
18770
19340
|
}
|
|
@@ -19721,6 +20291,8 @@ function toAffordanceHit(hit) {
|
|
|
19721
20291
|
const scratch = hit.binding.initialScratch ?? {};
|
|
19722
20292
|
return {
|
|
19723
20293
|
kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,
|
|
20294
|
+
owner: hit.affordanceId,
|
|
20295
|
+
strength: "shared",
|
|
19724
20296
|
...scratch.targetId !== void 0 ? { targetIds: [scratch.targetId] } : {},
|
|
19725
20297
|
...scratch.anchor !== void 0 ? { anchor: scratch.anchor } : {},
|
|
19726
20298
|
...scratch.fixedPoint !== void 0 ? { fixedPoint: scratch.fixedPoint } : {},
|
|
@@ -19906,23 +20478,25 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
|
|
|
19906
20478
|
const ab = areaBounds ?? boundsOf(area);
|
|
19907
20479
|
if (!ab) return [];
|
|
19908
20480
|
const hits = [];
|
|
19909
|
-
|
|
19910
|
-
|
|
19911
|
-
|
|
20481
|
+
const order = scene.renderOrderNodes();
|
|
20482
|
+
for (let i = 0; i < order.length; i++) {
|
|
20483
|
+
const node = order[i];
|
|
20484
|
+
if (node.kind === "container") continue;
|
|
19912
20485
|
const pose = node.pose;
|
|
19913
|
-
const
|
|
20486
|
+
const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
|
|
20487
|
+
const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose)) : aabbOfPose(pose);
|
|
19914
20488
|
if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
|
|
19915
20489
|
continue;
|
|
19916
20490
|
}
|
|
19917
20491
|
if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
|
|
19918
20492
|
continue;
|
|
19919
20493
|
}
|
|
19920
|
-
if (!
|
|
19921
|
-
hits.push(id);
|
|
20494
|
+
if (!silhouette) {
|
|
20495
|
+
hits.push(node.id);
|
|
19922
20496
|
continue;
|
|
19923
20497
|
}
|
|
19924
20498
|
if (silhouetteOverlapsArea(pose.coords, area)) {
|
|
19925
|
-
hits.push(id);
|
|
20499
|
+
hits.push(node.id);
|
|
19926
20500
|
}
|
|
19927
20501
|
}
|
|
19928
20502
|
return hits;
|
|
@@ -20249,7 +20823,7 @@ function chordLengthParam(samples) {
|
|
|
20249
20823
|
ts[0] = 0;
|
|
20250
20824
|
let total = 0;
|
|
20251
20825
|
for (let i = 1; i < n; i++) {
|
|
20252
|
-
total +=
|
|
20826
|
+
total += dist2(samples[i - 1], samples[i]);
|
|
20253
20827
|
ts[i] = total;
|
|
20254
20828
|
}
|
|
20255
20829
|
if (total === 0) return null;
|
|
@@ -20282,13 +20856,13 @@ function solveLS(samples, ts, a, e, tStart, tEnd) {
|
|
|
20282
20856
|
let alpha1;
|
|
20283
20857
|
let alpha2;
|
|
20284
20858
|
if (Math.abs(det) < 1e-12) {
|
|
20285
|
-
const chord =
|
|
20859
|
+
const chord = dist2(a, e);
|
|
20286
20860
|
alpha1 = alpha2 = chord / 3;
|
|
20287
20861
|
} else {
|
|
20288
20862
|
alpha1 = (c22 * x1 - c12 * x2) / det;
|
|
20289
20863
|
alpha2 = (c11 * x2 - c12 * x1) / det;
|
|
20290
20864
|
if (alpha1 < 1e-6 || alpha2 < 1e-6) {
|
|
20291
|
-
const chord =
|
|
20865
|
+
const chord = dist2(a, e);
|
|
20292
20866
|
alpha1 = alpha2 = chord / 3;
|
|
20293
20867
|
}
|
|
20294
20868
|
}
|
|
@@ -20324,7 +20898,7 @@ function newtonRefineT(p, t, p0, p1, p2, p3) {
|
|
|
20324
20898
|
function sub(a, b) {
|
|
20325
20899
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
20326
20900
|
}
|
|
20327
|
-
function
|
|
20901
|
+
function dist2(a, b) {
|
|
20328
20902
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
20329
20903
|
}
|
|
20330
20904
|
function unit(v) {
|
|
@@ -21334,7 +21908,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
21334
21908
|
const getMode = getActiveModeRef.current;
|
|
21335
21909
|
if (!getMode) return true;
|
|
21336
21910
|
const registry2 = toolsForEligibilityRef.current?.registry;
|
|
21337
|
-
const caps = registry2?.[toolId]?.capabilities ?? [];
|
|
21911
|
+
const caps = registry2?.[toolId]?.eligibility.capabilities ?? [];
|
|
21338
21912
|
if (caps.length === 0) return false;
|
|
21339
21913
|
const allowed = getMode().allowedCapabilities;
|
|
21340
21914
|
for (const c of caps) if (allowed.has(c)) return true;
|
|
@@ -21808,10 +22382,6 @@ function GestureDispatcherMounter({
|
|
|
21808
22382
|
for (const tool of tools.ambient) m.set(tool.id, tool);
|
|
21809
22383
|
return m;
|
|
21810
22384
|
}, [tools.registry, tools.ambient]);
|
|
21811
|
-
const ambientToolIds = useMemo(
|
|
21812
|
-
() => tools.ambient.map((t) => t.id),
|
|
21813
|
-
[tools.ambient]
|
|
21814
|
-
);
|
|
21815
22385
|
const boundsOfRef = useRef(boundsOf2);
|
|
21816
22386
|
boundsOfRef.current = boundsOf2;
|
|
21817
22387
|
const pickEveryRef = useRef(pickEvery);
|
|
@@ -21923,9 +22493,14 @@ function GestureDispatcherMounter({
|
|
|
21923
22493
|
const worldPoint = clientToWorld2(screenPoint.x, screenPoint.y);
|
|
21924
22494
|
const extra = canvasApiRef?.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);
|
|
21925
22495
|
if (extra) {
|
|
22496
|
+
const claim = extra.hit;
|
|
21926
22497
|
return {
|
|
21927
22498
|
kind: `layer:${extra.layerId}`,
|
|
21928
|
-
|
|
22499
|
+
owner: extra.layerId,
|
|
22500
|
+
strength: claim.strength ?? "shared",
|
|
22501
|
+
...claim.claimedKinds !== void 0 ? { claimedKinds: claim.claimedKinds } : {},
|
|
22502
|
+
...claim.cursor !== void 0 ? { cursor: claim.cursor } : {},
|
|
22503
|
+
...claim.initialScratch !== void 0 ? { payload: claim.initialScratch } : {}
|
|
21929
22504
|
};
|
|
21930
22505
|
}
|
|
21931
22506
|
return affordanceAt ? affordanceAt(worldPoint) : null;
|
|
@@ -21945,7 +22520,6 @@ function GestureDispatcherMounter({
|
|
|
21945
22520
|
canvasRef,
|
|
21946
22521
|
actions: registry2,
|
|
21947
22522
|
toolsById,
|
|
21948
|
-
ambientToolIds,
|
|
21949
22523
|
enabled,
|
|
21950
22524
|
keyboard,
|
|
21951
22525
|
affordanceAt: wrappedAffordanceAt,
|
|
@@ -22156,14 +22730,25 @@ var inferredNodeProperties = inferredNodeRouting.map((e) => ({
|
|
|
22156
22730
|
|
|
22157
22731
|
// src/canvas/sceneViewRender.ts
|
|
22158
22732
|
var RENDERER_CACHE = /* @__PURE__ */ new WeakMap();
|
|
22159
|
-
function
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
|
|
22733
|
+
function sceneAsHierarchy(scene) {
|
|
22734
|
+
return {
|
|
22735
|
+
getLayers: () => scene.layers.map((l) => ({ id: l.id, visible: l.visible })),
|
|
22736
|
+
getNode: (id) => scene.get(id),
|
|
22737
|
+
getChildren: (parentId) => {
|
|
22738
|
+
if (parentId === null) return scene.roots;
|
|
22739
|
+
const node = scene.get(parentId);
|
|
22740
|
+
return node && node.kind === "container" ? node.children : [];
|
|
22741
|
+
},
|
|
22742
|
+
getPose: (id) => scene.get(id).pose
|
|
22743
|
+
};
|
|
22744
|
+
}
|
|
22745
|
+
function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor) {
|
|
22746
|
+
const wrappedDrawOne = (node, pose, v) => wrapNodeOutput(drawOne(node, pose, v), pose, alphaFor ? alphaFor(node.id) : 1);
|
|
22747
|
+
const children = buildSceneTree(
|
|
22748
|
+
sceneAsHierarchy(scene),
|
|
22749
|
+
wrappedDrawOne,
|
|
22750
|
+
view
|
|
22751
|
+
);
|
|
22167
22752
|
if (extraCommands && extraCommands.length > 0) {
|
|
22168
22753
|
for (const cmd of extraCommands) children.push(cmd);
|
|
22169
22754
|
}
|
|
@@ -22174,7 +22759,7 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands) {
|
|
|
22174
22759
|
}];
|
|
22175
22760
|
}
|
|
22176
22761
|
function renderSceneToCanvas(args) {
|
|
22177
|
-
const { canvas, scene, view, width, height, drawOne, extraCommands } = args;
|
|
22762
|
+
const { canvas, scene, view, width, height, drawOne, extraCommands, alphaFor } = args;
|
|
22178
22763
|
const dpr = args.dpr ?? (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
22179
22764
|
let entry = RENDERER_CACHE.get(canvas);
|
|
22180
22765
|
if (!entry) {
|
|
@@ -22202,11 +22787,11 @@ function renderSceneToCanvas(args) {
|
|
|
22202
22787
|
entry.height = height;
|
|
22203
22788
|
entry.dpr = dpr;
|
|
22204
22789
|
}
|
|
22205
|
-
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands);
|
|
22206
|
-
entry.renderer.render(commands);
|
|
22790
|
+
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor);
|
|
22791
|
+
entry.renderer.render(commands, viewToMat3(view));
|
|
22207
22792
|
}
|
|
22208
22793
|
function SceneViewCanvasInner(props) {
|
|
22209
|
-
const { scene, view, width, height, drawOne, extraCommands, className, canvasRef } = props;
|
|
22794
|
+
const { scene, view, width, height, drawOne, extraCommands, alphaFor, className, canvasRef } = props;
|
|
22210
22795
|
useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);
|
|
22211
22796
|
const localRef = useRef(null);
|
|
22212
22797
|
const setCanvasRef = (el) => {
|
|
@@ -22227,7 +22812,8 @@ function SceneViewCanvasInner(props) {
|
|
|
22227
22812
|
width,
|
|
22228
22813
|
height,
|
|
22229
22814
|
drawOne,
|
|
22230
|
-
extraCommands
|
|
22815
|
+
extraCommands,
|
|
22816
|
+
alphaFor
|
|
22231
22817
|
});
|
|
22232
22818
|
});
|
|
22233
22819
|
return /* @__PURE__ */ jsx(
|
|
@@ -22326,6 +22912,7 @@ function MinimapCanvasInner(props) {
|
|
|
22326
22912
|
width,
|
|
22327
22913
|
height,
|
|
22328
22914
|
drawOne,
|
|
22915
|
+
alphaFor,
|
|
22329
22916
|
fit = "scene",
|
|
22330
22917
|
poseBounds,
|
|
22331
22918
|
indicatorStyle,
|
|
@@ -22437,6 +23024,7 @@ function MinimapCanvasInner(props) {
|
|
|
22437
23024
|
height,
|
|
22438
23025
|
drawOne,
|
|
22439
23026
|
extraCommands,
|
|
23027
|
+
alphaFor,
|
|
22440
23028
|
className,
|
|
22441
23029
|
canvasRef: setCanvasRef
|
|
22442
23030
|
}
|
|
@@ -22478,7 +23066,7 @@ function planPixelRender(args) {
|
|
|
22478
23066
|
fill: { fill: "solid", color: args.background }
|
|
22479
23067
|
});
|
|
22480
23068
|
}
|
|
22481
|
-
commands.push(...buildSceneViewCommands(args.scene, view, drawOne));
|
|
23069
|
+
commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor));
|
|
22482
23070
|
return { width, height, view, commands };
|
|
22483
23071
|
}
|
|
22484
23072
|
function defaultCreateCanvas(width, height) {
|
|
@@ -22543,7 +23131,7 @@ function renderSceneToPixels(args) {
|
|
|
22543
23131
|
bakeBudget: Infinity
|
|
22544
23132
|
});
|
|
22545
23133
|
try {
|
|
22546
|
-
renderer.render(plan.commands);
|
|
23134
|
+
renderer.render(plan.commands, viewToMat3(plan.view));
|
|
22547
23135
|
if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
|
|
22548
23136
|
throw new Error("renderSceneToPixels: WebGL2 context was lost during render");
|
|
22549
23137
|
}
|
|
@@ -22830,15 +23418,11 @@ function measureText(ctx, text, maxWidth, style) {
|
|
|
22830
23418
|
function measureTextBounds(text, style, opts) {
|
|
22831
23419
|
const resolved = resolveTextStyle(style);
|
|
22832
23420
|
const runs = resolveRuns([{ text }], resolved);
|
|
22833
|
-
const { bounds } = layoutRuns(
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
align: resolved.align
|
|
22839
|
-
},
|
|
22840
|
-
{ x: 0, y: 0 }
|
|
22841
|
-
);
|
|
23421
|
+
const { bounds } = layoutRuns(runs, {
|
|
23422
|
+
maxWidth: opts?.maxWidth ?? Infinity,
|
|
23423
|
+
lineHeight: opts?.lineHeight ?? resolved.lineHeight,
|
|
23424
|
+
align: resolved.align
|
|
23425
|
+
});
|
|
22842
23426
|
return bounds;
|
|
22843
23427
|
}
|
|
22844
23428
|
|
|
@@ -23267,6 +23851,64 @@ function applyStyleToRange(runs, start, end, patch) {
|
|
|
23267
23851
|
return coalesceRuns(out);
|
|
23268
23852
|
}
|
|
23269
23853
|
|
|
23854
|
+
// src/features/text/runs/flagRange.ts
|
|
23855
|
+
function nodeHasFlag(style, key) {
|
|
23856
|
+
switch (key) {
|
|
23857
|
+
case "bold":
|
|
23858
|
+
return isBoldWeight(style.fontWeight);
|
|
23859
|
+
case "italic":
|
|
23860
|
+
return style.fontStyle === "italic";
|
|
23861
|
+
case "underline":
|
|
23862
|
+
return style.underline === true;
|
|
23863
|
+
case "strikethrough":
|
|
23864
|
+
return style.strikethrough === true;
|
|
23865
|
+
}
|
|
23866
|
+
}
|
|
23867
|
+
function isBoldWeight(w) {
|
|
23868
|
+
if (w === void 0) return false;
|
|
23869
|
+
if (typeof w === "number") return w >= 600;
|
|
23870
|
+
return w === "bold" || w === "bolder";
|
|
23871
|
+
}
|
|
23872
|
+
function weightIsExpressibleAsRunBold(w) {
|
|
23873
|
+
return w === 700 || w === "bold";
|
|
23874
|
+
}
|
|
23875
|
+
function clearNodeFlag(style, key) {
|
|
23876
|
+
const next = { ...style };
|
|
23877
|
+
switch (key) {
|
|
23878
|
+
case "bold":
|
|
23879
|
+
next.fontWeight = 400;
|
|
23880
|
+
break;
|
|
23881
|
+
case "italic":
|
|
23882
|
+
next.fontStyle = "normal";
|
|
23883
|
+
break;
|
|
23884
|
+
case "underline":
|
|
23885
|
+
delete next.underline;
|
|
23886
|
+
break;
|
|
23887
|
+
case "strikethrough":
|
|
23888
|
+
delete next.strikethrough;
|
|
23889
|
+
break;
|
|
23890
|
+
}
|
|
23891
|
+
return next;
|
|
23892
|
+
}
|
|
23893
|
+
function setFlagOverRange(runs, style, start, end, key, value) {
|
|
23894
|
+
if (value || !nodeHasFlag(style, key)) {
|
|
23895
|
+
return {
|
|
23896
|
+
runs: applyStyleToRange(runs, start, end, { [key]: value }),
|
|
23897
|
+
style,
|
|
23898
|
+
applied: true
|
|
23899
|
+
};
|
|
23900
|
+
}
|
|
23901
|
+
if (key === "bold" && !weightIsExpressibleAsRunBold(style.fontWeight)) {
|
|
23902
|
+
return { runs: [...runs], style, applied: false };
|
|
23903
|
+
}
|
|
23904
|
+
const total = runs.reduce((n, r) => n + r.text.length, 0);
|
|
23905
|
+
const lo = Math.max(0, start);
|
|
23906
|
+
const hi = Math.min(total, end);
|
|
23907
|
+
let next = applyStyleToRange(runs, 0, total, { [key]: true });
|
|
23908
|
+
if (lo < hi) next = applyStyleToRange(next, lo, hi, { [key]: false });
|
|
23909
|
+
return { runs: next, style: clearNodeFlag(style, key), applied: true };
|
|
23910
|
+
}
|
|
23911
|
+
|
|
23270
23912
|
// src/features/text/useTextEdit.ts
|
|
23271
23913
|
var FLAG_SHORTCUTS = [
|
|
23272
23914
|
{ key: "b", shift: false, flag: "bold" },
|
|
@@ -23428,7 +24070,21 @@ function useTextEdit(opts) {
|
|
|
23428
24070
|
togglePending(flag);
|
|
23429
24071
|
return;
|
|
23430
24072
|
}
|
|
23431
|
-
|
|
24073
|
+
if (editingId === null) return;
|
|
24074
|
+
const runs = domToRuns(overlay);
|
|
24075
|
+
const nodeStyle = optsRef.current.getStyle(editingId) ?? {};
|
|
24076
|
+
const setStyle = optsRef.current.setStyle;
|
|
24077
|
+
const current = styleAtRange(runs, range2.start, range2.end)[flag];
|
|
24078
|
+
const unsetting = current === true;
|
|
24079
|
+
if (unsetting && setStyle && nodeHasFlag(nodeStyle, flag)) {
|
|
24080
|
+
const r = setFlagOverRange(runs, nodeStyle, range2.start, range2.end, flag, false);
|
|
24081
|
+
if (!r.applied) return;
|
|
24082
|
+
setStyle(editingId, r.style);
|
|
24083
|
+
writeRunsPreservingSelection(overlay, r.runs, range2.start, range2.end);
|
|
24084
|
+
syncSelection();
|
|
24085
|
+
return;
|
|
24086
|
+
}
|
|
24087
|
+
const next = toggleFlagInRange(runs, range2.start, range2.end, flag);
|
|
23432
24088
|
writeRunsPreservingSelection(overlay, next, range2.start, range2.end);
|
|
23433
24089
|
syncSelection();
|
|
23434
24090
|
}
|
|
@@ -23679,10 +24335,9 @@ function useSceneTextEdit(scene, container, options = {}) {
|
|
|
23679
24335
|
const cy = view ? canvasY / view.scale.y + view.y : canvasY;
|
|
23680
24336
|
const readText = (data) => optsRef.current.getText ? optsRef.current.getText(data) : data.text ?? "";
|
|
23681
24337
|
const readStyle = (data) => optsRef.current.getStyle ? optsRef.current.getStyle(data) : data.style;
|
|
23682
|
-
const order =
|
|
24338
|
+
const order = sceneRef.current.renderOrderNodes();
|
|
23683
24339
|
for (let i = order.length - 1; i >= 0; i--) {
|
|
23684
|
-
const node =
|
|
23685
|
-
if (!node) continue;
|
|
24340
|
+
const node = order[i];
|
|
23686
24341
|
const text = readText(node.data);
|
|
23687
24342
|
const pose = {
|
|
23688
24343
|
x: node.pose.x,
|
|
@@ -23706,6 +24361,95 @@ function useSceneTextEdit(scene, container, options = {}) {
|
|
|
23706
24361
|
return { ...edit, onDoubleClick };
|
|
23707
24362
|
}
|
|
23708
24363
|
|
|
24364
|
+
// src/core/gradient.ts
|
|
24365
|
+
function sampleGradientStops(stops, t) {
|
|
24366
|
+
if (stops.length === 0) return "rgba(0,0,0,0)";
|
|
24367
|
+
const sorted = [...stops].sort((a, b) => a.offset - b.offset);
|
|
24368
|
+
if (sorted.length === 1) return sorted[0].color;
|
|
24369
|
+
const first = sorted[0];
|
|
24370
|
+
const last = sorted[sorted.length - 1];
|
|
24371
|
+
if (t <= first.offset) return first.color;
|
|
24372
|
+
if (t >= last.offset) return last.color;
|
|
24373
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
24374
|
+
const lo = sorted[i];
|
|
24375
|
+
const hi = sorted[i + 1];
|
|
24376
|
+
if (t < lo.offset || t > hi.offset) continue;
|
|
24377
|
+
const span = hi.offset - lo.offset;
|
|
24378
|
+
if (span <= 0) return hi.color;
|
|
24379
|
+
return lerpColor(lo.color, hi.color, (t - lo.offset) / span);
|
|
24380
|
+
}
|
|
24381
|
+
return last.color;
|
|
24382
|
+
}
|
|
24383
|
+
function lerpColor(a, b, t) {
|
|
24384
|
+
const [ar, ag, ab, aa] = hexToRgba(a);
|
|
24385
|
+
const [br, bg, bb, ba] = hexToRgba(b);
|
|
24386
|
+
return rgbaToHex([
|
|
24387
|
+
ar + (br - ar) * t,
|
|
24388
|
+
ag + (bg - ag) * t,
|
|
24389
|
+
ab + (bb - ab) * t,
|
|
24390
|
+
aa + (ba - aa) * t
|
|
24391
|
+
]);
|
|
24392
|
+
}
|
|
24393
|
+
function withGradientKind(fill, kind) {
|
|
24394
|
+
if (fill.fill === kind) return fill;
|
|
24395
|
+
const common = { stops: fill.stops, units: fill.units, opacity: fill.opacity };
|
|
24396
|
+
const { center, radius, angle } = gradientGeometry(fill);
|
|
24397
|
+
if (kind === "linear-gradient") {
|
|
24398
|
+
const dx = Math.cos(angle) * radius;
|
|
24399
|
+
const dy = Math.sin(angle) * radius;
|
|
24400
|
+
return {
|
|
24401
|
+
fill: "linear-gradient",
|
|
24402
|
+
from: { x: center.x - dx, y: center.y - dy },
|
|
24403
|
+
to: { x: center.x + dx, y: center.y + dy },
|
|
24404
|
+
...common
|
|
24405
|
+
};
|
|
24406
|
+
}
|
|
24407
|
+
if (kind === "radial-gradient") {
|
|
24408
|
+
return { fill: "radial-gradient", center, radius, ...common };
|
|
24409
|
+
}
|
|
24410
|
+
return { fill: "conic-gradient", center, angle, ...common };
|
|
24411
|
+
}
|
|
24412
|
+
function gradientGeometry(fill) {
|
|
24413
|
+
if (fill.fill === "linear-gradient") {
|
|
24414
|
+
const dx = fill.to.x - fill.from.x;
|
|
24415
|
+
const dy = fill.to.y - fill.from.y;
|
|
24416
|
+
return {
|
|
24417
|
+
center: { x: (fill.from.x + fill.to.x) / 2, y: (fill.from.y + fill.to.y) / 2 },
|
|
24418
|
+
radius: Math.hypot(dx, dy) / 2,
|
|
24419
|
+
angle: Math.atan2(dy, dx)
|
|
24420
|
+
};
|
|
24421
|
+
}
|
|
24422
|
+
if (fill.fill === "radial-gradient") {
|
|
24423
|
+
return { center: fill.center, radius: fill.radius, angle: 0 };
|
|
24424
|
+
}
|
|
24425
|
+
return { center: fill.center, radius: conicArmRadius(fill.units), angle: fill.angle };
|
|
24426
|
+
}
|
|
24427
|
+
function conicArmRadius(units) {
|
|
24428
|
+
return units === "bounds" ? 0.5 : 50;
|
|
24429
|
+
}
|
|
24430
|
+
function gradientForBounds(kind, bounds, stops, units) {
|
|
24431
|
+
const center = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
24432
|
+
if (kind === "linear-gradient") {
|
|
24433
|
+
return {
|
|
24434
|
+
fill: "linear-gradient",
|
|
24435
|
+
from: { x: bounds.x, y: center.y },
|
|
24436
|
+
to: { x: bounds.x + bounds.width, y: center.y },
|
|
24437
|
+
stops,
|
|
24438
|
+
units
|
|
24439
|
+
};
|
|
24440
|
+
}
|
|
24441
|
+
if (kind === "radial-gradient") {
|
|
24442
|
+
return {
|
|
24443
|
+
fill: "radial-gradient",
|
|
24444
|
+
center,
|
|
24445
|
+
radius: Math.max(bounds.width, bounds.height) / 2,
|
|
24446
|
+
stops,
|
|
24447
|
+
units
|
|
24448
|
+
};
|
|
24449
|
+
}
|
|
24450
|
+
return { fill: "conic-gradient", center, angle: 0, stops, units };
|
|
24451
|
+
}
|
|
24452
|
+
|
|
23709
24453
|
// src/core/ops/setText.ts
|
|
23710
24454
|
function createSetTextOp(args) {
|
|
23711
24455
|
const { id, from, to, label, coalesceKey = `setText:${id}` } = args;
|
|
@@ -25040,8 +25784,8 @@ function bestAxis(b, axis, anchors, candidates, worldTolerance) {
|
|
|
25040
25784
|
return { delta: bestGuide ? bestDelta : 0, guide: bestGuide };
|
|
25041
25785
|
}
|
|
25042
25786
|
function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
25043
|
-
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance);
|
|
25044
|
-
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance);
|
|
25787
|
+
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance.x);
|
|
25788
|
+
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance.y);
|
|
25045
25789
|
return { dx: x.delta, dy: y.delta, activeX: x.guide, activeY: y.guide };
|
|
25046
25790
|
}
|
|
25047
25791
|
|
|
@@ -25049,7 +25793,7 @@ function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
|
25049
25793
|
var activeList = (m) => [m.activeX, m.activeY].filter((g) => g !== null);
|
|
25050
25794
|
function worldTol(base) {
|
|
25051
25795
|
const t = base.tolerance ?? 6;
|
|
25052
|
-
return base.getView ? t
|
|
25796
|
+
return base.getView ? pxExtent(t, base.getView().scale) : { x: t, y: t };
|
|
25053
25797
|
}
|
|
25054
25798
|
function alignMoveBehavior(args) {
|
|
25055
25799
|
const proj = args.projection ?? RECT_ALIGN_PROJECTION;
|
|
@@ -27270,6 +28014,24 @@ function snapPoint(opts) {
|
|
|
27270
28014
|
};
|
|
27271
28015
|
}
|
|
27272
28016
|
|
|
27273
|
-
|
|
27274
|
-
|
|
27275
|
-
|
|
28017
|
+
// src/contributions/merge.ts
|
|
28018
|
+
function mergeContributions(...bundles) {
|
|
28019
|
+
const out = [];
|
|
28020
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28021
|
+
for (const bundle of bundles) {
|
|
28022
|
+
for (const entry of bundle) {
|
|
28023
|
+
if (seen.has(entry.id)) {
|
|
28024
|
+
throw new Error(
|
|
28025
|
+
`mergeContributions: duplicate contribution id "${entry.id}". Two bundles registered the same id; rename one or merge them by hand.`
|
|
28026
|
+
);
|
|
28027
|
+
}
|
|
28028
|
+
seen.add(entry.id);
|
|
28029
|
+
out.push(entry);
|
|
28030
|
+
}
|
|
28031
|
+
}
|
|
28032
|
+
return out;
|
|
28033
|
+
}
|
|
28034
|
+
|
|
28035
|
+
export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TextIcon, UnionIcon, UnknownIcon, VERSION, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, isPatternSpec, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, liveScope, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveFillPattern, resolvePatternSpec, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sampleGradientStops, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textLineBoxes, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
|
|
28036
|
+
//# sourceMappingURL=chunk-FVNWK7U3.js.map
|
|
28037
|
+
//# sourceMappingURL=chunk-FVNWK7U3.js.map
|