diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3fd9e0b7..42e65a0f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -123,8 +123,9 @@ jobs: cache-android-cargo-${{ hashFiles('**/Cargo.toml') }} cache-android-cargo - run: | - CC=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang \ AR=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar \ + CC=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang \ + CXX=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang++ \ cargo clippy --target=aarch64-linux-android --no-default-features --all-targets --features=${{ matrix.features }} -- -D warnings clippy_ios: diff --git a/Cargo.toml b/Cargo.toml index 43a15067..9bad705b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,13 @@ manage_clipboard = ["arboard", "thread_local"] open_url = ["webbrowser"] default_fonts = ["egui/default_fonts"] render = [ - "bevy_render", - "bevy_asset", - "encase", - "bytemuck", - "egui/bytemuck", - "wgpu-types", + "bevy_asset", + "bevy_image", + "bevy_render", + "encase", + "bytemuck", + "egui/bytemuck", + "wgpu-types", ] serde = ["egui/serde"] # The enabled logs will print with the info log level, to make it less cumbersome to debug in browsers. @@ -59,28 +60,28 @@ required-features = ["render"] [dependencies] egui = { version = "0.29", default-features = false } - -bevy_app = "0.14" -bevy_derive = "0.14" -bevy_ecs = "0.14" -bevy_input = "0.14" -bevy_log = "0.14" -bevy_math = "0.14" -bevy_reflect = "0.14" -bevy_time = "0.14" -bevy_utils = "0.14" -bevy_winit = "0.14" -bevy_window = "0.14" +bevy_app = "0.15.0" +bevy_derive = "0.15.0" +bevy_ecs = "0.15.0" +bevy_input = "0.15.0" +bevy_log = "0.15.0" +bevy_math = "0.15.0" +bevy_reflect = "0.15.0" +bevy_time = "0.15.0" +bevy_utils = "0.15.0" +bevy_winit = { version = "0.15.0", features = ["custom_cursor"] } +bevy_window = "0.15.0" # `open_url` feature webbrowser = { version = "1.0.1", optional = true } # `render` feature bytemuck = { version = "1", optional = true } -bevy_asset = { version = "0.14", optional = true } -bevy_render = { version = "0.14", optional = true } -encase = { version = "0.8", optional = true } -wgpu-types = { version = "0.20", optional = true } +bevy_asset = { version = "0.15.0", optional = true } +bevy_image = { version = "0.15.0", optional = true } +bevy_render = { version = "0.15.0", optional = true } +encase = { version = "0.10", optional = true } +wgpu-types = { version = "23.0", optional = true } # `manage_clipboard` feature [target.'cfg(not(any(target_arch = "wasm32", target_os = "android")))'.dependencies] @@ -89,33 +90,35 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] version-sync = "0.9.4" -bevy = { version = "0.14.0", default-features = false, features = [ - "x11", - "png", - "bevy_pbr", - "bevy_core_pipeline", - "bevy_asset", - "bevy_winit", - "tonemapping_luts", - "webgl2", +bevy = { version = "0.15.0", default-features = false, features = [ + "x11", + "png", + "bevy_pbr", + "bevy_core_pipeline", + "bevy_asset", + "bevy_window", + "bevy_winit", + "tonemapping_luts", + "webgl2", + "android-game-activity", ] } egui = { version = "0.29", default-features = false, features = ["bytemuck"] } [target.'cfg(target_arch = "wasm32")'.dependencies] winit = "0.30" -web-sys = { version = "0.3.63", features = [ - "Clipboard", - "ClipboardEvent", - "CompositionEvent", - "DataTransfer", - "Document", - "EventTarget", - "HtmlInputElement", - "InputEvent", - "KeyboardEvent", - "Navigator", - "TouchEvent", - "Window", +web-sys = { version = "0.3.74", features = [ + "Clipboard", + "ClipboardEvent", + "CompositionEvent", + "DataTransfer", + "Document", + "EventTarget", + "HtmlInputElement", + "InputEvent", + "KeyboardEvent", + "Navigator", + "TouchEvent", + "Window", ] } js-sys = "0.3.63" wasm-bindgen = "0.2.84" diff --git a/examples/paint_callback.rs b/examples/paint_callback.rs index 8bb2b217..8569e21b 100644 --- a/examples/paint_callback.rs +++ b/examples/paint_callback.rs @@ -8,6 +8,7 @@ use bevy::{ MultisampleState, PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines, }, + sync_world::RenderEntity, RenderApp, }, }; @@ -52,7 +53,7 @@ impl EguiBevyPaintCallbackImpl for CustomPaintCallback { fn update( &self, _info: egui::PaintCallbackInfo, - window_entity: Entity, + window_entity: RenderEntity, key: EguiPipelineKey, world: &mut World, ) { @@ -72,7 +73,7 @@ impl EguiBevyPaintCallbackImpl for CustomPaintCallback { ); world - .entity_mut(window_entity) + .entity_mut(window_entity.id()) .insert(CustomPaintPipelineIdComp { pipeline_id }); pipeline_id }, @@ -86,12 +87,13 @@ impl EguiBevyPaintCallbackImpl for CustomPaintCallback { &self, _info: egui::PaintCallbackInfo, render_pass: &mut bevy::render::render_phase::TrackedRenderPass<'pass>, - window_entity: Entity, + window_entity: RenderEntity, _key: EguiPipelineKey, world: &'pass World, ) { let Some(pipeline) = world - .get_entity(window_entity) + .get_entity(window_entity.id()) + .ok() .and_then(|entity| entity.get::()) .and_then(|comp| { world @@ -158,6 +160,7 @@ impl SpecializedRenderPipeline for CustomPipeline { write_mask: ColorWrites::ALL, })], }), + zero_initialize_workgroup_memory: false, } } } @@ -202,23 +205,22 @@ fn setup_worldspace( output_texture }); - commands.spawn(PbrBundle { - mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh()), - material: materials.add(StandardMaterial { + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh())), + MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, base_color_texture: Some(Handle::clone(&output_texture)), alpha_mode: AlphaMode::Blend, // Remove this if you want it to use the world's lighting. unlit: true, ..default() - }), - ..default() - }); + })), + )); commands.spawn(EguiRenderToTextureHandle(output_texture)); - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), - ..default() - }); + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), + )); } fn ui_render_to_texture_example_system( diff --git a/examples/render_egui_to_texture.rs b/examples/render_egui_to_texture.rs index 953ee584..dfde0f50 100644 --- a/examples/render_egui_to_texture.rs +++ b/examples/render_egui_to_texture.rs @@ -49,21 +49,20 @@ fn setup_worldspace( output_texture }); - commands.spawn(PbrBundle { - mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh()), - material: materials.add(StandardMaterial { + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh())), + MeshMaterial3d(materials.add(StandardMaterial { base_color: Color::WHITE, base_color_texture: Some(Handle::clone(&output_texture)), alpha_mode: AlphaMode::Blend, // Remove this if you want it to use the world's lighting. unlit: true, ..default() - }), - ..default() - }); + })), + )); commands.spawn(EguiRenderToTextureHandle(output_texture)); - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), - ..default() - }); + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), + )); } diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index c782b689..39a52c23 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -82,37 +82,36 @@ fn setup( // The cube that will be rendered to the texture. commands - .spawn(PbrBundle { - mesh: cube_handle, - material: preview_material_handle, - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), - ..default() - }) + .spawn(( + Mesh3d(cube_handle), + MeshMaterial3d(preview_material_handle), + Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), + )) .insert(PreviewPassCube) .insert(preview_pass_layer.clone()); // The same light is reused for both passes, // you can specify different lights for preview and main pass by setting appropriate RenderLayers. commands - .spawn(PointLightBundle { - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), - ..default() - }) + .spawn(( + PointLight::default(), + Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + )) .insert(RenderLayers::default().with(1)); commands - .spawn(Camera3dBundle { - camera: Camera { + .spawn(( + Camera3d::default(), + Camera { // render before the "main pass" camera order: -1, target: RenderTarget::Image(image_handle), clear_color: ClearColorConfig::Custom(Color::srgba(1.0, 1.0, 1.0, 0.0)), ..default() }, - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) + Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), - ..default() - }) + )) .insert(preview_pass_layer); let cube_size = 4.0; @@ -122,30 +121,28 @@ fn setup( // Main pass cube. commands - .spawn(PbrBundle { - mesh: cube_handle, - material: main_material_handle, - transform: Transform { + .spawn(( + Mesh3d(cube_handle), + MeshMaterial3d(main_material_handle), + Transform { translation: Vec3::new(0.0, 0.0, 1.5), rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), ..default() }, - ..default() - }) + )) .insert(MainPassCube); // The main pass camera. - commands.spawn(Camera3dBundle { - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) - .looking_at(Vec3::default(), Vec3::Y), - ..default() - }); + commands.spawn(( + Camera3d::default(), + Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)).looking_at(Vec3::default(), Vec3::Y), + )); } fn render_to_image_example_system( cube_preview_image: Res, - preview_cube_query: Query<&Handle, With>, - main_cube_query: Query<&Handle, With>, + preview_cube_query: Query<&MeshMaterial3d, With>, + main_cube_query: Query<&MeshMaterial3d, With>, mut materials: ResMut>, mut contexts: EguiContexts, ) { @@ -225,7 +222,7 @@ fn rotator_system( mut query: Query<&mut Transform, Or<(With, With)>>, ) { for mut transform in &mut query { - transform.rotate_x(1.5 * time.delta_seconds()); - transform.rotate_z(1.3 * time.delta_seconds()); + transform.rotate_x(1.5 * time.delta_secs()); + transform.rotate_z(1.3 * time.delta_secs()); } } diff --git a/examples/side_panel.rs b/examples/side_panel.rs index 14e9c56e..4980bee2 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -88,36 +88,30 @@ fn setup_system( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands.spawn(PbrBundle { - mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), - material: materials.add(Color::srgb(0.3, 0.5, 0.3)), - ..Default::default() - }); - commands.spawn(PbrBundle { - mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), - material: materials.add(Color::srgb(0.8, 0.7, 0.6)), - transform: Transform::from_xyz(0.0, 0.5, 0.0), - ..Default::default() - }); - commands.spawn(PointLightBundle { - point_light: PointLight { + commands.spawn(( + Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), + MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), + )); + commands.spawn(( + Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))), + MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), + Transform::from_xyz(0.0, 0.5, 0.0), + )); + commands.spawn(( + PointLight { intensity: 1500.0, shadows_enabled: true, ..Default::default() }, - transform: Transform::from_xyz(4.0, 8.0, 4.0), - ..Default::default() - }); + Transform::from_xyz(4.0, 8.0, 4.0), + )); let camera_pos = Vec3::new(-2.0, 2.5, 5.0); let camera_transform = Transform::from_translation(camera_pos).looking_at(CAMERA_TARGET, Vec3::Y); commands.insert_resource(OriginalCameraTransform(camera_transform)); - commands.spawn(Camera3dBundle { - transform: camera_transform, - ..Default::default() - }); + commands.spawn((Camera3d::default(), camera_transform)); } fn update_camera_transform_system( diff --git a/examples/two_windows.rs b/examples/two_windows.rs index b072950f..ba6174af 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -35,14 +35,14 @@ fn create_new_window_system(mut commands: Commands) { .id(); // second window camera - commands.spawn(Camera3dBundle { - camera: Camera { + commands.spawn(( + Camera3d::default(), + Camera { target: RenderTarget::Window(WindowRef::Entity(second_window_id)), ..Default::default() }, - transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), - ..Default::default() - }); + Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + )); } fn load_assets_system(mut commands: Commands, assets: Res) { diff --git a/examples/ui.rs b/examples/ui.rs index c47e7412..6f6cc4c3 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -1,4 +1,7 @@ -use bevy::prelude::*; +use bevy::{ + log::{Level, LogPlugin}, + prelude::*, +}; use bevy_egui::{EguiContexts, EguiPlugin, EguiSettings}; struct Images { @@ -23,16 +26,23 @@ impl FromWorld for Images { fn main() { App::new() .insert_resource(ClearColor(Color::BLACK)) - .insert_resource(Msaa::Sample4) .init_resource::() - .add_plugins(DefaultPlugins.set(WindowPlugin { - primary_window: Some(Window { - // You may want this set to `true` if you need virtual keyboard work in mobile browsers. - prevent_default_event_handling: false, - ..default() - }), - ..default() - })) + .add_plugins( + DefaultPlugins + .set(LogPlugin { + filter: "warn,ui=info".to_string(), + level: Level::INFO, + ..Default::default() + }) + .set(WindowPlugin { + primary_window: Some(Window { + // You may want this set to `true` if you need virtual keyboard work in mobile browsers. + prevent_default_event_handling: false, + ..default() + }), + ..default() + }), + ) .add_plugins(EguiPlugin) .add_systems(Startup, configure_visuals_system) .add_systems(Startup, configure_ui_state_system) diff --git a/src/egui_node.rs b/src/egui_node.rs index daeda9c3..243526a7 100644 --- a/src/egui_node.rs +++ b/src/egui_node.rs @@ -9,6 +9,7 @@ use bevy_ecs::{ prelude::*, world::{FromWorld, World}, }; +use bevy_image::{Image, ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor}; use bevy_render::{ render_asset::RenderAssetUsages, render_graph::{Node, NodeRunError, RenderGraphContext}, @@ -24,9 +25,8 @@ use bevy_render::{ VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderContext, RenderDevice, RenderQueue}, - texture::{ - GpuImage, Image, ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor, - }, + sync_world::{MainEntity, RenderEntity}, + texture::GpuImage, view::{ExtractedWindow, ExtractedWindows}, }; use bytemuck::cast_slice; @@ -166,6 +166,7 @@ impl SpecializedRenderPipeline for EguiPipeline { depth_stencil: None, multisample: MultisampleState::default(), push_constant_ranges: vec![], + zero_initialize_workgroup_memory: false, } } } @@ -192,7 +193,8 @@ pub(crate) struct EguiDraw { /// Egui render node. pub struct EguiNode { - window_entity: Entity, + window_entity_main: MainEntity, + window_entity_render: RenderEntity, vertex_data: Vec, vertex_buffer_capacity: usize, vertex_buffer: Option, @@ -206,9 +208,10 @@ pub struct EguiNode { impl EguiNode { /// Constructs Egui render node. - pub fn new(window_entity: Entity) -> Self { + pub fn new(window_entity_main: MainEntity, window_entity_render: RenderEntity) -> Self { EguiNode { - window_entity, + window_entity_main, + window_entity_render, draw_commands: Vec::new(), vertex_data: Vec::new(), vertex_buffer_capacity: 0, @@ -226,7 +229,7 @@ impl Node for EguiNode { fn update(&mut self, world: &mut World) { let Some(key) = world .get_resource::() - .and_then(|windows| windows.windows.get(&self.window_entity)) + .and_then(|windows| windows.windows.get(&self.window_entity_main.id())) .and_then(EguiPipelineKey::from_extracted_window) else { return; @@ -236,7 +239,7 @@ impl Node for EguiNode { world.query::<(&EguiSettings, &RenderTargetSize, &mut EguiRenderOutput)>(); let Ok((egui_settings, window_size, mut render_output)) = - render_target_query.get_mut(world, self.window_entity) + render_target_query.get_mut(world, self.window_entity_render.id()) else { return; }; @@ -324,7 +327,7 @@ impl Node for EguiNode { index_offset += mesh.vertices.len() as u32; let texture_handle = match mesh.texture_id { - egui::TextureId::Managed(id) => EguiTextureId::Managed(self.window_entity, id), + egui::TextureId::Managed(id) => EguiTextureId::Managed(self.window_entity_main, id), egui::TextureId::User(id) => EguiTextureId::User(id), }; @@ -377,7 +380,7 @@ impl Node for EguiNode { command .callback .cb() - .update(info, self.window_entity, key, world); + .update(info, self.window_entity_render, key, world); } } @@ -391,10 +394,12 @@ impl Node for EguiNode { let pipeline_cache = world.get_resource::().unwrap(); let extracted_windows = &world.get_resource::().unwrap().windows; - let extracted_window = extracted_windows.get(&self.window_entity); + let extracted_window = extracted_windows.get(&self.window_entity_main.id()); let swap_chain_texture_view = match extracted_window.and_then(|v| v.swap_chain_texture_view.as_ref()) { - None => return Ok(()), + None => { + return Ok(()); + } Some(window) => window, }; @@ -402,7 +407,9 @@ impl Node for EguiNode { let (vertex_buffer, index_buffer) = match (&self.vertex_buffer, &self.index_buffer) { (Some(vertex), Some(index)) => (vertex, index), - _ => return Ok(()), + _ => { + return Ok(()); + } }; render_queue.write_buffer(vertex_buffer, 0, &self.vertex_data); @@ -434,7 +441,7 @@ impl Node for EguiNode { command.callback.cb().prepare_render( info, render_context, - self.window_entity, + self.window_entity_render, key, world, ); @@ -467,12 +474,12 @@ impl Node for EguiNode { }); let mut render_pass = TrackedRenderPass::new(device, render_pass); - let pipeline_id = egui_pipelines.get(&self.window_entity).unwrap(); + let pipeline_id = egui_pipelines.get(&self.window_entity_main).unwrap(); let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else { return Ok(()); }; - let transform_buffer_offset = egui_transforms.offsets[&self.window_entity]; + let transform_buffer_offset = egui_transforms.offsets[&self.window_entity_main]; let transform_buffer_bind_group = &egui_transforms.bind_group.as_ref().unwrap().1; let mut requires_reset = true; @@ -509,17 +516,17 @@ impl Node for EguiNode { }, }; - let scrissor_rect = + let scissor_rect = clip_urect.intersect(bevy_math::URect::new(0, 0, physical_width, physical_height)); - if scrissor_rect.is_empty() { + if scissor_rect.is_empty() { continue; } render_pass.set_scissor_rect( - scrissor_rect.min.x, - scrissor_rect.min.y, - scrissor_rect.width(), - scrissor_rect.height(), + scissor_rect.min.x, + scissor_rect.min.y, + scissor_rect.width(), + scissor_rect.height(), ); match &draw_command.primitive { @@ -573,7 +580,7 @@ impl Node for EguiNode { command.callback.cb().render( info, &mut render_pass, - self.window_entity, + self.window_entity_render, key, world, ); @@ -683,7 +690,7 @@ pub trait EguiBevyPaintCallbackImpl: Send + Sync { fn update( &self, info: egui::PaintCallbackInfo, - window_entity: Entity, + window_entity: RenderEntity, pipeline_key: EguiPipelineKey, world: &mut World, ); @@ -697,7 +704,7 @@ pub trait EguiBevyPaintCallbackImpl: Send + Sync { &self, info: egui::PaintCallbackInfo, render_context: &mut RenderContext<'w>, - window_entity: Entity, + window_entity: RenderEntity, pipeline_key: EguiPipelineKey, world: &'w World, ) { @@ -713,7 +720,7 @@ pub trait EguiBevyPaintCallbackImpl: Send + Sync { &self, info: egui::PaintCallbackInfo, render_pass: &mut TrackedRenderPass<'pass>, - window_entity: Entity, + window_entity: RenderEntity, pipeline_key: EguiPipelineKey, world: &'pass World, ); diff --git a/src/egui_render_to_texture_node.rs b/src/egui_render_to_texture_node.rs index 570ed0bc..6f669bb6 100644 --- a/src/egui_render_to_texture_node.rs +++ b/src/egui_render_to_texture_node.rs @@ -6,7 +6,7 @@ use crate::{ render_systems::{EguiPipelines, EguiTextureBindGroups, EguiTextureId, EguiTransforms}, EguiRenderOutput, EguiRenderToTextureHandle, EguiSettings, RenderTargetSize, }; -use bevy_ecs::{prelude::*, world::World}; +use bevy_ecs::world::World; use bevy_render::{ render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel}, @@ -16,9 +16,9 @@ use bevy_render::{ PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, }, renderer::{RenderContext, RenderDevice, RenderQueue}, + sync_world::{MainEntity, RenderEntity}, texture::GpuImage, }; - use bytemuck::cast_slice; /// [`RenderLabel`] type for the Egui Render to Texture pass. @@ -32,7 +32,8 @@ pub struct EguiRenderToTexturePass { /// Egui render to texture node. pub struct EguiRenderToTextureNode { - render_to_texture_target: Entity, + render_to_texture_target_render: RenderEntity, + render_to_texture_target_main: MainEntity, vertex_data: Vec, vertex_buffer_capacity: usize, vertex_buffer: Option, @@ -45,9 +46,13 @@ pub struct EguiRenderToTextureNode { } impl EguiRenderToTextureNode { /// Constructs Egui render node. - pub fn new(render_to_texture_target: Entity) -> Self { + pub fn new( + render_to_texture_target_render: RenderEntity, + render_to_texture_target_main: MainEntity, + ) -> Self { EguiRenderToTextureNode { - render_to_texture_target, + render_to_texture_target_render, + render_to_texture_target_main, draw_commands: Vec::new(), vertex_data: Vec::new(), vertex_buffer_capacity: 0, @@ -64,7 +69,7 @@ impl Node for EguiRenderToTextureNode { fn update(&mut self, world: &mut World) { let Ok(image_handle) = world .query::<&EguiRenderToTextureHandle>() - .get(world, self.render_to_texture_target) + .get(world, self.render_to_texture_target_render.id()) .map(|handle| handle.0.clone_weak()) else { return; @@ -80,7 +85,7 @@ impl Node for EguiRenderToTextureNode { let mut render_target_query = world.query::<(&EguiSettings, &RenderTargetSize, &mut EguiRenderOutput)>(); let Ok((egui_settings, render_target_size, mut render_output)) = - render_target_query.get_mut(world, self.render_to_texture_target) + render_target_query.get_mut(world, self.render_to_texture_target_render.id()) else { return; }; @@ -169,7 +174,7 @@ impl Node for EguiRenderToTextureNode { let texture_handle = match mesh.texture_id { egui::TextureId::Managed(id) => { - EguiTextureId::Managed(self.render_to_texture_target, id) + EguiTextureId::Managed(self.render_to_texture_target_main, id) } egui::TextureId::User(id) => EguiTextureId::User(id), }; @@ -223,7 +228,7 @@ impl Node for EguiRenderToTextureNode { command .callback .cb() - .update(info, self.render_to_texture_target, key, world); + .update(info, self.render_to_texture_target_render, key, world); } } @@ -237,7 +242,7 @@ impl Node for EguiRenderToTextureNode { let pipeline_cache = world.get_resource::().unwrap(); let extracted_render_to_texture: Option<&EguiRenderToTextureHandle> = - world.get(self.render_to_texture_target); + world.get(self.render_to_texture_target_render.id()); let Some(render_to_texture_gpu_image) = extracted_render_to_texture else { return Ok(()); }; @@ -270,7 +275,7 @@ impl Node for EguiRenderToTextureNode { command.callback.cb().prepare_render( info, render_context, - self.render_to_texture_target, + self.render_to_texture_target_render, key, world, ); @@ -304,7 +309,7 @@ impl Node for EguiRenderToTextureNode { let mut render_pass = TrackedRenderPass::new(device, render_pass); - let Some(pipeline_id) = egui_pipelines.get(&self.render_to_texture_target) else { + let Some(pipeline_id) = egui_pipelines.get(&self.render_to_texture_target_main) else { bevy_log::error!("no egui_pipeline"); return Ok(()); }; @@ -312,7 +317,7 @@ impl Node for EguiRenderToTextureNode { return Ok(()); }; - let transform_buffer_offset = egui_transforms.offsets[&self.render_to_texture_target]; + let transform_buffer_offset = egui_transforms.offsets[&self.render_to_texture_target_main]; let transform_buffer_bind_group = &egui_transforms.bind_group.as_ref().unwrap().1; let mut requires_reset = true; @@ -415,7 +420,7 @@ impl Node for EguiRenderToTextureNode { command.callback.cb().render( info, &mut render_pass, - self.render_to_texture_target, + self.render_to_texture_target_render, key, world, ); diff --git a/src/lib.rs b/src/lib.rs index 1524a744..2b22a24d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,11 @@ pub mod web_clipboard; pub use egui; use crate::systems::*; +#[cfg(target_arch = "wasm32")] +use crate::text_agent::{ + install_text_agent, is_mobile_safari, process_safari_virtual_keyboard, propagate_text, + SafariVirtualKeyboardHack, TextAgentChannel, VirtualTouchInfo, +}; #[cfg(feature = "render")] use crate::{ egui_node::{EguiPipeline, EGUI_SHADER_HANDLE}, @@ -92,19 +97,9 @@ use crate::{ not(any(target_arch = "wasm32", target_os = "android")) ))] use arboard::Clipboard; - +use bevy_app::prelude::*; #[cfg(feature = "render")] use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle}; -#[cfg(feature = "render")] -use bevy_render::{ - extract_component::{ExtractComponent, ExtractComponentPlugin}, - extract_resource::{ExtractResource, ExtractResourcePlugin}, - render_resource::SpecializedRenderPipelines, - texture::{Image, ImageSampler}, - ExtractSchedule, Render, RenderApp, RenderSet, -}; - -use bevy_app::prelude::*; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, @@ -112,25 +107,27 @@ use bevy_ecs::{ schedule::apply_deferred, system::SystemParam, }; +#[cfg(feature = "render")] +use bevy_image::{Image, ImageSampler}; use bevy_input::InputSystem; use bevy_reflect::Reflect; -use bevy_window::{PrimaryWindow, Window}; - +#[cfg(feature = "render")] +use bevy_render::{ + extract_component::{ExtractComponent, ExtractComponentPlugin}, + extract_resource::{ExtractResource, ExtractResourcePlugin}, + render_resource::SpecializedRenderPipelines, + ExtractSchedule, Render, RenderApp, RenderSet, +}; +use bevy_window::{PrimaryWindow, SystemCursorIcon, Window}; +use bevy_winit::cursor::CursorIcon; #[cfg(all( feature = "manage_clipboard", not(any(target_arch = "wasm32", target_os = "android")) ))] use std::cell::{RefCell, RefMut}; - #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -use crate::text_agent::{ - install_text_agent, is_mobile_safari, process_safari_virtual_keyboard, propagate_text, - SafariVirtualKeyboardHack, TextAgentChannel, VirtualTouchInfo, -}; - /// Adds all Egui resources and render graph nodes. pub struct EguiPlugin; @@ -866,6 +863,8 @@ pub struct EguiContextQuery { pub render_target_size: &'static mut RenderTargetSize, /// [`Window`] component, when rendering to a window. pub window: Option<&'static mut Window>, + /// [`CursorIcon`] component. + pub cursor: Option<&'static mut CursorIcon>, /// [`EguiRenderToTextureHandle`] component, when rendering to a texture. #[cfg(feature = "render")] pub render_to_texture: Option<&'static mut EguiRenderToTextureHandle>, @@ -919,6 +918,7 @@ pub fn setup_new_windows_system( EguiFullOutput::default(), EguiOutput::default(), RenderTargetSize::default(), + CursorIcon::System(SystemCursorIcon::Default), )); } } diff --git a/src/render_systems.rs b/src/render_systems.rs index 1823d846..ce33b7be 100644 --- a/src/render_systems.rs +++ b/src/render_systems.rs @@ -7,6 +7,7 @@ use crate::{ use bevy_asset::prelude::*; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, system::SystemParam}; +use bevy_image::Image; use bevy_math::Vec2; use bevy_render::{ extract_resource::ExtractResource, @@ -17,7 +18,8 @@ use bevy_render::{ DynamicUniformBuffer, PipelineCache, SpecializedRenderPipelines, }, renderer::{RenderDevice, RenderQueue}, - texture::{GpuImage, Image}, + sync_world::{MainEntity, RenderEntity}, + texture::GpuImage, view::ExtractedWindows, Extract, }; @@ -43,7 +45,7 @@ impl ExtractResource for ExtractedEguiManagedTextures { #[derive(Debug, PartialEq, Eq, Hash)] pub enum EguiTextureId { /// Textures allocated via Egui. - Managed(Entity, u64), + Managed(MainEntity, u64), /// Textures allocated via Bevy. User(u64), } @@ -73,7 +75,10 @@ impl ExtractedEguiTextures<'_> { .0 .iter() .map(|(&(window, texture_id), managed_tex)| { - (EguiTextureId::Managed(window, texture_id), managed_tex.id()) + ( + EguiTextureId::Managed(MainEntity::from(window), texture_id), + managed_tex.id(), + ) }) .chain( self.user_textures @@ -86,16 +91,15 @@ impl ExtractedEguiTextures<'_> { /// Sets up the pipeline for newly created windows. pub fn setup_new_windows_render_system( - windows: Extract>>, + windows: Extract>>, mut render_graph: ResMut, ) { - for window in windows.iter() { + for (window, render_window) in windows.iter() { let egui_pass = EguiPass { - entity_index: window.index(), - entity_generation: window.generation(), + entity_index: render_window.index(), + entity_generation: render_window.generation(), }; - - let new_node = EguiNode::new(window); + let new_node = EguiNode::new(MainEntity::from(window), *render_window); render_graph.add_node(egui_pass.clone(), new_node); @@ -104,16 +108,21 @@ pub fn setup_new_windows_render_system( } /// Sets up the pipeline for newly created Render to texture entities. pub fn setup_new_rtt_render_system( - render_to_texture_targets: Extract>>, + render_to_texture_targets: Extract< + Query<(Entity, &RenderEntity), Added>, + >, mut render_graph: ResMut, ) { - for render_to_texture_target in render_to_texture_targets.iter() { + for (render_to_texture_target, render_entity) in render_to_texture_targets.iter() { let egui_rtt_pass = EguiRenderToTexturePass { entity_index: render_to_texture_target.index(), entity_generation: render_to_texture_target.generation(), }; - let new_node = EguiRenderToTextureNode::new(render_to_texture_target); + let new_node = EguiRenderToTextureNode::new( + *render_entity, + MainEntity::from(render_to_texture_target), + ); render_graph.add_node(egui_rtt_pass.clone(), new_node); @@ -126,8 +135,8 @@ pub fn setup_new_rtt_render_system( pub struct EguiTransforms { /// Uniform buffer. pub buffer: DynamicUniformBuffer, - /// Offsets for each window. - pub offsets: HashMap, + /// The Entity is from the main world. + pub offsets: HashMap, /// Bind group. pub bind_group: Option<(BufferId, BindGroup)>, } @@ -161,7 +170,7 @@ impl EguiTransform { /// Prepares Egui transforms. pub fn prepare_egui_transforms_system( mut egui_transforms: ResMut, - render_targets: Query<(Entity, &EguiSettings, &RenderTargetSize)>, + render_targets: Query<(Option<&MainEntity>, &EguiSettings, &RenderTargetSize)>, render_device: Res, render_queue: Res, egui_pipeline: Res, @@ -169,14 +178,16 @@ pub fn prepare_egui_transforms_system( egui_transforms.buffer.clear(); egui_transforms.offsets.clear(); - for (render_target, egui_settings, size) in render_targets.iter() { + for (window_main, egui_settings, size) in render_targets.iter() { let offset = egui_transforms .buffer .push(&EguiTransform::from_render_target_size( *size, egui_settings.scale_factor, )); - egui_transforms.offsets.insert(render_target, offset); + if let Some(window_main) = window_main { + egui_transforms.offsets.insert(*window_main, offset); + } } egui_transforms @@ -240,7 +251,7 @@ pub fn queue_bind_groups_system( /// Cached Pipeline IDs for the specialized instances of `EguiPipeline`. #[derive(Resource)] -pub struct EguiPipelines(pub HashMap); +pub struct EguiPipelines(pub HashMap); /// Queue [`EguiPipeline`] instances specialized on each window's swap chain texture format. pub fn queue_pipelines_system( @@ -249,26 +260,31 @@ pub fn queue_pipelines_system( mut specialized_pipelines: ResMut>, egui_pipeline: Res, windows: Res, - render_to_texture: Query<(Entity, &EguiRenderToTextureHandle)>, + render_to_texture: Query<(&MainEntity, &EguiRenderToTextureHandle)>, images: Res>, ) { - let mut pipelines: HashMap = windows + let mut pipelines: HashMap = windows .iter() .filter_map(|(window_id, window)| { let key = EguiPipelineKey::from_extracted_window(window)?; let pipeline_id = specialized_pipelines.specialize(&pipeline_cache, &egui_pipeline, key); - Some((*window_id, pipeline_id)) + Some((MainEntity::from(*window_id), pipeline_id)) }) .collect(); - pipelines.extend(render_to_texture.iter().filter_map(|(entity_id, handle)| { - let img = images.get(&handle.0)?; - let key = EguiPipelineKey::from_gpu_image(img); - let pipeline_id = specialized_pipelines.specialize(&pipeline_cache, &egui_pipeline, key); + pipelines.extend( + render_to_texture + .iter() + .filter_map(|(main_entity, handle)| { + let img = images.get(&handle.0)?; + let key = EguiPipelineKey::from_gpu_image(img); + let pipeline_id = + specialized_pipelines.specialize(&pipeline_cache, &egui_pipeline, key); - Some((entity_id, pipeline_id)) - })); + Some((*main_entity, pipeline_id)) + }), + ); commands.insert_resource(EguiPipelines(pipelines)); } diff --git a/src/systems.rs b/src/systems.rs index cd75c32e..9abe1c57 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -6,12 +6,16 @@ use crate::{ EguiContext, EguiContextQuery, EguiContextQueryItem, EguiFullOutput, EguiInput, EguiSettings, RenderTargetSize, }; +#[cfg(feature = "render")] +use bevy_asset::Assets; use bevy_ecs::{ event::EventWriter, prelude::*, query::QueryEntityError, system::{Local, Res, SystemParam}, }; +#[cfg(feature = "render")] +use bevy_image::Image; use bevy_input::{ keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput}, mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel}, @@ -22,11 +26,6 @@ use bevy_log::{self, error}; use bevy_time::{Real, Time}; use bevy_window::{CursorMoved, Ime, RequestRedraw}; use bevy_winit::{EventLoopProxy, WakeUp}; - -#[cfg(feature = "render")] -use bevy_asset::Assets; -#[cfg(feature = "render")] -use bevy_render::texture::Image; use std::{marker::PhantomData, time::Duration}; #[allow(missing_docs)] @@ -97,7 +96,7 @@ impl ContextSystemParams<'_, '_> { } Err( err @ QueryEntityError::NoSuchEntity(_) - | err @ QueryEntityError::QueryDoesNotMatch(_), + | err @ QueryEntityError::QueryDoesNotMatch(_, _), ) => { bevy_log::error!( "Failed to get an Egui context for a window ({window:?}): {err:?}", @@ -504,7 +503,7 @@ pub fn process_input_system( for mut context in context_params.contexts.iter_mut() { context.egui_input.modifiers = modifiers; - context.egui_input.time = Some(time.elapsed_seconds_f64()); + context.egui_input.time = Some(time.elapsed_secs_f64()); } // In some cases, we may skip certain events. For example, we ignore `ReceivedCharacter` events @@ -625,10 +624,12 @@ pub fn process_output_system( egui_clipboard.set_contents(&platform_output.copied_text); } - if let Some(mut window) = context.window { + if let Some(mut cursor) = context.cursor { let mut set_icon = || { - window.cursor.icon = egui_to_winit_cursor_icon(platform_output.cursor_icon) - .unwrap_or(bevy_window::CursorIcon::Default); + *cursor = bevy_winit::cursor::CursorIcon::System( + egui_to_winit_cursor_icon(platform_output.cursor_icon) + .unwrap_or(bevy_window::SystemCursorIcon::Default), + ); }; #[cfg(windows)] @@ -687,42 +688,44 @@ pub fn process_output_system( } } -fn egui_to_winit_cursor_icon(cursor_icon: egui::CursorIcon) -> Option { +fn egui_to_winit_cursor_icon( + cursor_icon: egui::CursorIcon, +) -> Option { match cursor_icon { - egui::CursorIcon::Default => Some(bevy_window::CursorIcon::Default), - egui::CursorIcon::PointingHand => Some(bevy_window::CursorIcon::Pointer), - egui::CursorIcon::ResizeHorizontal => Some(bevy_window::CursorIcon::EwResize), - egui::CursorIcon::ResizeNeSw => Some(bevy_window::CursorIcon::NeswResize), - egui::CursorIcon::ResizeNwSe => Some(bevy_window::CursorIcon::NwseResize), - egui::CursorIcon::ResizeVertical => Some(bevy_window::CursorIcon::NsResize), - egui::CursorIcon::Text => Some(bevy_window::CursorIcon::Text), - egui::CursorIcon::Grab => Some(bevy_window::CursorIcon::Grab), - egui::CursorIcon::Grabbing => Some(bevy_window::CursorIcon::Grabbing), - egui::CursorIcon::ContextMenu => Some(bevy_window::CursorIcon::ContextMenu), - egui::CursorIcon::Help => Some(bevy_window::CursorIcon::Help), - egui::CursorIcon::Progress => Some(bevy_window::CursorIcon::Progress), - egui::CursorIcon::Wait => Some(bevy_window::CursorIcon::Wait), - egui::CursorIcon::Cell => Some(bevy_window::CursorIcon::Cell), - egui::CursorIcon::Crosshair => Some(bevy_window::CursorIcon::Crosshair), - egui::CursorIcon::VerticalText => Some(bevy_window::CursorIcon::VerticalText), - egui::CursorIcon::Alias => Some(bevy_window::CursorIcon::Alias), - egui::CursorIcon::Copy => Some(bevy_window::CursorIcon::Copy), - egui::CursorIcon::Move => Some(bevy_window::CursorIcon::Move), - egui::CursorIcon::NoDrop => Some(bevy_window::CursorIcon::NoDrop), - egui::CursorIcon::NotAllowed => Some(bevy_window::CursorIcon::NotAllowed), - egui::CursorIcon::AllScroll => Some(bevy_window::CursorIcon::AllScroll), - egui::CursorIcon::ZoomIn => Some(bevy_window::CursorIcon::ZoomIn), - egui::CursorIcon::ZoomOut => Some(bevy_window::CursorIcon::ZoomOut), - egui::CursorIcon::ResizeEast => Some(bevy_window::CursorIcon::EResize), - egui::CursorIcon::ResizeSouthEast => Some(bevy_window::CursorIcon::SeResize), - egui::CursorIcon::ResizeSouth => Some(bevy_window::CursorIcon::SResize), - egui::CursorIcon::ResizeSouthWest => Some(bevy_window::CursorIcon::SwResize), - egui::CursorIcon::ResizeWest => Some(bevy_window::CursorIcon::WResize), - egui::CursorIcon::ResizeNorthWest => Some(bevy_window::CursorIcon::NwResize), - egui::CursorIcon::ResizeNorth => Some(bevy_window::CursorIcon::NResize), - egui::CursorIcon::ResizeNorthEast => Some(bevy_window::CursorIcon::NeResize), - egui::CursorIcon::ResizeColumn => Some(bevy_window::CursorIcon::ColResize), - egui::CursorIcon::ResizeRow => Some(bevy_window::CursorIcon::RowResize), + egui::CursorIcon::Default => Some(bevy_window::SystemCursorIcon::Default), + egui::CursorIcon::PointingHand => Some(bevy_window::SystemCursorIcon::Pointer), + egui::CursorIcon::ResizeHorizontal => Some(bevy_window::SystemCursorIcon::EwResize), + egui::CursorIcon::ResizeNeSw => Some(bevy_window::SystemCursorIcon::NeswResize), + egui::CursorIcon::ResizeNwSe => Some(bevy_window::SystemCursorIcon::NwseResize), + egui::CursorIcon::ResizeVertical => Some(bevy_window::SystemCursorIcon::NsResize), + egui::CursorIcon::Text => Some(bevy_window::SystemCursorIcon::Text), + egui::CursorIcon::Grab => Some(bevy_window::SystemCursorIcon::Grab), + egui::CursorIcon::Grabbing => Some(bevy_window::SystemCursorIcon::Grabbing), + egui::CursorIcon::ContextMenu => Some(bevy_window::SystemCursorIcon::ContextMenu), + egui::CursorIcon::Help => Some(bevy_window::SystemCursorIcon::Help), + egui::CursorIcon::Progress => Some(bevy_window::SystemCursorIcon::Progress), + egui::CursorIcon::Wait => Some(bevy_window::SystemCursorIcon::Wait), + egui::CursorIcon::Cell => Some(bevy_window::SystemCursorIcon::Cell), + egui::CursorIcon::Crosshair => Some(bevy_window::SystemCursorIcon::Crosshair), + egui::CursorIcon::VerticalText => Some(bevy_window::SystemCursorIcon::VerticalText), + egui::CursorIcon::Alias => Some(bevy_window::SystemCursorIcon::Alias), + egui::CursorIcon::Copy => Some(bevy_window::SystemCursorIcon::Copy), + egui::CursorIcon::Move => Some(bevy_window::SystemCursorIcon::Move), + egui::CursorIcon::NoDrop => Some(bevy_window::SystemCursorIcon::NoDrop), + egui::CursorIcon::NotAllowed => Some(bevy_window::SystemCursorIcon::NotAllowed), + egui::CursorIcon::AllScroll => Some(bevy_window::SystemCursorIcon::AllScroll), + egui::CursorIcon::ZoomIn => Some(bevy_window::SystemCursorIcon::ZoomIn), + egui::CursorIcon::ZoomOut => Some(bevy_window::SystemCursorIcon::ZoomOut), + egui::CursorIcon::ResizeEast => Some(bevy_window::SystemCursorIcon::EResize), + egui::CursorIcon::ResizeSouthEast => Some(bevy_window::SystemCursorIcon::SeResize), + egui::CursorIcon::ResizeSouth => Some(bevy_window::SystemCursorIcon::SResize), + egui::CursorIcon::ResizeSouthWest => Some(bevy_window::SystemCursorIcon::SwResize), + egui::CursorIcon::ResizeWest => Some(bevy_window::SystemCursorIcon::WResize), + egui::CursorIcon::ResizeNorthWest => Some(bevy_window::SystemCursorIcon::NwResize), + egui::CursorIcon::ResizeNorth => Some(bevy_window::SystemCursorIcon::NResize), + egui::CursorIcon::ResizeNorthEast => Some(bevy_window::SystemCursorIcon::NeResize), + egui::CursorIcon::ResizeColumn => Some(bevy_window::SystemCursorIcon::ColResize), + egui::CursorIcon::ResizeRow => Some(bevy_window::SystemCursorIcon::RowResize), egui::CursorIcon::None => None, } } diff --git a/src/text_agent.rs b/src/text_agent.rs index 963ede14..9e46f7a6 100644 --- a/src/text_agent.rs +++ b/src/text_agent.rs @@ -1,17 +1,13 @@ //! The text agent is an `` element used to trigger //! mobile keyboard and IME input. -use std::sync::{LazyLock, Mutex}; - +use crate::{systems::ContextSystemParams, EventClosure, SubscribedEvents}; use bevy_ecs::prelude::*; use bevy_window::RequestRedraw; - use crossbeam_channel::{unbounded, Receiver, Sender}; - +use std::sync::{LazyLock, Mutex}; use wasm_bindgen::prelude::*; -use crate::{systems::ContextSystemParams, EventClosure, SubscribedEvents}; - static AGENT_ID: &str = "egui_text_agent"; #[allow(missing_docs)] @@ -100,7 +96,9 @@ pub fn install_text_agent( .expect("failed input type coercion"); let input = std::rc::Rc::new(input); input.set_type("text"); - input.set_autofocus(true); + if let Err(err) = (&input as &web_sys::HtmlElement).set_autofocus(true) { + log::warn!("Failed to set input autofocus: {err:?}"); + } input .set_attribute("autocapitalize", "off") .expect("failed to turn off autocapitalize"); @@ -138,7 +136,9 @@ pub fn install_text_agent( } // Set size as small as possible, in case user may click on it. input.set_size(1); - input.set_autofocus(true); + if let Err(err) = (&input as &web_sys::HtmlElement).set_autofocus(true) { + log::warn!("Failed to set input autofocus: {err:?}"); + } input.set_hidden(true); let sender = text_agent_channel.sender.clone();