@bornengine/engine 0.4.16 → 0.5.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.
Files changed (62) hide show
  1. package/README.md +24 -14
  2. package/native/android/Cargo.lock +577 -28
  3. package/native/android/Cargo.toml +2 -0
  4. package/native/android/src/lib.rs +39 -3
  5. package/native/ios/Cargo.lock +573 -24
  6. package/native/ios/Cargo.toml +2 -0
  7. package/native/ios/src/lib.rs +72 -9
  8. package/native/ios/src/text_input.rs +48 -0
  9. package/native/linux/Cargo.lock +623 -35
  10. package/native/linux/Cargo.toml +3 -0
  11. package/native/macos/Cargo.lock +630 -35
  12. package/native/macos/Cargo.toml +3 -0
  13. package/native/shared/Cargo.lock +430 -27
  14. package/native/shared/Cargo.toml +5 -0
  15. package/native/shared/build.rs +41 -1
  16. package/native/shared/src/engine.rs +54 -2
  17. package/native/shared/src/ffi.rs +54 -1
  18. package/native/shared/src/ffi_core/mod.rs +2 -0
  19. package/native/shared/src/ffi_core/ui.rs +182 -0
  20. package/native/shared/src/input.rs +317 -12
  21. package/native/shared/src/lib.rs +1 -0
  22. package/native/shared/src/renderer/mod.rs +47 -201
  23. package/native/shared/src/renderer/texture_store.rs +5 -0
  24. package/native/shared/src/renderer/ui_pass.rs +742 -0
  25. package/native/shared/src/textures.rs +2 -0
  26. package/native/shared/src/ui/commands.rs +213 -0
  27. package/native/shared/src/ui/egui.rs +1282 -0
  28. package/native/shared/src/ui/imgui.rs +1056 -0
  29. package/native/shared/src/ui/input.rs +173 -0
  30. package/native/shared/src/ui/mod.rs +252 -0
  31. package/native/shared/src/ui/responses.rs +61 -0
  32. package/native/tvos/Cargo.lock +576 -27
  33. package/native/tvos/Cargo.toml +2 -0
  34. package/native/visionos/Cargo.lock +571 -22
  35. package/native/visionos/Cargo.toml +2 -0
  36. package/native/watchos/src/ffi_stubs.rs +27 -0
  37. package/native/web/Cargo.lock +346 -11
  38. package/native/web/Cargo.toml +6 -0
  39. package/native/web/bloom_glue.js +84 -11
  40. package/native/web/build.sh +150 -34
  41. package/native/web/src/lib.rs +2 -0
  42. package/native/web/src/ui_ffi.rs +148 -0
  43. package/native/windows/Cargo.lock +638 -38
  44. package/native/windows/Cargo.toml +3 -0
  45. package/native/windows/src/lib.rs +50 -1
  46. package/native/windows/src/windows_text.rs +69 -0
  47. package/package.json +91 -2
  48. package/src/debug-ui/index.ts +4 -0
  49. package/src/debug-ui/types.ts +1 -0
  50. package/src/game/adapters/audio-source-component.ts +67 -0
  51. package/src/game/adapters/rigid-body-component.ts +69 -0
  52. package/src/game/adapters/scene-node-component.ts +49 -0
  53. package/src/game/game-component.ts +89 -0
  54. package/src/game/game-object.ts +338 -0
  55. package/src/game/game-scene.ts +303 -0
  56. package/src/game/index.ts +7 -0
  57. package/src/game/transform.ts +315 -0
  58. package/src/index.ts +27 -0
  59. package/src/ui/api.ts +216 -0
  60. package/src/ui/index.ts +5 -0
  61. package/src/ui/opcodes.ts +66 -0
  62. package/src/ui/types.ts +82 -0
