@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
|
@@ -17,6 +17,11 @@ pub struct InputState {
|
|
|
17
17
|
keys_down: [bool; MAX_KEYS],
|
|
18
18
|
keys_released: [bool; MAX_KEYS],
|
|
19
19
|
prev_keys_down: [bool; MAX_KEYS],
|
|
20
|
+
// Preserve complete edge transitions for UI input when press/release both
|
|
21
|
+
// arrive before begin_frame computes the gameplay-facing key state.
|
|
22
|
+
pending_key_pressed: [bool; MAX_KEYS],
|
|
23
|
+
pending_key_released: [bool; MAX_KEYS],
|
|
24
|
+
pending_ui_events: Vec<crate::ui::UiInputEvent>,
|
|
20
25
|
|
|
21
26
|
// Injected keys (bloom_inject_key_down/up) are staged here and applied at the
|
|
22
27
|
// top of begin_frame, rather than written straight into keys_down.
|
|
@@ -55,6 +60,11 @@ pub struct InputState {
|
|
|
55
60
|
char_queue: [u32; 32],
|
|
56
61
|
char_queue_head: usize,
|
|
57
62
|
char_queue_tail: usize,
|
|
63
|
+
/// Independent consumer queue for the UI input snapshot. Games and UI
|
|
64
|
+
/// widgets each receive text once without advancing the other's cursor.
|
|
65
|
+
ui_char_queue: [u32; 32],
|
|
66
|
+
ui_char_queue_head: usize,
|
|
67
|
+
ui_char_queue_tail: usize,
|
|
58
68
|
/// Cursor shape requested by the editor. The platform event loop polls
|
|
59
69
|
/// this and applies the appropriate native cursor.
|
|
60
70
|
/// 0=default, 1=hand, 2=move, 3=text, 4=resize_h, 5=resize_v, 6=crosshair
|
|
@@ -111,6 +121,9 @@ impl InputState {
|
|
|
111
121
|
keys_down: [false; MAX_KEYS],
|
|
112
122
|
keys_released: [false; MAX_KEYS],
|
|
113
123
|
prev_keys_down: [false; MAX_KEYS],
|
|
124
|
+
pending_key_pressed: [false; MAX_KEYS],
|
|
125
|
+
pending_key_released: [false; MAX_KEYS],
|
|
126
|
+
pending_ui_events: Vec::new(),
|
|
114
127
|
pending_key_down: [false; MAX_KEYS],
|
|
115
128
|
pending_key_up: [false; MAX_KEYS],
|
|
116
129
|
mouse_x: 0.0,
|
|
@@ -126,6 +139,9 @@ impl InputState {
|
|
|
126
139
|
char_queue: [0; 32],
|
|
127
140
|
char_queue_head: 0,
|
|
128
141
|
char_queue_tail: 0,
|
|
142
|
+
ui_char_queue: [0; 32],
|
|
143
|
+
ui_char_queue_head: 0,
|
|
144
|
+
ui_char_queue_tail: 0,
|
|
129
145
|
cursor_shape: 0,
|
|
130
146
|
cursor_disabled: false,
|
|
131
147
|
mouse_pressed: [false; MAX_MOUSE_BUTTONS],
|
|
@@ -154,6 +170,15 @@ impl InputState {
|
|
|
154
170
|
/// one-frame isKeyRepeated edge by the next begin_frame.
|
|
155
171
|
pub fn queue_key_repeat(&mut self, key: usize) {
|
|
156
172
|
if key < MAX_KEYS {
|
|
173
|
+
if !self.repeat_pending[key] {
|
|
174
|
+
self.pending_ui_events.push(crate::ui::UiInputEvent::Key(
|
|
175
|
+
crate::ui::UiKeyEvent {
|
|
176
|
+
key: key as u32,
|
|
177
|
+
pressed: true,
|
|
178
|
+
repeated: true,
|
|
179
|
+
},
|
|
180
|
+
));
|
|
181
|
+
}
|
|
157
182
|
self.repeat_pending[key] = true;
|
|
158
183
|
}
|
|
159
184
|
}
|
|
@@ -208,6 +233,8 @@ impl InputState {
|
|
|
208
233
|
|
|
209
234
|
pub fn end_frame(&mut self) {
|
|
210
235
|
self.prev_keys_down = self.keys_down;
|
|
236
|
+
// Pending UI key edges are consumed in ui_snapshot so late events can
|
|
237
|
+
// survive this frame boundary.
|
|
211
238
|
self.prev_mouse_down = self.mouse_down;
|
|
212
239
|
self.prev_gamepad_buttons = self.gamepad_buttons_down;
|
|
213
240
|
self.prev_mouse_x = self.mouse_x;
|
|
@@ -228,16 +255,67 @@ impl InputState {
|
|
|
228
255
|
// Keyboard
|
|
229
256
|
/// Real key events, delivered from the platform's message pump. These run
|
|
230
257
|
/// before begin_frame(), so writing keys_down directly is correct here.
|
|
231
|
-
pub fn set_key_down(&mut self, key: usize) {
|
|
232
|
-
|
|
258
|
+
pub fn set_key_down(&mut self, key: usize) {
|
|
259
|
+
if key < MAX_KEYS {
|
|
260
|
+
if !self.keys_down[key] {
|
|
261
|
+
self.pending_key_pressed[key] = true;
|
|
262
|
+
self.pending_ui_events.push(crate::ui::UiInputEvent::Key(
|
|
263
|
+
crate::ui::UiKeyEvent {
|
|
264
|
+
key: key as u32,
|
|
265
|
+
pressed: true,
|
|
266
|
+
repeated: false,
|
|
267
|
+
},
|
|
268
|
+
));
|
|
269
|
+
}
|
|
270
|
+
self.keys_down[key] = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
pub fn set_key_up(&mut self, key: usize) {
|
|
274
|
+
if key < MAX_KEYS {
|
|
275
|
+
if self.keys_down[key] {
|
|
276
|
+
self.pending_key_released[key] = true;
|
|
277
|
+
self.pending_ui_events.push(crate::ui::UiInputEvent::Key(
|
|
278
|
+
crate::ui::UiKeyEvent {
|
|
279
|
+
key: key as u32,
|
|
280
|
+
pressed: false,
|
|
281
|
+
repeated: false,
|
|
282
|
+
},
|
|
283
|
+
));
|
|
284
|
+
}
|
|
285
|
+
self.keys_down[key] = false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
233
288
|
|
|
234
289
|
/// Synthetic key events (bloom_inject_key_down/up), which callers raise from
|
|
235
|
-
/// inside the game loop.
|
|
290
|
+
/// inside the game loop. Their UI edges are kept even if a down/up pair is
|
|
291
|
+
/// staged before the next frame.
|
|
236
292
|
pub fn inject_key_down(&mut self, key: usize) {
|
|
237
|
-
if key < MAX_KEYS {
|
|
293
|
+
if key < MAX_KEYS {
|
|
294
|
+
self.pending_key_down[key] = true;
|
|
295
|
+
self.pending_key_up[key] = false;
|
|
296
|
+
self.pending_key_pressed[key] = true;
|
|
297
|
+
self.pending_ui_events.push(crate::ui::UiInputEvent::Key(
|
|
298
|
+
crate::ui::UiKeyEvent {
|
|
299
|
+
key: key as u32,
|
|
300
|
+
pressed: true,
|
|
301
|
+
repeated: false,
|
|
302
|
+
},
|
|
303
|
+
));
|
|
304
|
+
}
|
|
238
305
|
}
|
|
239
306
|
pub fn inject_key_up(&mut self, key: usize) {
|
|
240
|
-
if key < MAX_KEYS {
|
|
307
|
+
if key < MAX_KEYS {
|
|
308
|
+
self.pending_key_up[key] = true;
|
|
309
|
+
self.pending_key_down[key] = false;
|
|
310
|
+
self.pending_key_released[key] = true;
|
|
311
|
+
self.pending_ui_events.push(crate::ui::UiInputEvent::Key(
|
|
312
|
+
crate::ui::UiKeyEvent {
|
|
313
|
+
key: key as u32,
|
|
314
|
+
pressed: false,
|
|
315
|
+
repeated: false,
|
|
316
|
+
},
|
|
317
|
+
));
|
|
318
|
+
}
|
|
241
319
|
}
|
|
242
320
|
|
|
243
321
|
pub fn is_key_pressed(&self, key: usize) -> bool { key < MAX_KEYS && self.keys_pressed[key] }
|
|
@@ -256,15 +334,43 @@ impl InputState {
|
|
|
256
334
|
self.mouse_wheel_y = 0.0;
|
|
257
335
|
v
|
|
258
336
|
}
|
|
259
|
-
/// Push a Unicode codepoint into
|
|
260
|
-
///
|
|
337
|
+
/// Push a Unicode codepoint into independent gameplay and UI text queues.
|
|
338
|
+
/// Platform event loops call this for committed text input.
|
|
261
339
|
pub fn push_char(&mut self, c: u32) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
self.
|
|
265
|
-
self.
|
|
340
|
+
push_char_to_queue(
|
|
341
|
+
&mut self.char_queue,
|
|
342
|
+
&mut self.char_queue_head,
|
|
343
|
+
self.char_queue_tail,
|
|
344
|
+
c,
|
|
345
|
+
);
|
|
346
|
+
push_char_to_queue(
|
|
347
|
+
&mut self.ui_char_queue,
|
|
348
|
+
&mut self.ui_char_queue_head,
|
|
349
|
+
self.ui_char_queue_tail,
|
|
350
|
+
c,
|
|
351
|
+
);
|
|
352
|
+
if let Some(character) = char::from_u32(c) {
|
|
353
|
+
self.pending_ui_events
|
|
354
|
+
.push(crate::ui::UiInputEvent::Text(character.to_string()));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Push committed text directly to the UI without exposing it through the
|
|
359
|
+
/// gameplay character queue.
|
|
360
|
+
pub fn push_ui_text(&mut self, text: String) {
|
|
361
|
+
if text.is_empty() {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
self.pending_ui_events
|
|
365
|
+
.push(crate::ui::UiInputEvent::Text(text.clone()));
|
|
366
|
+
for character in text.chars() {
|
|
367
|
+
push_char_to_queue(
|
|
368
|
+
&mut self.ui_char_queue,
|
|
369
|
+
&mut self.ui_char_queue_head,
|
|
370
|
+
self.ui_char_queue_tail,
|
|
371
|
+
character as u32,
|
|
372
|
+
);
|
|
266
373
|
}
|
|
267
|
-
// If the queue is full, silently drop the character.
|
|
268
374
|
}
|
|
269
375
|
|
|
270
376
|
/// Pop the next queued character. Returns 0 when the queue is empty.
|
|
@@ -392,6 +498,90 @@ impl InputState {
|
|
|
392
498
|
|| self.gamepad_buttons_pressed.iter().any(|&g| g)
|
|
393
499
|
|| self.touch_count > 0
|
|
394
500
|
}
|
|
501
|
+
|
|
502
|
+
/// Snapshot input for UI evaluation. Text is consumed from its dedicated UI
|
|
503
|
+
/// queue, leaving the game's character queue and other getters untouched.
|
|
504
|
+
pub fn ui_snapshot(&mut self) -> crate::ui::UiInputSnapshot {
|
|
505
|
+
use crate::ui::{
|
|
506
|
+
UiInputSnapshot, UiKeyEvent, UiModifiers, UiPointerButtonEvent, UiTouchEvent,
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
let pointer_buttons = (0..MAX_MOUSE_BUTTONS).flat_map(|button| {
|
|
510
|
+
let mut events = Vec::with_capacity(2);
|
|
511
|
+
if self.mouse_pressed[button] {
|
|
512
|
+
events.push(UiPointerButtonEvent { button: button as u8, pressed: true });
|
|
513
|
+
}
|
|
514
|
+
if self.mouse_released[button] {
|
|
515
|
+
events.push(UiPointerButtonEvent { button: button as u8, pressed: false });
|
|
516
|
+
}
|
|
517
|
+
events
|
|
518
|
+
}).collect();
|
|
519
|
+
|
|
520
|
+
let ordered_events = std::mem::take(&mut self.pending_ui_events);
|
|
521
|
+
let mut keys = Vec::new();
|
|
522
|
+
for key in 0..MAX_KEYS {
|
|
523
|
+
if self.keys_pressed[key] || self.pending_key_pressed[key] {
|
|
524
|
+
keys.push(UiKeyEvent { key: key as u32, pressed: true, repeated: false });
|
|
525
|
+
}
|
|
526
|
+
if self.keys_released[key] || self.pending_key_released[key] {
|
|
527
|
+
keys.push(UiKeyEvent { key: key as u32, pressed: false, repeated: false });
|
|
528
|
+
}
|
|
529
|
+
// Clear an edge only after this snapshot observes it. UI events
|
|
530
|
+
// arriving after this point belong to the next snapshot.
|
|
531
|
+
self.pending_key_pressed[key] = false;
|
|
532
|
+
self.pending_key_released[key] = false;
|
|
533
|
+
if self.keys_repeated[key] {
|
|
534
|
+
keys.push(UiKeyEvent { key: key as u32, pressed: true, repeated: true });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let mut text = Vec::new();
|
|
539
|
+
while self.ui_char_queue_tail != self.ui_char_queue_head {
|
|
540
|
+
let character = self.ui_char_queue[self.ui_char_queue_tail];
|
|
541
|
+
self.ui_char_queue_tail = (self.ui_char_queue_tail + 1) % self.ui_char_queue.len();
|
|
542
|
+
if let Some(ch) = char::from_u32(character) {
|
|
543
|
+
text.push(ch.to_string());
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let touches = (0..MAX_TOUCH_POINTS).filter_map(|slot| {
|
|
548
|
+
let point = &self.touch_points[slot];
|
|
549
|
+
(point.active || self.touch_pending_release[slot]).then_some(UiTouchEvent {
|
|
550
|
+
slot,
|
|
551
|
+
x: point.x,
|
|
552
|
+
y: point.y,
|
|
553
|
+
active: point.active,
|
|
554
|
+
released: self.touch_pending_release[slot],
|
|
555
|
+
})
|
|
556
|
+
}).collect();
|
|
557
|
+
|
|
558
|
+
UiInputSnapshot {
|
|
559
|
+
pointer_position: Some([self.mouse_x, self.mouse_y]),
|
|
560
|
+
pointer_delta: [self.mouse_delta_x, self.mouse_delta_y],
|
|
561
|
+
pointer_buttons,
|
|
562
|
+
scroll_x: 0.0,
|
|
563
|
+
scroll_y: self.mouse_wheel_y,
|
|
564
|
+
ordered_events,
|
|
565
|
+
keys,
|
|
566
|
+
modifiers: UiModifiers {
|
|
567
|
+
shift: self.keys_down[280] || self.keys_down[281],
|
|
568
|
+
ctrl: self.keys_down[282] || self.keys_down[283],
|
|
569
|
+
alt: self.keys_down[284] || self.keys_down[285],
|
|
570
|
+
super_key: self.keys_down[286] || self.keys_down[287],
|
|
571
|
+
},
|
|
572
|
+
text,
|
|
573
|
+
touches,
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
fn push_char_to_queue(queue: &mut [u32; 32], head: &mut usize, tail: usize, character: u32) {
|
|
579
|
+
let next = (*head + 1) % queue.len();
|
|
580
|
+
if next != tail {
|
|
581
|
+
queue[*head] = character;
|
|
582
|
+
*head = next;
|
|
583
|
+
}
|
|
584
|
+
// If the queue is full, silently drop the character.
|
|
395
585
|
}
|
|
396
586
|
|
|
397
587
|
#[cfg(test)]
|
|
@@ -445,4 +635,119 @@ mod tests {
|
|
|
445
635
|
assert!(!input.is_mouse_button_down(0));
|
|
446
636
|
assert!(!input.is_mouse_button_released(0));
|
|
447
637
|
}
|
|
638
|
+
|
|
639
|
+
#[test]
|
|
640
|
+
fn ui_snapshot_keeps_scroll_and_text_after_game_consumes_them() {
|
|
641
|
+
let mut input = InputState::new();
|
|
642
|
+
input.accumulate_mouse_wheel(1.0);
|
|
643
|
+
input.push_char('é' as u32);
|
|
644
|
+
input.begin_frame();
|
|
645
|
+
|
|
646
|
+
let snapshot = input.ui_snapshot();
|
|
647
|
+
assert_eq!(snapshot.scroll_y, 1.0);
|
|
648
|
+
assert_eq!(input.consume_mouse_wheel(), 1.0);
|
|
649
|
+
assert_eq!(snapshot.text, vec!["é"]);
|
|
650
|
+
assert_eq!(input.pop_char(), 'é' as u32);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
#[test]
|
|
654
|
+
fn ui_snapshot_delivers_each_character_only_once() {
|
|
655
|
+
let mut input = InputState::new();
|
|
656
|
+
input.push_char('x' as u32);
|
|
657
|
+
|
|
658
|
+
assert_eq!(input.ui_snapshot().text, vec!["x"]);
|
|
659
|
+
assert!(input.ui_snapshot().text.is_empty());
|
|
660
|
+
assert_eq!(input.pop_char(), 'x' as u32);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
#[test]
|
|
664
|
+
fn game_character_reads_do_not_consume_ui_text() {
|
|
665
|
+
let mut input = InputState::new();
|
|
666
|
+
input.push_char('y' as u32);
|
|
667
|
+
|
|
668
|
+
assert_eq!(input.pop_char(), 'y' as u32);
|
|
669
|
+
assert_eq!(input.ui_snapshot().text, vec!["y"]);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
#[test]
|
|
673
|
+
fn ui_snapshot_keeps_touch_release_for_a_fast_tap() {
|
|
674
|
+
let mut input = InputState::new();
|
|
675
|
+
input.set_touch(0, 12.0, 34.0, true);
|
|
676
|
+
input.release_touch(0, 12.0, 34.0);
|
|
677
|
+
input.begin_frame();
|
|
678
|
+
|
|
679
|
+
let snapshot = input.ui_snapshot();
|
|
680
|
+
let touch = snapshot.touches.iter().find(|touch| touch.slot == 0).unwrap();
|
|
681
|
+
assert!(touch.active);
|
|
682
|
+
assert!(touch.released);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
#[test]
|
|
686
|
+
fn ui_snapshot_keeps_fast_key_click_edges() {
|
|
687
|
+
let mut input = InputState::new();
|
|
688
|
+
input.set_key_down(65);
|
|
689
|
+
input.set_key_up(65);
|
|
690
|
+
input.begin_frame();
|
|
691
|
+
|
|
692
|
+
let snapshot = input.ui_snapshot();
|
|
693
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 65 && event.pressed));
|
|
694
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 65 && !event.pressed));
|
|
695
|
+
assert!(!input.is_key_pressed(65));
|
|
696
|
+
assert!(!input.is_key_released(65));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
#[test]
|
|
700
|
+
fn ui_snapshot_keeps_injected_key_tap_edges() {
|
|
701
|
+
let mut input = InputState::new();
|
|
702
|
+
input.inject_key_down(8);
|
|
703
|
+
input.inject_key_up(8);
|
|
704
|
+
input.begin_frame();
|
|
705
|
+
|
|
706
|
+
let snapshot = input.ui_snapshot();
|
|
707
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 8 && event.pressed));
|
|
708
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 8 && !event.pressed));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
#[test]
|
|
712
|
+
fn ui_snapshot_keeps_injected_key_tap_until_next_snapshot() {
|
|
713
|
+
let mut input = InputState::new();
|
|
714
|
+
input.begin_frame();
|
|
715
|
+
let _ = input.ui_snapshot();
|
|
716
|
+
|
|
717
|
+
input.inject_key_down(8);
|
|
718
|
+
input.inject_key_up(8);
|
|
719
|
+
input.end_frame();
|
|
720
|
+
input.begin_frame();
|
|
721
|
+
|
|
722
|
+
let snapshot = input.ui_snapshot();
|
|
723
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 8 && event.pressed));
|
|
724
|
+
assert!(snapshot.keys.iter().any(|event| event.key == 8 && !event.pressed));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
#[test]
|
|
728
|
+
fn ui_snapshot_preserves_text_and_key_event_order() {
|
|
729
|
+
let mut input = InputState::new();
|
|
730
|
+
input.push_ui_text("x".to_owned());
|
|
731
|
+
input.inject_key_down(8);
|
|
732
|
+
input.inject_key_up(8);
|
|
733
|
+
input.begin_frame();
|
|
734
|
+
|
|
735
|
+
assert_eq!(input.pop_char(), 0);
|
|
736
|
+
assert_eq!(
|
|
737
|
+
input.ui_snapshot().ordered_events,
|
|
738
|
+
[
|
|
739
|
+
crate::ui::UiInputEvent::Text("x".to_owned()),
|
|
740
|
+
crate::ui::UiInputEvent::Key(crate::ui::UiKeyEvent {
|
|
741
|
+
key: 8,
|
|
742
|
+
pressed: true,
|
|
743
|
+
repeated: false,
|
|
744
|
+
}),
|
|
745
|
+
crate::ui::UiInputEvent::Key(crate::ui::UiKeyEvent {
|
|
746
|
+
key: 8,
|
|
747
|
+
pressed: false,
|
|
748
|
+
repeated: false,
|
|
749
|
+
}),
|
|
750
|
+
]
|
|
751
|
+
);
|
|
752
|
+
}
|
|
448
753
|
}
|
package/native/shared/src/lib.rs
CHANGED
|
@@ -36,6 +36,7 @@ pub mod jolt_sys;
|
|
|
36
36
|
pub mod physics_jolt;
|
|
37
37
|
pub mod engine;
|
|
38
38
|
pub mod drs;
|
|
39
|
+
pub mod ui;
|
|
39
40
|
// Host-surface attach path (PerryTS/perry#5519). Pulls in wgpu's
|
|
40
41
|
// raw-surface API; web builds its surface from a canvas id instead, so
|
|
41
42
|
// this is native-only.
|