@neta-art/cohub 12.2.0 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -3
- package/dist/board/editor/context.d.ts +2 -0
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +7 -3
- package/dist/board/editor/editor.js +8 -1
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +99 -5
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +13 -1
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/model/asset-key.d.ts +9 -1
- package/dist/board/model/asset-key.js +27 -7
- package/dist/board/model/index.d.ts +5 -5
- package/dist/board/model/index.js +4 -4
- package/dist/board/model/items/create.js +1 -1
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/text-metrics.d.ts +21 -1
- package/dist/board/model/text-metrics.js +24 -1
- package/dist/board/model/themes.d.ts +49 -42
- package/dist/board/model/themes.js +49 -44
- package/dist/board/render/index.d.ts +8 -6
- package/dist/board/render/index.js +7 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +9 -1
- package/dist/board/render/lod.js +7 -1
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +14 -2
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/board-renderer-registry.d.ts +12 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/far-plate.d.ts +4 -1
- package/dist/board/render/renderers/far-plate.js +5 -1
- package/dist/board/render/renderers/file-card-renderer.js +44 -24
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +2 -2
- package/dist/board/render/renderers/model-card-renderer.js +2 -2
- package/dist/board/render/renderers/task-card-renderer.js +2 -2
- package/dist/board/render/renderers/video-card-renderer.js +2 -2
- package/dist/board/render/style.d.ts +1 -0
- package/dist/board/render/style.js +3 -1
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +4 -43
- package/dist/board/render/text-measurement.d.ts +12 -2
- package/dist/board/render/text-measurement.js +55 -10
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +34 -21
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +16 -4
- package/dist/board/stage/assets.js +155 -30
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +9 -5
- package/dist/board/stage/index.js +5 -2
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +4 -0
- package/dist/board/stage/scene.js +45 -13
- package/dist/board/stage/stage.d.ts +21 -2
- package/dist/board/stage/stage.js +309 -89
- package/dist/board/stage/texture-pool.d.ts +6 -4
- package/dist/board/stage/texture-pool.js +42 -8
- package/dist/board/stage/views.d.ts +1 -1
- package/dist/board/stage/views.js +2 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +65 -733
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.js +4 -0
- package/dist/protocol/dist/index.d.ts +2 -2
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
2
2
|
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
3
|
-
import { pointToWorld, screenPoint, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
3
|
+
import { itemBounds, pointToWorld, rectsIntersect, screenPoint, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
4
4
|
import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
|
|
5
5
|
import { boardScriptTarget } from "../model/script-hit.js";
|
|
6
6
|
import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
|
|
@@ -13,20 +13,25 @@ import { resizeCursorForHandle } from "../editor/selection-transform.js";
|
|
|
13
13
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
14
14
|
import { itemAlpha } from "../render/palette.js";
|
|
15
15
|
import { onBoardTextFontsChange } from "../render/text-measurement.js";
|
|
16
|
+
import { boardLodRatio, boardLodSize } from "../render/lod.js";
|
|
16
17
|
import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
|
|
17
18
|
import { getBoardResolution } from "../render/text-resolution.js";
|
|
18
19
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
19
20
|
import { createBoardScriptHost } from "../render/script-host.js";
|
|
20
21
|
import { defaultBoardRenderStyle } from "../render/style.js";
|
|
21
22
|
import { boardRenderTheme } from "../render/theme.js";
|
|
22
|
-
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
23
23
|
import { createBoardPlayer } from "../player/index.js";
|
|
24
|
+
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
25
|
+
import { defaultBoardDeviceBudget } from "./device.js";
|
|
26
|
+
import { scheduleBoardIdle } from "./idle.js";
|
|
24
27
|
import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
|
|
25
28
|
import { observeDisplayDensity } from "./display-density.js";
|
|
29
|
+
import { createBoardFrameClock } from "./frame-budget.js";
|
|
26
30
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
27
31
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
28
32
|
import { createBoardScene } from "./scene.js";
|
|
29
33
|
import { scheduleBoardMaintenance } from "./maintenance.js";
|
|
34
|
+
import { boardStagePress, createBoardLongPress, createGhostClickGuard } from "./presses.js";
|
|
30
35
|
import { createBoardScriptPointerRouter } from "./script-pointer.js";
|
|
31
36
|
import { drawBoardOverlay } from "./overlay.js";
|
|
32
37
|
import { createTextResolutionTracker } from "./text-resolution-tracker.js";
|
|
@@ -55,6 +60,15 @@ const TEXTURE_SETTLE_MS = 150;
|
|
|
55
60
|
const ADOPT_PIXELS_PER_FRAME = 4194304;
|
|
56
61
|
const LOAD_LEAD_MS = 300;
|
|
57
62
|
const CULL_PADDING = 96;
|
|
63
|
+
const PREFETCH_REST_MS = 200;
|
|
64
|
+
const PREFETCH_LIMIT = 96;
|
|
65
|
+
const FRAME_SAMPLES = 120;
|
|
66
|
+
const ANTIALIAS_MAX_DENSITY = 3;
|
|
67
|
+
const FRAME_SYNC = 1;
|
|
68
|
+
const FRAME_RENDER = 2;
|
|
69
|
+
const FRAME_CARDS = 4;
|
|
70
|
+
const FRAME_SCRIPTS = 8;
|
|
71
|
+
const FRAME_RESIZE = 16;
|
|
58
72
|
const ROTATE_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%23fff' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%231d1d1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 12 12, crosshair`;
|
|
59
73
|
const defaultStyles = /* @__PURE__ */ new Map();
|
|
60
74
|
function styleOf(theme) {
|
|
@@ -106,14 +120,23 @@ function mountBoardStage(host, options) {
|
|
|
106
120
|
let stopDensity = null;
|
|
107
121
|
let active = true;
|
|
108
122
|
let disposed = false;
|
|
109
|
-
let
|
|
110
|
-
let
|
|
111
|
-
let
|
|
112
|
-
let
|
|
123
|
+
let frameHandle = 0;
|
|
124
|
+
let frameWork = 0;
|
|
125
|
+
let inFrame = false;
|
|
126
|
+
let chained = false;
|
|
127
|
+
const clock = createBoardFrameClock();
|
|
128
|
+
const device = defaultBoardDeviceBudget();
|
|
129
|
+
const currentLod = () => options.lod?.() ?? device.lod;
|
|
130
|
+
const frameTimes = new Float32Array(FRAME_SAMPLES);
|
|
131
|
+
let frameCount = 0;
|
|
132
|
+
let frameCamera = null;
|
|
113
133
|
let cardTickTimer = null;
|
|
114
134
|
let cardTickDue = 0;
|
|
135
|
+
let prefetchKey = "";
|
|
136
|
+
let prefetchMovedAt = 0;
|
|
137
|
+
let prefetchContext = null;
|
|
138
|
+
let cancelPrefetch = null;
|
|
115
139
|
const staleIds = /* @__PURE__ */ new Set();
|
|
116
|
-
let resizeFrame = 0;
|
|
117
140
|
let canvasBox = null;
|
|
118
141
|
let cardEpoch = 0;
|
|
119
142
|
let fontEpoch = 0;
|
|
@@ -124,8 +147,15 @@ function mountBoardStage(host, options) {
|
|
|
124
147
|
};
|
|
125
148
|
let playbackCamera = null;
|
|
126
149
|
let cursor = "";
|
|
150
|
+
let lastContext = null;
|
|
151
|
+
let hoveredHit = null;
|
|
152
|
+
const ghostClicks = createGhostClickGuard(host.ownerDocument.defaultView);
|
|
153
|
+
const longPress = createBoardLongPress((press) => {
|
|
154
|
+
const item = editor.itemAt(pointToWorld(localPoint(press.clientX, press.clientY), renderCamera()));
|
|
155
|
+
if (item) options.onLongPress?.(item, press);
|
|
156
|
+
});
|
|
127
157
|
const renderCamera = () => playbackCamera ?? editor.camera;
|
|
128
|
-
const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera, scheduleTick);
|
|
158
|
+
const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera, scheduleTick, options.viewLayer);
|
|
129
159
|
const player = createBoardPlayer({ resolveColor: (color) => {
|
|
130
160
|
const current = theme();
|
|
131
161
|
return {
|
|
@@ -206,6 +236,7 @@ function mountBoardStage(host, options) {
|
|
|
206
236
|
};
|
|
207
237
|
}
|
|
208
238
|
const arrivals = /* @__PURE__ */ new Set();
|
|
239
|
+
const toned = /* @__PURE__ */ new Set();
|
|
209
240
|
function adoptArrivals() {
|
|
210
241
|
let pixels = 0;
|
|
211
242
|
for (const key of arrivals) {
|
|
@@ -298,13 +329,15 @@ function mountBoardStage(host, options) {
|
|
|
298
329
|
assetKey: assets.assetKey,
|
|
299
330
|
getTexture: (key) => assets.getTexture(key),
|
|
300
331
|
hasError: (key) => assets.hasError(key),
|
|
332
|
+
assetTone: (key) => assets.getTone(key),
|
|
301
333
|
fileState: (path) => options.fileState?.(path) ?? "ok",
|
|
302
334
|
acquireTexture: (key, item) => {
|
|
303
335
|
assets.acquire(key);
|
|
304
336
|
assets.requestItem(item, boardTextureEdge(item, textureScale()));
|
|
305
337
|
if (assets.getTexture(key)) naturalIds.add(item.id);
|
|
306
338
|
},
|
|
307
|
-
releaseTexture: (key) => assets.release(key)
|
|
339
|
+
releaseTexture: (key) => assets.release(key),
|
|
340
|
+
hoveredHit
|
|
308
341
|
};
|
|
309
342
|
}
|
|
310
343
|
function syncBackground(current) {
|
|
@@ -474,6 +507,37 @@ function mountBoardStage(host, options) {
|
|
|
474
507
|
if (style.selection.dash > 0 && selected.length > 0 && !(options.reducedMotion?.() ?? false)) scheduleMarch(style);
|
|
475
508
|
else stopMarch();
|
|
476
509
|
}
|
|
510
|
+
function opensOnTap(item) {
|
|
511
|
+
return !editor.selection.includes(item.id) && editor.registry.activation(item) === "tap";
|
|
512
|
+
}
|
|
513
|
+
function hitAt(item, point) {
|
|
514
|
+
if (!lastContext || !scene?.getNode(item.id)) return null;
|
|
515
|
+
const hit = resolveRenderer(item, lastContext).hitAt?.(item, point, lastContext);
|
|
516
|
+
return hit && (options.handlesHit?.(item, hit.action) ?? true) ? hit : null;
|
|
517
|
+
}
|
|
518
|
+
function hoverHit(input) {
|
|
519
|
+
if (!options.onHit) return;
|
|
520
|
+
const item = input && editor.interaction.type === "idle" && editor.hoverId ? editor.itemById(editor.hoverId) : null;
|
|
521
|
+
const hit = item && input ? hitAt(item, input.world) : null;
|
|
522
|
+
const next = item && hit ? {
|
|
523
|
+
itemId: item.id,
|
|
524
|
+
action: hit.action,
|
|
525
|
+
cursor: hit.cursor ?? "pointer"
|
|
526
|
+
} : null;
|
|
527
|
+
if (next?.itemId === hoveredHit?.itemId && next?.action === hoveredHit?.action) return;
|
|
528
|
+
for (const id of [hoveredHit?.itemId, next?.itemId]) if (id) staleIds.add(id);
|
|
529
|
+
hoveredHit = next;
|
|
530
|
+
scheduleTick();
|
|
531
|
+
}
|
|
532
|
+
function pressHit(event, input) {
|
|
533
|
+
if (!options.onHit || event.button !== 0) return false;
|
|
534
|
+
const item = editor.itemAt(input.world);
|
|
535
|
+
const hit = item ? hitAt(item, input.world) : null;
|
|
536
|
+
if (!item || !hit || options.onHit(item, hit.action, boardStagePress(event)) === false) return false;
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
if (event.pointerType !== "mouse") ghostClicks.arm();
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
477
541
|
function cursorFor() {
|
|
478
542
|
if (scriptMode === "interact" && scriptHover) {
|
|
479
543
|
const output = scriptOutputs.get(scriptHover.owner);
|
|
@@ -488,49 +552,191 @@ function mountBoardStage(host, options) {
|
|
|
488
552
|
case "draggingArrowHandle":
|
|
489
553
|
case "brushing": return "crosshair";
|
|
490
554
|
}
|
|
491
|
-
if (
|
|
555
|
+
if (interaction.type === "pressing") return "pointer";
|
|
556
|
+
if (editor.spaceHeld) return "grab";
|
|
557
|
+
if (hoveredHit) return hoveredHit.cursor;
|
|
492
558
|
const control = editor.hoveredTransformControl;
|
|
493
559
|
if (control?.kind === "resize") return resizeCursorForHandle(control.handle, editor.selectionTransform?.frame.rotation ?? 0);
|
|
494
560
|
if (control?.kind === "rotate") return ROTATE_CURSOR;
|
|
495
|
-
if (isCreationBoardTool(editor.tool)) return "crosshair";
|
|
496
561
|
const hovered = editor.hoverId ? editor.itemById(editor.hoverId) : null;
|
|
562
|
+
if (hovered && (editor.tool === "select" || editor.tool === "hand") && opensOnTap(hovered)) return "pointer";
|
|
563
|
+
if (editor.tool === "hand") return "grab";
|
|
564
|
+
if (isCreationBoardTool(editor.tool)) return "crosshair";
|
|
497
565
|
return hovered && !hovered.locked && editor.registry.capabilities(hovered).canMove ? "move" : "default";
|
|
498
566
|
}
|
|
567
|
+
function requestFrame(work) {
|
|
568
|
+
if (!active || disposed) return;
|
|
569
|
+
frameWork |= work;
|
|
570
|
+
if (!frameHandle && !inFrame) {
|
|
571
|
+
chained = false;
|
|
572
|
+
frameHandle = requestAnimationFrame(runFrame);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function cancelFrames() {
|
|
576
|
+
cancelAnimationFrame(frameHandle);
|
|
577
|
+
frameHandle = 0;
|
|
578
|
+
frameWork = 0;
|
|
579
|
+
if (cardTickTimer !== null) clearTimeout(cardTickTimer);
|
|
580
|
+
cardTickTimer = null;
|
|
581
|
+
}
|
|
582
|
+
function runFrame(now) {
|
|
583
|
+
frameHandle = 0;
|
|
584
|
+
if (!app) return;
|
|
585
|
+
const start = performance.now();
|
|
586
|
+
let work = frameWork;
|
|
587
|
+
frameWork = 0;
|
|
588
|
+
inFrame = true;
|
|
589
|
+
try {
|
|
590
|
+
const camera = renderCamera();
|
|
591
|
+
const moving = editor.gestureActive || !frameCamera || camera.x !== frameCamera.x || camera.y !== frameCamera.y || camera.zoom !== frameCamera.zoom;
|
|
592
|
+
frameCamera = camera;
|
|
593
|
+
clock.begin(now, {
|
|
594
|
+
moving,
|
|
595
|
+
chained
|
|
596
|
+
});
|
|
597
|
+
if (work & FRAME_RESIZE && !applyResize()) work &= -17;
|
|
598
|
+
const synced = Boolean(work & 17);
|
|
599
|
+
if (synced) sync();
|
|
600
|
+
else if (work & FRAME_SCRIPTS) {
|
|
601
|
+
syncScripts();
|
|
602
|
+
if (scriptMode !== "paused" && scripts?.animated) frameWork |= FRAME_SCRIPTS;
|
|
603
|
+
}
|
|
604
|
+
if (work & FRAME_CARDS && !synced && scene) {
|
|
605
|
+
const next = scene.tick(performance.now());
|
|
606
|
+
work |= FRAME_RENDER;
|
|
607
|
+
if (next !== null) scheduleCardTick(next);
|
|
608
|
+
}
|
|
609
|
+
if ((work | frameWork) & FRAME_RENDER) {
|
|
610
|
+
frameWork &= -3;
|
|
611
|
+
app.render();
|
|
612
|
+
}
|
|
613
|
+
} finally {
|
|
614
|
+
inFrame = false;
|
|
615
|
+
frameTimes[frameCount++ % FRAME_SAMPLES] = performance.now() - start;
|
|
616
|
+
if (frameWork && active && !disposed) {
|
|
617
|
+
chained = true;
|
|
618
|
+
frameHandle = requestAnimationFrame(runFrame);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function waitingCards() {
|
|
623
|
+
if (!scene || surface.width === 0) return 0;
|
|
624
|
+
const view = visibleWorldRect(renderCamera(), surface.width, surface.height);
|
|
625
|
+
let waiting = 0;
|
|
626
|
+
for (const id of scene.liveIds()) {
|
|
627
|
+
const item = editor.scene.get(id);
|
|
628
|
+
const key = item && assets.assetKey(item);
|
|
629
|
+
if (key && !assets.hasError(key) && !assets.getTexture(key) && rectsIntersect(itemBounds(item.frame), view)) waiting += 1;
|
|
630
|
+
}
|
|
631
|
+
return waiting;
|
|
632
|
+
}
|
|
633
|
+
function frameStats() {
|
|
634
|
+
const samples = Array.from(frameTimes.subarray(0, Math.min(frameCount, FRAME_SAMPLES))).sort((a, b) => a - b);
|
|
635
|
+
const at = (ratio) => samples[Math.min(samples.length - 1, Math.floor(samples.length * ratio))] ?? 0;
|
|
636
|
+
return {
|
|
637
|
+
p50: at(.5),
|
|
638
|
+
p95: at(.95),
|
|
639
|
+
max: samples.at(-1) ?? 0
|
|
640
|
+
};
|
|
641
|
+
}
|
|
499
642
|
function scheduleRender() {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
643
|
+
requestFrame(FRAME_RENDER);
|
|
644
|
+
}
|
|
645
|
+
function scheduleTick() {
|
|
646
|
+
requestFrame(FRAME_SYNC);
|
|
647
|
+
}
|
|
648
|
+
function scheduleScriptTick() {
|
|
649
|
+
requestFrame(FRAME_SCRIPTS);
|
|
650
|
+
}
|
|
651
|
+
function resize() {
|
|
652
|
+
requestFrame(FRAME_RESIZE);
|
|
653
|
+
}
|
|
654
|
+
function flushResize() {
|
|
655
|
+
if (!active || disposed) return;
|
|
656
|
+
cancelAnimationFrame(frameHandle);
|
|
657
|
+
frameWork |= FRAME_RESIZE;
|
|
658
|
+
chained = false;
|
|
659
|
+
runFrame(performance.now());
|
|
660
|
+
chained = false;
|
|
505
661
|
}
|
|
506
662
|
function scheduleCardTick(wait) {
|
|
507
663
|
if (!active || disposed) return;
|
|
508
664
|
const due = performance.now() + wait;
|
|
509
|
-
if (
|
|
665
|
+
if (frameWork & FRAME_CARDS || cardTickTimer !== null && cardTickDue <= due) return;
|
|
510
666
|
if (cardTickTimer !== null) clearTimeout(cardTickTimer);
|
|
511
667
|
cardTickTimer = null;
|
|
512
668
|
if (wait > 0) {
|
|
513
669
|
cardTickDue = due;
|
|
514
670
|
cardTickTimer = setTimeout(() => {
|
|
515
671
|
cardTickTimer = null;
|
|
516
|
-
|
|
672
|
+
requestFrame(FRAME_CARDS);
|
|
517
673
|
}, wait);
|
|
518
674
|
return;
|
|
519
675
|
}
|
|
520
|
-
|
|
521
|
-
cardTickFrame = 0;
|
|
522
|
-
if (!scene) return;
|
|
523
|
-
const next = scene.tick(performance.now());
|
|
524
|
-
scheduleRender();
|
|
525
|
-
if (next !== null) scheduleCardTick(next);
|
|
526
|
-
});
|
|
676
|
+
requestFrame(FRAME_CARDS);
|
|
527
677
|
}
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
678
|
+
function schedulePrefetch(camera, context) {
|
|
679
|
+
const margin = assets.prefetch ? options.prefetch ?? device.prefetch : 0;
|
|
680
|
+
if (margin <= 0 || surface.width === 0 || surface.height === 0) return;
|
|
681
|
+
const key = `${camera.x}|${camera.y}|${camera.zoom}|${surface.width}|${surface.height}|${editor.structureVersion}`;
|
|
682
|
+
if (key === prefetchKey) return;
|
|
683
|
+
prefetchKey = key;
|
|
684
|
+
prefetchContext = context;
|
|
685
|
+
prefetchMovedAt = performance.now();
|
|
686
|
+
if (!cancelPrefetch) awaitRest(margin, PREFETCH_REST_MS);
|
|
687
|
+
}
|
|
688
|
+
function awaitRest(margin, delay) {
|
|
689
|
+
cancelPrefetch = scheduleBoardIdle(() => {
|
|
690
|
+
cancelPrefetch = null;
|
|
691
|
+
const remaining = PREFETCH_REST_MS - (performance.now() - prefetchMovedAt);
|
|
692
|
+
if (remaining > 0) awaitRest(margin, remaining);
|
|
693
|
+
else if (prefetchContext) prefetch(margin, prefetchContext);
|
|
694
|
+
}, delay);
|
|
695
|
+
}
|
|
696
|
+
function stopPrefetch() {
|
|
697
|
+
cancelPrefetch?.();
|
|
698
|
+
cancelPrefetch = null;
|
|
699
|
+
prefetchKey = "";
|
|
700
|
+
prefetchContext = null;
|
|
701
|
+
assets.prefetch?.([]);
|
|
702
|
+
}
|
|
703
|
+
function prefetch(margin, context) {
|
|
704
|
+
if (!scene || !active || disposed) return;
|
|
705
|
+
if (editor.gestureActive) {
|
|
706
|
+
prefetchKey = "";
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const camera = renderCamera();
|
|
710
|
+
const view = visibleWorldRect(camera, surface.width, surface.height);
|
|
711
|
+
const culled = stableCullRect(view);
|
|
712
|
+
const padX = view.width * margin;
|
|
713
|
+
const padY = view.height * margin;
|
|
714
|
+
const ring = {
|
|
715
|
+
x: culled.x - padX,
|
|
716
|
+
y: culled.y - padY,
|
|
717
|
+
width: culled.width + padX * 2,
|
|
718
|
+
height: culled.height + padY * 2
|
|
719
|
+
};
|
|
720
|
+
const centerX = view.x + view.width / 2;
|
|
721
|
+
const centerY = view.y + view.height / 2;
|
|
722
|
+
const lod = currentLod();
|
|
723
|
+
const scale = textureScale();
|
|
724
|
+
const wanted = [];
|
|
725
|
+
for (const id of editor.idsInRect(ring)) {
|
|
726
|
+
const item = editor.scene.get(id);
|
|
727
|
+
if (!item || rectsIntersect(itemBounds(item.frame), culled)) continue;
|
|
728
|
+
const renderer = resolveRenderer(item, context);
|
|
729
|
+
if (renderer.renderFar && boardLodRatio(renderer, boardLodSize(renderer, item), camera.zoom, lod) < 1) continue;
|
|
730
|
+
const { frame } = item;
|
|
731
|
+
const distance = (frame.x + frame.width / 2 - centerX) ** 2 + (frame.y + frame.height / 2 - centerY) ** 2;
|
|
732
|
+
wanted.push({
|
|
733
|
+
item,
|
|
734
|
+
edge: boardTextureEdge(item, scale),
|
|
735
|
+
distance
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
wanted.sort((a, b) => a.distance - b.distance);
|
|
739
|
+
assets.prefetch?.(wanted.slice(0, PREFETCH_LIMIT));
|
|
534
740
|
}
|
|
535
741
|
function cancelScriptPointers(owner, retained) {
|
|
536
742
|
scriptPointers.cancelScripts((pointerId, target) => {
|
|
@@ -559,15 +765,6 @@ function mountBoardStage(host, options) {
|
|
|
559
765
|
}));
|
|
560
766
|
scripts.sync(editor.document, editor.scene, camera, scriptTime, scriptMode === "interact", visibleIds());
|
|
561
767
|
}
|
|
562
|
-
function scheduleScriptTick() {
|
|
563
|
-
if (scriptTickFrame || !active || disposed) return;
|
|
564
|
-
scriptTickFrame = requestAnimationFrame(() => {
|
|
565
|
-
scriptTickFrame = 0;
|
|
566
|
-
if (tickFrame) return;
|
|
567
|
-
syncScripts();
|
|
568
|
-
if (scriptMode !== "paused" && scripts?.animated) scheduleScriptTick();
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
768
|
function createScripts() {
|
|
572
769
|
return createBoardScriptHost({
|
|
573
770
|
host,
|
|
@@ -686,8 +883,13 @@ function mountBoardStage(host, options) {
|
|
|
686
883
|
const center = pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera);
|
|
687
884
|
assets.focus(loadFocus(center, camera));
|
|
688
885
|
adoptArrivals();
|
|
886
|
+
if (toned.size > 0) {
|
|
887
|
+
scene?.repaintFar(toned, assets.assetKey);
|
|
888
|
+
toned.clear();
|
|
889
|
+
}
|
|
689
890
|
textResolution.update(textureScale());
|
|
690
891
|
const context = renderContext(current, renderScene, frame.time);
|
|
892
|
+
lastContext = context;
|
|
691
893
|
if (animationId && effectSampler) {
|
|
692
894
|
context.time = editor.playhead?.time ?? frame.time;
|
|
693
895
|
context.frameAt = effectSampler.frameAt;
|
|
@@ -723,9 +925,11 @@ function mountBoardStage(host, options) {
|
|
|
723
925
|
gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
|
|
724
926
|
focus: center,
|
|
725
927
|
view: ids ? cullView(camera) : void 0,
|
|
726
|
-
lod:
|
|
928
|
+
lod: currentLod(),
|
|
929
|
+
budget: clock
|
|
727
930
|
});
|
|
728
931
|
staleIds.clear();
|
|
932
|
+
schedulePrefetch(camera, context);
|
|
729
933
|
if (scene.liveVersion !== liveVersion) {
|
|
730
934
|
liveVersion = scene.liveVersion;
|
|
731
935
|
options.onLiveChange?.(scene.liveIds());
|
|
@@ -749,29 +953,26 @@ function mountBoardStage(host, options) {
|
|
|
749
953
|
const cardWait = scene.tick(performance.now());
|
|
750
954
|
if (cardWait !== null) scheduleCardTick(cardWait);
|
|
751
955
|
}
|
|
752
|
-
function
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
sync();
|
|
774
|
-
});
|
|
956
|
+
function applyResize() {
|
|
957
|
+
if (!app) return false;
|
|
958
|
+
const size = {
|
|
959
|
+
width: host.clientWidth,
|
|
960
|
+
height: host.clientHeight
|
|
961
|
+
};
|
|
962
|
+
if (size.width <= 0 || size.height <= 0) return false;
|
|
963
|
+
const density = host.ownerDocument.defaultView?.devicePixelRatio ?? 1;
|
|
964
|
+
const canvas = boardCanvasSize(canvasBox ?? {
|
|
965
|
+
...size,
|
|
966
|
+
density,
|
|
967
|
+
device: null
|
|
968
|
+
}, density);
|
|
969
|
+
if (canvas.width !== app.screen.width || canvas.height !== app.screen.height || canvas.resolution !== app.renderer.resolution) app.renderer.resize(canvas.width, canvas.height, canvas.resolution);
|
|
970
|
+
if (size.width !== surface.width || size.height !== surface.height) {
|
|
971
|
+
surface = size;
|
|
972
|
+
editor.surfaceSize = surface;
|
|
973
|
+
options.onSurfaceChange?.(surface);
|
|
974
|
+
}
|
|
975
|
+
return true;
|
|
775
976
|
}
|
|
776
977
|
const localPoint = (x, y) => elementClientToLocal(host)(x, y);
|
|
777
978
|
const inViews = (event) => event.target instanceof Node && views.element.contains(event.target);
|
|
@@ -853,8 +1054,11 @@ function mountBoardStage(host, options) {
|
|
|
853
1054
|
return;
|
|
854
1055
|
}
|
|
855
1056
|
if (options.onPointer?.("down", event, input) === false) return;
|
|
1057
|
+
if (pressHit(event, input)) return;
|
|
856
1058
|
pointers.start(event);
|
|
857
1059
|
editor.pointerDown(input);
|
|
1060
|
+
if (options.onLongPress && editor.itemAt(input.world)) longPress.start(event);
|
|
1061
|
+
else longPress.cancel();
|
|
858
1062
|
}
|
|
859
1063
|
function handlePointerMove(event) {
|
|
860
1064
|
if (!active || pointers.has(event.pointerId) || inViews(event)) return;
|
|
@@ -863,21 +1067,28 @@ function mountBoardStage(host, options) {
|
|
|
863
1067
|
function routePointerMove(event) {
|
|
864
1068
|
const input = toInput(event);
|
|
865
1069
|
if (scriptPointer("move", event, input)) return;
|
|
1070
|
+
longPress.move(event);
|
|
866
1071
|
if (options.onPointer?.("move", event, input) === false) return;
|
|
867
1072
|
editor.pointerMove(input);
|
|
1073
|
+
hoverHit(input);
|
|
868
1074
|
}
|
|
869
1075
|
function handlePointerUp(event) {
|
|
870
1076
|
const input = toInput(event);
|
|
871
1077
|
if (scriptPointer("up", event, input)) return;
|
|
1078
|
+
longPress.end(event);
|
|
872
1079
|
if (options.onPointer?.("up", event, input) === false) return;
|
|
873
1080
|
editor.pointerUp(input);
|
|
874
|
-
if (event.type === "pointercancel" || event.pointerType !== "mouse")
|
|
1081
|
+
if (event.type === "pointercancel" || event.pointerType !== "mouse") {
|
|
1082
|
+
editor.pointerLeave();
|
|
1083
|
+
hoverHit(null);
|
|
1084
|
+
}
|
|
875
1085
|
}
|
|
876
1086
|
function handlePointerLeave(event) {
|
|
877
1087
|
if (event.buttons !== 0) return;
|
|
878
1088
|
scriptPointer("leave", event, toInput(event));
|
|
879
1089
|
if (options.onPointer?.("leave", event, toInput(event)) === false) return;
|
|
880
1090
|
editor.pointerLeave();
|
|
1091
|
+
hoverHit(null);
|
|
881
1092
|
}
|
|
882
1093
|
function handleWheel(event) {
|
|
883
1094
|
if (inViews(event)) return;
|
|
@@ -896,7 +1107,7 @@ function mountBoardStage(host, options) {
|
|
|
896
1107
|
return;
|
|
897
1108
|
}
|
|
898
1109
|
if (item && item.type !== "frame" && item.type !== "arrow") return;
|
|
899
|
-
const label = editor.labelItemAt(point);
|
|
1110
|
+
const label = editor.labelItemAt(point, styleOf(theme()).frameLabelSize);
|
|
900
1111
|
if (label) editor.editingId = label.id;
|
|
901
1112
|
else if (!item) editor.beginTextDraft(point);
|
|
902
1113
|
}
|
|
@@ -914,27 +1125,40 @@ function mountBoardStage(host, options) {
|
|
|
914
1125
|
["dblclick", handleDoubleClick]
|
|
915
1126
|
];
|
|
916
1127
|
const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
|
|
1128
|
+
const stopActivations = editor.subscribeActivations(({ itemId, pointerType, selected }) => {
|
|
1129
|
+
const item = editor.itemById(itemId);
|
|
1130
|
+
if (!item || !options.onActivate) return;
|
|
1131
|
+
if (pointerType !== "mouse") ghostClicks.arm();
|
|
1132
|
+
options.onActivate(item, {
|
|
1133
|
+
itemId,
|
|
1134
|
+
pointerType,
|
|
1135
|
+
selected
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
917
1138
|
const stopFonts = onBoardTextFontsChange(() => {
|
|
918
1139
|
editor.relayoutText();
|
|
919
1140
|
fontEpoch += 1;
|
|
920
1141
|
scene?.redrawText();
|
|
921
1142
|
scheduleTick();
|
|
922
1143
|
});
|
|
923
|
-
const stopAssets = assets.subscribe((key) => {
|
|
924
|
-
arrivals.add(key);
|
|
1144
|
+
const stopAssets = assets.subscribe((key, change) => {
|
|
1145
|
+
if (change === "texture") arrivals.add(key);
|
|
1146
|
+
else if (scene?.counts().far) toned.add(key);
|
|
1147
|
+
else return;
|
|
925
1148
|
scheduleTick();
|
|
926
1149
|
});
|
|
927
1150
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
928
1151
|
const entry = entries[entries.length - 1];
|
|
929
1152
|
if (entry) canvasBox = measureBoardCanvasBox(entry, host.ownerDocument.defaultView?.devicePixelRatio ?? 1);
|
|
930
|
-
|
|
1153
|
+
flushResize();
|
|
931
1154
|
});
|
|
932
1155
|
return {
|
|
933
1156
|
ready: (async () => {
|
|
934
1157
|
const instance = new Application();
|
|
1158
|
+
const density = host.ownerDocument.defaultView?.devicePixelRatio ?? 1;
|
|
935
1159
|
try {
|
|
936
1160
|
await instance.init({
|
|
937
|
-
antialias:
|
|
1161
|
+
antialias: density < ANTIALIAS_MAX_DENSITY,
|
|
938
1162
|
backgroundAlpha: 0,
|
|
939
1163
|
width: host.clientWidth,
|
|
940
1164
|
height: host.clientHeight,
|
|
@@ -1042,16 +1266,8 @@ function mountBoardStage(host, options) {
|
|
|
1042
1266
|
stopMarch();
|
|
1043
1267
|
stopRefine();
|
|
1044
1268
|
cancelScriptPointers();
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
cancelAnimationFrame(renderFrame);
|
|
1048
|
-
cancelAnimationFrame(tickFrame);
|
|
1049
|
-
cancelAnimationFrame(cardTickFrame);
|
|
1050
|
-
if (cardTickTimer !== null) clearTimeout(cardTickTimer);
|
|
1051
|
-
renderFrame = 0;
|
|
1052
|
-
tickFrame = 0;
|
|
1053
|
-
cardTickFrame = 0;
|
|
1054
|
-
cardTickTimer = null;
|
|
1269
|
+
cancelFrames();
|
|
1270
|
+
stopPrefetch();
|
|
1055
1271
|
return;
|
|
1056
1272
|
}
|
|
1057
1273
|
player.reset();
|
|
@@ -1065,7 +1281,7 @@ function mountBoardStage(host, options) {
|
|
|
1065
1281
|
},
|
|
1066
1282
|
isMaterialized: (id) => Boolean(scene?.getNode(id)),
|
|
1067
1283
|
stats() {
|
|
1068
|
-
const { textures, cooling, bytes } = assets.stats();
|
|
1284
|
+
const { textures, cooling, bytes, loading, prefetching } = assets.stats();
|
|
1069
1285
|
return {
|
|
1070
1286
|
...scene?.counts() ?? {
|
|
1071
1287
|
live: 0,
|
|
@@ -1073,7 +1289,13 @@ function mountBoardStage(host, options) {
|
|
|
1073
1289
|
},
|
|
1074
1290
|
textures,
|
|
1075
1291
|
cooling,
|
|
1076
|
-
textureBytes: bytes
|
|
1292
|
+
textureBytes: bytes,
|
|
1293
|
+
loading: loading ?? 0,
|
|
1294
|
+
prefetching: prefetching ?? 0,
|
|
1295
|
+
waiting: waitingCards(),
|
|
1296
|
+
settling: scene?.settling ?? false,
|
|
1297
|
+
frame: frameStats(),
|
|
1298
|
+
interval: clock.interval
|
|
1077
1299
|
};
|
|
1078
1300
|
},
|
|
1079
1301
|
toWorld: (clientX, clientY) => pointToWorld(localPoint(clientX, clientY), renderCamera()),
|
|
@@ -1090,16 +1312,14 @@ function mountBoardStage(host, options) {
|
|
|
1090
1312
|
stopMaintenance?.();
|
|
1091
1313
|
stopDensity?.();
|
|
1092
1314
|
stopEditor();
|
|
1315
|
+
stopActivations();
|
|
1316
|
+
longPress.cancel();
|
|
1317
|
+
ghostClicks.dispose();
|
|
1093
1318
|
stopFonts();
|
|
1094
1319
|
stopAssets();
|
|
1095
1320
|
resizeObserver.disconnect();
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
cancelAnimationFrame(renderFrame);
|
|
1099
|
-
cancelAnimationFrame(tickFrame);
|
|
1100
|
-
cancelAnimationFrame(cardTickFrame);
|
|
1101
|
-
if (cardTickTimer !== null) clearTimeout(cardTickTimer);
|
|
1102
|
-
cardTickTimer = null;
|
|
1321
|
+
cancelFrames();
|
|
1322
|
+
stopPrefetch();
|
|
1103
1323
|
for (const [type, listener] of listeners) host.removeEventListener(type, listener);
|
|
1104
1324
|
editor.setPlayedItems(/* @__PURE__ */ new Map());
|
|
1105
1325
|
editor.setScriptItems(/* @__PURE__ */ new Map());
|
|
@@ -8,12 +8,14 @@ type BoardImageTexturePool = {
|
|
|
8
8
|
declare function loadBoardImageTexture(url: string): Promise<Texture>;
|
|
9
9
|
declare function unloadBoardImageTexture(url: string, texture: Texture | null): Promise<void>;
|
|
10
10
|
/**
|
|
11
|
-
* Decode an image no larger than `edge` along its long side
|
|
12
|
-
* without CDN variants. The texture is unshared; destroy
|
|
11
|
+
* Decode an image no larger than `edge` along its long side (short side with
|
|
12
|
+
* `cover`), for sources without CDN variants. The texture is unshared; destroy
|
|
13
|
+
* it to release it.
|
|
13
14
|
*/
|
|
14
|
-
declare function loadFittedImageTexture(url: string, edge: number): Promise<Texture>;
|
|
15
|
+
declare function loadFittedImageTexture(url: string, edge: number, cover?: boolean): Promise<Texture>;
|
|
15
16
|
/** A smaller unshared copy of a decoded texture, or null when it cannot be read back. */
|
|
16
17
|
declare function shrinkBoardTexture(texture: Texture, edge: number): Promise<Texture | null>;
|
|
18
|
+
declare function readTextureTone(texture: Texture): Promise<number | null>;
|
|
17
19
|
/**
|
|
18
20
|
* Share image textures by resolved URL without sharing Board ownership.
|
|
19
21
|
*
|
|
@@ -28,4 +30,4 @@ declare function createBoardImageTexturePool(options?: {
|
|
|
28
30
|
}): BoardImageTexturePool;
|
|
29
31
|
declare const boardImageTexturePool: BoardImageTexturePool;
|
|
30
32
|
//#endregion
|
|
31
|
-
export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture };
|
|
33
|
+
export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, readTextureTone, shrinkBoardTexture, unloadBoardImageTexture };
|