proteus-sdk 0.0.0-stage → 0.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.
@@ -0,0 +1,57 @@
1
+ import { ProteusApp } from "./index.js";
2
+ import type { ProteusConfigOverrides } from "./types.js";
3
+ /** Options for {@link mount}. */
4
+ export interface MountOptions {
5
+ /** Called once, before the first frame, to create the app's components. */
6
+ setup: (app: ProteusApp) => void;
7
+ /**
8
+ * Called every frame, after the app has been ticked, with the time since
9
+ * the previous frame in seconds. To use the app here, keep the one passed
10
+ * to `setup`.
11
+ *
12
+ * Optional, because many apps need no per-frame code: an app that reacts
13
+ * only through callbacks registered in `setup`, such as `onClick` and
14
+ * `onTransitionComplete`, can leave it out. Use it for work that runs
15
+ * every frame, such as a countdown or an animation of your own.
16
+ */
17
+ update?: (deltaSeconds: number) => void;
18
+ /**
19
+ * Engine settings. Omit it, or any field, to keep the web default.
20
+ *
21
+ * ```ts
22
+ * await mount("canvas", {
23
+ * setup,
24
+ * config: {
25
+ * render: { clearColor: [0.05, 0.05, 0.08, 1] },
26
+ * resources: { imageMaxSide: 512 },
27
+ * },
28
+ * });
29
+ * ```
30
+ *
31
+ * A misspelled field, an unknown value, a font that can't be read, or a
32
+ * value the device can't support, such as an atlas larger than WebGL2
33
+ * allows, throws an error that names the field.
34
+ */
35
+ config?: ProteusConfigOverrides;
36
+ }
37
+ /**
38
+ * Runs an app on the `<canvas>` element with the given id: creates a
39
+ * {@link ProteusApp}, calls `setup` with it, then draws and ticks it every
40
+ * frame and reports pointer input to it.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * await mount("canvas", {
45
+ * setup(app) {
46
+ * const button = app.component({ geometry: buttonGeometry });
47
+ * const panel = app.component({ geometry: panelGeometry, visible: false });
48
+ * const open = app.transitionChannel();
49
+ * button.onClick(() => open.set(panel, button, { duration: 0.4 }));
50
+ * },
51
+ * });
52
+ * ```
53
+ *
54
+ * @throws if the canvas isn't found, the GPU can't be initialized, or
55
+ * `config` is invalid.
56
+ */
57
+ export declare function mount(canvasId: string, opts: MountOptions): Promise<void>;
package/dist/mount.js ADDED
@@ -0,0 +1,32 @@
1
+ // `mount`, which runs a TypeScript app on a canvas using `proteus-host-web`.
2
+ //
3
+ // The host is a separately compiled wasm module, and the app object it passes
4
+ // to `setup` comes from that module, not from this package's own. Wrapping it
5
+ // in this package's `ProteusApp` is safe: both modules compile the same
6
+ // `ProteusApp` bindings, and each method call stays within the module that
7
+ // created the object.
8
+ import { mount as wasmMount } from "../pkg-host/proteus_host_web.js";
9
+ import { ProteusApp } from "./index.js";
10
+ /**
11
+ * Runs an app on the `<canvas>` element with the given id: creates a
12
+ * {@link ProteusApp}, calls `setup` with it, then draws and ticks it every
13
+ * frame and reports pointer input to it.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * await mount("canvas", {
18
+ * setup(app) {
19
+ * const button = app.component({ geometry: buttonGeometry });
20
+ * const panel = app.component({ geometry: panelGeometry, visible: false });
21
+ * const open = app.transitionChannel();
22
+ * button.onClick(() => open.set(panel, button, { duration: 0.4 }));
23
+ * },
24
+ * });
25
+ * ```
26
+ *
27
+ * @throws if the canvas isn't found, the GPU can't be initialized, or
28
+ * `config` is invalid.
29
+ */
30
+ export async function mount(canvasId, opts) {
31
+ await wasmMount(canvasId, (rawApp) => opts.setup(new ProteusApp(rawApp)), opts.update ? (dt) => opts.update?.(dt) : undefined, opts.config ?? null);
32
+ }
@@ -0,0 +1,586 @@
1
+ /** A 2D vector. */
2
+ export interface Vec2 {
3
+ /** Horizontal: to the right. */
4
+ x: number;
5
+ /** Vertical: up in world units. */
6
+ y: number;
7
+ }
8
+ /** A width and a height, in pixels. */
9
+ export interface Size {
10
+ /** Width, in pixels. */
11
+ width: number;
12
+ /** Height, in pixels. */
13
+ height: number;
14
+ }
15
+ /** A 3D position. Among top-level components that overlap, higher `z` is drawn on top. */
16
+ export interface Vec3 {
17
+ /** To the right of the viewport's center. */
18
+ x: number;
19
+ /** Up from the viewport's center. */
20
+ y: number;
21
+ /** Drawing order: higher is drawn on top. */
22
+ z: number;
23
+ }
24
+ /**
25
+ * An RGBA color, each channel from `0` to `1`. Use {@link colorFrom} to convert
26
+ * from a hex string or color name.
27
+ */
28
+ export interface Color {
29
+ /** Red, `0`–`1`. */
30
+ r: number;
31
+ /** Green, `0`–`1`. */
32
+ g: number;
33
+ /** Blue, `0`–`1`. */
34
+ b: number;
35
+ /** Alpha: `0` is transparent, `1` opaque. */
36
+ a: number;
37
+ }
38
+ /** A component's position, size, shape and color: everything a transition animates. */
39
+ export interface Geometry {
40
+ /** World units: origin at the viewport center, y up. See {@link topLeftToWorld}. */
41
+ position: Vec3;
42
+ /** Width and height, in pixels. */
43
+ size: Size;
44
+ /** Radians. See {@link degreesToRadians}. */
45
+ rotation: number;
46
+ /** Uniform scale; `1` is the component's natural size. */
47
+ scale: number;
48
+ /**
49
+ * The point of the component that `position` refers to, as fractions of its
50
+ * size from the top-left: `{ x: 0.5, y: 0.5 }` is the center.
51
+ */
52
+ anchor: Vec2;
53
+ /** Fill color. */
54
+ color: Color;
55
+ /** Corner radius in pixels; `0` for square corners. */
56
+ cornerRadius: number;
57
+ }
58
+ /**
59
+ * The style a component takes in one interaction state, such as hover.
60
+ * Declare only the fields that change; the rest come from the component's
61
+ * declared geometry.
62
+ */
63
+ export interface StyleOverride {
64
+ /** Position, in world units. */
65
+ position?: Vec3;
66
+ /** Width and height, in pixels. */
67
+ size?: Size;
68
+ /** Rotation, in radians. */
69
+ rotation?: number;
70
+ /** Uniform scale. */
71
+ scale?: number;
72
+ /** The point `position` refers to, as fractions of the size. */
73
+ anchor?: Vec2;
74
+ /** Fill color. */
75
+ color?: Color;
76
+ /** Corner radius, in pixels. */
77
+ cornerRadius?: number;
78
+ }
79
+ /**
80
+ * A single line of text. The host bakes it the next time it renders a frame.
81
+ * Text wider than an atlas page (2048 pixels by default) is clipped to it,
82
+ * with a warning.
83
+ */
84
+ export interface TextSpec {
85
+ /** The text: a single line. */
86
+ content: string;
87
+ /** Font size in pixels. */
88
+ sizePx: number;
89
+ /** Defaults to opaque white — set a dark value for light backgrounds. */
90
+ color?: Color;
91
+ /** Extra tracking between glyphs, in pixels. Default `0`. */
92
+ letterSpacingPx?: number;
93
+ }
94
+ /**
95
+ * An image, as the bytes of a PNG or JPEG file, for example from a `fetch()`
96
+ * response. The format is detected from the data. The host decodes and bakes
97
+ * it the next time it renders a frame. An image larger than an atlas page
98
+ * (2048 pixels by default) is scaled down to fit it, with a warning.
99
+ */
100
+ export interface ImageSpec {
101
+ /** The PNG or JPEG file's bytes. */
102
+ bytes: Uint8Array;
103
+ /** Scale the image down so its longer side is at most this many pixels. Omit to keep its full size. */
104
+ maxSide?: number;
105
+ }
106
+ /** A border drawn around a component. */
107
+ export interface Border {
108
+ /** Pixels. `0` disables the border. */
109
+ width: number;
110
+ /** The border's color. An alpha of `0` turns it off. */
111
+ color: Color;
112
+ /** Where the border sits: `-1` inside the edge, `0` centered on it, `1` outside. Only `-1` draws correctly today. */
113
+ offset: number;
114
+ }
115
+ /** A soft glow behind a component. A component shows a glow or a drop shadow, not both; if both are set, the drop shadow is drawn. */
116
+ export interface Glow {
117
+ /** How far the glow spreads, in pixels. */
118
+ radius: number;
119
+ /** The glow's color. An alpha of `0` turns it off. */
120
+ color: Color;
121
+ /** Opacity multiplier (effective alpha = `color.a * intensity`). */
122
+ intensity: number;
123
+ }
124
+ /** A drop shadow behind a component. Takes precedence over {@link Glow} if both are set. */
125
+ export interface DropShadow {
126
+ /** How far the shadow is offset, in pixels (x right, y up). */
127
+ offset: Vec2;
128
+ /** The shadow's color. An alpha of `0` turns it off. */
129
+ color: Color;
130
+ /** How soft the shadow's edge is, in pixels. The minimum is `0.5`. */
131
+ softness: number;
132
+ /** How much larger than the component the shadow is, in pixels, before softening. */
133
+ spread: number;
134
+ }
135
+ /**
136
+ * A description of a component, passed to {@link ProteusApp.component}.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const button = app.component({
141
+ * geometry: {
142
+ * position: { x: 0, y: 0, z: 0 },
143
+ * size: { width: 160, height: 48 },
144
+ * rotation: 0,
145
+ * scale: 1,
146
+ * anchor: { x: 0.5, y: 0.5 },
147
+ * color: colorFrom("#3366e6"),
148
+ * cornerRadius: 8,
149
+ * },
150
+ * hover: { scale: 1.05 },
151
+ * text: { content: "Open", sizePx: 18 },
152
+ * });
153
+ * ```
154
+ */
155
+ export interface ComponentSpec {
156
+ /**
157
+ * The component's declared geometry: what it shows when no interaction
158
+ * style applies, and where a transition into it ends.
159
+ */
160
+ geometry: Geometry;
161
+ /** The style while the pointer is over the component. */
162
+ hover?: StyleOverride;
163
+ /** The style while the component is pressed. */
164
+ pressed?: StyleOverride;
165
+ /** The style while the component has focus. */
166
+ focused?: StyleOverride;
167
+ /** The style while the component is disabled. See {@link ComponentSpec.startDisabled} and {@link Handle.setDisabled}. */
168
+ disabled?: StyleOverride;
169
+ /**
170
+ * IDs ({@link Handle.id}) of the components to make its children. Their
171
+ * geometry becomes relative to this component's. To add children later, use
172
+ * {@link Handle.addChild}.
173
+ */
174
+ children?: number[];
175
+ /**
176
+ * Bakes the component and its children into a single texture, permanently.
177
+ * The host renders them once and then destroys the children, so their
178
+ * handles stop working. Suited to detailed content that never changes.
179
+ * Baking happens once the host has baked the text and images in them,
180
+ * usually within a frame or two.
181
+ *
182
+ * A component larger than an atlas page (2048 pixels by default) can't be
183
+ * baked. It is drawn normally instead, with its children, and a warning is
184
+ * logged.
185
+ */
186
+ bake?: boolean;
187
+ /** A single line of text drawn on the component. */
188
+ text?: TextSpec;
189
+ /** An image drawn on the component. */
190
+ image?: ImageSpec;
191
+ /** A border around the component. */
192
+ border?: Border;
193
+ /** A glow behind the component. */
194
+ glow?: Glow;
195
+ /** A drop shadow behind the component. */
196
+ dropShadow?: DropShadow;
197
+ /**
198
+ * Makes the component ignore all input.
199
+ *
200
+ * Components are interactive by default, so handlers such as
201
+ * {@link Handle.onClick} work without an opt-in. A non-interactive component
202
+ * is not there for input: input goes to whatever is behind it. Use it for
203
+ * passive elements such as backgrounds and labels. For a control that is
204
+ * temporarily unavailable, use {@link ComponentSpec.startDisabled}. See
205
+ * {@link Handle.setInteractive}.
206
+ */
207
+ nonInteractive?: boolean;
208
+ /**
209
+ * Whether the component starts visible. Defaults to `true`.
210
+ *
211
+ * A hidden component is neither drawn nor hit-tested. A transition into it
212
+ * through {@link TransitionChannel.set} shows it, so a component that should
213
+ * first appear through a transition can start hidden.
214
+ */
215
+ visible?: boolean;
216
+ /**
217
+ * The opacity of the component and everything under it, clamped to `0`–`1`.
218
+ * Defaults to `1`.
219
+ *
220
+ * Opacity multiplies down the hierarchy: a child at `0.6` under a parent at
221
+ * `0.6` is drawn at `0.36`. It only affects drawing, so a component at `0`
222
+ * still receives pointer input; use {@link ComponentSpec.visible} to take it
223
+ * out of input as well.
224
+ */
225
+ opacity?: number;
226
+ /**
227
+ * Creates the component disabled: drawn and blocking input from reaching
228
+ * what is behind it, but firing no events, and showing its
229
+ * {@link ComponentSpec.disabled} style.
230
+ *
231
+ * Use this for a control that is enabled later with
232
+ * {@link Handle.setDisabled}. For something that is never a control, such as
233
+ * a background or a label, use {@link ComponentSpec.nonInteractive} instead,
234
+ * which lets input through.
235
+ */
236
+ startDisabled?: boolean;
237
+ /** Whether the component accepts input while transitioning. Without this, it doesn't. */
238
+ transitionInteraction?: TransitionInteractionConfig;
239
+ }
240
+ /**
241
+ * Engine settings for {@link mount}. Every field is optional; anything omitted
242
+ * keeps the web default.
243
+ *
244
+ * Only settings that have an effect are listed. A misspelled field is
245
+ * rejected when the app mounts, rather than silently ignored.
246
+ */
247
+ export interface ProteusConfigOverrides {
248
+ /** Clear color and presentation. */
249
+ render?: {
250
+ /** The color behind everything, and what shows through transparency: RGBA, each `0`–`1`. */
251
+ clearColor?: [number, number, number, number];
252
+ /** How frames are synchronized with the display. `"autoVsync"` suits most apps. */
253
+ presentMode?: "autoVsync" | "autoNoVsync" | "fifo" | "fifoRelaxed" | "immediate" | "mailbox";
254
+ /** Which GPU to prefer on a device that has more than one. */
255
+ powerPreference?: "none" | "lowPower" | "highPerformance";
256
+ };
257
+ /** GPU memory: atlas and instance-buffer sizes. */
258
+ memory?: {
259
+ /** The size and number of pages in the main texture atlas. A size larger than WebGL2 allows, 2048, throws at mount, in every browser. */
260
+ mainAtlas?: {
261
+ /**
262
+ * Width and height of each page, in pixels. No texture can be larger
263
+ * than a page: larger text is clipped, a larger image is scaled down,
264
+ * and a larger baked component is drawn unbaked, each with a warning.
265
+ */
266
+ pageSize?: number;
267
+ /** Number of pages. */
268
+ pageCount?: number;
269
+ };
270
+ /** The size of the atlas used for images of components during splits and merges. */
271
+ transitionAtlasSize?: number;
272
+ /** The most components that can be drawn in one frame. */
273
+ maxInstances?: number;
274
+ };
275
+ /** Frame timing. */
276
+ frame?: {
277
+ /** The longest time step one frame can take, so a tab returning from the background doesn't jump ahead. */
278
+ dtClampSecs?: number;
279
+ };
280
+ /** Image and texture loading. */
281
+ resources?: {
282
+ /**
283
+ * Scale images down so their longer side is at most this many pixels.
284
+ * `null` keeps full size, up to an atlas page: a larger image is scaled
285
+ * down to fit the page.
286
+ */
287
+ imageMaxSide?: number | null;
288
+ /** Wait to bake a component's text or image until the component is visible. */
289
+ lazyLoad?: boolean;
290
+ };
291
+ /** Text settings. */
292
+ text?: {
293
+ /**
294
+ * A TTF or OTF font file's bytes, used for all text, for example from a
295
+ * `fetch()` response. Omit it for the embedded font, Inter Bold. Bytes
296
+ * that aren't a font make {@link mount} throw, naming `text.font`.
297
+ */
298
+ font?: Uint8Array;
299
+ };
300
+ }
301
+ /** How to add a texture to the atlas. */
302
+ export interface TextureRequest {
303
+ /**
304
+ * Scale the texture down so its longer side is at most this many pixels.
305
+ * Omit to keep its full size; a photo from the network is usually much
306
+ * larger than it will be drawn.
307
+ */
308
+ maxSide?: number;
309
+ /**
310
+ * Keep the texture in the atlas for the life of the app, never evicting it.
311
+ * For textures in constant use, such as animation frames.
312
+ */
313
+ eternal?: boolean;
314
+ }
315
+ /** Whether a component accepts input while it is transitioning. Both default to `false`. */
316
+ export interface TransitionInteractionConfig {
317
+ /** Accept pointer input during a transition. */
318
+ allowPointer?: boolean;
319
+ /** Not read yet; reserved for keyboard navigation. */
320
+ allowNavigation?: boolean;
321
+ }
322
+ /**
323
+ * A built-in easing curve: how a transition speeds up and slows down.
324
+ *
325
+ * - `"linear"`: constant speed.
326
+ * - `"easeInQuad"`: starts slowly, then speeds up.
327
+ * - `"easeOutQuad"`: starts quickly, then slows to a stop.
328
+ * - `"easeInOutQuad"`: starts slowly, speeds up, then slows to a stop. The
329
+ * default.
330
+ * - `"easeOutCubic"`: like `"easeOutQuad"`, with a stronger slowdown at the end.
331
+ */
332
+ export type EasingName = "linear" | "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeOutCubic";
333
+ /**
334
+ * How a transition speeds up and slows down: a built-in curve's name, or a
335
+ * cubic Bézier curve.
336
+ *
337
+ * `{ cubicBezier: [x1, y1, x2, y2] }` takes the same four numbers as CSS's
338
+ * `cubic-bezier()`, so a curve can be copied from CSS or any easing tool. `x1`
339
+ * and `x2` are clamped to `0`–`1`; `y1` and `y2` may go outside it, for a curve
340
+ * that overshoots its target and settles back. Sizes and corner radii never go
341
+ * below zero, and colors stay within `0`–`1`.
342
+ *
343
+ * An unknown name, or a malformed `cubicBezier`, throws when the transition is
344
+ * requested.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * // Overshoots slightly, then settles: CSS's "back out" curve.
349
+ * handle.animateTo(target, {
350
+ * duration: 0.4,
351
+ * easing: { cubicBezier: [0.34, 1.56, 0.64, 1] },
352
+ * });
353
+ * ```
354
+ */
355
+ export type Easing = EasingName | {
356
+ /** The control points, as in CSS's `cubic-bezier(x1, y1, x2, y2)`. */
357
+ cubicBezier: [number, number, number, number];
358
+ };
359
+ /** How a transition is timed. */
360
+ export interface TransitionConfig {
361
+ /**
362
+ * How long the transition takes, in seconds. `0` means instant: the
363
+ * transition completes on the next frame, after any `delay`. A negative or
364
+ * `NaN` duration logs a warning and is treated as `0`.
365
+ */
366
+ duration: number;
367
+ /** Seconds to wait before the transition starts. Default `0`. */
368
+ delay?: number;
369
+ /** How the transition speeds up and slows down. Default `"easeInOutQuad"`. */
370
+ easing?: Easing;
371
+ }
372
+ /**
373
+ * Sets each component's transition config (timing properties) in a split or merge. Called once per target
374
+ * (or source) with its index and the total; its result replaces the shared
375
+ * config for that component. The usual use is a stagger:
376
+ *
377
+ * ```ts
378
+ * source.splitTo(targets, config, { kind: "row" },
379
+ * (i) => ({ duration: 0.4, delay: i * 0.08, easing: "easeOutCubic" }));
380
+ * ```
381
+ *
382
+ * Called once for each component when the transition is requested. If it
383
+ * throws, or returns something that isn't a {@link TransitionConfig}, the
384
+ * call fails with an error naming the index.
385
+ */
386
+ export type ChildBehavior = (index: number, total: number) => TransitionConfig;
387
+ /**
388
+ * Which part of an image a component shows; see {@link Handle.cropImage}.
389
+ * Always measured from the whole image, so changing the crop never compounds
390
+ * it.
391
+ */
392
+ export type ImageCrop =
393
+ /** The whole image: no crop. */
394
+ {
395
+ /** Selects this crop. */
396
+ kind: "none";
397
+ }
398
+ /** The largest centered square. */
399
+ | {
400
+ /** Selects this crop. */
401
+ kind: "centeredSquare";
402
+ }
403
+ /**
404
+ * The largest region with this width-to-height `ratio`, placed by `anchor`:
405
+ * `{ x: 0.5, y: 0.5 }`, the default, centers it; `{ x: 0, y: 0 }` keeps the
406
+ * top-left corner.
407
+ */
408
+ | {
409
+ /** Selects this crop. */
410
+ kind: "aspect";
411
+ /** Width divided by height, such as `16 / 9`. */
412
+ ratio: number;
413
+ /** Where the region sits within the image, each axis from `0` to `1`. */
414
+ anchor?: Vec2;
415
+ }
416
+ /** An explicit region, in fractions of the image, clamped to it. */
417
+ | {
418
+ /** Selects this crop. */
419
+ kind: "rect";
420
+ /** Left edge, from `0` to `1`. */
421
+ x: number;
422
+ /** Top edge, from `0` to `1`. */
423
+ y: number;
424
+ /** Width, from `0` to `1`. */
425
+ width: number;
426
+ /** Height, from `0` to `1`. */
427
+ height: number;
428
+ };
429
+ /**
430
+ * How a split ({@link Handle.splitTo}) turns one component into several.
431
+ */
432
+ export type SplitStrategy =
433
+ /**
434
+ * Each target moves from the source's position to its own, showing its own
435
+ * content. **Experimental:** not yet used by the reference demo.
436
+ *
437
+ * Use it when you design each target yourself and want to control where and
438
+ * when each one moves; {@link ChildBehavior} sets their timing. Completion is
439
+ * reported on each target, not on the source; see
440
+ * {@link Handle.onTransitionComplete}.
441
+ */
442
+ {
443
+ /** Selects this strategy. */
444
+ kind: "perTarget";
445
+ }
446
+ /**
447
+ * Bakes the source, with its children, into an image, then moves one slice
448
+ * of that image to each target. Use it when the pieces should read as parts
449
+ * of what was there. The slices are strips side by side, left to right.
450
+ */
451
+ | {
452
+ /** Selects this strategy. */
453
+ kind: "row";
454
+ }
455
+ /** Like `"row"`, but the strips are stacked top to bottom. */
456
+ | {
457
+ /** Selects this strategy. */
458
+ kind: "column";
459
+ }
460
+ /**
461
+ * Like `"row"`, but the slices are a grid of `cols` by `rows`, filled row by
462
+ * row from the top-left. Throws if the grid has fewer cells than targets.
463
+ */
464
+ | {
465
+ /** Selects this strategy. */
466
+ kind: "grid";
467
+ /** Columns in the grid. */
468
+ cols: number;
469
+ /** Rows in the grid. */
470
+ rows: number;
471
+ };
472
+ /**
473
+ * How a merge ({@link Handle.mergeFrom}) divides the destination among its
474
+ * sources: which part of the destination each source moves toward.
475
+ */
476
+ export type MergeLayout =
477
+ /** Strips side by side, left to right, one per source in order. */
478
+ {
479
+ /** Selects this layout. */
480
+ kind: "row";
481
+ }
482
+ /** Strips stacked top to bottom, one per source in order. */
483
+ | {
484
+ /** Selects this layout. */
485
+ kind: "column";
486
+ }
487
+ /**
488
+ * A grid of `cols` by `rows`, filled row by row from the top-left. Use it
489
+ * when the sources are themselves laid out in a grid. Throws if the grid
490
+ * has fewer cells than sources.
491
+ */
492
+ | {
493
+ /** Selects this layout. */
494
+ kind: "grid";
495
+ /** Columns in the grid. */
496
+ cols: number;
497
+ /** Rows in the grid. */
498
+ rows: number;
499
+ };
500
+ /**
501
+ * The interaction style a component is showing. More states may be added,
502
+ * such as one for keyboard focus, so a `switch` over it should keep a
503
+ * `default` case.
504
+ */
505
+ export type InteractionState = "default" | "hover" | "pressed" | "focused" | "disabled";
506
+ /** A snapshot of a transition in progress. */
507
+ export interface TransitionSnapshot {
508
+ /** The geometry the transition started from. */
509
+ base: Geometry;
510
+ /** The geometry the transition ends at. */
511
+ target: Geometry;
512
+ /** The current geometry, the same as {@link ComponentData.geometry}. */
513
+ current: Geometry;
514
+ /**
515
+ * Progress through the transition, from `0` to `1`, before easing. Useful for
516
+ * keeping other animation in step with the transition.
517
+ */
518
+ progress: number;
519
+ }
520
+ /**
521
+ * A snapshot of a component's state, from {@link ProteusApp.get} or
522
+ * {@link Handle.get}. It does not update afterwards.
523
+ */
524
+ export interface ComponentData {
525
+ /**
526
+ * The component's current geometry: its declared geometry when idle, or its
527
+ * in-progress geometry while transitioning.
528
+ */
529
+ geometry: Geometry;
530
+ /**
531
+ * The interaction style currently applied.
532
+ *
533
+ * Stays `"default"` for a component that declared no interaction styles,
534
+ * even while it is disabled, and updates one tick after the change. To check
535
+ * whether a component is disabled, use {@link ComponentData.disabled}.
536
+ */
537
+ state: InteractionState;
538
+ /**
539
+ * Whether the component is disabled. Unlike {@link ComponentData.state}, this
540
+ * changes as soon as {@link Handle.setDisabled} is called, and is correct for
541
+ * every component, including one with no interaction styles.
542
+ */
543
+ disabled: boolean;
544
+ /** Whether the component is visible. A component inside a hidden parent is not. Updated each tick. */
545
+ visible: boolean;
546
+ /** The opacity it is drawn with: its own opacity multiplied by its parents'. Updated each tick. */
547
+ opacity: number;
548
+ /** IDs of the component's direct children. Get a handle with {@link ProteusApp.handleFromId}. */
549
+ children: number[];
550
+ /**
551
+ * The transition in progress, or `undefined` when idle. A change of
552
+ * interaction style, such as a hover effect, isn't reported here.
553
+ */
554
+ transition?: TransitionSnapshot;
555
+ }
556
+ /**
557
+ * Why a {@link TransitionChannel.set} request couldn't run:
558
+ *
559
+ * - `"channelNotFound"`: the channel has been destroyed.
560
+ * - `"entityNotFound"`: `to` or `from` has been destroyed.
561
+ * - `"alreadyTransitioning"`: `to` is transitioning and the request wasn't
562
+ * `interruptible`.
563
+ * - `"entityNotVisible"`: `from` is hidden, so there's nothing to transition
564
+ * from.
565
+ */
566
+ export type DropReason = "channelNotFound" | "entityNotFound" | "alreadyTransitioning" | "entityNotVisible";
567
+ /** A {@link TransitionChannel.set} request that couldn't run, passed to {@link TransitionChannel.onDropped}. */
568
+ export interface TransitionDropped {
569
+ /** ID of the request's `to` component. */
570
+ to: number;
571
+ /** ID of the request's `from` component. */
572
+ from: number;
573
+ /** Why the request couldn't run. */
574
+ reason: DropReason;
575
+ }
576
+ /** What a texture holds. `"animated"` is reserved and not used yet. */
577
+ export type TextureKind = "static" | "video" | "animated";
578
+ /** A texture's kind and size, from {@link TextureHandle.state}. */
579
+ export interface TextureState {
580
+ /** What the texture holds. */
581
+ kind: TextureKind;
582
+ /** Width in pixels. */
583
+ width: number;
584
+ /** Height in pixels. */
585
+ height: number;
586
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ // The data types the SDK takes and returns. Their field names must match the
2
+ // Rust types in `../../src/dto.rs`, since these objects cross into the wasm
3
+ // module as they are.
4
+ //
5
+ // Units are radians, colors are RGBA with each channel from 0 to 1, and
6
+ // positions are in world units (origin at the viewport center, y up). The
7
+ // helpers in `convert.ts` convert from degrees, hex colors and page
8
+ // coordinates.
9
+ export {};