bevy 1.0.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +4279 -0
  3. data/Cargo.toml +36 -0
  4. data/README.md +226 -0
  5. data/crates/bevy/Cargo.toml +52 -0
  6. data/crates/bevy/src/app.rs +43 -0
  7. data/crates/bevy/src/component.rs +111 -0
  8. data/crates/bevy/src/entity.rs +30 -0
  9. data/crates/bevy/src/error.rs +32 -0
  10. data/crates/bevy/src/event.rs +190 -0
  11. data/crates/bevy/src/input_bridge.rs +300 -0
  12. data/crates/bevy/src/lib.rs +42 -0
  13. data/crates/bevy/src/mesh_renderer.rs +328 -0
  14. data/crates/bevy/src/query.rs +53 -0
  15. data/crates/bevy/src/render_app.rs +689 -0
  16. data/crates/bevy/src/resource.rs +28 -0
  17. data/crates/bevy/src/schedule.rs +186 -0
  18. data/crates/bevy/src/sprite_renderer.rs +355 -0
  19. data/crates/bevy/src/system.rs +44 -0
  20. data/crates/bevy/src/text_renderer.rs +258 -0
  21. data/crates/bevy/src/types/color.rs +114 -0
  22. data/crates/bevy/src/types/dynamic.rs +131 -0
  23. data/crates/bevy/src/types/math.rs +260 -0
  24. data/crates/bevy/src/types/mod.rs +9 -0
  25. data/crates/bevy/src/types/transform.rs +166 -0
  26. data/crates/bevy/src/world.rs +163 -0
  27. data/crates/bevy_ruby_render/Cargo.toml +22 -0
  28. data/crates/bevy_ruby_render/src/asset.rs +360 -0
  29. data/crates/bevy_ruby_render/src/audio.rs +511 -0
  30. data/crates/bevy_ruby_render/src/camera.rs +365 -0
  31. data/crates/bevy_ruby_render/src/gamepad.rs +398 -0
  32. data/crates/bevy_ruby_render/src/lib.rs +26 -0
  33. data/crates/bevy_ruby_render/src/material.rs +310 -0
  34. data/crates/bevy_ruby_render/src/mesh.rs +491 -0
  35. data/crates/bevy_ruby_render/src/sprite.rs +289 -0
  36. data/ext/bevy/Cargo.toml +20 -0
  37. data/ext/bevy/extconf.rb +6 -0
  38. data/ext/bevy/src/conversions.rs +137 -0
  39. data/ext/bevy/src/lib.rs +29 -0
  40. data/ext/bevy/src/ruby_app.rs +65 -0
  41. data/ext/bevy/src/ruby_color.rs +149 -0
  42. data/ext/bevy/src/ruby_component.rs +189 -0
  43. data/ext/bevy/src/ruby_entity.rs +33 -0
  44. data/ext/bevy/src/ruby_math.rs +384 -0
  45. data/ext/bevy/src/ruby_query.rs +64 -0
  46. data/ext/bevy/src/ruby_render_app.rs +779 -0
  47. data/ext/bevy/src/ruby_system.rs +122 -0
  48. data/ext/bevy/src/ruby_world.rs +107 -0
  49. data/lib/bevy/animation.rb +597 -0
  50. data/lib/bevy/app.rb +675 -0
  51. data/lib/bevy/asset.rb +613 -0
  52. data/lib/bevy/audio.rb +545 -0
  53. data/lib/bevy/audio_effects.rb +224 -0
  54. data/lib/bevy/camera.rb +412 -0
  55. data/lib/bevy/component.rb +91 -0
  56. data/lib/bevy/diagnostics.rb +227 -0
  57. data/lib/bevy/ecs_advanced.rb +296 -0
  58. data/lib/bevy/event.rb +199 -0
  59. data/lib/bevy/gizmos.rb +158 -0
  60. data/lib/bevy/gltf.rb +227 -0
  61. data/lib/bevy/hierarchy.rb +444 -0
  62. data/lib/bevy/input.rb +514 -0
  63. data/lib/bevy/lighting.rb +369 -0
  64. data/lib/bevy/material.rb +248 -0
  65. data/lib/bevy/mesh.rb +257 -0
  66. data/lib/bevy/navigation.rb +344 -0
  67. data/lib/bevy/networking.rb +335 -0
  68. data/lib/bevy/particle.rb +337 -0
  69. data/lib/bevy/physics.rb +396 -0
  70. data/lib/bevy/plugins/default_plugins.rb +34 -0
  71. data/lib/bevy/plugins/input_plugin.rb +49 -0
  72. data/lib/bevy/reflect.rb +361 -0
  73. data/lib/bevy/render_graph.rb +210 -0
  74. data/lib/bevy/resource.rb +185 -0
  75. data/lib/bevy/scene.rb +254 -0
  76. data/lib/bevy/shader.rb +319 -0
  77. data/lib/bevy/shape.rb +195 -0
  78. data/lib/bevy/skeletal.rb +248 -0
  79. data/lib/bevy/sprite.rb +152 -0
  80. data/lib/bevy/sprite_sheet.rb +444 -0
  81. data/lib/bevy/state.rb +277 -0
  82. data/lib/bevy/system.rb +206 -0
  83. data/lib/bevy/text.rb +99 -0
  84. data/lib/bevy/text_advanced.rb +455 -0
  85. data/lib/bevy/timer.rb +147 -0
  86. data/lib/bevy/transform.rb +158 -0
  87. data/lib/bevy/ui.rb +454 -0
  88. data/lib/bevy/ui_advanced.rb +568 -0
  89. data/lib/bevy/version.rb +5 -0
  90. data/lib/bevy/visibility.rb +250 -0
  91. data/lib/bevy/window.rb +302 -0
  92. data/lib/bevy.rb +390 -0
  93. metadata +150 -0
