From 9fbeb10cd08bdbd8d4fea6622747428e43ee8856 Mon Sep 17 00:00:00 2001 From: DGriffin91 Date: Mon, 6 Feb 2023 17:22:08 -0800 Subject: [PATCH 01/13] update to use bevy main --- Cargo.toml | 4 +- examples/render_to_image_widget.rs | 6 +- examples/side_panel.rs | 15 +- examples/simple.rs | 11 +- examples/two_windows.rs | 54 +++-- examples/ui.rs | 36 ++-- src/egui_node.rs | 26 +-- src/lib.rs | 323 ++++++++++++++++------------- src/render_systems.rs | 15 +- src/systems.rs | 84 ++++---- 10 files changed, 313 insertions(+), 261 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 85933c494..a4c0864f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ open_url = ["webbrowser"] default_fonts = ["egui/default_fonts"] [dependencies] -bevy = { version = "0.9.0", default-features = false, features = ["bevy_render", "bevy_core_pipeline", "bevy_asset"] } +bevy = { git = "https://github.com/bevyengine/bevy", default-features = false, features = ["bevy_render", "bevy_core_pipeline", "bevy_asset"] } egui = { version = "0.20.0", default-features = false, features = ["bytemuck"] } webbrowser = { version = "0.8.2", optional = true } @@ -32,7 +32,7 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] once_cell = "1.16.0" version-sync = "0.9.4" -bevy = { version = "0.9.0", default-features = false, features = [ +bevy = { git = "https://github.com/bevyengine/bevy", default-features = false, features = [ "x11", "png", "bevy_pbr", diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index 8e76c202c..cf69fb6b7 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -58,6 +58,7 @@ fn setup( usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], }, ..default() }; @@ -107,7 +108,7 @@ fn setup( }, camera: Camera { // render before the "main pass" camera - priority: -1, + order: -1, target: RenderTarget::Image(image_handle), ..default() }, @@ -150,12 +151,13 @@ fn render_to_image_example_system( preview_cube_query: Query<&Handle, With>, main_cube_query: Query<&Handle, With>, mut materials: ResMut>, + windows: Query>, ) { let cube_preview_texture_id = egui_ctx.image_id(&cube_preview_image).unwrap(); let preview_material_handle = preview_cube_query.single(); let preview_material = materials.get_mut(preview_material_handle).unwrap(); - let ctx = egui_ctx.ctx_mut(); + let ctx = egui_ctx.ctx_for_window_mut(windows.iter().next().unwrap()); let mut apply = false; egui::Window::new("Cube material preview").show(ctx, |ui| { ui.image(cube_preview_texture_id, [300.0, 300.0]); diff --git a/examples/side_panel.rs b/examples/side_panel.rs index 60411a678..0d2c861a0 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -28,10 +28,13 @@ fn main() { fn ui_example_system( mut egui_context: ResMut, mut occupied_screen_space: ResMut, + windows: Query>, ) { + let ctx = egui_context.ctx_for_window_mut(windows.iter().next().unwrap()); + occupied_screen_space.left = egui::SidePanel::left("left_panel") .resizable(true) - .show(egui_context.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response @@ -39,7 +42,7 @@ fn ui_example_system( .width(); occupied_screen_space.right = egui::SidePanel::right("right_panel") .resizable(true) - .show(egui_context.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response @@ -47,7 +50,7 @@ fn ui_example_system( .width(); occupied_screen_space.top = egui::TopBottomPanel::top("top_panel") .resizable(true) - .show(egui_context.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response @@ -55,7 +58,7 @@ fn ui_example_system( .height(); occupied_screen_space.bottom = egui::TopBottomPanel::bottom("bottom_panel") .resizable(true) - .show(egui_context.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response @@ -103,7 +106,7 @@ fn setup_system( fn update_camera_transform_system( occupied_screen_space: Res, original_camera_transform: Res, - windows: Res, + windows: Query<&Window>, mut camera_query: Query<(&Projection, &mut Transform)>, ) { let (camera_projection, mut transform) = match camera_query.get_single_mut() { @@ -115,7 +118,7 @@ fn update_camera_transform_system( let frustum_height = 2.0 * distance_to_target * (camera_projection.fov * 0.5).tan(); let frustum_width = frustum_height * camera_projection.aspect_ratio; - let window = windows.get_primary().unwrap(); + let window = windows.iter().next().unwrap(); let left_taken = occupied_screen_space.left / window.width(); let right_taken = occupied_screen_space.right / window.width(); diff --git a/examples/simple.rs b/examples/simple.rs index cdfd0f996..20d1171a6 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -11,8 +11,11 @@ fn main() { .run(); } -fn ui_example_system(mut egui_context: ResMut) { - egui::Window::new("Hello").show(egui_context.ctx_mut(), |ui| { - ui.label("world"); - }); +fn ui_example_system(mut egui_context: ResMut, windows: Query>) { + egui::Window::new("Hello").show( + egui_context.ctx_for_window_mut(windows.iter().next().unwrap()), + |ui| { + ui.label("world"); + }, + ); } diff --git a/examples/two_windows.rs b/examples/two_windows.rs index 070bd24a9..dca51950a 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -1,12 +1,12 @@ use bevy::{ prelude::*, render::{camera::RenderTarget, render_graph::RenderGraph, RenderApp}, - window::{CreateWindow, PresentMode, WindowId}, + window::{PresentMode, WindowRef, WindowResolution}, }; use bevy_egui::{EguiContext, EguiPlugin}; -use once_cell::sync::Lazy; -static SECOND_WINDOW_ID: Lazy = Lazy::new(WindowId::new); +//TODO WindowId::new +//static SECOND_WINDOW_ID: Lazy = Lazy::new(WindowId::new); #[derive(Resource)] struct Images { @@ -24,12 +24,19 @@ fn main() { .add_system(ui_second_window_system); let render_app = app.sub_app_mut(RenderApp); + let window = render_app + .world + .query_filtered::>() + .iter(&render_app.world) + .next() + .unwrap(); + let mut graph = render_app.world.get_resource_mut::().unwrap(); bevy_egui::setup_pipeline( &mut graph, bevy_egui::RenderGraphConfig { - window_id: *SECOND_WINDOW_ID, + window, egui_pass: SECONDARY_EGUI_PASS, }, ); @@ -39,25 +46,21 @@ fn main() { const SECONDARY_EGUI_PASS: &str = "secondary_egui_pass"; -fn create_new_window_system( - mut create_window_events: EventWriter, - mut commands: Commands, -) { - // sends out a "CreateWindow" event, which will be received by the windowing backend - create_window_events.send(CreateWindow { - id: *SECOND_WINDOW_ID, - descriptor: WindowDescriptor { - width: 800., - height: 600., +fn create_new_window_system(mut commands: Commands) { + // Spawn a second window + let second_window_id = commands + .spawn(Window { + title: "Second window".to_owned(), + resolution: WindowResolution::new(800., 600.), present_mode: PresentMode::AutoVsync, - title: "Second window".to_string(), ..Default::default() - }, - }); + }) + .id(); + // second window camera commands.spawn(Camera3dBundle { camera: Camera { - target: RenderTarget::Window(*SECOND_WINDOW_ID), + 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), @@ -86,11 +89,13 @@ fn ui_first_window_system( mut ui_state: Local, mut shared_ui_state: ResMut, images: Res, + windows: Query>, ) { + let first_window = windows.iter().nth(0).unwrap(); let bevy_texture_id = egui_context.add_image(images.bevy_icon.clone_weak()); - egui::Window::new("First Window") - .vscroll(true) - .show(egui_context.ctx_mut(), |ui| { + egui::Window::new("First Window").vscroll(true).show( + egui_context.ctx_for_window_mut(first_window), + |ui| { ui.horizontal(|ui| { ui.label("Write something: "); ui.text_edit_singleline(&mut ui_state.input); @@ -101,7 +106,8 @@ fn ui_first_window_system( }); ui.add(egui::widgets::Image::new(bevy_texture_id, [256.0, 256.0])); - }); + }, + ); } fn ui_second_window_system( @@ -109,9 +115,11 @@ fn ui_second_window_system( mut ui_state: Local, mut shared_ui_state: ResMut, images: Res, + windows: Query>, ) { + let second_window = windows.iter().nth(1).unwrap(); let bevy_texture_id = egui_context.add_image(images.bevy_icon.clone_weak()); - let ctx = match egui_context.try_ctx_for_window_mut(*SECOND_WINDOW_ID) { + let ctx = match egui_context.try_ctx_for_window_mut(second_window) { Some(ctx) => ctx, None => return, }; diff --git a/examples/ui.rs b/examples/ui.rs index 11e1dae11..cf4cc7b66 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -23,7 +23,7 @@ impl FromWorld for Images { fn main() { App::new() .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0))) - .insert_resource(Msaa { samples: 4 }) + .insert_resource(Msaa::Sample4) .init_resource::() .add_plugins(DefaultPlugins) .add_plugin(EguiPlugin) @@ -43,11 +43,16 @@ struct UiState { is_window_open: bool, } -fn configure_visuals_system(mut egui_ctx: ResMut) { - egui_ctx.ctx_mut().set_visuals(egui::Visuals { - window_rounding: 0.0.into(), - ..Default::default() - }); +fn configure_visuals_system( + mut egui_ctx: ResMut, + windows: Query>, +) { + egui_ctx + .ctx_for_window_mut(windows.iter().next().unwrap()) + .set_visuals(egui::Visuals { + window_rounding: 0.0.into(), + ..Default::default() + }); } fn configure_ui_state_system(mut ui_state: ResMut) { @@ -58,12 +63,12 @@ fn update_ui_scale_factor_system( keyboard_input: Res>, mut toggle_scale_factor: Local>, mut egui_settings: ResMut, - windows: Res, + windows: Query<&Window>, ) { if keyboard_input.just_pressed(KeyCode::Slash) || toggle_scale_factor.is_none() { *toggle_scale_factor = Some(!toggle_scale_factor.unwrap_or(true)); - if let Some(window) = windows.get_primary() { + if let Some(window) = windows.iter().next() { let scale_factor = if toggle_scale_factor.unwrap() { 1.0 } else { @@ -85,11 +90,14 @@ fn ui_example_system( // If you need to access the ids from multiple systems, you can also initialize the `Images` // resource while building the app and use `Res` instead. images: Local, + windows: Query>, ) { + let primary_window = windows.iter().next().unwrap(); + let ctx = egui_ctx.ctx_for_window_mut(primary_window); let egui_texture_handle = ui_state .egui_texture_handle .get_or_insert_with(|| { - egui_ctx.ctx_mut().load_texture( + ctx.load_texture( "example-image", egui::ColorImage::example(), Default::default(), @@ -106,9 +114,11 @@ fn ui_example_system( *rendered_texture_id = egui_ctx.add_image(images.bevy_icon.clone_weak()); } + let ctx = egui_ctx.ctx_for_window_mut(primary_window); + egui::SidePanel::left("side_panel") .default_width(200.0) - .show(egui_ctx.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.heading("Side Panel"); ui.horizontal(|ui| { @@ -149,7 +159,7 @@ fn ui_example_system( }); }); - egui::TopBottomPanel::top("top_panel").show(egui_ctx.ctx_mut(), |ui| { + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { // The top panel is often a good place for a menu bar: egui::menu::bar(ui, |ui| { egui::menu::menu_button(ui, "File", |ui| { @@ -160,7 +170,7 @@ fn ui_example_system( }); }); - egui::CentralPanel::default().show(egui_ctx.ctx_mut(), |ui| { + egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Egui Template"); ui.hyperlink("https://github.com/emilk/egui_template"); ui.add(egui::github_link_file_line!( @@ -185,7 +195,7 @@ fn ui_example_system( egui::Window::new("Window") .vscroll(true) .open(&mut ui_state.is_window_open) - .show(egui_ctx.ctx_mut(), |ui| { + .show(ctx, |ui| { ui.label("Windows can be moved by dragging them."); ui.label("They are automatically sized based on contents."); ui.label("You can turn on resizing and scrolling if you like."); diff --git a/src/egui_node.rs b/src/egui_node.rs index 1ee93cf90..2abba9c5d 100644 --- a/src/egui_node.rs +++ b/src/egui_node.rs @@ -5,7 +5,7 @@ use crate::render_systems::{ use bevy::{ core::cast_slice, ecs::world::{FromWorld, World}, - prelude::{HandleUntyped, Resource}, + prelude::{Entity, HandleUntyped, Resource}, reflect::TypeUuid, render::{ render_graph::{Node, NodeRunError, RenderGraphContext}, @@ -23,7 +23,6 @@ use bevy::{ texture::Image, view::ExtractedWindows, }, - window::WindowId, }; /// Egui shader. @@ -159,7 +158,7 @@ struct DrawCommand { /// Egui render node. pub struct EguiNode { - window_id: WindowId, + window_entity: Entity, vertex_data: Vec, vertex_buffer_capacity: usize, vertex_buffer: Option, @@ -171,9 +170,9 @@ pub struct EguiNode { impl EguiNode { /// Constructs Egui render node. - pub fn new(window_id: WindowId) -> Self { + pub fn new(window_entity: Entity) -> Self { EguiNode { - window_id, + window_entity, draw_commands: Vec::new(), vertex_data: Vec::new(), vertex_buffer_capacity: 0, @@ -188,13 +187,14 @@ impl EguiNode { impl Node for EguiNode { fn update(&mut self, world: &mut World) { let mut shapes = world.get_resource_mut::().unwrap(); - let shapes = match shapes.get_mut(&self.window_id) { + let shapes = match shapes.0.get_mut(&self.window_entity) { Some(shapes) => shapes, None => return, }; let shapes = std::mem::take(&mut shapes.shapes); - let window_size = &world.get_resource::().unwrap()[&self.window_id]; + let window_size = + &world.get_resource::().unwrap()[&self.window_entity]; let egui_settings = &world.get_resource::().unwrap(); let egui_context = &world.get_resource::().unwrap(); @@ -205,7 +205,7 @@ impl Node for EguiNode { return; } - let egui_paint_jobs = egui_context[&self.window_id].tessellate(shapes); + let egui_paint_jobs = egui_context[&self.window_entity].tessellate(shapes); let mut index_offset = 0; @@ -252,7 +252,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_id, id), + egui::TextureId::Managed(id) => EguiTextureId::Managed(self.window_entity, id), egui::TextureId::User(id) => EguiTextureId::User(id), }; @@ -309,7 +309,7 @@ impl Node for EguiNode { let extracted_windows = &world.get_resource::().unwrap().windows; let extracted_window = - if let Some(extracted_window) = extracted_windows.get(&self.window_id) { + if let Some(extracted_window) = extracted_windows.get(&self.window_entity) { extracted_window } else { return Ok(()); // No window @@ -338,7 +338,7 @@ impl Node for EguiNode { let mut render_pass = render_context - .command_encoder + .command_encoder() .begin_render_pass(&RenderPassDescriptor { label: Some("egui render pass"), color_attachments: &[Some(RenderPassColorAttachment { @@ -352,7 +352,7 @@ impl Node for EguiNode { depth_stencil_attachment: None, }); - let Some(pipeline_id) = egui_pipelines.get(&extracted_window.id) else { return Ok(()) }; + let Some(pipeline_id) = egui_pipelines.get(&extracted_window.entity) else { return Ok(()) }; let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else { return Ok(()) }; render_pass.set_pipeline(pipeline); @@ -362,7 +362,7 @@ impl Node for EguiNode { IndexFormat::Uint32, ); - let transform_buffer_offset = egui_transforms.offsets[&self.window_id]; + let transform_buffer_offset = egui_transforms.offsets[&self.window_entity]; let transform_buffer_bind_group = &egui_transforms.bind_group.as_ref().unwrap().1; render_pass.set_bind_group(0, transform_buffer_bind_group, &[transform_buffer_offset]); diff --git a/src/lib.rs b/src/lib.rs index d1c0f8e0d..cc016bbf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,18 +68,21 @@ use crate::{ #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] use arboard::Clipboard; use bevy::{ - app::{App, CoreStage, Plugin, StartupStage}, + app::{App, Plugin}, asset::{AssetEvent, Assets, Handle}, - ecs::{event::EventReader, schedule::SystemLabel, system::ResMut}, + ecs::{event::EventReader, system::ResMut}, input::InputSystem, log, - prelude::{Deref, DerefMut, IntoSystemDescriptor, Resource, Shader}, + prelude::{ + CoreSet, Deref, DerefMut, Entity, IntoSystemConfig, Resource, Shader, StartupSet, + SystemSet, With, + }, render::{ render_graph::RenderGraph, render_resource::SpecializedRenderPipelines, texture::Image, - RenderApp, RenderStage, + ExtractSchedule, RenderApp, RenderSet, }, utils::HashMap, - window::WindowId, + window::Window, }; use egui_node::EguiNode; #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] @@ -125,19 +128,19 @@ impl Default for EguiSettings { /// Stores [`EguiRenderOutput`] for each window. #[derive(Resource, Deref, DerefMut, Default)] -pub struct EguiRenderOutputContainer(pub HashMap); +pub struct EguiRenderOutputContainer(pub HashMap); /// Stores [`EguiInput`] for each window. #[derive(Resource, Deref, DerefMut, Default)] -pub struct EguiRenderInputContainer(pub HashMap); +pub struct EguiRenderInputContainer(pub HashMap); /// Stores [`EguiOutputContainer`] for each window. #[derive(Resource, Deref, DerefMut, Default)] -pub struct EguiOutputContainer(pub HashMap); +pub struct EguiOutputContainer(pub HashMap); /// Stores [`WindowSize`] for each window. #[derive(Resource, Deref, DerefMut, Default)] -pub struct EguiWindowSizeContainer(pub HashMap); +pub struct EguiWindowSizeContainer(pub HashMap); /// Is used for storing the input passed to Egui in the [`EguiRenderInputContainer`] resource. /// @@ -239,10 +242,10 @@ pub struct EguiOutput { /// A resource for storing `bevy_egui` context. #[derive(Clone, Resource)] pub struct EguiContext { - ctx: HashMap, + ctx: HashMap, user_textures: HashMap, u64>, last_texture_id: u64, - mouse_position: Option<(WindowId, egui::Vec2)>, + mouse_position: Option<(Entity, egui::Vec2)>, } impl EguiContext { @@ -255,16 +258,17 @@ impl EguiContext { } } - /// Egui context of the primary window. - /// - /// This function is only available when the `immutable_ctx` feature is enabled. - /// The preferable way is to use `ctx_mut` to avoid unpredictable blocking inside UI systems. - #[cfg(feature = "immutable_ctx")] - #[must_use] - #[track_caller] - pub fn ctx(&self) -> &egui::Context { - self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") - } + // TODO WindowId::primary() + ///// Egui context of the primary window. + ///// + ///// This function is only available when the `immutable_ctx` feature is enabled. + ///// The preferable way is to use `ctx_mut` to avoid unpredictable blocking inside UI systems. + //#[cfg(feature = "immutable_ctx")] + //#[must_use] + //#[track_caller] + //pub fn ctx(&self) -> &egui::Context { + // self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") + //} /// Egui context for a specific window. /// If you want to display UI on a non-primary window, make sure to set up the render graph by @@ -294,28 +298,29 @@ impl EguiContext { self.ctx.get(&window) } - /// Egui context of the primary window. - #[must_use] - #[track_caller] - pub fn ctx_mut(&mut self) -> &egui::Context { - self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx_mut` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") - } + //TODO WindowId::primary() + ///// Egui context of the primary window. + //#[must_use] + //#[track_caller] + //pub fn ctx_mut(&mut self) -> &egui::Context { + // self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx_mut` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") + //} /// Egui context for a specific window. /// If you want to display UI on a non-primary window, make sure to set up the render graph by /// calling [`setup_pipeline`]. #[must_use] #[track_caller] - pub fn ctx_for_window_mut(&mut self, window: WindowId) -> &egui::Context { + pub fn ctx_for_window_mut(&mut self, window: Entity) -> &egui::Context { self.ctx .get(&window) - .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window_mut` was called for an uninitialized context (window {window}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)")) + .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window_mut` was called for an uninitialized context (window {window:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)")) } /// Fallible variant of [`EguiContext::ctx_for_window_mut`]. Make sure to set up the render /// graph by calling [`setup_pipeline`]. #[must_use] - pub fn try_ctx_for_window_mut(&mut self, window: WindowId) -> Option<&egui::Context> { + pub fn try_ctx_for_window_mut(&mut self, window: Entity) -> Option<&egui::Context> { self.ctx.get(&window) } @@ -327,16 +332,13 @@ impl EguiContext { /// Panics if the passed window ids aren't unique. #[must_use] #[track_caller] - pub fn ctx_for_windows_mut( - &mut self, - ids: [WindowId; N], - ) -> [&egui::Context; N] { + pub fn ctx_for_windows_mut(&mut self, ids: [Entity; N]) -> [&egui::Context; N] { let mut unique_ids = bevy::utils::HashSet::default(); assert!( ids.iter().all(move |id| unique_ids.insert(id)), "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique: {ids:?}", ); - ids.map(|id| self.ctx.get(&id).unwrap_or_else(|| panic!("`EguiContext::ctx_for_windows_mut` was called for an uninitialized context (window {id}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)"))) + ids.map(|id| self.ctx.get(&id).unwrap_or_else(|| panic!("`EguiContext::ctx_for_windows_mut` was called for an uninitialized context (window {id:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)"))) } /// Fallible variant of [`EguiContext::ctx_for_windows_mut`]. Make sure to set up the render @@ -348,7 +350,7 @@ impl EguiContext { #[must_use] pub fn try_ctx_for_windows_mut( &mut self, - ids: [WindowId; N], + ids: [Entity; N], ) -> [Option<&egui::Context>; N] { let mut unique_ids = bevy::utils::HashSet::default(); assert!( @@ -425,7 +427,7 @@ pub mod node { pub const EGUI_PASS: &str = "egui_pass"; } -#[derive(SystemLabel, Clone, Hash, Debug, Eq, PartialEq)] +#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)] /// The names of `bevy_egui` startup systems. pub enum EguiStartupSystem { /// Initializes Egui contexts for available windows. @@ -433,7 +435,7 @@ pub enum EguiStartupSystem { } /// The names of egui systems. -#[derive(SystemLabel, Clone, Hash, Debug, Eq, PartialEq)] +#[derive(SystemSet, Clone, Hash, Debug, Eq, PartialEq)] pub enum EguiSystem { /// Reads Egui inputs (keyboard, mouse, etc) and writes them into the [`EguiInput`] resource. /// @@ -452,49 +454,49 @@ impl Plugin for EguiPlugin { let world = &mut app.world; world.insert_resource(EguiSettings::default()); world.insert_resource(EguiRenderInputContainer( - HashMap::::default(), - )); - world.insert_resource(EguiOutputContainer( - HashMap::::default(), + HashMap::::default(), )); + world.insert_resource(EguiOutputContainer(HashMap::::default())); world.insert_resource(EguiWindowSizeContainer( - HashMap::::default(), + HashMap::::default(), + )); + world.insert_resource(EguiRenderOutputContainer( + HashMap::::default(), )); - world.insert_resource(EguiRenderOutputContainer(HashMap::< - WindowId, - EguiRenderOutput, - >::default())); world.insert_resource(EguiManagedTextures::default()); #[cfg(feature = "manage_clipboard")] world.insert_resource(EguiClipboard::default()); world.insert_resource(EguiContext::new()); - app.add_startup_system_to_stage( - StartupStage::PreStartup, - init_contexts_startup_system.label(EguiStartupSystem::InitContexts), + app.add_startup_system( + init_contexts_startup_system + .in_set(EguiStartupSystem::InitContexts) + .in_set(StartupSet::PreStartup), ); - app.add_system_to_stage( - CoreStage::PreUpdate, + app.add_system( process_input_system - .label(EguiSystem::ProcessInput) - .after(InputSystem), + .in_set(EguiSystem::ProcessInput) + .after(InputSystem) + .in_base_set(CoreSet::PreUpdate), ); - app.add_system_to_stage( - CoreStage::PreUpdate, + app.add_system( begin_frame_system - .label(EguiSystem::BeginFrame) - .after(EguiSystem::ProcessInput), + .in_set(EguiSystem::BeginFrame) + .after(EguiSystem::ProcessInput) + .in_base_set(CoreSet::PreUpdate), ); - app.add_system_to_stage( - CoreStage::PostUpdate, - process_output_system.label(EguiSystem::ProcessOutput), + app.add_system( + process_output_system + .in_set(EguiSystem::ProcessOutput) + .in_base_set(CoreSet::PostUpdate), ); - app.add_system_to_stage( - CoreStage::PostUpdate, - update_egui_textures_system.after(EguiSystem::ProcessOutput), + app.add_system( + update_egui_textures_system + .after(EguiSystem::ProcessOutput) + .in_base_set(CoreSet::PostUpdate), ); - app.add_system_to_stage(CoreStage::Last, free_egui_textures_system); + app.add_system(free_egui_textures_system.in_base_set(CoreSet::Last)); let mut shaders = app.world.resource_mut::>(); shaders.set_untracked( @@ -507,30 +509,43 @@ impl Plugin for EguiPlugin { .init_resource::() .init_resource::>() .init_resource::() - .add_system_to_stage( - RenderStage::Extract, - render_systems::extract_egui_render_data_system, + .add_systems_to_schedule( + ExtractSchedule, + ( + render_systems::extract_egui_render_data_system, + render_systems::extract_egui_textures_system, + ), ) - .add_system_to_stage( - RenderStage::Extract, - render_systems::extract_egui_textures_system, + .add_system( + render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), ) - .add_system_to_stage( - RenderStage::Prepare, - render_systems::prepare_egui_transforms_system, - ) - .add_system_to_stage(RenderStage::Queue, render_systems::queue_bind_groups_system) - .add_system_to_stage(RenderStage::Queue, render_systems::queue_pipelines_system); + .add_system(render_systems::queue_bind_groups_system.in_set(RenderSet::Queue)) + .add_system(render_systems::queue_pipelines_system.in_set(RenderSet::Queue)); + + // TODO window doesn't exist yet + let window = render_app + .world + .query_filtered::>() + .iter(&render_app.world) + .next() + .unwrap(); let mut render_graph = render_app.world.get_resource_mut::().unwrap(); - setup_pipeline(&mut render_graph, RenderGraphConfig::default()); + + setup_pipeline( + &mut render_graph, + RenderGraphConfig { + window, + egui_pass: node::EGUI_PASS, + }, + ); } } } /// Contains textures allocated and painted by Egui. #[derive(Resource, Deref, DerefMut, Default)] -pub struct EguiManagedTextures(pub HashMap<(WindowId, u64), EguiManagedTexture>); +pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>); /// Represents a texture allocated and painted by Egui. pub struct EguiManagedTexture { @@ -622,33 +637,32 @@ fn free_egui_textures_system( /// Egui's render graph config. pub struct RenderGraphConfig { /// Target window. - pub window_id: WindowId, + pub window: Entity, /// Render pass name. pub egui_pass: &'static str, } -impl Default for RenderGraphConfig { - fn default() -> Self { - RenderGraphConfig { - window_id: WindowId::primary(), - egui_pass: node::EGUI_PASS, - } - } -} +//TODO WindowId::primary() +//impl Default for RenderGraphConfig { +// fn default() -> Self { +// RenderGraphConfig { +// window_id: WindowId::primary(), +// egui_pass: node::EGUI_PASS, +// } +// } +//} /// Set up egui render pipeline. /// /// The pipeline for the primary window will already be set up by the [`EguiPlugin`], /// so you'll only need to manually call this if you want to use multiple windows. pub fn setup_pipeline(render_graph: &mut RenderGraph, config: RenderGraphConfig) { - render_graph.add_node(config.egui_pass, EguiNode::new(config.window_id)); + render_graph.add_node(config.egui_pass, EguiNode::new(config.window)); - render_graph - .add_node_edge( - bevy::render::main_graph::node::CAMERA_DRIVER, - config.egui_pass, - ) - .unwrap(); + render_graph.add_node_edge( + bevy::render::main_graph::node::CAMERA_DRIVER, + config.egui_pass, + ); let _ = render_graph.add_node_edge("ui_pass_driver", config.egui_pass); } @@ -657,7 +671,10 @@ pub fn setup_pipeline(render_graph: &mut RenderGraph, config: RenderGraphConfig) mod tests { use super::*; use bevy::{ - app::PluginGroup, render::settings::WgpuSettings, winit::WinitPlugin, DefaultPlugins, + app::PluginGroup, + render::{settings::WgpuSettings, RenderPlugin}, + winit::WinitPlugin, + DefaultPlugins, }; #[test] @@ -668,60 +685,70 @@ mod tests { #[test] fn test_headless_mode() { App::new() - .insert_resource(WgpuSettings { - backends: None, - ..Default::default() - }) - .add_plugins(DefaultPlugins.build().disable::()) + .add_plugins( + DefaultPlugins + .set(RenderPlugin { + wgpu_settings: WgpuSettings { + backends: None, + ..Default::default() + }, + }) + .build() + .disable::(), + ) .add_plugin(EguiPlugin) .update(); } - #[test] - fn test_ctx_for_windows_mut_unique_check_passes() { - let mut egui_context = EguiContext::new(); - let primary_window = WindowId::primary(); - let second_window = WindowId::new(); - egui_context.ctx.insert(primary_window, Default::default()); - egui_context.ctx.insert(second_window, Default::default()); - let [primary_ctx, second_ctx] = - egui_context.ctx_for_windows_mut([primary_window, second_window]); - assert!(primary_ctx != second_ctx); - } - - #[test] - #[should_panic( - expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - )] - fn test_ctx_for_windows_mut_unique_check_panics() { - let mut egui_context = EguiContext::new(); - let primary_window = WindowId::primary(); - egui_context.ctx.insert(primary_window, Default::default()); - let _ = egui_context.ctx_for_windows_mut([primary_window, primary_window]); - } - - #[test] - fn test_try_ctx_for_windows_mut_unique_check_passes() { - let mut egui_context = EguiContext::new(); - let primary_window = WindowId::primary(); - let second_window = WindowId::new(); - egui_context.ctx.insert(primary_window, Default::default()); - egui_context.ctx.insert(second_window, Default::default()); - let [primary_ctx, second_ctx] = - egui_context.try_ctx_for_windows_mut([primary_window, second_window]); - assert!(primary_ctx.is_some()); - assert!(second_ctx.is_some()); - assert!(primary_ctx != second_ctx); - } - - #[test] - #[should_panic( - expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - )] - fn test_try_ctx_for_windows_mut_unique_check_panics() { - let mut egui_context = EguiContext::new(); - let primary_window = WindowId::primary(); - egui_context.ctx.insert(primary_window, Default::default()); - let _ = egui_context.try_ctx_for_windows_mut([primary_window, primary_window]); - } + // TODO WindowId::primary() + //#[test] + //fn test_ctx_for_windows_mut_unique_check_passes() { + // let mut egui_context = EguiContext::new(); + // let primary_window = WindowId::primary(); + // let second_window = WindowId::new(); + // egui_context.ctx.insert(primary_window, Default::default()); + // egui_context.ctx.insert(second_window, Default::default()); + // let [primary_ctx, second_ctx] = + // egui_context.ctx_for_windows_mut([primary_window, second_window]); + // assert!(primary_ctx != second_ctx); + //} + + // TODO WindowId::primary() + //#[test] + //#[should_panic( + // expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" + //)] + //fn test_ctx_for_windows_mut_unique_check_panics() { + // let mut egui_context = EguiContext::new(); + // let primary_window = WindowId::primary(); + // egui_context.ctx.insert(primary_window, Default::default()); + // let _ = egui_context.ctx_for_windows_mut([primary_window, primary_window]); + //} + + //TODO WindowId::primary() + //#[test] + //fn test_try_ctx_for_windows_mut_unique_check_passes() { + // let mut egui_context = EguiContext::new(); + // let primary_window = WindowId::primary(); + // let second_window = WindowId::new(); + // egui_context.ctx.insert(primary_window, Default::default()); + // egui_context.ctx.insert(second_window, Default::default()); + // let [primary_ctx, second_ctx] = + // egui_context.try_ctx_for_windows_mut([primary_window, second_window]); + // assert!(primary_ctx.is_some()); + // assert!(second_ctx.is_some()); + // assert!(primary_ctx != second_ctx); + //} + + //TODO WindowId::primary() + //#[test] + //#[should_panic( + // expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" + //)] + //fn test_try_ctx_for_windows_mut_unique_check_panics() { + // let mut egui_context = EguiContext::new(); + // let primary_window = WindowId::primary(); + // egui_context.ctx.insert(primary_window, Default::default()); + // let _ = egui_context.try_ctx_for_windows_mut([primary_window, primary_window]); + //} } diff --git a/src/render_systems.rs b/src/render_systems.rs index 31505412a..3a921de67 100644 --- a/src/render_systems.rs +++ b/src/render_systems.rs @@ -19,16 +19,15 @@ use bevy::{ Extract, }, utils::HashMap, - window::WindowId, }; /// Extracted Egui render output. #[derive(Resource, Deref, DerefMut, Default)] -pub struct ExtractedRenderOutput(pub HashMap); +pub struct ExtractedRenderOutput(pub HashMap); /// Extracted window sizes. #[derive(Resource, Deref, DerefMut, Default)] -pub struct ExtractedWindowSizes(pub HashMap); +pub struct ExtractedWindowSizes(pub HashMap); /// Extracted Egui settings. #[derive(Resource, Deref, DerefMut, Default)] @@ -36,13 +35,13 @@ pub struct ExtractedEguiSettings(pub EguiSettings); /// Extracted Egui contexts. #[derive(Resource, Deref, DerefMut, Default)] -pub struct ExtractedEguiContext(pub HashMap); +pub struct ExtractedEguiContext(pub HashMap); /// Corresponds to Egui's [`egui::TextureId`]. #[derive(Debug, PartialEq, Eq, Hash)] pub enum EguiTextureId { /// Textures allocated via Egui. - Managed(WindowId, u64), + Managed(Entity, u64), /// Textures allocated via Bevy. User(u64), } @@ -51,7 +50,7 @@ pub enum EguiTextureId { #[derive(Resource, Default)] pub struct ExtractedEguiTextures { /// Maps Egui managed texture ids to Bevy image handles. - pub egui_textures: HashMap<(WindowId, u64), Handle>, + pub egui_textures: HashMap<(Entity, u64), Handle>, /// Maps Bevy managed texture handles to Egui user texture ids. pub user_textures: HashMap, u64>, } @@ -109,7 +108,7 @@ pub struct EguiTransforms { /// Uniform buffer. pub buffer: DynamicUniformBuffer, /// Offsets for each window. - pub offsets: HashMap, + pub offsets: HashMap, /// Bind group. pub bind_group: Option<(BufferId, BindGroup)>, } @@ -220,7 +219,7 @@ pub fn queue_bind_groups_system( /// Cached Pipeline IDs for the specialized `EguiPipeline`s #[derive(Resource)] -pub struct EguiPipelines(pub HashMap); +pub struct EguiPipelines(pub HashMap); /// Queue [`EguiPipeline`]s specialized on each window's swap chain texture format. pub fn queue_pipelines_system( diff --git a/src/systems.rs b/src/systems.rs index 419f875ae..213f1c3c5 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -14,11 +14,11 @@ use bevy::{ mouse::{MouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel}, ButtonState, Input, }, - prelude::{EventReader, Time}, + prelude::{Entity, EventReader, Query, Time}, utils::HashMap, window::{ - CursorEntered, CursorLeft, CursorMoved, ReceivedCharacter, RequestRedraw, WindowCreated, - WindowFocused, WindowId, Windows, + CursorEntered, CursorLeft, CursorMoved, ReceivedCharacter, RequestRedraw, Window, + WindowCreated, WindowFocused, }, }; use std::marker::PhantomData; @@ -66,8 +66,8 @@ pub struct InputResources<'w, 's> { #[allow(missing_docs)] #[derive(SystemParam)] pub struct WindowResources<'w, 's> { - pub focused_window: Local<'s, Option>, - pub windows: ResMut<'w, Windows>, + pub focused_window: Local<'s, Option>, + pub windows: Query<'w, 's, (Entity, &'static mut Window)>, pub window_sizes: ResMut<'w, EguiWindowSizeContainer>, #[system_param(ignore)] _marker: PhantomData<&'s ()>, @@ -99,12 +99,16 @@ pub fn process_input_system( ) { // This is a workaround for Windows. For some reason, `WindowFocused` event isn't fired // when a window is created. - if let Some(event) = input_events.ev_window_created.iter().next_back() { - *window_resources.focused_window = Some(event.id); + if let Some(event) = input_events.ev_window_created.iter().last() { + *window_resources.focused_window = Some(event.window); } for event in input_events.ev_window_focused.iter() { - *window_resources.focused_window = if event.focused { Some(event.id) } else { None }; + *window_resources.focused_window = if event.focused { + Some(event.window) + } else { + None + }; } update_window_contexts( @@ -139,14 +143,14 @@ pub fn process_input_system( }; let mut cursor_left_window = None; - if let Some(cursor_left) = input_events.ev_cursor_left.iter().next_back() { - cursor_left_window = Some(cursor_left.id); + if let Some(cursor_left) = input_events.ev_cursor_left.iter().last() { + cursor_left_window = Some(cursor_left.window); } let cursor_entered_window = input_events .ev_cursor_entered .iter() - .next_back() - .map(|event| event.id); + .last() + .map(|event| event.window); // When a user releases a mouse button, Safari emits both `CursorLeft` and `CursorEntered` // events during the same frame. We don't want to reset mouse position in such a case, otherwise @@ -159,20 +163,20 @@ pub fn process_input_system( None }; - if let Some(cursor_moved) = input_events.ev_cursor.iter().next_back() { + if let Some(cursor_moved) = input_events.ev_cursor.iter().last() { // If we've left the window, it's unlikely that we've moved the cursor back to the same // window this exact frame, so we are safe to ignore all `CursorMoved` events for the window // that has been left. - if cursor_left_window != Some(cursor_moved.id) { + if cursor_left_window != Some(cursor_moved.window) { let scale_factor = egui_settings.scale_factor as f32; let mut mouse_position: (f32, f32) = (cursor_moved.position / scale_factor).into(); - mouse_position.1 = window_resources.window_sizes[&cursor_moved.id].height() + mouse_position.1 = window_resources.window_sizes[&cursor_moved.window].height() / scale_factor - mouse_position.1; - egui_context.mouse_position = Some((cursor_moved.id, mouse_position.into())); + egui_context.mouse_position = Some((cursor_moved.window, mouse_position.into())); input_resources .egui_input - .get_mut(&cursor_moved.id) + .get_mut(&cursor_moved.window) .unwrap() .events .push(egui::Event::PointerMoved(egui::pos2( @@ -245,7 +249,7 @@ pub fn process_input_system( if !event.char.is_control() { input_resources .egui_input - .get_mut(&event.id) + .get_mut(&event.window) .unwrap() .events .push(egui::Event::Text(event.char.to_string())); @@ -307,12 +311,12 @@ pub fn process_input_system( fn update_window_contexts( egui_context: &mut EguiContext, - egui_input: &mut HashMap, + egui_input: &mut HashMap, window_resources: &mut WindowResources, egui_settings: &EguiSettings, ) { - for window in window_resources.windows.iter() { - let egui_input = egui_input.entry(window.id()).or_default(); + for (window_entity, window) in window_resources.windows.iter() { + let egui_input = egui_input.entry(window_entity).or_default(); let window_size = WindowSize::new( window.physical_width() as f32, @@ -340,8 +344,8 @@ fn update_window_contexts( window_resources .window_sizes - .insert(window.id(), window_size); - egui_context.ctx.entry(window.id()).or_default(); + .insert(window_entity, window_size); + egui_context.ctx.entry(window_entity).or_default(); } } @@ -373,9 +377,9 @@ pub fn process_output_system( mut egui_context: ResMut, mut output: OutputResources, #[cfg(feature = "manage_clipboard")] mut egui_clipboard: ResMut, - mut windows: Option>, + mut windows: Query<&mut Window>, mut event: EventWriter, - #[cfg(windows)] mut last_cursor_icon: Local>, + #[cfg(windows)] mut last_cursor_icon: Local>, ) { for (window_id, ctx) in egui_context.ctx.iter_mut() { let full_output = ctx.end_frame(); @@ -397,26 +401,22 @@ pub fn process_output_system( egui_clipboard.set_contents(&platform_output.copied_text); } - if let Some(ref mut windows) = windows { - if let Some(window) = windows.get_mut(*window_id) { - let mut set_icon = || { - window.set_cursor_icon( - egui_to_winit_cursor_icon(platform_output.cursor_icon) - .unwrap_or(bevy::window::CursorIcon::Default), - ) - }; + if let Ok(mut window) = windows.get_mut(*window_id) { + let mut set_icon = || { + window.cursor.icon = egui_to_winit_cursor_icon(platform_output.cursor_icon) + .unwrap_or(bevy::window::CursorIcon::Default); + }; - #[cfg(windows)] - { - let last_cursor_icon = last_cursor_icon.entry(*window_id).or_default(); - if *last_cursor_icon != platform_output.cursor_icon { - set_icon(); - *last_cursor_icon = platform_output.cursor_icon; - } + #[cfg(windows)] + { + let last_cursor_icon = last_cursor_icon.entry(*window_id).or_default(); + if *last_cursor_icon != platform_output.cursor_icon { + set_icon(); + *last_cursor_icon = platform_output.cursor_icon; } - #[cfg(not(windows))] - set_icon(); } + #[cfg(not(windows))] + set_icon(); } if repaint_after.is_zero() { From 5646a586d123aeb5a1a8fe916cd55df535217546 Mon Sep 17 00:00:00 2001 From: DGriffin91 Date: Tue, 21 Feb 2023 12:54:49 -0800 Subject: [PATCH 02/13] make example initially work --- examples/side_panel.rs | 5 +++- examples/two_windows.rs | 42 ++++++++++++++------------ src/egui_node.rs | 17 +++++++---- src/lib.rs | 65 +++++++++++++++++++++++++++-------------- 4 files changed, 82 insertions(+), 47 deletions(-) diff --git a/examples/side_panel.rs b/examples/side_panel.rs index 0d2c861a0..ca53bcd4e 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -72,7 +72,10 @@ fn setup_system( mut materials: ResMut>, ) { commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + mesh: meshes.add(Mesh::from(shape::Plane { + size: 5.0, + subdivisions: 0, + })), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..Default::default() }); diff --git a/examples/two_windows.rs b/examples/two_windows.rs index dca51950a..f9cb18a88 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -1,7 +1,7 @@ use bevy::{ prelude::*, - render::{camera::RenderTarget, render_graph::RenderGraph, RenderApp}, - window::{PresentMode, WindowRef, WindowResolution}, + render::{camera::RenderTarget, render_graph::RenderGraph, Extract, RenderApp}, + window::{PresentMode, PrimaryWindow, WindowRef, WindowResolution}, }; use bevy_egui::{EguiContext, EguiPlugin}; @@ -24,26 +24,32 @@ fn main() { .add_system(ui_second_window_system); let render_app = app.sub_app_mut(RenderApp); - let window = render_app - .world - .query_filtered::>() - .iter(&render_app.world) - .next() - .unwrap(); - - let mut graph = render_app.world.get_resource_mut::().unwrap(); - - bevy_egui::setup_pipeline( - &mut graph, - bevy_egui::RenderGraphConfig { - window, - egui_pass: SECONDARY_EGUI_PASS, - }, - ); + + render_app.add_system_to_schedule(ExtractSchedule, init_second_window); app.run(); } +fn init_second_window( + query: Extract>>, + mut render_graph: ResMut, + mut is_setup: Local, +) { + if *is_setup { + return; + } + if let Some((entity, _window)) = query.iter().next() { + bevy_egui::setup_pipeline( + &mut render_graph, + bevy_egui::RenderGraphConfig { + window: entity, + egui_pass: SECONDARY_EGUI_PASS, + }, + ); + *is_setup = true; + } +} + const SECONDARY_EGUI_PASS: &str = "secondary_egui_pass"; fn create_new_window_system(mut commands: Commands) { diff --git a/src/egui_node.rs b/src/egui_node.rs index 2abba9c5d..4d6172028 100644 --- a/src/egui_node.rs +++ b/src/egui_node.rs @@ -14,10 +14,11 @@ use bevy::{ BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, BufferAddress, BufferBindingType, BufferDescriptor, BufferUsages, ColorTargetState, ColorWrites, Extent3d, FragmentState, FrontFace, IndexFormat, LoadOp, MultisampleState, Operations, - PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor, - RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages, ShaderType, - SpecializedRenderPipeline, TextureDimension, TextureFormat, TextureSampleType, - TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + PipelineCache, PrimitiveState, PushConstantRange, RenderPassColorAttachment, + RenderPassDescriptor, RenderPipelineDescriptor, SamplerBindingType, Shader, + ShaderStages, ShaderType, SpecializedRenderPipeline, TextureDimension, TextureFormat, + TextureSampleType, TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, }, renderer::{RenderContext, RenderDevice, RenderQueue}, texture::Image, @@ -100,10 +101,10 @@ impl SpecializedRenderPipeline for EguiPipeline { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { RenderPipelineDescriptor { label: Some("egui render pipeline".into()), - layout: Some(vec![ + layout: vec![ self.transform_bind_group_layout.clone(), self.texture_bind_group_layout.clone(), - ]), + ], vertex: VertexState { shader: EGUI_SHADER_HANDLE.typed(), shader_defs: Vec::new(), @@ -145,6 +146,10 @@ impl SpecializedRenderPipeline for EguiPipeline { }, depth_stencil: None, multisample: MultisampleState::default(), + push_constant_ranges: vec![PushConstantRange { + stages: ShaderStages::FRAGMENT, + range: 0..0, + }], } } } diff --git a/src/lib.rs b/src/lib.rs index cc016bbf1..582373573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,15 +74,15 @@ use bevy::{ input::InputSystem, log, prelude::{ - CoreSet, Deref, DerefMut, Entity, IntoSystemConfig, Resource, Shader, StartupSet, - SystemSet, With, + CoreSet, Deref, DerefMut, Entity, IntoSystemConfig, Local, Query, Resource, Shader, + StartupSet, SystemSet, }, render::{ render_graph::RenderGraph, render_resource::SpecializedRenderPipelines, texture::Image, - ExtractSchedule, RenderApp, RenderSet, + Extract, ExtractSchedule, RenderApp, RenderSet, }, utils::HashMap, - window::Window, + window::PrimaryWindow, }; use egui_node::EguiNode; #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] @@ -471,7 +471,7 @@ impl Plugin for EguiPlugin { app.add_startup_system( init_contexts_startup_system .in_set(EguiStartupSystem::InitContexts) - .in_set(StartupSet::PreStartup), + .in_base_set(StartupSet::PreStartup), ); app.add_system( @@ -514,6 +514,7 @@ impl Plugin for EguiPlugin { ( render_systems::extract_egui_render_data_system, render_systems::extract_egui_textures_system, + init_primary_window, ), ) .add_system( @@ -523,26 +524,46 @@ impl Plugin for EguiPlugin { .add_system(render_systems::queue_pipelines_system.in_set(RenderSet::Queue)); // TODO window doesn't exist yet - let window = render_app - .world - .query_filtered::>() - .iter(&render_app.world) - .next() - .unwrap(); - - let mut render_graph = render_app.world.get_resource_mut::().unwrap(); - - setup_pipeline( - &mut render_graph, - RenderGraphConfig { - window, - egui_pass: node::EGUI_PASS, - }, - ); + // let window = render_app + // .world + // .query_filtered::>() + // .iter(&render_app.world) + // .next() + // .unwrap(); + // + // let mut render_graph = render_app.world.get_resource_mut::().unwrap(); + // + // setup_pipeline( + // &mut render_graph, + // RenderGraphConfig { + // window, + // egui_pass: node::EGUI_PASS, + // }, + // ); } } } +fn init_primary_window( + query: Extract>, + mut render_graph: ResMut, + mut is_setup: Local, +) { + if *is_setup { + return; + } + if let Some((entity, _window)) = query.iter().next() { + setup_pipeline( + &mut render_graph, + RenderGraphConfig { + window: entity, + egui_pass: node::EGUI_PASS, + }, + ); + *is_setup = true; + } +} + /// Contains textures allocated and painted by Egui. #[derive(Resource, Deref, DerefMut, Default)] pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>); @@ -664,7 +685,7 @@ pub fn setup_pipeline(render_graph: &mut RenderGraph, config: RenderGraphConfig) config.egui_pass, ); - let _ = render_graph.add_node_edge("ui_pass_driver", config.egui_pass); + //let _ = render_graph.add_node_edge("ui_pass_driver", config.egui_pass); } #[cfg(test)] From 3ae15f99f5fd9146950faadf69cd7bbd12be5a8c Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Sun, 5 Mar 2023 11:05:13 +0100 Subject: [PATCH 03/13] Fix build error from missing add_systems_to_schedule --- src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 582373573..c826e4d4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,8 +74,8 @@ use bevy::{ input::InputSystem, log, prelude::{ - CoreSet, Deref, DerefMut, Entity, IntoSystemConfig, Local, Query, Resource, Shader, - StartupSet, SystemSet, + CoreSet, Deref, DerefMut, Entity, IntoSystemAppConfigs, IntoSystemConfig, + IntoSystemConfigs, Local, Query, Resource, Shader, StartupSet, SystemSet, }, render::{ render_graph::RenderGraph, render_resource::SpecializedRenderPipelines, texture::Image, @@ -509,13 +509,14 @@ impl Plugin for EguiPlugin { .init_resource::() .init_resource::>() .init_resource::() - .add_systems_to_schedule( - ExtractSchedule, + .add_systems( ( render_systems::extract_egui_render_data_system, render_systems::extract_egui_textures_system, init_primary_window, - ), + ) + .into_configs() + .in_schedule(ExtractSchedule), ) .add_system( render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), From a581f0ac08f22ace0662bea3c8f7dd13e171f782 Mon Sep 17 00:00:00 2001 From: DGriffin91 Date: Mon, 6 Mar 2023 12:30:29 -0800 Subject: [PATCH 04/13] depend on bevy 0.10 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4c0864f6..49255b8d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ open_url = ["webbrowser"] default_fonts = ["egui/default_fonts"] [dependencies] -bevy = { git = "https://github.com/bevyengine/bevy", default-features = false, features = ["bevy_render", "bevy_core_pipeline", "bevy_asset"] } +bevy = { version = "0.10", default-features = false, features = ["bevy_render", "bevy_core_pipeline", "bevy_asset"] } egui = { version = "0.20.0", default-features = false, features = ["bytemuck"] } webbrowser = { version = "0.8.2", optional = true } @@ -32,7 +32,7 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] once_cell = "1.16.0" version-sync = "0.9.4" -bevy = { git = "https://github.com/bevyengine/bevy", default-features = false, features = [ +bevy = { version = "0.10", default-features = false, features = [ "x11", "png", "bevy_pbr", From b5c9b3e213e80e698beb84c84a67f6f09d5e82a8 Mon Sep 17 00:00:00 2001 From: mvlabat Date: Mon, 6 Mar 2023 23:44:08 +0100 Subject: [PATCH 05/13] Merge the main branch --- CHANGELOG.md | 6 ++ Cargo.toml | 3 +- examples/two_windows.rs | 37 +-------- src/lib.rs | 171 +++++++++++++--------------------------- src/render_systems.rs | 25 +++++- src/systems.rs | 5 +- 6 files changed, 92 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e75a5241a..c620fda97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Update Egui to 0.21 + ## [0.19.0] - 15-Jan-2023 ### Added diff --git a/Cargo.toml b/Cargo.toml index 49255b8d1..4bc309c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,11 @@ immutable_ctx = [] manage_clipboard = ["arboard", "thread_local"] open_url = ["webbrowser"] default_fonts = ["egui/default_fonts"] +serde = ["egui/serde"] [dependencies] bevy = { version = "0.10", default-features = false, features = ["bevy_render", "bevy_core_pipeline", "bevy_asset"] } -egui = { version = "0.20.0", default-features = false, features = ["bytemuck"] } +egui = { version = "0.21.0", default-features = false, features = ["bytemuck"] } webbrowser = { version = "0.8.2", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/two_windows.rs b/examples/two_windows.rs index f9cb18a88..639da8f60 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -1,13 +1,10 @@ use bevy::{ prelude::*, - render::{camera::RenderTarget, render_graph::RenderGraph, Extract, RenderApp}, - window::{PresentMode, PrimaryWindow, WindowRef, WindowResolution}, + render::camera::RenderTarget, + window::{PresentMode, WindowRef, WindowResolution}, }; use bevy_egui::{EguiContext, EguiPlugin}; -//TODO WindowId::new -//static SECOND_WINDOW_ID: Lazy = Lazy::new(WindowId::new); - #[derive(Resource)] struct Images { bevy_icon: Handle, @@ -23,41 +20,15 @@ fn main() { .add_system(ui_first_window_system) .add_system(ui_second_window_system); - let render_app = app.sub_app_mut(RenderApp); - - render_app.add_system_to_schedule(ExtractSchedule, init_second_window); - app.run(); } -fn init_second_window( - query: Extract>>, - mut render_graph: ResMut, - mut is_setup: Local, -) { - if *is_setup { - return; - } - if let Some((entity, _window)) = query.iter().next() { - bevy_egui::setup_pipeline( - &mut render_graph, - bevy_egui::RenderGraphConfig { - window: entity, - egui_pass: SECONDARY_EGUI_PASS, - }, - ); - *is_setup = true; - } -} - -const SECONDARY_EGUI_PASS: &str = "secondary_egui_pass"; - fn create_new_window_system(mut commands: Commands) { // Spawn a second window let second_window_id = commands .spawn(Window { title: "Second window".to_owned(), - resolution: WindowResolution::new(800., 600.), + resolution: WindowResolution::new(800.0, 600.0), present_mode: PresentMode::AutoVsync, ..Default::default() }) @@ -97,7 +68,7 @@ fn ui_first_window_system( images: Res, windows: Query>, ) { - let first_window = windows.iter().nth(0).unwrap(); + let first_window = windows.iter().next().unwrap(); let bevy_texture_id = egui_context.add_image(images.bevy_icon.clone_weak()); egui::Window::new("First Window").vscroll(true).show( egui_context.ctx_for_window_mut(first_window), diff --git a/src/lib.rs b/src/lib.rs index c826e4d4e..f8b49a948 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,23 +68,23 @@ use crate::{ #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] use arboard::Clipboard; use bevy::{ - app::{App, Plugin}, + app::{App, IntoSystemAppConfig, Plugin}, asset::{AssetEvent, Assets, Handle}, ecs::{event::EventReader, system::ResMut}, input::InputSystem, log, prelude::{ CoreSet, Deref, DerefMut, Entity, IntoSystemAppConfigs, IntoSystemConfig, - IntoSystemConfigs, Local, Query, Resource, Shader, StartupSet, SystemSet, + IntoSystemConfigs, Resource, Shader, StartupSet, SystemSet, }, render::{ render_graph::RenderGraph, render_resource::SpecializedRenderPipelines, texture::Image, - Extract, ExtractSchedule, RenderApp, RenderSet, + ExtractSchedule, RenderApp, RenderSet, }, utils::HashMap, - window::PrimaryWindow, }; use egui_node::EguiNode; +use std::borrow::Cow; #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] use std::cell::{RefCell, RefMut}; #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] @@ -271,8 +271,6 @@ impl EguiContext { //} /// Egui context for a specific window. - /// If you want to display UI on a non-primary window, make sure to set up the render graph by - /// calling [`setup_pipeline`]. /// /// This function is only available when the `immutable_ctx` feature is enabled. /// The preferable way is to use `ctx_for_window_mut` to avoid unpredictable blocking inside UI @@ -286,8 +284,7 @@ impl EguiContext { .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window` was called for an uninitialized context (window {}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)", window)) } - /// Fallible variant of [`EguiContext::ctx_for_window`]. Make sure to set up the render graph by - /// calling [`setup_pipeline`]. + /// Fallible variant of [`EguiContext::ctx_for_window`]. /// /// This function is only available when the `immutable_ctx` feature is enabled. /// The preferable way is to use `try_ctx_for_window_mut` to avoid unpredictable blocking inside @@ -307,8 +304,6 @@ impl EguiContext { //} /// Egui context for a specific window. - /// If you want to display UI on a non-primary window, make sure to set up the render graph by - /// calling [`setup_pipeline`]. #[must_use] #[track_caller] pub fn ctx_for_window_mut(&mut self, window: Entity) -> &egui::Context { @@ -317,8 +312,7 @@ impl EguiContext { .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window_mut` was called for an uninitialized context (window {window:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)")) } - /// Fallible variant of [`EguiContext::ctx_for_window_mut`]. Make sure to set up the render - /// graph by calling [`setup_pipeline`]. + /// Fallible variant of [`EguiContext::ctx_for_window_mut`]. #[must_use] pub fn try_ctx_for_window_mut(&mut self, window: Entity) -> Option<&egui::Context> { self.ctx.get(&window) @@ -341,8 +335,7 @@ impl EguiContext { ids.map(|id| self.ctx.get(&id).unwrap_or_else(|| panic!("`EguiContext::ctx_for_windows_mut` was called for an uninitialized context (window {id:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)"))) } - /// Fallible variant of [`EguiContext::ctx_for_windows_mut`]. Make sure to set up the render - /// graph by calling [`setup_pipeline`]. + /// Fallible variant of [`EguiContext::ctx_for_windows_mut`]. /// /// # Panics /// @@ -513,58 +506,20 @@ impl Plugin for EguiPlugin { ( render_systems::extract_egui_render_data_system, render_systems::extract_egui_textures_system, - init_primary_window, ) .into_configs() .in_schedule(ExtractSchedule), ) + .add_system(render_systems::setup_new_windows_system.in_schedule(ExtractSchedule)) .add_system( render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), ) .add_system(render_systems::queue_bind_groups_system.in_set(RenderSet::Queue)) .add_system(render_systems::queue_pipelines_system.in_set(RenderSet::Queue)); - - // TODO window doesn't exist yet - // let window = render_app - // .world - // .query_filtered::>() - // .iter(&render_app.world) - // .next() - // .unwrap(); - // - // let mut render_graph = render_app.world.get_resource_mut::().unwrap(); - // - // setup_pipeline( - // &mut render_graph, - // RenderGraphConfig { - // window, - // egui_pass: node::EGUI_PASS, - // }, - // ); } } } -fn init_primary_window( - query: Extract>, - mut render_graph: ResMut, - mut is_setup: Local, -) { - if *is_setup { - return; - } - if let Some((entity, _window)) = query.iter().next() { - setup_pipeline( - &mut render_graph, - RenderGraphConfig { - window: entity, - egui_pass: node::EGUI_PASS, - }, - ); - *is_setup = true; - } -} - /// Contains textures allocated and painted by Egui. #[derive(Resource, Deref, DerefMut, Default)] pub struct EguiManagedTextures(pub HashMap<(Entity, u64), EguiManagedTexture>); @@ -661,32 +616,22 @@ pub struct RenderGraphConfig { /// Target window. pub window: Entity, /// Render pass name. - pub egui_pass: &'static str, + pub egui_pass: Cow<'static, str>, } -//TODO WindowId::primary() -//impl Default for RenderGraphConfig { -// fn default() -> Self { -// RenderGraphConfig { -// window_id: WindowId::primary(), -// egui_pass: node::EGUI_PASS, -// } -// } -//} - /// Set up egui render pipeline. /// /// The pipeline for the primary window will already be set up by the [`EguiPlugin`], /// so you'll only need to manually call this if you want to use multiple windows. pub fn setup_pipeline(render_graph: &mut RenderGraph, config: RenderGraphConfig) { - render_graph.add_node(config.egui_pass, EguiNode::new(config.window)); + render_graph.add_node(config.egui_pass.clone(), EguiNode::new(config.window)); render_graph.add_node_edge( bevy::render::main_graph::node::CAMERA_DRIVER, - config.egui_pass, + config.egui_pass.to_string(), ); - //let _ = render_graph.add_node_edge("ui_pass_driver", config.egui_pass); + let _ = render_graph.try_add_node_edge("ui_pass_driver", config.egui_pass.to_string()); } #[cfg(test)] @@ -722,55 +667,51 @@ mod tests { .update(); } - // TODO WindowId::primary() - //#[test] - //fn test_ctx_for_windows_mut_unique_check_passes() { - // let mut egui_context = EguiContext::new(); - // let primary_window = WindowId::primary(); - // let second_window = WindowId::new(); - // egui_context.ctx.insert(primary_window, Default::default()); - // egui_context.ctx.insert(second_window, Default::default()); - // let [primary_ctx, second_ctx] = - // egui_context.ctx_for_windows_mut([primary_window, second_window]); - // assert!(primary_ctx != second_ctx); - //} + #[test] + fn test_ctx_for_windows_mut_unique_check_passes() { + let mut egui_context = EguiContext::new(); + let primary_window = Entity::from_raw(0); + let second_window = Entity::from_raw(1); + egui_context.ctx.insert(primary_window, Default::default()); + egui_context.ctx.insert(second_window, Default::default()); + let [primary_ctx, second_ctx] = + egui_context.ctx_for_windows_mut([primary_window, second_window]); + assert!(primary_ctx != second_ctx); + } - // TODO WindowId::primary() - //#[test] - //#[should_panic( - // expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - //)] - //fn test_ctx_for_windows_mut_unique_check_panics() { - // let mut egui_context = EguiContext::new(); - // let primary_window = WindowId::primary(); - // egui_context.ctx.insert(primary_window, Default::default()); - // let _ = egui_context.ctx_for_windows_mut([primary_window, primary_window]); - //} + #[test] + #[should_panic( + expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" + )] + fn test_ctx_for_windows_mut_unique_check_panics() { + let mut egui_context = EguiContext::new(); + let primary_window = Entity::from_raw(0); + egui_context.ctx.insert(primary_window, Default::default()); + let _ = egui_context.ctx_for_windows_mut([primary_window, primary_window]); + } - //TODO WindowId::primary() - //#[test] - //fn test_try_ctx_for_windows_mut_unique_check_passes() { - // let mut egui_context = EguiContext::new(); - // let primary_window = WindowId::primary(); - // let second_window = WindowId::new(); - // egui_context.ctx.insert(primary_window, Default::default()); - // egui_context.ctx.insert(second_window, Default::default()); - // let [primary_ctx, second_ctx] = - // egui_context.try_ctx_for_windows_mut([primary_window, second_window]); - // assert!(primary_ctx.is_some()); - // assert!(second_ctx.is_some()); - // assert!(primary_ctx != second_ctx); - //} + #[test] + fn test_try_ctx_for_windows_mut_unique_check_passes() { + let mut egui_context = EguiContext::new(); + let primary_window = Entity::from_raw(0); + let second_window = Entity::from_raw(1); + egui_context.ctx.insert(primary_window, Default::default()); + egui_context.ctx.insert(second_window, Default::default()); + let [primary_ctx, second_ctx] = + egui_context.try_ctx_for_windows_mut([primary_window, second_window]); + assert!(primary_ctx.is_some()); + assert!(second_ctx.is_some()); + assert!(primary_ctx != second_ctx); + } - //TODO WindowId::primary() - //#[test] - //#[should_panic( - // expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - //)] - //fn test_try_ctx_for_windows_mut_unique_check_panics() { - // let mut egui_context = EguiContext::new(); - // let primary_window = WindowId::primary(); - // egui_context.ctx.insert(primary_window, Default::default()); - // let _ = egui_context.try_ctx_for_windows_mut([primary_window, primary_window]); - //} + #[test] + #[should_panic( + expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" + )] + fn test_try_ctx_for_windows_mut_unique_check_panics() { + let mut egui_context = EguiContext::new(); + let primary_window = Entity::from_raw(0); + egui_context.ctx.insert(primary_window, Default::default()); + let _ = egui_context.try_ctx_for_windows_mut([primary_window, primary_window]); + } } diff --git a/src/render_systems.rs b/src/render_systems.rs index 3a921de67..3e7d5d80b 100644 --- a/src/render_systems.rs +++ b/src/render_systems.rs @@ -1,13 +1,14 @@ use crate::{ egui_node::{EguiPipeline, EguiPipelineKey}, - EguiContext, EguiManagedTextures, EguiRenderOutput, EguiRenderOutputContainer, EguiSettings, - EguiWindowSizeContainer, WindowSize, + setup_pipeline, EguiContext, EguiManagedTextures, EguiRenderOutput, EguiRenderOutputContainer, + EguiSettings, EguiWindowSizeContainer, RenderGraphConfig, WindowSize, }; use bevy::{ asset::HandleId, prelude::*, render::{ render_asset::RenderAssets, + render_graph::RenderGraph, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, CachedRenderPipelineId, DynamicUniformBuffer, PipelineCache, ShaderType, @@ -71,6 +72,26 @@ impl ExtractedEguiTextures { } } +/// Calls [`setup_pipeline`] for newly created windows to ensure egui works on them. +pub fn setup_new_windows_system( + mut graph: ResMut, + new_windows: Extract>>, +) { + for window in new_windows.iter() { + setup_pipeline( + &mut graph, + RenderGraphConfig { + window, + egui_pass: std::borrow::Cow::Owned(format!( + "egui-{}-{}", + window.index(), + window.generation() + )), + }, + ) + } +} + /// Extracts Egui context, render output, settings and application window sizes. pub fn extract_egui_render_data_system( mut commands: Commands, diff --git a/src/systems.rs b/src/systems.rs index 213f1c3c5..5417f2a5e 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -225,10 +225,6 @@ pub fn process_input_system( delta *= 50.0; } - // Winit has inverted hscroll. - // TODO: remove this line when Bevy updates winit after https://github.com/rust-windowing/winit/pull/2105 is merged and released. - delta.x *= -1.0; - if ctrl || mac_cmd { // Treat as zoom instead. let factor = (delta.y / 200.0).exp(); @@ -271,6 +267,7 @@ pub fn process_input_system( let egui_event = egui::Event::Key { key, pressed, + repeat: false, modifiers, }; focused_input.events.push(egui_event); From 6dad888c1de45f68f9e3e176e91dd0d728250ec8 Mon Sep 17 00:00:00 2001 From: DGriffin91 Date: Mon, 6 Mar 2023 17:06:34 -0800 Subject: [PATCH 06/13] make egui::Context a component --- examples/render_to_image_widget.rs | 12 +- examples/side_panel.rs | 5 +- examples/simple.rs | 11 +- examples/two_windows.rs | 31 ++-- examples/ui.rs | 37 +++-- src/egui_node.rs | 25 +++- src/lib.rs | 224 ++++++++--------------------- src/render_systems.rs | 55 ++++--- src/systems.rs | 45 +++--- 9 files changed, 160 insertions(+), 285 deletions(-) diff --git a/examples/render_to_image_widget.rs b/examples/render_to_image_widget.rs index cf69fb6b7..d629c0a82 100644 --- a/examples/render_to_image_widget.rs +++ b/examples/render_to_image_widget.rs @@ -9,7 +9,7 @@ use bevy::{ view::RenderLayers, }, }; -use bevy_egui::{egui, EguiContext, EguiPlugin}; +use bevy_egui::{egui, EguiContext, EguiPlugin, EguiUserTextures}; use egui::Widget; fn main() { @@ -34,7 +34,7 @@ struct MainPassCube; struct CubePreviewImage(Handle); fn setup( - mut egui_ctx: ResMut, + mut egui_ctx: ResMut, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, @@ -146,18 +146,18 @@ fn setup( } fn render_to_image_example_system( - mut egui_ctx: ResMut, + egui_user_textures: Res, cube_preview_image: Res, preview_cube_query: Query<&Handle, With>, main_cube_query: Query<&Handle, With>, mut materials: ResMut>, - windows: Query>, + egui_ctx: Query<&EguiContext, With>, ) { - let cube_preview_texture_id = egui_ctx.image_id(&cube_preview_image).unwrap(); + let cube_preview_texture_id = egui_user_textures.image_id(&cube_preview_image).unwrap(); let preview_material_handle = preview_cube_query.single(); let preview_material = materials.get_mut(preview_material_handle).unwrap(); - let ctx = egui_ctx.ctx_for_window_mut(windows.iter().next().unwrap()); + let ctx = egui_ctx.iter().next().unwrap(); let mut apply = false; egui::Window::new("Cube material preview").show(ctx, |ui| { ui.image(cube_preview_texture_id, [300.0, 300.0]); diff --git a/examples/side_panel.rs b/examples/side_panel.rs index ca53bcd4e..4c071b55b 100644 --- a/examples/side_panel.rs +++ b/examples/side_panel.rs @@ -26,11 +26,10 @@ fn main() { } fn ui_example_system( - mut egui_context: ResMut, + egui_ctx: Query<&EguiContext>, mut occupied_screen_space: ResMut, - windows: Query>, ) { - let ctx = egui_context.ctx_for_window_mut(windows.iter().next().unwrap()); + let ctx = egui_ctx.iter().next().unwrap(); occupied_screen_space.left = egui::SidePanel::left("left_panel") .resizable(true) diff --git a/examples/simple.rs b/examples/simple.rs index 20d1171a6..fb688a7e2 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -11,11 +11,8 @@ fn main() { .run(); } -fn ui_example_system(mut egui_context: ResMut, windows: Query>) { - egui::Window::new("Hello").show( - egui_context.ctx_for_window_mut(windows.iter().next().unwrap()), - |ui| { - ui.label("world"); - }, - ); +fn ui_example_system(egui_ctx: Query<&EguiContext>) { + egui::Window::new("Hello").show(egui_ctx.iter().next().unwrap(), |ui| { + ui.label("world"); + }); } diff --git a/examples/two_windows.rs b/examples/two_windows.rs index 639da8f60..5d871bb99 100644 --- a/examples/two_windows.rs +++ b/examples/two_windows.rs @@ -3,7 +3,7 @@ use bevy::{ render::camera::RenderTarget, window::{PresentMode, WindowRef, WindowResolution}, }; -use bevy_egui::{EguiContext, EguiPlugin}; +use bevy_egui::{EguiContext, EguiPlugin, EguiUserTextures}; #[derive(Resource)] struct Images { @@ -62,17 +62,16 @@ struct SharedUiState { } fn ui_first_window_system( - mut egui_context: ResMut, + mut egui_user_textures: ResMut, mut ui_state: Local, mut shared_ui_state: ResMut, images: Res, - windows: Query>, + egui_ctx: Query<&EguiContext>, ) { - let first_window = windows.iter().next().unwrap(); - let bevy_texture_id = egui_context.add_image(images.bevy_icon.clone_weak()); - egui::Window::new("First Window").vscroll(true).show( - egui_context.ctx_for_window_mut(first_window), - |ui| { + let bevy_texture_id = egui_user_textures.add_image(images.bevy_icon.clone_weak()); + egui::Window::new("First Window") + .vscroll(true) + .show(egui_ctx.iter().next().unwrap(), |ui| { ui.horizontal(|ui| { ui.label("Write something: "); ui.text_edit_singleline(&mut ui_state.input); @@ -83,22 +82,22 @@ fn ui_first_window_system( }); ui.add(egui::widgets::Image::new(bevy_texture_id, [256.0, 256.0])); - }, - ); + }); } fn ui_second_window_system( - mut egui_context: ResMut, + mut egui_user_textures: ResMut, mut ui_state: Local, mut shared_ui_state: ResMut, images: Res, - windows: Query>, + egui_ctx: Query<&EguiContext>, ) { - let second_window = windows.iter().nth(1).unwrap(); - let bevy_texture_id = egui_context.add_image(images.bevy_icon.clone_weak()); - let ctx = match egui_context.try_ctx_for_window_mut(second_window) { + let bevy_texture_id = egui_user_textures.add_image(images.bevy_icon.clone_weak()); + let ctx = match egui_ctx.iter().nth(1) { Some(ctx) => ctx, - None => return, + None => { + return; + } }; egui::Window::new("Second Window") .vscroll(true) diff --git a/examples/ui.rs b/examples/ui.rs index cf4cc7b66..f8c1db137 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -1,5 +1,5 @@ use bevy::prelude::*; -use bevy_egui::{egui, EguiContext, EguiPlugin, EguiSettings}; +use bevy_egui::{egui, EguiContext, EguiPlugin, EguiSettings, EguiUserTextures}; struct Images { bevy_icon: Handle, @@ -43,16 +43,11 @@ struct UiState { is_window_open: bool, } -fn configure_visuals_system( - mut egui_ctx: ResMut, - windows: Query>, -) { - egui_ctx - .ctx_for_window_mut(windows.iter().next().unwrap()) - .set_visuals(egui::Visuals { - window_rounding: 0.0.into(), - ..Default::default() - }); +fn configure_visuals_system(egui_ctx: Query<&EguiContext>) { + egui_ctx.iter().next().unwrap().set_visuals(egui::Visuals { + window_rounding: 0.0.into(), + ..Default::default() + }); } fn configure_ui_state_system(mut ui_state: ResMut) { @@ -80,7 +75,7 @@ fn update_ui_scale_factor_system( } fn ui_example_system( - mut egui_ctx: ResMut, + mut egui_user_textures: ResMut, mut ui_state: ResMut, // You are not required to store Egui texture ids in systems. We store this one here just to // demonstrate that rendering by using a texture id of a removed image is handled without @@ -90,10 +85,9 @@ fn ui_example_system( // If you need to access the ids from multiple systems, you can also initialize the `Images` // resource while building the app and use `Res` instead. images: Local, - windows: Query>, + egui_ctx: Query<&EguiContext>, ) { - let primary_window = windows.iter().next().unwrap(); - let ctx = egui_ctx.ctx_for_window_mut(primary_window); + let ctx = egui_ctx.iter().next().unwrap(); let egui_texture_handle = ui_state .egui_texture_handle .get_or_insert_with(|| { @@ -111,10 +105,10 @@ fn ui_example_system( if !*is_initialized { *is_initialized = true; - *rendered_texture_id = egui_ctx.add_image(images.bevy_icon.clone_weak()); + *rendered_texture_id = egui_user_textures.add_image(images.bevy_icon.clone_weak()); } - let ctx = egui_ctx.ctx_for_window_mut(primary_window); + let ctx = egui_ctx.iter().next().unwrap(); egui::SidePanel::left("side_panel") .default_width(200.0) @@ -208,14 +202,15 @@ fn ui_example_system( if load || invert { // If an image is already added to the context, it'll return an existing texture id. if ui_state.inverted { - *rendered_texture_id = egui_ctx.add_image(images.bevy_icon_inverted.clone_weak()); + *rendered_texture_id = + egui_user_textures.add_image(images.bevy_icon_inverted.clone_weak()); } else { - *rendered_texture_id = egui_ctx.add_image(images.bevy_icon.clone_weak()); + *rendered_texture_id = egui_user_textures.add_image(images.bevy_icon.clone_weak()); }; } if remove { - egui_ctx.remove_image(&images.bevy_icon); - egui_ctx.remove_image(&images.bevy_icon_inverted); + egui_user_textures.remove_image(&images.bevy_icon); + egui_user_textures.remove_image(&images.bevy_icon_inverted); } } diff --git a/src/egui_node.rs b/src/egui_node.rs index 4d6172028..ac90a993d 100644 --- a/src/egui_node.rs +++ b/src/egui_node.rs @@ -1,11 +1,14 @@ -use crate::render_systems::{ - EguiPipelines, EguiTextureBindGroups, EguiTextureId, EguiTransform, EguiTransforms, - ExtractedEguiContext, ExtractedEguiSettings, ExtractedRenderOutput, ExtractedWindowSizes, +use crate::{ + render_systems::{ + EguiPipelines, EguiTextureBindGroups, EguiTextureId, EguiTransform, EguiTransforms, + ExtractedEguiSettings, ExtractedRenderOutput, ExtractedWindowSizes, + }, + EguiContext, }; use bevy::{ core::cast_slice, ecs::world::{FromWorld, World}, - prelude::{Entity, HandleUntyped, Resource}, + prelude::{Entity, HandleUntyped, QueryState, Resource}, reflect::TypeUuid, render::{ render_graph::{Node, NodeRunError, RenderGraphContext}, @@ -171,11 +174,12 @@ pub struct EguiNode { index_buffer_capacity: usize, index_buffer: Option, draw_commands: Vec, + query: QueryState<&'static EguiContext>, } impl EguiNode { /// Constructs Egui render node. - pub fn new(window_entity: Entity) -> Self { + pub fn new(world: &mut World, window_entity: Entity) -> Self { EguiNode { window_entity, draw_commands: Vec::new(), @@ -185,6 +189,7 @@ impl EguiNode { index_data: Vec::new(), index_buffer_capacity: 0, index_buffer: None, + query: world.query_filtered(), } } } @@ -201,7 +206,6 @@ impl Node for EguiNode { let window_size = &world.get_resource::().unwrap()[&self.window_entity]; let egui_settings = &world.get_resource::().unwrap(); - let egui_context = &world.get_resource::().unwrap(); let render_device = world.get_resource::().unwrap(); @@ -210,7 +214,14 @@ impl Node for EguiNode { return; } - let egui_paint_jobs = egui_context[&self.window_entity].tessellate(shapes); + let Ok( + egui_context, + ) = self.query.get_manual(world, self.window_entity) else { + // No egui context + return; + }; + + let egui_paint_jobs = egui_context.tessellate(shapes); let mut index_offset = 0; diff --git a/src/lib.rs b/src/lib.rs index f8b49a948..bec23fbf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,8 +33,8 @@ //! .run(); //! } //! -//! fn ui_example_system(mut egui_context: ResMut) { -//! egui::Window::new("Hello").show(egui_context.ctx_mut(), |ui| { +//! fn ui_example_system(egui_context: Query<&EguiContext>) { +//! egui::Window::new("Hello").show(egui_context.iter().next().unwrap(), |ui| { //! ui.label("world"); //! }); //! } @@ -68,20 +68,21 @@ use crate::{ #[cfg(all(feature = "manage_clipboard", not(target_arch = "wasm32")))] use arboard::Clipboard; use bevy::{ - app::{App, IntoSystemAppConfig, Plugin}, + app::{App, Plugin}, asset::{AssetEvent, Assets, Handle}, ecs::{event::EventReader, system::ResMut}, input::InputSystem, log, prelude::{ - CoreSet, Deref, DerefMut, Entity, IntoSystemAppConfigs, IntoSystemConfig, - IntoSystemConfigs, Resource, Shader, StartupSet, SystemSet, + Added, Commands, Component, CoreSet, Deref, DerefMut, Entity, IntoSystemAppConfigs, + IntoSystemConfig, IntoSystemConfigs, Query, Resource, Shader, StartupSet, SystemSet, World, }, render::{ render_graph::RenderGraph, render_resource::SpecializedRenderPipelines, texture::Image, ExtractSchedule, RenderApp, RenderSet, }, utils::HashMap, + window::Window, }; use egui_node::EguiNode; use std::borrow::Cow; @@ -103,8 +104,8 @@ pub struct EguiSettings { /// use bevy::prelude::*; /// use bevy_egui::EguiSettings; /// - /// fn update_ui_scale_factor(mut egui_settings: ResMut, windows: Res) { - /// if let Some(window) = windows.get_primary() { + /// fn update_ui_scale_factor(mut egui_settings: ResMut, windows: Query<&Window>) { + /// if let Some(window) = windows.iter().next() { /// egui_settings.scale_factor = 1.0 / window.scale_factor(); /// } /// } @@ -239,120 +240,29 @@ pub struct EguiOutput { pub platform_output: egui::PlatformOutput, } -/// A resource for storing `bevy_egui` context. +/// A component for storing `bevy_egui` context. +#[derive(Clone, Component, Default, Deref, DerefMut)] +pub struct EguiContext(pub egui::Context); + +/// A resource for storing `bevy_egui` mouse position. +#[derive(Resource, Component, Default, Deref, DerefMut)] +pub struct EguiMousePosition(pub Option<(Entity, egui::Vec2)>); + +/// A resource for storing `bevy_egui` user textures. #[derive(Clone, Resource)] -pub struct EguiContext { - ctx: HashMap, - user_textures: HashMap, u64>, +pub struct EguiUserTextures { + textures: HashMap, u64>, last_texture_id: u64, - mouse_position: Option<(Entity, egui::Vec2)>, } -impl EguiContext { +impl EguiUserTextures { fn new() -> Self { Self { - ctx: HashMap::default(), - user_textures: Default::default(), + textures: Default::default(), last_texture_id: 0, - mouse_position: None, } } - // TODO WindowId::primary() - ///// Egui context of the primary window. - ///// - ///// This function is only available when the `immutable_ctx` feature is enabled. - ///// The preferable way is to use `ctx_mut` to avoid unpredictable blocking inside UI systems. - //#[cfg(feature = "immutable_ctx")] - //#[must_use] - //#[track_caller] - //pub fn ctx(&self) -> &egui::Context { - // self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") - //} - - /// Egui context for a specific window. - /// - /// This function is only available when the `immutable_ctx` feature is enabled. - /// The preferable way is to use `ctx_for_window_mut` to avoid unpredictable blocking inside UI - /// systems. - #[cfg(feature = "immutable_ctx")] - #[must_use] - #[track_caller] - pub fn ctx_for_window(&self, window: WindowId) -> &egui::Context { - self.ctx - .get(&window) - .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window` was called for an uninitialized context (window {}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)", window)) - } - - /// Fallible variant of [`EguiContext::ctx_for_window`]. - /// - /// This function is only available when the `immutable_ctx` feature is enabled. - /// The preferable way is to use `try_ctx_for_window_mut` to avoid unpredictable blocking inside - /// UI systems. - #[cfg(feature = "immutable_ctx")] - #[must_use] - pub fn try_ctx_for_window(&self, window: WindowId) -> Option<&egui::Context> { - self.ctx.get(&window) - } - - //TODO WindowId::primary() - ///// Egui context of the primary window. - //#[must_use] - //#[track_caller] - //pub fn ctx_mut(&mut self) -> &egui::Context { - // self.ctx.get(&WindowId::primary()).expect("`EguiContext::ctx_mut` was called for an uninitialized context (primary window), consider moving your startup system to the `StartupStage::Startup` stage or run it after the `EguiStartupSystem::InitContexts` system") - //} - - /// Egui context for a specific window. - #[must_use] - #[track_caller] - pub fn ctx_for_window_mut(&mut self, window: Entity) -> &egui::Context { - self.ctx - .get(&window) - .unwrap_or_else(|| panic!("`EguiContext::ctx_for_window_mut` was called for an uninitialized context (window {window:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)")) - } - - /// Fallible variant of [`EguiContext::ctx_for_window_mut`]. - #[must_use] - pub fn try_ctx_for_window_mut(&mut self, window: Entity) -> Option<&egui::Context> { - self.ctx.get(&window) - } - - /// Allows to get multiple contexts at the same time. This function is useful when you want - /// to get multiple window contexts without using the `immutable_ctx` feature. - /// - /// # Panics - /// - /// Panics if the passed window ids aren't unique. - #[must_use] - #[track_caller] - pub fn ctx_for_windows_mut(&mut self, ids: [Entity; N]) -> [&egui::Context; N] { - let mut unique_ids = bevy::utils::HashSet::default(); - assert!( - ids.iter().all(move |id| unique_ids.insert(id)), - "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique: {ids:?}", - ); - ids.map(|id| self.ctx.get(&id).unwrap_or_else(|| panic!("`EguiContext::ctx_for_windows_mut` was called for an uninitialized context (window {id:?}), consider moving your UI system to the `CoreStage::Update` stage or run it after the `EguiSystem::BeginFrame` system (`StartupStage::Startup` or `EguiStartupSystem::InitContexts` for startup systems respectively)"))) - } - - /// Fallible variant of [`EguiContext::ctx_for_windows_mut`]. - /// - /// # Panics - /// - /// Panics if the passed window ids aren't unique. - #[must_use] - pub fn try_ctx_for_windows_mut( - &mut self, - ids: [Entity; N], - ) -> [Option<&egui::Context>; N] { - let mut unique_ids = bevy::utils::HashSet::default(); - assert!( - ids.iter().all(move |id| unique_ids.insert(id)), - "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique: {ids:?}", - ); - ids.map(|id| self.ctx.get(&id)) - } - /// Can accept either a strong or a weak handle. /// /// You may want to pass a weak handle if you control removing texture assets in your @@ -361,7 +271,7 @@ impl EguiContext { /// You'll want to pass a strong handle if a texture is used only in Egui and there are no /// handle copies stored anywhere else. pub fn add_image(&mut self, image: Handle) -> egui::TextureId { - let id = *self.user_textures.entry(image.clone()).or_insert_with(|| { + let id = *self.textures.entry(image.clone()).or_insert_with(|| { let id = self.last_texture_id; log::debug!("Add a new image (id: {}, handle: {:?})", id, image); self.last_texture_id += 1; @@ -372,7 +282,7 @@ impl EguiContext { /// Removes the image handle and an Egui texture id associated with it. pub fn remove_image(&mut self, image: &Handle) -> Option { - let id = self.user_textures.remove(image); + let id = self.textures.remove(image); log::debug!("Remove image (id: {:?}, handle: {:?})", id, image); id.map(egui::TextureId::User) } @@ -380,7 +290,7 @@ impl EguiContext { /// Returns an associated Egui texture id. #[must_use] pub fn image_id(&self, image: &Handle) -> Option { - self.user_textures + self.textures .get(image) .map(|&id| egui::TextureId::User(id)) } @@ -459,7 +369,8 @@ impl Plugin for EguiPlugin { world.insert_resource(EguiManagedTextures::default()); #[cfg(feature = "manage_clipboard")] world.insert_resource(EguiClipboard::default()); - world.insert_resource(EguiContext::new()); + world.insert_resource(EguiUserTextures::new()); + world.insert_resource(EguiMousePosition::default()); app.add_startup_system( init_contexts_startup_system @@ -467,6 +378,19 @@ impl Plugin for EguiPlugin { .in_base_set(StartupSet::PreStartup), ); + // TODO where is the correct place for this? + // Probably shouldn't need both add_startup_system & add_system version. + app.add_startup_system( + setup_new_windows_system + .in_set(EguiStartupSystem::InitContexts) + .in_base_set(StartupSet::PreStartup), + ); + app.add_system( + setup_new_windows_system + .in_set(EguiStartupSystem::InitContexts) + .in_base_set(StartupSet::PreStartup), + ); + app.add_system( process_input_system .in_set(EguiSystem::ProcessInput) @@ -506,11 +430,11 @@ impl Plugin for EguiPlugin { ( render_systems::extract_egui_render_data_system, render_systems::extract_egui_textures_system, + render_systems::setup_new_windows_render_system, ) .into_configs() .in_schedule(ExtractSchedule), ) - .add_system(render_systems::setup_new_windows_system.in_schedule(ExtractSchedule)) .add_system( render_systems::prepare_egui_transforms_system.in_set(RenderSet::Prepare), ) @@ -532,6 +456,15 @@ pub struct EguiManagedTexture { pub color_image: egui::ColorImage, } +/// Adds bevy_egui components to newly created windows. +pub fn setup_new_windows_system(mut commands: Commands, new_windows: Query>) { + for window in new_windows.iter() { + commands + .entity(window) + .insert((EguiContext::default(), EguiMousePosition::default())); + } +} + /// Updates textures painted by Egui. pub fn update_egui_textures_system( mut egui_render_output: ResMut, @@ -586,7 +519,7 @@ pub fn update_egui_textures_system( } fn free_egui_textures_system( - mut egui_context: ResMut, + mut egui_user_textures: ResMut, mut egui_render_output: ResMut, mut egui_managed_textures: ResMut, mut image_assets: ResMut>, @@ -606,7 +539,7 @@ fn free_egui_textures_system( for image_event in image_events.iter() { if let AssetEvent::Removed { handle } = image_event { - egui_context.remove_image(handle); + egui_user_textures.remove_image(handle); } } } @@ -623,8 +556,11 @@ pub struct RenderGraphConfig { /// /// The pipeline for the primary window will already be set up by the [`EguiPlugin`], /// so you'll only need to manually call this if you want to use multiple windows. -pub fn setup_pipeline(render_graph: &mut RenderGraph, config: RenderGraphConfig) { - render_graph.add_node(config.egui_pass.clone(), EguiNode::new(config.window)); +pub fn setup_pipeline(world: &mut World, config: RenderGraphConfig) { + let new_node = EguiNode::new(world, config.window); + let mut render_graph = world.resource_mut::(); + + render_graph.add_node(config.egui_pass.clone(), new_node); render_graph.add_node_edge( bevy::render::main_graph::node::CAMERA_DRIVER, @@ -666,52 +602,4 @@ mod tests { .add_plugin(EguiPlugin) .update(); } - - #[test] - fn test_ctx_for_windows_mut_unique_check_passes() { - let mut egui_context = EguiContext::new(); - let primary_window = Entity::from_raw(0); - let second_window = Entity::from_raw(1); - egui_context.ctx.insert(primary_window, Default::default()); - egui_context.ctx.insert(second_window, Default::default()); - let [primary_ctx, second_ctx] = - egui_context.ctx_for_windows_mut([primary_window, second_window]); - assert!(primary_ctx != second_ctx); - } - - #[test] - #[should_panic( - expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - )] - fn test_ctx_for_windows_mut_unique_check_panics() { - let mut egui_context = EguiContext::new(); - let primary_window = Entity::from_raw(0); - egui_context.ctx.insert(primary_window, Default::default()); - let _ = egui_context.ctx_for_windows_mut([primary_window, primary_window]); - } - - #[test] - fn test_try_ctx_for_windows_mut_unique_check_passes() { - let mut egui_context = EguiContext::new(); - let primary_window = Entity::from_raw(0); - let second_window = Entity::from_raw(1); - egui_context.ctx.insert(primary_window, Default::default()); - egui_context.ctx.insert(second_window, Default::default()); - let [primary_ctx, second_ctx] = - egui_context.try_ctx_for_windows_mut([primary_window, second_window]); - assert!(primary_ctx.is_some()); - assert!(second_ctx.is_some()); - assert!(primary_ctx != second_ctx); - } - - #[test] - #[should_panic( - expected = "Window ids passed to `EguiContext::ctx_for_windows_mut` must be unique" - )] - fn test_try_ctx_for_windows_mut_unique_check_panics() { - let mut egui_context = EguiContext::new(); - let primary_window = Entity::from_raw(0); - egui_context.ctx.insert(primary_window, Default::default()); - let _ = egui_context.try_ctx_for_windows_mut([primary_window, primary_window]); - } } diff --git a/src/render_systems.rs b/src/render_systems.rs index 3e7d5d80b..2b22a590f 100644 --- a/src/render_systems.rs +++ b/src/render_systems.rs @@ -1,14 +1,14 @@ use crate::{ egui_node::{EguiPipeline, EguiPipelineKey}, setup_pipeline, EguiContext, EguiManagedTextures, EguiRenderOutput, EguiRenderOutputContainer, - EguiSettings, EguiWindowSizeContainer, RenderGraphConfig, WindowSize, + EguiSettings, EguiUserTextures, EguiWindowSizeContainer, RenderGraphConfig, WindowSize, }; use bevy::{ asset::HandleId, + ecs::system::SystemState, prelude::*, render::{ render_asset::RenderAssets, - render_graph::RenderGraph, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, CachedRenderPipelineId, DynamicUniformBuffer, PipelineCache, ShaderType, @@ -34,10 +34,6 @@ pub struct ExtractedWindowSizes(pub HashMap); #[derive(Resource, Deref, DerefMut, Default)] pub struct ExtractedEguiSettings(pub EguiSettings); -/// Extracted Egui contexts. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct ExtractedEguiContext(pub HashMap); - /// Corresponds to Egui's [`egui::TextureId`]. #[derive(Debug, PartialEq, Eq, Hash)] pub enum EguiTextureId { @@ -73,22 +69,23 @@ impl ExtractedEguiTextures { } /// Calls [`setup_pipeline`] for newly created windows to ensure egui works on them. -pub fn setup_new_windows_system( - mut graph: ResMut, - new_windows: Extract>>, -) { - for window in new_windows.iter() { - setup_pipeline( - &mut graph, - RenderGraphConfig { - window, - egui_pass: std::borrow::Cow::Owned(format!( - "egui-{}-{}", - window.index(), - window.generation() - )), - }, - ) +pub fn setup_new_windows_render_system(world: &mut World) { + let mut system_state: SystemState>>> = + SystemState::new(world); + let mut new_render_configs = Vec::new(); + for window in system_state.get_mut(world).iter() { + // TODO This is running every frame for some reason + new_render_configs.push(RenderGraphConfig { + window, + egui_pass: std::borrow::Cow::Owned(format!( + "egui-{}-{}", + window.index(), + window.generation() + )), + }); + } + for render_config in new_render_configs { + setup_pipeline(world, render_config); } } @@ -98,18 +95,20 @@ pub fn extract_egui_render_data_system( egui_render_output: Extract>, window_sizes: Extract>, egui_settings: Extract>, - egui_context: Extract>, + egui_context: Extract>, ) { commands.insert_resource(ExtractedRenderOutput(egui_render_output.clone())); commands.insert_resource(ExtractedEguiSettings(egui_settings.clone())); - commands.insert_resource(ExtractedEguiContext(egui_context.ctx.clone())); commands.insert_resource(ExtractedWindowSizes(window_sizes.clone())); + for (window_entity, ctx) in egui_context.iter() { + commands.get_or_spawn(window_entity).insert(ctx.clone()); + } } /// Extracts Egui textures. pub fn extract_egui_textures_system( mut commands: Commands, - egui_context: Extract>, + egui_user_textures: Extract>, egui_managed_textures: Extract>, ) { commands.insert_resource(ExtractedEguiTextures { @@ -119,7 +118,7 @@ pub fn extract_egui_textures_system( ((window_id, texture_id), managed_texture.handle.clone()) }) .collect(), - user_textures: egui_context.user_textures.clone(), + user_textures: egui_user_textures.textures.clone(), }); } @@ -245,7 +244,7 @@ pub struct EguiPipelines(pub HashMap); /// Queue [`EguiPipeline`]s specialized on each window's swap chain texture format. pub fn queue_pipelines_system( mut commands: Commands, - mut pipeline_cache: ResMut, + pipeline_cache: Res, mut pipelines: ResMut>, egui_pipeline: Res, windows: Res, @@ -256,7 +255,7 @@ pub fn queue_pipelines_system( let key = EguiPipelineKey { texture_format: window.swap_chain_texture_format?, }; - let pipeline_id = pipelines.specialize(&mut pipeline_cache, &egui_pipeline, key); + let pipeline_id = pipelines.specialize(&pipeline_cache, &egui_pipeline, key); Some((*window_id, pipeline_id)) }) diff --git a/src/systems.rs b/src/systems.rs index 5417f2a5e..20d027bf9 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,5 +1,5 @@ use crate::{ - EguiContext, EguiInput, EguiOutputContainer, EguiRenderInputContainer, + EguiContext, EguiInput, EguiMousePosition, EguiOutputContainer, EguiRenderInputContainer, EguiRenderOutputContainer, EguiSettings, EguiWindowSizeContainer, WindowSize, }; #[cfg(feature = "open_url")] @@ -75,26 +75,20 @@ pub struct WindowResources<'w, 's> { /// Initialises Egui contexts (for multiple windows) on startup. pub fn init_contexts_startup_system( - mut egui_context: ResMut, mut egui_input: ResMut, mut window_resources: WindowResources, egui_settings: Res, ) { - update_window_contexts( - &mut egui_context, - &mut egui_input.0, - &mut window_resources, - &egui_settings, - ); + update_window_contexts(&mut egui_input.0, &mut window_resources, &egui_settings); } /// Processes Bevy input and feeds it to Egui. pub fn process_input_system( - mut egui_context: ResMut, mut input_events: InputEvents, mut input_resources: InputResources, mut window_resources: WindowResources, egui_settings: Res, + mut egui_mouse_position: ResMut, time: Res