@@ -0,0 +1,315 @@
1
+ import { Mat4, Quat, Vec3 } from '../core/types';
2
+ import {
3
+ mat4Multiply,
4
+ quatMultiply,
5
+ quatNormalize,
6
+ quatToMat4,
7
+ } from '../math';
8
+
9
+ export interface TransformOptions {
10
+ position?: Vec3;
11
+ rotation?: Quat;
12
+ scale?: Vec3;
13
+ }
14
+
15
+ /** @internal Used by GameObject when preserving world transforms. */
16
+ export interface TransformTRS {
17
+ position: Vec3;
18
+ rotation: Quat;
19
+ scale: Vec3;
20
+ }
21
+
22
+ function copyVec3(value: Vec3): Vec3 {
23
+ return { x: value.x, y: value.y, z: value.z };
24
+ }
25
+
26
+ function copyQuat(value: Quat): Quat {
27
+ return { x: value.x, y: value.y, z: value.z, w: value.w };
28
+ }
29
+
30
+ function dot(a: Vec3, b: Vec3): number {
31
+ return a.x * b.x + a.y * b.y + a.z * b.z;
32
+ }
33
+
34
+ function cross(a: Vec3, b: Vec3): Vec3 {
35
+ return {
36
+ x: a.y * b.z - a.z * b.y,
37
+ y: a.z * b.x - a.x * b.z,
38
+ z: a.x * b.y - a.y * b.x,
39
+ };
40
+ }
41
+
42
+ function normalizeVector(value: Vec3): Vec3 {
43
+ const length = Math.sqrt(dot(value, value));
44
+ if (length === 0) return { x: 0, y: 0, z: 0 };
45
+ return { x: value.x / length, y: value.y / length, z: value.z / length };
46
+ }
47
+
48
+ function perpendicularTo(value: Vec3): Vec3 {
49
+ const basis = Math.abs(value.x) < 0.8
50
+ ? { x: 1, y: 0, z: 0 }
51
+ : { x: 0, y: 1, z: 0 };
52
+ const projection = dot(value, basis);
53
+ return normalizeVector({
54
+ x: basis.x - value.x * projection,
55
+ y: basis.y - value.y * projection,
56
+ z: basis.z - value.z * projection,
57
+ });
58
+ }
59
+
60
+ function rotationFromAxes(xAxis: Vec3, yAxis: Vec3, zAxis: Vec3): Quat {
61
+ const m00 = xAxis.x;
62
+ const m01 = yAxis.x;
63
+ const m02 = zAxis.x;
64
+ const m10 = xAxis.y;
65
+ const m11 = yAxis.y;
66
+ const m12 = zAxis.y;
67
+ const m20 = xAxis.z;
68
+ const m21 = yAxis.z;
69
+ const m22 = zAxis.z;
70
+ const trace = m00 + m11 + m22;
71
+
72
+ if (trace > 0) {
73
+ const s = Math.sqrt(trace + 1) * 2;
74
+ return quatNormalize({
75
+ x: (m21 - m12) / s,
76
+ y: (m02 - m20) / s,
77
+ z: (m10 - m01) / s,
78
+ w: 0.25 * s,
79
+ });
80
+ }
81
+
82
+ if (m00 > m11 && m00 > m22) {
83
+ const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
84
+ return quatNormalize({
85
+ x: 0.25 * s,
86
+ y: (m01 + m10) / s,
87
+ z: (m02 + m20) / s,
88
+ w: (m21 - m12) / s,
89
+ });
90
+ }
91
+
92
+ if (m11 > m22) {
93
+ const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
94
+ return quatNormalize({
95
+ x: (m01 + m10) / s,
96
+ y: 0.25 * s,
97
+ z: (m12 + m21) / s,
98
+ w: (m02 - m20) / s,
99
+ });
100
+ }
101
+
102
+ const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
103
+ return quatNormalize({
104
+ x: (m02 + m20) / s,
105
+ y: (m12 + m21) / s,
106
+ z: 0.25 * s,
107
+ w: (m10 - m01) / s,
108
+ });
109
+ }
110
+
111
+ /** @internal Inverts an affine transform, returning null for singular input. */
112
+ export function invertTransformMatrix(matrix: Mat4): Mat4 | null {
113
+ if (Math.abs(matrix[3]) > 0.00001 || Math.abs(matrix[7]) > 0.00001 ||
114
+ Math.abs(matrix[11]) > 0.00001 || Math.abs(matrix[15] - 1) > 0.00001) {
115
+ return null;
116
+ }
117
+
118
+ const a = matrix[0], b = matrix[4], c = matrix[8];
119
+ const d = matrix[1], e = matrix[5], f = matrix[9];
120
+ const g = matrix[2], h = matrix[6], i = matrix[10];
121
+ const determinant = a * (e * i - f * h) - b * (d * i - f * g) +
122
+ c * (d * h - e * g);
123
+ if (Math.abs(determinant) < 0.0000000001) return null;
124
+
125
+ const inverseDeterminant = 1 / determinant;
126
+ const i00 = (e * i - f * h) * inverseDeterminant;
127
+ const i01 = (c * h - b * i) * inverseDeterminant;
128
+ const i02 = (b * f - c * e) * inverseDeterminant;
129
+ const i10 = (f * g - d * i) * inverseDeterminant;
130
+ const i11 = (a * i - c * g) * inverseDeterminant;
131
+ const i12 = (c * d - a * f) * inverseDeterminant;
132
+ const i20 = (d * h - e * g) * inverseDeterminant;
133
+ const i21 = (b * g - a * h) * inverseDeterminant;
134
+ const i22 = (a * e - b * d) * inverseDeterminant;
135
+ const tx = matrix[12], ty = matrix[13], tz = matrix[14];
136
+
137
+ return [
138
+ i00, i10, i20, 0,
139
+ i01, i11, i21, 0,
140
+ i02, i12, i22, 0,
141
+ -(i00 * tx + i01 * ty + i02 * tz),
142
+ -(i10 * tx + i11 * ty + i12 * tz),
143
+ -(i20 * tx + i21 * ty + i22 * tz),
144
+ 1,
145
+ ];
146
+ }
147
+
148
+ /** @internal Decomposes an affine matrix when it can be represented as TRS. */
149
+ export function decomposeTransformMatrix(matrix: Mat4): TransformTRS | null {
150
+ const epsilon = 0.00001;
151
+ if (Math.abs(matrix[3]) > epsilon || Math.abs(matrix[7]) > epsilon ||
152
+ Math.abs(matrix[11]) > epsilon || Math.abs(matrix[15] - 1) > epsilon) {
153
+ return null;
154
+ }
155
+
156
+ let scaleX = Math.sqrt(matrix[0] * matrix[0] + matrix[1] * matrix[1] + matrix[2] * matrix[2]);
157
+ const scaleY = Math.sqrt(matrix[4] * matrix[4] + matrix[5] * matrix[5] + matrix[6] * matrix[6]);
158
+ const scaleZ = Math.sqrt(matrix[8] * matrix[8] + matrix[9] * matrix[9] + matrix[10] * matrix[10]);
159
+ let xAxis: Vec3 | null = scaleX > epsilon
160
+ ? { x: matrix[0] / scaleX, y: matrix[1] / scaleX, z: matrix[2] / scaleX }
161
+ : null;
162
+ let yAxis: Vec3 | null = scaleY > epsilon
163
+ ? { x: matrix[4] / scaleY, y: matrix[5] / scaleY, z: matrix[6] / scaleY }
164
+ : null;
165
+ let zAxis: Vec3 | null = scaleZ > epsilon
166
+ ? { x: matrix[8] / scaleZ, y: matrix[9] / scaleZ, z: matrix[10] / scaleZ }
167
+ : null;
168
+
169
+ if (xAxis !== null && yAxis !== null && Math.abs(dot(xAxis, yAxis)) > epsilon) return null;
170
+ if (xAxis !== null && zAxis !== null && Math.abs(dot(xAxis, zAxis)) > epsilon) return null;
171
+ if (yAxis !== null && zAxis !== null && Math.abs(dot(yAxis, zAxis)) > epsilon) return null;
172
+
173
+ if (xAxis !== null && yAxis !== null && zAxis !== null) {
174
+ const orientation = dot(cross(xAxis, yAxis), zAxis);
175
+ if (Math.abs(Math.abs(orientation) - 1) > epsilon) return null;
176
+ if (orientation < 0) {
177
+ scaleX = -scaleX;
178
+ xAxis = { x: -xAxis.x, y: -xAxis.y, z: -xAxis.z };
179
+ }
180
+ } else if (xAxis !== null && yAxis !== null) {
181
+ zAxis = normalizeVector(cross(xAxis, yAxis));
182
+ } else if (xAxis !== null && zAxis !== null) {
183
+ yAxis = normalizeVector(cross(zAxis, xAxis));
184
+ } else if (yAxis !== null && zAxis !== null) {
185
+ xAxis = normalizeVector(cross(yAxis, zAxis));
186
+ } else if (xAxis !== null) {
187
+ yAxis = perpendicularTo(xAxis);
188
+ zAxis = normalizeVector(cross(xAxis, yAxis));
189
+ } else if (yAxis !== null) {
190
+ xAxis = perpendicularTo(yAxis);
191
+ zAxis = normalizeVector(cross(xAxis, yAxis));
192
+ } else if (zAxis !== null) {
193
+ xAxis = perpendicularTo(zAxis);
194
+ yAxis = normalizeVector(cross(zAxis, xAxis));
195
+ } else {
196
+ xAxis = { x: 1, y: 0, z: 0 };
197
+ yAxis = { x: 0, y: 1, z: 0 };
198
+ zAxis = { x: 0, y: 0, z: 1 };
199
+ }
200
+
201
+ return {
202
+ position: { x: matrix[12], y: matrix[13], z: matrix[14] },
203
+ rotation: rotationFromAxes(xAxis, yAxis, zAxis),
204
+ scale: { x: scaleX, y: scaleY, z: scaleZ },
205
+ };
206
+ }
207
+
208
+ export class Transform {
209
+ position: Vec3;
210
+ rotation: Quat;
211
+ scale: Vec3;
212
+ private parentTransform: Transform | null = null;
213
+
214
+ constructor(options: TransformOptions = {}) {
215
+ this.position = copyVec3(options.position || { x: 0, y: 0, z: 0 });
216
+ this.rotation = copyQuat(options.rotation || { x: 0, y: 0, z: 0, w: 1 });
217
+ this.scale = copyVec3(options.scale || { x: 1, y: 1, z: 1 });
218
+ }
219
+
220
+ get localMatrix(): Mat4 {
221
+ const matrix = quatToMat4(quatNormalize(this.rotation));
222
+ matrix[0] *= this.scale.x;
223
+ matrix[1] *= this.scale.x;
224
+ matrix[2] *= this.scale.x;
225
+ matrix[4] *= this.scale.y;
226
+ matrix[5] *= this.scale.y;
227
+ matrix[6] *= this.scale.y;
228
+ matrix[8] *= this.scale.z;
229
+ matrix[9] *= this.scale.z;
230
+ matrix[10] *= this.scale.z;
231
+ matrix[12] = this.position.x;
232
+ matrix[13] = this.position.y;
233
+ matrix[14] = this.position.z;
234
+ return matrix;
235
+ }
236
+
237
+ get worldMatrix(): Mat4 {
238
+ const local = this.localMatrix;
239
+ if (this.parentTransform === null) return local;
240
+ return mat4Multiply(this.parentTransform.worldMatrix, local);
241
+ }
242
+
243
+ get worldPosition(): Vec3 {
244
+ const matrix = this.worldMatrix;
245
+ return { x: matrix[12], y: matrix[13], z: matrix[14] };
246
+ }
247
+
248
+ get worldRotation(): Quat {
249
+ const local = quatNormalize(this.rotation);
250
+ if (this.parentTransform === null) return copyQuat(local);
251
+ return quatNormalize(quatMultiply(this.parentTransform.worldRotation, local));
252
+ }
253
+
254
+ get worldScale(): Vec3 {
255
+ if (this.parentTransform === null) return copyVec3(this.scale);
256
+ const parentScale = this.parentTransform.worldScale;
257
+ return {
258
+ x: parentScale.x * this.scale.x,
259
+ y: parentScale.y * this.scale.y,
260
+ z: parentScale.z * this.scale.z,
261
+ };
262
+ }
263
+
264
+ setWorldPosition(position: Vec3): boolean {
265
+ if (this.parentTransform === null) {
266
+ this.position = copyVec3(position);
267
+ return true;
268
+ }
269
+
270
+ const inverse = invertTransformMatrix(this.parentTransform.worldMatrix);
271
+ if (inverse === null) return false;
272
+ this.position = {
273
+ x: inverse[0] * position.x + inverse[4] * position.y + inverse[8] * position.z + inverse[12],
274
+ y: inverse[1] * position.x + inverse[5] * position.y + inverse[9] * position.z + inverse[13],
275
+ z: inverse[2] * position.x + inverse[6] * position.y + inverse[10] * position.z + inverse[14],
276
+ };
277
+ return true;
278
+ }
279
+
280
+ setWorldRotation(rotation: Quat): boolean {
281
+ const desired = quatNormalize(rotation);
282
+ if (this.parentTransform === null) {
283
+ this.rotation = copyQuat(desired);
284
+ return true;
285
+ }
286
+
287
+ const parentRotation = this.parentTransform.worldRotation;
288
+ const inverseParent: Quat = {
289
+ x: -parentRotation.x,
290
+ y: -parentRotation.y,
291
+ z: -parentRotation.z,
292
+ w: parentRotation.w,
293
+ };
294
+ this.rotation = quatNormalize(quatMultiply(inverseParent, desired));
295
+ return true;
296
+ }
297
+
298
+ /** @internal GameObject is the sole hierarchy owner of this Transform. */
299
+ _setParent(parent: Transform | null): boolean {
300
+ let current = parent;
301
+ while (current !== null) {
302
+ if (current === this) return false;
303
+ current = current.parentTransform;
304
+ }
305
+ this.parentTransform = parent;
306
+ return true;
307
+ }
308
+
309
+ /** @internal Applies a prevalidated TRS conversion atomically. */
310
+ _setLocalTRS(position: Vec3, rotation: Quat, scale: Vec3): void {
311
+ this.position = copyVec3(position);
312
+ this.rotation = copyQuat(rotation);
313
+ this.scale = copyVec3(scale);
314
+ }
315
+ }
package/src/index.ts CHANGED
@@ -169,3 +169,30 @@ export type {
169
169
  RigidBodyHandle, ColliderHandle, JointHandle,
170
170
  PhysicsRayHit, CollisionInfo,
171
171
  } from './physics/index';
