@neta-art/cohub 8.21.1 → 8.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/core/connections.d.ts +18 -1
- package/dist/board/core/connections.js +43 -2
- package/dist/board/export/scene.js +2 -1
- package/dist/board/geometry.d.ts +3 -1
- package/dist/board/geometry.js +16 -1
- package/dist/board/index.d.ts +3 -3
- package/dist/board/index.js +3 -3
- package/dist/board/render/connection-layer.d.ts +6 -1
- package/dist/board/render/connection-layer.js +274 -122
- package/dist/chunks/environment.d.ts +157 -60
- package/dist/chunks/http.d.ts +1 -6
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +48 -14
- package/package.json +1 -1
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { rectsIntersect } from "../geometry.js";
|
|
3
|
+
import { connectionArrowheads, createConnectionGeometryCache } from "../core/connections.js";
|
|
3
4
|
import { pickBoardColor } from "../core/palette.js";
|
|
4
|
-
import { syncTextResolution, textResolutionForZoom } from "./text-resolution.js";
|
|
5
|
+
import { syncTextResolution, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
|
|
5
6
|
import { Container, Graphics, Text } from "pixi.js";
|
|
6
7
|
//#region src/board/render/connection-layer.ts
|
|
7
8
|
/**
|
|
8
9
|
* Connection drawing.
|
|
9
10
|
*
|
|
10
|
-
* Connections render into
|
|
11
|
+
* Connections render into shared layers beneath the cards rather than as a
|
|
11
12
|
* container per relation. A connection is a thin stroke with no texture and no
|
|
12
13
|
* interactive chrome of its own, so a per-connection container would add a
|
|
13
14
|
* transform, a render group and a draw call each for geometry that batches
|
|
14
15
|
* perfectly — on a densely connected board that is the difference between a few
|
|
15
16
|
* draw calls and a few thousand.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Tessellation is the real cost, so both layers are cached: `base` holds idle
|
|
19
|
+
* relations, `live` those touching manipulated, selected or hovered nodes.
|
|
20
|
+
* Details too small to read degrade to plain strokes; the document never changes.
|
|
21
|
+
*
|
|
22
|
+
* Labels are the exception to batching: text needs its own object to rasterise,
|
|
23
|
+
* so a `Text` is materialised only for visible connections that carry one and is
|
|
24
|
+
* pooled by connection id.
|
|
20
25
|
*/
|
|
21
26
|
/** Arrowhead length relative to stroke width, and its floor in world units. */
|
|
22
27
|
const HEAD_SCALE = 5.5;
|
|
@@ -25,85 +30,203 @@ const HEAD_SPREAD = Math.PI / 6;
|
|
|
25
30
|
/** Dash pattern for `dashed` connections, in world units. */
|
|
26
31
|
const DASH_LENGTH = 10;
|
|
27
32
|
const DASH_GAP = 7;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const LABEL_FONT_SIZE = 12;
|
|
34
|
+
/** Minimum on-screen sizes before a detail is drawn. */
|
|
35
|
+
const MIN_DASH_PX = 4;
|
|
36
|
+
const MIN_HEAD_PX = 3;
|
|
37
|
+
const MIN_LABEL_PX = 5;
|
|
38
|
+
const MIN_SEGMENT_PX = 2;
|
|
39
|
+
const MIN_ROUND_PX = 2;
|
|
40
|
+
/** Dashes per frame; past it all dashed relations draw solid (with hysteresis). */
|
|
41
|
+
const DASH_BUDGET = 4e4;
|
|
42
|
+
const DASH_RESUME_RATIO = .8;
|
|
43
|
+
function detailForZoom(zoom) {
|
|
44
|
+
const bucket = 2 ** (Math.floor(Math.log2(Math.max(zoom, 1e-4)) * 2) / 2);
|
|
45
|
+
return {
|
|
46
|
+
zoom: bucket,
|
|
47
|
+
dashes: DASH_LENGTH * bucket >= MIN_DASH_PX,
|
|
48
|
+
labels: LABEL_FONT_SIZE * bucket >= MIN_LABEL_PX,
|
|
49
|
+
minWidth: 1 / bucket,
|
|
50
|
+
minStep: MIN_SEGMENT_PX / bucket
|
|
51
|
+
};
|
|
37
52
|
}
|
|
38
|
-
/**
|
|
39
|
-
function
|
|
53
|
+
/** Visit each dash piece inside `clip`; `start` marks the first piece of a dash. */
|
|
54
|
+
function walkDashes(path, clip, visit) {
|
|
40
55
|
let penDown = true;
|
|
41
56
|
let remaining = DASH_LENGTH;
|
|
42
|
-
let
|
|
43
|
-
if (!current) return;
|
|
44
|
-
graphics.moveTo(current.x, current.y);
|
|
57
|
+
let open = false;
|
|
45
58
|
for (let index = 1; index < path.length; index += 1) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const a = path[index - 1];
|
|
60
|
+
const b = path[index];
|
|
61
|
+
if (!a || !b) continue;
|
|
62
|
+
const dx = b.x - a.x;
|
|
63
|
+
const dy = b.y - a.y;
|
|
64
|
+
const length = Math.hypot(dx, dy);
|
|
65
|
+
let travelled = 0;
|
|
66
|
+
while (length - travelled > 1e-4) {
|
|
67
|
+
const step = Math.min(length - travelled, remaining);
|
|
68
|
+
if (penDown) {
|
|
69
|
+
const t0 = travelled / length;
|
|
70
|
+
const t1 = (travelled + step) / length;
|
|
71
|
+
const x0 = a.x + dx * t0;
|
|
72
|
+
const y0 = a.y + dy * t0;
|
|
73
|
+
const x1 = a.x + dx * t1;
|
|
74
|
+
const y1 = a.y + dy * t1;
|
|
75
|
+
if (!clip || Math.max(x0, x1) >= clip.x && Math.min(x0, x1) <= clip.x + clip.width && Math.max(y0, y1) >= clip.y && Math.min(y0, y1) <= clip.y + clip.height) {
|
|
76
|
+
visit(x0, y0, x1, y1, !open);
|
|
77
|
+
open = true;
|
|
78
|
+
} else open = false;
|
|
79
|
+
}
|
|
80
|
+
travelled += step;
|
|
59
81
|
remaining -= step;
|
|
60
|
-
segmentRemaining -= step;
|
|
61
|
-
from = to;
|
|
62
82
|
if (remaining <= 1e-4) {
|
|
63
83
|
penDown = !penDown;
|
|
64
84
|
remaining = penDown ? DASH_LENGTH : DASH_GAP;
|
|
85
|
+
open = false;
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
|
-
current = next;
|
|
68
88
|
}
|
|
69
89
|
}
|
|
90
|
+
function countDashes(entries, clip) {
|
|
91
|
+
let count = 0;
|
|
92
|
+
for (const { connection, resolved } of entries) {
|
|
93
|
+
if (connection.style.line !== "dashed") continue;
|
|
94
|
+
walkDashes(resolved.path, clip, (_x0, _y0, _x1, _y1, start) => {
|
|
95
|
+
if (start) count += 1;
|
|
96
|
+
});
|
|
97
|
+
if (count > DASH_BUDGET) break;
|
|
98
|
+
}
|
|
99
|
+
return count;
|
|
100
|
+
}
|
|
101
|
+
function traceSolid(graphics, path, minStep) {
|
|
102
|
+
const first = path[0];
|
|
103
|
+
const last = path[path.length - 1];
|
|
104
|
+
if (!first || !last) return;
|
|
105
|
+
graphics.moveTo(first.x, first.y);
|
|
106
|
+
let previous = first;
|
|
107
|
+
for (let index = 1; index < path.length - 1; index += 1) {
|
|
108
|
+
const point = path[index];
|
|
109
|
+
if (!point) continue;
|
|
110
|
+
if (Math.abs(point.x - previous.x) + Math.abs(point.y - previous.y) < minStep) continue;
|
|
111
|
+
graphics.lineTo(point.x, point.y);
|
|
112
|
+
previous = point;
|
|
113
|
+
}
|
|
114
|
+
graphics.lineTo(last.x, last.y);
|
|
115
|
+
}
|
|
116
|
+
/** Trace an open chevron arrowhead aimed along `angle`. */
|
|
117
|
+
function traceArrowhead(graphics, tip, angle, size) {
|
|
118
|
+
graphics.moveTo(tip.x - size * Math.cos(angle - HEAD_SPREAD), tip.y - size * Math.sin(angle - HEAD_SPREAD)).lineTo(tip.x, tip.y).lineTo(tip.x - size * Math.cos(angle + HEAD_SPREAD), tip.y - size * Math.sin(angle + HEAD_SPREAD));
|
|
119
|
+
}
|
|
120
|
+
/** Stroke each style once instead of once per relation. */
|
|
121
|
+
function createStrokeBatch(graphics) {
|
|
122
|
+
const groups = /* @__PURE__ */ new Map();
|
|
123
|
+
return {
|
|
124
|
+
add(style, trace) {
|
|
125
|
+
const key = `${style.color}|${style.width}|${style.alpha}|${style.cap}|${style.join}`;
|
|
126
|
+
const group = groups.get(key);
|
|
127
|
+
if (group) group.traces.push(trace);
|
|
128
|
+
else groups.set(key, {
|
|
129
|
+
style,
|
|
130
|
+
traces: [trace]
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
flush() {
|
|
134
|
+
for (const { style, traces } of groups.values()) {
|
|
135
|
+
for (const trace of traces) trace();
|
|
136
|
+
graphics.stroke(style);
|
|
137
|
+
}
|
|
138
|
+
groups.clear();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function drawConnections(graphics, entries, states, input, detail, clip, dashes) {
|
|
143
|
+
const batch = createStrokeBatch(graphics);
|
|
144
|
+
entries.forEach(({ connection, resolved }, index) => {
|
|
145
|
+
const state = states[index] ?? "idle";
|
|
146
|
+
const color = pickBoardColor(input.colors, connection.style.color, input.colorScheme);
|
|
147
|
+
const width = Math.max(connection.style.size + (state === "selected" ? 1 : 0), detail.minWidth);
|
|
148
|
+
const round = width * detail.zoom >= MIN_ROUND_PX;
|
|
149
|
+
const alpha = state === "idle" ? .9 : 1;
|
|
150
|
+
const path = resolved.path;
|
|
151
|
+
const dashed = dashes && connection.style.line === "dashed";
|
|
152
|
+
batch.add({
|
|
153
|
+
color: color.stroke,
|
|
154
|
+
width,
|
|
155
|
+
alpha,
|
|
156
|
+
cap: dashed || !round ? "butt" : "round",
|
|
157
|
+
join: round ? "round" : "miter"
|
|
158
|
+
}, dashed ? () => walkDashes(path, clip, (x0, y0, x1, y1, start) => {
|
|
159
|
+
if (start) graphics.moveTo(x0, y0);
|
|
160
|
+
graphics.lineTo(x1, y1);
|
|
161
|
+
}) : () => traceSolid(graphics, path, detail.minStep));
|
|
162
|
+
const headSize = Math.max(HEAD_MIN, connection.style.size * HEAD_SCALE);
|
|
163
|
+
if (headSize * detail.zoom < MIN_HEAD_PX) return;
|
|
164
|
+
const heads = connectionArrowheads(connection);
|
|
165
|
+
const headStyle = {
|
|
166
|
+
color: color.stroke,
|
|
167
|
+
width,
|
|
168
|
+
alpha,
|
|
169
|
+
cap: round ? "round" : "butt",
|
|
170
|
+
join: round ? "round" : "miter"
|
|
171
|
+
};
|
|
172
|
+
const tip = path[path.length - 1];
|
|
173
|
+
const beforeTip = path[path.length - 2];
|
|
174
|
+
if (heads.atTarget && tip && beforeTip) {
|
|
175
|
+
const angle = Math.atan2(tip.y - beforeTip.y, tip.x - beforeTip.x);
|
|
176
|
+
batch.add(headStyle, () => traceArrowhead(graphics, tip, angle, headSize));
|
|
177
|
+
}
|
|
178
|
+
const origin = path[0];
|
|
179
|
+
const afterOrigin = path[1];
|
|
180
|
+
if (heads.atSource && origin && afterOrigin) {
|
|
181
|
+
const angle = Math.atan2(origin.y - afterOrigin.y, origin.x - afterOrigin.x);
|
|
182
|
+
batch.add(headStyle, () => traceArrowhead(graphics, origin, angle, headSize));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
batch.flush();
|
|
186
|
+
}
|
|
187
|
+
function isCurrent(drawn, entries, states, view, colors) {
|
|
188
|
+
return drawn !== null && drawn.view === view && drawn.colors === colors && sameItems(drawn.entries, entries) && sameItems(drawn.states, states);
|
|
189
|
+
}
|
|
190
|
+
function sameItems(a, b) {
|
|
191
|
+
if (a.length !== b.length) return false;
|
|
192
|
+
for (let index = 0; index < a.length; index += 1) if (a[index] !== b[index]) return false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
70
195
|
function createConnectionLayer(options) {
|
|
71
|
-
let
|
|
72
|
-
|
|
196
|
+
let layers = null;
|
|
197
|
+
const geometry = createConnectionGeometryCache();
|
|
73
198
|
const labels = /* @__PURE__ */ new Map();
|
|
74
|
-
let
|
|
199
|
+
let lastConnections = null;
|
|
200
|
+
let drawnBase = null;
|
|
201
|
+
let drawnLive = null;
|
|
202
|
+
let withinBudget = true;
|
|
75
203
|
function ensureAttached() {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
204
|
+
if (layers) return layers;
|
|
205
|
+
const next = {
|
|
206
|
+
base: new Graphics({ label: "board-connections" }),
|
|
207
|
+
live: new Graphics({ label: "board-connections-live" }),
|
|
208
|
+
labels: new Container({ label: "board-connection-labels" })
|
|
79
209
|
};
|
|
80
|
-
const nextGraphics = new Graphics({ label: "board-connections" });
|
|
81
|
-
const nextLabels = new Container({ label: "board-connection-labels" });
|
|
82
210
|
if (options.zIndex !== void 0) {
|
|
83
|
-
|
|
84
|
-
|
|
211
|
+
next.base.zIndex = options.zIndex;
|
|
212
|
+
next.live.zIndex = options.zIndex;
|
|
213
|
+
next.labels.zIndex = options.zIndex;
|
|
85
214
|
}
|
|
86
|
-
options.parent.addChild(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
graphics: nextGraphics,
|
|
91
|
-
labelLayer: nextLabels
|
|
92
|
-
};
|
|
215
|
+
options.parent.addChild(next.base, next.live, next.labels);
|
|
216
|
+
layers = next;
|
|
217
|
+
return next;
|
|
93
218
|
}
|
|
94
219
|
function releaseLabel(connectionId) {
|
|
95
220
|
const entry = labels.get(connectionId);
|
|
96
221
|
if (!entry) return;
|
|
97
|
-
|
|
222
|
+
layers?.labels.removeChild(entry.text);
|
|
98
223
|
entry.text.destroy();
|
|
99
224
|
labels.delete(connectionId);
|
|
100
225
|
}
|
|
101
|
-
function syncLabel(connection, resolved, input,
|
|
226
|
+
function syncLabel({ connection, resolved }, input, host) {
|
|
102
227
|
const value = connection.label.trim();
|
|
103
|
-
if (!value)
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
228
|
+
if (!value) return false;
|
|
229
|
+
const color = pickBoardColor(input.colors, connection.style.color, input.colorScheme).label;
|
|
107
230
|
let entry = labels.get(connection.id);
|
|
108
231
|
if (!entry) {
|
|
109
232
|
const resolution = textResolutionForZoom(input.zoom);
|
|
@@ -112,7 +235,7 @@ function createConnectionLayer(options) {
|
|
|
112
235
|
style: {
|
|
113
236
|
fill: color,
|
|
114
237
|
fontFamily: BOARD_FONT_STACK,
|
|
115
|
-
fontSize:
|
|
238
|
+
fontSize: LABEL_FONT_SIZE,
|
|
116
239
|
fontWeight: "500"
|
|
117
240
|
},
|
|
118
241
|
resolution,
|
|
@@ -135,82 +258,111 @@ function createConnectionLayer(options) {
|
|
|
135
258
|
entry.text.style.fill = color;
|
|
136
259
|
}
|
|
137
260
|
entry.text.position.set(resolved.mid.x, resolved.mid.y);
|
|
261
|
+
return true;
|
|
138
262
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
263
|
+
function drawLayer(graphics, layer, input, detail, clip) {
|
|
264
|
+
graphics.clear();
|
|
265
|
+
drawConnections(graphics, layer.entries, layer.states, input, detail, clip, layer.dashes);
|
|
266
|
+
const labelIds = /* @__PURE__ */ new Set();
|
|
267
|
+
if (detail.labels && layers) {
|
|
268
|
+
for (const entry of layer.entries) if (syncLabel(entry, input, layers.labels)) labelIds.add(entry.connection.id);
|
|
143
269
|
}
|
|
270
|
+
return {
|
|
271
|
+
...layer,
|
|
272
|
+
labelIds
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function sync(input) {
|
|
276
|
+
if (input.connections.length === 0 && !layers) return;
|
|
144
277
|
const host = ensureAttached();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
278
|
+
if (input.connections !== lastConnections) {
|
|
279
|
+
geometry.retain(input.connections);
|
|
280
|
+
lastConnections = input.connections;
|
|
281
|
+
}
|
|
282
|
+
const detail = detailForZoom(input.zoom);
|
|
283
|
+
const cull = input.cullRect ?? null;
|
|
284
|
+
const clip = detail.dashes ? cull : null;
|
|
285
|
+
const selected = input.selectedIds;
|
|
286
|
+
const liveNodes = input.liveNodeIds;
|
|
287
|
+
const baseEntries = [];
|
|
288
|
+
const baseStates = [];
|
|
289
|
+
const liveEntries = [];
|
|
290
|
+
const liveStates = [];
|
|
291
|
+
let hovered = null;
|
|
150
292
|
for (const connection of input.connections) {
|
|
151
|
-
const
|
|
152
|
-
if (!
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
else {
|
|
165
|
-
const first = resolved.path[0];
|
|
166
|
-
if (!first) continue;
|
|
167
|
-
host.graphics.moveTo(first.x, first.y);
|
|
168
|
-
for (let index = 1; index < resolved.path.length; index += 1) {
|
|
169
|
-
const point = resolved.path[index];
|
|
170
|
-
if (point) host.graphics.lineTo(point.x, point.y);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
host.graphics.stroke({
|
|
174
|
-
color: color.stroke,
|
|
175
|
-
width,
|
|
176
|
-
alpha,
|
|
177
|
-
cap: "round",
|
|
178
|
-
join: "round"
|
|
179
|
-
});
|
|
180
|
-
const heads = connectionArrowheads(connection);
|
|
181
|
-
const headSize = Math.max(HEAD_MIN, connection.style.size * HEAD_SCALE);
|
|
182
|
-
if (heads.atTarget) {
|
|
183
|
-
const tip = resolved.path[resolved.path.length - 1];
|
|
184
|
-
const previous = resolved.path[resolved.path.length - 2];
|
|
185
|
-
if (tip && previous) drawArrowhead(host.graphics, tip, Math.atan2(tip.y - previous.y, tip.x - previous.x), headSize, color.stroke, width, alpha);
|
|
293
|
+
const entry = geometry.get(connection, input.getFrame);
|
|
294
|
+
if (!entry || input.skipIds?.has(connection.id)) continue;
|
|
295
|
+
if (cull && !rectsIntersect(entry.bounds, cull)) continue;
|
|
296
|
+
if (selected?.has(connection.id)) {
|
|
297
|
+
liveEntries.push(entry);
|
|
298
|
+
liveStates.push("selected");
|
|
299
|
+
} else if (liveNodes?.has(connection.source.itemId) || liveNodes?.has(connection.target.itemId)) {
|
|
300
|
+
liveEntries.push(entry);
|
|
301
|
+
liveStates.push(connection.id === input.hoveredId ? "hovered" : "idle");
|
|
302
|
+
} else {
|
|
303
|
+
baseEntries.push(entry);
|
|
304
|
+
baseStates.push("idle");
|
|
305
|
+
if (connection.id === input.hoveredId) hovered = entry;
|
|
186
306
|
}
|
|
187
|
-
if (heads.atSource) {
|
|
188
|
-
const tip = resolved.path[0];
|
|
189
|
-
const next2 = resolved.path[1];
|
|
190
|
-
if (tip && next2) drawArrowhead(host.graphics, tip, Math.atan2(tip.y - next2.y, tip.x - next2.x), headSize, color.stroke, width, alpha);
|
|
191
|
-
}
|
|
192
|
-
syncLabel(connection, resolved, input, color.label, host.labelLayer);
|
|
193
307
|
}
|
|
194
|
-
|
|
195
|
-
|
|
308
|
+
if (hovered) {
|
|
309
|
+
liveEntries.push(hovered);
|
|
310
|
+
liveStates.push("hovered");
|
|
311
|
+
}
|
|
312
|
+
const view = `${detail.zoom}|${textZoomBucket(input.zoom)}|${input.colorScheme}|${clip ? `${clip.x},${clip.y},${clip.width},${clip.height}` : ""}`;
|
|
313
|
+
const baseCurrent = isCurrent(drawnBase, baseEntries, baseStates, view, input.colors);
|
|
314
|
+
const liveCurrent = isCurrent(drawnLive, liveEntries, liveStates, view, input.colors);
|
|
315
|
+
const countFor = (current, drawn, entries) => !detail.dashes ? 0 : current && drawn ? drawn.dashCount : countDashes(entries, clip);
|
|
316
|
+
const baseDashes = countFor(baseCurrent, drawnBase, baseEntries);
|
|
317
|
+
const liveDashes = countFor(liveCurrent, drawnLive, hovered ? liveEntries.slice(0, -1) : liveEntries);
|
|
318
|
+
const total = baseDashes + liveDashes;
|
|
319
|
+
withinBudget = withinBudget ? total <= DASH_BUDGET : total <= DASH_BUDGET * DASH_RESUME_RATIO;
|
|
320
|
+
const dashes = detail.dashes && withinBudget;
|
|
321
|
+
const redraw = (graphics, entries, states, dashCount) => drawLayer(graphics, {
|
|
322
|
+
entries,
|
|
323
|
+
states,
|
|
324
|
+
view,
|
|
325
|
+
colors: input.colors,
|
|
326
|
+
dashCount,
|
|
327
|
+
dashes
|
|
328
|
+
}, input, detail, clip);
|
|
329
|
+
let labelsChanged = false;
|
|
330
|
+
if (!baseCurrent || drawnBase?.dashes !== dashes) {
|
|
331
|
+
drawnBase = redraw(host.base, baseEntries, baseStates, baseDashes);
|
|
332
|
+
labelsChanged = true;
|
|
333
|
+
}
|
|
334
|
+
if (!liveCurrent || drawnLive?.dashes !== dashes) {
|
|
335
|
+
drawnLive = redraw(host.live, liveEntries, liveStates, liveDashes);
|
|
336
|
+
labelsChanged = true;
|
|
337
|
+
}
|
|
338
|
+
if (!labelsChanged) return;
|
|
339
|
+
const wanted = /* @__PURE__ */ new Set([...drawnBase?.labelIds ?? [], ...drawnLive?.labelIds ?? []]);
|
|
340
|
+
for (const connectionId of [...labels.keys()]) if (!wanted.has(connectionId)) releaseLabel(connectionId);
|
|
196
341
|
}
|
|
197
342
|
return {
|
|
198
343
|
sync,
|
|
199
|
-
resolved: (connectionId) =>
|
|
344
|
+
resolved: (connectionId) => geometry.peek(connectionId)?.resolved ?? null,
|
|
200
345
|
get children() {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
346
|
+
return layers ? [
|
|
347
|
+
layers.base,
|
|
348
|
+
layers.live,
|
|
349
|
+
layers.labels
|
|
350
|
+
] : [];
|
|
205
351
|
},
|
|
206
352
|
destroy: () => {
|
|
207
353
|
for (const entry of labels.values()) entry.text.destroy();
|
|
208
354
|
labels.clear();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
355
|
+
if (layers) {
|
|
356
|
+
layers.base.destroy();
|
|
357
|
+
layers.live.destroy();
|
|
358
|
+
layers.labels.destroy({ children: true });
|
|
359
|
+
}
|
|
360
|
+
layers = null;
|
|
361
|
+
drawnBase = null;
|
|
362
|
+
drawnLive = null;
|
|
363
|
+
withinBudget = true;
|
|
364
|
+
lastConnections = null;
|
|
365
|
+
geometry.clear();
|
|
214
366
|
}
|
|
215
367
|
};
|
|
216
368
|
}
|