@neta-art/cohub 9.0.1 → 10.0.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 +100 -103
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +14 -0
- package/dist/board/editor/animation.js +172 -0
- package/dist/board/editor/camera.d.ts +32 -0
- package/dist/board/editor/camera.js +137 -0
- package/dist/board/editor/clipboard.d.ts +41 -0
- package/dist/board/editor/clipboard.js +122 -0
- package/dist/board/editor/connection-ports.d.ts +19 -0
- package/dist/board/editor/connection-ports.js +85 -0
- package/dist/board/editor/context.d.ts +111 -0
- package/dist/board/editor/context.js +30 -0
- package/dist/board/editor/copy.d.ts +17 -0
- package/dist/board/editor/copy.js +116 -0
- package/dist/board/editor/creation.d.ts +47 -0
- package/dist/board/editor/creation.js +224 -0
- package/dist/board/editor/deletion.d.ts +9 -0
- package/dist/board/editor/deletion.js +90 -0
- package/dist/board/editor/document-edits.d.ts +22 -0
- package/dist/board/editor/document-edits.js +193 -0
- package/dist/board/editor/document.d.ts +35 -0
- package/dist/board/editor/document.js +255 -0
- package/dist/board/editor/editor.d.ts +210 -0
- package/dist/board/editor/editor.js +429 -0
- package/dist/board/editor/index.d.ts +18 -0
- package/dist/board/editor/index.js +12 -0
- package/dist/board/editor/interaction.d.ts +115 -0
- package/dist/board/editor/interaction.js +584 -0
- package/dist/board/editor/query.d.ts +27 -0
- package/dist/board/editor/query.js +201 -0
- package/dist/board/editor/remote.d.ts +11 -0
- package/dist/board/editor/remote.js +55 -0
- package/dist/board/editor/selection-transform.d.ts +24 -0
- package/dist/board/editor/selection-transform.js +81 -0
- package/dist/board/editor/selection.d.ts +33 -0
- package/dist/board/editor/selection.js +196 -0
- package/dist/board/editor/snapping.d.ts +33 -0
- package/dist/board/editor/snapping.js +75 -0
- package/dist/board/editor/snapshots.d.ts +27 -0
- package/dist/board/editor/snapshots.js +132 -0
- package/dist/board/editor/spatial.d.ts +41 -0
- package/dist/board/editor/spatial.js +191 -0
- package/dist/board/editor/state.d.ts +12 -0
- package/dist/board/editor/state.js +49 -0
- package/dist/board/editor/tool.d.ts +10 -0
- package/dist/board/editor/tool.js +19 -0
- package/dist/board/editor/wheel.d.ts +5 -0
- package/dist/board/editor/wheel.js +17 -0
- package/dist/board/export/index.d.ts +5 -4
- package/dist/board/export/index.js +4 -3
- package/dist/board/export/node/index.d.ts +55 -0
- package/dist/board/{headless → export/node}/index.js +12 -12
- package/dist/board/export/node/sketch.d.ts +19 -0
- package/dist/board/{headless → export/node}/sketch.js +6 -6
- package/dist/board/export/scene.d.ts +6 -4
- package/dist/board/export/scene.js +4 -3
- package/dist/board/{animation.d.ts → model/animation.d.ts} +4 -4
- package/dist/board/{animation.js → model/animation.js} +3 -3
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
- package/dist/board/{core → model}/draw-geometry.js +1 -1
- package/dist/board/model/draw-input.d.ts +14 -0
- package/dist/board/model/draw-input.js +28 -0
- package/dist/board/{core → model}/export-assets.d.ts +1 -1
- package/dist/board/{core → model}/export-assets.js +1 -1
- package/dist/board/{core → model}/export-plan.d.ts +2 -2
- package/dist/board/{core → model}/export-plan.js +2 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +1 -1
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +1 -1
- package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
- package/dist/board/{geometry.js → model/geometry.js} +1 -1
- package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
- package/dist/board/{image-key.js → model/image-key.js} +1 -1
- package/dist/board/model/index.d.ts +35 -0
- package/dist/board/{index.js → model/index.js} +26 -22
- package/dist/board/model/items/builtin.d.ts +5 -0
- package/dist/board/model/items/builtin.js +119 -0
- package/dist/board/model/items/create.d.ts +59 -0
- package/dist/board/model/items/create.js +271 -0
- package/dist/board/model/items/definition.d.ts +35 -0
- package/dist/board/model/items/definition.js +18 -0
- package/dist/board/model/items/id.d.ts +5 -0
- package/dist/board/model/items/id.js +11 -0
- package/dist/board/model/items/index.d.ts +6 -0
- package/dist/board/model/items/index.js +6 -0
- package/dist/board/model/items/registry.d.ts +35 -0
- package/dist/board/model/items/registry.js +84 -0
- package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +2 -2
- package/dist/board/{media-playback.js → model/media-playback.js} +1 -1
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +1 -1
- package/dist/board/{core → model}/palette.d.ts +1 -1
- package/dist/board/{core → model}/palette.js +1 -1
- package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
- package/dist/board/{presets.js → model/presets.js} +1 -1
- package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
- package/dist/board/{replay.js → model/replay.js} +2 -2
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- package/dist/board/model/shape-types.d.ts +8 -0
- package/dist/board/model/shape-types.js +8 -0
- package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
- package/dist/board/{task.js → model/task.js} +3 -3
- package/dist/board/{core → model}/text-metrics.d.ts +1 -1
- package/dist/board/{core → model}/text-metrics.js +1 -1
- package/dist/board/{core → model}/tool-styles.d.ts +1 -1
- package/dist/board/{core → model}/tool-styles.js +1 -1
- package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
- package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
- package/dist/board/render/board-background.d.ts +1 -1
- package/dist/board/render/clip.d.ts +1 -1
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +2 -2
- package/dist/board/render/media-interaction.d.ts +2 -2
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +1 -1
- package/dist/board/render/palette.js +1 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +1 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +11 -6
- package/dist/board/render/renderers/board-renderer-registry.js +15 -15
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +2 -2
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +1 -1
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- package/dist/board/render/stroke.d.ts +1 -1
- package/dist/board/render/stroke.js +1 -1
- package/dist/board/render/text-measurement.js +1 -1
- package/dist/board/replica/index.d.ts +70 -0
- package/dist/board/replica/index.js +276 -0
- package/dist/board/stage/assets.d.ts +41 -0
- package/dist/board/stage/assets.js +350 -0
- package/dist/board/stage/index.d.ts +8 -0
- package/dist/board/stage/index.js +8 -0
- package/dist/board/stage/lru.d.ts +32 -0
- package/dist/board/stage/lru.js +29 -0
- package/dist/board/stage/pointer.d.ts +8 -0
- package/dist/board/stage/pointer.js +33 -0
- package/dist/board/stage/scene.d.ts +85 -0
- package/dist/board/stage/scene.js +512 -0
- package/dist/board/stage/stage.d.ts +66 -0
- package/dist/board/stage/stage.js +568 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +154 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +24 -1
- package/dist/chunks/http.d.ts +99 -66
- package/dist/chunks/http.js +214 -207
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +20 -4
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board.d.ts +28 -2
- package/dist/protocol/dist/board.js +3 -1
- package/dist/protocol/dist/core/media.d.ts +5 -0
- package/dist/protocol/dist/core/media.js +39 -0
- package/dist/protocol/dist/index.d.ts +5 -5
- package/dist/types.d.ts +1 -1
- package/package.json +20 -8
- package/dist/board/core/shape-definition.d.ts +0 -23
- package/dist/board/core/shape-definition.js +0 -45
- package/dist/board/core/shape-types.d.ts +0 -38
- package/dist/board/core/shape-types.js +0 -22
- package/dist/board/headless/index.d.ts +0 -55
- package/dist/board/headless/sketch.d.ts +0 -19
- package/dist/board/index.d.ts +0 -30
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/board/stage/lru.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pure eviction policy for the board texture LRU cache.
|
|
4
|
+
*
|
|
5
|
+
* Textures whose cards have scrolled off-screen are not freed immediately:
|
|
6
|
+
* they enter a "cooling" pool (reference count zero but still on the GPU) so a
|
|
7
|
+
* quick pan back is instant instead of re-fetching. The pool is bounded by
|
|
8
|
+
* both entry count and approximate GPU bytes; when a budget is exceeded the least
|
|
9
|
+
* recently used entries are evicted first. Keeping this decision pure makes the
|
|
10
|
+
* release-vs-churn trade-off trivial to test.
|
|
11
|
+
*/
|
|
12
|
+
type LruEntry = {
|
|
13
|
+
key: string;
|
|
14
|
+
/** Timestamp (ms) the entry was last used; smaller = older = evict first. */
|
|
15
|
+
lastUsedAt: number;
|
|
16
|
+
/** Approximate GPU footprint in bytes. */
|
|
17
|
+
bytes: number;
|
|
18
|
+
};
|
|
19
|
+
type LruBudget = {
|
|
20
|
+
maxCount: number;
|
|
21
|
+
maxBytes: number;
|
|
22
|
+
};
|
|
23
|
+
/** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
|
|
24
|
+
declare const DEFAULT_LRU_BUDGET: LruBudget;
|
|
25
|
+
/**
|
|
26
|
+
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
27
|
+
* evict — oldest first — so the remaining pool fits within both limits. Returns
|
|
28
|
+
* an empty array when the pool already fits.
|
|
29
|
+
*/
|
|
30
|
+
declare function selectLruEvictions(entries: LruEntry[], budget: LruBudget): string[];
|
|
31
|
+
//#endregion
|
|
32
|
+
export { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/board/stage/lru.ts
|
|
2
|
+
/** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
|
|
3
|
+
const DEFAULT_LRU_BUDGET = {
|
|
4
|
+
maxCount: 64,
|
|
5
|
+
maxBytes: 134217728
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
9
|
+
* evict — oldest first — so the remaining pool fits within both limits. Returns
|
|
10
|
+
* an empty array when the pool already fits.
|
|
11
|
+
*/
|
|
12
|
+
function selectLruEvictions(entries, budget) {
|
|
13
|
+
if (entries.length === 0) return [];
|
|
14
|
+
let count = entries.length;
|
|
15
|
+
let bytes = 0;
|
|
16
|
+
for (const entry of entries) bytes += entry.bytes;
|
|
17
|
+
if (count <= budget.maxCount && bytes <= budget.maxBytes) return [];
|
|
18
|
+
const sorted = [...entries].sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
19
|
+
const evict = [];
|
|
20
|
+
for (const entry of sorted) {
|
|
21
|
+
if (count <= budget.maxCount && bytes <= budget.maxBytes) break;
|
|
22
|
+
evict.push(entry.key);
|
|
23
|
+
count -= 1;
|
|
24
|
+
bytes -= entry.bytes;
|
|
25
|
+
}
|
|
26
|
+
return evict;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DEFAULT_LRU_BUDGET, selectLruEvictions };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BoardViewport } from "../model/geometry.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
import { BoardPointerEvent } from "../editor/interaction.js";
|
|
4
|
+
import "../editor/index.js";
|
|
5
|
+
//#region src/board/stage/pointer.d.ts
|
|
6
|
+
declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { toBoardPointerEvent };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { pointToWorld, screenPoint } from "../model/geometry.js";
|
|
2
|
+
//#region src/board/stage/pointer.ts
|
|
3
|
+
function pressureOf(event) {
|
|
4
|
+
return event.pointerType === "pen" && event.pressure > 0 ? event.pressure : .5;
|
|
5
|
+
}
|
|
6
|
+
function screenOf(event, rect) {
|
|
7
|
+
return screenPoint(event.clientX - rect.left, event.clientY - rect.top);
|
|
8
|
+
}
|
|
9
|
+
function toBoardPointerEvent(event, rect, camera, coalesce = false) {
|
|
10
|
+
const screen = screenOf(event, rect);
|
|
11
|
+
const coalesced = coalesce ? event.getCoalescedEvents?.() : void 0;
|
|
12
|
+
const samples = coalesced && coalesced.length > 1 ? coalesced.map((sample) => ({
|
|
13
|
+
world: pointToWorld(screenOf(sample, rect), camera),
|
|
14
|
+
pressure: pressureOf(sample)
|
|
15
|
+
})) : void 0;
|
|
16
|
+
return {
|
|
17
|
+
pointerId: event.pointerId,
|
|
18
|
+
screen,
|
|
19
|
+
world: pointToWorld(screen, camera),
|
|
20
|
+
shiftKey: event.shiftKey,
|
|
21
|
+
metaKey: event.metaKey,
|
|
22
|
+
ctrlKey: event.ctrlKey,
|
|
23
|
+
altKey: event.altKey,
|
|
24
|
+
button: event.button,
|
|
25
|
+
buttons: event.buttons,
|
|
26
|
+
pointerType: event.pointerType,
|
|
27
|
+
cancelled: event.type === "pointercancel" || event.type === "lostpointercapture",
|
|
28
|
+
pressure: pressureOf(event),
|
|
29
|
+
...samples ? { samples } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { toBoardPointerEvent };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BoardDrawPoint } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { Rect, WorldPoint } from "../model/geometry.js";
|
|
4
|
+
import { BoardScene as BoardScene$1, BoardSceneItem } from "../model/scene.js";
|
|
5
|
+
import "../model/index.js";
|
|
6
|
+
import { BoardSelectionTransform, BoardTransformControl } from "../editor/selection-transform.js";
|
|
7
|
+
import "../editor/index.js";
|
|
8
|
+
import { BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
9
|
+
import "../render/index.js";
|
|
10
|
+
import { Container, Graphics } from "pixi.js";
|
|
11
|
+
//#region src/board/stage/scene.d.ts
|
|
12
|
+
type BoardLiveStroke = {
|
|
13
|
+
id: string;
|
|
14
|
+
points: BoardDrawPoint[];
|
|
15
|
+
color: string;
|
|
16
|
+
size: number;
|
|
17
|
+
};
|
|
18
|
+
type SceneSyncInput = {
|
|
19
|
+
items: readonly BoardSceneItem[];
|
|
20
|
+
strokes: readonly BoardLiveStroke[];
|
|
21
|
+
scene: BoardScene$1;
|
|
22
|
+
context: BoardRenderContext;
|
|
23
|
+
getItem: (id: string) => BoardSceneItem | null;
|
|
24
|
+
visibleIds: Set<string> | null;
|
|
25
|
+
pinnedIds: Set<string>;
|
|
26
|
+
globalSig: string;
|
|
27
|
+
structureVersion: number;
|
|
28
|
+
geometryVersion: number;
|
|
29
|
+
gestureActive: boolean;
|
|
30
|
+
};
|
|
31
|
+
type SceneOverlayInput = {
|
|
32
|
+
zoom: number;
|
|
33
|
+
pointerType: string;
|
|
34
|
+
marquee: Rect | null;
|
|
35
|
+
selection: string[];
|
|
36
|
+
transform: BoardSelectionTransform | null;
|
|
37
|
+
controls: boolean;
|
|
38
|
+
hoveredControl: BoardTransformControl | null;
|
|
39
|
+
rotationPointer: WorldPoint | null;
|
|
40
|
+
arrowEndpoints?: Array<{
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
}>;
|
|
44
|
+
ports?: ReadonlyArray<{
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
radius: number;
|
|
48
|
+
}>;
|
|
49
|
+
hoveredPort?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
} | null;
|
|
53
|
+
arrowDraft?: {
|
|
54
|
+
from: {
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
};
|
|
58
|
+
to: {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
};
|
|
62
|
+
size: number;
|
|
63
|
+
targetFrame: BoardFrame | null;
|
|
64
|
+
} | null;
|
|
65
|
+
bindTarget?: BoardFrame | null;
|
|
66
|
+
};
|
|
67
|
+
type BoardSceneNode = {
|
|
68
|
+
item: BoardSceneItem;
|
|
69
|
+
container: Container;
|
|
70
|
+
};
|
|
71
|
+
type BoardScene = {
|
|
72
|
+
sync: (input: SceneSyncInput) => void;
|
|
73
|
+
getNode: (itemId: string) => BoardSceneNode | null;
|
|
74
|
+
readonly animated: boolean;
|
|
75
|
+
drawOverlay: (input: SceneOverlayInput, palette: BoardRenderPalette) => void;
|
|
76
|
+
destroy: (context: BoardRenderContext) => void;
|
|
77
|
+
};
|
|
78
|
+
declare function createBoardScene(options: {
|
|
79
|
+
world: Container;
|
|
80
|
+
overlay: Graphics;
|
|
81
|
+
getRenderer: BoardCardRendererResolver;
|
|
82
|
+
onFarLayerFrame?: () => void;
|
|
83
|
+
}): BoardScene;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneOverlayInput, SceneSyncInput, createBoardScene };
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { CORNER_RESIZE_HANDLES, frameCorners, frameHandlePosition, frameRayIntersection, rotationHandleAnchor, rotationHandlePosition } from "../model/geometry.js";
|
|
4
|
+
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
5
|
+
import { Graphics } from "pixi.js";
|
|
6
|
+
//#region src/board/stage/scene.ts
|
|
7
|
+
const FAR_LAYER_ENTER = 450;
|
|
8
|
+
const FAR_LAYER_EXIT = 350;
|
|
9
|
+
const FAR_PASS_BATCH = 240;
|
|
10
|
+
const FAR_LIVE_LIMIT = 64;
|
|
11
|
+
const Z_FAR_LAYER = -1;
|
|
12
|
+
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
13
|
+
const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
|
|
14
|
+
const POOL_LIMIT_PER_RENDERER = 48;
|
|
15
|
+
function idSetSignature(ids) {
|
|
16
|
+
let hash = ids.size;
|
|
17
|
+
for (const id of ids) {
|
|
18
|
+
let local = 0;
|
|
19
|
+
for (let i = 0; i < id.length; i += 1) local = local * 31 + id.charCodeAt(i) | 0;
|
|
20
|
+
hash ^= local;
|
|
21
|
+
}
|
|
22
|
+
return hash;
|
|
23
|
+
}
|
|
24
|
+
function traceFrame(graphics, frame) {
|
|
25
|
+
const corners = frameCorners(frame);
|
|
26
|
+
const first = corners[0];
|
|
27
|
+
if (!first) return graphics;
|
|
28
|
+
graphics.moveTo(first.x, first.y);
|
|
29
|
+
for (let index = 1; index < corners.length; index += 1) {
|
|
30
|
+
const point = corners[index];
|
|
31
|
+
if (point) graphics.lineTo(point.x, point.y);
|
|
32
|
+
}
|
|
33
|
+
return graphics.closePath();
|
|
34
|
+
}
|
|
35
|
+
function frameEdgePoints(frame, handle) {
|
|
36
|
+
switch (handle) {
|
|
37
|
+
case "n": return [frameHandlePosition(frame, "nw"), frameHandlePosition(frame, "ne")];
|
|
38
|
+
case "e": return [frameHandlePosition(frame, "ne"), frameHandlePosition(frame, "se")];
|
|
39
|
+
case "s": return [frameHandlePosition(frame, "sw"), frameHandlePosition(frame, "se")];
|
|
40
|
+
case "w": return [frameHandlePosition(frame, "nw"), frameHandlePosition(frame, "sw")];
|
|
41
|
+
default: return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function createBoardScene(options) {
|
|
45
|
+
const { world, overlay, getRenderer } = options;
|
|
46
|
+
world.sortableChildren = true;
|
|
47
|
+
overlay.zIndex = Z_OVERLAY;
|
|
48
|
+
const cards = /* @__PURE__ */ new Map();
|
|
49
|
+
const clips = /* @__PURE__ */ new Map();
|
|
50
|
+
const animatedIds = /* @__PURE__ */ new Set();
|
|
51
|
+
const pools = /* @__PURE__ */ new Map();
|
|
52
|
+
const heldKeys = /* @__PURE__ */ new Map();
|
|
53
|
+
let orderById = /* @__PURE__ */ new Map();
|
|
54
|
+
let farSig = null;
|
|
55
|
+
let farActive = false;
|
|
56
|
+
let lastStructureVersion = -1;
|
|
57
|
+
const liveStrokes = /* @__PURE__ */ new Map();
|
|
58
|
+
function setHeldKey(context, cardId, desiredKey) {
|
|
59
|
+
const heldKey = heldKeys.get(cardId) ?? null;
|
|
60
|
+
if (desiredKey === heldKey) return;
|
|
61
|
+
if (heldKey) context.releaseTexture(heldKey);
|
|
62
|
+
if (desiredKey) context.acquireTexture(desiredKey);
|
|
63
|
+
if (desiredKey) heldKeys.set(cardId, desiredKey);
|
|
64
|
+
else heldKeys.delete(cardId);
|
|
65
|
+
}
|
|
66
|
+
function takeFromPool(rendererId) {
|
|
67
|
+
const pool = pools.get(rendererId);
|
|
68
|
+
if (!pool || pool.length === 0) return null;
|
|
69
|
+
return pool.pop() ?? null;
|
|
70
|
+
}
|
|
71
|
+
function recycle(id, entry, context, destroyed) {
|
|
72
|
+
setHeldKey(context, id, null);
|
|
73
|
+
cards.delete(id);
|
|
74
|
+
animatedIds.delete(id);
|
|
75
|
+
if (destroyed) return;
|
|
76
|
+
entry.container.parent?.removeChild(entry.container);
|
|
77
|
+
entry.container.visible = false;
|
|
78
|
+
const pool = pools.get(entry.renderer.id) ?? [];
|
|
79
|
+
if (pool.length >= POOL_LIMIT_PER_RENDERER) {
|
|
80
|
+
entry.renderer.destroy?.(entry.container, context);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
pool.push(entry.container);
|
|
84
|
+
pools.set(entry.renderer.id, pool);
|
|
85
|
+
}
|
|
86
|
+
function hostFor(item, scene) {
|
|
87
|
+
const clipId = clippingAncestor(scene, item);
|
|
88
|
+
const frame = clipId ? scene.get(clipId) : void 0;
|
|
89
|
+
if (!clipId || !frame) return world;
|
|
90
|
+
let entry = clips.get(clipId);
|
|
91
|
+
if (!entry) {
|
|
92
|
+
entry = syncClipGroup(void 0, frame);
|
|
93
|
+
entry.group.sortableChildren = true;
|
|
94
|
+
clips.set(clipId, entry);
|
|
95
|
+
}
|
|
96
|
+
const parent = hostFor(frame, scene);
|
|
97
|
+
if (entry.group.parent !== parent) parent.addChild(entry.group);
|
|
98
|
+
entry.group.zIndex = (orderById.get(clipId) ?? 0) + .5;
|
|
99
|
+
return entry.group;
|
|
100
|
+
}
|
|
101
|
+
function syncClips(scene) {
|
|
102
|
+
for (const entry of cards.values()) {
|
|
103
|
+
const host = hostFor(entry.item, scene);
|
|
104
|
+
if (entry.container.parent !== host) host.addChild(entry.container);
|
|
105
|
+
}
|
|
106
|
+
for (const [id, entry] of clips) {
|
|
107
|
+
const frame = scene.get(id);
|
|
108
|
+
if (!frame || !isClippingFrame(frame) || entry.group.children.length <= 1) {
|
|
109
|
+
for (const child of [...entry.group.children]) if (child !== entry.mask) world.addChild(child);
|
|
110
|
+
entry.group.destroy({ children: true });
|
|
111
|
+
clips.delete(id);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
syncClipGroup(entry, frame);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function materialize(item, context, globalSig, scene) {
|
|
118
|
+
const renderer = getRenderer(item, context);
|
|
119
|
+
const pooled = takeFromPool(renderer.id);
|
|
120
|
+
const container = pooled ?? renderer.create(item, context);
|
|
121
|
+
container.visible = true;
|
|
122
|
+
if (pooled) renderer.update(container, item, context);
|
|
123
|
+
container.alpha = scene.opacity(item.id);
|
|
124
|
+
hostFor(item, scene).addChild(container);
|
|
125
|
+
if (renderer.animated) animatedIds.add(item.id);
|
|
126
|
+
const entry = {
|
|
127
|
+
item,
|
|
128
|
+
container,
|
|
129
|
+
renderer,
|
|
130
|
+
selected: context.selectedIds.has(item.id),
|
|
131
|
+
hovered: context.hoveredId === item.id,
|
|
132
|
+
resizing: context.resizingIds.has(item.id),
|
|
133
|
+
globalSig
|
|
134
|
+
};
|
|
135
|
+
cards.set(item.id, entry);
|
|
136
|
+
setHeldKey(context, item.id, context.assetKey(item));
|
|
137
|
+
return entry;
|
|
138
|
+
}
|
|
139
|
+
let farFront = createFarLayer();
|
|
140
|
+
let farBack = createFarLayer();
|
|
141
|
+
let farPainted = false;
|
|
142
|
+
let farCovered = /* @__PURE__ */ new Set();
|
|
143
|
+
let farInput = null;
|
|
144
|
+
let farRunning = false;
|
|
145
|
+
let farQueued = false;
|
|
146
|
+
let farFrame = 0;
|
|
147
|
+
function createFarLayer() {
|
|
148
|
+
const layer = new Graphics({ label: "board-far-layer" });
|
|
149
|
+
layer.zIndex = Z_FAR_LAYER;
|
|
150
|
+
layer.visible = false;
|
|
151
|
+
world.addChild(layer);
|
|
152
|
+
return layer;
|
|
153
|
+
}
|
|
154
|
+
function requestFarPass(signature) {
|
|
155
|
+
farSig = signature;
|
|
156
|
+
if (farRunning) farQueued = true;
|
|
157
|
+
else runFarPass();
|
|
158
|
+
}
|
|
159
|
+
function runFarPass() {
|
|
160
|
+
if (!farInput) return;
|
|
161
|
+
farRunning = true;
|
|
162
|
+
farQueued = false;
|
|
163
|
+
const { context, getItem, pinnedIds } = farInput;
|
|
164
|
+
const ids = visibleFacts(farInput).orderedIds;
|
|
165
|
+
const target = farPainted ? farBack : farFront;
|
|
166
|
+
const painted = /* @__PURE__ */ new Set();
|
|
167
|
+
target.clear();
|
|
168
|
+
if (target === farFront) farCovered = painted;
|
|
169
|
+
let index = 0;
|
|
170
|
+
const step = () => {
|
|
171
|
+
farFrame = 0;
|
|
172
|
+
const end = Math.min(ids.length, index + FAR_PASS_BATCH);
|
|
173
|
+
for (; index < end; index += 1) {
|
|
174
|
+
const id = ids[index];
|
|
175
|
+
if (pinnedIds.has(id)) continue;
|
|
176
|
+
const item = getItem(id);
|
|
177
|
+
if (!item) continue;
|
|
178
|
+
getRenderer(item, context).renderFar?.(target, item, context);
|
|
179
|
+
painted.add(id);
|
|
180
|
+
}
|
|
181
|
+
if (index < ids.length) {
|
|
182
|
+
farFrame = requestAnimationFrame(step);
|
|
183
|
+
if (target === farFront) options.onFarLayerFrame?.();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (target === farBack) {
|
|
187
|
+
farBack = farFront;
|
|
188
|
+
farFront = target;
|
|
189
|
+
farBack.visible = false;
|
|
190
|
+
farBack.clear();
|
|
191
|
+
farCovered = painted;
|
|
192
|
+
}
|
|
193
|
+
farFront.visible = farActive;
|
|
194
|
+
farPainted = true;
|
|
195
|
+
farRunning = false;
|
|
196
|
+
options.onFarLayerFrame?.();
|
|
197
|
+
if (farQueued) runFarPass();
|
|
198
|
+
};
|
|
199
|
+
step();
|
|
200
|
+
}
|
|
201
|
+
function resetFarLayer() {
|
|
202
|
+
cancelAnimationFrame(farFrame);
|
|
203
|
+
farFrame = 0;
|
|
204
|
+
farRunning = false;
|
|
205
|
+
farQueued = false;
|
|
206
|
+
farInput = null;
|
|
207
|
+
farPainted = false;
|
|
208
|
+
farCovered = /* @__PURE__ */ new Set();
|
|
209
|
+
farSig = null;
|
|
210
|
+
farFront.clear();
|
|
211
|
+
farBack.clear();
|
|
212
|
+
}
|
|
213
|
+
function syncLiveStrokes(input) {
|
|
214
|
+
const { strokes, context, getItem } = input;
|
|
215
|
+
const live = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const stroke of strokes) {
|
|
217
|
+
if (stroke.points.length === 0 || getItem(stroke.id)) continue;
|
|
218
|
+
let entry = liveStrokes.get(stroke.id);
|
|
219
|
+
if (entry && (entry.template.style.stroke !== stroke.color || entry.template.style.strokeWidth !== stroke.size)) {
|
|
220
|
+
removeLiveStroke(stroke.id, context);
|
|
221
|
+
entry = void 0;
|
|
222
|
+
}
|
|
223
|
+
const item = entry ? liveStrokeItem(stroke, entry.template) : createLiveStroke(stroke, input);
|
|
224
|
+
if (!item) continue;
|
|
225
|
+
live.add(stroke.id);
|
|
226
|
+
entry = liveStrokes.get(stroke.id);
|
|
227
|
+
entry?.renderer.update(entry.container, item, context);
|
|
228
|
+
}
|
|
229
|
+
for (const id of liveStrokes.keys()) if (!live.has(id)) removeLiveStroke(id, context);
|
|
230
|
+
}
|
|
231
|
+
function liveStrokeItem(stroke, template) {
|
|
232
|
+
return {
|
|
233
|
+
...template,
|
|
234
|
+
id: stroke.id,
|
|
235
|
+
props: { points: stroke.points },
|
|
236
|
+
frame: {
|
|
237
|
+
...drawPointsBounds(stroke.points, stroke.size),
|
|
238
|
+
rotation: 0
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function createLiveStroke(stroke, input) {
|
|
243
|
+
const parsed = parseBoardItem({
|
|
244
|
+
type: "draw",
|
|
245
|
+
style: {
|
|
246
|
+
stroke: stroke.color,
|
|
247
|
+
strokeWidth: stroke.size
|
|
248
|
+
},
|
|
249
|
+
props: { points: stroke.points.slice(0, 1) }
|
|
250
|
+
});
|
|
251
|
+
if (!parsed.ok || parsed.item.type !== "draw") return null;
|
|
252
|
+
const template = parsed.item;
|
|
253
|
+
const item = liveStrokeItem(stroke, template);
|
|
254
|
+
const renderer = getRenderer(item, input.context);
|
|
255
|
+
const container = renderer.create(item, input.context);
|
|
256
|
+
container.zIndex = Z_LIVE_STROKE;
|
|
257
|
+
world.addChild(container);
|
|
258
|
+
liveStrokes.set(stroke.id, {
|
|
259
|
+
container,
|
|
260
|
+
renderer,
|
|
261
|
+
template
|
|
262
|
+
});
|
|
263
|
+
return item;
|
|
264
|
+
}
|
|
265
|
+
function removeLiveStroke(id, context) {
|
|
266
|
+
const entry = liveStrokes.get(id);
|
|
267
|
+
if (!entry) return;
|
|
268
|
+
liveStrokes.delete(id);
|
|
269
|
+
world.removeChild(entry.container);
|
|
270
|
+
entry.renderer.destroy?.(entry.container, context);
|
|
271
|
+
}
|
|
272
|
+
let visibleMemo = null;
|
|
273
|
+
function visibleFacts(input) {
|
|
274
|
+
const { items, visibleIds, structureVersion, context, getItem } = input;
|
|
275
|
+
if (visibleMemo?.ids === visibleIds && visibleMemo.structureVersion === structureVersion) return visibleMemo;
|
|
276
|
+
const orderedIds = visibleIds === null ? items.map((item) => item.id) : [...visibleIds].sort((a, b) => (orderById.get(a) ?? 0) - (orderById.get(b) ?? 0));
|
|
277
|
+
const unbatched = /* @__PURE__ */ new Set();
|
|
278
|
+
for (const id of orderedIds) {
|
|
279
|
+
const item = getItem(id);
|
|
280
|
+
if (item && !getRenderer(item, context).renderFar) unbatched.add(id);
|
|
281
|
+
}
|
|
282
|
+
visibleMemo = {
|
|
283
|
+
ids: visibleIds,
|
|
284
|
+
structureVersion,
|
|
285
|
+
signature: visibleIds === null ? 0 : idSetSignature(visibleIds),
|
|
286
|
+
orderedIds,
|
|
287
|
+
unbatched
|
|
288
|
+
};
|
|
289
|
+
return visibleMemo;
|
|
290
|
+
}
|
|
291
|
+
function sync(input) {
|
|
292
|
+
const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
|
|
293
|
+
const structureChanged = structureVersion !== lastStructureVersion;
|
|
294
|
+
if (structureChanged) {
|
|
295
|
+
lastStructureVersion = structureVersion;
|
|
296
|
+
const liveIds = new Set(items.map((item) => item.id));
|
|
297
|
+
for (const [id, entry] of [...cards]) if (!liveIds.has(id)) recycle(id, entry, context, false);
|
|
298
|
+
orderById = new Map(items.map((item, index) => [item.id, index]));
|
|
299
|
+
}
|
|
300
|
+
const visibleCount = visibleIds === null ? items.length : visibleIds.size;
|
|
301
|
+
const nextFarActive = farActive ? visibleCount > FAR_LAYER_EXIT : visibleCount > FAR_LAYER_ENTER;
|
|
302
|
+
const farModeChanged = nextFarActive !== farActive;
|
|
303
|
+
farActive = nextFarActive;
|
|
304
|
+
farFront.visible = farActive;
|
|
305
|
+
if (farActive) {
|
|
306
|
+
farInput = input;
|
|
307
|
+
const nextFarSig = [
|
|
308
|
+
structureVersion,
|
|
309
|
+
geometryVersion,
|
|
310
|
+
globalSig,
|
|
311
|
+
idSetSignature(pinnedIds),
|
|
312
|
+
visibleIds === null ? "all" : visibleFacts(input).signature
|
|
313
|
+
].join("|");
|
|
314
|
+
if (farSig === null || nextFarSig !== farSig && !gestureActive) requestFarPass(nextFarSig);
|
|
315
|
+
} else if (farModeChanged) resetFarLayer();
|
|
316
|
+
const wanted = new Set(pinnedIds);
|
|
317
|
+
if (farActive) {
|
|
318
|
+
const { orderedIds, unbatched } = visibleFacts(input);
|
|
319
|
+
for (const id of unbatched) wanted.add(id);
|
|
320
|
+
let budget = FAR_LIVE_LIMIT;
|
|
321
|
+
for (const id of orderedIds) {
|
|
322
|
+
if (budget === 0) break;
|
|
323
|
+
if (farCovered.has(id) || wanted.has(id)) continue;
|
|
324
|
+
wanted.add(id);
|
|
325
|
+
budget -= 1;
|
|
326
|
+
}
|
|
327
|
+
} else if (visibleIds === null) for (const item of items) wanted.add(item.id);
|
|
328
|
+
else for (const id of visibleIds) wanted.add(id);
|
|
329
|
+
let liveSetChanged = false;
|
|
330
|
+
for (const [id, entry] of [...cards]) if (!wanted.has(id)) {
|
|
331
|
+
recycle(id, entry, context, false);
|
|
332
|
+
liveSetChanged = true;
|
|
333
|
+
}
|
|
334
|
+
for (const id of wanted) {
|
|
335
|
+
const item = getItem(id);
|
|
336
|
+
if (!item) continue;
|
|
337
|
+
let entry = cards.get(id);
|
|
338
|
+
const renderer = getRenderer(item, context);
|
|
339
|
+
if (entry && entry.renderer.id !== renderer.id) {
|
|
340
|
+
world.removeChild(entry.container);
|
|
341
|
+
entry.renderer.destroy?.(entry.container, context);
|
|
342
|
+
recycle(id, entry, context, true);
|
|
343
|
+
entry = void 0;
|
|
344
|
+
}
|
|
345
|
+
if (!entry) {
|
|
346
|
+
materialize(item, context, globalSig, scene);
|
|
347
|
+
liveSetChanged = true;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const selected = context.selectedIds.has(id);
|
|
351
|
+
const hovered = context.hoveredId === id;
|
|
352
|
+
const resizing = context.resizingIds.has(id);
|
|
353
|
+
const changed = item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
|
|
354
|
+
entry.item = item;
|
|
355
|
+
entry.selected = selected;
|
|
356
|
+
entry.hovered = hovered;
|
|
357
|
+
entry.resizing = resizing;
|
|
358
|
+
entry.globalSig = globalSig;
|
|
359
|
+
setHeldKey(context, id, context.assetKey(item));
|
|
360
|
+
if (changed) {
|
|
361
|
+
entry.renderer.update(entry.container, item, context);
|
|
362
|
+
entry.container.alpha = scene.opacity(id);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
366
|
+
if (structureChanged || farModeChanged || liveSetChanged) applyChildOrder();
|
|
367
|
+
syncLiveStrokes(input);
|
|
368
|
+
}
|
|
369
|
+
function applyChildOrder() {
|
|
370
|
+
for (const [id, entry] of cards) entry.container.zIndex = orderById.get(id) ?? 0;
|
|
371
|
+
world.sortableChildren = true;
|
|
372
|
+
world.sortChildren();
|
|
373
|
+
}
|
|
374
|
+
function drawOverlay(input, palette) {
|
|
375
|
+
overlay.clear();
|
|
376
|
+
const { zoom, pointerType, marquee, selection, transform, controls, hoveredControl, rotationPointer, arrowEndpoints, ports, hoveredPort, arrowDraft, bindTarget } = input;
|
|
377
|
+
const inv = 1 / zoom;
|
|
378
|
+
const brand = palette.brand;
|
|
379
|
+
if (marquee) {
|
|
380
|
+
overlay.rect(marquee.x, marquee.y, marquee.width, marquee.height).fill({
|
|
381
|
+
color: brand,
|
|
382
|
+
alpha: .08
|
|
383
|
+
});
|
|
384
|
+
overlay.rect(marquee.x, marquee.y, marquee.width, marquee.height).stroke({
|
|
385
|
+
color: brand,
|
|
386
|
+
width: inv,
|
|
387
|
+
alpha: .7
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (bindTarget) traceFrame(overlay, bindTarget).stroke({
|
|
391
|
+
color: brand,
|
|
392
|
+
width: 2 * inv,
|
|
393
|
+
alpha: .9
|
|
394
|
+
});
|
|
395
|
+
if (arrowDraft) {
|
|
396
|
+
const { from, to, size, targetFrame } = arrowDraft;
|
|
397
|
+
if (targetFrame) traceFrame(overlay, targetFrame).stroke({
|
|
398
|
+
color: brand,
|
|
399
|
+
width: 2 * inv,
|
|
400
|
+
alpha: .9
|
|
401
|
+
});
|
|
402
|
+
overlay.moveTo(from.x, from.y).lineTo(to.x, to.y).stroke({
|
|
403
|
+
color: brand,
|
|
404
|
+
width: Math.max(size, 1.5) * inv,
|
|
405
|
+
alpha: .85,
|
|
406
|
+
cap: "round"
|
|
407
|
+
});
|
|
408
|
+
overlay.circle(to.x, to.y, 3.5 * inv).fill({
|
|
409
|
+
color: brand,
|
|
410
|
+
alpha: .95
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (ports && ports.length > 0) for (const port of ports) {
|
|
414
|
+
const hovered = hoveredPort && Math.abs(hoveredPort.x - port.x) < .001 && Math.abs(hoveredPort.y - port.y) < .001;
|
|
415
|
+
const r = (hovered ? port.radius * 1.5 : port.radius) * inv;
|
|
416
|
+
overlay.circle(port.x, port.y, r).fill({
|
|
417
|
+
color: palette.surface,
|
|
418
|
+
alpha: 1
|
|
419
|
+
}).stroke({
|
|
420
|
+
color: brand,
|
|
421
|
+
width: 1.5 * inv,
|
|
422
|
+
alpha: hovered ? 1 : .8
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (!transform || selection.length === 0) return;
|
|
426
|
+
const source = transform.frame;
|
|
427
|
+
traceFrame(overlay, source).stroke({
|
|
428
|
+
color: brand,
|
|
429
|
+
width: 1.5 * inv,
|
|
430
|
+
alpha: .95
|
|
431
|
+
});
|
|
432
|
+
if (!controls) return;
|
|
433
|
+
if (arrowEndpoints && arrowEndpoints.length > 0) {
|
|
434
|
+
const r = 5 * inv;
|
|
435
|
+
for (const point of arrowEndpoints) overlay.circle(point.x, point.y, r).fill({
|
|
436
|
+
color: palette.surface,
|
|
437
|
+
alpha: 1
|
|
438
|
+
}).stroke({
|
|
439
|
+
color: brand,
|
|
440
|
+
width: 1.5 * inv
|
|
441
|
+
});
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (hoveredControl?.kind === "resize" && transform.resizeMode === "free") {
|
|
445
|
+
const edge = frameEdgePoints(source, hoveredControl.handle);
|
|
446
|
+
if (edge) overlay.moveTo(edge[0].x, edge[0].y).lineTo(edge[1].x, edge[1].y).stroke({
|
|
447
|
+
color: brand,
|
|
448
|
+
width: 2.5 * inv,
|
|
449
|
+
alpha: 1
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
if (transform.resizeMode !== "none") for (const handle of CORNER_RESIZE_HANDLES) {
|
|
453
|
+
const position = frameHandlePosition(source, handle);
|
|
454
|
+
const hovered = hoveredControl?.kind === "resize" && hoveredControl.handle === handle;
|
|
455
|
+
overlay.circle(position.x, position.y, (hovered ? 5 : 4) * inv).fill({
|
|
456
|
+
color: hovered ? brand : palette.surface,
|
|
457
|
+
alpha: 1
|
|
458
|
+
}).stroke({
|
|
459
|
+
color: brand,
|
|
460
|
+
width: 1.5 * inv
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (transform.canRotate && (pointerType === "touch" || transform.resizeMode === "none")) {
|
|
464
|
+
const rotation = rotationPointer ?? rotationHandlePosition(source, zoom);
|
|
465
|
+
const anchor = rotationPointer ? frameRayIntersection(source, rotationPointer) : rotationHandleAnchor(source);
|
|
466
|
+
const hovered = rotationPointer !== null || hoveredControl?.kind === "rotate";
|
|
467
|
+
overlay.moveTo(anchor.x, anchor.y).lineTo(rotation.x, rotation.y).stroke({
|
|
468
|
+
color: brand,
|
|
469
|
+
width: inv,
|
|
470
|
+
alpha: .8
|
|
471
|
+
});
|
|
472
|
+
overlay.circle(rotation.x, rotation.y, (hovered ? 6 : 5) * inv).fill({ color: hovered ? brand : palette.surface }).stroke({
|
|
473
|
+
color: brand,
|
|
474
|
+
width: 1.5 * inv
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function destroy(context) {
|
|
479
|
+
for (const entry of cards.values()) entry.renderer.destroy?.(entry.container, context);
|
|
480
|
+
cards.clear();
|
|
481
|
+
for (const pool of pools.values()) for (const container of pool) container.destroy({ children: true });
|
|
482
|
+
pools.clear();
|
|
483
|
+
for (const key of heldKeys.values()) context.releaseTexture(key);
|
|
484
|
+
heldKeys.clear();
|
|
485
|
+
orderById = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const entry of clips.values()) entry.group.destroy({ children: true });
|
|
487
|
+
clips.clear();
|
|
488
|
+
animatedIds.clear();
|
|
489
|
+
for (const id of [...liveStrokes.keys()]) removeLiveStroke(id, context);
|
|
490
|
+
resetFarLayer();
|
|
491
|
+
farActive = false;
|
|
492
|
+
visibleMemo = null;
|
|
493
|
+
lastStructureVersion = -1;
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
sync,
|
|
497
|
+
getNode: (itemId) => {
|
|
498
|
+
const entry = cards.get(itemId);
|
|
499
|
+
return entry ? {
|
|
500
|
+
item: entry.item,
|
|
501
|
+
container: entry.container
|
|
502
|
+
} : null;
|
|
503
|
+
},
|
|
504
|
+
get animated() {
|
|
505
|
+
return animatedIds.size > 0;
|
|
506
|
+
},
|
|
507
|
+
drawOverlay,
|
|
508
|
+
destroy
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
export { createBoardScene };
|