proteus-sdk 0.0.0-stage → 0.1.1

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,586 @@
1
+ /**
2
+ * The Proteus TypeScript SDK: create components, connect them, and transition
3
+ * between them.
4
+ *
5
+ * Start with {@link mount}, which runs an app on a `<canvas>`. Each handle
6
+ * ({@link Handle}, {@link TransitionChannel}, {@link TextureHandle}) keeps a
7
+ * reference to the {@link ProteusApp} it came from, so its methods are called
8
+ * directly: `button.onClick(() => ...)`.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import { Handle as WasmHandle, ProteusApp as WasmApp, TransitionChannel as WasmTransitionChannel, TextureHandle as WasmTextureHandle, VideoHandle as WasmVideoHandle } from "../pkg/proteus_sdk_web.js";
13
+ import type { ImageCrop, ImageSpec, ChildBehavior, ComponentData, ComponentSpec, Geometry, MergeLayout, SplitStrategy, TextSpec, TextureRequest, TextureState, TransitionInteractionConfig, TransitionConfig, TransitionDropped, Size } from "./types.js";
14
+ export * from "./types.js";
15
+ export * from "./convert.js";
16
+ export * from "./mount.js";
17
+ /** A callback with no arguments, such as {@link Handle.onClick}'s. */
18
+ export type PlainCallback = () => void;
19
+ /**
20
+ * {@link Handle.onDrag}'s callback: how far the pointer moved since the last
21
+ * frame, in world units (x right, y up).
22
+ */
23
+ export type DragCallback = (delta: {
24
+ x: number;
25
+ y: number;
26
+ }) => void;
27
+ /** {@link TransitionChannel.onDropped}'s callback: the request that couldn't run, and why. */
28
+ export type DroppedCallback = (dropped: TransitionDropped) => void;
29
+ /**
30
+ * A handle to one component.
31
+ *
32
+ * Callbacks registered with the `on*` methods run every time their event
33
+ * happens, until the component is destroyed. They run after the app's update
34
+ * for that tick, so anything they start takes effect on the next tick.
35
+ *
36
+ * Once the component is destroyed, methods that change it throw, and the
37
+ * failure is also logged. Methods that read it return `undefined`. A method
38
+ * that returns `boolean` uses `false` for "there was nothing to do", such as
39
+ * an image that hasn't been baked yet; that is not an error and doesn't throw.
40
+ */
41
+ export declare class Handle {
42
+ private readonly app;
43
+ /** @internal */
44
+ readonly wasmHandle: WasmHandle;
45
+ /**
46
+ * Prefer {@link ProteusApp.component} or {@link ProteusApp.handleFromId}
47
+ * over calling this directly.
48
+ */
49
+ constructor(app: ProteusApp,
50
+ /** @internal */
51
+ wasmHandle: WasmHandle);
52
+ /** The component's ID, for {@link ProteusApp.handleFromId} and {@link ComponentSpec.children}. */
53
+ id(): number;
54
+ /** A snapshot of the component's current state, or `undefined` if it has been destroyed. */
55
+ get(): ComponentData | undefined;
56
+ /** Calls `cb` each time the pointer is pressed on this component. A click also gives it focus. */
57
+ onClick(cb: PlainCallback): void;
58
+ /**
59
+ * Calls `cb` each time a transition finishes on this component.
60
+ *
61
+ * Every transition reports its completion once, on one component:
62
+ *
63
+ * - {@link Handle.animateTo}: this component.
64
+ * - {@link TransitionChannel.set}: the `to` component.
65
+ * - {@link Handle.splitTo} or {@link Handle.splitToWithStates} with
66
+ * `"row"`, `"column"` or `"grid"`: the source, once every target has arrived.
67
+ * - {@link Handle.splitTo} or {@link Handle.splitToWithStates} with
68
+ * `"perTarget"`: each target, separately. The source has no transition of
69
+ * its own; it is hidden as soon as the split starts.
70
+ * - {@link Handle.mergeFrom}: the destination, once every source has
71
+ * arrived.
72
+ *
73
+ * Changes of interaction style, such as a hover effect, don't count.
74
+ */
75
+ onTransitionComplete(cb: PlainCallback): void;
76
+ /** Calls `cb` each time the pointer moves onto this component. */
77
+ onHoverEnter(cb: PlainCallback): void;
78
+ /** Calls `cb` each time the pointer moves off this component. */
79
+ onHoverExit(cb: PlainCallback): void;
80
+ /**
81
+ * Calls `cb` each time the pointer is pressed on this component. This fires
82
+ * at the same moment as {@link Handle.onClick}; use it with
83
+ * {@link Handle.onRelease} to follow a press from start to end.
84
+ */
85
+ onPress(cb: PlainCallback): void;
86
+ /**
87
+ * Calls `cb` each time the pointer is released after a press on this
88
+ * component, even if the pointer has moved off it.
89
+ */
90
+ onRelease(cb: PlainCallback): void;
91
+ /** Calls `cb` each time this component gains focus. */
92
+ onFocus(cb: PlainCallback): void;
93
+ /** Calls `cb` each time this component loses focus. */
94
+ onBlur(cb: PlainCallback): void;
95
+ /**
96
+ * Calls `cb` every tick while this component is pressed, with the distance
97
+ * the pointer moved since the previous tick, in world units (y points up).
98
+ */
99
+ onDrag(cb: DragCallback): void;
100
+ /**
101
+ * Makes `child` a child of this component. Its geometry becomes relative to
102
+ * this component's.
103
+ */
104
+ addChild(child: Handle): void;
105
+ /**
106
+ * Detaches `child` from this component. The child is kept, as a top-level
107
+ * component; to destroy it instead, call {@link Handle.destroy} on it.
108
+ *
109
+ * A detached child may move on screen: its geometry was relative to this
110
+ * component, and is now relative to the world.
111
+ *
112
+ * Throws if either component no longer exists, or if `child` isn't a child
113
+ * of this component.
114
+ */
115
+ removeChild(child: Handle): void;
116
+ /**
117
+ * Splits this component into `targets`: a 1→N transition.
118
+ *
119
+ * Each target ends at its own declared geometry. This component is hidden
120
+ * as soon as the split starts. With `"row"`, `"column"` and `"grid"`, slices of
121
+ * this component move into place and the targets appear when they arrive;
122
+ * with `"perTarget"`, the targets themselves move. The transition starts on
123
+ * the next tick.
124
+ *
125
+ * `childBehavior`, if given, sets each target's timing separately. It is
126
+ * called once per target with its index and the total; see
127
+ * {@link ChildBehavior}.
128
+ */
129
+ splitTo(targets: Handle[], config: TransitionConfig, strategy: SplitStrategy, childBehavior?: ChildBehavior): void;
130
+ /**
131
+ * Merges `sources` into this component: an N→1 transition.
132
+ *
133
+ * The sources are hidden as soon as the merge starts. `layout` decides
134
+ * which part of this component each source moves toward. The transition
135
+ * starts on the next tick. `childBehavior`, if given, sets each source's
136
+ * timing separately; see {@link ChildBehavior}.
137
+ */
138
+ mergeFrom(sources: Handle[], config: TransitionConfig, layout: MergeLayout, childBehavior?: ChildBehavior): void;
139
+ /**
140
+ * Destroys this component and its children, along with their callbacks and
141
+ * the channels they own.
142
+ *
143
+ * @throws if it was already destroyed. Safe to ignore, but reported so that
144
+ * destroying twice is visible.
145
+ */
146
+ destroy(): void;
147
+ /**
148
+ * Removes this component's text, image and baked content, and releases
149
+ * their textures. The component itself remains, drawn as a plain quad in
150
+ * its color. Nothing is baked again; to show new content, use
151
+ * {@link Handle.setText} or {@link Handle.setImage}.
152
+ *
153
+ * A component made with {@link ComponentSpec.bake} lost its children and
154
+ * its own color when it was baked, so afterwards it is a plain white quad.
155
+ *
156
+ * Releasing a texture doesn't free atlas space immediately. A texture that
157
+ * no component references becomes available for reuse, and the atlas
158
+ * reclaims its space when it needs room for another texture. Textures
159
+ * marked `eternal` are never reclaimed.
160
+ *
161
+ * @throws if this component no longer exists.
162
+ */
163
+ freeResources(): void;
164
+ /**
165
+ * Replaces this component's text, or adds text to a component that has
166
+ * none. The host bakes the new text before it next draws, and the old
167
+ * text's texture is released (see {@link Handle.freeResources}).
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * score.setText({ content: `Score: ${points}`, sizePx: 24 });
172
+ * ```
173
+ *
174
+ * @throws if this component no longer exists, or `text` isn't a valid
175
+ * {@link TextSpec}.
176
+ */
177
+ setText(text: TextSpec): void;
178
+ /**
179
+ * Replaces this component's image, or adds an image to a component that
180
+ * has none. The host decodes and bakes the new image before it next draws,
181
+ * and the old image's texture is released (see
182
+ * {@link Handle.freeResources}). A crop set with {@link Handle.cropImage}
183
+ * applied to the old image, so it is cleared.
184
+ *
185
+ * @throws if this component no longer exists, or `image` isn't a valid
186
+ * {@link ImageSpec}.
187
+ */
188
+ setImage(image: ImageSpec): void;
189
+ /**
190
+ * Like {@link Handle.splitTo}, with each target's end geometry given
191
+ * explicitly instead of taken from its declared geometry.
192
+ *
193
+ * Use this when a target should end somewhere other than its declared
194
+ * geometry, or when this component is also one of the targets. In the
195
+ * second case, pass its end geometry here rather than calling
196
+ * {@link Handle.setDeclaredGeometry} first, which would move it before the
197
+ * split begins.
198
+ */
199
+ splitToWithStates(targets: {
200
+ handle: Handle;
201
+ state: Geometry;
202
+ }[], config: TransitionConfig, strategy: SplitStrategy): void;
203
+ /**
204
+ * Sets this component's declared geometry and moves it there immediately.
205
+ *
206
+ * Use this when a component's layout can only be worked out after it is
207
+ * created, such as a cell sized to fit its text
208
+ * ({@link Handle.bakedTextSize}). Transitions into the component, and its
209
+ * interaction styles, use the new geometry.
210
+ */
211
+ setDeclaredGeometry(state: Geometry): void;
212
+ /**
213
+ * Transitions this component from its current geometry to `to`, where it
214
+ * then rests: `to` becomes its declared geometry, as with
215
+ * {@link Handle.setDeclaredGeometry}, so a later transition into it, or an
216
+ * interaction style, starts from there.
217
+ *
218
+ * Unlike {@link TransitionChannel.set}, only this component is involved, which
219
+ * makes this a good fit for moving a component around repeatedly. Calling
220
+ * it during a transition starts a new one from wherever the component is.
221
+ * The transition starts on the next tick.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * card.animateTo(
226
+ * { ...card.get()!.geometry, position: { x: 300, y: 0, z: 0 } },
227
+ * { duration: 0.3, easing: "easeOutCubic" },
228
+ * );
229
+ * ```
230
+ */
231
+ animateTo(to: Geometry, config: TransitionConfig): void;
232
+ /**
233
+ * The size in pixels of this component's baked text, or `undefined` if the
234
+ * text hasn't been baked yet or the component has none. The host bakes text
235
+ * the next time it renders a frame.
236
+ */
237
+ bakedTextSize(): Size | undefined;
238
+ /**
239
+ * The size in pixels of this component's baked image, or `undefined` if the
240
+ * image hasn't been baked yet or the component has none. This is the full
241
+ * image size, even after {@link Handle.cropImage}.
242
+ */
243
+ bakedImageSize(): Size | undefined;
244
+ /**
245
+ * Shows `source`'s baked image on this component, replacing its current
246
+ * image. Returns `false` if `source` has no baked image yet.
247
+ *
248
+ * The two components share the texture, which stays in the atlas as long as
249
+ * either one references it. Use this to prepare a hidden component before a
250
+ * transition reveals it: for example, before merging grid tiles into a
251
+ * detail view, copy the selected tile's image onto the detail view.
252
+ */
253
+ copyBakedImageFrom(source: Handle): boolean;
254
+ /**
255
+ * Shows only the part of this component's image that `crop` selects: for
256
+ * example `{ kind: "centeredSquare" }` to fill a square grid tile with an
257
+ * image of any shape.
258
+ *
259
+ * The crop is always measured from the whole image, so calling it again
260
+ * replaces the crop rather than cropping the crop, and `{ kind: "none" }`
261
+ * shows the whole image again. Only the visible region changes: no pixels
262
+ * are copied. Returns `false`, and changes nothing, if the image hasn't been
263
+ * baked yet. Throws if `crop` isn't a valid {@link ImageCrop}.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * // A 16:9 view of the image, kept to its top edge.
268
+ * tile.cropImage({ kind: "aspect", ratio: 16 / 9, anchor: { x: 0.5, y: 0 } });
269
+ * ```
270
+ */
271
+ cropImage(crop: ImageCrop): boolean;
272
+ /**
273
+ * Sets whether this component responds to input.
274
+ *
275
+ * A non-interactive component is not there for input: it is never hovered,
276
+ * pressed, dragged or focused, and input goes to whatever is behind it.
277
+ * `false` has the same effect as {@link ComponentSpec.nonInteractive},
278
+ * applied after creation. Use it for things that are never controls, such
279
+ * as backgrounds and labels. For a control that is temporarily unavailable,
280
+ * use {@link Handle.setDisabled}, which still blocks input.
281
+ *
282
+ * Proteus currently handles pointer input only (on the web, that includes
283
+ * touch and pen). Other kinds of input will follow the same rule.
284
+ */
285
+ setInteractive(interactive: boolean): void;
286
+ /**
287
+ * Shows or hides this component and its children.
288
+ *
289
+ * A hidden component is neither drawn nor hit-tested.
290
+ * {@link TransitionChannel.set} already hides the component it transitions from
291
+ * and shows the one it transitions to; use this for everything else.
292
+ *
293
+ * A hidden component stops being drawn on the next frame and stops receiving
294
+ * input one tick later. Input is matched against what was last drawn, so a
295
+ * click in the same tick as the hide still reaches it.
296
+ */
297
+ setVisible(visible: boolean): void;
298
+ /**
299
+ * Sets this component's opacity, clamped to `0`–`1`.
300
+ *
301
+ * Opacity multiplies down the hierarchy: a child at `0.6` under a parent at
302
+ * `0.6` is drawn at `0.36`. It only affects drawing, so a component at `0`
303
+ * still receives pointer input. To take a component out of input as well,
304
+ * use {@link Handle.setVisible}.
305
+ */
306
+ setOpacity(opacity: number): void;
307
+ /**
308
+ * Disables or re-enables this component.
309
+ *
310
+ * A disabled component is still drawn and still blocks input from reaching
311
+ * what is behind it, but fires no events itself, and shows its
312
+ * {@link ComponentSpec.disabled} style, as a disabled control does on the
313
+ * web. Use it for a control that isn't available yet, such as a submit
314
+ * button. For something that is never a control, use
315
+ * {@link Handle.setInteractive}, which lets input through.
316
+ */
317
+ setDisabled(disabled: boolean): void;
318
+ /**
319
+ * Sets whether this component accepts input while transitioning.
320
+ * `undefined` restores the default, where a transitioning component ignores
321
+ * input.
322
+ */
323
+ setTransitionInteraction(config: TransitionInteractionConfig | undefined): void;
324
+ /**
325
+ * Sets this component's image to `texture`, replacing its current image.
326
+ * Returns `false` if the texture has been evicted.
327
+ *
328
+ * Only the image changes: text on the component is still drawn on top, and
329
+ * video, if the component is showing it, still plays. Swapping textures is
330
+ * cheap, which makes this suitable for frame-by-frame animation from
331
+ * textures loaded in advance.
332
+ */
333
+ setTexture(texture: TextureHandle): boolean;
334
+ /**
335
+ * Shows `video` on this component, in place of its image or color.
336
+ * **Experimental**: see {@link VideoHandle}.
337
+ *
338
+ * If the component also has an image, {@link Handle.setVideoCrossfade}
339
+ * blends between the two; this starts fully on the video. Returns `false`,
340
+ * changing nothing, if `video` was released or replaced.
341
+ *
342
+ * @throws if this component no longer exists.
343
+ */
344
+ showVideo(video: VideoHandle): boolean;
345
+ /**
346
+ * Stops showing video on this component, which returns to its image or
347
+ * color.
348
+ *
349
+ * @throws if this component no longer exists.
350
+ */
351
+ hideVideo(): void;
352
+ /**
353
+ * Sets the blend between this component's image (`0`) and its video (`1`).
354
+ * To fade the video in, call it with `0` right after
355
+ * {@link Handle.showVideo}, then raise it over time. Returns `false` if the
356
+ * component isn't showing video.
357
+ *
358
+ * @throws if this component no longer exists.
359
+ */
360
+ setVideoCrossfade(videoT: number): boolean;
361
+ }
362
+ /**
363
+ * A transition channel, which transitions one component into another: see
364
+ * {@link TransitionChannel.set}.
365
+ */
366
+ export declare class TransitionChannel {
367
+ private readonly app;
368
+ /** @internal */
369
+ readonly wasmHandle: WasmTransitionChannel;
370
+ /** Prefer {@link ProteusApp.transitionChannel} over calling this directly. */
371
+ constructor(app: ProteusApp,
372
+ /** @internal */
373
+ wasmHandle: WasmTransitionChannel);
374
+ /**
375
+ * Transitions `from` into `to`: a 1→1 transition.
376
+ *
377
+ * `from` is hidden, and `to` is shown and moves from `from`'s current
378
+ * geometry to its own declared geometry. If `to` is already transitioning,
379
+ * the request is dropped unless `interruptible` is set; then a new
380
+ * transition starts from wherever `to` is. The transition starts on the
381
+ * next tick. A request that can't run is reported to
382
+ * {@link TransitionChannel.onDropped}. On a destroyed channel, the call is
383
+ * ignored with a warning.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * const open = app.transitionChannel();
388
+ * button.onClick(() => open.set(panel, button, { duration: 0.4 }));
389
+ * ```
390
+ */
391
+ set(to: Handle, from: Handle, config: TransitionConfig, interruptible?: boolean): void;
392
+ /**
393
+ * Calls `cb` with the reason each time a {@link TransitionChannel.set} request on
394
+ * this channel can't run. See {@link DropReason}.
395
+ */
396
+ onDropped(cb: DroppedCallback): void;
397
+ /** Destroys this channel and its `onDropped` handlers. Later `set` calls are ignored, with a warning. */
398
+ destroy(): void;
399
+ }
400
+ /**
401
+ * A texture in the atlas. Show it on a component with
402
+ * {@link Handle.setTexture}.
403
+ *
404
+ * A texture can't be freed through its handle: once no component references
405
+ * it, the atlas can reclaim its space when it needs room. See
406
+ * {@link Handle.freeResources}.
407
+ */
408
+ export declare class TextureHandle {
409
+ private readonly app;
410
+ /** @internal */
411
+ readonly wasmHandle: WasmTextureHandle;
412
+ /** Prefer {@link ProteusApp.texture} over calling this directly. */
413
+ constructor(app: ProteusApp,
414
+ /** @internal */
415
+ wasmHandle: WasmTextureHandle);
416
+ /** The texture's ID, for {@link ProteusApp.textureFromId}. */
417
+ id(): number;
418
+ /** The texture's kind and size, or `undefined` if it has been evicted or never existed. */
419
+ state(): TextureState | undefined;
420
+ }
421
+ /**
422
+ * A video whose frames your own player supplies. **Experimental.**
423
+ *
424
+ * Proteus shows video; it doesn't play it. Play the video with your own
425
+ * player, usually a `<video>` element, and upload each frame: with
426
+ * {@link VideoHandle.uploadFrom} for a `<video>` element, or
427
+ * {@link VideoHandle.uploadFrame} for pixels you decoded yourself. Show it on
428
+ * components with {@link Handle.showVideo}. Playback controls are the
429
+ * player's own.
430
+ *
431
+ * There is one video at a time: {@link ProteusApp.createVideo} replaces any
432
+ * video that already exists, and its handle stops working.
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * const video = app.createVideo();
437
+ * tile.showVideo(video);
438
+ * const element = document.querySelector("video")!;
439
+ * const upload = () => {
440
+ * video.uploadFrom(element);
441
+ * element.requestVideoFrameCallback(upload);
442
+ * };
443
+ * element.requestVideoFrameCallback(upload);
444
+ * ```
445
+ */
446
+ export declare class VideoHandle {
447
+ private readonly app;
448
+ /** @internal */
449
+ readonly wasmHandle: WasmVideoHandle;
450
+ private canvas?;
451
+ private context?;
452
+ /** Prefer {@link ProteusApp.createVideo} over calling this directly. */
453
+ constructor(app: ProteusApp,
454
+ /** @internal */
455
+ wasmHandle: WasmVideoHandle);
456
+ /**
457
+ * Uploads one frame of `width × height` RGBA pixels, `width * height * 4`
458
+ * bytes, which every component showing this video draws from the next
459
+ * frame. The video's texture follows the frames' size.
460
+ *
461
+ * Returns `false`, uploading nothing, if the video was released or
462
+ * replaced, or if `rgba` has the wrong length.
463
+ */
464
+ uploadFrame(width: number, height: number, rgba: Uint8Array | Uint8ClampedArray): boolean;
465
+ /**
466
+ * Uploads the frame `element` is showing now. Call it for each new frame,
467
+ * for example from `requestVideoFrameCallback`.
468
+ *
469
+ * The frame is drawn to an offscreen canvas and read back, which works in
470
+ * every browser. Returns `false` if the element has no frame yet, or as
471
+ * {@link VideoHandle.uploadFrame} does.
472
+ *
473
+ * @throws a `SecurityError` if the video comes from another origin without
474
+ * CORS, since the browser then won't let its pixels be read.
475
+ */
476
+ uploadFrom(element: HTMLVideoElement): boolean;
477
+ /**
478
+ * Releases this video and its GPU texture. Components showing video show
479
+ * nothing in its place until {@link Handle.hideVideo} or another video.
480
+ */
481
+ release(): void;
482
+ }
483
+ /**
484
+ * An app's components, channels and callbacks.
485
+ *
486
+ * {@link mount} creates one and passes it to your `setup` function; that is the
487
+ * usual way to get one. `new ProteusApp()` creates a standalone app that draws
488
+ * nothing, which is useful for tests and other headless use.
489
+ */
490
+ export declare class ProteusApp {
491
+ /** @internal */
492
+ readonly wasmApp: WasmApp;
493
+ /**
494
+ * Creates a standalone app that draws nothing.
495
+ *
496
+ * @param wasmApp For internal use by {@link mount}; omit it.
497
+ */
498
+ constructor(wasmApp?: WasmApp);
499
+ /** Creates a component from `spec` and returns its handle. */
500
+ component(spec: ComponentSpec): Handle;
501
+ /**
502
+ * Returns a {@link Handle} for a component ID, from {@link Handle.id} or
503
+ * {@link ComponentData.children}.
504
+ *
505
+ * The ID isn't checked. If its component has been destroyed, the handle
506
+ * behaves like any handle to a destroyed component.
507
+ */
508
+ handleFromId(id: number): Handle;
509
+ /**
510
+ * Creates a transition channel, which transitions one component into
511
+ * another with {@link TransitionChannel.set}. If `owner` is given, the
512
+ * channel is destroyed along with it.
513
+ */
514
+ transitionChannel(owner?: Handle): TransitionChannel;
515
+ /** Returns a {@link TextureHandle} for a texture ID. The same as {@link ProteusApp.textureFromId}. */
516
+ texture(id: number): TextureHandle;
517
+ /**
518
+ * Decodes a PNG or JPEG image and adds it to the atlas, returning a handle
519
+ * to show with {@link Handle.setTexture}.
520
+ *
521
+ * The pixels are on the GPU when this returns, so there is nothing to wait
522
+ * for. You supply the bytes:
523
+ *
524
+ * ```ts
525
+ * const bytes = new Uint8Array(await (await fetch(url)).arrayBuffer());
526
+ * const tex = app.loadTexture(bytes, { maxSide: 512 });
527
+ * if (tex) tile.setTexture(tex);
528
+ * ```
529
+ *
530
+ * Show the texture on a component in the same frame. From the next frame
531
+ * on, a texture that no component uses may be evicted to make room.
532
+ * Several textures can be loaded and then shown: none is evicted in the
533
+ * frame it was loaded.
534
+ *
535
+ * Returns `undefined` if the bytes can't be decoded. An image that decodes
536
+ * but doesn't fit the atlas returns a handle to no texture, which draws
537
+ * nothing.
538
+ */
539
+ loadTexture(bytes: Uint8Array, request?: TextureRequest): TextureHandle | undefined;
540
+ /**
541
+ * Adds RGBA pixels to the atlas and returns a handle to them. `rgba` holds
542
+ * `width * height * 4` bytes. For PNG or JPEG data, use
543
+ * {@link ProteusApp.loadTexture}.
544
+ *
545
+ * Show the texture on a component in the same frame: from the next frame
546
+ * on, a texture that no component uses may be evicted to make room. If the
547
+ * atlas is full, the handle refers to no texture and draws nothing.
548
+ */
549
+ bakeTexture(width: number, height: number, rgba: Uint8Array, request?: TextureRequest): TextureHandle;
550
+ /**
551
+ * Creates a video, whose frames your own player supplies.
552
+ * **Experimental**: see {@link VideoHandle}. There is one video at a time:
553
+ * this replaces any video that already exists.
554
+ */
555
+ createVideo(): VideoHandle;
556
+ /** Returns a {@link TextureHandle} for an ID from {@link TextureHandle.id}. */
557
+ textureFromId(id: number): TextureHandle;
558
+ /** A snapshot of a component's current state, or `undefined` if it has been destroyed. */
559
+ get(handle: Handle): ComponentData | undefined;
560
+ /**
561
+ * Advances the app by `deltaSeconds`, then runs callbacks for the events
562
+ * that occurred. {@link mount} calls this for you every frame.
563
+ */
564
+ tick(deltaSeconds: number): void;
565
+ /**
566
+ * Updates the pointer position, in world units: the origin is the center of
567
+ * the viewport and y points up. Convert from page coordinates with
568
+ * {@link topLeftToWorld}. {@link mount} reports the pointer for you.
569
+ */
570
+ pointerMoved(x: number, y: number): void;
571
+ /** Records that the pointer left the viewport. */
572
+ pointerLeft(): void;
573
+ /** Records that the pointer was pressed. */
574
+ pointerPressed(): void;
575
+ /** Records that the pointer was released. */
576
+ pointerReleased(): void;
577
+ /**
578
+ * Calls {@link ProteusApp.tick} on every animation frame, and returns a
579
+ * function that stops the loop.
580
+ *
581
+ * Only for a standalone app created with `new ProteusApp()`. An app from
582
+ * {@link mount} is already ticked every frame; starting this loop as well
583
+ * would tick it twice.
584
+ */
585
+ startAnimationLoop(): () => void;
586
+ }