172
+
173
+ export {
174
+ AudioSourceComponent,
175
+ GameComponent,
176
+ GameObject,
177
+ GameScene,
178
+ RigidBodyComponent,
179
+ SceneNodeComponent,
180
+ Transform,
181
+ } from './game/index';
182
+
183
+ export type {
184
+ AudioSourceComponentOptions,
185
+ GameComponentType,
186
+ GameObjectOptions,
187
+ ParentOptions,
188
+ RigidBodyComponentOptions,
189
+ RigidBodyMotionType,
190
+ SceneNodeComponentOptions,
191
+ TransformOptions,
192
+ } from './game/index';
193
+
194
+ export { ui } from './ui/index';
195
+ export type { UiApi, UiId, UiResponse, UiColor } from './ui/index';
196
+
197
+ export { debugUi } from './debug-ui/index';
198
+ export type { DebugUiApi } from './debug-ui/index';
package/src/ui/api.ts ADDED
@@ -0,0 +1,216 @@
1
+ import type { Color, Texture } from '../core/types';
2
+ import { UiOpcode, type UiBackendId } from './opcodes';
3
+ import type { UiApi, UiId, UiResponse } from './types';
4
+
5
+ declare function bloom_ui_command(backend: number, opcode: number, id: number, a: number, b: number, c: number, d: number, text: number): number;
6
+ declare function bloom_ui_scratch_reset(backend: number): void;
7
+ declare function bloom_ui_scratch_push_f64(backend: number, value: number): void;
8
+ declare function bloom_ui_scratch_command(backend: number, opcode: number, id: number, count: number, text: number): number;
9
+ declare function bloom_ui_inject_text(text: number): void;
10
+ declare function bloom_ui_response(backend: number, id: number, field: number): number;
11
+ declare function bloom_ui_response_text(backend: number, id: number): string;
12
+ declare function bloom_ui_is_available(backend: number): number;
13
+ declare function bloom_ui_wants_input(backend: number, kind: number): number;
14
+
15
+ const EMPTY_RESPONSE: UiResponse = {
16
+ present: false,
17
+ clicked: false,
18
+ changed: false,
19
+ hovered: false,
20
+ focused: false,
21
+ dragged: false,
22
+ value: 0,
23
+ text: '',
24
+ };
25
+
26
+ function textureRegistrationId(handle: number): UiId {
27
+ // Texture handles encode a generation above the low 32-bit slot index.
28
+ // The slot index is the bounded UI registration key; reusing a texture slot
29
+ // naturally replaces the stale generation in the native registration map.
30
+ return Math.trunc(handle % 0x1_0000_0000);
31
+ }
32
+
33
+ function colorValues(color: Color): number[] {
34
+ return [color.r, color.g, color.b, color.a];
35
+ }
36
+
37
+ export function createUiApi(backend: UiBackendId): UiApi {
38
+ function command(opcode: number, id: UiId, args: number[] = [], text = ''): void {
39
+ bloom_ui_command(
40
+ backend,
41
+ opcode,
42
+ id,
43
+ args[0] ?? 0,
44
+ args[1] ?? 0,
45
+ args[2] ?? 0,
46
+ args[3] ?? 0,
47
+ text as any,
48
+ );
49
+ }
50
+
51
+ function scratchCommand(opcode: number, id: UiId, values: number[], text = ''): void {
52
+ bloom_ui_scratch_reset(backend);
53
+ for (const value of values) bloom_ui_scratch_push_f64(backend, value);
54
+ bloom_ui_scratch_command(backend, opcode, id, values.length, text as any);
55
+ }
56
+
57
+ function response(id: UiId): UiResponse {
58
+ if (bloom_ui_response(backend, id, 6) < 0.5) return { ...EMPTY_RESPONSE };
59
+ return {
60
+ present: true,
61
+ clicked: bloom_ui_response(backend, id, 0) > 0.5,
62
+ changed: bloom_ui_response(backend, id, 1) > 0.5,
63
+ hovered: bloom_ui_response(backend, id, 2) > 0.5,
64
+ focused: bloom_ui_response(backend, id, 3) > 0.5,
65
+ dragged: bloom_ui_response(backend, id, 4) > 0.5,
66
+ value: bloom_ui_response(backend, id, 5),
67
+ text: bloom_ui_response_text(backend, id),
68
+ };
69
+ }
70
+
71
+ const api: UiApi = {
72
+ beginWindow(id, title, x = 16, y = 16, width = 320, height = 240) {
73
+ command(UiOpcode.SetWindowPosition, id, [x, y, 0]);
74
+ command(UiOpcode.SetWindowSize, id, [width, height, 0]);
75
+ command(UiOpcode.BeginWindow, id, [], title);
76
+ },
77
+ endWindow(id = 0) { command(UiOpcode.EndWindow, id); },
78
+ setWindowPosition(id, x, y) { command(UiOpcode.SetWindowPosition, id, [x, y, 1]); },
79
+ setWindowSize(id, width, height) { command(UiOpcode.SetWindowSize, id, [width, height, 1]); },
80
+ beginPanel(id) { command(UiOpcode.BeginPanel, id); },
81
+ endPanel(id = 0) { command(UiOpcode.EndPanel, id); },
82
+ beginHorizontal(id) { command(UiOpcode.BeginHorizontal, id); },
83
+ endHorizontal(id = 0) { command(UiOpcode.EndHorizontal, id); },
84
+ beginVertical(id) { command(UiOpcode.BeginVertical, id); },
85
+ endVertical(id = 0) { command(UiOpcode.EndVertical, id); },
86
+ spacing(amount = 8) { command(UiOpcode.Spacing, 0, [amount]); },
87
+ separator() { command(UiOpcode.Separator, 0); },
88
+ label(id, text) { command(UiOpcode.Label, id, [], text); },
89
+ link(id, text) {
90
+ command(UiOpcode.Link, id, [], text);
91
+ return response(id).clicked;
92
+ },
93
+ button(id, text) {
94
+ command(UiOpcode.Button, id, [], text);
95
+ return response(id).clicked;
96
+ },
97
+ checkbox(id, label, checked) {
98
+ command(UiOpcode.Checkbox, id, [checked ? 1 : 0], label);
99
+ const result = response(id);
100
+ return result.present ? result.value > 0.5 : checked;
101
+ },
102
+ radioButton(id, label, selected) {
103
+ command(UiOpcode.RadioButton, id, [selected ? 1 : 0], label);
104
+ return response(id).clicked;
105
+ },
106
+ sliderFloat(id, label, value, min, max) {
107
+ command(UiOpcode.SliderFloat, id, [value, min, max], label);
108
+ const result = response(id);
109
+ return result.present ? result.value : value;
110
+ },
111
+ sliderInt(id, label, value, min, max) {
112
+ command(UiOpcode.SliderInt, id, [value, min, max], label);
113
+ const result = response(id);
114
+ return result.present ? Math.round(result.value) : value;
115
+ },
116
+ dragFloat(id, prefix, value, speed = 0.1, min = Number.NaN, max = Number.NaN) {
117
+ command(UiOpcode.DragFloat, id, [value, speed, min, max], prefix);
118
+ const result = response(id);
119
+ return result.present ? result.value : value;
120
+ },
121
+ textEditSingleline(id, label, value) {
122
+ command(UiOpcode.Label, 0, [], label);
123
+ command(UiOpcode.TextEdit, id, [], value);
124
+ const result = response(id);
125
+ return result.present ? result.text : value;
126
+ },
127
+ beginCombo(id, label, selectedText) {
128
+ command(UiOpcode.Label, 0, [], label);
129
+ command(UiOpcode.Combo, id, [], selectedText);
130
+ },
131
+ endCombo(id = 0) { command(UiOpcode.EndCombo, id); },
132
+ selectable(id, label, selected) {
133
+ command(UiOpcode.Selectable, id, [selected ? 1 : 0], label);
134
+ return response(id).clicked;
135
+ },
136
+ beginCollapsingHeader(id, label) {
137
+ command(UiOpcode.CollapsingHeader, id, [], label);
138
+ return response(id).value > 0.5;
139
+ },
140
+ endCollapsingHeader(id = 0) { command(UiOpcode.EndCollapsingHeader, id); },
141
+ beginScrollArea(id) { command(UiOpcode.BeginScrollArea, id); },
142
+ endScrollArea(id = 0) { command(UiOpcode.EndScrollArea, id); },
143
+ beginTabBar(id) { command(UiOpcode.BeginTabBar, id); },
144
+ endTabBar(id = 0) { command(UiOpcode.EndTabBar, id); },
145
+ beginTabItem(id, label) {
146
+ command(UiOpcode.BeginTabItem, id, [], label);
147
+ return response(id).value > 0.5;
148
+ },
149
+ endTabItem(id = 0) { command(UiOpcode.EndTabItem, id); },
150
+ progressBar(id, fraction, label = '') { command(UiOpcode.ProgressBar, id, [fraction], label); },
151
+ registerTexture(texture) {
152
+ command(UiOpcode.RegisterTexture, textureRegistrationId(texture.handle), [texture.handle]);
153
+ return texture.handle;
154
+ },
155
+ image(id, texture, width, height) {
156
+ const handle = typeof texture === 'number' ? texture : api.registerTexture(texture);
157
+ const imageWidth = width ?? (typeof texture === 'number' ? 0 : texture.width);
158
+ const imageHeight = height ?? (typeof texture === 'number' ? 0 : texture.height);
159
+ command(UiOpcode.Image, id, [handle, imageWidth, imageHeight]);
160
+ },
161
+ paintLine(id, x1, y1, x2, y2, color, thickness = 1) {
162
+ scratchCommand(UiOpcode.PaintLine, id, [x1, y1, x2, y2, ...colorValues(color), thickness]);
163
+ },
164
+ paintRect(id, x, y, width, height, color) {
165
+ scratchCommand(UiOpcode.PaintRect, id, [x, y, width, height, ...colorValues(color)]);
166
+ },
167
+ paintCircle(id, x, y, radius, color, thickness = 1) {
168
+ scratchCommand(UiOpcode.PaintCircle, id, [x, y, radius, ...colorValues(color), thickness]);
169
+ },
170
+ paintText(id, text, x, y, size, color) {
171
+ scratchCommand(UiOpcode.PaintText, id, [x, y, size, ...colorValues(color)], text);
172
+ },
173
+ paintPolyline(id, points, color, thickness = 1) {
174
+ if (points.length < 2) return;
175
+ const values: number[] = [];
176
+ for (const point of points) values.push(point.x, point.y);
177
+ scratchCommand(UiOpcode.PaintPolyline, id, [...values, ...colorValues(color), thickness]);
178
+ },
179
+ paintPolygon(id, points, color, thickness = 1) {
180
+ if (points.length < 3) return;
181
+ const values: number[] = [];
182
+ for (const point of points) values.push(point.x, point.y);
183
+ scratchCommand(UiOpcode.PaintPolygon, id, [...values, ...colorValues(color), thickness]);
184
+ },
185
+ setTheme(theme) { command(UiOpcode.SetStyle, 0, [theme === 'light' ? 1 : 0, Number.NaN]); },
186
+ loadFont(id, family, fontBytes) {
187
+ scratchCommand(UiOpcode.LoadFont, id, fontBytes, family);
188
+ },
189
+ setFont(id) { command(UiOpcode.SetStyle, 0, [2, id]); },
190
+ beginMenuBar(id) { command(UiOpcode.BeginMenuBar, id); },
191
+ endMenuBar(id = 0) { command(UiOpcode.EndMenuBar, id); },
192
+ beginMenu(id, label) { command(UiOpcode.BeginMenu, id, [], label); },
193
+ endMenu(id = 0) { command(UiOpcode.EndMenu, id); },
194
+ menuItem(id, label) {
195
+ command(UiOpcode.MenuItem, id, [], label);
196
+ return response(id).clicked;
197
+ },
198
+ beginTreeNode(id, label) {
199
+ command(UiOpcode.TreeNode, id, [], label);
200
+ return response(id).value > 0.5;
201
+ },
202
+ endTreeNode(id = 0) { command(UiOpcode.TreePop, id); },
203
+ beginTable(id, columns) { command(UiOpcode.BeginTable, id, [columns]); },
204
+ endTable(id = 0) { command(UiOpcode.EndTable, id); },
205
+ tableNextRow() { command(UiOpcode.TableNextRow, 0); },
206
+ tableNextColumn() { command(UiOpcode.TableNextColumn, 0); },
207
+ demoWindow(id = 0) { command(UiOpcode.DemoWindow, id); },
208
+ metricsWindow(id = 0) { command(UiOpcode.MetricsWindow, id); },
209
+ response,
210
+ isAvailable() { return bloom_ui_is_available(backend) > 0.5; },
211
+ wantsPointerInput() { return bloom_ui_wants_input(backend, 0) > 0.5; },
212
+ wantsKeyboardInput() { return bloom_ui_wants_input(backend, 1) > 0.5; },
213
+ injectText(text) { bloom_ui_inject_text(text as any); },
214
+ };
215
+ return api;
216
+ }
@@ -0,0 +1,5 @@
1
+ import { createUiApi } from './api';
2
+
3
+ export const ui = createUiApi(0);
4
+ export type { UiApi, UiId, UiResponse, UiColor } from './types';
5
+ export { UiBackend, UiOpcode } from './opcodes';
@@ -0,0 +1,66 @@
1
+ export const UiBackend = {
2
+ Egui: 0,
3
+ DearImGui: 1,
4
+ } as const;
5
+
6
+ export const UiOpcode = {
7
+ BeginWindow: 1,
8
+ EndWindow: 2,
9
+ BeginPanel: 3,
10
+ EndPanel: 4,
11
+ BeginHorizontal: 5,
12
+ EndHorizontal: 6,
13
+ BeginVertical: 7,
14
+ EndVertical: 8,
15
+ SetWindowPosition: 9,
16
+ SetWindowSize: 10,
17
+ Spacing: 11,
18
+ Separator: 12,
19
+ Label: 13,
20
+ Link: 14,
21
+ Button: 15,
22
+ Checkbox: 16,
23
+ RadioButton: 17,
24
+ SliderFloat: 18,
25
+ SliderInt: 19,
26
+ DragFloat: 20,
27
+ TextEdit: 21,
28
+ Combo: 22,
29
+ Selectable: 23,
30
+ CollapsingHeader: 24,
31
+ BeginScrollArea: 25,
32
+ EndScrollArea: 26,
33
+ BeginTabBar: 27,
34
+ EndTabBar: 28,
35
+ BeginTabItem: 29,
36
+ EndTabItem: 30,
37
+ ProgressBar: 31,
38
+ Image: 32,
39
+ PaintLine: 33,
40
+ PaintRect: 34,
41
+ PaintCircle: 35,
42
+ PaintText: 36,
43
+ PaintPolyline: 37,
44
+ PaintPolygon: 38,
45
+ SetStyle: 39,
46
+ LoadFont: 40,
47
+ RegisterTexture: 41,
48
+ BeginMenuBar: 42,
49
+ EndMenuBar: 43,
50
+ BeginMenu: 44,
51
+ EndMenu: 45,
52
+ MenuItem: 46,
53
+ TreeNode: 47,
54
+ TreePop: 48,
55
+ BeginTable: 49,
56
+ EndTable: 50,
57
+ TableNextRow: 51,
58
+ TableNextColumn: 52,
59
+ PlotLines: 53,
60
+ DemoWindow: 54,
61
+ MetricsWindow: 55,
62
+ EndCombo: 56,
63
+ EndCollapsingHeader: 57,
64
+ } as const;
65
+
66
+ export type UiBackendId = typeof UiBackend[keyof typeof UiBackend];