@glissade/scene 0.5.0-pre.2 → 0.5.0-pre.3
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/index.d.ts +104 -3
- package/dist/index.js +227 -3
- package/dist/layout.d.ts +1 -1
- package/dist/layoutEngine.d.ts +2 -2
- package/dist/layoutEngine.js +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { BindableSignal, BoundTimeline, CompiledTimeline, Playhead, Timeline, Vec2 } from "@glissade/core";
|
|
1
|
+
import { $ as Resource, A as HitArea, B as segmentGraphemes, C as VideoProps, D as AnchorSpec, E as roundedRectSegs, F as TextMeasurer, G as DisplayListBuilder, H as setDefaultMeasurer, I as TextMetricsLite, J as FilterValidationError, K as DrawCommand, L as breakLines, M as NodeProps, N as PropInit, O as BindablePropTarget, P as resolveAnchor, Q as Rect$1, R as estimatingMeasurer, S as Video, T as revealSchedule, U as BlendMode, V as segmentWords, W as DisplayList, X as Paint, Y as FontSpec, Z as PathSeg, _ as Rect, a as LayoutEngineMissingError, at as glow, b as Text, c as requireLayoutEngine, ct as Mat2x3, d as Group, dt as invert, et as ResourceId, f as ImageNode, ft as matEquals, g as PathProps, h as Path, i as LayoutEngine, it as filtersToCanvasFilter, j as Node, k as EvalContext, l as setLayoutEngine, lt as applyToPoint, m as LineBox, n as LayoutChildSpec, nt as StrokeStyle, ot as validateFilters, p as ImageProps, pt as multiply, q as FilterSpec, r as LayoutContainerSpec, rt as createDisplayListBuilder, s as getLayoutEngine, st as IDENTITY, t as LayoutBox, tt as ShaderRef, u as Circle, ut as fromTRS, v as RevealMark, w as WordBox, x as TextProps, y as ShapeProps, z as quantize } from "./layoutEngine.js";
|
|
2
|
+
import { BindableSignal, BoundTimeline, CompiledTimeline, PathValue, Playhead, Timeline, Track, Vec2 } from "@glissade/core";
|
|
3
3
|
|
|
4
4
|
//#region src/highlight.d.ts
|
|
5
5
|
|
|
@@ -56,6 +56,107 @@ declare class TextCursor extends Node {
|
|
|
56
56
|
/** `children: [title, textCursor(title)]` — a caret riding the reveal head. */
|
|
57
57
|
declare function textCursor(text: Text, props?: Omit<TextCursorProps, 'text'>): TextCursor;
|
|
58
58
|
//#endregion
|
|
59
|
+
//#region src/typewriter.d.ts
|
|
60
|
+
/** One step of a typewriter performance. */
|
|
61
|
+
interface TypeEdit {
|
|
62
|
+
/** graphemes to type in, one keystroke at a time */
|
|
63
|
+
type?: string;
|
|
64
|
+
/** graphemes to backspace, one keystroke at a time */
|
|
65
|
+
delete?: number;
|
|
66
|
+
/** seconds to hold the current text before the next step (a pause beat) */
|
|
67
|
+
hold?: number;
|
|
68
|
+
/** seconds per keystroke for THIS step; overrides the global perChar */
|
|
69
|
+
perChar?: number;
|
|
70
|
+
}
|
|
71
|
+
/** One keystroke in the compiled schedule — the keystroke-SFX contract,
|
|
72
|
+
* extended with `kind` so a backspace can take a different sample. */
|
|
73
|
+
interface EditMark {
|
|
74
|
+
/** keystroke time, absolute timeline seconds */
|
|
75
|
+
time: number;
|
|
76
|
+
/** a character appeared (insert) or was removed (delete/backspace) */
|
|
77
|
+
kind: 'insert' | 'delete';
|
|
78
|
+
/** the grapheme inserted, or the one removed */
|
|
79
|
+
grapheme: string;
|
|
80
|
+
/** the full visible string AFTER this keystroke */
|
|
81
|
+
value: string;
|
|
82
|
+
}
|
|
83
|
+
interface TypewriterResult {
|
|
84
|
+
/** hold-key string track for the Text node's `<id>/text` target */
|
|
85
|
+
track: Track<string>;
|
|
86
|
+
/** every keystroke (insert + delete), for keystroke SFX */
|
|
87
|
+
marks: EditMark[];
|
|
88
|
+
/** time of the last keystroke or hold — the performance's end */
|
|
89
|
+
duration: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Compile an edit script into a string track + keystroke schedule.
|
|
93
|
+
*
|
|
94
|
+
* const tw = typewriter('prompt/text', [
|
|
95
|
+
* { type: 'make it pop' },
|
|
96
|
+
* { hold: 0.4 },
|
|
97
|
+
* { delete: 3 }, // backspace 'pop'
|
|
98
|
+
* { type: 'sing' },
|
|
99
|
+
* ]);
|
|
100
|
+
* // tracks: [tw.track, ...]; keystroke SFX: keystrokeClips(tw.marks, ...)
|
|
101
|
+
*/
|
|
102
|
+
declare function typewriter(target: string, edits: readonly TypeEdit[], opts?: {
|
|
103
|
+
start?: number;
|
|
104
|
+
perChar?: number;
|
|
105
|
+
}): TypewriterResult;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/motionPath.d.ts
|
|
108
|
+
/** An arc-length-parameterized sampler over a path. */
|
|
109
|
+
interface PathSampler {
|
|
110
|
+
/** total arc length */
|
|
111
|
+
readonly length: number;
|
|
112
|
+
/** point at arc-length s (clamped to [0, length]) */
|
|
113
|
+
at(s: number): Vec2;
|
|
114
|
+
/** unit tangent at arc-length s (forward direction of travel) */
|
|
115
|
+
tangentAt(s: number): Vec2;
|
|
116
|
+
/** point at normalized progress u in [0, 1] */
|
|
117
|
+
atProgress(u: number): Vec2;
|
|
118
|
+
/** unit tangent at normalized progress u in [0, 1] */
|
|
119
|
+
tangentAtProgress(u: number): Vec2;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build a reusable arc-length sampler. Densely samples each cubic into a
|
|
123
|
+
* cumulative-length polyline (samplesPerSegment, default 32) so `at`/`tangent`
|
|
124
|
+
* are simple span lerps — smooth enough for motion, no per-call bezier solve.
|
|
125
|
+
*/
|
|
126
|
+
declare function motionPath(path: PathValue, opts?: {
|
|
127
|
+
samplesPerSegment?: number;
|
|
128
|
+
}): PathSampler;
|
|
129
|
+
/** Total arc length of a path. */
|
|
130
|
+
declare function pathLength(path: PathValue): number;
|
|
131
|
+
/** Point at arc-length s along a path (clamped to [0, length]). */
|
|
132
|
+
declare function pointAtLength(path: PathValue, s: number): Vec2;
|
|
133
|
+
interface FollowPathProps extends NodeProps {
|
|
134
|
+
/** the node to move along the path; its position (and rotation, if orient) is owned by this */
|
|
135
|
+
target: Node;
|
|
136
|
+
path: PathValue;
|
|
137
|
+
/** 0→1 position along the path's arc length; default 1 (the end). Track `<id>/progress`. */
|
|
138
|
+
progress?: PropInit<number>;
|
|
139
|
+
/** rotate the target to the path tangent — a cursor that points where it heads; default false */
|
|
140
|
+
orient?: boolean;
|
|
141
|
+
/** degrees added to the orient angle (e.g. if the sprite points up at rest) */
|
|
142
|
+
orientOffset?: number;
|
|
143
|
+
samplesPerSegment?: number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A companion node that drives `target` along `path` as `progress` animates.
|
|
147
|
+
* Owns the target's `position` (and `rotation` when `orient`) via pull-based
|
|
148
|
+
* binding, so there's no eval-order side effect. Add it to the scene (its
|
|
149
|
+
* `progress` is the animatable target); it draws nothing itself.
|
|
150
|
+
*/
|
|
151
|
+
declare class FollowPath extends Node {
|
|
152
|
+
readonly target: Node;
|
|
153
|
+
readonly progress: BindableSignal<number>;
|
|
154
|
+
constructor(props: FollowPathProps);
|
|
155
|
+
protected draw(): void;
|
|
156
|
+
}
|
|
157
|
+
/** `children: [route, cursor, followPath(cursor, route, { orient: true })]` — cursor traces the route. */
|
|
158
|
+
declare function followPath(target: Node, path: PathValue | Path, props?: Omit<FollowPathProps, 'target' | 'path'>): FollowPath;
|
|
159
|
+
//#endregion
|
|
59
160
|
//#region src/tokenHighlight.d.ts
|
|
60
161
|
interface TokenRange {
|
|
61
162
|
/** token text (whitespace-insensitive run match) or inclusive [from, to] wordBoxes indices */
|
|
@@ -298,4 +399,4 @@ declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
|
298
399
|
*/
|
|
299
400
|
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
300
401
|
//#endregion
|
|
301
|
-
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, DuplicateNodeIdError, type EvalContext, type FilterSpec, FilterValidationError, type FontSpec, Group, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSeg, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, multiply, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, validateFilters };
|
|
402
|
+
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSampler, type PathSeg, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, typewriter, validateFilters };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, signal, vec2Signal } from "@glissade/core";
|
|
1
|
+
import { A as fromTRS, C as FilterValidationError, D as validateFilters, E as glow, M as matEquals, N as multiply, O as IDENTITY, S as setDefaultMeasurer, T as filtersToCanvasFilter, _ as estimatingMeasurer, a as Circle, b as segmentGraphemes, c as Path, d as Video, f as revealSchedule, g as breakLines, h as resolveAnchor, i as setLayoutEngine, j as invert, k as applyToPoint, l as Rect, m as Node, n as getLayoutEngine, o as Group, p as roundedRectSegs, r as requireLayoutEngine, s as ImageNode, t as LayoutEngineMissingError, u as Text, v as fallbackMeasurer, w as createDisplayListBuilder, x as segmentWords, y as quantize } from "./layoutEngine.js";
|
|
2
|
+
import { bindTimeline, compileTimeline, createPlayhead, emitDevWarning, evaluateAt, key, signal, track, vec2Signal } from "@glissade/core";
|
|
3
3
|
//#region src/highlight.ts
|
|
4
4
|
/**
|
|
5
5
|
* Marker-style text highlight: per-line rounded rects behind a Text node's
|
|
@@ -153,6 +153,230 @@ function init$1(sig, v) {
|
|
|
153
153
|
return sig;
|
|
154
154
|
}
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region src/typewriter.ts
|
|
157
|
+
/**
|
|
158
|
+
* Edit-event-aware typewriter authoring. `Text.reveal` is monotonic sugar for
|
|
159
|
+
* the type-only case; real terminal cold-opens type, delete, and retype. Since
|
|
160
|
+
* `Text.text` is itself a signal, the honest substrate is a hold-key STRING
|
|
161
|
+
* track that carries the visible text after every keystroke — including
|
|
162
|
+
* backspaces. This compiles a compact edit script into that track plus a
|
|
163
|
+
* per-keystroke schedule (deletes included) for keystroke SFX.
|
|
164
|
+
*
|
|
165
|
+
* Drive `Text.text` with the returned track and leave `reveal` at its default
|
|
166
|
+
* (Infinity): the whole current string shows, so deletion just works, and
|
|
167
|
+
* `textCursor` rides the end of the live text with no extra wiring.
|
|
168
|
+
*/
|
|
169
|
+
const DEFAULT_PER_CHAR = .06;
|
|
170
|
+
/**
|
|
171
|
+
* Compile an edit script into a string track + keystroke schedule.
|
|
172
|
+
*
|
|
173
|
+
* const tw = typewriter('prompt/text', [
|
|
174
|
+
* { type: 'make it pop' },
|
|
175
|
+
* { hold: 0.4 },
|
|
176
|
+
* { delete: 3 }, // backspace 'pop'
|
|
177
|
+
* { type: 'sing' },
|
|
178
|
+
* ]);
|
|
179
|
+
* // tracks: [tw.track, ...]; keystroke SFX: keystrokeClips(tw.marks, ...)
|
|
180
|
+
*/
|
|
181
|
+
function typewriter(target, edits, opts = {}) {
|
|
182
|
+
const start = opts.start ?? 0;
|
|
183
|
+
const globalPer = opts.perChar ?? DEFAULT_PER_CHAR;
|
|
184
|
+
let t = start;
|
|
185
|
+
const shown = [];
|
|
186
|
+
const keys = [key(start, "", { interp: "hold" })];
|
|
187
|
+
const marks = [];
|
|
188
|
+
for (const edit of edits) {
|
|
189
|
+
const per = edit.perChar ?? globalPer;
|
|
190
|
+
if (edit.type !== void 0) for (const g of segmentGraphemes(edit.type)) {
|
|
191
|
+
t += per;
|
|
192
|
+
shown.push(g);
|
|
193
|
+
const value = shown.join("");
|
|
194
|
+
keys.push(key(t, value, { interp: "hold" }));
|
|
195
|
+
marks.push({
|
|
196
|
+
time: t,
|
|
197
|
+
kind: "insert",
|
|
198
|
+
grapheme: g,
|
|
199
|
+
value
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (edit.delete !== void 0) for (let i = 0; i < edit.delete && shown.length > 0; i++) {
|
|
203
|
+
t += per;
|
|
204
|
+
const removed = shown.pop();
|
|
205
|
+
const value = shown.join("");
|
|
206
|
+
keys.push(key(t, value, { interp: "hold" }));
|
|
207
|
+
marks.push({
|
|
208
|
+
time: t,
|
|
209
|
+
kind: "delete",
|
|
210
|
+
grapheme: removed,
|
|
211
|
+
value
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (edit.hold !== void 0) t += edit.hold;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
track: track(target, "string", keys),
|
|
218
|
+
marks,
|
|
219
|
+
duration: t
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/motionPath.ts
|
|
224
|
+
/**
|
|
225
|
+
* Motion along a path: sample a point (and tangent) at an arc-length position on
|
|
226
|
+
* a PathValue, and drive a node along it over time. The Path node draws/morphs
|
|
227
|
+
* geometry; this is the companion that makes another node — a cursor, a dot, an
|
|
228
|
+
* arrow — *travel* that geometry.
|
|
229
|
+
*
|
|
230
|
+
* Sampling is arc-length parameterized (constant speed), so progress 0→1 moves
|
|
231
|
+
* evenly instead of bunching at the control points. Pure and deterministic: the
|
|
232
|
+
* table is built once from a static PathValue and `atProgress` is a pure
|
|
233
|
+
* function of progress, so evaluate() stays pure and goldens are byte-stable.
|
|
234
|
+
*/
|
|
235
|
+
const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
236
|
+
function cubicPoint(p0, p1, p2, p3, t) {
|
|
237
|
+
const mt = 1 - t;
|
|
238
|
+
const a = mt * mt * mt;
|
|
239
|
+
const b = 3 * mt * mt * t;
|
|
240
|
+
const c = 3 * mt * t * t;
|
|
241
|
+
const d = t * t * t;
|
|
242
|
+
return [a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0], a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]];
|
|
243
|
+
}
|
|
244
|
+
/** PathValue contours → cubic segments, same v/in/out math as Path.pathSegs. */
|
|
245
|
+
function toCubics(path) {
|
|
246
|
+
const out = [];
|
|
247
|
+
for (const ct of path) {
|
|
248
|
+
const n = ct.v.length;
|
|
249
|
+
for (let i = 0; i < n - 1; i++) out.push([
|
|
250
|
+
ct.v[i],
|
|
251
|
+
[ct.v[i][0] + ct.out[i][0], ct.v[i][1] + ct.out[i][1]],
|
|
252
|
+
[ct.v[i + 1][0] + ct.in[i + 1][0], ct.v[i + 1][1] + ct.in[i + 1][1]],
|
|
253
|
+
ct.v[i + 1]
|
|
254
|
+
]);
|
|
255
|
+
if (ct.closed && n > 1) out.push([
|
|
256
|
+
ct.v[n - 1],
|
|
257
|
+
[ct.v[n - 1][0] + ct.out[n - 1][0], ct.v[n - 1][1] + ct.out[n - 1][1]],
|
|
258
|
+
[ct.v[0][0] + ct.in[0][0], ct.v[0][1] + ct.in[0][1]],
|
|
259
|
+
ct.v[0]
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Build a reusable arc-length sampler. Densely samples each cubic into a
|
|
266
|
+
* cumulative-length polyline (samplesPerSegment, default 32) so `at`/`tangent`
|
|
267
|
+
* are simple span lerps — smooth enough for motion, no per-call bezier solve.
|
|
268
|
+
*/
|
|
269
|
+
function motionPath(path, opts = {}) {
|
|
270
|
+
const steps = Math.max(1, Math.floor(opts.samplesPerSegment ?? 32));
|
|
271
|
+
const cubics = toCubics(path);
|
|
272
|
+
const pts = [];
|
|
273
|
+
const cum = [];
|
|
274
|
+
if (cubics.length > 0) {
|
|
275
|
+
let prev = cubicPoint(...cubics[0], 0);
|
|
276
|
+
pts.push(prev);
|
|
277
|
+
cum.push(0);
|
|
278
|
+
let acc = 0;
|
|
279
|
+
for (const cub of cubics) for (let k = 1; k <= steps; k++) {
|
|
280
|
+
const p = cubicPoint(...cub, k / steps);
|
|
281
|
+
acc += Math.hypot(p[0] - prev[0], p[1] - prev[1]);
|
|
282
|
+
pts.push(p);
|
|
283
|
+
cum.push(acc);
|
|
284
|
+
prev = p;
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
const first = path[0]?.v[0];
|
|
288
|
+
pts.push(first ? [first[0], first[1]] : [0, 0]);
|
|
289
|
+
cum.push(0);
|
|
290
|
+
}
|
|
291
|
+
const total = cum[cum.length - 1];
|
|
292
|
+
const locate = (s) => {
|
|
293
|
+
if (total <= 0 || s <= 0) return {
|
|
294
|
+
i: 0,
|
|
295
|
+
f: 0
|
|
296
|
+
};
|
|
297
|
+
if (s >= total) return {
|
|
298
|
+
i: pts.length - 2,
|
|
299
|
+
f: 1
|
|
300
|
+
};
|
|
301
|
+
let i = 0;
|
|
302
|
+
while (i < cum.length - 1 && cum[i + 1] < s) i++;
|
|
303
|
+
const span = cum[i + 1] - cum[i];
|
|
304
|
+
return {
|
|
305
|
+
i,
|
|
306
|
+
f: span > 0 ? (s - cum[i]) / span : 0
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
const at = (s) => {
|
|
310
|
+
if (pts.length === 1) return [pts[0][0], pts[0][1]];
|
|
311
|
+
const { i, f } = locate(s);
|
|
312
|
+
const a = pts[i];
|
|
313
|
+
const b = pts[i + 1];
|
|
314
|
+
return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
|
|
315
|
+
};
|
|
316
|
+
const tangentAt = (s) => {
|
|
317
|
+
if (pts.length === 1) return [1, 0];
|
|
318
|
+
const { i } = locate(s);
|
|
319
|
+
const a = pts[i];
|
|
320
|
+
const b = pts[i + 1];
|
|
321
|
+
const dx = b[0] - a[0];
|
|
322
|
+
const dy = b[1] - a[1];
|
|
323
|
+
const len = Math.hypot(dx, dy);
|
|
324
|
+
return len > 0 ? [dx / len, dy / len] : [1, 0];
|
|
325
|
+
};
|
|
326
|
+
return {
|
|
327
|
+
length: total,
|
|
328
|
+
at,
|
|
329
|
+
tangentAt,
|
|
330
|
+
atProgress: (u) => at(clamp01(u) * total),
|
|
331
|
+
tangentAtProgress: (u) => tangentAt(clamp01(u) * total)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/** Total arc length of a path. */
|
|
335
|
+
function pathLength(path) {
|
|
336
|
+
return motionPath(path).length;
|
|
337
|
+
}
|
|
338
|
+
/** Point at arc-length s along a path (clamped to [0, length]). */
|
|
339
|
+
function pointAtLength(path, s) {
|
|
340
|
+
return motionPath(path).at(s);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* A companion node that drives `target` along `path` as `progress` animates.
|
|
344
|
+
* Owns the target's `position` (and `rotation` when `orient`) via pull-based
|
|
345
|
+
* binding, so there's no eval-order side effect. Add it to the scene (its
|
|
346
|
+
* `progress` is the animatable target); it draws nothing itself.
|
|
347
|
+
*/
|
|
348
|
+
var FollowPath = class extends Node {
|
|
349
|
+
target;
|
|
350
|
+
progress;
|
|
351
|
+
constructor(props) {
|
|
352
|
+
super(props);
|
|
353
|
+
this.target = props.target;
|
|
354
|
+
this.progress = signal(1);
|
|
355
|
+
if (typeof props.progress === "function") this.progress.bindSource(props.progress);
|
|
356
|
+
else if (props.progress !== void 0) this.progress.set(props.progress);
|
|
357
|
+
this.registerTarget("progress", this.progress);
|
|
358
|
+
const sampler = motionPath(props.path, props.samplesPerSegment !== void 0 ? { samplesPerSegment: props.samplesPerSegment } : {});
|
|
359
|
+
props.target.position.bindSource(() => sampler.atProgress(this.progress()));
|
|
360
|
+
if (props.orient) {
|
|
361
|
+
const offset = props.orientOffset ?? 0;
|
|
362
|
+
props.target.rotation.bindSource(() => {
|
|
363
|
+
const t = sampler.tangentAtProgress(this.progress());
|
|
364
|
+
return Math.atan2(t[1], t[0]) * 180 / Math.PI + offset;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
draw() {}
|
|
369
|
+
};
|
|
370
|
+
/** `children: [route, cursor, followPath(cursor, route, { orient: true })]` — cursor traces the route. */
|
|
371
|
+
function followPath(target, path, props = {}) {
|
|
372
|
+
const pv = path instanceof Path ? path.data() : path;
|
|
373
|
+
return new FollowPath({
|
|
374
|
+
...props,
|
|
375
|
+
target,
|
|
376
|
+
path: pv
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
156
380
|
//#region src/tokenHighlight.ts
|
|
157
381
|
/**
|
|
158
382
|
* Multi-range token highlight: sub-line ranges over a Text node's wordBoxes,
|
|
@@ -826,4 +1050,4 @@ function evaluate(scene, doc, t) {
|
|
|
826
1050
|
});
|
|
827
1051
|
}
|
|
828
1052
|
//#endregion
|
|
829
|
-
export { Circle, ColdAssetError, DuplicateNodeIdError, FilterValidationError, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, multiply, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, validateFilters };
|
|
1053
|
+
export { Circle, ColdAssetError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, bindScene, breakLines, createDisplayListBuilder, createScene, estimatingMeasurer, evaluate, filtersToCanvasFilter, followPath, fontString, fromTRS, getLayoutEngine, glow, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, revealSchedule, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, textCursor, tokenHighlight, typewriter, validateFilters };
|
package/dist/layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as TextMeasurer,
|
|
1
|
+
import { F as TextMeasurer, G as DisplayListBuilder, M as NodeProps, N as PropInit, a as LayoutEngineMissingError, d as Group, i as LayoutEngine, j as Node, k as EvalContext, l as setLayoutEngine, n as LayoutChildSpec, o as LayoutResult, r as LayoutContainerSpec, s as getLayoutEngine, t as LayoutBox } from "./layoutEngine.js";
|
|
2
2
|
import { BindableSignal } from "@glissade/core";
|
|
3
3
|
|
|
4
4
|
//#region src/layout.d.ts
|
package/dist/layoutEngine.d.ts
CHANGED
|
@@ -211,7 +211,7 @@ declare function segmentWords(text: string): string[];
|
|
|
211
211
|
* (reveal masking), Text.graphemes() (authoring), and revealSchedule() (the SFX
|
|
212
212
|
* keystroke contract) all count the SAME units.
|
|
213
213
|
*/
|
|
214
|
-
|
|
214
|
+
declare function segmentGraphemes(text: string): string[];
|
|
215
215
|
/**
|
|
216
216
|
* Greedy line breaking: explicit '\n' always breaks; otherwise word segments
|
|
217
217
|
* flow until maxWidth is exceeded (Intl.Segmenter boundaries, so CJK wraps
|
|
@@ -673,4 +673,4 @@ declare function setLayoutEngine(e: LayoutEngine): void;
|
|
|
673
673
|
declare function getLayoutEngine(): LayoutEngine | null;
|
|
674
674
|
declare function requireLayoutEngine(): LayoutEngine;
|
|
675
675
|
//#endregion
|
|
676
|
-
export {
|
|
676
|
+
export { Resource as $, HitArea as A, segmentGraphemes as B, VideoProps as C, AnchorSpec as D, roundedRectSegs as E, TextMeasurer as F, DisplayListBuilder as G, setDefaultMeasurer as H, TextMetricsLite as I, FilterValidationError as J, DrawCommand as K, breakLines as L, NodeProps as M, PropInit as N, BindablePropTarget as O, resolveAnchor as P, Rect$1 as Q, estimatingMeasurer as R, Video as S, revealSchedule as T, BlendMode as U, segmentWords as V, DisplayList as W, Paint as X, FontSpec as Y, PathSeg as Z, Rect as _, LayoutEngineMissingError as a, glow as at, Text as b, requireLayoutEngine as c, Mat2x3 as ct, Group as d, invert as dt, ResourceId as et, ImageNode as f, matEquals as ft, PathProps as g, Path as h, LayoutEngine as i, filtersToCanvasFilter as it, Node as j, EvalContext as k, setLayoutEngine as l, applyToPoint as lt, LineBox as m, LayoutChildSpec as n, StrokeStyle as nt, LayoutResult as o, validateFilters as ot, ImageProps as p, multiply as pt, FilterSpec as q, LayoutContainerSpec as r, createDisplayListBuilder as rt, getLayoutEngine as s, IDENTITY as st, LayoutBox as t, ShaderRef as tt, Circle as u, fromTRS as ut, RevealMark as v, WordBox as w, TextProps as x, ShapeProps as y, quantize as z };
|
package/dist/layoutEngine.js
CHANGED
|
@@ -1224,4 +1224,4 @@ function requireLayoutEngine() {
|
|
|
1224
1224
|
return engine;
|
|
1225
1225
|
}
|
|
1226
1226
|
//#endregion
|
|
1227
|
-
export {
|
|
1227
|
+
export { fromTRS as A, FilterValidationError as C, validateFilters as D, glow as E, matEquals as M, multiply as N, IDENTITY as O, setDefaultMeasurer as S, filtersToCanvasFilter as T, estimatingMeasurer as _, Circle as a, segmentGraphemes as b, Path as c, Video as d, revealSchedule as f, breakLines as g, resolveAnchor as h, setLayoutEngine as i, invert as j, applyToPoint as k, Rect as l, Node as m, getLayoutEngine as n, Group as o, roundedRectSegs as p, requireLayoutEngine as r, ImageNode as s, LayoutEngineMissingError as t, Text as u, fallbackMeasurer as v, createDisplayListBuilder as w, segmentWords as x, quantize as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.5.0-pre.
|
|
3
|
+
"version": "0.5.0-pre.3",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"yoga-layout": "^3.2.1",
|
|
23
|
-
"@glissade/core": "0.5.0-pre.
|
|
23
|
+
"@glissade/core": "0.5.0-pre.3"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|