@@ -0,0 +1,689 @@
1
+ //! RenderApp module for managing the Bevy rendering application.
2
+
3
+ #[cfg(feature = "rendering")]
4
+ use bevy_a11y::AccessibilityPlugin;
5
+ #[cfg(feature = "rendering")]
6
+ use bevy_app::{App, AppExit, Startup, Update};
7
+ #[cfg(feature = "rendering")]
8
+ use bevy_asset::AssetPlugin;
9
+ #[cfg(feature = "rendering")]
10
+ use bevy_core::{FrameCountPlugin, Name, TaskPoolPlugin, TypeRegistrationPlugin};
11
+ #[cfg(feature = "rendering")]
12
+ use bevy_core_pipeline::CorePipelinePlugin;
13
+ #[cfg(feature = "rendering")]
14
+ use bevy_core_pipeline::core_2d::Camera2d;
15
+ #[cfg(feature = "rendering")]
16
+ use bevy_ecs::event::{EventReader, EventWriter};
17
+ #[cfg(feature = "rendering")]
18
+ use bevy_ecs::system::{Commands, Res};
19
+ #[cfg(feature = "rendering")]
20
+ use bevy_ecs::world::World;
21
+ #[cfg(feature = "rendering")]
22
+ use bevy_hierarchy::HierarchyPlugin;
23
+ #[cfg(feature = "rendering")]
24
+ use bevy_input::gamepad::{
25
+ Gamepad, GamepadAxis, GamepadButton, GamepadRumbleIntensity, GamepadRumbleRequest,
26
+ };
27
+ #[cfg(feature = "rendering")]
28
+ use bevy_input::keyboard::KeyCode;
29
+ #[cfg(feature = "rendering")]
30
+ use bevy_input::mouse::MouseButton;
31
+ #[cfg(feature = "rendering")]
32
+ use bevy_input::{ButtonInput, InputPlugin};
33
+ #[cfg(feature = "rendering")]
34
+ use bevy_log::LogPlugin;
35
+ #[cfg(feature = "rendering")]
36
+ use bevy_picking::{
37
+ DefaultPickingPlugins,
38
+ events::{Click, Down, Out, Over, Pointer, Up},
39
+ pointer::{PointerButton, PointerId},
40
+ };
41
+ #[cfg(feature = "rendering")]
42
+ use bevy_render::RenderPlugin;
43
+ #[cfg(feature = "rendering")]
44
+ use bevy_render::camera::Camera;
45
+ #[cfg(feature = "rendering")]
46
+ use bevy_render::prelude::ImagePlugin;
47
+ #[cfg(feature = "rendering")]
48
+ use bevy_sprite::SpritePlugin;
49
+ #[cfg(feature = "rendering")]
50
+ use bevy_text::TextPlugin;
51
+ #[cfg(feature = "rendering")]
52
+ use bevy_time::TimePlugin;
53
+ #[cfg(feature = "rendering")]
54
+ use bevy_transform::TransformPlugin;
55
+ #[cfg(feature = "rendering")]
56
+ use bevy_transform::components::Transform;
57
+ #[cfg(feature = "rendering")]
58
+ use bevy_window::{Window, WindowPlugin};
59
+ #[cfg(feature = "rendering")]
60
+ use bevy_winit::{WakeUp, WinitPlugin};
61
+ #[cfg(feature = "rendering")]
62
+ use std::sync::Arc;
63
+ #[cfg(feature = "rendering")]
64
+ use std::sync::Mutex;
65
+
66
+ /// Window configuration for the render application.
67
+ #[derive(Debug, Clone)]
68
+ pub struct WindowConfig {
69
+ pub title: String,
70
+ pub width: f32,
71
+ pub height: f32,
72
+ pub resizable: bool,
73
+ }
74
+
75
+ impl Default for WindowConfig {
76
+ fn default() -> Self {
77
+ Self {
78
+ title: "Bevy Ruby".to_string(),
79
+ width: 800.0,
80
+ height: 600.0,
81
+ resizable: true,
82
+ }
83
+ }
84
+ }
85
+
86
+ use crate::{DefaultSpriteTexture, InputState, MeshSync, SpriteSync, TextSync};
87
+
88
+ #[cfg(feature = "rendering")]
89
+ type UpdateCallback = Arc<Mutex<Option<Box<dyn FnMut(&mut RubyBridgeState) + Send>>>>;
90
+
91
+ #[cfg(feature = "rendering")]
92
+ #[derive(bevy_ecs::system::Resource)]
93
+ pub struct RubyBridge {
94
+ pub callback: UpdateCallback,
95
+ pub state: Arc<Mutex<RubyBridgeState>>,
96
+ }
97
+
98
+ #[cfg(feature = "rendering")]
99
+ pub struct RubyBridgeState {
100
+ pub input_state: InputState,
101
+ pub sprite_sync: SpriteSync,
102
+ pub text_sync: TextSync,
103
+ pub mesh_sync: MeshSync,
104
+ pub pending_gamepad_rumble: Vec<GamepadRumbleCommand>,
105
+ pub picking_events: Vec<PickingEventData>,
106
+ pub should_exit: bool,
107
+ pub world_access: Option<*mut World>,
108
+ pub camera_position: (f32, f32, f32),
109
+ pub camera_scale: f32,
110
+ pub camera_dirty: bool,
111
+ }
112
+
113
+ #[cfg(feature = "rendering")]
114
+ #[derive(Debug, Clone, Copy)]
115
+ pub struct GamepadRumbleCommand {
116
+ pub gamepad_id: u64,
117
+ pub strong_motor: f32,
118
+ pub weak_motor: f32,
119
+ pub duration_secs: f32,
120
+ pub stop: bool,
121
+ }
122
+
123
+ #[cfg(feature = "rendering")]
124
+ #[derive(Debug, Clone)]
125
+ pub struct PickingEventData {
126
+ pub kind: String,
127
+ pub target_id: u64,
128
+ pub pointer_id: String,
129
+ pub pointer_position: (f32, f32),
130
+ pub button: Option<String>,
131
+ pub camera_id: Option<u64>,
132
+ pub depth: Option<f32>,
133
+ pub hit_position: Option<(f32, f32, f32)>,
134
+ pub hit_normal: Option<(f32, f32, f32)>,
135
+ }
136
+
137
+ #[cfg(feature = "rendering")]
138
+ unsafe impl Send for RubyBridgeState {}
139
+ #[cfg(feature = "rendering")]
140
+ unsafe impl Sync for RubyBridgeState {}
141
+
142
+ #[cfg(feature = "rendering")]
143
+ impl Default for RubyBridgeState {
144
+ fn default() -> Self {
145
+ Self {
146
+ input_state: InputState::new(),
147
+ sprite_sync: SpriteSync::new(),
148
+ text_sync: TextSync::new(),
149
+ mesh_sync: MeshSync::new(),
150
+ pending_gamepad_rumble: Vec::new(),
151
+ picking_events: Vec::new(),
152
+ should_exit: false,
153
+ world_access: None,
154
+ camera_position: (0.0, 0.0, 0.0),
155
+ camera_scale: 1.0,
156
+ camera_dirty: false,
157
+ }
158
+ }
159
+ }
160
+
161
+ #[cfg(feature = "rendering")]
162
+ fn spawn_camera_2d_system(mut commands: Commands) {
163
+ commands.spawn((Camera::default(), Camera2d::default(), Transform::default()));
164
+ }
165
+
166
+ #[cfg(feature = "rendering")]
167
+ fn setup_default_sprite_texture_system(world: &mut World) {
168
+ DefaultSpriteTexture::insert_into_world(world);
169
+ }
170
+
171
+ #[cfg(feature = "rendering")]
172
+ fn ruby_bridge_system(
173
+ bridge: Res<RubyBridge>,
174
+ keyboard: Res<ButtonInput<KeyCode>>,
175
+ mouse_buttons: Res<ButtonInput<MouseButton>>,
176
+ windows: bevy_ecs::system::Query<&Window>,
177
+ gamepad_query: bevy_ecs::system::Query<(bevy_ecs::entity::Entity, Option<&Name>, &Gamepad)>,
178
+ mut over_events: EventReader<Pointer<Over>>,
179
+ mut out_events: EventReader<Pointer<Out>>,
180
+ mut down_events: EventReader<Pointer<Down>>,
181
+ mut up_events: EventReader<Pointer<Up>>,
182
+ mut click_events: EventReader<Pointer<Click>>,
183
+ mut gamepad_rumble_requests: EventWriter<GamepadRumbleRequest>,
184
+ mut exit_writer: EventWriter<AppExit>,
185
+ ) {
186
+ let mut state = bridge.state.lock().unwrap();
187
+
188
+ state.input_state.clear();
189
+
190
+ for key in keyboard.get_pressed() {
191
+ if let Some(key_name) = keycode_to_string(*key) {
192
+ state.input_state.set_pressed(&key_name);
193
+ }
194
+ }
195
+
196
+ for key in keyboard.get_just_pressed() {
197
+ if let Some(key_name) = keycode_to_string(*key) {
198
+ state.input_state.set_just_pressed(&key_name);
199
+ }
200
+ }
201
+
202
+ for key in keyboard.get_just_released() {
203
+ if let Some(key_name) = keycode_to_string(*key) {
204
+ state.input_state.set_just_released(&key_name);
205
+ }
206
+ }
207
+
208
+ if mouse_buttons.pressed(MouseButton::Left) {
209
+ state.input_state.set_mouse_pressed("LEFT");
210
+ }
211
+ if mouse_buttons.pressed(MouseButton::Right) {
212
+ state.input_state.set_mouse_pressed("RIGHT");
213
+ }
214
+ if mouse_buttons.pressed(MouseButton::Middle) {
215
+ state.input_state.set_mouse_pressed("MIDDLE");
216
+ }
217
+
218
+ if mouse_buttons.just_pressed(MouseButton::Left) {
219
+ state.input_state.set_mouse_just_pressed("LEFT");
220
+ }
221
+ if mouse_buttons.just_pressed(MouseButton::Right) {
222
+ state.input_state.set_mouse_just_pressed("RIGHT");
223
+ }
224
+ if mouse_buttons.just_pressed(MouseButton::Middle) {
225
+ state.input_state.set_mouse_just_pressed("MIDDLE");
226
+ }
227
+
228
+ for (entity, maybe_name, gamepad) in gamepad_query.iter() {
229
+ let id = entity.to_bits();
230
+ let gamepad_name = maybe_name
231
+ .map(|name| name.as_str().to_string())
232
+ .unwrap_or_else(|| format!("Gamepad {}", id));
233
+
234
+ state.input_state.set_gamepad_connected(id, &gamepad_name);
235
+
236
+ for button in gamepad.get_pressed() {
237
+ let button_name = gamepad_button_to_string(*button);
238
+ state
239
+ .input_state
240
+ .set_gamepad_button_pressed(id, &button_name);
241
+ }
242
+
243
+ for button in gamepad.get_just_pressed() {
244
+ let button_name = gamepad_button_to_string(*button);
245
+ state
246
+ .input_state
247
+ .set_gamepad_button_just_pressed(id, &button_name);
248
+ }
249
+
250
+ for button in gamepad.get_just_released() {
251
+ let button_name = gamepad_button_to_string(*button);
252
+ state
253
+ .input_state
254
+ .set_gamepad_button_just_released(id, &button_name);
255
+ }
256
+
257
+ for axis in GamepadAxis::all() {
258
+ let axis_name = gamepad_axis_to_string(axis);
259
+ let axis_value = gamepad.get(axis).unwrap_or(0.0);
260
+ state
261
+ .input_state
262
+ .set_gamepad_axis(id, &axis_name, axis_value);
263
+ }
264
+ }
265
+
266
+ if let Ok(window) = windows.get_single() {
267
+ if let Some(pos) = window.cursor_position() {
268
+ let center_x = window.width() / 2.0;
269
+ let center_y = window.height() / 2.0;
270
+ state.input_state.mouse_position = (pos.x - center_x, center_y - pos.y);
271
+ }
272
+ }
273
+
274
+ state.picking_events.clear();
275
+
276
+ for event in over_events.read() {
277
+ let hit = &event.event.hit;
278
+ state.picking_events.push(PickingEventData {
279
+ kind: "over".to_string(),
280
+ target_id: event.target.to_bits(),
281
+ pointer_id: pointer_id_to_string(event.pointer_id),
282
+ pointer_position: (
283
+ event.pointer_location.position.x,
284
+ event.pointer_location.position.y,
285
+ ),
286
+ button: None,
287
+ camera_id: Some(hit.camera.to_bits()),
288
+ depth: Some(hit.depth),
289
+ hit_position: hit
290
+ .position
291
+ .map(|position| (position.x, position.y, position.z)),
292
+ hit_normal: hit.normal.map(|normal| (normal.x, normal.y, normal.z)),
293
+ });
294
+ }
295
+
296
+ for event in out_events.read() {
297
+ let hit = &event.event.hit;
298
+ state.picking_events.push(PickingEventData {
299
+ kind: "out".to_string(),
300
+ target_id: event.target.to_bits(),
301
+ pointer_id: pointer_id_to_string(event.pointer_id),
302
+ pointer_position: (
303
+ event.pointer_location.position.x,
304
+ event.pointer_location.position.y,
305
+ ),
306
+ button: None,
307
+ camera_id: Some(hit.camera.to_bits()),
308
+ depth: Some(hit.depth),
309
+ hit_position: hit
310
+ .position
311
+ .map(|position| (position.x, position.y, position.z)),
312
+ hit_normal: hit.normal.map(|normal| (normal.x, normal.y, normal.z)),
313
+ });
314
+ }
315
+
316
+ for event in down_events.read() {
317
+ let hit = &event.event.hit;
318
+ state.picking_events.push(PickingEventData {
319
+ kind: "down".to_string(),
320
+ target_id: event.target.to_bits(),
321
+ pointer_id: pointer_id_to_string(event.pointer_id),
322
+ pointer_position: (
323
+ event.pointer_location.position.x,
324
+ event.pointer_location.position.y,
325
+ ),
326
+ button: Some(pointer_button_to_string(event.event.button).to_string()),
327
+ camera_id: Some(hit.camera.to_bits()),
328
+ depth: Some(hit.depth),
329
+ hit_position: hit
330
+ .position
331
+ .map(|position| (position.x, position.y, position.z)),
332
+ hit_normal: hit.normal.map(|normal| (normal.x, normal.y, normal.z)),
333
+ });
334
+ }
335
+
336
+ for event in up_events.read() {
337
+ let hit = &event.event.hit;
338
+ state.picking_events.push(PickingEventData {
339
+ kind: "up".to_string(),
340
+ target_id: event.target.to_bits(),
341
+ pointer_id: pointer_id_to_string(event.pointer_id),
342
+ pointer_position: (
343
+ event.pointer_location.position.x,
344
+ event.pointer_location.position.y,
345
+ ),
346
+ button: Some(pointer_button_to_string(event.event.button).to_string()),
347
+ camera_id: Some(hit.camera.to_bits()),
348
+ depth: Some(hit.depth),
349
+ hit_position: hit
350
+ .position
351
+ .map(|position| (position.x, position.y, position.z)),
352
+ hit_normal: hit.normal.map(|normal| (normal.x, normal.y, normal.z)),
353
+ });
354
+ }
355
+
356
+ for event in click_events.read() {
357
+ let hit = &event.event.hit;
358
+ state.picking_events.push(PickingEventData {
359
+ kind: "click".to_string(),
360
+ target_id: event.target.to_bits(),
361
+ pointer_id: pointer_id_to_string(event.pointer_id),
362
+ pointer_position: (
363
+ event.pointer_location.position.x,
364
+ event.pointer_location.position.y,
365
+ ),
366
+ button: Some(pointer_button_to_string(event.event.button).to_string()),
367
+ camera_id: Some(hit.camera.to_bits()),
368
+ depth: Some(hit.depth),
369
+ hit_position: hit
370
+ .position
371
+ .map(|position| (position.x, position.y, position.z)),
372
+ hit_normal: hit.normal.map(|normal| (normal.x, normal.y, normal.z)),
373
+ });
374
+ }
375
+
376
+ drop(state);
377
+
378
+ if let Ok(mut callback) = bridge.callback.lock() {
379
+ if let Some(ref mut cb) = *callback {
380
+ let mut state = bridge.state.lock().unwrap();
381
+ cb(&mut state);
382
+ }
383
+ }
384
+
385
+ let mut state = bridge.state.lock().unwrap();
386
+ for command in state.pending_gamepad_rumble.drain(..) {
387
+ let gamepad = bevy_ecs::entity::Entity::from_bits(command.gamepad_id);
388
+ if command.stop || (command.strong_motor <= 0.0 && command.weak_motor <= 0.0) {
389
+ gamepad_rumble_requests.send(GamepadRumbleRequest::Stop { gamepad });
390
+ continue;
391
+ }
392
+
393
+ gamepad_rumble_requests.send(GamepadRumbleRequest::Add {
394
+ gamepad,
395
+ intensity: GamepadRumbleIntensity {
396
+ strong_motor: command.strong_motor.clamp(0.0, 1.0),
397
+ weak_motor: command.weak_motor.clamp(0.0, 1.0),
398
+ },
399
+ duration: std::time::Duration::from_secs_f32(command.duration_secs.max(0.0)),
400
+ });
401
+ }
402
+
403
+ if state.should_exit {
404
+ exit_writer.send(AppExit::Success);
405
+ }
406
+ }
407
+
408
+ #[cfg(feature = "rendering")]
409
+ fn sprite_sync_system(world: &mut World) {
410
+ let state_arc = {
411
+ let bridge = world.resource::<RubyBridge>();
412
+ bridge.state.clone()
413
+ };
414
+
415
+ let mut state = state_arc.lock().unwrap();
416
+ state.sprite_sync.apply_pending(world);
417
+ }
418
+
419
+ #[cfg(feature = "rendering")]
420
+ fn text_sync_system(world: &mut World) {
421
+ let state_arc = {
422
+ let bridge = world.resource::<RubyBridge>();
423
+ bridge.state.clone()
424
+ };
425
+
426
+ let mut state = state_arc.lock().unwrap();
427
+ state.text_sync.apply_pending(world);
428
+ }
429
+
430
+ #[cfg(feature = "rendering")]
431
+ fn mesh_sync_system(world: &mut World) {
432
+ let state_arc = {
433
+ let bridge = world.resource::<RubyBridge>();
434
+ bridge.state.clone()
435
+ };
436
+
437
+ let mut state = state_arc.lock().unwrap();
438
+ state.mesh_sync.apply_pending(world);
439
+ }
440
+
441
+ #[cfg(feature = "rendering")]
442
+ fn camera_sync_system(
443
+ bridge: Res<RubyBridge>,
444
+ mut query: bevy_ecs::system::Query<&mut Transform, bevy_ecs::query::With<Camera2d>>,
445
+ ) {
446
+ let mut state = bridge.state.lock().unwrap();
447
+ if !state.camera_dirty {
448
+ return;
449
+ }
450
+
451
+ for mut transform in query.iter_mut() {
452
+ transform.translation.x = state.camera_position.0;
453
+ transform.translation.y = state.camera_position.1;
454
+ transform.translation.z = state.camera_position.2;
455
+ transform.scale.x = state.camera_scale;
456
+ transform.scale.y = state.camera_scale;
457
+ }
458
+
459
+ state.camera_dirty = false;
460
+ }
461
+
462
+ #[cfg(feature = "rendering")]
463
+ fn keycode_to_string(key: KeyCode) -> Option<String> {
464
+ match key {
465
+ KeyCode::KeyA => Some("A".to_string()),
466
+ KeyCode::KeyB => Some("B".to_string()),
467
+ KeyCode::KeyC => Some("C".to_string()),
468
+ KeyCode::KeyD => Some("D".to_string()),
469
+ KeyCode::KeyE => Some("E".to_string()),
470
+ KeyCode::KeyF => Some("F".to_string()),
471
+ KeyCode::KeyG => Some("G".to_string()),
472
+ KeyCode::KeyH => Some("H".to_string()),
473
+ KeyCode::KeyI => Some("I".to_string()),
474
+ KeyCode::KeyJ => Some("J".to_string()),
475
+ KeyCode::KeyK => Some("K".to_string()),
476
+ KeyCode::KeyL => Some("L".to_string()),
477
+ KeyCode::KeyM => Some("M".to_string()),
478
+ KeyCode::KeyN => Some("N".to_string()),
479
+ KeyCode::KeyO => Some("O".to_string()),
480
+ KeyCode::KeyP => Some("P".to_string()),
481
+ KeyCode::KeyQ => Some("Q".to_string()),
482
+ KeyCode::KeyR => Some("R".to_string()),
483
+ KeyCode::KeyS => Some("S".to_string()),
484
+ KeyCode::KeyT => Some("T".to_string()),
485
+ KeyCode::KeyU => Some("U".to_string()),
486
+ KeyCode::KeyV => Some("V".to_string()),
487
+ KeyCode::KeyW => Some("W".to_string()),
488
+ KeyCode::KeyX => Some("X".to_string()),
489
+ KeyCode::KeyY => Some("Y".to_string()),
490
+ KeyCode::KeyZ => Some("Z".to_string()),
491
+ KeyCode::Digit0 => Some("0".to_string()),
492
+ KeyCode::Digit1 => Some("1".to_string()),
493
+ KeyCode::Digit2 => Some("2".to_string()),
494
+ KeyCode::Digit3 => Some("3".to_string()),
495
+ KeyCode::Digit4 => Some("4".to_string()),
496
+ KeyCode::Digit5 => Some("5".to_string()),
497
+ KeyCode::Digit6 => Some("6".to_string()),
498
+ KeyCode::Digit7 => Some("7".to_string()),
499
+ KeyCode::Digit8 => Some("8".to_string()),
500
+ KeyCode::Digit9 => Some("9".to_string()),
501
+ KeyCode::Space => Some("SPACE".to_string()),
502
+ KeyCode::Enter => Some("ENTER".to_string()),
503
+ KeyCode::Escape => Some("ESCAPE".to_string()),
504
+ KeyCode::ArrowUp => Some("UP".to_string()),
505
+ KeyCode::ArrowDown => Some("DOWN".to_string()),
506
+ KeyCode::ArrowLeft => Some("LEFT".to_string()),
507
+ KeyCode::ArrowRight => Some("RIGHT".to_string()),
508
+ KeyCode::ShiftLeft | KeyCode::ShiftRight => Some("SHIFT".to_string()),
509
+ KeyCode::ControlLeft | KeyCode::ControlRight => Some("CONTROL".to_string()),
510
+ KeyCode::AltLeft | KeyCode::AltRight => Some("ALT".to_string()),
511
+ KeyCode::Tab => Some("TAB".to_string()),
512
+ KeyCode::Backspace => Some("BACKSPACE".to_string()),
513
+ _ => None,
514
+ }
515
+ }
516
+
517
+ #[cfg(feature = "rendering")]
518
+ fn gamepad_button_to_string(button: GamepadButton) -> String {
519
+ match button {
520
+ GamepadButton::South => "South".to_string(),
521
+ GamepadButton::East => "East".to_string(),
522
+ GamepadButton::North => "North".to_string(),
523
+ GamepadButton::West => "West".to_string(),
524
+ GamepadButton::C => "C".to_string(),
525
+ GamepadButton::Z => "Z".to_string(),
526
+ GamepadButton::LeftTrigger => "LeftTrigger".to_string(),
527
+ GamepadButton::LeftTrigger2 => "LeftTrigger2".to_string(),
528
+ GamepadButton::RightTrigger => "RightTrigger".to_string(),
529
+ GamepadButton::RightTrigger2 => "RightTrigger2".to_string(),
530
+ GamepadButton::Select => "Select".to_string(),
531
+ GamepadButton::Start => "Start".to_string(),
532
+ GamepadButton::Mode => "Mode".to_string(),
533
+ GamepadButton::LeftThumb => "LeftThumb".to_string(),
534
+ GamepadButton::RightThumb => "RightThumb".to_string(),
535
+ GamepadButton::DPadUp => "DPadUp".to_string(),
536
+ GamepadButton::DPadDown => "DPadDown".to_string(),
537
+ GamepadButton::DPadLeft => "DPadLeft".to_string(),
538
+ GamepadButton::DPadRight => "DPadRight".to_string(),
539
+ GamepadButton::Other(id) => format!("Other({})", id),
540
+ }
541
+ }
542
+
543
+ #[cfg(feature = "rendering")]
544
+ fn gamepad_axis_to_string(axis: GamepadAxis) -> String {
545
+ match axis {
546
+ GamepadAxis::LeftStickX => "LeftStickX".to_string(),
547
+ GamepadAxis::LeftStickY => "LeftStickY".to_string(),
548
+ GamepadAxis::LeftZ => "LeftZ".to_string(),
549
+ GamepadAxis::RightStickX => "RightStickX".to_string(),
550
+ GamepadAxis::RightStickY => "RightStickY".to_string(),
551
+ GamepadAxis::RightZ => "RightZ".to_string(),
552
+ GamepadAxis::Other(id) => format!("Other({})", id),
553
+ }
554
+ }
555
+
556
+ #[cfg(feature = "rendering")]
557
+ fn pointer_id_to_string(pointer_id: PointerId) -> String {
558
+ match pointer_id {
559
+ PointerId::Mouse => "Mouse".to_string(),
560
+ PointerId::Touch(id) => format!("Touch({})", id),
561
+ PointerId::Custom(id) => format!("Custom({})", id),
562
+ }
563
+ }
564
+
565
+ #[cfg(feature = "rendering")]
566
+ fn pointer_button_to_string(button: PointerButton) -> &'static str {
567
+ match button {
568
+ PointerButton::Primary => "Primary",
569
+ PointerButton::Secondary => "Secondary",
570
+ PointerButton::Middle => "Middle",
571
+ }
572
+ }
573
+
574
+ #[cfg(feature = "rendering")]
575
+ pub struct RenderApp {
576
+ app: App,
577
+ bridge: Arc<Mutex<RubyBridgeState>>,
578
+ callback: UpdateCallback,
579
+ }
580
+
581
+ #[cfg(feature = "rendering")]
582
+ impl RenderApp {
583
+ pub fn new(config: WindowConfig) -> Self {
584
+ let mut app = App::new();
585
+
586
+ app.add_plugins((
587
+ LogPlugin::default(),
588
+ TaskPoolPlugin::default(),
589
+ TypeRegistrationPlugin::default(),
590
+ FrameCountPlugin::default(),
591
+ TimePlugin::default(),
592
+ TransformPlugin::default(),
593
+ HierarchyPlugin::default(),
594
+ InputPlugin::default(),
595
+ ));
596
+
597
+ app.add_plugins((
598
+ WindowPlugin {
599
+ primary_window: Some(Window {
600
+ title: config.title,
601
+ resolution: (config.width, config.height).into(),
602
+ resizable: config.resizable,
603
+ ..Default::default()
604
+ }),
605
+ ..Default::default()
606
+ },
607
+ AccessibilityPlugin,
608
+ AssetPlugin::default(),
609
+ WinitPlugin::<WakeUp>::default(),
610
+ ));
611
+
612
+ app.add_plugins((
613
+ RenderPlugin::default(),
614
+ ImagePlugin::default(),
615
+ CorePipelinePlugin::default(),
616
+ DefaultPickingPlugins,
617
+ SpritePlugin::default(),
618
+ TextPlugin::default(),
619
+ bevy_prototype_lyon::prelude::ShapePlugin,
620
+ ));
621
+
622
+ let bridge_state = Arc::new(Mutex::new(RubyBridgeState::default()));
623
+ let callback: UpdateCallback = Arc::new(Mutex::new(None));
624
+
625
+ let bridge = RubyBridge {
626
+ callback: callback.clone(),
627
+ state: bridge_state.clone(),
628
+ };
629
+
630
+ app.insert_resource(bridge);
631
+ app.add_systems(Startup, spawn_camera_2d_system);
632
+ app.add_systems(Startup, setup_default_sprite_texture_system);
633
+ app.add_systems(Update, ruby_bridge_system);
634
+ app.add_systems(Update, sprite_sync_system);
635
+ app.add_systems(Update, text_sync_system);
636
+ app.add_systems(Update, mesh_sync_system);
637
+ app.add_systems(Update, camera_sync_system);
638
+
639
+ Self {
640
+ app,
641
+ bridge: bridge_state,
642
+ callback,
643
+ }
644
+ }
645
+
646
+ pub fn set_callback<F>(&mut self, callback: F)
647
+ where
648
+ F: FnMut(&mut RubyBridgeState) + Send + 'static,
649
+ {
650
+ let mut cb = self.callback.lock().unwrap();
651
+ *cb = Some(Box::new(callback));
652
+ }
653
+
654
+ pub fn run(&mut self) {
655
+ self.app.run();
656
+ }
657
+
658
+ pub fn bridge_state(&self) -> Arc<Mutex<RubyBridgeState>> {
659
+ self.bridge.clone()
660
+ }
661
+
662
+ pub fn should_exit(&self) -> bool {
663
+ self.bridge.lock().map(|s| s.should_exit).unwrap_or(false)
664
+ }
665
+
666
+ pub fn is_initialized(&self) -> bool {
667
+ true
668
+ }
669
+ }
670
+
671
+ #[cfg(not(feature = "rendering"))]
672
+ pub struct RenderApp;
673
+
674
+ #[cfg(not(feature = "rendering"))]
675
+ impl RenderApp {
676
+ pub fn new(_config: WindowConfig) -> Self {
677
+ Self
678
+ }
679
+
680
+ pub fn run(&mut self) {}
681
+
682
+ pub fn should_exit(&self) -> bool {
683
+ false
684
+ }
685
+
686
+ pub fn is_initialized(&self) -> bool {
687
+ false
688
+ }
689
+ }