lecodes-cli 0.1.5 → 0.1.7
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.js +300 -149
- package/package.json +5 -3
- package/runtime/sdk/animate/animate.ts +238 -0
- package/runtime/sdk/animate/bezier.ts +138 -0
- package/runtime/sdk/animate/easings.ts +126 -0
- package/runtime/sdk/core/color.ts +66 -0
- package/runtime/sdk/core/events.ts +38 -0
- package/runtime/sdk/core/registry.ts +23 -0
- package/runtime/sdk/g2/Camera2D.ts +73 -0
- package/runtime/sdk/g2/Node2D.ts +104 -0
- package/runtime/sdk/g2/Scene2D.ts +73 -0
- package/runtime/sdk/g2/Sprite.ts +164 -0
- package/runtime/sdk/g2/Texture2D.ts +37 -0
- package/runtime/sdk/g2/Tilemap.ts +41 -0
- package/runtime/sdk/g2/loop.ts +22 -0
- package/runtime/sdk/gl/Body.ts +101 -0
- package/runtime/sdk/gl/Camera.ts +40 -0
- package/runtime/sdk/gl/Geometry.ts +218 -0
- package/runtime/sdk/gl/Light.ts +37 -0
- package/runtime/sdk/gl/Material.ts +124 -0
- package/runtime/sdk/gl/Mesh.ts +123 -0
- package/runtime/sdk/gl/Node.ts +307 -0
- package/runtime/sdk/gl/Noise.ts +30 -0
- package/runtime/sdk/gl/Particles.ts +185 -0
- package/runtime/sdk/gl/Plane.ts +53 -0
- package/runtime/sdk/gl/Ray.ts +16 -0
- package/runtime/sdk/gl/Scene.ts +179 -0
- package/runtime/sdk/gl/Texture.ts +35 -0
- package/runtime/sdk/gl/controls.ts +95 -0
- package/runtime/sdk/gl/state.ts +6 -0
- package/runtime/sdk/gl/touch.ts +56 -0
- package/runtime/sdk/inject.ts +72 -0
- package/runtime/sdk/math/Mathf.ts +118 -0
- package/runtime/sdk/math/mat4.ts +278 -0
- package/runtime/sdk/math/quat.ts +232 -0
- package/runtime/sdk/math/vec.ts +255 -0
- package/runtime/sdk/plugins/qr.ts +36 -0
- package/runtime/sdk/runtime/device.ts +33 -0
- package/runtime/sdk/runtime/fetch.ts +71 -0
- package/runtime/sdk/runtime/files.ts +23 -0
- package/runtime/sdk/runtime/input.ts +12 -0
- package/runtime/sdk/runtime/media.ts +111 -0
- package/runtime/sdk/runtime/misc.ts +12 -0
- package/runtime/sdk/runtime/net.ts +36 -0
- package/runtime/sdk/runtime/storage.ts +13 -0
- package/runtime/sdk/runtime/touch.ts +44 -0
- package/runtime/sdk/ui/UI.ts +19 -0
- package/runtime/sdk/ui/UIButton.ts +84 -0
- package/runtime/sdk/ui/UIContainer.ts +100 -0
- package/runtime/sdk/ui/UIImage.ts +68 -0
- package/runtime/sdk/ui/UIInput.ts +78 -0
- package/runtime/sdk/ui/UINode.ts +342 -0
- package/runtime/sdk/ui/UIScreen.ts +122 -0
- package/runtime/sdk/ui/UIScrollable.ts +70 -0
- package/runtime/sdk/ui/UISpacer.ts +14 -0
- package/runtime/sdk/ui/UIText.ts +40 -0
- package/runtime/sdk/ui/UIVideo.ts +38 -0
- package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
- package/runtime/sdk/ui/UIWidget.ts +97 -0
- package/runtime/sdk/ui/fonts.ts +10 -0
- package/runtime/sdk/ui/router.ts +73 -0
- package/runtime/gl-matrix/common.js +0 -54
- package/runtime/gl-matrix/header.js +0 -12
- package/runtime/gl-matrix/index.js +0 -11
- package/runtime/gl-matrix/mat2.js +0 -425
- package/runtime/gl-matrix/mat2d.js +0 -476
- package/runtime/gl-matrix/mat3.js +0 -765
- package/runtime/gl-matrix/mat4.js +0 -1947
- package/runtime/gl-matrix/quat.js +0 -737
- package/runtime/gl-matrix/quat2.js +0 -822
- package/runtime/gl-matrix/vec2.js +0 -620
- package/runtime/gl-matrix/vec3.js +0 -791
- package/runtime/gl-matrix/vec4.js +0 -652
- package/runtime/worker/animate.js +0 -304
- package/runtime/worker/chunk-4ZN32GYS.js +0 -34
- package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
- package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
- package/runtime/worker/creator.js +0 -930
- package/runtime/worker/globals.js +0 -208
- package/runtime/worker/ui.js +0 -763
- package/runtime/worker/utils.js +0 -16
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Fluent 2D / 3D vectors — the SDK's user-facing vector math (no gl-matrix, no out-params, no imports).
|
|
2
|
+
//
|
|
3
|
+
// Design (see PLAN.md): a vector is a *mutable little struct* — `v.x = 3`, `v.set(x, y, z)` mutate in
|
|
4
|
+
// place — but every *method* is PURE: `a.add(b)` returns a NEW vector and never touches `a`. So chains
|
|
5
|
+
// (`dir.normalize().scale(s)`) can't clobber their source, and the immutable-method style is the most
|
|
6
|
+
// amenable to a future escape-analysis / scalar-replacement compiler pass. Backing is plain number
|
|
7
|
+
// fields (not a Float32Array), which the optimizer can trivially scalar-replace.
|
|
8
|
+
//
|
|
9
|
+
// "Never wrap away the metal": vectors expose `.x/.y/.z`, are iterable (`const [x, y] = v`, `[...v]`),
|
|
10
|
+
// and every method / constructor accepts a raw tuple just as well as a wrapper. Reads off a `*Like`
|
|
11
|
+
// input go through the `cx/cy/cz` helpers below, which work uniformly on a wrapper (`.x`) or an
|
|
12
|
+
// array-like (`[0]`), so callers can always pass `[0, 1, 0]` where a vector is expected.
|
|
13
|
+
|
|
14
|
+
export type Vec2Like = Vec2 | readonly [number, number] | number[] | Float32Array
|
|
15
|
+
export type Vec3Like = Vec3 | readonly [number, number, number] | number[] | Float32Array
|
|
16
|
+
|
|
17
|
+
/** Read the x / first component of any vector-like (wrapper, tuple, or typed array). `?? ` is safe on
|
|
18
|
+
* 0 and NaN — it only falls through on `undefined` (i.e. a non-wrapper that has no `.x`). */
|
|
19
|
+
export const cx = (v: { x?: number } | ArrayLike<number>): number => (v as { x?: number }).x ?? (v as ArrayLike<number>)[0]
|
|
20
|
+
export const cy = (v: { y?: number } | ArrayLike<number>): number => (v as { y?: number }).y ?? (v as ArrayLike<number>)[1]
|
|
21
|
+
export const cz = (v: { z?: number } | ArrayLike<number>): number => (v as { z?: number }).z ?? (v as ArrayLike<number>)[2]
|
|
22
|
+
|
|
23
|
+
/** Relative-epsilon compare (matches gl-matrix's `equals`), so the oracle tests agree to the bit. */
|
|
24
|
+
const approx = (a: number, b: number, eps: number): boolean => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b))
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------------------------------
|
|
27
|
+
// Vec2
|
|
28
|
+
// ---------------------------------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export class Vec2 {
|
|
31
|
+
x: number
|
|
32
|
+
y: number
|
|
33
|
+
|
|
34
|
+
constructor(x: number | Vec2Like = 0, y = 0) {
|
|
35
|
+
if (typeof x === "number") { this.x = x; this.y = y }
|
|
36
|
+
else { this.x = cx(x); this.y = cy(x) }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- mutation (the only methods that change `this`) ---
|
|
40
|
+
/** Mutate the components in place. Returns `this`. */
|
|
41
|
+
set(x: number, y: number): this { this.x = x; this.y = y; return this }
|
|
42
|
+
/** Copy another vector's components into this one. Returns `this`. */
|
|
43
|
+
copy(v: Vec2Like): this { this.x = cx(v); this.y = cy(v); return this }
|
|
44
|
+
|
|
45
|
+
// --- pure arithmetic (each returns a NEW Vec2) ---
|
|
46
|
+
add(v: Vec2Like): Vec2 { return new Vec2(this.x + cx(v), this.y + cy(v)) }
|
|
47
|
+
sub(v: Vec2Like): Vec2 { return new Vec2(this.x - cx(v), this.y - cy(v)) }
|
|
48
|
+
mul(v: Vec2Like): Vec2 { return new Vec2(this.x * cx(v), this.y * cy(v)) }
|
|
49
|
+
div(v: Vec2Like): Vec2 { return new Vec2(this.x / cx(v), this.y / cy(v)) }
|
|
50
|
+
scale(s: number): Vec2 { return new Vec2(this.x * s, this.y * s) }
|
|
51
|
+
/** `this + v * s` (fused multiply-add). */
|
|
52
|
+
scaleAndAdd(v: Vec2Like, s: number): Vec2 { return new Vec2(this.x + cx(v) * s, this.y + cy(v) * s) }
|
|
53
|
+
negate(): Vec2 { return new Vec2(-this.x, -this.y) }
|
|
54
|
+
|
|
55
|
+
// --- products / measures ---
|
|
56
|
+
dot(v: Vec2Like): number { return this.x * cx(v) + this.y * cy(v) }
|
|
57
|
+
/** 2D cross product → the scalar z of the 3D cross (signed parallelogram area). */
|
|
58
|
+
cross(v: Vec2Like): number { return this.x * cy(v) - this.y * cx(v) }
|
|
59
|
+
length(): number { return Math.hypot(this.x, this.y) }
|
|
60
|
+
lengthSq(): number { return this.x * this.x + this.y * this.y }
|
|
61
|
+
distanceTo(v: Vec2Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v)) }
|
|
62
|
+
distanceSqTo(v: Vec2Like): number { const dx = this.x - cx(v), dy = this.y - cy(v); return dx * dx + dy * dy }
|
|
63
|
+
|
|
64
|
+
normalize(): Vec2 {
|
|
65
|
+
const l = Math.hypot(this.x, this.y)
|
|
66
|
+
return l === 0 ? new Vec2(0, 0) : new Vec2(this.x / l, this.y / l)
|
|
67
|
+
}
|
|
68
|
+
lerp(v: Vec2Like, t: number): Vec2 { return new Vec2(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t) }
|
|
69
|
+
clamp(min: Vec2Like, max: Vec2Like): Vec2 {
|
|
70
|
+
return new Vec2(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)))
|
|
71
|
+
}
|
|
72
|
+
min(v: Vec2Like): Vec2 { return new Vec2(Math.min(this.x, cx(v)), Math.min(this.y, cy(v))) }
|
|
73
|
+
max(v: Vec2Like): Vec2 { return new Vec2(Math.max(this.x, cx(v)), Math.max(this.y, cy(v))) }
|
|
74
|
+
/** Left-hand perpendicular: (-y, x), a 90° CCW turn. */
|
|
75
|
+
perp(): Vec2 { return new Vec2(-this.y, this.x) }
|
|
76
|
+
/** Rotate by `rad` (CCW) around `origin` (default 0). */
|
|
77
|
+
rotate(rad: number, origin: Vec2Like = ZERO2): Vec2 {
|
|
78
|
+
const ox = cx(origin), oy = cy(origin)
|
|
79
|
+
const c = Math.cos(rad), s = Math.sin(rad)
|
|
80
|
+
const dx = this.x - ox, dy = this.y - oy
|
|
81
|
+
return new Vec2(ox + dx * c - dy * s, oy + dx * s + dy * c)
|
|
82
|
+
}
|
|
83
|
+
/** The vector's heading in radians: `atan2(y, x)`. */
|
|
84
|
+
heading(): number { return Math.atan2(this.y, this.x) }
|
|
85
|
+
/** Angle (radians) between this and `v`. */
|
|
86
|
+
angle(v: Vec2Like): number {
|
|
87
|
+
const mag = Math.sqrt(this.lengthSq() * (cx(v) * cx(v) + cy(v) * cy(v)))
|
|
88
|
+
return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
withX(x: number): Vec2 { return new Vec2(x, this.y) }
|
|
92
|
+
withY(y: number): Vec2 { return new Vec2(this.x, y) }
|
|
93
|
+
|
|
94
|
+
// --- interop ---
|
|
95
|
+
equals(v: Vec2Like, eps = 1e-6): boolean { return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) }
|
|
96
|
+
clone(): Vec2 { return new Vec2(this.x, this.y) }
|
|
97
|
+
toArray(): [number, number] { return [ this.x, this.y ] }
|
|
98
|
+
*[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y }
|
|
99
|
+
|
|
100
|
+
static from(v: Vec2Like): Vec2 { return new Vec2(cx(v), cy(v)) }
|
|
101
|
+
static get zero(): Vec2 { return new Vec2(0, 0) }
|
|
102
|
+
static get one(): Vec2 { return new Vec2(1, 1) }
|
|
103
|
+
static get up(): Vec2 { return new Vec2(0, 1) }
|
|
104
|
+
static get down(): Vec2 { return new Vec2(0, -1) }
|
|
105
|
+
static get left(): Vec2 { return new Vec2(-1, 0) }
|
|
106
|
+
static get right(): Vec2 { return new Vec2(1, 0) }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------------------------------
|
|
110
|
+
// Vec3
|
|
111
|
+
// ---------------------------------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export class Vec3 {
|
|
114
|
+
x: number
|
|
115
|
+
y: number
|
|
116
|
+
z: number
|
|
117
|
+
|
|
118
|
+
constructor(x: number | Vec3Like = 0, y = 0, z = 0) {
|
|
119
|
+
if (typeof x === "number") { this.x = x; this.y = y; this.z = z }
|
|
120
|
+
else { this.x = cx(x); this.y = cy(x); this.z = cz(x) }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- mutation ---
|
|
124
|
+
set(x: number, y: number, z: number): this { this.x = x; this.y = y; this.z = z; return this }
|
|
125
|
+
copy(v: Vec3Like): this { this.x = cx(v); this.y = cy(v); this.z = cz(v); return this }
|
|
126
|
+
|
|
127
|
+
// --- pure arithmetic ---
|
|
128
|
+
add(v: Vec3Like): Vec3 { return new Vec3(this.x + cx(v), this.y + cy(v), this.z + cz(v)) }
|
|
129
|
+
sub(v: Vec3Like): Vec3 { return new Vec3(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
|
|
130
|
+
mul(v: Vec3Like): Vec3 { return new Vec3(this.x * cx(v), this.y * cy(v), this.z * cz(v)) }
|
|
131
|
+
div(v: Vec3Like): Vec3 { return new Vec3(this.x / cx(v), this.y / cy(v), this.z / cz(v)) }
|
|
132
|
+
scale(s: number): Vec3 { return new Vec3(this.x * s, this.y * s, this.z * s) }
|
|
133
|
+
/** `this + v * s` (fused multiply-add). */
|
|
134
|
+
scaleAndAdd(v: Vec3Like, s: number): Vec3 { return new Vec3(this.x + cx(v) * s, this.y + cy(v) * s, this.z + cz(v) * s) }
|
|
135
|
+
negate(): Vec3 { return new Vec3(-this.x, -this.y, -this.z) }
|
|
136
|
+
|
|
137
|
+
// --- products / measures ---
|
|
138
|
+
dot(v: Vec3Like): number { return this.x * cx(v) + this.y * cy(v) + this.z * cz(v) }
|
|
139
|
+
cross(v: Vec3Like): Vec3 {
|
|
140
|
+
const bx = cx(v), by = cy(v), bz = cz(v)
|
|
141
|
+
return new Vec3(this.y * bz - this.z * by, this.z * bx - this.x * bz, this.x * by - this.y * bx)
|
|
142
|
+
}
|
|
143
|
+
length(): number { return Math.hypot(this.x, this.y, this.z) }
|
|
144
|
+
lengthSq(): number { return this.x * this.x + this.y * this.y + this.z * this.z }
|
|
145
|
+
distanceTo(v: Vec3Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
|
|
146
|
+
distanceSqTo(v: Vec3Like): number {
|
|
147
|
+
const dx = this.x - cx(v), dy = this.y - cy(v), dz = this.z - cz(v)
|
|
148
|
+
return dx * dx + dy * dy + dz * dz
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
normalize(): Vec3 {
|
|
152
|
+
const l = Math.hypot(this.x, this.y, this.z)
|
|
153
|
+
return l === 0 ? new Vec3(0, 0, 0) : new Vec3(this.x / l, this.y / l, this.z / l)
|
|
154
|
+
}
|
|
155
|
+
lerp(v: Vec3Like, t: number): Vec3 {
|
|
156
|
+
return new Vec3(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t, this.z + (cz(v) - this.z) * t)
|
|
157
|
+
}
|
|
158
|
+
clamp(min: Vec3Like, max: Vec3Like): Vec3 {
|
|
159
|
+
return new Vec3(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)), clampN(this.z, cz(min), cz(max)))
|
|
160
|
+
}
|
|
161
|
+
min(v: Vec3Like): Vec3 { return new Vec3(Math.min(this.x, cx(v)), Math.min(this.y, cy(v)), Math.min(this.z, cz(v))) }
|
|
162
|
+
max(v: Vec3Like): Vec3 { return new Vec3(Math.max(this.x, cx(v)), Math.max(this.y, cy(v)), Math.max(this.z, cz(v))) }
|
|
163
|
+
/** Reflect this vector about a (unit) `normal`: `v - 2·(v·n)·n`. */
|
|
164
|
+
reflect(normal: Vec3Like): Vec3 {
|
|
165
|
+
const d = 2 * this.dot(normal)
|
|
166
|
+
return new Vec3(this.x - d * cx(normal), this.y - d * cy(normal), this.z - d * cz(normal))
|
|
167
|
+
}
|
|
168
|
+
/** Vector projection of this onto `onto`. */
|
|
169
|
+
project(onto: Vec3Like): Vec3 {
|
|
170
|
+
const ox = cx(onto), oy = cy(onto), oz = cz(onto)
|
|
171
|
+
const denom = ox * ox + oy * oy + oz * oz
|
|
172
|
+
if (denom === 0) return new Vec3(0, 0, 0)
|
|
173
|
+
const k = this.dot(onto) / denom
|
|
174
|
+
return new Vec3(ox * k, oy * k, oz * k)
|
|
175
|
+
}
|
|
176
|
+
/** Angle (radians) between this and `v`. */
|
|
177
|
+
angle(v: Vec3Like): number {
|
|
178
|
+
const bx = cx(v), by = cy(v), bz = cz(v)
|
|
179
|
+
const mag = Math.sqrt(this.lengthSq() * (bx * bx + by * by + bz * bz))
|
|
180
|
+
return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// --- rotation ---
|
|
184
|
+
/** Rotate this point around the X axis through `origin` (default 0) by `rad`. */
|
|
185
|
+
rotateX(rad: number, origin: Vec3Like = ZERO3): Vec3 {
|
|
186
|
+
const ox = cx(origin), oy = cy(origin), oz = cz(origin)
|
|
187
|
+
const py = this.y - oy, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
|
|
188
|
+
return new Vec3(this.x, oy + py * c - pz * s, oz + py * s + pz * c)
|
|
189
|
+
}
|
|
190
|
+
rotateY(rad: number, origin: Vec3Like = ZERO3): Vec3 {
|
|
191
|
+
const ox = cx(origin), oy = cy(origin), oz = cz(origin)
|
|
192
|
+
const px = this.x - ox, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
|
|
193
|
+
return new Vec3(ox + pz * s + px * c, this.y, oz + pz * c - px * s)
|
|
194
|
+
}
|
|
195
|
+
rotateZ(rad: number, origin: Vec3Like = ZERO3): Vec3 {
|
|
196
|
+
const ox = cx(origin), oy = cy(origin), oz = cz(origin)
|
|
197
|
+
const px = this.x - ox, py = this.y - oy, c = Math.cos(rad), s = Math.sin(rad)
|
|
198
|
+
return new Vec3(ox + px * c - py * s, oy + px * s + py * c, this.z)
|
|
199
|
+
}
|
|
200
|
+
/** Rotate this vector by a quaternion (x, y, z, w). Accepts a `Quat` or a raw `[x,y,z,w]`. */
|
|
201
|
+
rotate(q: ArrayLike<number> | { x: number, y: number, z: number, w: number }): Vec3 {
|
|
202
|
+
const qx = cx(q as Vec3Like), qy = cy(q as Vec3Like), qz = cz(q as Vec3Like)
|
|
203
|
+
const qw = (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
|
|
204
|
+
const vx = this.x, vy = this.y, vz = this.z
|
|
205
|
+
// t = 2 * (q.xyz × v)
|
|
206
|
+
let tx = qy * vz - qz * vy, ty = qz * vx - qx * vz, tz = qx * vy - qy * vx
|
|
207
|
+
tx += tx; ty += ty; tz += tz
|
|
208
|
+
// v + w·t + q.xyz × t
|
|
209
|
+
return new Vec3(
|
|
210
|
+
vx + qw * tx + qy * tz - qz * ty,
|
|
211
|
+
vy + qw * ty + qz * tx - qx * tz,
|
|
212
|
+
vz + qw * tz + qx * ty - qy * tx,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
/** Transform this point by a column-major mat4 (a `Mat4`, its `.m`, or a raw length-16 array),
|
|
216
|
+
* applying translation and the perspective divide. */
|
|
217
|
+
transform(m: ArrayLike<number> | { m: ArrayLike<number> }): Vec3 {
|
|
218
|
+
const a = (m as { m?: ArrayLike<number> }).m ?? (m as ArrayLike<number>)
|
|
219
|
+
const x = this.x, y = this.y, z = this.z
|
|
220
|
+
let w = a[3] * x + a[7] * y + a[11] * z + a[15]
|
|
221
|
+
w = w || 1
|
|
222
|
+
return new Vec3(
|
|
223
|
+
(a[0] * x + a[4] * y + a[8] * z + a[12]) / w,
|
|
224
|
+
(a[1] * x + a[5] * y + a[9] * z + a[13]) / w,
|
|
225
|
+
(a[2] * x + a[6] * y + a[10] * z + a[14]) / w,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
withX(x: number): Vec3 { return new Vec3(x, this.y, this.z) }
|
|
230
|
+
withY(y: number): Vec3 { return new Vec3(this.x, y, this.z) }
|
|
231
|
+
withZ(z: number): Vec3 { return new Vec3(this.x, this.y, z) }
|
|
232
|
+
|
|
233
|
+
// --- interop ---
|
|
234
|
+
equals(v: Vec3Like, eps = 1e-6): boolean {
|
|
235
|
+
return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) && approx(this.z, cz(v), eps)
|
|
236
|
+
}
|
|
237
|
+
clone(): Vec3 { return new Vec3(this.x, this.y, this.z) }
|
|
238
|
+
toArray(): [number, number, number] { return [ this.x, this.y, this.z ] }
|
|
239
|
+
*[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y; yield this.z }
|
|
240
|
+
|
|
241
|
+
static from(v: Vec3Like): Vec3 { return new Vec3(cx(v), cy(v), cz(v)) }
|
|
242
|
+
static get zero(): Vec3 { return new Vec3(0, 0, 0) }
|
|
243
|
+
static get one(): Vec3 { return new Vec3(1, 1, 1) }
|
|
244
|
+
static get up(): Vec3 { return new Vec3(0, 1, 0) }
|
|
245
|
+
static get down(): Vec3 { return new Vec3(0, -1, 0) }
|
|
246
|
+
static get left(): Vec3 { return new Vec3(-1, 0, 0) }
|
|
247
|
+
static get right(): Vec3 { return new Vec3(1, 0, 0) }
|
|
248
|
+
/** Forward is −Z (the engine's cameras look down −Z; `node.forward` is −Z at identity). */
|
|
249
|
+
static get forward(): Vec3 { return new Vec3(0, 0, -1) }
|
|
250
|
+
static get back(): Vec3 { return new Vec3(0, 0, 1) }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const clampN = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
|
|
254
|
+
const ZERO2 = new Vec2(0, 0)
|
|
255
|
+
const ZERO3 = new Vec3(0, 0, 0)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// QRScanner — an OPTIONAL, host-provided capability. The native scanner is implemented by some hosts
|
|
2
|
+
// (the le.codes app) and not others (the generic web viewer), so it's gated on host support and throws
|
|
3
|
+
// a clear error where it's missing — the same approach as physics (`physicsHasSupport`) and AR
|
|
4
|
+
// (`requestCamera`). Ported 1:1 from worker/src/utils/QRScanner.ts.
|
|
5
|
+
//
|
|
6
|
+
// It lives under `plugins/` rather than the core engine folders to mark it as host-optional. This is a
|
|
7
|
+
// *convention*, not a plugin *system* — when there are several of these (NFC, Bluetooth, …) we can
|
|
8
|
+
// extract a real mechanism; for one capability that would be premature.
|
|
9
|
+
|
|
10
|
+
export class QRScanner {
|
|
11
|
+
private readonly _onData: (data: string | null) => void
|
|
12
|
+
|
|
13
|
+
/** `onData` fires for every value the scanner decodes (null = a frame with no readable code). */
|
|
14
|
+
constructor(onData: (data: string | null) => void) {
|
|
15
|
+
this._onData = onData
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Whether this host provides a native QR scanner (the le.codes app does; many hosts don't). */
|
|
19
|
+
static get isSupported(): boolean {
|
|
20
|
+
return typeof _creatorUtils.openScanner === "function"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Ask for camera access, then open the native scanner. Rejects if the host can't scan or access is denied. */
|
|
24
|
+
async open(): Promise<void> {
|
|
25
|
+
if (!QRScanner.isSupported) {
|
|
26
|
+
throw new Error("QRScanner isn't available on this host — it needs the le.codes app's native scanner.")
|
|
27
|
+
}
|
|
28
|
+
await new Promise<void>((res, rej) => _creator.requestCamera(res, rej))
|
|
29
|
+
await new Promise<void>((res, rej) => _creatorUtils.openScanner(res, rej, this._onData))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Close the scanner and release the camera. */
|
|
33
|
+
close(): void {
|
|
34
|
+
_creatorUtils.closeScanner()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Platform info + the host resize event. The resize listener registers with the host lazily on the
|
|
2
|
+
// first subscription (no top-level side effect, so tree-shaking can drop this entirely if unused).
|
|
3
|
+
|
|
4
|
+
type ResizeCallback = (width: number, height: number) => void
|
|
5
|
+
|
|
6
|
+
const resizeListeners: ResizeCallback[] = []
|
|
7
|
+
let resizeRegistered = false
|
|
8
|
+
|
|
9
|
+
const onResize = (width: number, height: number): void => {
|
|
10
|
+
for (const cb of resizeListeners.slice()) cb(width, height)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const device = {
|
|
14
|
+
get platform(): "web" | "android" | "ios" | string {
|
|
15
|
+
return _creatorUtils.platform
|
|
16
|
+
},
|
|
17
|
+
get language(): string {
|
|
18
|
+
return _creatorUtils.language
|
|
19
|
+
},
|
|
20
|
+
addEventListener(channel: "resize", callback: ResizeCallback): void {
|
|
21
|
+
if (channel !== "resize") return
|
|
22
|
+
resizeListeners.push(callback)
|
|
23
|
+
if (!resizeRegistered) {
|
|
24
|
+
resizeRegistered = true
|
|
25
|
+
_creatorUI.registerResizeEvent(onResize)
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
removeEventListener(channel: "resize", callback: ResizeCallback): void {
|
|
29
|
+
if (channel !== "resize") return
|
|
30
|
+
const i = resizeListeners.indexOf(callback)
|
|
31
|
+
if (i >= 0) resizeListeners.splice(i, 1)
|
|
32
|
+
},
|
|
33
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// HTTP via the host. `fetch` is async (host streams the body), `fetchLocal` reads a preloaded asset
|
|
2
|
+
// synchronously. Ported from worker/src/fetch.ts over the same _creatorUtils bridge.
|
|
3
|
+
|
|
4
|
+
export type FetchOptions = {
|
|
5
|
+
body?: any
|
|
6
|
+
method?: string
|
|
7
|
+
headers?: Record<string, string>
|
|
8
|
+
useOnce?: boolean
|
|
9
|
+
onProgress?: (progress: { loaded: number, total?: number }) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FetchResponse {
|
|
13
|
+
readonly status: number
|
|
14
|
+
json<T = any>(): T
|
|
15
|
+
text(): string
|
|
16
|
+
dispose(): void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class FetchResponseImpl implements FetchResponse {
|
|
20
|
+
readonly status: number
|
|
21
|
+
private readonly _id: number
|
|
22
|
+
constructor(id: number, status: number) {
|
|
23
|
+
this._id = id
|
|
24
|
+
this.status = status
|
|
25
|
+
}
|
|
26
|
+
json<T = any>(): T {
|
|
27
|
+
return _creatorUtils.fetchToJson(this._id)
|
|
28
|
+
}
|
|
29
|
+
text(): string {
|
|
30
|
+
return _creatorUtils.fetchToText(this._id)
|
|
31
|
+
}
|
|
32
|
+
dispose(): void {
|
|
33
|
+
_creatorUtils.disposeFetch(this._id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const fetch = (url: string, options: FetchOptions = {}): Promise<FetchResponse> =>
|
|
38
|
+
new Promise<FetchResponse>((resolve, reject) => {
|
|
39
|
+
_creatorUtils.fetch(url, options, (systemId, statusCode) => {
|
|
40
|
+
resolve(new FetchResponseImpl(systemId, statusCode))
|
|
41
|
+
}, reject)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const fetchLocal = (path: string): FetchResponse =>
|
|
45
|
+
new FetchResponseImpl(_creatorUtils.fetchLocal(path), 200)
|
|
46
|
+
|
|
47
|
+
/** A handle to a picked / fetched file (see openFilePicker). */
|
|
48
|
+
export class File {
|
|
49
|
+
readonly id: number
|
|
50
|
+
readonly name: string
|
|
51
|
+
readonly size: number
|
|
52
|
+
constructor(id: number, name: string, size: number) {
|
|
53
|
+
this.id = id
|
|
54
|
+
this.name = name
|
|
55
|
+
this.size = size
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type FormDataValue = string | number | boolean | File | FetchResponse
|
|
60
|
+
|
|
61
|
+
export class FormData {
|
|
62
|
+
private _entries: [string, FormDataValue, filename?: string][] = []
|
|
63
|
+
|
|
64
|
+
append(name: string, value: FormDataValue, filename?: string): void {
|
|
65
|
+
this._entries.push([ name, value, filename ?? (typeof value === "object" ? (value as any).name : undefined) ])
|
|
66
|
+
}
|
|
67
|
+
delete(name: string): void {
|
|
68
|
+
const index = this._entries.findIndex((i) => i[0] === name)
|
|
69
|
+
if (index >= 0) this._entries.splice(index, 1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Native file picker. Ported from worker/src/utils/openFilePicker.ts; resolves to File handle(s)
|
|
2
|
+
// (see runtime/fetch.ts) that can be appended to a FormData and uploaded.
|
|
3
|
+
|
|
4
|
+
import { File } from "./fetch"
|
|
5
|
+
|
|
6
|
+
type OpenFilePickerOptions = { accept?: string }
|
|
7
|
+
|
|
8
|
+
export function openFilePicker(options?: OpenFilePickerOptions & { multiple?: false }): Promise<File | null>
|
|
9
|
+
export function openFilePicker(options: OpenFilePickerOptions & { multiple: true }): Promise<File[]>
|
|
10
|
+
export function openFilePicker(
|
|
11
|
+
options: OpenFilePickerOptions & { multiple?: boolean } = {},
|
|
12
|
+
): Promise<File | File[] | null> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
_creatorUtils.openFilePicker((resp) => {
|
|
15
|
+
if (!resp || resp.length === 0) {
|
|
16
|
+
resolve(options.multiple ? [] : null)
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
if (options.multiple) resolve(resp.map((i: any) => new File(i[0], i[1], i[2])))
|
|
20
|
+
else resolve(new File(resp[0][0], resp[0][1], resp[0][2]))
|
|
21
|
+
}, reject, !!options.multiple, options.accept)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Keyboard/pointer input. Replaces worker's bare `inputKey(code)` global with a small `Input`
|
|
2
|
+
// namespace so the surface reads better and has room to grow (pointer, gamepad) without new globals.
|
|
3
|
+
|
|
4
|
+
export const Input = {
|
|
5
|
+
/**
|
|
6
|
+
* Is a key currently held? `code` is a KeyboardEvent.code-style string, e.g. 'ArrowRight',
|
|
7
|
+
* 'KeyW', 'Space'. Poll this inside setLoop for frame-independent movement.
|
|
8
|
+
*/
|
|
9
|
+
key(code: string): boolean {
|
|
10
|
+
return _creatorUtils.inputKey(code)
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Audio / video playback over the host. Ported from worker/src/MediaPlayer.ts, with the listener
|
|
2
|
+
// boilerplate folded into the shared Emitter. VideoPlayer.texture (a 3D Texture) is wired in Phase 4
|
|
3
|
+
// when the Texture type exists — see gl/.
|
|
4
|
+
//
|
|
5
|
+
// The host no longer routes end-of-playback events back by id through a Registry: each player hands
|
|
6
|
+
// the host a `_finished` callback at creation time. The host holds the JS closure directly — valid
|
|
7
|
+
// on every backend (web JS, Android quickjs, iOS JavaScriptCore) — and invokes it when a play-through
|
|
8
|
+
// completes, so there is no id table to keep in sync.
|
|
9
|
+
|
|
10
|
+
import { Emitter } from "../core/events"
|
|
11
|
+
import { Texture } from "../gl/Texture"
|
|
12
|
+
|
|
13
|
+
type MediaEvents = {
|
|
14
|
+
/** Fired at each loop boundary while `loop` is true. */
|
|
15
|
+
loopReached: () => void
|
|
16
|
+
/** Fired once when playback reaches the end while `loop` is false. */
|
|
17
|
+
completed: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
abstract class MediaPlayer extends Emitter<MediaEvents> {
|
|
21
|
+
protected _id = -1
|
|
22
|
+
protected _src = ""
|
|
23
|
+
private _playing = false
|
|
24
|
+
private _loop = false
|
|
25
|
+
private _volume = 1
|
|
26
|
+
private _disposed = false
|
|
27
|
+
|
|
28
|
+
// Every host-touching operation runs through this so a disposed player can never address a freed
|
|
29
|
+
// (and possibly reused) host slot.
|
|
30
|
+
private _assertAlive(): void {
|
|
31
|
+
if (this._disposed) throw new Error("MediaPlayer has been disposed")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
play(): void { this.playing = true }
|
|
35
|
+
pause(): void { this.playing = false }
|
|
36
|
+
|
|
37
|
+
get playing(): boolean { return this._playing }
|
|
38
|
+
set playing(value: boolean) {
|
|
39
|
+
this._assertAlive()
|
|
40
|
+
this._playing = value
|
|
41
|
+
_creatorUtils.updateMediaPlayerPlaying(this._id, value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get volume(): number { return this._volume }
|
|
45
|
+
set volume(value: number) {
|
|
46
|
+
this._assertAlive()
|
|
47
|
+
this._volume = value
|
|
48
|
+
_creatorUtils.updateMediaPlayerVolume(this._id, value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get loop(): boolean { return this._loop }
|
|
52
|
+
set loop(value: boolean) {
|
|
53
|
+
this._assertAlive()
|
|
54
|
+
this._loop = value
|
|
55
|
+
_creatorUtils.updateMediaPlayerLoop(this._id, value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Current playback position, in seconds. Settable to seek. */
|
|
59
|
+
get time(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerTime(this._id) }
|
|
60
|
+
set time(value: number) { this._assertAlive(); _creatorUtils.setMediaPlayerTime(this._id, value) }
|
|
61
|
+
|
|
62
|
+
/** Total media length in seconds (0 until metadata has loaded). */
|
|
63
|
+
get duration(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerDuration(this._id) }
|
|
64
|
+
|
|
65
|
+
/** True once {@link dispose} has been called; the player is unusable afterwards. */
|
|
66
|
+
get disposed(): boolean { return this._disposed }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Release the underlying host media resource (decoder, network, buffers) immediately and detach
|
|
70
|
+
* all listeners. Idempotent. Any further use of the player throws.
|
|
71
|
+
*/
|
|
72
|
+
dispose(): void {
|
|
73
|
+
if (this._disposed) return
|
|
74
|
+
this._disposed = true
|
|
75
|
+
this._playing = false
|
|
76
|
+
this.clearListeners()
|
|
77
|
+
_creatorUtils.removeMediaPlayer(this._id)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @internal — the host calls this when a play-through finishes. */
|
|
81
|
+
protected _finished(): void {
|
|
82
|
+
if (this._disposed) return
|
|
83
|
+
if (this._loop) {
|
|
84
|
+
this.dispatch("loopReached")
|
|
85
|
+
} else {
|
|
86
|
+
this._playing = false
|
|
87
|
+
this.dispatch("completed")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class AudioPlayer extends MediaPlayer {
|
|
93
|
+
constructor(src?: string) {
|
|
94
|
+
super()
|
|
95
|
+
this._src = src ?? ""
|
|
96
|
+
this._id = _creatorUtils.createAudioPlayer(this._src, () => this._finished())
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class VideoPlayer extends MediaPlayer {
|
|
101
|
+
constructor(src?: string) {
|
|
102
|
+
super()
|
|
103
|
+
this._src = src ?? ""
|
|
104
|
+
this._id = _creatorUtils.createVideoPlayer(this._src, () => this._finished())
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A 3D Texture that samples this video (e.g. for a Material.video()). */
|
|
108
|
+
get texture(): Texture {
|
|
109
|
+
return new Texture(0, 0, _creator.createMediaPlayerTexture(this._id))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Small platform globals that are real (not engine-specific). Grown in Phase 1.
|
|
2
|
+
|
|
3
|
+
/** Show a transient toast notification. */
|
|
4
|
+
export const toast = (msg: string): void => _creatorUI.toast(msg)
|
|
5
|
+
|
|
6
|
+
export type SvgSourceValue = {
|
|
7
|
+
readonly svg: string
|
|
8
|
+
tintColor: string | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Wrap raw SVG XML so it can be used as an image source. */
|
|
12
|
+
export const SvgSource = (svg: string): SvgSourceValue => ({ svg, tintColor: null })
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// WebSocket over the host bridge. Same event surface as the browser's (open/message/close/error)
|
|
2
|
+
// via the shared Emitter. Ported from worker/src/WebSocket.ts.
|
|
3
|
+
|
|
4
|
+
import { Emitter } from "../core/events"
|
|
5
|
+
|
|
6
|
+
type SocketEvents = {
|
|
7
|
+
open: () => void
|
|
8
|
+
message: (data: any) => void
|
|
9
|
+
close: (code?: number) => void
|
|
10
|
+
error: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class WebSocket extends Emitter<SocketEvents> {
|
|
14
|
+
private _id: number | null = null
|
|
15
|
+
|
|
16
|
+
constructor(url: string, headers?: Record<string, string>) {
|
|
17
|
+
super()
|
|
18
|
+
this._id = _creatorUtils.websocketOpen(url, (channel, data) => this._onMessage(channel, data), headers)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
send(message: string | ArrayBuffer): void {
|
|
22
|
+
if (this._id !== null) _creatorUtils.websocketSend(this._id, message)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
close(): void {
|
|
26
|
+
if (this._id !== null) {
|
|
27
|
+
_creatorUtils.websocketClose(this._id)
|
|
28
|
+
this._id = null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private _onMessage(channel: string, data: any): void {
|
|
33
|
+
if (data !== undefined && data !== null) this.dispatch(channel as keyof SocketEvents, data)
|
|
34
|
+
else this.dispatch(channel as keyof SocketEvents)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Key/value persistence backed by the host. Same shape as the web localStorage subset projects use.
|
|
2
|
+
|
|
3
|
+
export const localStorage = {
|
|
4
|
+
getItem(key: string): string | null {
|
|
5
|
+
return _creatorUtils.localStorageGetValue(key)
|
|
6
|
+
},
|
|
7
|
+
setItem(key: string, value: string): void {
|
|
8
|
+
_creatorUtils.localStorageSetValue(key, value)
|
|
9
|
+
},
|
|
10
|
+
removeItem(key: string): void {
|
|
11
|
+
_creatorUtils.localStorageRemoveValue(key)
|
|
12
|
+
},
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Pointer event objects delivered to 3D scene/node touch handlers. Ported from worker/src/TouchEvent.ts.
|
|
2
|
+
// The dispatch wiring (reading the active scene, hit entity) is set up in Phase 4 with the 3D Scene;
|
|
3
|
+
// these classes are the injected globals user code receives. `target` is the hit Node (typed loosely
|
|
4
|
+
// here to avoid a forward dependency on gl/).
|
|
5
|
+
|
|
6
|
+
export class ClickEvent<T = any> {
|
|
7
|
+
readonly type = "click"
|
|
8
|
+
readonly pointerId: number
|
|
9
|
+
readonly clientX: number
|
|
10
|
+
readonly clientY: number
|
|
11
|
+
target!: T
|
|
12
|
+
constructor(clientX: number, clientY: number, pointerId: number) {
|
|
13
|
+
this.clientX = clientX
|
|
14
|
+
this.clientY = clientY
|
|
15
|
+
this.pointerId = pointerId
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TrackHandler {
|
|
20
|
+
onMove?(pos: { clientX: number, clientY: number, deltaX: number, deltaY: number }): void
|
|
21
|
+
onEnd?(pos: { clientX: number, clientY: number, deltaX: number, deltaY: number }): void
|
|
22
|
+
onCancel?(): void
|
|
23
|
+
claim?: boolean | "pan-x" | "pan-y" | "pan-down" | "pan-up" | "pan-left" | "pan-right"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TouchStartEvent<T = any> {
|
|
27
|
+
readonly type = "touchstart"
|
|
28
|
+
readonly pointerId: number
|
|
29
|
+
readonly clientX: number
|
|
30
|
+
readonly clientY: number
|
|
31
|
+
target!: T
|
|
32
|
+
/** @internal — read by the dispatch system after the handler runs. */
|
|
33
|
+
_trackHandler?: TrackHandler
|
|
34
|
+
constructor(clientX: number, clientY: number, pointerId: number) {
|
|
35
|
+
this.clientX = clientX
|
|
36
|
+
this.clientY = clientY
|
|
37
|
+
this.pointerId = pointerId
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Capture subsequent moves/end for this pointer (drag/pan gestures). */
|
|
41
|
+
track(handler: TrackHandler): void {
|
|
42
|
+
this._trackHandler = handler
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Renderer-side contract + per-renderer status (DOM / canvas-ui / desktop) for every component
|
|
2
|
+
// below is tracked in packages/canvas-ui/COMPONENTS.md.
|
|
3
|
+
export { UIButton, type UIButtonStyle } from './UIButton'
|
|
4
|
+
export { UIScreen, type UIScreenStyle } from './UIScreen'
|
|
5
|
+
export { UISpacer, type UISpacerStyle } from './UISpacer'
|
|
6
|
+
export { UIText, type UITextStyle } from './UIText'
|
|
7
|
+
export { registerFont } from './fonts'
|
|
8
|
+
export { UIImage, type UIImageStyle } from './UIImage'
|
|
9
|
+
export { UIVideo, type UIVideoStyle } from './UIVideo'
|
|
10
|
+
export { UIInput, UITextArea, type UIInputStyle } from './UIInput'
|
|
11
|
+
|
|
12
|
+
export { UIWidget, type UIWidgetStyle } from './UIWidget'
|
|
13
|
+
|
|
14
|
+
export { UIRow, UIColumn, UIBox, type UIContainerStyle } from './UIContainer'
|
|
15
|
+
export { UIScrollable, type UIScrollableStyle } from './UIScrollable'
|
|
16
|
+
|
|
17
|
+
export { UIVirtualizedList } from './UIVirtualizedList'
|
|
18
|
+
|
|
19
|
+
export { Router } from './router'
|