@bornengine/engine 0.4.16 → 0.5.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.
- package/README.md +24 -14
- package/native/android/Cargo.lock +577 -28
- package/native/android/Cargo.toml +2 -0
- package/native/android/src/lib.rs +39 -3
- package/native/ios/Cargo.lock +573 -24
- package/native/ios/Cargo.toml +2 -0
- package/native/ios/src/lib.rs +72 -9
- package/native/ios/src/text_input.rs +48 -0
- package/native/linux/Cargo.lock +623 -35
- package/native/linux/Cargo.toml +3 -0
- package/native/macos/Cargo.lock +630 -35
- package/native/macos/Cargo.toml +3 -0
- package/native/shared/Cargo.lock +430 -27
- package/native/shared/Cargo.toml +5 -0
- package/native/shared/build.rs +41 -1
- package/native/shared/src/engine.rs +54 -2
- package/native/shared/src/ffi.rs +54 -1
- package/native/shared/src/ffi_core/mod.rs +2 -0
- package/native/shared/src/ffi_core/ui.rs +182 -0
- package/native/shared/src/input.rs +317 -12
- package/native/shared/src/lib.rs +1 -0
- package/native/shared/src/renderer/mod.rs +47 -201
- package/native/shared/src/renderer/texture_store.rs +5 -0
- package/native/shared/src/renderer/ui_pass.rs +742 -0
- package/native/shared/src/textures.rs +2 -0
- package/native/shared/src/ui/commands.rs +213 -0
- package/native/shared/src/ui/egui.rs +1282 -0
- package/native/shared/src/ui/imgui.rs +1056 -0
- package/native/shared/src/ui/input.rs +173 -0
- package/native/shared/src/ui/mod.rs +252 -0
- package/native/shared/src/ui/responses.rs +61 -0
- package/native/tvos/Cargo.lock +576 -27
- package/native/tvos/Cargo.toml +2 -0
- package/native/visionos/Cargo.lock +571 -22
- package/native/visionos/Cargo.toml +2 -0
- package/native/watchos/src/ffi_stubs.rs +27 -0
- package/native/web/Cargo.lock +346 -11
- package/native/web/Cargo.toml +6 -0
- package/native/web/bloom_glue.js +84 -11
- package/native/web/build.sh +150 -34
- package/native/web/src/lib.rs +2 -0
- package/native/web/src/ui_ffi.rs +148 -0
- package/native/windows/Cargo.lock +638 -38
- package/native/windows/Cargo.toml +3 -0
- package/native/windows/src/lib.rs +50 -1
- package/native/windows/src/windows_text.rs +69 -0
- package/package.json +91 -2
- package/src/debug-ui/index.ts +4 -0
- package/src/debug-ui/types.ts +1 -0
- package/src/game/adapters/audio-source-component.ts +67 -0
- package/src/game/adapters/rigid-body-component.ts +69 -0
- package/src/game/adapters/scene-node-component.ts +49 -0
- package/src/game/game-component.ts +89 -0
- package/src/game/game-object.ts +338 -0
- package/src/game/game-scene.ts +303 -0
- package/src/game/index.ts +7 -0
- package/src/game/transform.ts +315 -0
- package/src/index.ts +27 -0
- package/src/ui/api.ts +240 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/opcodes.ts +66 -0
- package/src/ui/types.ts +82 -0
package/native/shared/Cargo.toml
CHANGED
|
@@ -25,12 +25,17 @@ models3d = ["dep:gltf", "dep:image_dds"]
|
|
|
25
25
|
# beyond PNG" in the same sense as jpeg/bmp/tga (decode-only here; the
|
|
26
26
|
# encoder lives in tools/bloom-cook).
|
|
27
27
|
image-extras = ["image/jpeg", "image/bmp", "image/tga", "image/hdr", "dep:image_dds"]
|
|
28
|
+
debug-ui = ["dep:imgui", "dep:imgui-wgpu"]
|
|
28
29
|
|
|
29
30
|
[build-dependencies]
|
|
30
31
|
cmake = "0.1"
|
|
31
32
|
|
|
32
33
|
[dependencies]
|
|
33
34
|
wgpu = "29"
|
|
35
|
+
egui = "0.35"
|
|
36
|
+
egui-wgpu = "0.35"
|
|
37
|
+
imgui = { version = "0.12", optional = true, features = ["tables-api"] }
|
|
38
|
+
imgui-wgpu = { version = "0.28", optional = true }
|
|
34
39
|
raw-window-handle = "0.6"
|
|
35
40
|
fontdue = "0.9"
|
|
36
41
|
libc = "0.2"
|
package/native/shared/build.rs
CHANGED
|
@@ -53,6 +53,9 @@ fn build_jolt() {
|
|
|
53
53
|
let from_source = std::env::var_os("BLOOM_JOLT_FROM_SOURCE").is_some();
|
|
54
54
|
if !from_source {
|
|
55
55
|
if let Some(prebuilt_dir) = find_prebuilt_dir(&manifest_dir, &target_os, &target_arch) {
|
|
56
|
+
if target_os == "windows" {
|
|
57
|
+
stage_windows_prebuilt_for_perry(&prebuilt_dir, &shim_dir, &target_arch);
|
|
58
|
+
}
|
|
56
59
|
link_prebuilt(&prebuilt_dir, &target_os);
|
|
57
60
|
emit_cxx_runtime_link(&target_os);
|
|
58
61
|
return;
|
|
@@ -171,7 +174,12 @@ fn find_prebuilt_dir(
|
|
|
171
174
|
} else {
|
|
172
175
|
""
|
|
173
176
|
};
|
|
174
|
-
let
|
|
177
|
+
let os_token = if target_os == "windows" {
|
|
178
|
+
"win32"
|
|
179
|
+
} else {
|
|
180
|
+
target_os
|
|
181
|
+
};
|
|
182
|
+
let target_token = format!("{}-{}{}", os_token, arch_token, sim_suffix);
|
|
175
183
|
|
|
176
184
|
let candidates = std::iter::empty::<std::path::PathBuf>()
|
|
177
185
|
.chain(
|
|
@@ -224,6 +232,38 @@ fn walk_up_for_node_modules(
|
|
|
224
232
|
})
|
|
225
233
|
}
|
|
226
234
|
|
|
235
|
+
#[cfg(feature = "jolt")]
|
|
236
|
+
fn stage_windows_prebuilt_for_perry(
|
|
237
|
+
prebuilt_dir: &std::path::Path,
|
|
238
|
+
shim_dir: &std::path::Path,
|
|
239
|
+
target_arch: &str,
|
|
240
|
+
) {
|
|
241
|
+
let lib_dir = shim_dir
|
|
242
|
+
.join("build")
|
|
243
|
+
.join(format!("windows-{target_arch}"))
|
|
244
|
+
.join("lib");
|
|
245
|
+
std::fs::create_dir_all(&lib_dir)
|
|
246
|
+
.unwrap_or_else(|error| panic!("could not create {}: {error}", lib_dir.display()));
|
|
247
|
+
for name in ["bloom_jolt.lib", "Jolt.lib"] {
|
|
248
|
+
let source = prebuilt_dir.join(name);
|
|
249
|
+
let destination = lib_dir.join(name);
|
|
250
|
+
let same_file = source
|
|
251
|
+
.canonicalize()
|
|
252
|
+
.ok()
|
|
253
|
+
.zip(destination.canonicalize().ok())
|
|
254
|
+
.is_some_and(|(source, destination)| source == destination);
|
|
255
|
+
if !same_file {
|
|
256
|
+
std::fs::copy(&source, &destination).unwrap_or_else(|error| {
|
|
257
|
+
panic!(
|
|
258
|
+
"could not stage {} to {}: {error}",
|
|
259
|
+
source.display(),
|
|
260
|
+
destination.display()
|
|
261
|
+
)
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
227
267
|
#[cfg(feature = "jolt")]
|
|
228
268
|
fn link_prebuilt(dir: &std::path::Path, target_os: &str) {
|
|
229
269
|
// Rerun if any archive in the prebuilt dir changes — covers the
|
|
@@ -22,6 +22,7 @@ pub struct EngineState {
|
|
|
22
22
|
pub renderer: Renderer,
|
|
23
23
|
pub text: TextRenderer,
|
|
24
24
|
pub input: InputState,
|
|
25
|
+
pub ui: crate::ui::UiSystem,
|
|
25
26
|
pub audio: AudioMixer,
|
|
26
27
|
pub textures: TextureManager,
|
|
27
28
|
#[cfg(feature = "models3d")]
|
|
@@ -84,10 +85,18 @@ impl EngineState {
|
|
|
84
85
|
// BLAS_INPUT cost only when it can be used.
|
|
85
86
|
let mut scene = SceneGraph::new();
|
|
86
87
|
scene.hw_rt_enabled = renderer.hw_rt_enabled;
|
|
88
|
+
let ui = crate::ui::UiSystem::default();
|
|
89
|
+
#[cfg(feature = "debug-ui")]
|
|
90
|
+
let renderer = {
|
|
91
|
+
let mut renderer = renderer;
|
|
92
|
+
renderer.init_dear_imgui();
|
|
93
|
+
renderer
|
|
94
|
+
};
|
|
87
95
|
Self {
|
|
88
96
|
renderer,
|
|
89
97
|
text: TextRenderer::new(),
|
|
90
98
|
input: InputState::new(),
|
|
99
|
+
ui,
|
|
91
100
|
audio: AudioMixer::new(),
|
|
92
101
|
textures: TextureManager::new(),
|
|
93
102
|
#[cfg(feature = "models3d")]
|
|
@@ -162,6 +171,8 @@ impl EngineState {
|
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
self.input.begin_frame();
|
|
174
|
+
self.ui.begin_frame();
|
|
175
|
+
self.ui.set_input_snapshot(self.input.ui_snapshot());
|
|
165
176
|
self.renderer.begin_frame();
|
|
166
177
|
self.frame_count += 1;
|
|
167
178
|
}
|
|
@@ -182,6 +193,37 @@ impl EngineState {
|
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
pub fn end_frame(&mut self) {
|
|
196
|
+
let logical_width = self.renderer.width().max(1) as f32;
|
|
197
|
+
let logical_height = self.renderer.height().max(1) as f32;
|
|
198
|
+
let pixels_per_point = self.renderer.physical_width() as f32 / logical_width;
|
|
199
|
+
let ui_frame = self.ui.evaluate_egui(
|
|
200
|
+
[0.0, 0.0, logical_width, logical_height],
|
|
201
|
+
pixels_per_point,
|
|
202
|
+
self.delta_time,
|
|
203
|
+
);
|
|
204
|
+
let native_texture_indices = ui_frame
|
|
205
|
+
.registered_textures
|
|
206
|
+
.values()
|
|
207
|
+
.filter_map(|&handle| {
|
|
208
|
+
self.textures
|
|
209
|
+
.get(handle as f64)
|
|
210
|
+
.map(|texture| (handle, texture.bind_group_idx))
|
|
211
|
+
})
|
|
212
|
+
.collect();
|
|
213
|
+
self.renderer
|
|
214
|
+
.set_egui_frame(ui_frame, native_texture_indices);
|
|
215
|
+
|
|
216
|
+
let debug_native_texture_indices = self
|
|
217
|
+
.ui
|
|
218
|
+
.registered_texture_handles(crate::ui::UiBackend::DearImGui)
|
|
219
|
+
.into_iter()
|
|
220
|
+
.filter_map(|handle| {
|
|
221
|
+
self.textures
|
|
222
|
+
.get(handle as f64)
|
|
223
|
+
.map(|texture| (handle, texture.bind_group_idx))
|
|
224
|
+
})
|
|
225
|
+
.collect();
|
|
226
|
+
|
|
185
227
|
if self.direct_2d_mode {
|
|
186
228
|
// Fast path for pure-2D games: render direct to the swapchain,
|
|
187
229
|
// skipping scene prep, shadow maps, HDR/tonemap, SSAO, bloom,
|
|
@@ -190,7 +232,11 @@ impl EngineState {
|
|
|
190
232
|
// when the scene graph is empty; this path typically hits
|
|
191
233
|
// 60 fps on the same device.
|
|
192
234
|
self.profiler.begin("render_total");
|
|
193
|
-
self.renderer.
|
|
235
|
+
self.renderer.end_frame_with_ui(
|
|
236
|
+
&mut self.ui,
|
|
237
|
+
self.delta_time,
|
|
238
|
+
debug_native_texture_indices,
|
|
239
|
+
);
|
|
194
240
|
self.profiler.end("render_total");
|
|
195
241
|
} else {
|
|
196
242
|
self.profiler.begin("scene_prepare");
|
|
@@ -233,7 +279,13 @@ impl EngineState {
|
|
|
233
279
|
self.renderer.material_system_begin_frame(t, dt);
|
|
234
280
|
|
|
235
281
|
self.profiler.begin("render_total");
|
|
236
|
-
self.renderer.
|
|
282
|
+
self.renderer.end_frame_with_scene_ui(
|
|
283
|
+
&mut self.scene,
|
|
284
|
+
&mut self.profiler,
|
|
285
|
+
&mut self.ui,
|
|
286
|
+
self.delta_time,
|
|
287
|
+
debug_native_texture_indices,
|
|
288
|
+
);
|
|
237
289
|
self.profiler.end("render_total");
|
|
238
290
|
}
|
|
239
291
|
|
package/native/shared/src/ffi.rs
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
//! Logging goes through [`log_error`], which writes to logcat on Android
|
|
18
18
|
//! (stderr is discarded there) and stderr everywhere else.
|
|
19
19
|
|
|
20
|
+
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
21
|
+
use std::borrow::Cow;
|
|
22
|
+
|
|
20
23
|
/// Default return value for an FFI function whose body panicked.
|
|
21
24
|
pub trait FfiDefault {
|
|
22
25
|
fn ffi_default() -> Self;
|
|
@@ -112,5 +115,55 @@ pub fn log_error(msg: &str) {
|
|
|
112
115
|
return;
|
|
113
116
|
}
|
|
114
117
|
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
115
|
-
|
|
118
|
+
{
|
|
119
|
+
use std::io::IsTerminal;
|
|
120
|
+
|
|
121
|
+
let color_enabled = should_color_terminal_output(
|
|
122
|
+
std::io::stderr().is_terminal(),
|
|
123
|
+
std::env::var_os("NO_COLOR").is_some(),
|
|
124
|
+
);
|
|
125
|
+
eprintln!("{}", format_terminal_error(msg, color_enabled));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
130
|
+
fn should_color_terminal_output(stderr_is_terminal: bool, no_color_is_set: bool) -> bool {
|
|
131
|
+
stderr_is_terminal && !no_color_is_set
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
135
|
+
fn format_terminal_error(msg: &str, color_enabled: bool) -> Cow<'_, str> {
|
|
136
|
+
if color_enabled {
|
|
137
|
+
Cow::Owned(format!("\x1b[31m{msg}\x1b[0m"))
|
|
138
|
+
} else {
|
|
139
|
+
Cow::Borrowed(msg)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[cfg(all(test, not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
144
|
+
mod tests {
|
|
145
|
+
use super::{format_terminal_error, should_color_terminal_output};
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn terminal_errors_are_red_and_reset_terminal_style() {
|
|
149
|
+
assert_eq!(
|
|
150
|
+
format_terminal_error("bloom: failure", true).as_ref(),
|
|
151
|
+
"\x1b[31mbloom: failure\x1b[0m"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[test]
|
|
156
|
+
fn errors_remain_plain_when_color_is_disabled() {
|
|
157
|
+
assert_eq!(
|
|
158
|
+
format_terminal_error("bloom: failure", false).as_ref(),
|
|
159
|
+
"bloom: failure"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn color_is_only_enabled_for_tty_without_no_color() {
|
|
165
|
+
assert!(should_color_terminal_output(true, false));
|
|
166
|
+
assert!(!should_color_terminal_output(false, false));
|
|
167
|
+
assert!(!should_color_terminal_output(true, true));
|
|
168
|
+
}
|
|
116
169
|
}
|
|
@@ -69,6 +69,7 @@ mod scene;
|
|
|
69
69
|
mod visual;
|
|
70
70
|
mod vfx;
|
|
71
71
|
mod ragdoll_ffi;
|
|
72
|
+
mod ui;
|
|
72
73
|
|
|
73
74
|
/// Expand the full shared (non-physics) FFI surface. Composed from the
|
|
74
75
|
/// per-subsystem section macros in this directory; platform crates invoke
|
|
@@ -88,6 +89,7 @@ macro_rules! define_core_ffi {
|
|
|
88
89
|
$crate::__bloom_ffi_visual!();
|
|
89
90
|
$crate::__bloom_ffi_vfx!();
|
|
90
91
|
$crate::__bloom_ffi_ragdoll!();
|
|
92
|
+
$crate::__bloom_ffi_ui!();
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//! Typed UI command, input, response, and availability FFI.
|
|
2
|
+
//!
|
|
3
|
+
//! Section of [`define_core_ffi!`](crate::define_core_ffi) — see
|
|
4
|
+
//! `ffi_core/mod.rs` for the architecture and the invoking-crate contract.
|
|
5
|
+
|
|
6
|
+
#[doc(hidden)]
|
|
7
|
+
#[macro_export]
|
|
8
|
+
macro_rules! __bloom_ffi_ui {
|
|
9
|
+
() => {
|
|
10
|
+
#[no_mangle]
|
|
11
|
+
pub extern "C" fn bloom_ui_command(
|
|
12
|
+
backend: f64,
|
|
13
|
+
opcode: f64,
|
|
14
|
+
id: f64,
|
|
15
|
+
a: f64,
|
|
16
|
+
b: f64,
|
|
17
|
+
c: f64,
|
|
18
|
+
d: f64,
|
|
19
|
+
text_ptr: *const u8,
|
|
20
|
+
) -> f64 {
|
|
21
|
+
$crate::ffi::guard("bloom_ui_command", move || {
|
|
22
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
23
|
+
return 0.0;
|
|
24
|
+
};
|
|
25
|
+
let Some(opcode) = $crate::ui::UiOpcode::from_abi(opcode) else {
|
|
26
|
+
return 0.0;
|
|
27
|
+
};
|
|
28
|
+
let Some(id) = $crate::ui::id_from_abi(id) else {
|
|
29
|
+
return 0.0;
|
|
30
|
+
};
|
|
31
|
+
let text = $crate::string_header::str_from_header(text_ptr).to_owned();
|
|
32
|
+
let command = $crate::ui::UiCommand::new(backend, opcode, id, [a, b, c, d], text);
|
|
33
|
+
if engine().ui.queue_command(command) {
|
|
34
|
+
1.0
|
|
35
|
+
} else {
|
|
36
|
+
0.0
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[no_mangle]
|
|
42
|
+
pub extern "C" fn bloom_ui_scratch_reset(backend: f64) {
|
|
43
|
+
$crate::ffi::guard("bloom_ui_scratch_reset", move || {
|
|
44
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
45
|
+
return;
|
|
46
|
+
};
|
|
47
|
+
engine().ui.reset_scratch(backend);
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[no_mangle]
|
|
52
|
+
pub extern "C" fn bloom_ui_scratch_push_f64(backend: f64, value: f64) {
|
|
53
|
+
$crate::ffi::guard("bloom_ui_scratch_push_f64", move || {
|
|
54
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
55
|
+
return;
|
|
56
|
+
};
|
|
57
|
+
engine().ui.push_scratch(backend, value);
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[no_mangle]
|
|
62
|
+
pub extern "C" fn bloom_ui_scratch_command(
|
|
63
|
+
backend: f64,
|
|
64
|
+
opcode: f64,
|
|
65
|
+
id: f64,
|
|
66
|
+
count: f64,
|
|
67
|
+
text_ptr: *const u8,
|
|
68
|
+
) -> f64 {
|
|
69
|
+
$crate::ffi::guard("bloom_ui_scratch_command", move || {
|
|
70
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
71
|
+
return 0.0;
|
|
72
|
+
};
|
|
73
|
+
let Some(opcode) = $crate::ui::UiOpcode::from_abi(opcode) else {
|
|
74
|
+
return 0.0;
|
|
75
|
+
};
|
|
76
|
+
let Some(id) = $crate::ui::id_from_abi(id) else {
|
|
77
|
+
return 0.0;
|
|
78
|
+
};
|
|
79
|
+
let Some(count) = $crate::ui::id_from_abi(count) else {
|
|
80
|
+
return 0.0;
|
|
81
|
+
};
|
|
82
|
+
let text = $crate::string_header::str_from_header(text_ptr).to_owned();
|
|
83
|
+
if engine()
|
|
84
|
+
.ui
|
|
85
|
+
.queue_scratch_command(backend, opcode, id, count as usize, text)
|
|
86
|
+
{
|
|
87
|
+
1.0
|
|
88
|
+
} else {
|
|
89
|
+
0.0
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[no_mangle]
|
|
95
|
+
pub extern "C" fn bloom_ui_inject_text(text_ptr: *const u8) {
|
|
96
|
+
$crate::ffi::guard("bloom_ui_inject_text", move || {
|
|
97
|
+
let text = $crate::string_header::str_from_header(text_ptr).to_owned();
|
|
98
|
+
engine().ui.inject_text(text);
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[no_mangle]
|
|
103
|
+
pub extern "C" fn bloom_ui_response(backend: f64, id: f64, field: f64) -> f64 {
|
|
104
|
+
$crate::ffi::guard("bloom_ui_response", move || {
|
|
105
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
106
|
+
return 0.0;
|
|
107
|
+
};
|
|
108
|
+
let Some(id) = $crate::ui::id_from_abi(id) else {
|
|
109
|
+
return 0.0;
|
|
110
|
+
};
|
|
111
|
+
let Some(field) = $crate::ui::id_from_abi(field) else {
|
|
112
|
+
return 0.0;
|
|
113
|
+
};
|
|
114
|
+
let response = engine().ui.response(backend, id);
|
|
115
|
+
match field {
|
|
116
|
+
0 => response.clicked as u8 as f64,
|
|
117
|
+
1 => response.changed as u8 as f64,
|
|
118
|
+
2 => response.hovered as u8 as f64,
|
|
119
|
+
3 => response.focused as u8 as f64,
|
|
120
|
+
4 => response.dragged as u8 as f64,
|
|
121
|
+
5 => response.value,
|
|
122
|
+
6 => engine().ui.has_response(backend, id) as u8 as f64,
|
|
123
|
+
_ => 0.0,
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[no_mangle]
|
|
129
|
+
pub extern "C" fn bloom_ui_response_text(backend: f64, id: f64) -> *const u8 {
|
|
130
|
+
$crate::ffi::guard("bloom_ui_response_text", move || {
|
|
131
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
132
|
+
return $crate::string_header::alloc_perry_string("");
|
|
133
|
+
};
|
|
134
|
+
let Some(id) = $crate::ui::id_from_abi(id) else {
|
|
135
|
+
return $crate::string_header::alloc_perry_string("");
|
|
136
|
+
};
|
|
137
|
+
let text = engine().ui.response_text(backend, id);
|
|
138
|
+
$crate::string_header::alloc_perry_string(&text)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[no_mangle]
|
|
143
|
+
pub extern "C" fn bloom_ui_is_available(backend: f64) -> f64 {
|
|
144
|
+
$crate::ffi::guard("bloom_ui_is_available", move || {
|
|
145
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
146
|
+
return 0.0;
|
|
147
|
+
};
|
|
148
|
+
engine().ui.is_available(backend) as u8 as f64
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[no_mangle]
|
|
153
|
+
pub extern "C" fn bloom_ui_wants_input(backend: f64, kind: f64) -> f64 {
|
|
154
|
+
$crate::ffi::guard("bloom_ui_wants_input", move || {
|
|
155
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
156
|
+
return 0.0;
|
|
157
|
+
};
|
|
158
|
+
let Some(kind) = $crate::ui::id_from_abi(kind) else {
|
|
159
|
+
return 0.0;
|
|
160
|
+
};
|
|
161
|
+
engine().ui.wants_input(backend, kind) as u8 as f64
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[no_mangle]
|
|
166
|
+
pub extern "C" fn bloom_ui_take_keyboard_request(backend: f64) -> f64 {
|
|
167
|
+
$crate::ffi::guard("bloom_ui_take_keyboard_request", move || {
|
|
168
|
+
let Some(backend) = $crate::ui::UiBackend::from_abi(backend) else {
|
|
169
|
+
return -1.0;
|
|
170
|
+
};
|
|
171
|
+
if backend != $crate::ui::UiBackend::Egui {
|
|
172
|
+
return -1.0;
|
|
173
|
+
}
|
|
174
|
+
match engine().ui.take_keyboard_request() {
|
|
175
|
+
Some(true) => 1.0,
|
|
176
|
+
Some(false) => 0.0,
|
|
177
|
+
None => -1.0,
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|