@neta-art/cohub 12.0.0 → 12.1.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/export/index.js +1 -1
- package/dist/board/export/scene.js +2 -1
- package/dist/board/render/card-tag.d.ts +0 -1
- package/dist/board/render/card-tag.js +15 -17
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +6 -6
- package/dist/board/render/renderers/arrow-card-renderer.js +5 -7
- package/dist/board/render/renderers/audio-card-renderer.js +8 -7
- package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/base-card-renderer.js +2 -3
- package/dist/board/render/renderers/board-renderer-registry.d.ts +3 -1
- package/dist/board/render/renderers/custom-card-renderer.js +19 -17
- package/dist/board/render/renderers/file-card-renderer.js +10 -8
- package/dist/board/render/renderers/frame-card-renderer.js +5 -6
- package/dist/board/render/renderers/shape-card-renderer.js +5 -7
- package/dist/board/render/renderers/task-card-renderer.js +9 -9
- package/dist/board/render/renderers/text-card-renderer.js +5 -7
- package/dist/board/render/renderers/unknown-card-renderer.js +5 -7
- package/dist/board/render/text-resolution.d.ts +4 -7
- package/dist/board/render/text-resolution.js +20 -22
- package/dist/board/stage/canvas-size.d.ts +18 -0
- package/dist/board/stage/canvas-size.js +31 -0
- package/dist/board/stage/scene.d.ts +5 -1
- package/dist/board/stage/scene.js +35 -9
- package/dist/board/stage/stage.js +34 -13
- package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
- package/dist/board/stage/text-resolution-tracker.js +33 -0
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@ import { buildBoardScene } from "../model/scene.js";
|
|
|
2
2
|
import { resolveBoardAnimationCamera } from "../model/sampling.js";
|
|
3
3
|
import { normalizeBoardDocument, planBoardExport } from "../model/export-plan.js";
|
|
4
4
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
5
|
-
import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
|
|
6
5
|
import { defaultBoardPalette } from "../render/palette.js";
|
|
7
6
|
import { createBoardRenderSampler } from "../render/sampling.js";
|
|
7
|
+
import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
|
|
8
8
|
import { createBoardExportScene } from "./scene.js";
|
|
9
9
|
import { Rectangle, RendererType } from "pixi.js";
|
|
10
10
|
//#region src/board/export/index.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildFallbackShapeColors } from "../model/palette.js";
|
|
2
2
|
import { imageAssetKey } from "../model/image-key.js";
|
|
3
|
-
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
4
3
|
import { defaultBoardPalette } from "../render/palette.js";
|
|
4
|
+
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
5
5
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
6
6
|
import { resolveBoardRenderStyle } from "../render/style.js";
|
|
7
7
|
import { Container, Graphics, Sprite, TilingSprite } from "pixi.js";
|
|
@@ -29,6 +29,7 @@ function buildContext(input) {
|
|
|
29
29
|
themeKey: "export",
|
|
30
30
|
rendererType: "canvas",
|
|
31
31
|
zoom: input.scale,
|
|
32
|
+
textResolution: input.scale,
|
|
32
33
|
reducedMotion: true,
|
|
33
34
|
labels: DEFAULT_BOARD_RENDER_LABELS,
|
|
34
35
|
taskRun: () => null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fitTextToWidth } from "./text-fit.js";
|
|
2
|
-
import { syncTextResolution
|
|
2
|
+
import { syncTextResolution } from "./text-resolution.js";
|
|
3
3
|
import { Container, Graphics, Text } from "pixi.js";
|
|
4
4
|
//#region src/board/render/card-tag.ts
|
|
5
5
|
const CARD_TAG_SIZE = 11;
|
|
@@ -177,13 +177,15 @@ function createCardTag() {
|
|
|
177
177
|
icon,
|
|
178
178
|
text: null,
|
|
179
179
|
sig: "",
|
|
180
|
-
resolution: 0,
|
|
181
180
|
width: 0
|
|
182
181
|
};
|
|
183
182
|
}
|
|
183
|
+
function tagScale(zoom) {
|
|
184
|
+
return Math.min(MAX_GROWTH, Math.max(1, 1 / Math.max(zoom, .01)));
|
|
185
|
+
}
|
|
184
186
|
function zoomCardTag(tag, zoom) {
|
|
185
187
|
if (!tag.text || !tag.sig) return;
|
|
186
|
-
const scale =
|
|
188
|
+
const scale = tagScale(zoom);
|
|
187
189
|
const span = 17 + tag.text.width;
|
|
188
190
|
tag.root.scale.set(scale);
|
|
189
191
|
tag.root.position.set(0, -18 * scale);
|
|
@@ -197,7 +199,6 @@ function syncCardTag(tag, input, context) {
|
|
|
197
199
|
return;
|
|
198
200
|
}
|
|
199
201
|
if (!tag.text) {
|
|
200
|
-
tag.resolution = textResolutionForZoom(context.zoom);
|
|
201
202
|
tag.text = new Text({
|
|
202
203
|
text: "",
|
|
203
204
|
style: {
|
|
@@ -205,30 +206,27 @@ function syncCardTag(tag, input, context) {
|
|
|
205
206
|
fontSize: 11,
|
|
206
207
|
fontWeight: "500"
|
|
207
208
|
},
|
|
208
|
-
resolution: tag.resolution,
|
|
209
209
|
roundPixels: true
|
|
210
210
|
});
|
|
211
211
|
tag.root.addChild(tag.text);
|
|
212
212
|
}
|
|
213
|
-
syncTextResolution(tag.text, tag, context.zoom);
|
|
214
213
|
const sig = [
|
|
215
214
|
input.glyph,
|
|
216
215
|
input.text,
|
|
217
216
|
input.width,
|
|
218
217
|
context.themeKey
|
|
219
218
|
].join("|");
|
|
220
|
-
if (sig
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
if (sig !== tag.sig) {
|
|
220
|
+
tag.sig = sig;
|
|
221
|
+
const color = context.palette.muted;
|
|
222
|
+
const pixel = context.style.icons === "pixel";
|
|
223
|
+
drawCardGlyph(tag.icon.clear(), input.glyph, color, pixel);
|
|
224
|
+
tag.text.style.fill = color;
|
|
225
|
+
tag.text.style.fontFamily = context.style.font;
|
|
226
|
+
fitTextToWidth(tag.text, input.text, Math.max(1, input.width - ICON - GAP));
|
|
227
|
+
tag.text.position.set(17, (ICON - tag.text.height) / 2);
|
|
223
228
|
}
|
|
224
|
-
tag.
|
|
225
|
-
const color = context.palette.muted;
|
|
226
|
-
const pixel = context.style.icons === "pixel";
|
|
227
|
-
drawCardGlyph(tag.icon.clear(), input.glyph, color, pixel);
|
|
228
|
-
tag.text.style.fill = color;
|
|
229
|
-
tag.text.style.fontFamily = context.style.font;
|
|
230
|
-
fitTextToWidth(tag.text, input.text, Math.max(1, input.width - ICON - GAP));
|
|
231
|
-
tag.text.position.set(17, (ICON - tag.text.height) / 2);
|
|
229
|
+
syncTextResolution(tag.text, context.textResolution * tagScale(context.zoom));
|
|
232
230
|
zoomCardTag(tag, context.zoom);
|
|
233
231
|
}
|
|
234
232
|
//#endregion
|
|
@@ -15,6 +15,6 @@ import { CardCrop, CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevati
|
|
|
15
15
|
import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
|
|
16
16
|
import { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth } from "./text-fit.js";
|
|
17
17
|
import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
|
|
18
|
-
import { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
18
|
+
import { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket } from "./text-resolution.js";
|
|
19
19
|
import { VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
|
|
20
|
-
export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderLabels, BoardRenderPalette, BoardRenderStyle, BoardScriptHost, BoardScriptHostOptions, BoardSelectionStyle, BoardShadowStyle, BrowserBoardScriptHost, CARD_TAG_SIZE, CardCrop, CardElevation, CardGlyph, CardRect, CardSurface, CardTag, ClipGroup, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardCardDetailed, boardMediaActionAt, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth,
|
|
20
|
+
export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderLabels, BoardRenderPalette, BoardRenderStyle, BoardScriptHost, BoardScriptHostOptions, BoardSelectionStyle, BoardShadowStyle, BrowserBoardScriptHost, CARD_TAG_SIZE, CardCrop, CardElevation, CardGlyph, CardRect, CardSurface, CardTag, ClipGroup, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardCardDetailed, boardMediaActionAt, boardTextResolution, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
|
|
2
|
+
import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
|
|
3
|
+
import { createBoardRenderSampler } from "./sampling.js";
|
|
4
|
+
import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
|
|
5
|
+
import { inspectBoardItems, inspectBoardScene, measureBoardText } from "./diagnostics.js";
|
|
2
6
|
import { createBoardBackground, updateBoardBackground } from "./board-background.js";
|
|
3
7
|
import { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth } from "./text-fit.js";
|
|
4
|
-
import { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
8
|
+
import { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket } from "./text-resolution.js";
|
|
5
9
|
import { CARD_TAG_SIZE, createCardTag, drawCardGlyph, formatBoardDuration, syncCardTag, traceCube, zoomCardTag } from "./card-tag.js";
|
|
6
10
|
import { clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
|
|
7
|
-
import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
|
|
8
|
-
import { inspectBoardItems, inspectBoardScene, measureBoardText } from "./diagnostics.js";
|
|
9
|
-
import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
|
|
10
|
-
import { createBoardRenderSampler } from "./sampling.js";
|
|
11
11
|
import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
|
|
12
12
|
import { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect } from "./surface.js";
|
|
13
13
|
import { boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter } from "./media-interaction.js";
|
|
@@ -17,4 +17,4 @@ import { createBoardScriptHost } from "./script-host.js";
|
|
|
17
17
|
import { prepareBrowserBoardScripts } from "./script-export.js";
|
|
18
18
|
import { defaultBoardRenderStyle, resolveBoardRenderStyle } from "./style.js";
|
|
19
19
|
import { VIDEO_THUMBNAIL_MAX_EDGE, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
|
|
20
|
-
export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, CARD_TAG_SIZE, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, boardCardDetailed, boardMediaActionAt, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth,
|
|
20
|
+
export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, CARD_TAG_SIZE, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, boardCardDetailed, boardMediaActionAt, boardTextResolution, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
2
|
import { pathPointAt, resolveSceneArrow } from "../../model/arrow-geometry.js";
|
|
3
|
-
import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
|
|
4
3
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
5
5
|
import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "../stroke.js";
|
|
6
6
|
import { drawCardSurface } from "../surface.js";
|
|
7
7
|
import { drawFarStroke } from "./far-plate.js";
|
|
@@ -18,7 +18,6 @@ function sync(container, item, context) {
|
|
|
18
18
|
parts.line.alpha = itemAlpha(context, style.stroke);
|
|
19
19
|
const color = itemColor(context, style.stroke, "brand");
|
|
20
20
|
const size = style.strokeWidth ?? 2.5;
|
|
21
|
-
syncTextResolution(parts.label, parts, context.zoom);
|
|
22
21
|
const resolved = resolveSceneArrow(item, context.scene);
|
|
23
22
|
const path = trimPolyline(resolved.path, style.trim);
|
|
24
23
|
const lineSig = [
|
|
@@ -85,6 +84,7 @@ function sync(container, item, context) {
|
|
|
85
84
|
border: context.palette.border
|
|
86
85
|
});
|
|
87
86
|
}
|
|
87
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
88
88
|
const visible = props.label.length > 0 && (style.trim === void 0 || style.trim >= .5);
|
|
89
89
|
parts.label.visible = visible;
|
|
90
90
|
parts.backdrop.visible = visible;
|
|
@@ -98,7 +98,6 @@ const arrowCardRenderer = {
|
|
|
98
98
|
create: (item, context) => {
|
|
99
99
|
const root = new Container();
|
|
100
100
|
const line = new Graphics();
|
|
101
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
102
101
|
const label = new Text({
|
|
103
102
|
text: "",
|
|
104
103
|
style: {
|
|
@@ -107,7 +106,7 @@ const arrowCardRenderer = {
|
|
|
107
106
|
fontSize: 14,
|
|
108
107
|
fontWeight: "500"
|
|
109
108
|
},
|
|
110
|
-
resolution,
|
|
109
|
+
resolution: context.textResolution,
|
|
111
110
|
roundPixels: true
|
|
112
111
|
});
|
|
113
112
|
label.anchor.set(.5);
|
|
@@ -119,8 +118,7 @@ const arrowCardRenderer = {
|
|
|
119
118
|
backdrop,
|
|
120
119
|
label,
|
|
121
120
|
lineSig: "",
|
|
122
|
-
labelSig: ""
|
|
123
|
-
resolution
|
|
121
|
+
labelSig: ""
|
|
124
122
|
});
|
|
125
123
|
if (item.type === "arrow") sync(root, item, context);
|
|
126
124
|
return root;
|
|
@@ -130,7 +128,7 @@ const arrowCardRenderer = {
|
|
|
130
128
|
},
|
|
131
129
|
resolution: (container, _item, context) => {
|
|
132
130
|
const parts = partsByContainer.get(container);
|
|
133
|
-
if (parts) syncTextResolution(parts.label,
|
|
131
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
134
132
|
},
|
|
135
133
|
renderFar: (graphics, item, context) => {
|
|
136
134
|
if (item.type !== "arrow") return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getMediaResourceTitle } from "../../model/media.js";
|
|
2
|
-
import { drawAudioWaveform } from "../audio-waveform.js";
|
|
3
2
|
import { fitTextToWidth } from "../text-fit.js";
|
|
4
|
-
import { boardCardDetailed, syncTextResolution
|
|
3
|
+
import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
|
|
5
4
|
import { formatBoardDuration } from "../card-tag.js";
|
|
5
|
+
import { drawAudioWaveform } from "../audio-waveform.js";
|
|
6
6
|
import { cardElevation, drawCardSurface } from "../surface.js";
|
|
7
7
|
import { positionShell } from "./base-card-renderer.js";
|
|
8
8
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -25,12 +25,15 @@ function audioKeyCenter(width, height) {
|
|
|
25
25
|
function sync(container, item, context) {
|
|
26
26
|
const parts = partsByContainer.get(container);
|
|
27
27
|
if (!parts) return;
|
|
28
|
+
syncContent(parts, item, context);
|
|
29
|
+
syncTextResolution(parts.title, context.textResolution);
|
|
30
|
+
}
|
|
31
|
+
function syncContent(parts, item, context) {
|
|
28
32
|
positionShell(parts.root, item);
|
|
29
33
|
const { width, height } = item.frame;
|
|
30
34
|
const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
|
|
31
35
|
const durationMs = item.props.snapshot?.durationMs;
|
|
32
36
|
const detailed = boardCardDetailed(context.zoom);
|
|
33
|
-
syncTextResolution(parts.title, parts, context.zoom);
|
|
34
37
|
const elevation = cardElevation(context, item.id);
|
|
35
38
|
const sig = [
|
|
36
39
|
width,
|
|
@@ -83,14 +86,13 @@ const audioCardRenderer = {
|
|
|
83
86
|
const root = new Container();
|
|
84
87
|
const plate = new Graphics();
|
|
85
88
|
const wave = new Graphics();
|
|
86
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
87
89
|
const title = new Text({
|
|
88
90
|
text: "",
|
|
89
91
|
style: {
|
|
90
92
|
fontSize: 13,
|
|
91
93
|
fontWeight: "600"
|
|
92
94
|
},
|
|
93
|
-
resolution,
|
|
95
|
+
resolution: context.textResolution,
|
|
94
96
|
roundPixels: true
|
|
95
97
|
});
|
|
96
98
|
root.addChild(plate, wave, title);
|
|
@@ -99,8 +101,7 @@ const audioCardRenderer = {
|
|
|
99
101
|
plate,
|
|
100
102
|
wave,
|
|
101
103
|
title,
|
|
102
|
-
sig: ""
|
|
103
|
-
resolution
|
|
104
|
+
sig: ""
|
|
104
105
|
});
|
|
105
106
|
if (item.type === "audio") sync(root, item, context);
|
|
106
107
|
return root;
|
|
@@ -2,7 +2,7 @@ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
|
|
|
2
2
|
import "../../../protocol/dist/index.js";
|
|
3
3
|
import { Container, Text } from "pixi.js";
|
|
4
4
|
//#region src/board/render/renderers/base-card-renderer.d.ts
|
|
5
|
-
declare function createLabel(text: string, style: Record<string, unknown
|
|
5
|
+
declare function createLabel(text: string, style: Record<string, unknown>, resolution: number): Text;
|
|
6
6
|
declare function positionShell(root: Container, item: {
|
|
7
7
|
frame: BoardFrame;
|
|
8
8
|
}): void;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
-
import { getBoardResolution } from "../text-resolution.js";
|
|
3
2
|
import { Text } from "pixi.js";
|
|
4
3
|
//#region src/board/render/renderers/base-card-renderer.ts
|
|
5
|
-
function createLabel(text, style) {
|
|
4
|
+
function createLabel(text, style, resolution) {
|
|
6
5
|
return new Text({
|
|
7
|
-
resolution
|
|
6
|
+
resolution,
|
|
8
7
|
roundPixels: true,
|
|
9
8
|
text,
|
|
10
9
|
style: {
|
|
@@ -48,6 +48,8 @@ type BoardRenderContext = {
|
|
|
48
48
|
themeKey: string;
|
|
49
49
|
rendererType: "gpu" | "canvas";
|
|
50
50
|
zoom: number;
|
|
51
|
+
/** Texture pixels per world unit for text; see `boardTextResolution`. */
|
|
52
|
+
textResolution: number;
|
|
51
53
|
time: number;
|
|
52
54
|
reducedMotion: boolean;
|
|
53
55
|
labels: BoardRenderLabels;
|
|
@@ -73,7 +75,7 @@ type BoardCardRenderer = {
|
|
|
73
75
|
tick?: (container: Container, clock: number) => number | null;
|
|
74
76
|
create: (item: BoardSceneItem, context: BoardRenderContext) => Container;
|
|
75
77
|
update: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
|
|
76
|
-
/** Called
|
|
78
|
+
/** Called when the zoom bucket or text resolution changes. */
|
|
77
79
|
resolution?: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
|
|
78
80
|
/** Runs on every zoom step; only update transforms and visibility. */
|
|
79
81
|
zoom?: (container: Container, zoom: number) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
3
3
|
import { positionShell } from "./base-card-renderer.js";
|
|
4
4
|
import { drawFarPlate } from "./far-plate.js";
|
|
5
5
|
import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
|
|
@@ -13,7 +13,6 @@ function sync(container, item, context) {
|
|
|
13
13
|
parts.id = item.id;
|
|
14
14
|
}
|
|
15
15
|
positionShell(parts.root, item);
|
|
16
|
-
syncTextResolution(parts.label, parts.label, context.zoom);
|
|
17
16
|
const { width, height } = item.frame;
|
|
18
17
|
const texture = context.scripts?.frame(item, context.time) ?? null;
|
|
19
18
|
const error = context.scripts?.error(item.id) ?? null;
|
|
@@ -31,19 +30,21 @@ function sync(container, item, context) {
|
|
|
31
30
|
message,
|
|
32
31
|
context.themeKey
|
|
33
32
|
].join("|");
|
|
34
|
-
if (sig
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
if (sig !== parts.sig) {
|
|
34
|
+
parts.sig = sig;
|
|
35
|
+
parts.placeholder.clear();
|
|
36
|
+
if (!ready || error) parts.placeholder.rect(0, 0, width, height).stroke({
|
|
37
|
+
color: context.palette.border,
|
|
38
|
+
width: 1,
|
|
39
|
+
alpha: .8
|
|
40
|
+
});
|
|
41
|
+
parts.label.visible = !ready || Boolean(error);
|
|
42
|
+
parts.label.text = message;
|
|
43
|
+
parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
|
|
44
|
+
parts.label.style.wordWrapWidth = Math.max(40, width - 16);
|
|
45
|
+
parts.label.position.set(width / 2, height / 2);
|
|
46
|
+
}
|
|
47
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
47
48
|
}
|
|
48
49
|
const customCardRenderer = {
|
|
49
50
|
id: "custom-card",
|
|
@@ -62,7 +63,8 @@ const customCardRenderer = {
|
|
|
62
63
|
wordWrap: true,
|
|
63
64
|
breakWords: true
|
|
64
65
|
},
|
|
65
|
-
resolution:
|
|
66
|
+
resolution: context.textResolution,
|
|
67
|
+
roundPixels: true
|
|
66
68
|
});
|
|
67
69
|
label.anchor.set(.5);
|
|
68
70
|
root.addChild(sprite, placeholder, label);
|
|
@@ -82,7 +84,7 @@ const customCardRenderer = {
|
|
|
82
84
|
},
|
|
83
85
|
resolution: (container, _item, context) => {
|
|
84
86
|
const parts = partsByContainer.get(container);
|
|
85
|
-
if (parts) syncTextResolution(parts.label,
|
|
87
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
86
88
|
},
|
|
87
89
|
renderFar: (graphics, item, context) => {
|
|
88
90
|
drawFarPlate(graphics, item.frame, {
|
|
@@ -47,6 +47,14 @@ function syncClip(parts, rect, style) {
|
|
|
47
47
|
function sync(container, item, context) {
|
|
48
48
|
const parts = partsByContainer.get(container);
|
|
49
49
|
if (!parts) return;
|
|
50
|
+
syncContent(parts, item, context);
|
|
51
|
+
for (const text of [
|
|
52
|
+
parts.title,
|
|
53
|
+
parts.excerpt,
|
|
54
|
+
parts.typeMark
|
|
55
|
+
]) syncTextResolution(text, context.textResolution);
|
|
56
|
+
}
|
|
57
|
+
function syncContent(parts, item, context) {
|
|
50
58
|
positionShell(parts.root, item);
|
|
51
59
|
const { width, height } = item.frame;
|
|
52
60
|
const detail = detailFor(context.zoom);
|
|
@@ -64,9 +72,6 @@ function sync(container, item, context) {
|
|
|
64
72
|
const kind = filePreviewKind(item.props.snapshot);
|
|
65
73
|
const category = fileCategory(item.props.src, item.props.snapshot?.mimeType);
|
|
66
74
|
const accent = fileCategoryAccent(category, context.palette);
|
|
67
|
-
syncTextResolution(parts.title, parts.titleRes, context.zoom);
|
|
68
|
-
syncTextResolution(parts.excerpt, parts.excerptRes, context.zoom);
|
|
69
|
-
syncTextResolution(parts.typeMark, parts.typeMarkRes, context.zoom);
|
|
70
75
|
const visualSig = [
|
|
71
76
|
key ?? "",
|
|
72
77
|
width,
|
|
@@ -190,7 +195,7 @@ const fileCardRenderer = {
|
|
|
190
195
|
const cover = new Sprite(Texture.EMPTY);
|
|
191
196
|
const coverMask = new Graphics();
|
|
192
197
|
cover.mask = coverMask;
|
|
193
|
-
const resolution =
|
|
198
|
+
const resolution = context.textResolution;
|
|
194
199
|
const title = new Text({
|
|
195
200
|
text: "",
|
|
196
201
|
style: {
|
|
@@ -247,10 +252,7 @@ const fileCardRenderer = {
|
|
|
247
252
|
title,
|
|
248
253
|
excerpt,
|
|
249
254
|
visualSig: "",
|
|
250
|
-
textSig: ""
|
|
251
|
-
titleRes: { resolution },
|
|
252
|
-
excerptRes: { resolution },
|
|
253
|
-
typeMarkRes: { resolution }
|
|
255
|
+
textSig: ""
|
|
254
256
|
});
|
|
255
257
|
if (item.type === "file") sync(root, item, context);
|
|
256
258
|
return root;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
-
import { syncTextResolution } from "../text-resolution.js";
|
|
3
2
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
3
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
4
4
|
import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
|
|
5
5
|
import { createLabel, positionShell } from "./base-card-renderer.js";
|
|
6
6
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -13,7 +13,6 @@ function sync(container, item, context) {
|
|
|
13
13
|
if (!parts) return;
|
|
14
14
|
const { frame, style, props, locked } = item;
|
|
15
15
|
positionShell(parts.root, item);
|
|
16
|
-
syncTextResolution(parts.label, parts, context.zoom);
|
|
17
16
|
const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
|
|
18
17
|
const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
|
|
19
18
|
const baseStroke = itemColor(context, style.stroke, "neutral");
|
|
@@ -69,6 +68,7 @@ function sync(container, item, context) {
|
|
|
69
68
|
parts.label.style.fill = labelColor;
|
|
70
69
|
parts.label.style.fontFamily = context.style.font;
|
|
71
70
|
}
|
|
71
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
72
72
|
parts.label.position.set(4, -18);
|
|
73
73
|
}
|
|
74
74
|
const frameCardRenderer = {
|
|
@@ -82,15 +82,14 @@ const frameCardRenderer = {
|
|
|
82
82
|
fontFamily: BOARD_FONT_STACK,
|
|
83
83
|
fontSize: 12,
|
|
84
84
|
fontWeight: "600"
|
|
85
|
-
});
|
|
85
|
+
}, context.textResolution);
|
|
86
86
|
root.addChild(box, label);
|
|
87
87
|
partsByContainer.set(root, {
|
|
88
88
|
root,
|
|
89
89
|
box,
|
|
90
90
|
label,
|
|
91
91
|
visualSig: "",
|
|
92
|
-
textSig: ""
|
|
93
|
-
resolution: label.resolution
|
|
92
|
+
textSig: ""
|
|
94
93
|
});
|
|
95
94
|
if (item.type === "frame") sync(root, item, context);
|
|
96
95
|
return root;
|
|
@@ -100,7 +99,7 @@ const frameCardRenderer = {
|
|
|
100
99
|
},
|
|
101
100
|
resolution: (container, _item, context) => {
|
|
102
101
|
const parts = partsByContainer.get(container);
|
|
103
|
-
if (parts) syncTextResolution(parts.label,
|
|
102
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
104
103
|
},
|
|
105
104
|
renderFar: (graphics, item, context) => {
|
|
106
105
|
if (item.type !== "frame" || item.props.decoration === "none") return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
2
|
import { worldPoint } from "../../model/geometry.js";
|
|
3
|
-
import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
|
|
4
3
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
+
import { syncTextResolution, syncTextWrapWidth } from "../text-resolution.js";
|
|
5
5
|
import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
|
|
6
6
|
import { positionShell } from "./base-card-renderer.js";
|
|
7
7
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -100,7 +100,6 @@ function sync(container, item, context) {
|
|
|
100
100
|
const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
|
|
101
101
|
const strokeAlpha = itemAlpha(context, style.stroke ?? style.fill);
|
|
102
102
|
const strokeWidth = style.strokeWidth ?? 1.75;
|
|
103
|
-
syncTextResolution(parts.label, parts, context.zoom);
|
|
104
103
|
const visualSig = [
|
|
105
104
|
width,
|
|
106
105
|
height,
|
|
@@ -159,6 +158,7 @@ function sync(container, item, context) {
|
|
|
159
158
|
parts.label.style.align = props.align;
|
|
160
159
|
}
|
|
161
160
|
syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), context.resizingIds.has(item.id));
|
|
161
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
162
162
|
parts.label.position.set(width / 2, height / 2);
|
|
163
163
|
}
|
|
164
164
|
const shapeCardRenderer = {
|
|
@@ -167,7 +167,6 @@ const shapeCardRenderer = {
|
|
|
167
167
|
create: (item, context) => {
|
|
168
168
|
const root = new Container();
|
|
169
169
|
const shape = new Graphics();
|
|
170
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
171
170
|
const label = new Text({
|
|
172
171
|
text: "",
|
|
173
172
|
style: {
|
|
@@ -179,7 +178,7 @@ const shapeCardRenderer = {
|
|
|
179
178
|
wordWrap: true,
|
|
180
179
|
lineHeight: 28
|
|
181
180
|
},
|
|
182
|
-
resolution,
|
|
181
|
+
resolution: context.textResolution,
|
|
183
182
|
roundPixels: true
|
|
184
183
|
});
|
|
185
184
|
label.anchor.set(.5);
|
|
@@ -190,8 +189,7 @@ const shapeCardRenderer = {
|
|
|
190
189
|
label,
|
|
191
190
|
visualSig: "",
|
|
192
191
|
textSig: "",
|
|
193
|
-
wrapWidth: 0
|
|
194
|
-
resolution
|
|
192
|
+
wrapWidth: 0
|
|
195
193
|
});
|
|
196
194
|
if (item.type === "shape") sync(root, item, context);
|
|
197
195
|
return root;
|
|
@@ -201,7 +199,7 @@ const shapeCardRenderer = {
|
|
|
201
199
|
},
|
|
202
200
|
resolution: (container, _item, context) => {
|
|
203
201
|
const parts = partsByContainer.get(container);
|
|
204
|
-
if (parts) syncTextResolution(parts.label,
|
|
202
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
205
203
|
},
|
|
206
204
|
renderFar: (graphics, item, context) => {
|
|
207
205
|
if (item.type !== "shape") return;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
2
|
import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
|
|
3
|
-
import { drawAudioWaveform } from "../audio-waveform.js";
|
|
4
3
|
import { fitTextToLines } from "../text-fit.js";
|
|
5
|
-
import { boardCardDetailed, syncTextResolution
|
|
4
|
+
import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
|
|
6
5
|
import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
|
|
6
|
+
import { drawAudioWaveform } from "../audio-waveform.js";
|
|
7
7
|
import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
|
|
8
8
|
import { positionShell } from "./base-card-renderer.js";
|
|
9
9
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -283,10 +283,6 @@ function sync(container, item, context) {
|
|
|
283
283
|
const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
|
|
284
284
|
const textArtifact = kind === "text" && artifact?.type === "text";
|
|
285
285
|
const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
|
|
286
|
-
syncTextResolution(parts.body, parts, context.zoom);
|
|
287
|
-
syncTextResolution(parts.detail, parts, context.zoom);
|
|
288
|
-
syncTextResolution(parts.meta, parts, context.zoom);
|
|
289
|
-
syncTextResolution(parts.badge, parts, context.zoom);
|
|
290
286
|
const textSig = [
|
|
291
287
|
counting ? label : bodyText,
|
|
292
288
|
detailText,
|
|
@@ -317,6 +313,12 @@ function sync(container, item, context) {
|
|
|
317
313
|
parts.badge.style.fill = context.palette.text;
|
|
318
314
|
if (parts.badge.text !== count) parts.badge.text = count;
|
|
319
315
|
}
|
|
316
|
+
for (const text of [
|
|
317
|
+
parts.body,
|
|
318
|
+
parts.detail,
|
|
319
|
+
parts.meta,
|
|
320
|
+
parts.badge
|
|
321
|
+
]) syncTextResolution(text, context.textResolution);
|
|
320
322
|
const badgeWidth = Math.max(22, parts.badge.width + 12);
|
|
321
323
|
const visualSig = [
|
|
322
324
|
width,
|
|
@@ -434,7 +436,6 @@ const taskCardRenderer = {
|
|
|
434
436
|
const previewMask = new Graphics();
|
|
435
437
|
const shimmer = new Graphics();
|
|
436
438
|
const previewArt = new Graphics();
|
|
437
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
438
439
|
const text = (fontFamily, fontSize, fontWeight) => new Text({
|
|
439
440
|
text: "",
|
|
440
441
|
style: {
|
|
@@ -444,7 +445,7 @@ const taskCardRenderer = {
|
|
|
444
445
|
wordWrap: true,
|
|
445
446
|
breakWords: true
|
|
446
447
|
},
|
|
447
|
-
resolution,
|
|
448
|
+
resolution: context.textResolution,
|
|
448
449
|
roundPixels: true
|
|
449
450
|
});
|
|
450
451
|
const body = text(BOARD_FONT_STACK, 11.5, "500");
|
|
@@ -478,7 +479,6 @@ const taskCardRenderer = {
|
|
|
478
479
|
assetKey: null,
|
|
479
480
|
visualSig: "",
|
|
480
481
|
textSig: "",
|
|
481
|
-
resolution,
|
|
482
482
|
motion: {
|
|
483
483
|
itemId: "",
|
|
484
484
|
width: 0,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
|
|
2
2
|
import { boardFontFamily } from "../../model/text-metrics.js";
|
|
3
|
-
import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
|
|
4
|
-
import { BOARD_FONT_STACKS } from "../text-measurement.js";
|
|
5
3
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
+
import { BOARD_FONT_STACKS } from "../text-measurement.js";
|
|
5
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
6
6
|
import { positionShell } from "./base-card-renderer.js";
|
|
7
7
|
import { drawFarPlate } from "./far-plate.js";
|
|
8
8
|
import { Container, Graphics, Text } from "pixi.js";
|
|
@@ -32,7 +32,6 @@ function sync(container, item, context) {
|
|
|
32
32
|
const anchorX = props.align === "center" ? .5 : props.align === "right" ? 1 : 0;
|
|
33
33
|
parts.body.anchor.set(anchorX, 0);
|
|
34
34
|
parts.body.position.set(item.frame.width * anchorX, 0);
|
|
35
|
-
syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
|
|
36
35
|
parts.body.alpha = itemAlpha(context, item.style.fill);
|
|
37
36
|
parts.caret.alpha = parts.body.alpha;
|
|
38
37
|
const ink = itemColor(context, item.style.fill, "neutral");
|
|
@@ -62,6 +61,7 @@ function sync(container, item, context) {
|
|
|
62
61
|
breakWords: true
|
|
63
62
|
});
|
|
64
63
|
}
|
|
64
|
+
syncTextResolution(parts.body, context.textResolution * previewScale);
|
|
65
65
|
syncCaret(parts, item, context);
|
|
66
66
|
}
|
|
67
67
|
const textCardRenderer = {
|
|
@@ -69,11 +69,10 @@ const textCardRenderer = {
|
|
|
69
69
|
canRender: (item) => item.type === "text",
|
|
70
70
|
create: (item, context) => {
|
|
71
71
|
const root = new Container();
|
|
72
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
73
72
|
const body = new Text({
|
|
74
73
|
text: "",
|
|
75
74
|
style: { fontFamily: BOARD_FONT_STACKS.sans },
|
|
76
|
-
resolution,
|
|
75
|
+
resolution: context.textResolution,
|
|
77
76
|
roundPixels: true
|
|
78
77
|
});
|
|
79
78
|
const caret = new Graphics();
|
|
@@ -82,7 +81,6 @@ const textCardRenderer = {
|
|
|
82
81
|
root,
|
|
83
82
|
body,
|
|
84
83
|
caret,
|
|
85
|
-
resolution,
|
|
86
84
|
contentSig: ""
|
|
87
85
|
});
|
|
88
86
|
if (item.type === "text") sync(root, item, context);
|
|
@@ -93,7 +91,7 @@ const textCardRenderer = {
|
|
|
93
91
|
},
|
|
94
92
|
resolution: (container, _item, context) => {
|
|
95
93
|
const parts = partsByContainer.get(container);
|
|
96
|
-
if (parts) syncTextResolution(parts.body,
|
|
94
|
+
if (parts) syncTextResolution(parts.body, context.textResolution * parts.body.scale.x);
|
|
97
95
|
},
|
|
98
96
|
renderFar: (graphics, item, context) => {
|
|
99
97
|
if (item.type !== "text") return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
-
import { syncTextResolution
|
|
2
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
3
3
|
import { cardElevation, drawCardSurface, traceCardOutline } from "../surface.js";
|
|
4
4
|
import { positionShell } from "./base-card-renderer.js";
|
|
5
5
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -12,7 +12,6 @@ function sync(container, item, context) {
|
|
|
12
12
|
positionShell(parts.root, item);
|
|
13
13
|
const { width, height } = item.frame;
|
|
14
14
|
const realType = item.type;
|
|
15
|
-
syncTextResolution(parts.label, parts, context.zoom);
|
|
16
15
|
const elevation = cardElevation(context, item.id);
|
|
17
16
|
const visualSig = [
|
|
18
17
|
width,
|
|
@@ -47,6 +46,7 @@ function sync(container, item, context) {
|
|
|
47
46
|
parts.label.text = realType;
|
|
48
47
|
parts.label.style.fill = context.palette.muted;
|
|
49
48
|
}
|
|
49
|
+
syncTextResolution(parts.label, context.textResolution);
|
|
50
50
|
parts.label.position.set(width / 2, height / 2);
|
|
51
51
|
}
|
|
52
52
|
const unknownCardRenderer = {
|
|
@@ -56,7 +56,6 @@ const unknownCardRenderer = {
|
|
|
56
56
|
const root = new Container();
|
|
57
57
|
const box = new Graphics();
|
|
58
58
|
box.context.batchMode = "batch";
|
|
59
|
-
const resolution = textResolutionForZoom(context.zoom);
|
|
60
59
|
const label = new Text({
|
|
61
60
|
text: "",
|
|
62
61
|
style: {
|
|
@@ -64,7 +63,7 @@ const unknownCardRenderer = {
|
|
|
64
63
|
fontSize: 11,
|
|
65
64
|
fontWeight: "600"
|
|
66
65
|
},
|
|
67
|
-
resolution,
|
|
66
|
+
resolution: context.textResolution,
|
|
68
67
|
roundPixels: true
|
|
69
68
|
});
|
|
70
69
|
label.anchor.set(.5);
|
|
@@ -74,8 +73,7 @@ const unknownCardRenderer = {
|
|
|
74
73
|
box,
|
|
75
74
|
label,
|
|
76
75
|
visualSig: "",
|
|
77
|
-
textSig: ""
|
|
78
|
-
resolution
|
|
76
|
+
textSig: ""
|
|
79
77
|
});
|
|
80
78
|
sync(root, item, context);
|
|
81
79
|
return root;
|
|
@@ -85,7 +83,7 @@ const unknownCardRenderer = {
|
|
|
85
83
|
},
|
|
86
84
|
resolution: (container, _item, context) => {
|
|
87
85
|
const parts = partsByContainer.get(container);
|
|
88
|
-
if (parts) syncTextResolution(parts.label,
|
|
86
|
+
if (parts) syncTextResolution(parts.label, context.textResolution);
|
|
89
87
|
},
|
|
90
88
|
renderFar: (graphics, item, context) => {
|
|
91
89
|
drawFarPlate(graphics, item.frame, {
|
|
@@ -6,15 +6,12 @@ declare function getBoardResolution(size?: {
|
|
|
6
6
|
}, devicePixelRatio?: number): number;
|
|
7
7
|
declare function textZoomBucket(zoom: number): number;
|
|
8
8
|
declare function boardCardDetailed(zoom: number): boolean;
|
|
9
|
-
/**
|
|
10
|
-
declare function
|
|
11
|
-
|
|
12
|
-
declare function syncTextResolution(text: Text, state: {
|
|
13
|
-
resolution: number;
|
|
14
|
-
}, zoom: number): void;
|
|
9
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
10
|
+
declare function boardTextResolution(target: number, current: number, settled: boolean): number;
|
|
11
|
+
declare function syncTextResolution(text: Text, resolution: number): void;
|
|
15
12
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
16
13
|
declare function syncTextWrapWidth(text: Text, state: {
|
|
17
14
|
wrapWidth: number;
|
|
18
15
|
}, width: number, defer: boolean): void;
|
|
19
16
|
//#endregion
|
|
20
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
17
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
//#region src/board/render/text-resolution.ts
|
|
2
2
|
const MAX_BOARD_RESOLUTION = 3;
|
|
3
|
-
const MAX_BOARD_PIXELS =
|
|
4
|
-
const
|
|
3
|
+
const MAX_BOARD_PIXELS = 1e7;
|
|
4
|
+
const MIN_TEXT_RESOLUTION = .125;
|
|
5
|
+
const MAX_TEXT_EDGE = 4096;
|
|
6
|
+
const TEXT_UPSCALE_TOLERANCE = 1.25;
|
|
5
7
|
function getBoardResolution(size, devicePixelRatio = globalThis.devicePixelRatio) {
|
|
6
8
|
const density = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
7
9
|
const pixels = size ? size.width * size.height : 0;
|
|
8
|
-
const budget = pixels > 0 ? Math.sqrt(MAX_BOARD_PIXELS / pixels) : MAX_BOARD_RESOLUTION;
|
|
10
|
+
const budget = pixels > 0 ? Math.max(1, Math.sqrt(MAX_BOARD_PIXELS / pixels)) : MAX_BOARD_RESOLUTION;
|
|
9
11
|
return Math.min(density, MAX_BOARD_RESOLUTION, budget);
|
|
10
12
|
}
|
|
11
|
-
|
|
12
|
-
* Zoom buckets for text re-rasterisation. Pixi Text is a bitmap: at high zoom it
|
|
13
|
-
* looks soft unless we raise its resolution. We quantise zoom into a few
|
|
14
|
-
* buckets so small zoom changes don't thrash texture regeneration.
|
|
15
|
-
*/
|
|
16
|
-
const TEXT_ZOOM_BUCKETS = [
|
|
13
|
+
const ZOOM_BUCKETS = [
|
|
17
14
|
.5,
|
|
18
15
|
1,
|
|
19
16
|
1.5,
|
|
@@ -23,23 +20,24 @@ const TEXT_ZOOM_BUCKETS = [
|
|
|
23
20
|
];
|
|
24
21
|
function textZoomBucket(zoom) {
|
|
25
22
|
const clamped = Math.max(.1, Math.min(4, zoom));
|
|
26
|
-
for (const bucket of
|
|
27
|
-
return
|
|
23
|
+
for (const bucket of ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
|
|
24
|
+
return ZOOM_BUCKETS[ZOOM_BUCKETS.length - 1] ?? 4;
|
|
28
25
|
}
|
|
29
26
|
function boardCardDetailed(zoom) {
|
|
30
27
|
return textZoomBucket(zoom) >= 1;
|
|
31
28
|
}
|
|
32
|
-
/**
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
30
|
+
function boardTextResolution(target, current, settled) {
|
|
31
|
+
if (!Number.isFinite(target) || target <= 0) return current > 0 ? current : 1;
|
|
32
|
+
if (settled || !(current > 0)) return target;
|
|
33
|
+
if (target <= current * TEXT_UPSCALE_TOLERANCE && target >= current / 2) return current;
|
|
34
|
+
return 2 ** Math.ceil(Math.log2(target));
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
text.resolution =
|
|
42
|
-
state.resolution = resolution;
|
|
36
|
+
function syncTextResolution(text, resolution) {
|
|
37
|
+
const { width, height } = text.bounds;
|
|
38
|
+
const limit = MAX_TEXT_EDGE / Math.max(1, Math.ceil(width + text.style.padding * 2), Math.ceil(height + text.style.padding * 2));
|
|
39
|
+
const next = Math.min(limit, Math.max(MIN_TEXT_RESOLUTION, resolution));
|
|
40
|
+
if (text.resolution !== next) text.resolution = next;
|
|
43
41
|
}
|
|
44
42
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
45
43
|
function syncTextWrapWidth(text, state, width, defer) {
|
|
@@ -48,4 +46,4 @@ function syncTextWrapWidth(text, state, width, defer) {
|
|
|
48
46
|
state.wrapWidth = width;
|
|
49
47
|
}
|
|
50
48
|
//#endregion
|
|
51
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
49
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/board/stage/canvas-size.d.ts
|
|
2
|
+
type BoardCanvasBox = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
density: number;
|
|
6
|
+
device: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
} | null;
|
|
10
|
+
};
|
|
11
|
+
declare function measureBoardCanvasBox(entry: ResizeObserverEntry, density: number): BoardCanvasBox;
|
|
12
|
+
declare function boardCanvasSize(box: BoardCanvasBox, density: number): {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
resolution: number;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { BoardCanvasBox, boardCanvasSize, measureBoardCanvasBox };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getBoardResolution } from "../render/text-resolution.js";
|
|
2
|
+
//#region src/board/stage/canvas-size.ts
|
|
3
|
+
function measureBoardCanvasBox(entry, density) {
|
|
4
|
+
const css = entry.contentBoxSize?.[0];
|
|
5
|
+
const device = entry.devicePixelContentBoxSize?.[0];
|
|
6
|
+
return {
|
|
7
|
+
width: css?.inlineSize ?? entry.contentRect.width,
|
|
8
|
+
height: css?.blockSize ?? entry.contentRect.height,
|
|
9
|
+
density,
|
|
10
|
+
device: device ? {
|
|
11
|
+
width: device.inlineSize,
|
|
12
|
+
height: device.blockSize
|
|
13
|
+
} : null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function boardCanvasSize(box, density) {
|
|
17
|
+
const resolution = getBoardResolution(box, density);
|
|
18
|
+
const { device } = box;
|
|
19
|
+
if (device && box.density === density && resolution === density) return {
|
|
20
|
+
width: device.width / density,
|
|
21
|
+
height: device.height / density,
|
|
22
|
+
resolution
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
width: box.width,
|
|
26
|
+
height: box.height,
|
|
27
|
+
resolution
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { boardCanvasSize, measureBoardCanvasBox };
|
|
@@ -24,6 +24,10 @@ type SceneSyncInput = {
|
|
|
24
24
|
structureVersion: number;
|
|
25
25
|
geometryVersion: number;
|
|
26
26
|
gestureActive: boolean;
|
|
27
|
+
focus?: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
27
31
|
};
|
|
28
32
|
type BoardSceneNode = {
|
|
29
33
|
item: BoardSceneItem;
|
|
@@ -43,7 +47,7 @@ declare function createBoardScene(options: {
|
|
|
43
47
|
world: Container;
|
|
44
48
|
overlay: Graphics;
|
|
45
49
|
getRenderer: BoardCardRendererResolver;
|
|
46
|
-
|
|
50
|
+
requestFrame?: () => void;
|
|
47
51
|
}): BoardScene;
|
|
48
52
|
//#endregion
|
|
49
53
|
export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
|
-
import {
|
|
3
|
+
import { textZoomBucket } from "../render/text-resolution.js";
|
|
4
4
|
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
5
5
|
import { Graphics } from "pixi.js";
|
|
6
6
|
//#region src/board/stage/scene.ts
|
|
@@ -8,6 +8,7 @@ const FAR_LAYER_ENTER = 450;
|
|
|
8
8
|
const FAR_LAYER_EXIT = 350;
|
|
9
9
|
const FAR_PASS_BATCH = 240;
|
|
10
10
|
const FAR_LIVE_LIMIT = 64;
|
|
11
|
+
const RESOLUTION_BATCH = 32;
|
|
11
12
|
const Z_FAR_LAYER = -1;
|
|
12
13
|
const Z_LIFTED = 1e9;
|
|
13
14
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
@@ -40,6 +41,7 @@ function createBoardScene(options) {
|
|
|
40
41
|
let lastStructureVersion = -1;
|
|
41
42
|
let lastZoom = NaN;
|
|
42
43
|
let lastResolution = "";
|
|
44
|
+
const pendingResolution = /* @__PURE__ */ new Set();
|
|
43
45
|
let liveVersion = 0;
|
|
44
46
|
let lifted = /* @__PURE__ */ new Set();
|
|
45
47
|
const liveStrokes = /* @__PURE__ */ new Map();
|
|
@@ -69,6 +71,7 @@ function createBoardScene(options) {
|
|
|
69
71
|
function recycle(id, entry, context, destroyed) {
|
|
70
72
|
setHeldKey(context, id, null);
|
|
71
73
|
cards.delete(id);
|
|
74
|
+
pendingResolution.delete(id);
|
|
72
75
|
animatedIds.delete(id);
|
|
73
76
|
tickingIds.delete(id);
|
|
74
77
|
if (destroyed) return;
|
|
@@ -182,7 +185,7 @@ function createBoardScene(options) {
|
|
|
182
185
|
}
|
|
183
186
|
if (index < ids.length) {
|
|
184
187
|
farFrame = requestAnimationFrame(step);
|
|
185
|
-
if (target === farFront) options.
|
|
188
|
+
if (target === farFront) options.requestFrame?.();
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
if (target === farBack) {
|
|
@@ -195,7 +198,7 @@ function createBoardScene(options) {
|
|
|
195
198
|
farFront.visible = farActive;
|
|
196
199
|
farPainted = true;
|
|
197
200
|
farRunning = false;
|
|
198
|
-
options.
|
|
201
|
+
options.requestFrame?.();
|
|
199
202
|
if (farQueued) runFarPass();
|
|
200
203
|
};
|
|
201
204
|
step();
|
|
@@ -292,9 +295,11 @@ function createBoardScene(options) {
|
|
|
292
295
|
}
|
|
293
296
|
function sync(input) {
|
|
294
297
|
const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
|
|
295
|
-
const resolution = `${textZoomBucket(context.zoom)}:${
|
|
296
|
-
|
|
297
|
-
|
|
298
|
+
const resolution = `${textZoomBucket(context.zoom)}:${context.textResolution}`;
|
|
299
|
+
if (resolution !== lastResolution) {
|
|
300
|
+
lastResolution = resolution;
|
|
301
|
+
queueResolution(input.focus);
|
|
302
|
+
}
|
|
298
303
|
const structureChanged = structureVersion !== lastStructureVersion;
|
|
299
304
|
if (structureChanged) {
|
|
300
305
|
lastStructureVersion = structureVersion;
|
|
@@ -376,11 +381,10 @@ function createBoardScene(options) {
|
|
|
376
381
|
entry.renderer.update(entry.container, item, context);
|
|
377
382
|
entry.container.alpha = scene.opacity(id);
|
|
378
383
|
if (entry.renderer.tick) tickingIds.add(id);
|
|
379
|
-
|
|
380
|
-
entry.renderer.resolution(entry.container, item, context);
|
|
381
|
-
if (entry.renderer.tick) tickingIds.add(id);
|
|
384
|
+
pendingResolution.delete(id);
|
|
382
385
|
}
|
|
383
386
|
}
|
|
387
|
+
refreshResolution(context);
|
|
384
388
|
if (liveSetChanged) liveVersion += 1;
|
|
385
389
|
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
386
390
|
if (context.zoom !== lastZoom) {
|
|
@@ -392,6 +396,27 @@ function createBoardScene(options) {
|
|
|
392
396
|
if (structureChanged || farModeChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
|
|
393
397
|
syncLiveStrokes(input);
|
|
394
398
|
}
|
|
399
|
+
function queueResolution(focus) {
|
|
400
|
+
const ids = [...cards].filter(([, entry]) => entry.renderer.resolution);
|
|
401
|
+
if (focus) {
|
|
402
|
+
const distance = ({ item: { frame } }) => (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2;
|
|
403
|
+
ids.sort(([, a], [, b]) => distance(a) - distance(b));
|
|
404
|
+
}
|
|
405
|
+
pendingResolution.clear();
|
|
406
|
+
for (const [id] of ids) pendingResolution.add(id);
|
|
407
|
+
}
|
|
408
|
+
function refreshResolution(context) {
|
|
409
|
+
let budget = RESOLUTION_BATCH;
|
|
410
|
+
for (const id of pendingResolution) {
|
|
411
|
+
if (budget-- === 0) break;
|
|
412
|
+
pendingResolution.delete(id);
|
|
413
|
+
const entry = cards.get(id);
|
|
414
|
+
if (!entry) continue;
|
|
415
|
+
entry.renderer.resolution?.(entry.container, entry.item, context);
|
|
416
|
+
if (entry.renderer.tick) tickingIds.add(id);
|
|
417
|
+
}
|
|
418
|
+
if (pendingResolution.size) options.requestFrame?.();
|
|
419
|
+
}
|
|
395
420
|
function tick(clock) {
|
|
396
421
|
let next = null;
|
|
397
422
|
for (const id of tickingIds) {
|
|
@@ -433,6 +458,7 @@ function createBoardScene(options) {
|
|
|
433
458
|
lastStructureVersion = -1;
|
|
434
459
|
lastZoom = NaN;
|
|
435
460
|
lastResolution = "";
|
|
461
|
+
pendingResolution.clear();
|
|
436
462
|
}
|
|
437
463
|
return {
|
|
438
464
|
sync,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
2
|
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
3
|
-
import { pointToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
3
|
+
import { pointToWorld, 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";
|
|
@@ -9,16 +9,17 @@ import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../m
|
|
|
9
9
|
import { elementClientToLocal } from "../model/client-geometry.js";
|
|
10
10
|
import { isCreationBoardTool } from "../editor/tool.js";
|
|
11
11
|
import { resizeCursorForHandle } from "../editor/selection-transform.js";
|
|
12
|
-
import { createBoardPlayer } from "../player/index.js";
|
|
13
|
-
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
14
12
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
13
|
+
import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
|
|
15
14
|
import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
|
|
16
15
|
import { getBoardResolution } from "../render/text-resolution.js";
|
|
17
|
-
import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
|
|
18
16
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
19
17
|
import { createBoardScriptHost } from "../render/script-host.js";
|
|
20
18
|
import { defaultBoardRenderStyle } from "../render/style.js";
|
|
19
|
+
import { createBoardPlayer } from "../player/index.js";
|
|
20
|
+
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
21
21
|
import { boardTextureEdge } from "./assets.js";
|
|
22
|
+
import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
|
|
22
23
|
import { observeDisplayDensity } from "./display-density.js";
|
|
23
24
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
24
25
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
@@ -26,6 +27,7 @@ import { createBoardScene } from "./scene.js";
|
|
|
26
27
|
import { scheduleBoardMaintenance } from "./maintenance.js";
|
|
27
28
|
import { createBoardScriptPointerRouter } from "./script-pointer.js";
|
|
28
29
|
import { drawBoardOverlay } from "./overlay.js";
|
|
30
|
+
import { createTextResolutionTracker } from "./text-resolution-tracker.js";
|
|
29
31
|
import { createBoardItemViewLayer } from "./views.js";
|
|
30
32
|
import { Application, Container, Graphics, RendererType } from "pixi.js";
|
|
31
33
|
//#region src/board/stage/stage.ts
|
|
@@ -111,6 +113,7 @@ function mountBoardStage(host, options) {
|
|
|
111
113
|
let cardTickDue = 0;
|
|
112
114
|
const staleIds = /* @__PURE__ */ new Set();
|
|
113
115
|
let resizeFrame = 0;
|
|
116
|
+
let canvasBox = null;
|
|
114
117
|
let cardEpoch = 0;
|
|
115
118
|
let surface = {
|
|
116
119
|
width: 0,
|
|
@@ -146,6 +149,7 @@ function mountBoardStage(host, options) {
|
|
|
146
149
|
return cull.ids;
|
|
147
150
|
}
|
|
148
151
|
const textureScale = () => renderCamera().zoom * (app?.renderer.resolution ?? 1);
|
|
152
|
+
const textResolution = createTextResolutionTracker(TEXTURE_SETTLE_MS, scheduleTick);
|
|
149
153
|
let refined = {
|
|
150
154
|
scale: 0,
|
|
151
155
|
geometry: 0
|
|
@@ -240,6 +244,7 @@ function mountBoardStage(host, options) {
|
|
|
240
244
|
themeKey: current.key,
|
|
241
245
|
rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
|
|
242
246
|
zoom: renderCamera().zoom,
|
|
247
|
+
textResolution: textResolution.value,
|
|
243
248
|
reducedMotion: options.reducedMotion?.() ?? false,
|
|
244
249
|
labels: options.labels?.() ?? DEFAULT_BOARD_RENDER_LABELS,
|
|
245
250
|
taskRun: options.taskRun ?? (() => null),
|
|
@@ -631,6 +636,7 @@ function mountBoardStage(host, options) {
|
|
|
631
636
|
world.position.set(camera.x, camera.y);
|
|
632
637
|
world.scale.set(camera.zoom);
|
|
633
638
|
const ids = visibleIds();
|
|
639
|
+
textResolution.update(textureScale());
|
|
634
640
|
const context = renderContext(current, renderScene, frame.time);
|
|
635
641
|
if (animationId && effectSampler) {
|
|
636
642
|
context.time = editor.playhead?.time ?? frame.time;
|
|
@@ -657,7 +663,8 @@ function mountBoardStage(host, options) {
|
|
|
657
663
|
staleIds,
|
|
658
664
|
structureVersion: editor.structureVersion,
|
|
659
665
|
geometryVersion: editor.geometryVersion,
|
|
660
|
-
gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
|
|
666
|
+
gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
|
|
667
|
+
focus: pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera)
|
|
661
668
|
});
|
|
662
669
|
staleIds.clear();
|
|
663
670
|
if (scene.liveVersion !== liveVersion) {
|
|
@@ -692,10 +699,14 @@ function mountBoardStage(host, options) {
|
|
|
692
699
|
height: host.clientHeight
|
|
693
700
|
};
|
|
694
701
|
if (size.width <= 0 || size.height <= 0) return;
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
702
|
+
const density = host.ownerDocument.defaultView?.devicePixelRatio ?? 1;
|
|
703
|
+
const canvas = boardCanvasSize(canvasBox ?? {
|
|
704
|
+
...size,
|
|
705
|
+
density,
|
|
706
|
+
device: null
|
|
707
|
+
}, density);
|
|
708
|
+
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);
|
|
709
|
+
if (size.width !== surface.width || size.height !== surface.height) {
|
|
699
710
|
surface = size;
|
|
700
711
|
editor.surfaceSize = surface;
|
|
701
712
|
options.onSurfaceChange?.(surface);
|
|
@@ -853,14 +864,17 @@ function mountBoardStage(host, options) {
|
|
|
853
864
|
}
|
|
854
865
|
scheduleTick();
|
|
855
866
|
});
|
|
856
|
-
const resizeObserver = new ResizeObserver(
|
|
867
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
868
|
+
const entry = entries[entries.length - 1];
|
|
869
|
+
if (entry) canvasBox = measureBoardCanvasBox(entry, host.ownerDocument.defaultView?.devicePixelRatio ?? 1);
|
|
870
|
+
resize();
|
|
871
|
+
});
|
|
857
872
|
return {
|
|
858
873
|
ready: (async () => {
|
|
859
874
|
const instance = new Application();
|
|
860
875
|
try {
|
|
861
876
|
await instance.init({
|
|
862
877
|
antialias: true,
|
|
863
|
-
autoDensity: true,
|
|
864
878
|
backgroundAlpha: 0,
|
|
865
879
|
width: host.clientWidth,
|
|
866
880
|
height: host.clientHeight,
|
|
@@ -885,6 +899,8 @@ function mountBoardStage(host, options) {
|
|
|
885
899
|
position: "relative",
|
|
886
900
|
zIndex: "1",
|
|
887
901
|
display: "block",
|
|
902
|
+
width: "100%",
|
|
903
|
+
height: "100%",
|
|
888
904
|
touchAction: "none"
|
|
889
905
|
});
|
|
890
906
|
host.insertBefore(instance.canvas, views.element);
|
|
@@ -903,10 +919,14 @@ function mountBoardStage(host, options) {
|
|
|
903
919
|
world: nodes,
|
|
904
920
|
overlay,
|
|
905
921
|
getRenderer: resolveRenderer,
|
|
906
|
-
|
|
922
|
+
requestFrame: scheduleTick
|
|
907
923
|
});
|
|
908
924
|
for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
|
|
909
|
-
|
|
925
|
+
try {
|
|
926
|
+
resizeObserver.observe(instance.canvas, { box: "device-pixel-content-box" });
|
|
927
|
+
} catch {
|
|
928
|
+
resizeObserver.observe(instance.canvas);
|
|
929
|
+
}
|
|
910
930
|
const window = host.ownerDocument.defaultView;
|
|
911
931
|
if (window) stopDensity = observeDisplayDensity(window, resize);
|
|
912
932
|
resize();
|
|
@@ -990,6 +1010,7 @@ function mountBoardStage(host, options) {
|
|
|
990
1010
|
pointers.cancel();
|
|
991
1011
|
stopMarch();
|
|
992
1012
|
stopRefine();
|
|
1013
|
+
textResolution.dispose();
|
|
993
1014
|
scriptPointers.clear();
|
|
994
1015
|
stopTransforms?.();
|
|
995
1016
|
scriptObservers.clear();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/board/stage/text-resolution-tracker.d.ts
|
|
2
|
+
declare function createTextResolutionTracker(settleMs: number, onSettle: () => void): {
|
|
3
|
+
readonly value: number;
|
|
4
|
+
update(next: number): number;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createTextResolutionTracker };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { boardTextResolution } from "../render/text-resolution.js";
|
|
2
|
+
//#region src/board/stage/text-resolution-tracker.ts
|
|
3
|
+
function createTextResolutionTracker(settleMs, onSettle) {
|
|
4
|
+
let target = 0;
|
|
5
|
+
let value = 0;
|
|
6
|
+
let settled = false;
|
|
7
|
+
let timer = null;
|
|
8
|
+
return {
|
|
9
|
+
get value() {
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
update(next) {
|
|
13
|
+
if (next !== target) {
|
|
14
|
+
target = next;
|
|
15
|
+
settled = false;
|
|
16
|
+
if (timer !== null) clearTimeout(timer);
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
timer = null;
|
|
19
|
+
settled = true;
|
|
20
|
+
onSettle();
|
|
21
|
+
}, settleMs);
|
|
22
|
+
}
|
|
23
|
+
value = boardTextResolution(target, value, settled);
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
dispose() {
|
|
27
|
+
if (timer !== null) clearTimeout(timer);
|
|
28
|
+
timer = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createTextResolutionTracker };
|